@ai.weget.jp/bot 0.1.10 → 0.1.12

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 CHANGED
@@ -43,7 +43,7 @@ Default built-in endpoints:
43
43
  - `botUploadApiUrl=https://api.weget.jp/bot/upload-url`
44
44
  - `gmoCryptoPrivateApiBaseUrl=https://api.coin.z.com/private`
45
45
  - `gmoFxPrivateApiBaseUrl=https://forex-api.coin.z.com/private`
46
- - `aiModel=gpt-4.1-mini`
46
+ - `aiModel=gpt-5.4`
47
47
 
48
48
  ## Local UI Structure
49
49
 
package/dist/src/index.js CHANGED
@@ -1 +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.10",skills:[]});
1
+ export*from"./skills.js";export*from"./skillHostState.js";export*from"./platformApi.js";export const createBotHostManifest=()=>({name:"@ai.weget.jp/bot",version:"0.1.12",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,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)}}})};
1
+ const r=r=>r&&"object"==typeof r?r:{};export const registerIpcHandlers=({ipcMain:e,env:t,signInWithLoginApi:o,saveLoginProfile:i,readSavedLoginProfile:n,getSkillConfig:s,saveSkillConfig:a,getAiConfig:c,saveAiConfig:g,setCurrentSession:m,getCurrentSession:l,getActiveSkills:u,getCodexAuthStatus:d,startCodexLogin:S,getManagedSkills:k,emitLog:y,openGmoKlineWindow:f,getGmoMarketQuotes:p,getGmoSymbolRules:h,getGmoBuyingPower:b,getGmoAccountMetrics:w,getGmoPositionSummary:C,getGmoOpenPositions:L,placeGmoFxOrder:E,placeGmoCoinOrder:x,placeGmoFxCloseOrder:I,placeGmoCoinCloseOrder:v,getGmoApiState:A,testGmoApi:U,buildTradeStatusSnapshot:G,closeTradeWindows:q,startWsClient:P,stopWsClient:z,handleBotChatMessage:B,writeErrorLog:N,writeRuntimeLog:F})=>{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 m(e),P({botId:s,userId:String(r.userId||""),accessToken:String(r.accessToken||"")}),y("[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 y("[auth] login failed",{error:e}),{ok:!1,error:e}}}),e.handle("auth:logout",async()=>(z(),m(null),q(),y("[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("skill:getConfig",async(r,e)=>{try{const r=String(e||"").trim();return{ok:!0,...s(r)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("skill:saveConfig",async(e,t)=>{try{const e=r(t),o=String(e.skillName||"").trim();return{ok:!0,...await a(o,e.config)}}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}),e.handle("ai:getConfig",async()=>({ok:!0,config:c()})),e.handle("ai:saveConfig",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("bot:getRuntimeInfo",async()=>{const e=l(),o=r(e),i=await d();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:A("fx"),gmoCoinApiState:A("coin"),aiModel:c().aiModel,activeSkills:u(),codexAuthStatus:i.status,codexAuthDetail:i.detail}}}),e.handle("bot:getSkills",async()=>({ok:!0,skills:k()})),e.handle("codex:startLogin",async()=>S()),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 f({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 p({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 h({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 b({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 w({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 C({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 L({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 E({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 x({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 I({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 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:testGmoApi",async(e,t)=>{const o=r(t),i="coin"===String(o.market||"").trim().toLowerCase()?"coin":"fx";return U(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 G(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 B({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 N({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 F({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 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()});
1
+ import t from"node:path";import e from"node:fs/promises";import{existsSync as i}from"node:fs";import{fileURLToPath as a}from"node:url";import{createRequire as n}from"node:module";import{SYSTEM_CONFIG as s}from"./config/systemConfig.js";import{BotWsClient as o}from"./wsClient.js";import{registerIpcHandlers as r}from"./ipc/registerHandlers.js";import{createAuthApiService as l}from"./services/authApiService.js";import{createCodexService as g}from"./services/codexService.js";import{createPlatformApiClient as c}from"./platformApi.js";import{createSkillRuntimeManager as d}from"./skillRuntimeManager.js";import{createWindowManager as m}from"./services/windowManagerService.js";import{createSkillHostState as p}from"./skillHostState.js";const u=n(import.meta.url),{app:f,BrowserWindow:S,ipcMain:y,nativeImage:w}=u("electron"),k=a(import.meta.url),b=t.dirname(k),h=["trade","ai"],_=()=>t.join(f.getPath("userData"),"login-profile.json"),v=()=>t.join(f.getPath("userData"),"host-config.json"),x=()=>t.join(f.getPath("userData"),"skill-states.json");let A=null,C=null,O={aiModel:"gpt-5.4",logOutputDir:""};const E=new Map;let M=()=>t.join(f.getPath("documents"),"weget-bot-logs");const P=t=>{const e=String(t||"").toLowerCase();return e.includes("debug")||e.includes("[debug]")?"debug":e.includes("error")||e.includes("failed")||e.includes("exception")?"error":"info"},j=async({level:i,source:a,message:n,details:s,ts:o})=>{const r=M();await e.mkdir(r,{recursive:!0});const l=new Date,g=l.getFullYear(),c=String(l.getMonth()+1).padStart(2,"0"),d=String(l.getDate()).padStart(2,"0"),m=t.join(r,`${i}-${g}${c}${d}.log`),p=JSON.stringify({ts:o||l.toISOString(),level:i,source:a,message:n,details:s??null},null,0);await e.appendFile(m,`${p}\n`,"utf8")},D=m({BrowserWindow:S,preloadPath:t.join(b,"preload.cjs"),rendererDir:t.join(b,"renderer"),windowIconPath:t.join(b,"renderer","logo.png")}),I=(t,e)=>{const i=(new Date).toISOString();D.emitBotLog({message:t,data:e||null,ts:i}),j({level:P(t),source:"runtime",message:t,details:e,ts:i}).catch(()=>{})},L=t=>{D.emitChatEvent(t)},T=t=>t&&"object"==typeof t?t:{},B=p({log:I}),U=l({getEnv:()=>({loginApiUrl:s.loginApiUrl,botUploadApiUrl:s.botUploadApiUrl}),emitLog:I,getCurrentSession:()=>A}),F=c({getBaseUrl:()=>String(s.loginApiUrl||"").trim().replace(/\/login\/?$/i,""),getSession:()=>A,log:I}),N=d({installRoot:t.join(f.getPath("userData"),"skills"),botId:()=>String(A?.botId||"").trim()||void 0,log:I,api:F,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}),emitSkillEvent:(t,e)=>{if("trade_execution"!==e.type){if("trade_history"===e.type){const i=`trade-${Date.now()}`;C?.sendTaskEvent(i,"trade_history",{package_name:t,...e.payload&&"object"==typeof e.payload?e.payload:{}}),I("[trade] history event sent (execution)",{package_name:t,payload:e.payload})}}else D.emitTradeExecution(e.payload)}}),G=()=>{const t=N.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime;if(!t)throw new Error("GMO FX skill runtime is not installed");return t},R=()=>{const t=N.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime;if(!t)throw new Error("GMO Coin skill runtime is not installed");return t},J=()=>N.getLoadedSkill("@ai.weget.jp/skill-gmo-fx")?.runtime,W=()=>N.getLoadedSkill("@ai.weget.jp/skill-gmo-coin")?.runtime,$=(t,e)=>{const i=t.gateway[e];if("function"!=typeof i)throw new Error(`skill runtime does not implement gateway.${e}`);return i},K=t=>{const e=T(t);return{riskDailyLossLimitJpy:Number(e.riskDailyLossLimitJpy||0),fxApiKey:String(e.fxApiKey||"").trim(),fxApiSecret:String(e.fxApiSecret||"").trim(),aiModel:String(e.aiModel||"").trim()||"gpt-5.4"}},z=t=>{const e=T(t);return{riskDailyLossLimitJpy:Number(e.riskDailyLossLimitJpy||0),cryptoApiKey:String(e.cryptoApiKey||"").trim(),cryptoApiSecret:String(e.cryptoApiSecret||"").trim(),aiModel:String(e.aiModel||"").trim()||"gpt-5.4"}};M=()=>String(O.logOutputDir||"").trim()||t.join(f.getPath("documents"),"weget-bot-logs");const q=g({getModel:()=>{const t=J(),e=W();return O.aiModel||t?.getConfig?.().aiModel||e?.getConfig?.().aiModel||"gpt-5.4"},getWorkingDir:()=>f.getPath("userData"),emitLog:I}),H=async t=>{const i=T(t);return O={aiModel:String(i.aiModel||O.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i.logOutputDir||"").trim()},await e.writeFile(v(),JSON.stringify({...O,updatedAt:(new Date).toISOString()},null,2),"utf8"),O};const Q=async()=>{const t=await N.refreshLoadedSkills();return B.setAvailableSkills(t.map(t=>t.manifest)),t},X=async t=>{const e=await Q();I("[skills] reconciled bundled skills",{desired:t.map(t=>({package_name:t.package_name,install_status:t.install_status,enabled:t.enabled,version:t.version})),bundled:e.map(t=>({package_name:t.packageName,version:t.version}))})},Y=()=>{const t=B.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"),e=B.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"),i=J(),a=W();i&&("connected"===C?.getStatus()&&t?i.startExecutionStreams():i.stopExecutionStreams()),a&&("connected"===C?.getStatus()&&e?a.startExecutionStreams():a.stopExecutionStreams()),I("[skills] applied managed states",{active_skills:B.getActiveSkills().map(t=>t.name)})},V=async()=>{const t=U.getBotRuntimeConfigApiUrl(),e=String(A?.botId||"").trim();if(!t||!e)return;const i=await U.callBotApi(t,{bot_id:e}),a=String(i.chat_model||"").trim(),n={...a?{aiModel:a}:{}};if(0===Object.keys(n).length)return;await H({...O,...n});const s=J(),o=W();s&&await s.saveConfig({...s.getConfig(),...n}),o&&await o.saveConfig({...o.getConfig(),...n}),I("[config] runtime config synced",{chat_model:a||""})},Z=async()=>{const t=U.getBotSkillsListApiUrl(),i=String(A?.botId||"").trim();if(!t||!i)return;const a=await U.callBotApi(t,{bot_id:i}),n=Array.isArray(a.states)?a.states.filter(t=>Boolean(t&&"object"==typeof t)).map(t=>({user_id:String(t.user_id||""),bot_id:String(t.bot_id||""),skill_id:String(t.skill_id||""),package_name:String(t.package_name||""),enabled:Boolean(t.enabled),install_status:String(t.install_status||""),version:t.version?String(t.version):null,config_json:T(t.config_json),created_at:t.created_at?String(t.created_at):void 0,updated_at:t.updated_at?String(t.updated_at):void 0})):[];B.applySnapshots(n),await(async t=>{await e.writeFile(x(),JSON.stringify({updatedAt:(new Date).toISOString(),items:t},null,2),"utf8")})(n),await X(n),Y(),I("[config] skill states synced",{count:n.length,bot_id:i,enabled:n.filter(t=>t.enabled).length})},tt=t=>{if("fx"===t&&!B.isSkillEnabled("@ai.weget.jp/skill-gmo-fx"))throw new Error("GMO FX skill is disabled");if("coin"===t&&!B.isSkillEnabled("@ai.weget.jp/skill-gmo-coin"))throw new Error("GMO Coin skill is disabled")},et={"@ai.weget.jp/skill-gmo-fx":{getConfig:()=>K(G().getConfig()),saveConfig:async t=>{const e=G(),i=await e.saveConfig({...e.getConfig(),...K(t)});return"connected"===C?.getStatus()&&Y(),K(i)}},"@ai.weget.jp/skill-gmo-coin":{getConfig:()=>z(R().getConfig()),saveConfig:async t=>{const e=R(),i=await e.saveConfig({...e.getConfig(),...z(t)});return"connected"===C?.getStatus()&&Y(),z(i)}}},it=t=>{const e=String(t||"").trim(),i=et[e];if(!i)throw new Error(`skill config is not managed by bot host: ${t}`);return i},at=async()=>{await N.ensureInstallRoot(),await Q();const t=await async function(){try{const t=await e.readFile(x(),"utf8"),i=JSON.parse(t);return(Array.isArray(i?.items)?i.items:[]).filter(t=>Boolean(t&&"object"==typeof t)).map(t=>({user_id:String(t.user_id||""),bot_id:String(t.bot_id||""),skill_id:String(t.skill_id||""),package_name:String(t.package_name||""),enabled:Boolean(t.enabled),install_status:String(t.install_status||""),version:t.version?String(t.version):null,config_json:T(t.config_json),created_at:t.created_at?String(t.created_at):void 0,updated_at:t.updated_at?String(t.updated_at):void 0}))}catch(t){if("ENOENT"===t?.code)return[];throw t}}();t.length>0&&(B.applySnapshots(t),I("[skills] restored local snapshot",{count:t.length}),await X(t)),Y()};C=new o({wsUrl:"wss://ws.weget.jp",version:"0.2.0",capabilities:h,heartbeatSec:30,reconnectMs:3e3,reconnectMaxAttempts:10,wsPingIntervalSec:25,wsPongTimeoutSec:12},{onLog:(t,e)=>I(t,e),onStatus:t=>{D.emitBotStatus({status:t}),Y()},onLineMessage:async t=>{const e=String(t.line_user_id||""),i=String(t.text||"").trim();if(L({type:"line_user",lineUserId:e,text:i}),!i)return{answer:""};try{const t=await q.runPrompt({prompt:i,contextLabel:"line_message"});return L({type:"line_assistant",lineUserId:e,text:t}),{answer:t}}catch(t){const i=t instanceof Error?t.message:String(t);I("[ai] line reply failed",{error:i});const a=`AI error: ${i}`;return L({type:"line_assistant",lineUserId:e,text:a}),{answer:a}}},onRunTask:async t=>{const e=String(t.task_id||"").trim(),i=String(t.prompt||"").trim();if(e){if(E.set(e,{cancelled:!1}),C?.sendTaskEvent(e,"task_received",{prompt:i,conversation_id:String(t.conversation_id||""),continue_from_task_id:String(t.continue_from_task_id||""),ts:(new Date).toISOString()}),!i)return C?.sendTaskEvent(e,"task_failed",{error:"prompt is required",ts:(new Date).toISOString()}),void E.delete(e);try{C?.sendTaskEvent(e,"step_started",{step:"codex_chat",ts:(new Date).toISOString()});const t=await q.runPrompt({prompt:i,contextLabel:`task:${e}`}),a=E.get(e);if(a?.cancelled)return C?.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()}),void E.delete(e);C?.sendTaskEvent(e,"ai_output",{summary:t,ts:(new Date).toISOString()}),C?.sendTaskEvent(e,"step_finished",{step:"codex_chat",ts:(new Date).toISOString()}),C?.sendTaskEvent(e,"task_completed",{summary:t.slice(0,500),ts:(new Date).toISOString()})}catch(t){C?.sendTaskEvent(e,"task_failed",{error:t instanceof Error?t.message:String(t),ts:(new Date).toISOString()})}finally{E.delete(e)}}else I("[task] run_task missing task_id")},onTaskControl:async t=>{const e=String(t.task_id||"").trim(),i=String(t.action||"").trim();if(e&&i&&"cancel_task"===i){const t=E.get(e);if(t)return t.cancelled=!0,void I("[task] cancel requested",{task_id:e});C?.sendTaskEvent(e,"task_cancelled",{reason:"server_cancelled",ts:(new Date).toISOString()})}},onServerConfigUpdate:async t=>{const e=Array.isArray(t.scopes)?t.scopes.map(t=>String(t||"").trim()):[],i=e.length>0?e:["runtime","skills"];i.includes("runtime")&&await V(),i.includes("skills")&&await Z()}}),r({ipcMain:y,env:{capabilities:h,loginApiUrl:s.loginApiUrl},signInWithLoginApi:U.signInWithLoginApi,saveLoginProfile:async({botId:t,email:i,password:a,remember:n})=>{if(n)await e.writeFile(_(),JSON.stringify({botId:t,email:i,password:a,remember:!0,updatedAt:(new Date).toISOString()},null,2),"utf8");else try{await e.unlink(_())}catch(t){if("ENOENT"!==t?.code)throw t}},readSavedLoginProfile:async()=>{try{const t=await e.readFile(_(),"utf8"),i=JSON.parse(t);return{botId:String(i?.botId||""),email:String(i?.email||""),password:String(i?.password||""),remember:Boolean(i?.remember)}}catch(t){if("ENOENT"===t?.code)return{botId:"",email:"",password:"",remember:!1};throw t}},getSkillConfig:t=>({config:it(t).getConfig()}),saveSkillConfig:async(t,e)=>({config:await it(t).saveConfig(e)}),getAiConfig:()=>({aiModel:String(O.aiModel||"gpt-5.4"),logOutputDir:String(O.logOutputDir||"")}),saveAiConfig:async t=>{const e=t&&"object"==typeof t?t:{},i=await H({...O,...e}),a=J(),n=W(),s=a?await a.saveConfig({...a.getConfig(),...e}):null;return n&&await n.saveConfig({...n.getConfig(),...e}),{aiModel:String(i.aiModel||s?.aiModel||e.aiModel||"gpt-5.4"),logOutputDir:String(i.logOutputDir||"")}},setCurrentSession:t=>{A=t||null;const e=A?"connected":"disconnected";D.emitBotStatus({status:e})},getCurrentSession:()=>A,getActiveSkills:()=>B.getActiveSkills().map(t=>({name:t.name,displayName:t.displayName,version:t.version})),getCodexAuthStatus:()=>q.getAuthStatus(),startCodexLogin:()=>q.startLogin(),getManagedSkills:()=>B.getManagedSkills().map(t=>({...t,ui:N.getLoadedSkill(t.name)?.ui||void 0})),emitLog:I,openGmoKlineWindow:async({symbol:t,interval:e,market:i})=>(()=>{const a="fx"===i?"fx":"coin";return tt(a),("fx"===a?G().marketData:R().marketData).fetchKline({symbol:t,interval:e,market:a})})(),getGmoMarketQuotes:async({market:t,symbols:e})=>(()=>{const i="fx"===t?"fx":"coin";return tt(i),("fx"===i?G().marketData:R().marketData).fetchQuotes({market:i,symbols:e})})(),getGmoSymbolRules:async({market:t})=>(()=>{const e="fx"===t?"fx":"coin";return tt(e),("fx"===e?G().marketData:R().marketData).fetchSymbolRules({market:e})})(),getGmoBuyingPower:async({market:t})=>({market:t,availableJpy:await(tt(t),("fx"===t?G().gateway:R().gateway).getBuyingPower(t))}),getGmoAccountMetrics:async({market:t})=>(tt(t),("fx"===t?G().gateway:R().gateway).getAccountMetrics(t)),getGmoPositionSummary:async({market:t,symbol:e})=>({market:t,symbol:e,items:await(tt(t),("fx"===t?G().gateway:R().gateway).getPositionSummary({market:t,symbol:e}))}),getGmoOpenPositions:async({market:t,symbol:e,page:i,prevId:a,count:n})=>({market:t,symbol:e||"",items:await(tt(t),("fx"===t?G().gateway:R().gateway).getOpenPositions({market:"fx"===t?"fx":"crypto",symbol:e||void 0,page:i,prevId:a,count:n}))}),placeGmoFxOrder:async({symbol:t,side:e,size:i})=>{tt("fx");return await $(G(),"placeFxOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoCoinOrder:async({symbol:t,side:e,size:i})=>{tt("coin");return await $(R(),"placeCoinOrder")({symbol:t,side:e,size:i,executionType:"MARKET"})},placeGmoFxCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{tt("fx");return await $(G(),"placeFxCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},placeGmoCoinCloseOrder:async({symbol:t,side:e,positionId:i,size:a})=>{tt("coin");return await $(R(),"placeCoinCloseOrder")({symbol:t,side:e,positionId:i,size:a,executionType:"MARKET"})},getGmoApiState:t=>{const e="fx"===t?J():W();return e?.status.getApiState()||"unknown"},testGmoApi:t=>(tt(t),"fx"===t?G().status.testApi():R().status.testApi()),buildTradeStatusSnapshot:t=>(tt(t),"fx"===t?G().status.buildSnapshot():R().status.buildSnapshot()),closeTradeWindows:()=>{},startWsClient:t=>{C?.start(t),V().catch(t=>{I("[config] runtime sync failed after login",{error:t instanceof Error?t.message:String(t)})}),Z().catch(t=>{I("[config] skill sync failed after login",{error:t instanceof Error?t.message:String(t)})}),Y()},stopWsClient:()=>{J()?.stopExecutionStreams(),W()?.stopExecutionStreams(),C?.stop()},handleBotChatMessage:async({text:t})=>{L({type:"user",text:String(t||"")});const e=await q.runPrompt({prompt:t,contextLabel:"desktop_chat"});return L({type:"assistant",text:e}),{answer:e}},writeErrorLog:async({source:t,message:e,details:i})=>{await(async({source:t,message:e,details:i})=>{await j({level:"error",source:t,message:e,details:i})})({source:t,message:e,details:i})},writeRuntimeLog:async({level:t,source:e,message:i,details:a})=>{await j({level:t,source:e,message:i,details:a})}});const nt=t.join(b,"renderer","logo.png");f.whenReady().then(async()=>{(()=>{if("darwin"!==process.platform)return;if(!i(nt))return;const t=w.createFromPath(nt);t.isEmpty()||f.dock.setIcon(t)})(),await(async()=>{try{const t=await e.readFile(v(),"utf8"),i=JSON.parse(t);O={aiModel:String(i?.aiModel||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(i?.logOutputDir||"").trim()}}catch(t){if("ENOENT"!==t?.code)throw t;O={aiModel:"gpt-5.4",logOutputDir:""}}return O})();try{await at()}catch(t){I("[skills] local initialization failed",{error:t instanceof Error?t.message:String(t)})}D.createMainWindow(),f.on("activate",()=>{0===S.getAllWindows().length&&D.createMainWindow()})}),f.on("window-all-closed",()=>{J()?.stopExecutionStreams(),W()?.stopExecutionStreams(),C?.stop(),"darwin"!==process.platform&&f.quit()});
@@ -4,10 +4,8 @@ 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
- 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),
7
+ getSkillConfig: (skillName) => ipcRenderer.invoke('skill:getConfig', skillName),
8
+ saveSkillConfig: (skillName, config) => ipcRenderer.invoke('skill:saveConfig', { skillName, config }),
11
9
  getAiConfig: () => ipcRenderer.invoke('ai:getConfig'),
12
10
  saveAiConfig: (config) => ipcRenderer.invoke('ai:saveConfig', config),
13
11
  getRuntimeInfo: () => ipcRenderer.invoke('bot:getRuntimeInfo'),
@@ -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("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{};
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("sidebar-nav"),r=document.getElementById("sidebar-toggle-btn"),a=document.getElementById("user-notice"),s=document.getElementById("bot-id"),l=document.getElementById("email"),c=document.getElementById("password"),d=document.getElementById("remember-me"),m=document.getElementById("status"),u=document.getElementById("session"),g=document.getElementById("runtime-info"),f=document.getElementById("skill-state-list"),p=document.getElementById("tab-btn-skill-gmo-coin"),y=document.getElementById("tab-btn-skill-gmo-fx"),b=document.getElementById("tab-btn-skill-browser"),w=document.getElementById("coin-skill-title"),x=document.getElementById("fx-skill-title"),k=document.getElementById("browser-skill-title"),S=document.getElementById("coin-skill-package"),h=document.getElementById("fx-skill-package"),E=document.getElementById("browser-skill-package"),v=document.getElementById("coin-skill-enabled"),C=document.getElementById("fx-skill-enabled"),I=document.getElementById("browser-skill-enabled"),A=document.getElementById("coin-skill-version"),B=document.getElementById("fx-skill-version"),L=document.getElementById("browser-skill-version"),N=document.getElementById("browser-skill-cards"),$=document.getElementById("browser-skill-tools"),T=document.getElementById("logs"),U=document.getElementById("messages"),M=document.getElementById("chat-form"),P=document.getElementById("chat-input"),D=document.getElementById("send-btn"),F=document.getElementById("fx-risk-daily-loss-limit-jpy"),j=document.getElementById("coin-risk-daily-loss-limit-jpy"),z=document.getElementById("crypto-api-key"),O=document.getElementById("crypto-api-secret"),J=document.getElementById("fx-api-key"),q=document.getElementById("fx-api-secret"),Y=document.getElementById("toggle-crypto-secret-btn"),H=document.getElementById("toggle-fx-secret-btn"),R=document.getElementById("save-fx-config-btn"),K=document.getElementById("save-coin-config-btn"),_=document.getElementById("fx-config-title"),W=document.getElementById("coin-config-title"),G=document.getElementById("fx-config-label-riskDailyLossLimitJpy"),V=document.getElementById("fx-config-label-fxApiKey"),X=document.getElementById("fx-config-label-fxApiSecret"),Q=document.getElementById("coin-config-label-riskDailyLossLimitJpy"),Z=document.getElementById("coin-config-label-cryptoApiKey"),ee=document.getElementById("coin-config-label-cryptoApiSecret"),te=document.getElementById("ai-model"),ne=document.getElementById("host-log-output-dir"),ie=document.getElementById("save-ai-config-btn"),oe=document.getElementById("codex-login-btn"),re=document.getElementById("codex-copy-login-btn"),ae=document.getElementById("codex-refresh-auth-btn"),se=document.getElementById("codex-login-command"),le=document.getElementById("logout-btn"),ce=document.getElementById("login-btn"),de=document.getElementById("coin-symbol-select"),me=document.getElementById("fx-symbol-select"),ue=document.getElementById("coin-kline-intervals"),ge=document.getElementById("fx-kline-intervals"),fe=document.getElementById("coin-kline-canvas"),pe=document.getElementById("fx-kline-canvas"),ye=document.getElementById("coin-market-icon"),be=document.getElementById("fx-market-icon"),we=document.getElementById("coin-order-bid"),xe=document.getElementById("coin-order-ask"),ke=document.getElementById("coin-order-spread"),Se=document.getElementById("fx-order-bid"),he=document.getElementById("fx-order-ask"),Ee=document.getElementById("fx-order-spread"),ve=document.getElementById("coin-order-qty"),Ce=document.getElementById("fx-order-qty"),Ie=document.getElementById("coin-buy-btn"),Ae=document.getElementById("coin-sell-btn"),Be=document.getElementById("fx-buy-btn"),Le=document.getElementById("fx-sell-btn"),Ne=document.getElementById("coin-required-amount"),$e=document.getElementById("fx-required-amount"),Te=document.getElementById("coin-account-info-refresh-btn"),Ue=document.getElementById("fx-account-info-refresh-btn"),Me=document.getElementById("coin-account-pnl"),Pe=document.getElementById("coin-account-margin"),De=document.getElementById("coin-account-available"),Fe=document.getElementById("coin-account-margin-ratio"),je=document.getElementById("fx-account-pnl"),ze=document.getElementById("fx-account-margin"),Oe=document.getElementById("fx-account-available"),Je=document.getElementById("fx-account-margin-ratio"),qe=document.getElementById("coin-position-summary-body"),Ye=document.getElementById("coin-position-list-body"),He=document.getElementById("fx-position-summary-body"),Re=document.getElementById("fx-position-list-body"),Ke=document.getElementById("coin-position-summary-refresh-btn"),_e=document.getElementById("coin-position-list-refresh-btn"),We=document.getElementById("fx-position-summary-refresh-btn"),Ge=document.getElementById("fx-position-list-refresh-btn"),Ve=Array.from(document.querySelectorAll(".tab-btn")),Xe=Array.from(document.querySelectorAll(".tab-panel"));let Qe="15m",Ze="15m",et=String(de?.value||"BTC_JPY").trim().toUpperCase(),tt=String(me?.value||"USD_JPY").trim().toUpperCase(),nt=null,it=null,ot=null,rt=null,at=!1,st=!1,lt=!1,ct=!1,dt=!1,mt="",ut="",gt=0,ft=0,pt=null,yt=null,bt=[];const wt=new Map,xt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1},kt={symbol:"",interval:"",candles:[],lastFetchBucket:-1,fetching:!1};let St=[],ht=[];const Et=e=>e&&"object"==typeof e?e:{},vt="weget.bot.sidebar.collapsed",Ct=e=>{i.classList.toggle("sidebar-collapsed",e),r&&(r.textContent=e?"▶":"◀",r.setAttribute("aria-label",e?"Expand menu":"Collapse menu")),o&&o.setAttribute("data-collapsed",e?"true":"false")},It=e=>{for(const t of Ve)t.classList.toggle("is-active",t.dataset.tab===e);for(const t of Xe)t.classList.toggle("is-active",t.id===`tab-${e}`)};for(const e of Ve)e.addEventListener("click",()=>It(e.dataset.tab||"coin"));r?.addEventListener("click",()=>{const e=!i.classList.contains("sidebar-collapsed");Ct(e);try{window.localStorage.setItem(vt,String(e))}catch{}});const At=e=>{const n=at!==e;at=e,t.classList.toggle("hidden",e),i.classList.toggle("hidden",!e),e&&gn(),e?n&&En():(Xt(),st=!1)},Bt=e=>{const t=String(e||"disconnected").trim().toLowerCase();m.textContent=t,m.classList.remove("status-connected","status-disconnected","status-connecting"),"connected"!==t?"reconnecting"!==t&&"connecting"!==t?m.classList.add("status-disconnected"):m.classList.add("status-connecting"):m.classList.add("status-connected")},Lt=(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})},Nt=e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,$t=e=>`${e>0?"+":""}${e.toLocaleString("ja-JP",{maximumFractionDigits:0})} 円`,Tt=e=>!Number.isFinite(e)||e<=0||e>1e5?"- %":(e=>`${e.toLocaleString("ja-JP",{maximumFractionDigits:2})} %`)(e),Ut=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(/\.$/,"")},Mt=e=>{const t=String(e||"").trim(),n=t.indexOf(".");return n>=0?t.length-n-1:0},Pt=(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),l=BigInt((r||"0")+s);return i?-l:l},Dt=({symbol:e,quote:t,orderBidNode:n,orderAskNode:i,orderSpreadNode:o})=>{if(!t)return n.textContent="-",i.textContent="-",void(o.textContent="-");const r=Lt(t.bid),a=Lt(t.ask);n.textContent=r,i.textContent=a,o.textContent=Lt(t.spread,8)},Ft=e=>{if("coin"===e){const e=Number(String(ve.value||"").trim()),t=pt;return!Number.isFinite(e)||e<=0||null===t||!Number.isFinite(t)?void(Ne.textContent="- 円"):void(Ne.textContent=Nt(e*t/2))}const t=Number(String(Ce.value||"").trim()),n=yt;!Number.isFinite(t)||t<=0||null===n||!Number.isFinite(n)?$e.textContent="- 円":$e.textContent=Nt(t*n/20)},jt=e=>{const t=(e=>bt.find(t=>t.name===e))(e);if(!t)return!1;const n=String(t.installStatus||"").trim().toLowerCase();return t.enabled&&"active"===n},zt=e=>jt("fx"===e?"@ai.weget.jp/skill-gmo-fx":"@ai.weget.jp/skill-gmo-coin"),Ot=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?(Dt({symbol:n,quote:o,orderBidNode:we,orderAskNode:xe,orderSpreadNode:ke}),pt=o?.ask??null,Ft("coin")):(Dt({symbol:n,quote:o,orderBidNode:Se,orderAskNode:he,orderSpreadNode:Ee}),yt=o?.ask??null,Ft("fx")),o},Jt=(e,t)=>{const n=String(t||"").trim().toUpperCase();if(!n)return;if("coin"===e){const e=n.replace("_","-").toLowerCase();return void(ye.src=`https://coin.z.com/jp/member/imgs/icon-${e}.svg`)}const[i,o]=n.split("_"),r="JPY"===o?i:`${i}_${o}`;be.src=`https://coin.z.com/jp/member/imgs/fx/icon_${r}.svg`},qt=e=>"5m"===e?3e5:"15m"===e?9e5:"30m"===e?18e5:"1h"===e?36e5:6e4,Yt=e=>"coin"===e?xt:kt,Ht=e=>"coin"===e?fe:pe,Rt=e=>"coin"===e?et:tt,Kt=e=>{const t=Rt(e),n=("coin"===e?St:ht).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},_t=e=>{const t=Yt(e);t.candles.length&&pn(Ht(e),t.candles,Kt(e))},Wt=(e,t)=>{if(!t)return;const n=Yt(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),pn(Ht(e),n.candles,Kt(e)))},Gt=async e=>{const t=Yt(e);if(!t.interval||!t.symbol||t.fetching)return;if(t.symbol!==Rt(e))return;if(!(Math.floor(Date.now()/qt(t.interval))<=t.lastFetchBucket)){t.fetching=!0;try{"coin"===e?await kn():await Sn()}finally{t.fetching=!1}}},Vt=async e=>{if(zt(e))if("coin"!==e){if(!dt){dt=!0;try{const e=await Ot("fx",tt);Wt("fx",e),await Gt("fx")}catch(e){const t=ln(e instanceof Error?e.message:String(e)),n=Date.now();(t!==ut||n-ft>15e3)&&(an("[fx] quote poll failed",{error:t}),ut=t,ft=n)}finally{dt=!1}}}else{if(ct)return;ct=!0;try{const e=await Ot("coin",et);Wt("coin",e),await Gt("coin")}catch(e){const t=ln(e instanceof Error?e.message:String(e)),n=Date.now();(t!==mt||n-gt>15e3)&&(an("[coin] quote poll failed",{error:t}),mt=t,gt=n)}finally{ct=!1}}},Xt=()=>{ot&&(clearInterval(ot),ot=null),rt&&(clearInterval(rt),rt=null)},Qt=async e=>{if(window.botApi?.getAccountMetrics){if((!e||"coin"===e)&&zt("coin")){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);Me.textContent=n>0?$t(i):Nt(0),Pe.textContent=Nt(n),De.textContent=Nt(t),Fe.textContent=Tt(Number(e.marginRatio||0))}else an("[coin] account metrics fetch failed",{error:e?.error||"unknown error"})}if((!e||"fx"===e)&&zt("fx")){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);je.textContent=n>0?$t(i):Nt(0),ze.textContent=Nt(n),Oe.textContent=Nt(t),Je.textContent=Tt(Number(e.marginRatio||0))}else an("[fx] account metrics fetch failed",{error:e?.error||"unknown error"})}}},Zt=(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),l=Number(i.price||0),c=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>${l.toLocaleString("ja-JP",{maximumFractionDigits:6})}</td>\n <td>${$t(c)}<br />${$t(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>'},en=(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),l=Number("fx"===e&&i.sumTotalSwap||0),c=document.createElement("tr");c.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>${$t(s)}<br />${$t(l)}</td>\n `,t.appendChild(c)}}else t.innerHTML='<tr><td colspan="5" class="empty-cell">対象のお取引はございません。</td></tr>'},tn=async e=>{if(window.botApi?.getPositionSummary&&window.botApi?.getOpenPositions){if((!e||"coin"===e)&&zt("coin")){const e=await window.botApi.getPositionSummary({market:"coin",symbol:et});e?.ok?(St=Array.isArray(e.items)?e.items:[],en("coin",qe,St),_t("coin")):(an("[coin] position summary fetch failed",{error:e?.error||"unknown error"}),St=[],en("coin",qe,[]),_t("coin"))}if((!e||"fx"===e)&&zt("fx")){const e=await window.botApi.getPositionSummary({market:"fx"});e?.ok?(ht=Array.isArray(e.items)?e.items:[],en("fx",He,ht),_t("fx")):(an("[fx] position summary fetch failed",{error:e?.error||"unknown error"}),ht=[],en("fx",He,[]),_t("fx"))}if((!e||"coin"===e)&&zt("coin")){const e=await window.botApi.getOpenPositions({market:"coin",symbol:et,page:1,count:100});e?.ok?Zt("coin",Ye,Array.isArray(e.items)?e.items:[]):(an("[coin] open positions fetch failed",{error:e?.error||"unknown error"}),Zt("coin",Ye,[]))}if((!e||"fx"===e)&&zt("fx")){const e=await window.botApi.getOpenPositions({market:"fx",count:100});e?.ok?Zt("fx",Re,Array.isArray(e.items)?e.items:[]):(an("[fx] open positions fetch failed",{error:e?.error||"unknown error"}),Zt("fx",Re,[]))}}},nn=async(e,t)=>{const n=window.botApi;if(!n?.placeFxOrder)return;const i=String(Ce.value||"").trim();i?await fn(t,async()=>{const t=await n.placeFxOrder({symbol:tt,side:e,size:i});if(!t?.ok){const n=ln(t?.error||"fx order failed");return an("[fx] order failed",{symbol:tt,side:e,size:i,error:n}),cn("error",n),void await dn("fx.order",n,t)}an("[fx] order placed",{symbol:tt,side:e,size:i,result:t.result||null}),cn("success",`${e} 注文を送信しました。`),await Qt("fx"),await tn("fx")}):cn("error","数量を入力してください。")},on=async(e,t)=>{const n=window.botApi;if(!n?.placeCoinOrder)return;const i=Ut(String(ve.value||""));if(!i)return void cn("error","数量の形式が不正です。");const o=((e,t)=>{const n=wt.get(e);if(!n)return{ok:!0};const i=Ut(n.minOrderSize),o=Ut(n.sizeStep),r=Ut(n.maxOrderSize);if(!i||!o)return{ok:!0};const a=Math.max(Mt(t),Mt(i),Mt(o),Mt(r)),s=Pt(t,a),l=Pt(i,a),c=Pt(o,a);if(s<l)return{ok:!1,reason:`${e} の最小数量は ${i} です。`};if(c>0n&&s%c!==0n)return{ok:!1,reason:`${e} の数量刻みは ${o} です。`};if(r&&s>Pt(r,a))return{ok:!1,reason:`${e} の最大数量は ${r} です。`};return{ok:!0}})(et,i);o.ok?await fn(t,async()=>{const t=await n.placeCoinOrder({symbol:et,side:e,size:i});if(!t?.ok){const n=ln(t?.error||"coin order failed");return an("[coin] order failed",{symbol:et,side:e,size:i,error:n}),cn("error",n),void await dn("coin.order",n,t)}an("[coin] order placed",{symbol:et,side:e,size:i,result:t.result||null}),cn("success",`${e} 注文を送信しました。`),await Qt("coin"),await tn("coin")}):cn("error",o.reason||"数量が取引ルールに一致しません。")},rn=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"},an=(e,t=null,n=null)=>{if(window.botApi?.writeLog&&window.botApi.writeLog({level:rn(e),source:"ui.log",message:e,details:{data:t,ts:n||(new Date).toISOString()}}).catch(()=>{}),!T)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(T.appendChild(i);T.children.length>300;)T.removeChild(T.firstChild);T.scrollTop=T.scrollHeight},sn=(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}`,U.appendChild(i),U.scrollTop=U.scrollHeight},ln=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:"处理失败,请稍后重试。"},cn=(e,t)=>{nt&&(clearTimeout(nt),nt=null),a.classList.remove("hidden","notice-error","notice-success"),a.classList.add("error"===e?"notice-error":"notice-success"),a.textContent=t,nt=setTimeout(()=>{mn()},"success"===e?2200:4200)},dn=async(e,t,n=null)=>{window.botApi?.writeErrorLog&&await window.botApi.writeErrorLog({source:e,message:String(t||""),details:n})},mn=()=>{nt&&(clearTimeout(nt),nt=null),a.classList.add("hidden"),a.classList.remove("notice-error","notice-success"),a.textContent=""},un=(e,t)=>{it&&(clearTimeout(it),it=null),n.classList.remove("hidden","notice-error","notice-success"),n.classList.add("error"===e?"notice-error":"notice-success"),n.textContent=t,it=setTimeout(()=>{gn()},"success"===e?2200:4200)},gn=()=>{it&&(clearTimeout(it),it=null),n.classList.add("hidden"),n.classList.remove("notice-error","notice-success"),n.textContent=""},fn=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=ln(e instanceof Error?e.message:String(e));an("[ui] action failed",{error:t}),cn("error",t)}finally{e.classList.remove("is-loading"),e.textContent=n,e.disabled=!1}},pn=(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)),l=n.map(e=>e.price).filter(e=>Number.isFinite(e)),c=l.length?Math.max(a,...l):a,d=l.length?Math.min(s,...l):s,m=Math.max(1e-8,c-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+(c-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),l=e.close>=e.open;i.strokeStyle=l?"#1f9d55":"#d64545",i.fillStyle=l?"#1f9d55":"#d64545",i.lineWidth=1,i.beginPath(),i.moveTo(n,o),i.lineTo(n,r),i.stroke();const c=n-.3*y,d=Math.max(1,.6*y),m=Math.min(a,s),g=Math.max(1,Math.abs(s-a));i.fillRect(c,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=c-m/4*e,n=g+p/4*e;i.fillText(w(t),u+f+8,n)}const S=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(S(t[n]?.time||0),o,g+p+6)}},yn=(e,t)=>{const{tabBtn:n,titleNode:i,packageNode:o,enabledNode:r,versionNode:a,fallbackTitle:s,fallbackPackage:l}=t,c=String(e?.ui?.surfaceTitle||e?.displayName||s).trim()||s,d=e?.name||l,m=String(e?.ui?.tabLabel||d.replace("@ai.weget.jp/","")).trim()||d.replace("@ai.weget.jp/",""),u=!!e&&Boolean(e.enabled),g=String(e?.installStatus||"uninstalled").trim()||"uninstalled",f=String(e?.configuredVersion||e?.version||"-").trim()||"-";if(n){const e=n.querySelector(".tab-btn-label");e&&(e.textContent=m),n.classList.toggle("is-disabled",!u)}i&&(i.textContent=c),o&&(o.textContent=d),r&&(r.textContent=u?"enabled":"disabled",r.classList.toggle("is-disabled",!u)),a&&(a.textContent=`${g} · ${f}`)},bn=(e,t)=>{t.titleNode&&(t.titleNode.textContent=String(e?.ui?.configTitle||t.fallbackTitle).trim()||t.fallbackTitle);const n=Array.isArray(e?.ui?.configFields)&&e.ui?.configFields||[];for(const[e,i]of Object.entries(t.fields)){const t=n.find(t=>t.key===e);t?.label&&i.labelNode&&(i.labelNode.textContent=t.label),void 0!==t?.placeholder&&(i.inputNode.placeholder=t.placeholder||"")}},wn=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;const n=t?.ok&&t.skills||[];bt=n,Bt(e.status||"disconnected"),e.session?(At(!0),u.textContent=`${e.session.email} (${e.session.userId}) [${e.session.botId}]`):(At(!1),u.textContent="local mode (not logged in)"),(e=>{g.innerHTML="";const t=Et(e),n=Et(t.runtime),i=t.session?Et(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=Et(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,g.appendChild(n),g.appendChild(i)}})(e),(e=>{if(!f)return;f.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 f.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 l=document.createElement("span");l.className="skill-badge status",l.textContent=e.installStatus||"unknown",a.appendChild(l),n.appendChild(i),n.appendChild(a),t.appendChild(n);const c=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=c("Package"),m=document.createElement("code");m.className="skill-package-code",m.textContent=e.name,d.appendChild(m),t.appendChild(d);const u=c("Tools"),g=document.createElement("div");g.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,g.appendChild(t)}else{const e=document.createElement("span");e.className="muted",e.textContent="No tools declared",g.appendChild(e)}u.appendChild(g),t.appendChild(u);const y=c("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=c("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),f.appendChild(t)}})(n),yn(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{tabBtn:p,titleNode:w,packageNode:S,enabledNode:v,versionNode:A,fallbackTitle:"GMO Coin Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-coin"}),yn(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{tabBtn:y,titleNode:x,packageNode:h,enabledNode:C,versionNode:B,fallbackTitle:"GMO FX Skill",fallbackPackage:"@ai.weget.jp/skill-gmo-fx"});const i=n.find(e=>"@ai.weget.jp/skill-browser"===e.name);yn(i,{tabBtn:b,titleNode:k,packageNode:E,enabledNode:I,versionNode:L,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)}}})($,Array.isArray(i?.tools)?i.tools:[],"No browser tools declared."),(e=>{if(!N)return;N.innerHTML="";const t=Array.isArray(e?.ui?.cards)&&e.ui?.cards||[];if(0===t.length){const e=document.createElement("div");return e.className="browser-skill-card",e.innerHTML='<h3>Skill UI</h3><p class="muted">No browser UI cards declared by the installed skill.</p>',void N.appendChild(e)}for(const e of t){const t=document.createElement("div");t.className="browser-skill-card";const n=document.createElement("h3");n.textContent=e.title;const i=document.createElement("p");i.className="muted",i.textContent=e.body,t.append(n,i),N.appendChild(t)}})(i),bn(n.find(e=>"@ai.weget.jp/skill-gmo-fx"===e.name),{titleNode:_,fallbackTitle:"FX Skill Config",fields:{riskDailyLossLimitJpy:{labelNode:G,inputNode:F},fxApiKey:{labelNode:V,inputNode:J},fxApiSecret:{labelNode:X,inputNode:q}}}),bn(n.find(e=>"@ai.weget.jp/skill-gmo-coin"===e.name),{titleNode:W,fallbackTitle:"Coin Skill Config",fields:{riskDailyLossLimitJpy:{labelNode:Q,inputNode:j},cryptoApiKey:{labelNode:Z,inputNode:z},cryptoApiSecret:{labelNode:ee,inputNode:O}}}),e.session&&En()};R.addEventListener("click",async()=>{await fn(R,async()=>{if(!window.botApi?.saveSkillConfig)return;const e={riskDailyLossLimitJpy:Number(F.value||"0"),fxApiKey:String(J.value||"").trim(),fxApiSecret:String(q.value||"").trim()},t=await window.botApi.saveSkillConfig("@ai.weget.jp/skill-gmo-fx",e);if(!t.ok)return an(`[trade-config] fx save failed: ${t.error||"unknown"}`),cn("error",ln(t.error)),void await dn("trade-config.fx.save",ln(t.error),t);an("[trade-config] fx saved"),cn("success","FX skill 配置已保存到本机。"),await wn()})}),K.addEventListener("click",async()=>{await fn(K,async()=>{if(!window.botApi?.saveSkillConfig)return;const e={riskDailyLossLimitJpy:Number(j.value||"0"),cryptoApiKey:String(z.value||"").trim(),cryptoApiSecret:String(O.value||"").trim()},t=await window.botApi.saveSkillConfig("@ai.weget.jp/skill-gmo-coin",e);if(!t.ok)return an(`[trade-config] coin save failed: ${t.error||"unknown"}`),cn("error",ln(t.error)),void await dn("trade-config.coin.save",ln(t.error),t);an("[trade-config] coin saved"),cn("success","Coin skill 配置已保存到本机。"),await wn()})}),ie.addEventListener("click",async()=>{await fn(ie,async()=>{if(!window.botApi?.saveAiConfig)return;const e={aiModel:String(te.value||"gpt-5.4").trim()||"gpt-5.4",logOutputDir:String(ne.value||"").trim()},t=await window.botApi.saveAiConfig(e);if(!t.ok)return an(`[ai-config] save failed: ${t.error||"unknown"}`),cn("error",ln(t.error)),void await dn("ai-config.save",ln(t.error),t);an("[ai-config] saved"),cn("success","AI 设置已保存。"),await wn()})}),oe.addEventListener("click",async()=>{await fn(oe,async()=>{if(!window.botApi?.startCodexLogin)return;const e=await window.botApi.startCodexLogin();if(!e.ok)return an(`[codex] login launch failed: ${e.detail||"unknown"}`),cn("error",ln(e.detail)),void await dn("codex.login.launch",ln(e.detail),e);an("[codex] login launched",{detail:e.detail}),cn("success",e.detail||"Codex login started."),setTimeout(()=>{wn()},1500)})}),re.addEventListener("click",async()=>{const e=String(se.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 cn("error","无法复制命令,请手动执行。");an("[codex] login command copied"),cn("success","Codex 登录命令已复制。")}catch(e){const t=ln(e instanceof Error?e.message:String(e));an("[codex] copy login command failed",{error:t}),cn("error",t)}}),ae.addEventListener("click",async()=>{await fn(ae,async()=>{await wn(),an("[codex] auth status refreshed"),cn("success","Codex 状态已刷新。")})}),Y.addEventListener("click",()=>{const e="password"===O.type?"text":"password";O.type=e,Y.textContent="password"===e?"显示":"隐藏"}),H.addEventListener("click",()=>{const e="password"===q.type?"text":"password";q.type=e,H.textContent="password"===e?"显示":"隐藏"}),e.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi)return;gn();const t=l.value.trim(),n=s.value.trim(),i=c.value;if(t&&n&&i){ce.disabled=!0,ce.textContent="Loading...";try{const e=await window.botApi.login(t,i,n,d.checked);if(!e.ok)return an(`[ui] login failed: ${e.error||"unknown"}`),void un("error",ln(e.error));an("[ui] login success"),un("success","登录成功。"),cn("success","登录成功。"),await wn()}finally{ce.disabled=!1,ce.textContent="Login"}}}),le.addEventListener("click",async()=>{await fn(le,async()=>{if(!window.botApi)return;const e=await window.botApi.logout();if(!e.ok)return an(`[ui] logout failed: ${e.error||"unknown"}`),cn("error",ln(e.error)),void await dn("auth.logout",ln(e.error),e);an("[ui] logout"),cn("success","已登出。"),At(!1),u.textContent="local mode (not logged in)",await wn()})});const xn=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 an(`[trade] open ${e} kline failed: ${o.error||"unknown"}`),cn("error",ln(o.error)),void await dn(`${e}.kline`,ln(o.error),o);const r=Array.isArray(o.candles)?o.candles:[],a=Yt(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()/qt(n)),pn(i,a.candles,Kt(e)),an("[trade] kline rendered",{market:e,symbol:o.symbol||t,interval:o.interval||n,candles:r.length}),mn()},kn=async()=>{await xn({market:"coin",symbol:et,interval:Qe,canvas:fe})},Sn=async()=>{await xn({market:"fx",symbol:tt,interval:Ze,canvas:pe})},hn=({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()}})};de.addEventListener("change",async()=>{et=String(de.value||"BTC_JPY").trim().toUpperCase(),Jt("coin",et);try{await Ot("coin",et),await kn(),await tn("coin")}catch(e){const t=ln(e instanceof Error?e.message:String(e));cn("error",t)}}),me.addEventListener("change",async()=>{tt=String(me.value||"USD_JPY").trim().toUpperCase(),Jt("fx",tt);try{await Ot("fx",tt),await Sn(),await tn("fx")}catch(e){const t=ln(e instanceof Error?e.message:String(e));cn("error",t)}}),hn({container:ue,getCurrent:()=>Qe,setCurrent:e=>{Qe=e},onChange:kn}),hn({container:ge,getCurrent:()=>Ze,setCurrent:e=>{Ze=e},onChange:Sn}),Te.addEventListener("click",async()=>{await fn(Te,async()=>{await Qt("coin")})}),Ue.addEventListener("click",async()=>{await fn(Ue,async()=>{await Qt("fx")})}),ve.addEventListener("input",()=>{Ft("coin")}),Ce.addEventListener("input",()=>{Ft("fx")}),Ye.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),l=Ut(String(n.dataset.size||""));o&&Number.isFinite(s)&&!(s<=0)&&l?await fn(n,async()=>{const e=await i.closeCoinPosition({symbol:o,side:a,positionId:s,size:l});if(!e?.ok){const t=ln(e?.error||"coin close order failed");return an("[coin] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:l,error:t}),cn("error",t),void await dn("coin.closeOrder",t,e)}an("[coin] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:l,result:e.result||null}),cn("success","決済注文を送信しました。"),await Qt("coin"),await tn("coin")}):cn("error","決済対象データが不正です。")}),Re.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),l=String(n.dataset.size||"").trim();o&&Number.isFinite(s)&&!(s<=0)&&l?await fn(n,async()=>{const e=await i.closeFxPosition({symbol:o,side:a,positionId:s,size:l});if(!e?.ok){const t=ln(e?.error||"fx close order failed");return an("[fx] close order failed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:l,error:t}),cn("error",t),void await dn("fx.closeOrder",t,e)}an("[fx] close order placed",{symbol:o,positionSide:r,closeSide:a,positionId:s,size:l,result:e.result||null}),cn("success","決済注文を送信しました。"),await Qt("fx"),await tn("fx")}):cn("error","決済対象データが不正です。")}),Ie.addEventListener("click",async()=>{await on("BUY",Ie)}),Ae.addEventListener("click",async()=>{await on("SELL",Ae)}),Be.addEventListener("click",async()=>{await nn("BUY",Be)}),Le.addEventListener("click",async()=>{await nn("SELL",Le)}),Ke.addEventListener("click",async()=>{await fn(Ke,async()=>{await tn("coin")})}),_e.addEventListener("click",async()=>{await fn(_e,async()=>{await tn("coin")})}),We.addEventListener("click",async()=>{await fn(We,async()=>{await tn("fx")})}),Ge.addEventListener("click",async()=>{await fn(Ge,async()=>{await tn("fx")})}),M.addEventListener("submit",async e=>{if(e.preventDefault(),!window.botApi?.sendChat)return;const t=String(P.value||"").trim();if(t){sn("user",t),P.value="",D.disabled=!0;try{const e=await window.botApi.sendChat(t);if(!e.ok)return sn("assistant",`Error: ${e.error||"unknown"}`),cn("error",ln(e.error)),void await dn("ai.chat",ln(e.error),e);sn("assistant",String(e.answer||"")),mn()}catch(e){const t=ln(e instanceof Error?e.message:String(e));sn("assistant",`Error: ${t}`),cn("error",t),await dn("ai.chat.exception",t,e)}finally{D.disabled=!1}}}),window.addEventListener("error",e=>{const t=ln(e.error?.message||e.message||"Unknown UI error");an("[ui] uncaught error",{error:t}),cn("error",t),dn("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=ln(t);an("[ui] unhandled rejection",{error:n}),cn("error",n),dn("ui.unhandledrejection",n,e.reason)}),window.botApi&&(window.botApi.onStatus(e=>{Bt(String(e.status||""))}),window.botApi.onChatEvent(e=>{if(!e)return;const t=Et(e);if("line_user"===t.type){const e=t.lineUserId?`LINE(${String(t.lineUserId)})`:"LINE";return void sn("line-user",String(t.text||""),e)}if("line_assistant"===t.type){const e=t.lineUserId?`AI->LINE(${String(t.lineUserId)})`:"AI->LINE";sn("line-assistant",String(t.text||""),e)}}),window.botApi.onTradeExecution(e=>{if(!e)return;const t=Et(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(),l="CLOSE"===r?"決済":"新規",c=`${i} ${o} ${a}${s?` @ ${s}`:""}`;an(`[${n}] execution filled`,{symbol:i,side:o,settleType:r,size:a,price:s,raw:t}),cn("success",`約定成功 (${l}) ${c}`.trim()),Qt(n).catch(()=>{}),tn(n).catch(()=>{})}));const En=async()=>{if(at&&!st&&!lt){lt=!0;try{if(!await async function(){let e=0;if(Jt("coin",et),Jt("fx",tt),zt("coin")){e+=1;try{await Ot("coin",et)}catch(e){const t=ln(e instanceof Error?e.message:String(e));an("[coin] quote fetch failed",{error:t}),cn("error",t)}await kn()}if(zt("fx")){e+=1;try{await Ot("fx",tt)}catch(e){const t=ln(e instanceof Error?e.message:String(e));an("[fx] quote fetch failed",{error:t}),cn("error",t)}await Sn()}if(window.botApi?.getSymbolRules&&zt("coin"))try{const e=await window.botApi.getSymbolRules({market:"coin"});if(e?.ok&&Array.isArray(e.rules)){wt.clear();for(const t of e.rules){const e=String(t.symbol||"").trim().toUpperCase();e&&wt.set(e,{minOrderSize:String(t.minOrderSize||""),maxOrderSize:String(t.maxOrderSize||""),sizeStep:String(t.sizeStep||"")})}}}catch(e){an("[coin] symbol rules fetch failed",{error:e instanceof Error?e.message:String(e)})}return e>0}())return;await Qt(),await tn(),zt("coin")&&!ot&&(ot=setInterval(()=>{Vt("coin")},1e3)),zt("fx")&&!rt&&(rt=setInterval(()=>{Vt("fx")},1e3)),st=!0}finally{lt=!1}}};At(!1),(()=>{try{Ct("true"===window.localStorage.getItem(vt))}catch{Ct(!1)}})(),(async()=>{if(!window.botApi?.getSavedProfile)return;const e=await window.botApi.getSavedProfile();e?.ok&&e.profile&&(s.value=e.profile.botId||"",l.value=e.profile.email||"",c.value=e.profile.password||"",d.checked=Boolean(e.profile.remember))})(),(async()=>{if(!window.botApi?.getSkillConfig||!window.botApi.getAiConfig)return;const[e,t,n]=await Promise.all([window.botApi.getSkillConfig("@ai.weget.jp/skill-gmo-fx"),window.botApi.getSkillConfig("@ai.weget.jp/skill-gmo-coin"),window.botApi.getAiConfig()]);e?.ok&&e.config&&(F.value=String(e.config.riskDailyLossLimitJpy||5e4),J.value=String(e.config.fxApiKey||""),q.value=String(e.config.fxApiSecret||"")),t?.ok&&t.config&&(j.value=String(t.config.riskDailyLossLimitJpy||5e4),z.value=String(t.config.cryptoApiKey||""),O.value=String(t.config.cryptoApiSecret||"")),n?.ok&&n.config&&(te.value=n.config.aiModel||"gpt-5.4",ne.value=n.config.logOutputDir||"")})(),wn();export{};
@@ -58,24 +58,47 @@
58
58
  </div>
59
59
  </header>
60
60
 
61
- <nav class="tabs-shell">
62
- <div class="tab-group">
63
- <span class="tab-group-label">Host</span>
64
- <div class="tabs">
65
- <button type="button" class="tab-btn" data-tab="config">Host Control</button>
66
- <button type="button" class="tab-btn" data-tab="ai">Codex Console</button>
61
+ <div class="app-layout">
62
+ <aside id="sidebar-nav" class="sidebar-nav">
63
+ <div class="sidebar-head">
64
+ <strong>Console</strong>
65
+ <button id="sidebar-toggle-btn" type="button" class="sidebar-toggle-btn secondary" aria-label="Toggle menu">◀</button>
67
66
  </div>
68
- </div>
69
- <div class="tab-group">
70
- <span class="tab-group-label">Installed Skills</span>
71
- <div class="tabs">
72
- <button id="tab-btn-skill-gmo-coin" type="button" class="tab-btn is-active" data-tab="coin">skill-gmo-coin</button>
73
- <button id="tab-btn-skill-gmo-fx" type="button" class="tab-btn" data-tab="fx">skill-gmo-fx</button>
74
- <button id="tab-btn-skill-browser" type="button" class="tab-btn" data-tab="browser">skill-browser</button>
75
- </div>
76
- </div>
77
- </nav>
67
+ <nav class="tabs-shell">
68
+ <div class="tab-group">
69
+ <span class="tab-group-label">Host</span>
70
+ <div class="tabs">
71
+ <button type="button" class="tab-btn" data-tab="config" data-icon="HC">
72
+ <span class="tab-btn-icon">HC</span>
73
+ <span class="tab-btn-label">Host Control</span>
74
+ </button>
75
+ <button type="button" class="tab-btn" data-tab="ai" data-icon="AI">
76
+ <span class="tab-btn-icon">AI</span>
77
+ <span class="tab-btn-label">Codex Console</span>
78
+ </button>
79
+ </div>
80
+ </div>
81
+ <div class="tab-group">
82
+ <span class="tab-group-label">Installed Skills</span>
83
+ <div class="tabs">
84
+ <button id="tab-btn-skill-gmo-coin" type="button" class="tab-btn is-active" data-tab="coin" data-icon="C">
85
+ <span class="tab-btn-icon">C</span>
86
+ <span class="tab-btn-label">skill-gmo-coin</span>
87
+ </button>
88
+ <button id="tab-btn-skill-gmo-fx" type="button" class="tab-btn" data-tab="fx" data-icon="FX">
89
+ <span class="tab-btn-icon">FX</span>
90
+ <span class="tab-btn-label">skill-gmo-fx</span>
91
+ </button>
92
+ <button id="tab-btn-skill-browser" type="button" class="tab-btn" data-tab="browser" data-icon="Br">
93
+ <span class="tab-btn-icon">Br</span>
94
+ <span class="tab-btn-label">skill-browser</span>
95
+ </button>
96
+ </div>
97
+ </div>
98
+ </nav>
99
+ </aside>
78
100
 
101
+ <section class="content-shell">
79
102
  <section id="tab-coin" class="tab-panel is-active">
80
103
  <section class="panel market-shell coin-theme">
81
104
  <div class="skill-surface-head">
@@ -211,6 +234,27 @@
211
234
  </table>
212
235
  </section>
213
236
  </div>
237
+ <section class="panel host-section">
238
+ <h3 id="coin-config-title">Coin Skill Config</h3>
239
+ <label>
240
+ <span id="coin-config-label-riskDailyLossLimitJpy">Coin Risk Daily Loss Limit (JPY)</span>
241
+ <input id="coin-risk-daily-loss-limit-jpy" type="number" min="1" step="1" />
242
+ </label>
243
+ <label>
244
+ <span id="coin-config-label-cryptoApiKey">Crypto API Key</span>
245
+ <input id="crypto-api-key" type="text" />
246
+ </label>
247
+ <label>
248
+ <span id="coin-config-label-cryptoApiSecret">Crypto API Secret</span>
249
+ <div class="inline-input-row">
250
+ <input id="crypto-api-secret" type="password" />
251
+ <button id="toggle-crypto-secret-btn" type="button" class="secondary">显示</button>
252
+ </div>
253
+ </label>
254
+ <div class="row config-actions">
255
+ <button id="save-coin-config-btn" type="button">Save Coin Skill Config</button>
256
+ </div>
257
+ </section>
214
258
  </section>
215
259
  </section>
216
260
 
@@ -351,6 +395,27 @@
351
395
  </table>
352
396
  </section>
353
397
  </div>
398
+ <section class="panel host-section">
399
+ <h3 id="fx-config-title">FX Skill Config</h3>
400
+ <label>
401
+ <span id="fx-config-label-riskDailyLossLimitJpy">FX Risk Daily Loss Limit (JPY)</span>
402
+ <input id="fx-risk-daily-loss-limit-jpy" type="number" min="1" step="1" />
403
+ </label>
404
+ <label>
405
+ <span id="fx-config-label-fxApiKey">FX API Key</span>
406
+ <input id="fx-api-key" type="text" />
407
+ </label>
408
+ <label>
409
+ <span id="fx-config-label-fxApiSecret">FX API Secret</span>
410
+ <div class="inline-input-row">
411
+ <input id="fx-api-secret" type="password" />
412
+ <button id="toggle-fx-secret-btn" type="button" class="secondary">显示</button>
413
+ </div>
414
+ </label>
415
+ <div class="row config-actions">
416
+ <button id="save-fx-config-btn" type="button">Save FX Skill Config</button>
417
+ </div>
418
+ </section>
354
419
  </section>
355
420
  </section>
356
421
 
@@ -367,15 +432,10 @@
367
432
  <span id="browser-skill-version" class="skill-surface-badge is-muted">builtin</span>
368
433
  </div>
369
434
  </div>
370
- <div class="browser-skill-shell">
371
- <div class="browser-skill-card">
372
- <h3>Local UI Status</h3>
373
- <p class="muted">这个 skill 已经作为公开包安装进宿主,但当前本地页仍是 scaffold,占位用于后续接 browser runtime。</p>
374
- </div>
375
- <div class="browser-skill-card">
376
- <h3>Declared Tools</h3>
377
- <div id="browser-skill-tools" class="skill-tags"></div>
378
- </div>
435
+ <div id="browser-skill-cards" class="browser-skill-shell"></div>
436
+ <div class="browser-skill-card">
437
+ <h3>Declared Tools</h3>
438
+ <div id="browser-skill-tools" class="skill-tags"></div>
379
439
  </div>
380
440
  </section>
381
441
  </section>
@@ -426,50 +486,18 @@
426
486
  <label>
427
487
  <span>模型</span>
428
488
  <select id="ai-model">
429
- <option value="gpt-4.1-mini">gpt-4.1-mini</option>
489
+ <option value="gpt-5.4">gpt-5.4</option>
490
+ <option value="gpt-5-mini">gpt-5-mini</option>
430
491
  <option value="gpt-4.1">gpt-4.1</option>
431
- <option value="gpt-4o-mini">gpt-4o-mini</option>
432
492
  </select>
433
493
  </label>
434
- <div class="row config-actions">
435
- <button id="save-ai-config-btn" type="button">保存 AI 设置</button>
436
- <button id="logout-btn" type="button" class="secondary">Logout</button>
437
- </div>
438
- </section>
439
- <section class="host-section">
440
- <h3>GMO Credentials</h3>
441
- <label>
442
- <span>Error Log Directory</span>
443
- <input id="error-log-dir" type="text" placeholder="e.g. D:\\weget-bot\\logs" />
444
- </label>
445
- <label>
446
- <span>Risk Daily Loss Limit (JPY)</span>
447
- <input id="risk-daily-loss-limit-jpy" type="number" min="1" step="1" />
448
- </label>
449
494
  <label>
450
- <span>Crypto API Key</span>
451
- <input id="crypto-api-key" type="text" />
452
- </label>
453
- <label>
454
- <span>Crypto API Secret</span>
455
- <div class="inline-input-row">
456
- <input id="crypto-api-secret" type="password" />
457
- <button id="toggle-crypto-secret-btn" type="button" class="secondary">显示</button>
458
- </div>
459
- </label>
460
- <label>
461
- <span>FX API Key</span>
462
- <input id="fx-api-key" type="text" />
463
- </label>
464
- <label>
465
- <span>FX API Secret</span>
466
- <div class="inline-input-row">
467
- <input id="fx-api-secret" type="password" />
468
- <button id="toggle-fx-secret-btn" type="button" class="secondary">显示</button>
469
- </div>
495
+ <span>Log Output Directory</span>
496
+ <input id="host-log-output-dir" type="text" placeholder="e.g. D:\\weget-bot\\logs" />
470
497
  </label>
471
498
  <div class="row config-actions">
472
- <button id="save-api-config-btn" type="button">Save API Keys</button>
499
+ <button id="save-ai-config-btn" type="button">保存 AI 设置</button>
500
+ <button id="logout-btn" type="button" class="secondary">Logout</button>
473
501
  </div>
474
502
  </section>
475
503
  <section class="host-section skill-panel">
@@ -481,6 +509,8 @@
481
509
  </section>
482
510
  </section>
483
511
  </section>
512
+ </section>
513
+ </div>
484
514
  </main>
485
515
  <script type="module" src="./app.js"></script>
486
516
  </body>
@@ -6,11 +6,72 @@ body {
6
6
  }
7
7
 
8
8
  .app-shell {
9
- max-width: 980px;
9
+ max-width: 1400px;
10
10
  margin: 16px auto;
11
11
  padding: 0 12px 16px;
12
12
  }
13
13
 
14
+ .app-layout {
15
+ display: grid;
16
+ grid-template-columns: 260px minmax(0, 1fr);
17
+ gap: 14px;
18
+ align-items: start;
19
+ }
20
+
21
+ .content-shell {
22
+ min-width: 0;
23
+ }
24
+
25
+ .sidebar-nav {
26
+ position: sticky;
27
+ top: 16px;
28
+ display: grid;
29
+ gap: 12px;
30
+ padding: 14px;
31
+ border-radius: 14px;
32
+ background: linear-gradient(180deg, #122033 0%, #18283f 100%);
33
+ color: #dce7f5;
34
+ box-shadow: 0 10px 24px rgba(20, 25, 45, 0.18);
35
+ }
36
+
37
+ .sidebar-head {
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ gap: 10px;
42
+ }
43
+
44
+ .sidebar-toggle-btn {
45
+ min-width: 38px;
46
+ padding: 8px 10px;
47
+ background: rgba(255, 255, 255, 0.12);
48
+ }
49
+
50
+ .app-shell.sidebar-collapsed .app-layout {
51
+ grid-template-columns: 76px minmax(0, 1fr);
52
+ }
53
+
54
+ .app-shell.sidebar-collapsed .sidebar-nav {
55
+ padding-inline: 10px;
56
+ }
57
+
58
+ .app-shell.sidebar-collapsed .sidebar-head strong,
59
+ .app-shell.sidebar-collapsed .tab-group-label {
60
+ display: none;
61
+ }
62
+
63
+ .app-shell.sidebar-collapsed .tab-btn {
64
+ width: 52px;
65
+ min-width: 52px;
66
+ min-height: 44px;
67
+ padding: 8px 6px;
68
+ justify-items: center;
69
+ }
70
+
71
+ .app-shell.sidebar-collapsed .tab-btn-label {
72
+ display: none;
73
+ }
74
+
14
75
  .user-notice {
15
76
  position: fixed;
16
77
  top: 14px;
@@ -173,7 +234,6 @@ body {
173
234
  .tabs-shell {
174
235
  display: grid;
175
236
  gap: 10px;
176
- margin-bottom: 12px;
177
237
  }
178
238
 
179
239
  .tab-group {
@@ -190,9 +250,8 @@ body {
190
250
  }
191
251
 
192
252
  .tabs {
193
- display: flex;
253
+ display: grid;
194
254
  gap: 8px;
195
- flex-wrap: wrap;
196
255
  }
197
256
 
198
257
  .tab-btn {
@@ -202,6 +261,11 @@ body {
202
261
  border-radius: 8px;
203
262
  padding: 8px 12px;
204
263
  cursor: pointer;
264
+ text-align: left;
265
+ display: grid;
266
+ grid-template-columns: auto 1fr;
267
+ align-items: center;
268
+ gap: 10px;
205
269
  }
206
270
 
207
271
  .tab-btn.is-active {
@@ -210,6 +274,31 @@ body {
210
274
  border-color: #2359d1;
211
275
  }
212
276
 
277
+ .tab-btn-icon {
278
+ width: 28px;
279
+ height: 28px;
280
+ display: inline-grid;
281
+ place-items: center;
282
+ border-radius: 8px;
283
+ background: rgba(35, 89, 209, 0.12);
284
+ color: #2359d1;
285
+ font-size: 12px;
286
+ font-weight: 700;
287
+ line-height: 1;
288
+ }
289
+
290
+ .tab-btn.is-active .tab-btn-icon {
291
+ background: rgba(255, 255, 255, 0.18);
292
+ color: #ffffff;
293
+ }
294
+
295
+ .tab-btn-label {
296
+ min-width: 0;
297
+ overflow: hidden;
298
+ text-overflow: ellipsis;
299
+ white-space: nowrap;
300
+ }
301
+
213
302
  .tab-panel {
214
303
  display: none;
215
304
  }
@@ -318,6 +407,17 @@ body {
318
407
  border-color: #d8dee8;
319
408
  }
320
409
 
410
+ .tab-btn.is-active.is-disabled {
411
+ background: #2359d1;
412
+ color: #fff;
413
+ border-color: #2359d1;
414
+ }
415
+
416
+ .tab-btn.is-active.is-disabled .tab-btn-icon {
417
+ background: rgba(255, 255, 255, 0.18);
418
+ color: #ffffff;
419
+ }
420
+
321
421
  .market-layout {
322
422
  display: grid;
323
423
  grid-template-columns: 1fr 340px;
@@ -1137,6 +1237,24 @@ button.is-loading::before {
1137
1237
  }
1138
1238
 
1139
1239
  @media (max-width: 980px) {
1240
+ .app-layout {
1241
+ grid-template-columns: 1fr;
1242
+ }
1243
+
1244
+ .sidebar-nav {
1245
+ position: static;
1246
+ }
1247
+
1248
+ .app-shell.sidebar-collapsed .app-layout {
1249
+ grid-template-columns: 1fr;
1250
+ }
1251
+
1252
+ .app-shell.sidebar-collapsed .sidebar-head strong,
1253
+ .app-shell.sidebar-collapsed .tab-group-label,
1254
+ .app-shell.sidebar-collapsed .tab-btn:not(.is-active) {
1255
+ display: initial;
1256
+ }
1257
+
1140
1258
  .positions-grid {
1141
1259
  grid-template-columns: 1fr;
1142
1260
  }
@@ -1 +1 @@
1
- const t=t=>t&&"object"==typeof t?t:{},e=(t,o="")=>{if(null==t)return t;const r=String(o||"");if("string"==typeof t)return/(password|token|authorization|api_?key|secret|cookie|jwt)/i.test(r)?t.length<=8?"****":`${t.slice(0,3)}****${t.slice(-3)}`:t.length>2e3?`${t.slice(0,2e3)}...(truncated)`:t;if("number"==typeof t||"boolean"==typeof t)return t;if(Array.isArray(t)){const o=t.slice(0,50).map(t=>e(t,r));return t.length>50&&o.push(`...(${t.length-50} more items)`),o}if("object"==typeof t){const o={};for(const[r,i]of Object.entries(t))o[r]=e(i,r);return o}return String(t)};export const createAuthApiService=({getEnv:o,emitLog:r,getCurrentSession:i})=>{const n=async e=>{const o=await e.text();if(!o)return{};try{const e=JSON.parse(o);return t(e)}catch{return{raw:o}}};return{signInWithLoginApi:async(e,i)=>{const s=o();if(!s.loginApiUrl)throw new Error("Missing loginApiUrl in local config");const l=new AbortController,a=setTimeout(()=>l.abort(),15e3);let c;r("[auth] login api request",{url:s.loginApiUrl,email:e});try{c=await fetch(s.loginApiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e,password:i,client:"bot"}),signal:l.signal})}catch(e){const o=t(t(e).cause),r=o?`${String(o.code||"")} ${String(o.syscall||"")} ${String(o.hostname||"")}`.trim():"";throw new Error("login fetch failed"+(r?`: ${r}`:""))}finally{clearTimeout(a)}r("[auth] login api response",{status:c.status});const p=await n(c);if(!c.ok)throw new Error(String(p.error||p.message||p.msg||"Login failed"));return((e,o)=>{const r=t(e),i=t(r.data),n=t(r.user),s=t(i.user),l=r.access_token||r.token||r.jwt||i.access_token||i.token;if(!l)throw new Error("Login API response missing token field (access_token/token/jwt)");const a=r.user_id||n.id||r.sub||i.user_id||s.id||"",c=r.email||n.email||i.email||s.email||o,p=r.chat_model||i.chat_model||"gpt-4.1-mini",u=r.chat_system_prompt||i.chat_system_prompt||"";return{accessToken:String(l),userId:String(a),email:String(c),chatModel:String(p),chatSystemPrompt:String(u)}})(p,e)},callBotApi:async(t,o)=>{if(!t)throw new Error("bot control api url is missing");const s=i();if(!s?.accessToken)throw new Error("bot session access token missing");r("[bot-api] request",{url:t,bot_id:s.botId||"",body:e(o)});const l=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s.accessToken}`},body:JSON.stringify(o||{})}),a=await n(l);if(r("[bot-api] response",{url:t,status:l.status,ok:l.ok,body:e(a)}),!l.ok)throw new Error(String(a.error||a.message||`api failed: ${l.status}`));return a},requestBotImageUploadUrl:async({filename:t,contentType:s})=>{const l=(()=>{const t=o();return t.botUploadApiUrl?t.botUploadApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/upload-url"):""})();if(!l)throw new Error("Missing upload/login api url in local config");const a=i();if(!a?.accessToken)throw new Error("bot session access token missing");r("[bot-api] request",{url:l,bot_id:a.botId||"",body:e({bot_id:a.botId||"",filename:t,content_type:s})});const c=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a.accessToken}`},body:JSON.stringify({bot_id:a.botId||"",filename:t,content_type:s})}),p=await n(c);if(r("[bot-api] response",{url:l,status:c.status,ok:c.ok,body:e(p)}),!c.ok)throw new Error(String(p.error||p.message||"failed to request bot upload url"));if(!p.upload_url||!p.preview_url)throw new Error("upload url api response missing upload_url or preview_url");return{upload_url:String(p.upload_url),preview_url:String(p.preview_url),file_url:p.file_url?String(p.file_url):void 0}},uploadImageToSignedUrl:async({uploadUrl:t,contentType:e,bytes:o})=>{const r=await fetch(t,{method:"PUT",headers:{"Content-Type":e},body:o});if(!r.ok){const t=await r.text();throw new Error(`s3 upload failed: ${r.status} ${t}`)}},getBotRuntimeConfigApiUrl:()=>{const t=o();return t.botRuntimeConfigApiUrl?t.botRuntimeConfigApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/runtime-config"):""},getBotSkillsListApiUrl:()=>{const t=o();return t.botSkillsListApiUrl?t.botSkillsListApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/skills/list"):""}}};
1
+ const t=t=>t&&"object"==typeof t?t:{},e=(t,o="")=>{if(null==t)return t;const r=String(o||"");if("string"==typeof t)return/(password|token|authorization|api_?key|secret|cookie|jwt)/i.test(r)?t.length<=8?"****":`${t.slice(0,3)}****${t.slice(-3)}`:t.length>2e3?`${t.slice(0,2e3)}...(truncated)`:t;if("number"==typeof t||"boolean"==typeof t)return t;if(Array.isArray(t)){const o=t.slice(0,50).map(t=>e(t,r));return t.length>50&&o.push(`...(${t.length-50} more items)`),o}if("object"==typeof t){const o={};for(const[r,i]of Object.entries(t))o[r]=e(i,r);return o}return String(t)};export const createAuthApiService=({getEnv:o,emitLog:r,getCurrentSession:i})=>{const n=async e=>{const o=await e.text();if(!o)return{};try{const e=JSON.parse(o);return t(e)}catch{return{raw:o}}};return{signInWithLoginApi:async(e,i)=>{const s=o();if(!s.loginApiUrl)throw new Error("Missing loginApiUrl in local config");const l=new AbortController,a=setTimeout(()=>l.abort(),15e3);let c;r("[auth] login api request",{url:s.loginApiUrl,email:e});try{c=await fetch(s.loginApiUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e,password:i,client:"bot"}),signal:l.signal})}catch(e){const o=t(t(e).cause),r=o?`${String(o.code||"")} ${String(o.syscall||"")} ${String(o.hostname||"")}`.trim():"";throw new Error("login fetch failed"+(r?`: ${r}`:""))}finally{clearTimeout(a)}r("[auth] login api response",{status:c.status});const p=await n(c);if(!c.ok)throw new Error(String(p.error||p.message||p.msg||"Login failed"));return((e,o)=>{const r=t(e),i=t(r.data),n=t(r.user),s=t(i.user),l=r.access_token||r.token||r.jwt||i.access_token||i.token;if(!l)throw new Error("Login API response missing token field (access_token/token/jwt)");const a=r.user_id||n.id||r.sub||i.user_id||s.id||"",c=r.email||n.email||i.email||s.email||o,p=r.chat_model||i.chat_model||"gpt-5.4",u=r.chat_system_prompt||i.chat_system_prompt||"";return{accessToken:String(l),userId:String(a),email:String(c),chatModel:String(p),chatSystemPrompt:String(u)}})(p,e)},callBotApi:async(t,o)=>{if(!t)throw new Error("bot control api url is missing");const s=i();if(!s?.accessToken)throw new Error("bot session access token missing");r("[bot-api] request",{url:t,bot_id:s.botId||"",body:e(o)});const l=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s.accessToken}`},body:JSON.stringify(o||{})}),a=await n(l);if(r("[bot-api] response",{url:t,status:l.status,ok:l.ok,body:e(a)}),!l.ok)throw new Error(String(a.error||a.message||`api failed: ${l.status}`));return a},requestBotImageUploadUrl:async({filename:t,contentType:s})=>{const l=(()=>{const t=o();return t.botUploadApiUrl?t.botUploadApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/upload-url"):""})();if(!l)throw new Error("Missing upload/login api url in local config");const a=i();if(!a?.accessToken)throw new Error("bot session access token missing");r("[bot-api] request",{url:l,bot_id:a.botId||"",body:e({bot_id:a.botId||"",filename:t,content_type:s})});const c=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a.accessToken}`},body:JSON.stringify({bot_id:a.botId||"",filename:t,content_type:s})}),p=await n(c);if(r("[bot-api] response",{url:l,status:c.status,ok:c.ok,body:e(p)}),!c.ok)throw new Error(String(p.error||p.message||"failed to request bot upload url"));if(!p.upload_url||!p.preview_url)throw new Error("upload url api response missing upload_url or preview_url");return{upload_url:String(p.upload_url),preview_url:String(p.preview_url),file_url:p.file_url?String(p.file_url):void 0}},uploadImageToSignedUrl:async({uploadUrl:t,contentType:e,bytes:o})=>{const r=await fetch(t,{method:"PUT",headers:{"Content-Type":e},body:o});if(!r.ok){const t=await r.text();throw new Error(`s3 upload failed: ${r.status} ${t}`)}},getBotRuntimeConfigApiUrl:()=>{const t=o();return t.botRuntimeConfigApiUrl?t.botRuntimeConfigApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/runtime-config"):""},getBotSkillsListApiUrl:()=>{const t=o();return t.botSkillsListApiUrl?t.botSkillsListApiUrl:t.loginApiUrl?t.loginApiUrl.replace(/\/login\/?$/i,"/bot/skills/list"):""}}};
@@ -1 +1 @@
1
- import t from"node:fs/promises";import e from"node:os";import o from"node:path";import{spawn as r}from"node:child_process";const n="gpt-4.1-mini",i=async t=>{if(!t)return"";const e=[];for await(const o of t)e.push(Buffer.isBuffer(o)?o:Buffer.from(String(o)));return Buffer.concat(e).toString("utf8")},d=async({args:t,stdinText:e,cwd:o})=>new Promise((n,d)=>{const s=r("codex",t,{cwd:o,stdio:["pipe","pipe","pipe"],windowsHide:!0});let a=!1;const c=t=>{a||(a=!0,t())},u=i(s.stdout),l=i(s.stderr);s.on("error",t=>{c(()=>d(t))}),s.on("close",async t=>{const[e,o]=await Promise.all([u,l]);c(()=>n({stdout:e,stderr:o,exitCode:Number.isFinite(t)?Number(t):1}))}),"string"==typeof e&&s.stdin.write(e),s.stdin.end()});export const createCodexService=({getModel:i,getWorkingDir:s,emitLog:a})=>({runPrompt:async({prompt:r,model:c,contextLabel:u})=>{const l=String(c||i()||n).trim()||n,g=s(),m=await t.mkdtemp(o.join(e.tmpdir(),"weget-codex-")),f=o.join(m,"last-message.txt"),w=`${String(r||"").trim()}\n\nRespond as plain text only.`;a("[codex] exec start",{model:l,cwd:g,context:String(u||"").trim()});try{const e=await d({cwd:g,args:["exec","-C",g,"--skip-git-repo-check","-m",l,"-o",f,"-"],stdinText:w}),o=await t.readFile(f,"utf8").catch(()=>"");if(0!==e.exitCode)throw new Error((e.stderr||e.stdout||`codex exec failed with code ${e.exitCode}`).trim());const r=String(o||e.stdout||"").trim();if(!r)throw new Error("codex returned empty output");return a("[codex] exec completed",{model:l,context:String(u||"").trim()}),r}finally{await t.rm(m,{recursive:!0,force:!0}).catch(()=>{})}},getAuthStatus:async()=>{try{const t=await d({cwd:s(),args:["login","status"]}),e=String(t.stdout||t.stderr||"").trim();return 0!==t.exitCode?{status:"unknown",detail:e||"codex login status failed"}:/logged in/i.test(e)?{status:"logged_in",detail:e}:/not logged in|logged out/i.test(e)?{status:"logged_out",detail:e}:{status:"unknown",detail:e||"unknown codex auth state"}}catch(t){return{status:"unknown",detail:t instanceof Error?t.message:String(t)}}},startLogin:async()=>{const t=s();try{if("win32"===process.platform)return r("cmd.exe",["/c","start",'""',"codex","login","--device-auth"],{cwd:t,detached:!0,stdio:"ignore",windowsHide:!1}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in a new terminal window."};if("darwin"===process.platform){const e=`cd ${JSON.stringify(t)} && codex login --device-auth`;return r("open",["-a","Terminal",e],{cwd:t,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in Terminal."}}const e=`cd ${JSON.stringify(t)} && codex login --device-auth; exec $SHELL`,o=[["x-terminal-emulator",["-e","bash","-lc",e]],["gnome-terminal",["--","bash","-lc",e]],["konsole",["-e","bash","-lc",e]]];for(const[e,n]of o)try{return r(e,n,{cwd:t,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:`Started \`codex login --device-auth\` using ${e}.`}}catch{}return{ok:!1,detail:"No supported terminal launcher was found. Run `codex login --device-auth` manually."}}catch(t){const e=t instanceof Error?t.message:String(t);return a("[codex] login launch failed",{error:e}),{ok:!1,detail:e}}}});
1
+ import e from"node:fs/promises";import{existsSync as t}from"node:fs";import o from"node:os";import n from"node:path";import{spawn as r,spawnSync as i}from"node:child_process";const s="gpt-5.4";let c=null;const a=async e=>{if(!e)return"";const t=[];for await(const o of e)t.push(Buffer.isBuffer(o)?o:Buffer.from(String(o)));return Buffer.concat(t).toString("utf8")},d=async({args:e,stdinText:o,cwd:s})=>new Promise((d,l)=>{const u=(()=>{if(c)return c;if("win32"===process.platform){const e=[String(process.env.ProgramFiles||"C:\\Program Files"),String(process.env["ProgramFiles(x86)"]||"C:\\Program Files (x86)")];for(const o of e){const e=n.join(o,"nodejs","node.exe"),r=n.join(o,"nodejs","node_modules","@openai","codex","bin","codex.js");if(t(e)&&t(r))return c={command:e,baseArgs:[r]},c}const o=["codex.cmd","codex"];for(const e of o){const t=i("where.exe",[e],{windowsHide:!0,encoding:"utf8"}),o=String(t.stdout||"").split(/\r?\n/).map(e=>e.trim()).find(Boolean);if(o)return c={command:o,baseArgs:[]},c}}return c={command:"codex",baseArgs:[]},c})(),g={...process.env,NO_COLOR:"1",FORCE_COLOR:"0",CLICOLOR:"0"},m=r(u.command,[...u.baseArgs,...e],{cwd:s,stdio:["pipe","pipe","pipe"],windowsHide:!0,env:g});let p=!1;const f=e=>{p||(p=!0,e())},w=a(m.stdout),x=a(m.stderr);m.on("error",e=>{f(()=>l(e))}),m.on("close",async e=>{const[t,o]=await Promise.all([w,x]);f(()=>d({stdout:t,stderr:o,exitCode:Number.isFinite(e)?Number(e):1}))}),"string"==typeof o&&m.stdin.write(o),m.stdin.end()});export const createCodexService=({getModel:t,getWorkingDir:i,emitLog:c})=>({runPrompt:async({prompt:r,model:a,contextLabel:l})=>{const u=String(a||t()||s).trim()||s,g=i(),m=await e.mkdtemp(n.join(o.tmpdir(),"weget-codex-")),p=n.join(m,"last-message.txt"),f=`${String(r||"").trim()}\n\nRespond as plain text only.`;c("[codex] exec start",{model:u,cwd:g,context:String(l||"").trim()});try{const t=await d({cwd:g,args:["exec","-C",g,"--skip-git-repo-check","-m",u,"-o",p,"-"],stdinText:f}),o=await e.readFile(p,"utf8").catch(()=>"");if(0!==t.exitCode)throw new Error((t.stderr||t.stdout||`codex exec failed with code ${t.exitCode}`).trim());const n=String(o||t.stdout||"").trim();if(!n)throw new Error("codex returned empty output");return c("[codex] exec completed",{model:u,context:String(l||"").trim()}),n}finally{await e.rm(m,{recursive:!0,force:!0}).catch(()=>{})}},getAuthStatus:async()=>{try{const e=await d({cwd:i(),args:["login","status"]}),t=String(e.stdout||e.stderr||"").trim();return 0!==e.exitCode?{status:"unknown",detail:t||"codex login status failed"}:/logged in/i.test(t)?{status:"logged_in",detail:t}:/not logged in|logged out/i.test(t)?{status:"logged_out",detail:t}:{status:"unknown",detail:t||"unknown codex auth state"}}catch(e){return{status:"unknown",detail:e instanceof Error?e.message:String(e)}}},startLogin:async()=>{const t=i();try{if("win32"===process.platform){const i=String(t||"").replace(/'/g,"''"),s=n.join(o.tmpdir(),`weget-codex-login-${Date.now()}.ps1`),c=["$OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)","chcp 65001 > $null",'$env:NO_COLOR = "1"','$env:FORCE_COLOR = "0"','$env:CLICOLOR = "0"','$env:LANG = "en_US.UTF-8"','$env:LC_ALL = "en_US.UTF-8"',`Set-Location -LiteralPath '${i}'`,"$stripAnsi = {"," param([string]$line)",' if ($null -eq $line) { return "" }',' return ($line -replace "`e\\[[0-9;?]*[ -/]*[@-~]", "")',"}","codex login --device-auth 2>&1 | ForEach-Object { & $stripAnsi ([string]$_) }"].join(`${o.EOL}`);await e.writeFile(s,c,"utf8");const a=["$script = @()",'$script += "-NoExit"','$script += "-ExecutionPolicy"','$script += "Bypass"','$script += "-File"',`$script += '${s.replace(/'/g,"''")}'`,`Start-Process -FilePath 'powershell.exe' -WorkingDirectory '${i}' -ArgumentList $script`].join("; ");return r("powershell.exe",["-NoProfile","-ExecutionPolicy","Bypass","-Command",a],{cwd:t,stdio:"ignore",windowsHide:!0}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in a new PowerShell window with UTF-8 output."}}if("darwin"===process.platform){const e=`cd ${JSON.stringify(t)} && codex login --device-auth`;return r("open",["-a","Terminal",e],{cwd:t,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:"Started `codex login --device-auth` in Terminal."}}const i=`cd ${JSON.stringify(t)} && codex login --device-auth; exec $SHELL`,s=[["x-terminal-emulator",["-e","bash","-lc",i]],["gnome-terminal",["--","bash","-lc",i]],["konsole",["-e","bash","-lc",i]]];for(const[e,o]of s)try{return r(e,o,{cwd:t,detached:!0,stdio:"ignore"}).unref(),{ok:!0,detail:`Started \`codex login --device-auth\` using ${e}.`}}catch{}return{ok:!1,detail:"No supported terminal launcher was found. Run `codex login --device-auth` manually."}}catch(e){const t=e instanceof Error?e.message:String(e);return c("[codex] login launch failed",{error:t}),{ok:!1,detail:t}}}});
@@ -1 +1 @@
1
- import{createGmoCoinSkill as e}from"@ai.weget.jp/skill-gmo-coin";import{createGmoFxSkill as s}from"@ai.weget.jp/skill-gmo-fx";import{createSkillRegistry as t}from"./skills.js";const i=new Set(["active"]);export const createSkillHostState=({gmoFxConfigPath:o,gmoCoinConfigPath:a,log:n})=>{const r=t(),l=[s(o),e(a),{manifest:{name:"@ai.weget.jp/skill-browser",displayName:"Browser Skill",version:"0.1.0",description:"Browser automation skill scaffold",permissions:["browser:read"],tools:["browser.open_url","browser.extract_text","browser.screenshot"],hasUi:!0},createServices:()=>({})}];for(const e of l)r.register(e);let m=new Map;const p=e=>m.get(String(e||"").trim())||null,f=e=>{const s=p(e);return!s||Boolean(s.enabled)&&i.has(String(s.install_status||"").trim().toLowerCase())},g=()=>r.list().filter(e=>f(e.name)).map(e=>e.module.manifest);return{registry:r,getAvailableSkills:()=>r.list().map(e=>e.module.manifest),getActiveSkills:g,getManagedSkills:()=>r.list().map(e=>{const s=p(e.name);return{name:e.module.manifest.name,displayName:e.module.manifest.displayName,version:e.module.manifest.version,description:e.module.manifest.description,permissions:Array.isArray(e.module.manifest.permissions)?e.module.manifest.permissions:[],tools:Array.isArray(e.module.manifest.tools)?e.module.manifest.tools:[],hasUi:Boolean(e.module.manifest.hasUi),enabled:!s||f(e.name),installStatus:s?String(s.install_status||"active"):"builtin",configuredVersion:s?.version??null,configJson:s?.config_json&&"object"==typeof s.config_json?s.config_json:{}}}),getStateForSkill:p,isSkillEnabled:f,applySnapshots:e=>{m=new Map((Array.isArray(e)?e:[]).filter(e=>e&&"object"==typeof e&&String(e.package_name||"").trim()).map(e=>[String(e.package_name||"").trim(),e])),n?.("[skills] host state updated",{available:r.list().length,active:g().map(e=>e.name)})}}};
1
+ const e=new Set(["active"]);export const createSkillHostState=({log:a})=>{let t=new Map,s=new Map;const i=e=>t.get(String(e||"").trim())||null,r=a=>{const t=i(a);return t?Boolean(t.enabled)&&e.has(String(t.install_status||"").trim().toLowerCase()):s.has(String(a||"").trim())},n=()=>Array.from(s.values()).filter(e=>r(e.name));return{setAvailableSkills:e=>{s=new Map((Array.isArray(e)?e:[]).filter(e=>e&&"object"==typeof e&&String(e.name||"").trim()).map(e=>[String(e.name||"").trim(),e])),a?.("[skills] available skills updated",{count:s.size,skills:Array.from(s.keys())})},getAvailableSkills:()=>Array.from(s.values()),getActiveSkills:n,getManagedSkills:()=>{const e=new Set([...Array.from(s.keys()),...Array.from(t.keys())]);return Array.from(e).map(e=>{const a=s.get(e),i=t.get(e);return{name:e,displayName:a?.displayName||i?.skill_id||e,version:a?.version||i?.version||"unknown",description:a?.description,permissions:Array.isArray(a?.permissions)?[...a.permissions]:[],tools:Array.isArray(a?.tools)?[...a.tools]:[],hasUi:Boolean(a?.hasUi),enabled:r(e),installStatus:i?String(i.install_status||"active"):s.has(e)?"active":"uninstalled",configuredVersion:i?.version??a?.version??null,configJson:i?.config_json&&"object"==typeof i.config_json?i.config_json:{},installed:s.has(e)}})},getStateForSkill:i,isSkillEnabled:r,applySnapshots:e=>{t=new Map((Array.isArray(e)?e:[]).filter(e=>e&&"object"==typeof e&&String(e.package_name||"").trim()).map(e=>[String(e.package_name||"").trim(),e])),a?.("[skills] host state updated",{available:s.size,active:n().map(e=>e.name)})}}};
@@ -0,0 +1 @@
1
+ import e from"node:path";import a from"node:fs/promises";import*as t from"@ai.weget.jp/skill-browser";import*as i from"@ai.weget.jp/skill-gmo-coin";import*as n from"@ai.weget.jp/skill-gmo-fx";const r=async e=>{await a.mkdir(e,{recursive:!0})},l=[t,i,n];export const createSkillRuntimeManager=({installRoot:a,botId:t,log:i,api:n,getRuntimeConfig:s,emitSkillEvent:o})=>{let m=new Map;const c=async()=>{await r(a),await r(e.join(a,"data"))},g=t=>{const i=String(t||"").trim().replace(/^@/,"").replace(/[\/\\]/g,"__");return e.join(a,"data",`${i}.json`)},p=t=>{const i=String(t||"").trim().replace(/^@/,"").replace(/[\/\\]/g,"__");return e.join(a,"data",i)},u=async e=>{const a=e.default||e.skill||null;if(!a?.manifest?.name)return null;const l=String(a.manifest.name||"").trim();await r(p(l));const m={packageName:l,packageVersion:String(a.manifest.version||""),botId:t?.(),log:i,api:n,skillDataDir:p(l),configPath:g(l),runtimeConfig:s?.()||{},emitEvent:e=>o?.(l,e)},c="function"==typeof e.createRuntime?e.createRuntime:a.createRuntime,u=c?c(m):{};return{packageName:l,packageDir:"",version:String(a.manifest.version||""),manifest:a.manifest,ui:e.hostUi||a.hostUi||void 0,module:a,runtime:u}},k=async()=>{await c();const e=new Map;for(const a of l){let t=null;try{t=await u(a)}catch(e){i?.("[skills] failed to load bundled skill",{error:e instanceof Error?e.message:String(e)})}t&&e.set(t.packageName,t)}return m=e,Array.from(m.values())};return{ensureInstallRoot:c,installSkill:async({packageName:e,version:a})=>(i?.("[skills] install skipped for bundled skill",{packageName:e,version:a||null}),k()),uninstallSkill:async({packageName:e})=>(i?.("[skills] uninstall skipped for bundled skill",{packageName:e}),k()),refreshLoadedSkills:k,getLoadedSkill:e=>m.get(String(e||"").trim())||null,getLoadedSkills:()=>Array.from(m.values())}};
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@ai.weget.jp/bot",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "WeGet bot host for Codex-centered skill runtime",
6
+ "description": "WeGet bot host for Codex-centered skill runtime ",
7
7
  "author": "weget.jp",
8
8
  "bin": {
9
9
  "weget-bot": "bin/weget-bot.cjs"
@@ -28,10 +28,10 @@
28
28
  "build": "npm run build:ts"
29
29
  },
30
30
  "dependencies": {
31
- "@ai.weget.jp/skill-gmo-coin": "^0.1.0",
32
- "@ai.weget.jp/skill-gmo-core": "^0.1.0",
33
- "@ai.weget.jp/skill-gmo-fx": "^0.1.0",
34
- "@ai.weget.jp/skill-sdk": "^0.1.0",
31
+ "@ai.weget.jp/skill-browser": ">=0.1.0 <1",
32
+ "@ai.weget.jp/skill-gmo-coin": ">=0.1.2 <1",
33
+ "@ai.weget.jp/skill-gmo-fx": ">=0.1.2 <1",
34
+ "@ai.weget.jp/skill-sdk": ">=0.1.1 <1",
35
35
  "electron": "^31.7.7",
36
36
  "ws": "^8.18.3"
37
37
  },
@@ -11,6 +11,7 @@ try {
11
11
 
12
12
  const root = process.cwd();
13
13
  const srcDir = path.join(root, 'src');
14
+ const distDir = path.join(root, 'dist');
14
15
  const buildSrcDir = path.join(root, 'dist', 'src');
15
16
 
16
17
  const runTsc = () => {
@@ -86,6 +87,7 @@ const minifyBuildJs = async () => {
86
87
  };
87
88
 
88
89
  const main = async () => {
90
+ await fs.rm(distDir, { recursive: true, force: true });
89
91
  runTsc();
90
92
  await copyRendererAssets();
91
93
  await copyFileIfExists(path.join(srcDir, 'preload.cjs'), path.join(buildSrcDir, 'preload.cjs'));
@@ -1 +0,0 @@
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()}};
@@ -1 +0,0 @@
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()}};