@decibelsystems/tools 2.2.0-beta.0 → 3.1.0

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.
Files changed (435) hide show
  1. package/CHANGELOG.md +226 -0
  2. package/dist/agentCommands.d.ts +0 -1
  3. package/dist/agentCommands.d.ts.map +1 -1
  4. package/dist/agentCommands.js +19 -8
  5. package/dist/agentCommands.js.map +1 -1
  6. package/dist/agentPresence.d.ts +82 -0
  7. package/dist/agentPresence.d.ts.map +1 -1
  8. package/dist/agentPresence.js +194 -26
  9. package/dist/agentPresence.js.map +1 -1
  10. package/dist/agentic/index.d.ts +18 -8
  11. package/dist/agentic/index.d.ts.map +1 -1
  12. package/dist/agentic/index.js +18 -144
  13. package/dist/agentic/index.js.map +1 -1
  14. package/dist/agentic/types.d.ts +106 -0
  15. package/dist/agentic/types.d.ts.map +1 -1
  16. package/dist/agentic/types.js +77 -1
  17. package/dist/agentic/types.js.map +1 -1
  18. package/dist/architectAdrs.d.ts +27 -3
  19. package/dist/architectAdrs.d.ts.map +1 -1
  20. package/dist/architectAdrs.js +92 -4
  21. package/dist/architectAdrs.js.map +1 -1
  22. package/dist/artifacts.d.ts +60 -0
  23. package/dist/artifacts.d.ts.map +1 -0
  24. package/dist/artifacts.js +208 -0
  25. package/dist/artifacts.js.map +1 -0
  26. package/dist/asks.d.ts +93 -0
  27. package/dist/asks.d.ts.map +1 -0
  28. package/dist/asks.js +383 -0
  29. package/dist/asks.js.map +1 -0
  30. package/dist/chat.d.ts +47 -0
  31. package/dist/chat.d.ts.map +1 -0
  32. package/dist/chat.js +288 -0
  33. package/dist/chat.js.map +1 -0
  34. package/dist/client/transports.d.ts.map +1 -1
  35. package/dist/client/transports.js +13 -3
  36. package/dist/client/transports.js.map +1 -1
  37. package/dist/daemon.d.ts +33 -3
  38. package/dist/daemon.d.ts.map +1 -1
  39. package/dist/daemon.js +225 -52
  40. package/dist/daemon.js.map +1 -1
  41. package/dist/daemonConfig.d.ts +46 -0
  42. package/dist/daemonConfig.d.ts.map +1 -1
  43. package/dist/daemonConfig.js +57 -12
  44. package/dist/daemonConfig.js.map +1 -1
  45. package/dist/decibelPaths.d.ts.map +1 -1
  46. package/dist/decibelPaths.js +39 -6
  47. package/dist/decibelPaths.js.map +1 -1
  48. package/dist/domain/issue.d.ts +81 -0
  49. package/dist/domain/issue.d.ts.map +1 -0
  50. package/dist/domain/issue.js +106 -0
  51. package/dist/domain/issue.js.map +1 -0
  52. package/dist/domain/issueCodec.d.ts +39 -0
  53. package/dist/domain/issueCodec.d.ts.map +1 -0
  54. package/dist/domain/issueCodec.js +308 -0
  55. package/dist/domain/issueCodec.js.map +1 -0
  56. package/dist/domain/issueRepository.d.ts +124 -0
  57. package/dist/domain/issueRepository.d.ts.map +1 -0
  58. package/dist/domain/issueRepository.js +296 -0
  59. package/dist/domain/issueRepository.js.map +1 -0
  60. package/dist/domain/uid.d.ts +16 -0
  61. package/dist/domain/uid.d.ts.map +1 -0
  62. package/dist/domain/uid.js +43 -0
  63. package/dist/domain/uid.js.map +1 -0
  64. package/dist/facades/definitions.d.ts +0 -1
  65. package/dist/facades/definitions.d.ts.map +1 -1
  66. package/dist/facades/definitions.js +140 -116
  67. package/dist/facades/definitions.js.map +1 -1
  68. package/dist/facades/index.d.ts +2 -1
  69. package/dist/facades/index.d.ts.map +1 -1
  70. package/dist/facades/index.js +1 -1
  71. package/dist/facades/index.js.map +1 -1
  72. package/dist/facades/types.d.ts +17 -0
  73. package/dist/facades/types.d.ts.map +1 -1
  74. package/dist/hq/defineAgent.d.ts.map +1 -1
  75. package/dist/hq/defineAgent.js +7 -11
  76. package/dist/hq/defineAgent.js.map +1 -1
  77. package/dist/httpArgs.d.ts +11 -0
  78. package/dist/httpArgs.d.ts.map +1 -0
  79. package/dist/httpArgs.js +39 -0
  80. package/dist/httpArgs.js.map +1 -0
  81. package/dist/httpServer.d.ts +1 -12
  82. package/dist/httpServer.d.ts.map +1 -1
  83. package/dist/httpServer.js +298 -144
  84. package/dist/httpServer.js.map +1 -1
  85. package/dist/identity.d.ts +123 -0
  86. package/dist/identity.d.ts.map +1 -0
  87. package/dist/identity.js +312 -0
  88. package/dist/identity.js.map +1 -0
  89. package/dist/kernel.d.ts +46 -1
  90. package/dist/kernel.d.ts.map +1 -1
  91. package/dist/kernel.js +334 -26
  92. package/dist/kernel.js.map +1 -1
  93. package/dist/killSwitch.d.ts +117 -0
  94. package/dist/killSwitch.d.ts.map +1 -0
  95. package/dist/killSwitch.js +161 -0
  96. package/dist/killSwitch.js.map +1 -0
  97. package/dist/lib/agent-services/assumptions.d.ts.map +1 -1
  98. package/dist/lib/agent-services/assumptions.js +8 -2
  99. package/dist/lib/agent-services/assumptions.js.map +1 -1
  100. package/dist/lib/atomicWrite.d.ts +49 -0
  101. package/dist/lib/atomicWrite.d.ts.map +1 -0
  102. package/dist/lib/atomicWrite.js +157 -0
  103. package/dist/lib/atomicWrite.js.map +1 -0
  104. package/dist/lib/envelope.d.ts +31 -0
  105. package/dist/lib/envelope.d.ts.map +1 -0
  106. package/dist/lib/envelope.js +80 -0
  107. package/dist/lib/envelope.js.map +1 -0
  108. package/dist/lib/fileLock.d.ts +38 -0
  109. package/dist/lib/fileLock.d.ts.map +1 -0
  110. package/dist/lib/fileLock.js +124 -0
  111. package/dist/lib/fileLock.js.map +1 -0
  112. package/dist/lib/recordIdAllocator.d.ts +102 -0
  113. package/dist/lib/recordIdAllocator.d.ts.map +1 -0
  114. package/dist/lib/recordIdAllocator.js +179 -0
  115. package/dist/lib/recordIdAllocator.js.map +1 -0
  116. package/dist/orgId.d.ts +23 -0
  117. package/dist/orgId.d.ts.map +1 -0
  118. package/dist/orgId.js +73 -0
  119. package/dist/orgId.js.map +1 -0
  120. package/dist/pairCli.d.ts +2 -0
  121. package/dist/pairCli.d.ts.map +1 -0
  122. package/dist/pairCli.js +97 -0
  123. package/dist/pairCli.js.map +1 -0
  124. package/dist/peerBroker.d.ts +40 -0
  125. package/dist/peerBroker.d.ts.map +1 -0
  126. package/dist/peerBroker.js +70 -0
  127. package/dist/peerBroker.js.map +1 -0
  128. package/dist/peerBrokerSupervisor.d.ts +21 -0
  129. package/dist/peerBrokerSupervisor.d.ts.map +1 -0
  130. package/dist/peerBrokerSupervisor.js +131 -0
  131. package/dist/peerBrokerSupervisor.js.map +1 -0
  132. package/dist/projectPaths.d.ts.map +1 -1
  133. package/dist/projectPaths.js +1 -19
  134. package/dist/projectPaths.js.map +1 -1
  135. package/dist/projectRegistry.d.ts +22 -0
  136. package/dist/projectRegistry.d.ts.map +1 -1
  137. package/dist/projectRegistry.js +69 -2
  138. package/dist/projectRegistry.js.map +1 -1
  139. package/dist/pty/boundary.d.ts +39 -0
  140. package/dist/pty/boundary.d.ts.map +1 -0
  141. package/dist/pty/boundary.js +100 -0
  142. package/dist/pty/boundary.js.map +1 -0
  143. package/dist/pty/errors.d.ts +6 -0
  144. package/dist/pty/errors.d.ts.map +1 -0
  145. package/dist/pty/errors.js +12 -0
  146. package/dist/pty/errors.js.map +1 -0
  147. package/dist/pty/fleet.d.ts +159 -0
  148. package/dist/pty/fleet.d.ts.map +1 -0
  149. package/dist/pty/fleet.js +543 -0
  150. package/dist/pty/fleet.js.map +1 -0
  151. package/dist/pty/launchProfile.d.ts +45 -0
  152. package/dist/pty/launchProfile.d.ts.map +1 -0
  153. package/dist/pty/launchProfile.js +149 -0
  154. package/dist/pty/launchProfile.js.map +1 -0
  155. package/dist/pty/openTerminal.d.ts +48 -0
  156. package/dist/pty/openTerminal.d.ts.map +1 -0
  157. package/dist/pty/openTerminal.js +185 -0
  158. package/dist/pty/openTerminal.js.map +1 -0
  159. package/dist/pty/pairDialog.d.ts +16 -0
  160. package/dist/pty/pairDialog.d.ts.map +1 -0
  161. package/dist/pty/pairDialog.js +121 -0
  162. package/dist/pty/pairDialog.js.map +1 -0
  163. package/dist/pty/pairing.d.ts +86 -0
  164. package/dist/pty/pairing.d.ts.map +1 -0
  165. package/dist/pty/pairing.js +351 -0
  166. package/dist/pty/pairing.js.map +1 -0
  167. package/dist/pty/routes.d.ts +49 -0
  168. package/dist/pty/routes.d.ts.map +1 -0
  169. package/dist/pty/routes.js +624 -0
  170. package/dist/pty/routes.js.map +1 -0
  171. package/dist/pty/sessions.d.ts +167 -0
  172. package/dist/pty/sessions.d.ts.map +1 -0
  173. package/dist/pty/sessions.js +513 -0
  174. package/dist/pty/sessions.js.map +1 -0
  175. package/dist/pty/tmux.d.ts +109 -0
  176. package/dist/pty/tmux.d.ts.map +1 -0
  177. package/dist/pty/tmux.js +271 -0
  178. package/dist/pty/tmux.js.map +1 -0
  179. package/dist/roster.d.ts +37 -0
  180. package/dist/roster.d.ts.map +1 -0
  181. package/dist/roster.js +126 -0
  182. package/dist/roster.js.map +1 -0
  183. package/dist/runtime/circuitBreaker.d.ts +95 -0
  184. package/dist/runtime/circuitBreaker.d.ts.map +1 -0
  185. package/dist/runtime/circuitBreaker.js +178 -0
  186. package/dist/runtime/circuitBreaker.js.map +1 -0
  187. package/dist/runtime/daemonMeta.d.ts +53 -0
  188. package/dist/runtime/daemonMeta.d.ts.map +1 -0
  189. package/dist/runtime/daemonMeta.js +121 -0
  190. package/dist/runtime/daemonMeta.js.map +1 -0
  191. package/dist/runtime/ensureRuntime.d.ts +103 -0
  192. package/dist/runtime/ensureRuntime.d.ts.map +1 -0
  193. package/dist/runtime/ensureRuntime.js +276 -0
  194. package/dist/runtime/ensureRuntime.js.map +1 -0
  195. package/dist/runtime/extensions.d.ts +57 -0
  196. package/dist/runtime/extensions.d.ts.map +1 -0
  197. package/dist/runtime/extensions.js +216 -0
  198. package/dist/runtime/extensions.js.map +1 -0
  199. package/dist/runtime/localHttp.d.ts +24 -0
  200. package/dist/runtime/localHttp.d.ts.map +1 -0
  201. package/dist/runtime/localHttp.js +84 -0
  202. package/dist/runtime/localHttp.js.map +1 -0
  203. package/dist/runtime/projectResolution.d.ts +29 -0
  204. package/dist/runtime/projectResolution.d.ts.map +1 -0
  205. package/dist/runtime/projectResolution.js +67 -0
  206. package/dist/runtime/projectResolution.js.map +1 -0
  207. package/dist/runtime/protocol.d.ts +62 -0
  208. package/dist/runtime/protocol.d.ts.map +1 -0
  209. package/dist/runtime/protocol.js +74 -0
  210. package/dist/runtime/protocol.js.map +1 -0
  211. package/dist/sentinelIssues.d.ts +42 -2
  212. package/dist/sentinelIssues.d.ts.map +1 -1
  213. package/dist/sentinelIssues.js +167 -65
  214. package/dist/sentinelIssues.js.map +1 -1
  215. package/dist/server.js +123 -15
  216. package/dist/server.js.map +1 -1
  217. package/dist/toolConfig.d.ts.map +1 -1
  218. package/dist/toolConfig.js +12 -0
  219. package/dist/toolConfig.js.map +1 -1
  220. package/dist/tools/agentic/agentQueue.d.ts.map +1 -1
  221. package/dist/tools/agentic/agentQueue.js +4 -0
  222. package/dist/tools/agentic/agentQueue.js.map +1 -1
  223. package/dist/tools/agentic/index.d.ts.map +1 -1
  224. package/dist/tools/agentic/index.js +28 -2
  225. package/dist/tools/agentic/index.js.map +1 -1
  226. package/dist/tools/architect/index.d.ts +1 -0
  227. package/dist/tools/architect/index.d.ts.map +1 -1
  228. package/dist/tools/architect/index.js +52 -3
  229. package/dist/tools/architect/index.js.map +1 -1
  230. package/dist/tools/architect.d.ts.map +1 -1
  231. package/dist/tools/architect.js +59 -8
  232. package/dist/tools/architect.js.map +1 -1
  233. package/dist/tools/artifacts.d.ts +5 -0
  234. package/dist/tools/artifacts.d.ts.map +1 -0
  235. package/dist/tools/artifacts.js +89 -0
  236. package/dist/tools/artifacts.js.map +1 -0
  237. package/dist/tools/asks.d.ts +6 -0
  238. package/dist/tools/asks.d.ts.map +1 -0
  239. package/dist/tools/asks.js +104 -0
  240. package/dist/tools/asks.js.map +1 -0
  241. package/dist/tools/context.d.ts +10 -5
  242. package/dist/tools/context.d.ts.map +1 -1
  243. package/dist/tools/context.js +45 -18
  244. package/dist/tools/context.js.map +1 -1
  245. package/dist/tools/coordinator/coordinator.d.ts.map +1 -1
  246. package/dist/tools/coordinator/coordinator.js +48 -19
  247. package/dist/tools/coordinator/coordinator.js.map +1 -1
  248. package/dist/tools/coordinator/index.d.ts +1 -0
  249. package/dist/tools/coordinator/index.d.ts.map +1 -1
  250. package/dist/tools/coordinator/index.js +42 -2
  251. package/dist/tools/coordinator/index.js.map +1 -1
  252. package/dist/tools/crit.d.ts +2 -1
  253. package/dist/tools/crit.d.ts.map +1 -1
  254. package/dist/tools/crit.js +19 -4
  255. package/dist/tools/crit.js.map +1 -1
  256. package/dist/tools/data-inspector.d.ts +1 -1
  257. package/dist/tools/data-inspector.d.ts.map +1 -1
  258. package/dist/tools/data-inspector.js +1 -1
  259. package/dist/tools/data-inspector.js.map +1 -1
  260. package/dist/tools/designer/index.d.ts.map +1 -1
  261. package/dist/tools/designer/index.js +4 -3
  262. package/dist/tools/designer/index.js.map +1 -1
  263. package/dist/tools/designer/lateral-tools.d.ts.map +1 -1
  264. package/dist/tools/designer/lateral-tools.js +4 -2
  265. package/dist/tools/designer/lateral-tools.js.map +1 -1
  266. package/dist/tools/designer.d.ts +2 -1
  267. package/dist/tools/designer.d.ts.map +1 -1
  268. package/dist/tools/designer.js +24 -12
  269. package/dist/tools/designer.js.map +1 -1
  270. package/dist/tools/dojo/index.d.ts.map +1 -1
  271. package/dist/tools/dojo/index.js +52 -3
  272. package/dist/tools/dojo/index.js.map +1 -1
  273. package/dist/tools/dojo.d.ts +65 -3
  274. package/dist/tools/dojo.d.ts.map +1 -1
  275. package/dist/tools/dojo.js +148 -23
  276. package/dist/tools/dojo.js.map +1 -1
  277. package/dist/tools/friction.d.ts.map +1 -1
  278. package/dist/tools/friction.js +16 -6
  279. package/dist/tools/friction.js.map +1 -1
  280. package/dist/tools/guardian.d.ts +7 -0
  281. package/dist/tools/guardian.d.ts.map +1 -1
  282. package/dist/tools/guardian.js +152 -11
  283. package/dist/tools/guardian.js.map +1 -1
  284. package/dist/tools/hygiene/oracle-hygiene.d.ts.map +1 -1
  285. package/dist/tools/hygiene/oracle-hygiene.js +8 -2
  286. package/dist/tools/hygiene/oracle-hygiene.js.map +1 -1
  287. package/dist/tools/index.d.ts.map +1 -1
  288. package/dist/tools/index.js +33 -18
  289. package/dist/tools/index.js.map +1 -1
  290. package/dist/tools/killswitch/index.d.ts +13 -0
  291. package/dist/tools/killswitch/index.d.ts.map +1 -0
  292. package/dist/tools/killswitch/index.js +138 -0
  293. package/dist/tools/killswitch/index.js.map +1 -0
  294. package/dist/tools/learnings.d.ts +2 -1
  295. package/dist/tools/learnings.d.ts.map +1 -1
  296. package/dist/tools/learnings.js +31 -15
  297. package/dist/tools/learnings.js.map +1 -1
  298. package/dist/tools/oracle/index.d.ts.map +1 -1
  299. package/dist/tools/oracle/index.js +8 -0
  300. package/dist/tools/oracle/index.js.map +1 -1
  301. package/dist/tools/oracle.d.ts +7 -0
  302. package/dist/tools/oracle.d.ts.map +1 -1
  303. package/dist/tools/oracle.js +41 -12
  304. package/dist/tools/oracle.js.map +1 -1
  305. package/dist/tools/policy.d.ts +2 -1
  306. package/dist/tools/policy.d.ts.map +1 -1
  307. package/dist/tools/policy.js +28 -9
  308. package/dist/tools/policy.js.map +1 -1
  309. package/dist/tools/postoffice/index.d.ts +10 -0
  310. package/dist/tools/postoffice/index.d.ts.map +1 -0
  311. package/dist/tools/postoffice/index.js +218 -0
  312. package/dist/tools/postoffice/index.js.map +1 -0
  313. package/dist/tools/postoffice.d.ts +78 -0
  314. package/dist/tools/postoffice.d.ts.map +1 -0
  315. package/dist/tools/postoffice.js +199 -0
  316. package/dist/tools/postoffice.js.map +1 -0
  317. package/dist/tools/provenance.d.ts +18 -2
  318. package/dist/tools/provenance.d.ts.map +1 -1
  319. package/dist/tools/provenance.js +68 -40
  320. package/dist/tools/provenance.js.map +1 -1
  321. package/dist/tools/registry/index.d.ts.map +1 -1
  322. package/dist/tools/registry/index.js +24 -0
  323. package/dist/tools/registry/index.js.map +1 -1
  324. package/dist/tools/sentinel/index.d.ts +1 -0
  325. package/dist/tools/sentinel/index.d.ts.map +1 -1
  326. package/dist/tools/sentinel/index.js +142 -6
  327. package/dist/tools/sentinel/index.js.map +1 -1
  328. package/dist/tools/sentinel.d.ts +152 -1
  329. package/dist/tools/sentinel.d.ts.map +1 -1
  330. package/dist/tools/sentinel.js +609 -328
  331. package/dist/tools/sentinel.js.map +1 -1
  332. package/dist/tools/shared/index.d.ts +2 -1
  333. package/dist/tools/shared/index.d.ts.map +1 -1
  334. package/dist/tools/shared/index.js +2 -1
  335. package/dist/tools/shared/index.js.map +1 -1
  336. package/dist/tools/shared/response.d.ts +24 -0
  337. package/dist/tools/shared/response.d.ts.map +1 -1
  338. package/dist/tools/shared/response.js +61 -7
  339. package/dist/tools/shared/response.js.map +1 -1
  340. package/dist/tools/shared/runTracker.d.ts +2 -0
  341. package/dist/tools/shared/runTracker.d.ts.map +1 -1
  342. package/dist/tools/shared/runTracker.js +38 -0
  343. package/dist/tools/shared/runTracker.js.map +1 -1
  344. package/dist/tools/shared/storeRead.d.ts +153 -0
  345. package/dist/tools/shared/storeRead.d.ts.map +1 -0
  346. package/dist/tools/shared/storeRead.js +242 -0
  347. package/dist/tools/shared/storeRead.js.map +1 -0
  348. package/dist/tools/swarm.d.ts +3 -1
  349. package/dist/tools/swarm.d.ts.map +1 -1
  350. package/dist/tools/swarm.js +6 -12
  351. package/dist/tools/swarm.js.map +1 -1
  352. package/dist/tools/testSpec.d.ts +3 -2
  353. package/dist/tools/testSpec.d.ts.map +1 -1
  354. package/dist/tools/testSpec.js +51 -14
  355. package/dist/tools/testSpec.js.map +1 -1
  356. package/dist/tools/vector.d.ts +33 -1
  357. package/dist/tools/vector.d.ts.map +1 -1
  358. package/dist/tools/vector.js +31 -5
  359. package/dist/tools/vector.js.map +1 -1
  360. package/dist/tools/voice/index.d.ts +1 -0
  361. package/dist/tools/voice/index.d.ts.map +1 -1
  362. package/dist/tools/voice/index.js +33 -2
  363. package/dist/tools/voice/index.js.map +1 -1
  364. package/dist/tools/voice.d.ts +48 -1
  365. package/dist/tools/voice.d.ts.map +1 -1
  366. package/dist/tools/voice.js +63 -3
  367. package/dist/tools/voice.js.map +1 -1
  368. package/dist/tools/zoom/index.d.ts +15 -0
  369. package/dist/tools/zoom/index.d.ts.map +1 -0
  370. package/dist/tools/zoom/index.js +174 -0
  371. package/dist/tools/zoom/index.js.map +1 -0
  372. package/dist/tools/zoom.d.ts +234 -0
  373. package/dist/tools/zoom.d.ts.map +1 -0
  374. package/dist/tools/zoom.js +627 -0
  375. package/dist/tools/zoom.js.map +1 -0
  376. package/dist/transports/bridge.d.ts +1 -1
  377. package/dist/transports/bridge.d.ts.map +1 -1
  378. package/dist/transports/bridge.js +4 -1
  379. package/dist/transports/bridge.js.map +1 -1
  380. package/dist/transports/http.d.ts +1 -1
  381. package/dist/transports/http.d.ts.map +1 -1
  382. package/dist/transports/http.js +3 -1
  383. package/dist/transports/http.js.map +1 -1
  384. package/dist/transports/index.d.ts +1 -0
  385. package/dist/transports/index.d.ts.map +1 -1
  386. package/dist/transports/index.js +1 -0
  387. package/dist/transports/index.js.map +1 -1
  388. package/dist/transports/mcp.d.ts +1 -1
  389. package/dist/transports/mcp.d.ts.map +1 -1
  390. package/dist/transports/mcp.js +9 -4
  391. package/dist/transports/mcp.js.map +1 -1
  392. package/dist/transports/stdio.d.ts +1 -1
  393. package/dist/transports/stdio.d.ts.map +1 -1
  394. package/dist/transports/stdio.js +2 -0
  395. package/dist/transports/stdio.js.map +1 -1
  396. package/dist/transports/thinStdio.d.ts +22 -0
  397. package/dist/transports/thinStdio.d.ts.map +1 -0
  398. package/dist/transports/thinStdio.js +218 -0
  399. package/dist/transports/thinStdio.js.map +1 -0
  400. package/dist/transports/types.d.ts +8 -1
  401. package/dist/transports/types.d.ts.map +1 -1
  402. package/dist/types/agent-services.d.ts +7 -0
  403. package/dist/types/agent-services.d.ts.map +1 -1
  404. package/dist/version.d.ts +5 -0
  405. package/dist/version.d.ts.map +1 -0
  406. package/dist/version.js +23 -0
  407. package/dist/version.js.map +1 -0
  408. package/package.json +20 -4
  409. package/templates/AGENT.md +3 -0
  410. package/templates/CLAUDE-decibel.md +11 -0
  411. package/templates/com.decibel.daemon.plist +6 -1
  412. package/dist/tools/deck.d.ts +0 -11
  413. package/dist/tools/deck.d.ts.map +0 -1
  414. package/dist/tools/deck.js +0 -1656
  415. package/dist/tools/deck.js.map +0 -1
  416. package/dist/tools/mother.d.ts +0 -11
  417. package/dist/tools/mother.d.ts.map +0 -1
  418. package/dist/tools/mother.js +0 -503
  419. package/dist/tools/mother.js.map +0 -1
  420. package/dist/tools/registry.d.ts +0 -3
  421. package/dist/tools/registry.d.ts.map +0 -1
  422. package/dist/tools/registry.js +0 -189
  423. package/dist/tools/registry.js.map +0 -1
  424. package/dist/tools/senken.d.ts +0 -11
  425. package/dist/tools/senken.d.ts.map +0 -1
  426. package/dist/tools/senken.js +0 -482
  427. package/dist/tools/senken.js.map +0 -1
  428. package/dist/tools/terminal.d.ts +0 -17
  429. package/dist/tools/terminal.d.ts.map +0 -1
  430. package/dist/tools/terminal.js +0 -968
  431. package/dist/tools/terminal.js.map +0 -1
  432. package/dist/tools/toolsIndex.d.ts +0 -5
  433. package/dist/tools/toolsIndex.d.ts.map +0 -1
  434. package/dist/tools/toolsIndex.js +0 -37
  435. package/dist/tools/toolsIndex.js.map +0 -1
@@ -1,1656 +0,0 @@
1
- /**
2
- * Deck Tools - MTG Card Database (Supabase)
3
- *
4
- * Card catalog, price history, and market movers for Magic: The Gathering.
5
- * Data lives in the Core Supabase project (dqftcixhsvcjtyxsxoao).
6
- *
7
- * Env var: DECK_SUPABASE_KEY (service role or anon key — tables have public SELECT policies)
8
- */
9
- import { createClient } from '@supabase/supabase-js';
10
- const CORE_SUPABASE_URL = 'https://dqftcixhsvcjtyxsxoao.supabase.co';
11
- // Anon key for public read access (all deck tables have public SELECT RLS policies)
12
- const CORE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRxZnRjaXhoc3ZjanR5eHN4b2FvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjcyMTgzMTYsImV4cCI6MjA4Mjc5NDMxNn0.erZq0zJMF-tATX7b0mofGsajFUvcqlZE5PQ4qcggmGc';
13
- // Lazy-init client
14
- let client = null;
15
- function getClient() {
16
- if (!client) {
17
- const key = process.env.DECK_SUPABASE_KEY || CORE_ANON_KEY;
18
- client = createClient(CORE_SUPABASE_URL, key, {
19
- auth: { persistSession: false, autoRefreshToken: false },
20
- });
21
- }
22
- return client;
23
- }
24
- // =============================================================================
25
- // Helpers
26
- // =============================================================================
27
- function jsonResult(data, isError = false) {
28
- return {
29
- content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
30
- isError,
31
- };
32
- }
33
- /**
34
- * Fallback to Scryfall API when a card isn't found in the local database.
35
- * Returns results shaped like the Supabase cards table for consistency.
36
- */
37
- async function scryfallSearch(name, setCode) {
38
- let query = `name:${name}`;
39
- if (setCode)
40
- query += ` set:${setCode}`;
41
- const url = `https://api.scryfall.com/cards/search?q=${encodeURIComponent(query)}&order=usd&dir=desc`;
42
- const res = await fetch(url);
43
- if (!res.ok)
44
- return [];
45
- const json = (await res.json());
46
- if (!json.data?.length)
47
- return [];
48
- return json.data.slice(0, 50).map((card) => ({
49
- name: card.name,
50
- set_code: card.set,
51
- set_name: card.set_name,
52
- rarity: card.rarity,
53
- price_usd: card.prices?.usd ?? null,
54
- price_usd_foil: card.prices?.usd_foil ?? null,
55
- legalities: card.legalities,
56
- scryfall_id: card.id,
57
- image_uri: card.image_uris?.normal ??
58
- card.image_uris?.large ??
59
- null,
60
- type_line: card.type_line,
61
- mana_cost: card.mana_cost,
62
- source: 'scryfall',
63
- }));
64
- }
65
- // =============================================================================
66
- // Validation Helpers
67
- // =============================================================================
68
- const VALID_FORMATS = [
69
- 'standard', 'modern', 'pioneer', 'commander', 'legacy', 'vintage',
70
- 'pauper', 'historic', 'explorer', 'brawl', 'alchemy', 'penny', 'oathbreaker',
71
- ];
72
- const VALID_RARITIES = ['common', 'uncommon', 'rare', 'mythic'];
73
- function validateFormat(format) {
74
- const f = format.toLowerCase();
75
- if (!VALID_FORMATS.includes(f)) {
76
- throw new Error(`Invalid format "${format}". Valid: ${VALID_FORMATS.join(', ')}`);
77
- }
78
- return f;
79
- }
80
- function validateRarity(rarity) {
81
- const r = rarity.toLowerCase();
82
- if (!VALID_RARITIES.includes(r)) {
83
- throw new Error(`Invalid rarity "${rarity}". Valid: ${VALID_RARITIES.join(', ')}`);
84
- }
85
- return r;
86
- }
87
- /** Grade price stability from coefficient of variation */
88
- function gradeStability(cv) {
89
- if (cv < 5)
90
- return { grade: 'A', label: 'Very Stable' };
91
- if (cv < 10)
92
- return { grade: 'B', label: 'Stable' };
93
- if (cv < 20)
94
- return { grade: 'C', label: 'Moderate' };
95
- if (cv < 35)
96
- return { grade: 'D', label: 'Volatile' };
97
- return { grade: 'F', label: 'Highly Volatile' };
98
- }
99
- // =============================================================================
100
- // Tool Specs
101
- // =============================================================================
102
- const deckSearch = {
103
- definition: {
104
- name: 'deck_search',
105
- description: 'Search MTG cards by name. Returns card details with current prices.',
106
- annotations: {
107
- title: 'Search Cards',
108
- readOnlyHint: true,
109
- destructiveHint: false,
110
- openWorldHint: true,
111
- },
112
- inputSchema: {
113
- type: 'object',
114
- properties: {
115
- name: {
116
- type: 'string',
117
- description: 'Card name to search for (partial match, case-insensitive)',
118
- },
119
- set_code: {
120
- type: 'string',
121
- description: 'Optional: filter by set code (e.g. "MH3", "LCI")',
122
- },
123
- },
124
- required: ['name'],
125
- },
126
- },
127
- handler: async (args) => {
128
- try {
129
- const db = getClient();
130
- let query = db
131
- .from('cards')
132
- .select('name, set_code, set_name, rarity, price_usd, price_usd_foil, legalities, scryfall_id, image_uri, type_line, mana_cost')
133
- .ilike('name', `%${args.name}%`)
134
- .order('price_usd', { ascending: false, nullsFirst: false })
135
- .limit(50);
136
- if (args.set_code) {
137
- query = query.eq('set_code', args.set_code.toLowerCase());
138
- }
139
- const { data, error } = await query;
140
- if (error)
141
- throw new Error(error.message);
142
- // Fallback to Scryfall API when local DB returns no results
143
- if (!data || data.length === 0) {
144
- const fallback = await scryfallSearch(args.name, args.set_code);
145
- return jsonResult({
146
- success: true,
147
- cards: fallback,
148
- count: fallback.length,
149
- source: fallback.length > 0 ? 'scryfall_fallback' : 'none',
150
- });
151
- }
152
- return jsonResult({
153
- success: true,
154
- cards: data,
155
- count: data.length,
156
- source: 'database',
157
- });
158
- }
159
- catch (error) {
160
- const msg = error instanceof Error ? error.message : String(error);
161
- return jsonResult({ success: false, error: msg }, true);
162
- }
163
- },
164
- };
165
- const deckList = {
166
- definition: {
167
- name: 'deck_list',
168
- description: 'List top price movers for a given period. Returns cards with the biggest price changes (gainers and losers).',
169
- annotations: {
170
- title: 'Top Movers',
171
- readOnlyHint: true,
172
- destructiveHint: false,
173
- openWorldHint: true,
174
- },
175
- inputSchema: {
176
- type: 'object',
177
- properties: {
178
- period: {
179
- type: 'string',
180
- description: 'Time period for price changes (default: "7d"). E.g. "1d", "7d", "30d"',
181
- },
182
- limit: {
183
- type: 'number',
184
- description: 'Max results (default: 50)',
185
- },
186
- },
187
- required: [],
188
- },
189
- },
190
- handler: async (args) => {
191
- try {
192
- const db = getClient();
193
- const period = args.period || '7d';
194
- const limit = args.limit || 50;
195
- // Fetch movers — grab extra to cover abs() sorting
196
- const { data: movers, error: moversErr } = await db
197
- .from('price_movers')
198
- .select('scryfall_id, period, price_from, price_to, change_pct, computed_at')
199
- .eq('period', period)
200
- .order('change_pct', { ascending: false })
201
- .limit(limit * 2);
202
- if (moversErr)
203
- throw new Error(moversErr.message);
204
- if (!movers || movers.length === 0) {
205
- return jsonResult({ success: true, movers: [], count: 0, period });
206
- }
207
- // Sort by abs(change_pct), take top N
208
- movers.sort((a, b) => Math.abs(b.change_pct) - Math.abs(a.change_pct));
209
- const topMovers = movers.slice(0, limit);
210
- // Batch-lookup card names
211
- const ids = topMovers.map((m) => m.scryfall_id);
212
- const { data: cards, error: cardsErr } = await db
213
- .from('cards')
214
- .select('scryfall_id, name, set_code')
215
- .in('scryfall_id', ids);
216
- if (cardsErr)
217
- throw new Error(cardsErr.message);
218
- const cardMap = new Map((cards || []).map((c) => [
219
- c.scryfall_id,
220
- c,
221
- ]));
222
- const results = topMovers.map((m) => {
223
- const card = cardMap.get(m.scryfall_id);
224
- return {
225
- name: card?.name || 'Unknown',
226
- set_code: card?.set_code || '',
227
- price_from: m.price_from,
228
- price_to: m.price_to,
229
- change_pct: m.change_pct,
230
- computed_at: m.computed_at,
231
- };
232
- });
233
- return jsonResult({
234
- success: true,
235
- movers: results,
236
- count: results.length,
237
- period,
238
- });
239
- }
240
- catch (error) {
241
- const msg = error instanceof Error ? error.message : String(error);
242
- return jsonResult({ success: false, error: msg }, true);
243
- }
244
- },
245
- };
246
- const deckSummary = {
247
- definition: {
248
- name: 'deck_summary',
249
- description: 'Get a summary of the card database: card count, price sources, last update time, available mover periods.',
250
- annotations: {
251
- title: 'Data Summary',
252
- readOnlyHint: true,
253
- destructiveHint: false,
254
- openWorldHint: true,
255
- },
256
- inputSchema: {
257
- type: 'object',
258
- properties: {},
259
- required: [],
260
- },
261
- },
262
- handler: async () => {
263
- try {
264
- const db = getClient();
265
- const [cardCount, sources, latestUpdate, periods] = await Promise.all([
266
- db.from('cards').select('*', { count: 'exact', head: true }),
267
- db.from('price_history').select('source').limit(1000),
268
- db
269
- .from('cards')
270
- .select('price_updated_at')
271
- .order('price_updated_at', { ascending: false })
272
- .limit(1),
273
- db.from('price_movers').select('period').limit(1000),
274
- ]);
275
- const distinctSources = [
276
- ...new Set((sources.data || []).map((r) => r.source)),
277
- ];
278
- const distinctPeriods = [
279
- ...new Set((periods.data || []).map((r) => r.period)),
280
- ];
281
- return jsonResult({
282
- success: true,
283
- summary: {
284
- card_count: cardCount.count || 0,
285
- price_sources: distinctSources,
286
- latest_price_update: latestUpdate.data?.[0]?.price_updated_at || null,
287
- available_periods: distinctPeriods,
288
- },
289
- });
290
- }
291
- catch (error) {
292
- const msg = error instanceof Error ? error.message : String(error);
293
- return jsonResult({ success: false, error: msg }, true);
294
- }
295
- },
296
- };
297
- const deckHistory = {
298
- definition: {
299
- name: 'deck_history',
300
- description: 'Get price history for a specific card. Accepts scryfall_id or card name. Returns up to 90 days of price data.',
301
- annotations: {
302
- title: 'Price History',
303
- readOnlyHint: true,
304
- destructiveHint: false,
305
- openWorldHint: true,
306
- },
307
- inputSchema: {
308
- type: 'object',
309
- properties: {
310
- scryfall_id: {
311
- type: 'string',
312
- description: 'Scryfall UUID of the card',
313
- },
314
- name: {
315
- type: 'string',
316
- description: 'Card name (exact match preferred; used if scryfall_id not provided)',
317
- },
318
- limit: {
319
- type: 'number',
320
- description: 'Max rows (default: 90 — ~3 months)',
321
- },
322
- },
323
- required: [],
324
- },
325
- },
326
- handler: async (args) => {
327
- try {
328
- if (!args.scryfall_id && !args.name) {
329
- return jsonResult({ success: false, error: 'Provide either scryfall_id or name' }, true);
330
- }
331
- const db = getClient();
332
- const limit = args.limit || 90;
333
- let scryfallId = args.scryfall_id;
334
- // Resolve name → scryfall_id if needed
335
- if (!scryfallId && args.name) {
336
- const { data: cards, error: cardErr } = await db
337
- .from('cards')
338
- .select('scryfall_id, name')
339
- .ilike('name', args.name)
340
- .limit(1);
341
- if (cardErr)
342
- throw new Error(cardErr.message);
343
- if (!cards || cards.length === 0) {
344
- return jsonResult({ success: false, error: `Card not found: ${args.name}` }, true);
345
- }
346
- scryfallId = cards[0]
347
- .scryfall_id;
348
- }
349
- const { data, error } = await db
350
- .from('price_history')
351
- .select('date, price_usd, price_usd_foil, source')
352
- .eq('scryfall_id', scryfallId)
353
- .order('date', { ascending: false })
354
- .limit(limit);
355
- if (error)
356
- throw new Error(error.message);
357
- return jsonResult({
358
- success: true,
359
- scryfall_id: scryfallId,
360
- history: data,
361
- count: data?.length || 0,
362
- });
363
- }
364
- catch (error) {
365
- const msg = error instanceof Error ? error.message : String(error);
366
- return jsonResult({ success: false, error: msg }, true);
367
- }
368
- },
369
- };
370
- // =============================================================================
371
- // Tool 5: Format Deals
372
- // =============================================================================
373
- const deckFormatDeals = {
374
- definition: {
375
- name: 'deck_format_deals',
376
- description: 'Find underpriced cards legal in a specific format. Great for budget deck building.',
377
- annotations: {
378
- title: 'Format Deals',
379
- readOnlyHint: true,
380
- destructiveHint: false,
381
- openWorldHint: true,
382
- },
383
- inputSchema: {
384
- type: 'object',
385
- properties: {
386
- format: {
387
- type: 'string',
388
- description: `Format to check legality (${VALID_FORMATS.join(', ')})`,
389
- },
390
- max_price: {
391
- type: 'number',
392
- description: 'Maximum price in USD (default: no limit)',
393
- },
394
- min_price: {
395
- type: 'number',
396
- description: 'Minimum price in USD (default: 0.01 to skip bulk)',
397
- },
398
- rarity: {
399
- type: 'string',
400
- description: 'Filter by rarity (common, uncommon, rare, mythic)',
401
- },
402
- sort_by: {
403
- type: 'string',
404
- description: 'Sort order: price_asc (default) or price_desc',
405
- },
406
- limit: {
407
- type: 'number',
408
- description: 'Max results (default: 50, max: 200)',
409
- },
410
- },
411
- required: ['format'],
412
- },
413
- },
414
- handler: async (args) => {
415
- try {
416
- const format = validateFormat(args.format);
417
- const minPrice = args.min_price ?? 0.01;
418
- const limit = Math.min(args.limit || 50, 200);
419
- const ascending = (args.sort_by || 'price_asc') === 'price_asc';
420
- const db = getClient();
421
- let query = db
422
- .from('cards')
423
- .select('name, set_code, set_name, rarity, price_usd, type_line, mana_cost, scryfall_id')
424
- .filter(`legalities->>${format}`, 'eq', 'legal')
425
- .gte('price_usd', minPrice)
426
- .order('price_usd', { ascending, nullsFirst: false })
427
- .limit(limit);
428
- if (args.max_price != null) {
429
- query = query.lte('price_usd', args.max_price);
430
- }
431
- if (args.rarity) {
432
- query = query.eq('rarity', validateRarity(args.rarity));
433
- }
434
- const { data, error } = await query;
435
- if (error)
436
- throw new Error(error.message);
437
- return jsonResult({
438
- success: true,
439
- format,
440
- cards: data,
441
- count: data?.length || 0,
442
- });
443
- }
444
- catch (error) {
445
- const msg = error instanceof Error ? error.message : String(error);
446
- return jsonResult({ success: false, error: msg }, true);
447
- }
448
- },
449
- };
450
- // =============================================================================
451
- // Tool 6: Type Search
452
- // =============================================================================
453
- const deckTypeSearch = {
454
- definition: {
455
- name: 'deck_type_search',
456
- description: 'Search cards by type line for tribal/archetype building (e.g. Elf, Planeswalker, Equipment).',
457
- annotations: {
458
- title: 'Type Search',
459
- readOnlyHint: true,
460
- destructiveHint: false,
461
- openWorldHint: true,
462
- },
463
- inputSchema: {
464
- type: 'object',
465
- properties: {
466
- type: {
467
- type: 'string',
468
- description: 'Type to search for (e.g. "Elf", "Planeswalker", "Equipment")',
469
- },
470
- format: {
471
- type: 'string',
472
- description: 'Filter by format legality',
473
- },
474
- max_price: {
475
- type: 'number',
476
- description: 'Maximum price in USD',
477
- },
478
- min_price: {
479
- type: 'number',
480
- description: 'Minimum price in USD',
481
- },
482
- set_code: {
483
- type: 'string',
484
- description: 'Filter by set code (e.g. "MH3")',
485
- },
486
- limit: {
487
- type: 'number',
488
- description: 'Max results (default: 50, max: 200)',
489
- },
490
- },
491
- required: ['type'],
492
- },
493
- },
494
- handler: async (args) => {
495
- try {
496
- const limit = Math.min(args.limit || 50, 200);
497
- const db = getClient();
498
- let query = db
499
- .from('cards')
500
- .select('name, set_code, set_name, rarity, price_usd, type_line, mana_cost, scryfall_id')
501
- .ilike('type_line', `%${args.type}%`)
502
- .order('price_usd', { ascending: false, nullsFirst: false })
503
- .limit(limit);
504
- if (args.format) {
505
- const format = validateFormat(args.format);
506
- query = query.filter(`legalities->>${format}`, 'eq', 'legal');
507
- }
508
- if (args.max_price != null) {
509
- query = query.lte('price_usd', args.max_price);
510
- }
511
- if (args.min_price != null) {
512
- query = query.gte('price_usd', args.min_price);
513
- }
514
- if (args.set_code) {
515
- query = query.eq('set_code', args.set_code.toLowerCase());
516
- }
517
- const { data, error } = await query;
518
- if (error)
519
- throw new Error(error.message);
520
- return jsonResult({
521
- success: true,
522
- type: args.type,
523
- cards: data,
524
- count: data?.length || 0,
525
- });
526
- }
527
- catch (error) {
528
- const msg = error instanceof Error ? error.message : String(error);
529
- return jsonResult({ success: false, error: msg }, true);
530
- }
531
- },
532
- };
533
- // =============================================================================
534
- // Tool 7: Price Bracket
535
- // =============================================================================
536
- const deckPriceBracket = {
537
- definition: {
538
- name: 'deck_price_bracket',
539
- description: 'Multi-filter power search: combine price range, rarity, format, set, and type filters.',
540
- annotations: {
541
- title: 'Price Bracket',
542
- readOnlyHint: true,
543
- destructiveHint: false,
544
- openWorldHint: true,
545
- },
546
- inputSchema: {
547
- type: 'object',
548
- properties: {
549
- min_price: { type: 'number', description: 'Minimum price in USD' },
550
- max_price: { type: 'number', description: 'Maximum price in USD' },
551
- rarity: { type: 'string', description: 'Card rarity filter' },
552
- format: { type: 'string', description: 'Format legality filter' },
553
- set_code: { type: 'string', description: 'Set code filter' },
554
- type_line: { type: 'string', description: 'Type line filter (partial match)' },
555
- sort_by: {
556
- type: 'string',
557
- description: 'Sort: price_desc (default), price_asc, name',
558
- },
559
- limit: { type: 'number', description: 'Max results (default: 50, max: 200)' },
560
- },
561
- required: [],
562
- },
563
- },
564
- handler: async (args) => {
565
- try {
566
- const hasFilter = args.min_price != null ||
567
- args.max_price != null ||
568
- args.rarity ||
569
- args.format ||
570
- args.set_code ||
571
- args.type_line;
572
- if (!hasFilter) {
573
- return jsonResult({ success: false, error: 'Provide at least one filter (min_price, max_price, rarity, format, set_code, type_line)' }, true);
574
- }
575
- const limit = Math.min(args.limit || 50, 200);
576
- const db = getClient();
577
- const filtersApplied = [];
578
- let query = db
579
- .from('cards')
580
- .select('name, set_code, set_name, rarity, price_usd, type_line, mana_cost, scryfall_id')
581
- .limit(limit);
582
- if (args.min_price != null) {
583
- query = query.gte('price_usd', args.min_price);
584
- filtersApplied.push(`min_price=${args.min_price}`);
585
- }
586
- if (args.max_price != null) {
587
- query = query.lte('price_usd', args.max_price);
588
- filtersApplied.push(`max_price=${args.max_price}`);
589
- }
590
- if (args.rarity) {
591
- query = query.eq('rarity', validateRarity(args.rarity));
592
- filtersApplied.push(`rarity=${args.rarity}`);
593
- }
594
- if (args.format) {
595
- const format = validateFormat(args.format);
596
- query = query.filter(`legalities->>${format}`, 'eq', 'legal');
597
- filtersApplied.push(`format=${format}`);
598
- }
599
- if (args.set_code) {
600
- query = query.eq('set_code', args.set_code.toLowerCase());
601
- filtersApplied.push(`set_code=${args.set_code}`);
602
- }
603
- if (args.type_line) {
604
- query = query.ilike('type_line', `%${args.type_line}%`);
605
- filtersApplied.push(`type_line~${args.type_line}`);
606
- }
607
- // Sort
608
- const sortBy = args.sort_by || 'price_desc';
609
- if (sortBy === 'price_asc') {
610
- query = query.order('price_usd', { ascending: true, nullsFirst: false });
611
- }
612
- else if (sortBy === 'name') {
613
- query = query.order('name', { ascending: true });
614
- }
615
- else {
616
- query = query.order('price_usd', { ascending: false, nullsFirst: false });
617
- }
618
- const { data, error } = await query;
619
- if (error)
620
- throw new Error(error.message);
621
- return jsonResult({
622
- success: true,
623
- filters_applied: filtersApplied,
624
- cards: data,
625
- count: data?.length || 0,
626
- });
627
- }
628
- catch (error) {
629
- const msg = error instanceof Error ? error.message : String(error);
630
- return jsonResult({ success: false, error: msg }, true);
631
- }
632
- },
633
- };
634
- // =============================================================================
635
- // Tool 8: Set Analysis
636
- // =============================================================================
637
- const deckSetAnalysis = {
638
- definition: {
639
- name: 'deck_set_analysis',
640
- description: 'Aggregate price stats for a set: EV breakdown by rarity, top cards, cheapest mythics.',
641
- annotations: {
642
- title: 'Set Analysis',
643
- readOnlyHint: true,
644
- destructiveHint: false,
645
- openWorldHint: true,
646
- },
647
- inputSchema: {
648
- type: 'object',
649
- properties: {
650
- set_code: {
651
- type: 'string',
652
- description: 'Set code (e.g. "MH3", "LCI", "ONE")',
653
- },
654
- },
655
- required: ['set_code'],
656
- },
657
- },
658
- handler: async (args) => {
659
- try {
660
- const db = getClient();
661
- const setCode = args.set_code.toLowerCase();
662
- // Use the deck_set_stats SQL function for efficient server-side aggregation
663
- const { data: stats, error: statsErr } = await db.rpc('deck_set_stats', {
664
- p_set_code: setCode,
665
- });
666
- if (statsErr)
667
- throw new Error(statsErr.message);
668
- if (!stats || stats.length === 0) {
669
- return jsonResult({ success: false, error: `No cards found for set "${args.set_code}"` }, true);
670
- }
671
- const row = stats[0];
672
- // Also fetch top 10 most expensive for detail
673
- const { data: topCards, error: topErr } = await db
674
- .from('cards')
675
- .select('name, rarity, price_usd')
676
- .eq('set_code', setCode)
677
- .not('price_usd', 'is', null)
678
- .gt('price_usd', 0)
679
- .order('price_usd', { ascending: false, nullsFirst: false })
680
- .limit(10);
681
- if (topErr)
682
- throw new Error(topErr.message);
683
- return jsonResult({
684
- success: true,
685
- set_code: row.set_code,
686
- set_name: row.set_name,
687
- total_cards: row.total_cards,
688
- cards_with_price: row.cards_with_price,
689
- total_value: row.total_value,
690
- avg_price: row.avg_price,
691
- median_price: row.median_price,
692
- max_price: row.max_price,
693
- max_price_card: row.max_price_card,
694
- rarity_averages: {
695
- mythic: row.mythic_avg,
696
- rare: row.rare_avg,
697
- uncommon: row.uncommon_avg,
698
- common: row.common_avg,
699
- },
700
- cheapest_mythic: row.cheapest_mythic_name
701
- ? { name: row.cheapest_mythic_name, price: row.cheapest_mythic_price }
702
- : null,
703
- top_10_expensive: topCards || [],
704
- });
705
- }
706
- catch (error) {
707
- const msg = error instanceof Error ? error.message : String(error);
708
- return jsonResult({ success: false, error: msg }, true);
709
- }
710
- },
711
- };
712
- // =============================================================================
713
- // Tool 9: Volatility
714
- // =============================================================================
715
- const deckVolatility = {
716
- definition: {
717
- name: 'deck_volatility',
718
- description: 'Price volatility scoring. Broad scan (no name) finds most volatile cards. Single card mode (with name/scryfall_id) gives detailed stability analysis with A-F grade.',
719
- annotations: {
720
- title: 'Volatility',
721
- readOnlyHint: true,
722
- destructiveHint: false,
723
- openWorldHint: true,
724
- },
725
- inputSchema: {
726
- type: 'object',
727
- properties: {
728
- name: {
729
- type: 'string',
730
- description: 'Card name for single-card analysis (omit for broad scan)',
731
- },
732
- scryfall_id: {
733
- type: 'string',
734
- description: 'Scryfall UUID for single-card analysis',
735
- },
736
- min_price: {
737
- type: 'number',
738
- description: 'Minimum price filter for broad scan (default: 1.00)',
739
- },
740
- days: {
741
- type: 'number',
742
- description: 'History window for single-card mode (default: 90)',
743
- },
744
- limit: {
745
- type: 'number',
746
- description: 'Max results for broad scan (default: 25, max: 100)',
747
- },
748
- },
749
- required: [],
750
- },
751
- },
752
- handler: async (args) => {
753
- try {
754
- const db = getClient();
755
- // ── Single card mode ──
756
- if (args.name || args.scryfall_id) {
757
- let scryfallId = args.scryfall_id;
758
- // Resolve name → scryfall_id
759
- if (!scryfallId && args.name) {
760
- const { data: cards, error: cardErr } = await db
761
- .from('cards')
762
- .select('scryfall_id, name, price_usd')
763
- .ilike('name', args.name)
764
- .limit(1);
765
- if (cardErr)
766
- throw new Error(cardErr.message);
767
- if (!cards || cards.length === 0) {
768
- return jsonResult({ success: false, error: `Card not found: ${args.name}` }, true);
769
- }
770
- scryfallId = cards[0].scryfall_id;
771
- }
772
- const days = args.days || 90;
773
- // Fetch price history and movers in parallel
774
- const [historyRes, moversRes] = await Promise.all([
775
- db
776
- .from('price_history')
777
- .select('date, price_usd')
778
- .eq('scryfall_id', scryfallId)
779
- .order('date', { ascending: false })
780
- .limit(days),
781
- db
782
- .from('price_movers')
783
- .select('period, price_from, price_to, change_pct')
784
- .eq('scryfall_id', scryfallId),
785
- ]);
786
- if (historyRes.error)
787
- throw new Error(historyRes.error.message);
788
- const prices = (historyRes.data || [])
789
- .map((r) => r.price_usd)
790
- .filter((p) => p != null && p > 0);
791
- if (prices.length < 2) {
792
- return jsonResult({
793
- success: true,
794
- mode: 'single',
795
- scryfall_id: scryfallId,
796
- message: 'Not enough price history for volatility analysis',
797
- data_points: prices.length,
798
- });
799
- }
800
- const mean = prices.reduce((s, p) => s + p, 0) / prices.length;
801
- const variance = prices.reduce((s, p) => s + (p - mean) ** 2, 0) / prices.length;
802
- const stddev = Math.sqrt(variance);
803
- const cv = (stddev / mean) * 100; // coefficient of variation as %
804
- const stability = gradeStability(cv);
805
- const trend = prices.length >= 2
806
- ? prices[0] > prices[prices.length - 1]
807
- ? 'rising'
808
- : prices[0] < prices[prices.length - 1]
809
- ? 'falling'
810
- : 'flat'
811
- : 'unknown';
812
- return jsonResult({
813
- success: true,
814
- mode: 'single',
815
- scryfall_id: scryfallId,
816
- data_points: prices.length,
817
- stats: {
818
- mean: Math.round(mean * 100) / 100,
819
- stddev: Math.round(stddev * 100) / 100,
820
- coefficient_of_variation: Math.round(cv * 100) / 100,
821
- min: Math.min(...prices),
822
- max: Math.max(...prices),
823
- range: Math.round((Math.max(...prices) - Math.min(...prices)) * 100) / 100,
824
- trend,
825
- },
826
- stability_grade: stability.grade,
827
- stability_label: stability.label,
828
- recent_movers: moversRes.data || [],
829
- });
830
- }
831
- // ── Broad scan mode — use SQL function for real CV-based volatility ──
832
- const minPrice = args.min_price ?? 1.0;
833
- const limit = Math.min(args.limit || 25, 100);
834
- const days = args.days || 30;
835
- const { data: volData, error: volErr } = await db.rpc('deck_volatility_scores', { p_days: days, p_min_price: minPrice, p_limit: limit });
836
- if (volErr)
837
- throw new Error(volErr.message);
838
- const results = (volData || []).map((r) => ({
839
- name: r.name,
840
- set_code: r.set_code,
841
- rarity: r.rarity,
842
- scryfall_id: r.scryfall_id,
843
- current_price: r.current_price,
844
- avg_price: r.avg_price,
845
- stddev: r.stddev_price,
846
- price_range: { min: r.min_price, max: r.max_price },
847
- cv_pct: r.cv_pct,
848
- stability: gradeStability(Number(r.cv_pct) || 0),
849
- data_points: r.data_points,
850
- }));
851
- return jsonResult({
852
- success: true,
853
- mode: 'broad',
854
- days,
855
- min_price_filter: minPrice,
856
- cards: results,
857
- count: results.length,
858
- });
859
- }
860
- catch (error) {
861
- const msg = error instanceof Error ? error.message : String(error);
862
- return jsonResult({ success: false, error: msg }, true);
863
- }
864
- },
865
- };
866
- // =============================================================================
867
- // Tool 10: Reprints
868
- // =============================================================================
869
- const deckReprints = {
870
- definition: {
871
- name: 'deck_reprints',
872
- description: 'Cross-printing price comparison and arbitrage finder. Shows all printings of a card sorted by price.',
873
- annotations: {
874
- title: 'Reprints',
875
- readOnlyHint: true,
876
- destructiveHint: false,
877
- openWorldHint: true,
878
- },
879
- inputSchema: {
880
- type: 'object',
881
- properties: {
882
- name: {
883
- type: 'string',
884
- description: 'Card name (exact match, case-insensitive)',
885
- },
886
- },
887
- required: ['name'],
888
- },
889
- },
890
- handler: async (args) => {
891
- try {
892
- const db = getClient();
893
- // Exact match (case-insensitive, no wildcards)
894
- const { data, error } = await db
895
- .from('cards')
896
- .select('name, set_code, set_name, rarity, price_usd, price_usd_foil, scryfall_id, image_uri')
897
- .ilike('name', args.name)
898
- .order('price_usd', { ascending: true, nullsFirst: false })
899
- .limit(100);
900
- if (error)
901
- throw new Error(error.message);
902
- // No exact match — try partial for suggestions
903
- if (!data || data.length === 0) {
904
- const { data: suggestions, error: sugErr } = await db
905
- .from('cards')
906
- .select('name')
907
- .ilike('name', `%${args.name}%`)
908
- .limit(20);
909
- if (sugErr)
910
- throw new Error(sugErr.message);
911
- const uniqueNames = [...new Set((suggestions || []).map((s) => s.name))].slice(0, 5);
912
- return jsonResult({
913
- success: false,
914
- error: `No exact match for "${args.name}"`,
915
- suggestions: uniqueNames.length > 0 ? uniqueNames : undefined,
916
- }, true);
917
- }
918
- const printings = data;
919
- // Compute summary stats
920
- const priced = printings.filter((c) => c.price_usd != null && c.price_usd > 0);
921
- const prices = priced.map((c) => c.price_usd);
922
- const summary = prices.length > 0
923
- ? {
924
- total_printings: printings.length,
925
- priced_printings: prices.length,
926
- cheapest: Math.min(...prices),
927
- most_expensive: Math.max(...prices),
928
- price_spread: Math.round((Math.max(...prices) - Math.min(...prices)) * 100) / 100,
929
- average_price: Math.round((prices.reduce((s, p) => s + p, 0) / prices.length) * 100) / 100,
930
- }
931
- : {
932
- total_printings: printings.length,
933
- priced_printings: 0,
934
- cheapest: null,
935
- most_expensive: null,
936
- price_spread: null,
937
- average_price: null,
938
- };
939
- return jsonResult({
940
- success: true,
941
- name: printings[0].name,
942
- summary,
943
- printings: printings.map((c) => ({
944
- set_code: c.set_code,
945
- set_name: c.set_name,
946
- rarity: c.rarity,
947
- price_usd: c.price_usd,
948
- price_usd_foil: c.price_usd_foil,
949
- scryfall_id: c.scryfall_id,
950
- })),
951
- });
952
- }
953
- catch (error) {
954
- const msg = error instanceof Error ? error.message : String(error);
955
- return jsonResult({ success: false, error: msg }, true);
956
- }
957
- },
958
- };
959
- // =============================================================================
960
- // Tool 11: Match History
961
- // =============================================================================
962
- const deckMatchHistory = {
963
- definition: {
964
- name: 'deck_match_history',
965
- description: 'Recent MTG Arena match history. Returns opponent, format, result, deck, duration.',
966
- annotations: {
967
- title: 'Match History',
968
- readOnlyHint: true,
969
- destructiveHint: false,
970
- openWorldHint: true,
971
- },
972
- inputSchema: {
973
- type: 'object',
974
- properties: {
975
- limit: {
976
- type: 'number',
977
- description: 'Max results (default: 20, max: 100)',
978
- },
979
- format: {
980
- type: 'string',
981
- description: 'Optional: filter by format (e.g. "Standard", "Historic")',
982
- },
983
- },
984
- required: [],
985
- },
986
- },
987
- handler: async (args) => {
988
- try {
989
- const db = getClient();
990
- const limit = Math.min(args.limit || 20, 100);
991
- let query = db
992
- .from('match_summary')
993
- .select('match_id, opponent, format, result, deck_name, duration_seconds, started_at')
994
- .order('started_at_ms', { ascending: false })
995
- .limit(limit);
996
- if (args.format) {
997
- query = query.ilike('format', args.format);
998
- }
999
- const { data, error } = await query;
1000
- if (error)
1001
- throw new Error(error.message);
1002
- return jsonResult({
1003
- success: true,
1004
- matches: data || [],
1005
- count: (data || []).length,
1006
- });
1007
- }
1008
- catch (error) {
1009
- const msg = error instanceof Error ? error.message : String(error);
1010
- return jsonResult({ success: false, error: msg }, true);
1011
- }
1012
- },
1013
- };
1014
- // =============================================================================
1015
- // Tool 12: Win Rate
1016
- // =============================================================================
1017
- const deckWinRate = {
1018
- definition: {
1019
- name: 'deck_win_rate',
1020
- description: 'Win rate statistics grouped by format. Includes overall stats and current streak.',
1021
- annotations: {
1022
- title: 'Win Rate',
1023
- readOnlyHint: true,
1024
- destructiveHint: false,
1025
- openWorldHint: true,
1026
- },
1027
- inputSchema: {
1028
- type: 'object',
1029
- properties: {
1030
- format: {
1031
- type: 'string',
1032
- description: 'Optional: filter to a specific format',
1033
- },
1034
- },
1035
- required: [],
1036
- },
1037
- },
1038
- handler: async (args) => {
1039
- try {
1040
- const db = getClient();
1041
- let query = db
1042
- .from('match_summary')
1043
- .select('format, result, started_at_ms')
1044
- .order('started_at_ms', { ascending: false });
1045
- if (args.format) {
1046
- query = query.ilike('format', args.format);
1047
- }
1048
- const { data, error } = await query;
1049
- if (error)
1050
- throw new Error(error.message);
1051
- const matches = (data || []);
1052
- // Group by format
1053
- const byFormat = new Map();
1054
- for (const m of matches) {
1055
- const fmt = m.format || 'Unknown';
1056
- if (!byFormat.has(fmt))
1057
- byFormat.set(fmt, { wins: 0, losses: 0, draws: 0, results: [] });
1058
- const bucket = byFormat.get(fmt);
1059
- const result = m.result || '';
1060
- if (result === 'win')
1061
- bucket.wins++;
1062
- else if (result === 'loss' || result === 'concede')
1063
- bucket.losses++;
1064
- else
1065
- bucket.draws++;
1066
- bucket.results.push(result);
1067
- }
1068
- // Compute current streak from all matches (chronological, most recent first)
1069
- const allResults = matches.map((m) => m.result);
1070
- let streak = 0;
1071
- const streakType = allResults[0] === 'win' ? 'W' : allResults[0] === 'loss' || allResults[0] === 'concede' ? 'L' : 'D';
1072
- for (const r of allResults) {
1073
- const rType = r === 'win' ? 'W' : r === 'loss' || r === 'concede' ? 'L' : 'D';
1074
- if (rType === streakType)
1075
- streak++;
1076
- else
1077
- break;
1078
- }
1079
- const formatStats = Array.from(byFormat.entries()).map(([fmt, b]) => {
1080
- const total = b.wins + b.losses + b.draws;
1081
- return {
1082
- format: fmt,
1083
- total,
1084
- wins: b.wins,
1085
- losses: b.losses,
1086
- draws: b.draws,
1087
- win_rate: total > 0 ? Math.round((b.wins / total) * 10000) / 100 : 0,
1088
- };
1089
- });
1090
- // Overall
1091
- const totalWins = formatStats.reduce((s, f) => s + f.wins, 0);
1092
- const totalLosses = formatStats.reduce((s, f) => s + f.losses, 0);
1093
- const totalDraws = formatStats.reduce((s, f) => s + f.draws, 0);
1094
- const totalAll = totalWins + totalLosses + totalDraws;
1095
- return jsonResult({
1096
- success: true,
1097
- overall: {
1098
- total: totalAll,
1099
- wins: totalWins,
1100
- losses: totalLosses,
1101
- draws: totalDraws,
1102
- win_rate: totalAll > 0 ? Math.round((totalWins / totalAll) * 10000) / 100 : 0,
1103
- current_streak: `${streak}${streakType}`,
1104
- },
1105
- by_format: formatStats,
1106
- });
1107
- }
1108
- catch (error) {
1109
- const msg = error instanceof Error ? error.message : String(error);
1110
- return jsonResult({ success: false, error: msg }, true);
1111
- }
1112
- },
1113
- };
1114
- // =============================================================================
1115
- // Tool 13: Card Performance
1116
- // =============================================================================
1117
- const deckCardPerformance = {
1118
- definition: {
1119
- name: 'deck_card_performance',
1120
- description: 'Best and worst cards by win rate. Requires minimum match threshold to filter noise.',
1121
- annotations: {
1122
- title: 'Card Performance',
1123
- readOnlyHint: true,
1124
- destructiveHint: false,
1125
- openWorldHint: true,
1126
- },
1127
- inputSchema: {
1128
- type: 'object',
1129
- properties: {
1130
- format: {
1131
- type: 'string',
1132
- description: 'Optional: filter by format',
1133
- },
1134
- min_matches: {
1135
- type: 'number',
1136
- description: 'Minimum matches to include (default: 3)',
1137
- },
1138
- sort: {
1139
- type: 'string',
1140
- description: 'Sort order: "win_rate desc" (default), "win_rate asc", "matches desc"',
1141
- },
1142
- limit: {
1143
- type: 'number',
1144
- description: 'Max results (default: 20, max: 100)',
1145
- },
1146
- },
1147
- required: [],
1148
- },
1149
- },
1150
- handler: async (args) => {
1151
- try {
1152
- const db = getClient();
1153
- const minMatches = args.min_matches || 3;
1154
- const limit = Math.min(args.limit || 20, 100);
1155
- let query = db
1156
- .from('card_win_rates')
1157
- .select('name, format, matches, wins, losses, win_rate, set_code')
1158
- .gte('matches', minMatches);
1159
- if (args.format) {
1160
- query = query.ilike('format', args.format);
1161
- }
1162
- // Sort
1163
- const sort = (args.sort || 'win_rate desc').toLowerCase();
1164
- if (sort === 'win_rate asc') {
1165
- query = query.order('win_rate', { ascending: true }).order('matches', { ascending: false });
1166
- }
1167
- else if (sort === 'matches desc') {
1168
- query = query.order('matches', { ascending: false });
1169
- }
1170
- else {
1171
- query = query.order('win_rate', { ascending: false }).order('matches', { ascending: false });
1172
- }
1173
- query = query.limit(limit);
1174
- const { data, error } = await query;
1175
- if (error)
1176
- throw new Error(error.message);
1177
- return jsonResult({
1178
- success: true,
1179
- cards: data || [],
1180
- count: (data || []).length,
1181
- min_matches_filter: minMatches,
1182
- });
1183
- }
1184
- catch (error) {
1185
- const msg = error instanceof Error ? error.message : String(error);
1186
- return jsonResult({ success: false, error: msg }, true);
1187
- }
1188
- },
1189
- };
1190
- // =============================================================================
1191
- // Tool 14: Deck Record
1192
- // =============================================================================
1193
- const deckRecord = {
1194
- definition: {
1195
- name: 'deck_record',
1196
- description: 'Deck-level win/loss records from deck_stats. Search by name or list all.',
1197
- annotations: {
1198
- title: 'Deck Record',
1199
- readOnlyHint: true,
1200
- destructiveHint: false,
1201
- openWorldHint: true,
1202
- },
1203
- inputSchema: {
1204
- type: 'object',
1205
- properties: {
1206
- deck_name: {
1207
- type: 'string',
1208
- description: 'Optional: search by deck name (partial match)',
1209
- },
1210
- limit: {
1211
- type: 'number',
1212
- description: 'Max results (default: 20)',
1213
- },
1214
- },
1215
- required: [],
1216
- },
1217
- },
1218
- handler: async (args) => {
1219
- try {
1220
- const db = getClient();
1221
- const limit = Math.min(args.limit || 20, 100);
1222
- let query = db
1223
- .from('deck_stats')
1224
- .select('deck_name, total_matches, wins, losses, last_played_ms')
1225
- .order('total_matches', { ascending: false })
1226
- .limit(limit);
1227
- if (args.deck_name) {
1228
- query = query.ilike('deck_name', `%${args.deck_name}%`);
1229
- }
1230
- const { data, error } = await query;
1231
- if (error)
1232
- throw new Error(error.message);
1233
- const decks = (data || []).map((d) => {
1234
- const total = d.total_matches || 0;
1235
- const wins = d.wins || 0;
1236
- const losses = d.losses || 0;
1237
- return {
1238
- deck_name: d.deck_name,
1239
- total_matches: total,
1240
- wins,
1241
- losses,
1242
- win_rate: total > 0 ? Math.round((wins / total) * 10000) / 100 : 0,
1243
- last_played: d.last_played_ms
1244
- ? new Date(Number(d.last_played_ms)).toISOString()
1245
- : null,
1246
- };
1247
- });
1248
- return jsonResult({
1249
- success: true,
1250
- decks,
1251
- count: decks.length,
1252
- });
1253
- }
1254
- catch (error) {
1255
- const msg = error instanceof Error ? error.message : String(error);
1256
- return jsonResult({ success: false, error: msg }, true);
1257
- }
1258
- },
1259
- };
1260
- // =============================================================================
1261
- // Tool 15: Player Summary
1262
- // =============================================================================
1263
- const deckPlayerSummary = {
1264
- definition: {
1265
- name: 'deck_player_summary',
1266
- description: 'Combined gameplay dashboard: total matches, overall win rate, best format, best deck, longest streak, recent form (last 10).',
1267
- annotations: {
1268
- title: 'Player Summary',
1269
- readOnlyHint: true,
1270
- destructiveHint: false,
1271
- openWorldHint: true,
1272
- },
1273
- inputSchema: {
1274
- type: 'object',
1275
- properties: {},
1276
- required: [],
1277
- },
1278
- },
1279
- handler: async () => {
1280
- try {
1281
- const db = getClient();
1282
- // Fetch matches and deck stats in parallel
1283
- const [matchRes, deckRes] = await Promise.all([
1284
- db
1285
- .from('match_summary')
1286
- .select('format, result, deck_name, started_at_ms')
1287
- .order('started_at_ms', { ascending: false }),
1288
- db
1289
- .from('deck_stats')
1290
- .select('deck_name, total_matches, wins, losses')
1291
- .order('total_matches', { ascending: false }),
1292
- ]);
1293
- if (matchRes.error)
1294
- throw new Error(matchRes.error.message);
1295
- if (deckRes.error)
1296
- throw new Error(deckRes.error.message);
1297
- const matches = (matchRes.data || []);
1298
- const decks = (deckRes.data || []);
1299
- // Overall stats
1300
- const totalMatches = matches.length;
1301
- const wins = matches.filter((m) => m.result === 'win').length;
1302
- const losses = matches.filter((m) => m.result === 'loss' || m.result === 'concede').length;
1303
- const overallWinRate = totalMatches > 0 ? Math.round((wins / totalMatches) * 10000) / 100 : 0;
1304
- // Best format (by win rate, min 5 matches)
1305
- const fmtMap = new Map();
1306
- for (const m of matches) {
1307
- const fmt = m.format || 'Unknown';
1308
- if (!fmtMap.has(fmt))
1309
- fmtMap.set(fmt, { w: 0, t: 0 });
1310
- const b = fmtMap.get(fmt);
1311
- b.t++;
1312
- if (m.result === 'win')
1313
- b.w++;
1314
- }
1315
- let bestFormat = null;
1316
- for (const [fmt, b] of fmtMap) {
1317
- if (b.t >= 5) {
1318
- const wr = b.w / b.t;
1319
- if (!bestFormat || wr > bestFormat.win_rate) {
1320
- bestFormat = { format: fmt, win_rate: Math.round(wr * 10000) / 100, matches: b.t };
1321
- }
1322
- }
1323
- }
1324
- // Best deck (from deck_stats, min 3 matches)
1325
- let bestDeck = null;
1326
- for (const d of decks) {
1327
- const total = d.total_matches || 0;
1328
- const w = d.wins || 0;
1329
- if (total >= 3) {
1330
- const wr = w / total;
1331
- if (!bestDeck || wr > bestDeck.win_rate) {
1332
- bestDeck = {
1333
- deck_name: d.deck_name,
1334
- win_rate: Math.round(wr * 10000) / 100,
1335
- matches: total,
1336
- };
1337
- }
1338
- }
1339
- }
1340
- // Longest streak (all-time)
1341
- let longestStreak = 0;
1342
- let longestStreakType = '';
1343
- let currentRun = 0;
1344
- let currentType = '';
1345
- for (const m of matches) {
1346
- const r = m.result === 'win' ? 'W' : 'L';
1347
- if (r === currentType) {
1348
- currentRun++;
1349
- }
1350
- else {
1351
- currentType = r;
1352
- currentRun = 1;
1353
- }
1354
- if (currentRun > longestStreak) {
1355
- longestStreak = currentRun;
1356
- longestStreakType = currentType;
1357
- }
1358
- }
1359
- // Recent form (last 10)
1360
- const recentForm = matches.slice(0, 10).map((m) => {
1361
- const r = m.result;
1362
- return r === 'win' ? 'W' : r === 'loss' || r === 'concede' ? 'L' : 'D';
1363
- });
1364
- return jsonResult({
1365
- success: true,
1366
- total_matches: totalMatches,
1367
- overall_win_rate: overallWinRate,
1368
- wins,
1369
- losses,
1370
- best_format: bestFormat,
1371
- best_deck: bestDeck,
1372
- longest_streak: `${longestStreak}${longestStreakType}`,
1373
- recent_form: recentForm.join(''),
1374
- total_decks: decks.length,
1375
- });
1376
- }
1377
- catch (error) {
1378
- const msg = error instanceof Error ? error.message : String(error);
1379
- return jsonResult({ success: false, error: msg }, true);
1380
- }
1381
- },
1382
- };
1383
- // =============================================================================
1384
- // Tool 16: Draft Stats
1385
- // =============================================================================
1386
- const deckDraftStats = {
1387
- definition: {
1388
- name: 'deck_draft_stats',
1389
- description: 'Draft history and performance from MTG Arena. Returns event, set, record, trophy status per draft.',
1390
- annotations: {
1391
- title: 'Draft Stats',
1392
- readOnlyHint: true,
1393
- destructiveHint: false,
1394
- openWorldHint: true,
1395
- },
1396
- inputSchema: {
1397
- type: 'object',
1398
- properties: {
1399
- set_code: {
1400
- type: 'string',
1401
- description: 'Optional: filter by set code (e.g. "OTJ", "MKM")',
1402
- },
1403
- limit: {
1404
- type: 'number',
1405
- description: 'Max results (default: 20, max: 100)',
1406
- },
1407
- },
1408
- required: [],
1409
- },
1410
- },
1411
- handler: async (args) => {
1412
- try {
1413
- const db = getClient();
1414
- const limit = Math.min(args.limit || 20, 100);
1415
- let query = db
1416
- .from('draft_summary')
1417
- .select('event_name, set_code, completed_at, total_picks, wins, losses')
1418
- .order('completed_at', { ascending: false })
1419
- .limit(limit);
1420
- if (args.set_code) {
1421
- query = query.ilike('set_code', args.set_code);
1422
- }
1423
- const { data, error } = await query;
1424
- if (error)
1425
- throw new Error(error.message);
1426
- const drafts = (data || []).map((d) => {
1427
- const wins = d.wins || 0;
1428
- const losses = d.losses || 0;
1429
- return {
1430
- event_name: d.event_name,
1431
- set_code: d.set_code,
1432
- completed_at: d.completed_at,
1433
- total_picks: d.total_picks,
1434
- wins,
1435
- losses,
1436
- record: `${wins}-${losses}`,
1437
- trophy: wins >= 7,
1438
- };
1439
- });
1440
- return jsonResult({
1441
- success: true,
1442
- drafts,
1443
- count: drafts.length,
1444
- });
1445
- }
1446
- catch (error) {
1447
- const msg = error instanceof Error ? error.message : String(error);
1448
- return jsonResult({ success: false, error: msg }, true);
1449
- }
1450
- },
1451
- };
1452
- // =============================================================================
1453
- // Intel Reports
1454
- // =============================================================================
1455
- const INTEL_TYPES = [
1456
- 'price_spike', 'price_crash', 'format_shakeup', 'sleeper_pick',
1457
- 'deck_tech', 'meta_shift', 'budget_alert', 'spec_target',
1458
- 'correlation', 'event_impact', 'reprint_arb', 'supply_signal',
1459
- ];
1460
- const INTEL_URGENCIES = ['breaking', 'daily', 'weekly', 'background'];
1461
- const deckIntelWrite = {
1462
- definition: {
1463
- name: 'deck_intel_write',
1464
- description: 'Write an intel report to the intel_reports table. Used by the deckscry-intel agent to publish analysis results.',
1465
- annotations: {
1466
- title: 'Write Intel Report',
1467
- readOnlyHint: false,
1468
- destructiveHint: false,
1469
- openWorldHint: true,
1470
- },
1471
- inputSchema: {
1472
- type: 'object',
1473
- properties: {
1474
- type: {
1475
- type: 'string',
1476
- enum: [...INTEL_TYPES],
1477
- description: 'Intel report type',
1478
- },
1479
- format: {
1480
- type: 'string',
1481
- description: 'MTG format (standard, pioneer, modern, etc.) or null for cross-format',
1482
- },
1483
- urgency: {
1484
- type: 'string',
1485
- enum: [...INTEL_URGENCIES],
1486
- description: 'Report urgency (default: daily)',
1487
- },
1488
- title: {
1489
- type: 'string',
1490
- description: 'Report title — e.g. "Sheoldred spikes 40% after Pioneer RCQ results"',
1491
- },
1492
- summary: {
1493
- type: 'string',
1494
- description: '2-3 sentence analysis with evidence and reasoning',
1495
- },
1496
- cards: {
1497
- type: 'array',
1498
- items: { type: 'string' },
1499
- description: 'Scryfall UUIDs of cards involved',
1500
- },
1501
- data: {
1502
- type: 'object',
1503
- description: 'Structured payload — analyzer name, confidence, evidence, involved_cards details',
1504
- },
1505
- social_hook: {
1506
- type: 'string',
1507
- description: 'Pre-written social media hook with specific numbers, timeframe, and why. Under 200 chars.',
1508
- },
1509
- },
1510
- required: ['type', 'title', 'summary'],
1511
- },
1512
- },
1513
- handler: async (args) => {
1514
- try {
1515
- const db = getClient();
1516
- const { data, error } = await db
1517
- .from('intel_reports')
1518
- .insert({
1519
- type: args.type,
1520
- format: args.format || null,
1521
- urgency: args.urgency || 'daily',
1522
- title: args.title,
1523
- summary: args.summary,
1524
- cards: args.cards || [],
1525
- data: args.data || {},
1526
- social_hook: args.social_hook || null,
1527
- })
1528
- .select('id, type, format, urgency, title, created_at')
1529
- .single();
1530
- if (error)
1531
- throw new Error(error.message);
1532
- return jsonResult({ success: true, report: data });
1533
- }
1534
- catch (error) {
1535
- const msg = error instanceof Error ? error.message : String(error);
1536
- return jsonResult({ success: false, error: msg }, true);
1537
- }
1538
- },
1539
- };
1540
- const deckIntelReports = {
1541
- definition: {
1542
- name: 'deck_intel_reports',
1543
- description: 'Read intel reports. Filter by type, format, urgency, or unpublished status. Used by marketer and other consumers.',
1544
- annotations: {
1545
- title: 'Read Intel Reports',
1546
- readOnlyHint: true,
1547
- destructiveHint: false,
1548
- openWorldHint: true,
1549
- },
1550
- inputSchema: {
1551
- type: 'object',
1552
- properties: {
1553
- type: { type: 'string', description: 'Filter by intel type' },
1554
- format: { type: 'string', description: 'Filter by MTG format' },
1555
- urgency: { type: 'string', description: 'Filter by urgency level' },
1556
- unpublished: { type: 'boolean', description: 'If true, only return reports not yet consumed by marketer' },
1557
- limit: { type: 'number', description: 'Max reports to return (default: 20)' },
1558
- },
1559
- required: [],
1560
- },
1561
- },
1562
- handler: async (args) => {
1563
- try {
1564
- const db = getClient();
1565
- let query = db
1566
- .from('intel_reports')
1567
- .select('*')
1568
- .order('created_at', { ascending: false })
1569
- .limit(args.limit || 20);
1570
- if (args.type)
1571
- query = query.eq('type', args.type);
1572
- if (args.format)
1573
- query = query.eq('format', args.format);
1574
- if (args.urgency)
1575
- query = query.eq('urgency', args.urgency);
1576
- if (args.unpublished)
1577
- query = query.eq('published', false);
1578
- const { data, error } = await query;
1579
- if (error)
1580
- throw new Error(error.message);
1581
- return jsonResult({
1582
- success: true,
1583
- reports: data,
1584
- count: data?.length || 0,
1585
- });
1586
- }
1587
- catch (error) {
1588
- const msg = error instanceof Error ? error.message : String(error);
1589
- return jsonResult({ success: false, error: msg }, true);
1590
- }
1591
- },
1592
- };
1593
- const deckIntelPublish = {
1594
- definition: {
1595
- name: 'deck_intel_publish',
1596
- description: 'Mark intel reports as published (consumed by marketer). Accepts one or more report IDs.',
1597
- annotations: {
1598
- title: 'Mark Intel Published',
1599
- readOnlyHint: false,
1600
- destructiveHint: false,
1601
- openWorldHint: true,
1602
- },
1603
- inputSchema: {
1604
- type: 'object',
1605
- properties: {
1606
- report_ids: {
1607
- type: 'array',
1608
- items: { type: 'string' },
1609
- description: 'UUIDs of reports to mark as published',
1610
- },
1611
- },
1612
- required: ['report_ids'],
1613
- },
1614
- },
1615
- handler: async (args) => {
1616
- try {
1617
- const db = getClient();
1618
- const { error } = await db
1619
- .from('intel_reports')
1620
- .update({ published: true })
1621
- .in('id', args.report_ids);
1622
- if (error)
1623
- throw new Error(error.message);
1624
- return jsonResult({ success: true, published: args.report_ids.length });
1625
- }
1626
- catch (error) {
1627
- const msg = error instanceof Error ? error.message : String(error);
1628
- return jsonResult({ success: false, error: msg }, true);
1629
- }
1630
- },
1631
- };
1632
- // =============================================================================
1633
- // Export
1634
- // =============================================================================
1635
- export const deckTools = [
1636
- deckSearch,
1637
- deckList,
1638
- deckSummary,
1639
- deckHistory,
1640
- deckFormatDeals,
1641
- deckTypeSearch,
1642
- deckPriceBracket,
1643
- deckSetAnalysis,
1644
- deckVolatility,
1645
- deckReprints,
1646
- deckMatchHistory,
1647
- deckWinRate,
1648
- deckCardPerformance,
1649
- deckRecord,
1650
- deckPlayerSummary,
1651
- deckDraftStats,
1652
- deckIntelWrite,
1653
- deckIntelReports,
1654
- deckIntelPublish,
1655
- ];
1656
- //# sourceMappingURL=deck.js.map