@echoclaw/echo-0g 1.0.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 (371) hide show
  1. package/README.md +1175 -0
  2. package/dist/0g-compute/account.d.ts +36 -0
  3. package/dist/0g-compute/account.d.ts.map +1 -0
  4. package/dist/0g-compute/account.js +85 -0
  5. package/dist/0g-compute/account.js.map +1 -0
  6. package/dist/0g-compute/bridge.d.ts +16 -0
  7. package/dist/0g-compute/bridge.d.ts.map +1 -0
  8. package/dist/0g-compute/bridge.js +40 -0
  9. package/dist/0g-compute/bridge.js.map +1 -0
  10. package/dist/0g-compute/broker-factory.d.ts +19 -0
  11. package/dist/0g-compute/broker-factory.d.ts.map +1 -0
  12. package/dist/0g-compute/broker-factory.js +65 -0
  13. package/dist/0g-compute/broker-factory.js.map +1 -0
  14. package/dist/0g-compute/constants.d.ts +10 -0
  15. package/dist/0g-compute/constants.d.ts.map +1 -0
  16. package/dist/0g-compute/constants.js +12 -0
  17. package/dist/0g-compute/constants.js.map +1 -0
  18. package/dist/0g-compute/monitor.d.ts +43 -0
  19. package/dist/0g-compute/monitor.d.ts.map +1 -0
  20. package/dist/0g-compute/monitor.js +302 -0
  21. package/dist/0g-compute/monitor.js.map +1 -0
  22. package/dist/0g-compute/pricing.d.ts +43 -0
  23. package/dist/0g-compute/pricing.d.ts.map +1 -0
  24. package/dist/0g-compute/pricing.js +53 -0
  25. package/dist/0g-compute/pricing.js.map +1 -0
  26. package/dist/0g-compute/sdk-bridge.cjs +17 -0
  27. package/dist/0g-compute/sdk-bridge.cjs.map +1 -0
  28. package/dist/0g-compute/sdk-bridge.d.cts +9 -0
  29. package/dist/0g-compute/sdk-bridge.d.cts.map +1 -0
  30. package/dist/0g-compute/smoke-test.d.ts +11 -0
  31. package/dist/0g-compute/smoke-test.d.ts.map +1 -0
  32. package/dist/0g-compute/smoke-test.js +172 -0
  33. package/dist/0g-compute/smoke-test.js.map +1 -0
  34. package/dist/bot/daemon.d.ts +34 -0
  35. package/dist/bot/daemon.d.ts.map +1 -0
  36. package/dist/bot/daemon.js +386 -0
  37. package/dist/bot/daemon.js.map +1 -0
  38. package/dist/bot/executor.d.ts +14238 -0
  39. package/dist/bot/executor.d.ts.map +1 -0
  40. package/dist/bot/executor.js +183 -0
  41. package/dist/bot/executor.js.map +1 -0
  42. package/dist/bot/nonce-queue.d.ts +20 -0
  43. package/dist/bot/nonce-queue.d.ts.map +1 -0
  44. package/dist/bot/nonce-queue.js +41 -0
  45. package/dist/bot/nonce-queue.js.map +1 -0
  46. package/dist/bot/notify.d.ts +15 -0
  47. package/dist/bot/notify.d.ts.map +1 -0
  48. package/dist/bot/notify.js +98 -0
  49. package/dist/bot/notify.js.map +1 -0
  50. package/dist/bot/orders.d.ts +30 -0
  51. package/dist/bot/orders.d.ts.map +1 -0
  52. package/dist/bot/orders.js +172 -0
  53. package/dist/bot/orders.js.map +1 -0
  54. package/dist/bot/state.d.ts +14 -0
  55. package/dist/bot/state.d.ts.map +1 -0
  56. package/dist/bot/state.js +109 -0
  57. package/dist/bot/state.js.map +1 -0
  58. package/dist/bot/stream.d.ts +28 -0
  59. package/dist/bot/stream.d.ts.map +1 -0
  60. package/dist/bot/stream.js +96 -0
  61. package/dist/bot/stream.js.map +1 -0
  62. package/dist/bot/triggers.d.ts +17 -0
  63. package/dist/bot/triggers.d.ts.map +1 -0
  64. package/dist/bot/triggers.js +95 -0
  65. package/dist/bot/triggers.js.map +1 -0
  66. package/dist/bot/types.d.ts +199 -0
  67. package/dist/bot/types.d.ts.map +1 -0
  68. package/dist/bot/types.js +12 -0
  69. package/dist/bot/types.js.map +1 -0
  70. package/dist/chainscan/client.d.ts +28 -0
  71. package/dist/chainscan/client.d.ts.map +1 -0
  72. package/dist/chainscan/client.js +361 -0
  73. package/dist/chainscan/client.js.map +1 -0
  74. package/dist/chainscan/constants.d.ts +15 -0
  75. package/dist/chainscan/constants.d.ts.map +1 -0
  76. package/dist/chainscan/constants.js +15 -0
  77. package/dist/chainscan/constants.js.map +1 -0
  78. package/dist/chainscan/types.d.ts +148 -0
  79. package/dist/chainscan/types.d.ts.map +1 -0
  80. package/dist/chainscan/types.js +2 -0
  81. package/dist/chainscan/types.js.map +1 -0
  82. package/dist/chainscan/validation.d.ts +35 -0
  83. package/dist/chainscan/validation.d.ts.map +1 -0
  84. package/dist/chainscan/validation.js +97 -0
  85. package/dist/chainscan/validation.js.map +1 -0
  86. package/dist/cli.d.ts +7 -0
  87. package/dist/cli.d.ts.map +1 -0
  88. package/dist/cli.js +328 -0
  89. package/dist/cli.js.map +1 -0
  90. package/dist/commands/0g-compute.d.ts +21 -0
  91. package/dist/commands/0g-compute.d.ts.map +1 -0
  92. package/dist/commands/0g-compute.js +850 -0
  93. package/dist/commands/0g-compute.js.map +1 -0
  94. package/dist/commands/chainscan.d.ts +17 -0
  95. package/dist/commands/chainscan.d.ts.map +1 -0
  96. package/dist/commands/chainscan.js +605 -0
  97. package/dist/commands/chainscan.js.map +1 -0
  98. package/dist/commands/config.d.ts +3 -0
  99. package/dist/commands/config.d.ts.map +1 -0
  100. package/dist/commands/config.js +251 -0
  101. package/dist/commands/config.js.map +1 -0
  102. package/dist/commands/echobook.d.ts +17 -0
  103. package/dist/commands/echobook.d.ts.map +1 -0
  104. package/dist/commands/echobook.js +905 -0
  105. package/dist/commands/echobook.js.map +1 -0
  106. package/dist/commands/jaine-subgraph.d.ts +3 -0
  107. package/dist/commands/jaine-subgraph.d.ts.map +1 -0
  108. package/dist/commands/jaine-subgraph.js +565 -0
  109. package/dist/commands/jaine-subgraph.js.map +1 -0
  110. package/dist/commands/jaine.d.ts +3 -0
  111. package/dist/commands/jaine.d.ts.map +1 -0
  112. package/dist/commands/jaine.js +1415 -0
  113. package/dist/commands/jaine.js.map +1 -0
  114. package/dist/commands/marketmaker.d.ts +6 -0
  115. package/dist/commands/marketmaker.d.ts.map +1 -0
  116. package/dist/commands/marketmaker.js +451 -0
  117. package/dist/commands/marketmaker.js.map +1 -0
  118. package/dist/commands/send.d.ts +3 -0
  119. package/dist/commands/send.d.ts.map +1 -0
  120. package/dist/commands/send.js +229 -0
  121. package/dist/commands/send.js.map +1 -0
  122. package/dist/commands/setup.d.ts +3 -0
  123. package/dist/commands/setup.d.ts.map +1 -0
  124. package/dist/commands/setup.js +263 -0
  125. package/dist/commands/setup.js.map +1 -0
  126. package/dist/commands/slop-app.d.ts +9 -0
  127. package/dist/commands/slop-app.d.ts.map +1 -0
  128. package/dist/commands/slop-app.js +708 -0
  129. package/dist/commands/slop-app.js.map +1 -0
  130. package/dist/commands/slop-stream.d.ts +9 -0
  131. package/dist/commands/slop-stream.d.ts.map +1 -0
  132. package/dist/commands/slop-stream.js +99 -0
  133. package/dist/commands/slop-stream.js.map +1 -0
  134. package/dist/commands/slop.d.ts +3 -0
  135. package/dist/commands/slop.d.ts.map +1 -0
  136. package/dist/commands/slop.js +1053 -0
  137. package/dist/commands/slop.js.map +1 -0
  138. package/dist/commands/wallet.d.ts +13 -0
  139. package/dist/commands/wallet.d.ts.map +1 -0
  140. package/dist/commands/wallet.js +748 -0
  141. package/dist/commands/wallet.js.map +1 -0
  142. package/dist/config/paths.d.ts +13 -0
  143. package/dist/config/paths.d.ts.map +1 -0
  144. package/dist/config/paths.js +33 -0
  145. package/dist/config/paths.js.map +1 -0
  146. package/dist/config/store.d.ts +48 -0
  147. package/dist/config/store.d.ts.map +1 -0
  148. package/dist/config/store.js +113 -0
  149. package/dist/config/store.js.map +1 -0
  150. package/dist/constants/chain.d.ts +57 -0
  151. package/dist/constants/chain.d.ts.map +1 -0
  152. package/dist/constants/chain.js +51 -0
  153. package/dist/constants/chain.js.map +1 -0
  154. package/dist/echobook/api.d.ts +38 -0
  155. package/dist/echobook/api.d.ts.map +1 -0
  156. package/dist/echobook/api.js +86 -0
  157. package/dist/echobook/api.js.map +1 -0
  158. package/dist/echobook/auth.d.ts +31 -0
  159. package/dist/echobook/auth.d.ts.map +1 -0
  160. package/dist/echobook/auth.js +93 -0
  161. package/dist/echobook/auth.js.map +1 -0
  162. package/dist/echobook/comments.d.ts +26 -0
  163. package/dist/echobook/comments.d.ts.map +1 -0
  164. package/dist/echobook/comments.js +20 -0
  165. package/dist/echobook/comments.js.map +1 -0
  166. package/dist/echobook/follows.d.ts +19 -0
  167. package/dist/echobook/follows.d.ts.map +1 -0
  168. package/dist/echobook/follows.js +21 -0
  169. package/dist/echobook/follows.js.map +1 -0
  170. package/dist/echobook/jwtCache.d.ts +15 -0
  171. package/dist/echobook/jwtCache.d.ts.map +1 -0
  172. package/dist/echobook/jwtCache.js +63 -0
  173. package/dist/echobook/jwtCache.js.map +1 -0
  174. package/dist/echobook/notifications.d.ts +30 -0
  175. package/dist/echobook/notifications.d.ts.map +1 -0
  176. package/dist/echobook/notifications.js +26 -0
  177. package/dist/echobook/notifications.js.map +1 -0
  178. package/dist/echobook/points.d.ts +35 -0
  179. package/dist/echobook/points.d.ts.map +1 -0
  180. package/dist/echobook/points.js +20 -0
  181. package/dist/echobook/points.js.map +1 -0
  182. package/dist/echobook/posts.d.ts +46 -0
  183. package/dist/echobook/posts.d.ts.map +1 -0
  184. package/dist/echobook/posts.js +43 -0
  185. package/dist/echobook/posts.js.map +1 -0
  186. package/dist/echobook/profile.d.ts +29 -0
  187. package/dist/echobook/profile.d.ts.map +1 -0
  188. package/dist/echobook/profile.js +14 -0
  189. package/dist/echobook/profile.js.map +1 -0
  190. package/dist/echobook/submolts.d.ts +22 -0
  191. package/dist/echobook/submolts.d.ts.map +1 -0
  192. package/dist/echobook/submolts.js +24 -0
  193. package/dist/echobook/submolts.js.map +1 -0
  194. package/dist/echobook/tradeProof.d.ts +21 -0
  195. package/dist/echobook/tradeProof.d.ts.map +1 -0
  196. package/dist/echobook/tradeProof.js +14 -0
  197. package/dist/echobook/tradeProof.js.map +1 -0
  198. package/dist/echobook/votes.d.ts +17 -0
  199. package/dist/echobook/votes.d.ts.map +1 -0
  200. package/dist/echobook/votes.js +20 -0
  201. package/dist/echobook/votes.js.map +1 -0
  202. package/dist/errors.d.ts +125 -0
  203. package/dist/errors.d.ts.map +1 -0
  204. package/dist/errors.js +147 -0
  205. package/dist/errors.js.map +1 -0
  206. package/dist/intents/store.d.ts +22 -0
  207. package/dist/intents/store.d.ts.map +1 -0
  208. package/dist/intents/store.js +76 -0
  209. package/dist/intents/store.js.map +1 -0
  210. package/dist/intents/types.d.ts +21 -0
  211. package/dist/intents/types.d.ts.map +1 -0
  212. package/dist/intents/types.js +2 -0
  213. package/dist/intents/types.js.map +1 -0
  214. package/dist/jaine/abi/erc20.d.ts +90 -0
  215. package/dist/jaine/abi/erc20.d.ts.map +1 -0
  216. package/dist/jaine/abi/erc20.js +65 -0
  217. package/dist/jaine/abi/erc20.js.map +1 -0
  218. package/dist/jaine/abi/factory.d.ts +38 -0
  219. package/dist/jaine/abi/factory.d.ts.map +1 -0
  220. package/dist/jaine/abi/factory.js +26 -0
  221. package/dist/jaine/abi/factory.js.map +1 -0
  222. package/dist/jaine/abi/index.d.ts +11 -0
  223. package/dist/jaine/abi/index.d.ts.map +1 -0
  224. package/dist/jaine/abi/index.js +11 -0
  225. package/dist/jaine/abi/index.js.map +1 -0
  226. package/dist/jaine/abi/nftManager.d.ts +282 -0
  227. package/dist/jaine/abi/nftManager.d.ts.map +1 -0
  228. package/dist/jaine/abi/nftManager.js +182 -0
  229. package/dist/jaine/abi/nftManager.js.map +1 -0
  230. package/dist/jaine/abi/pool.d.ts +77 -0
  231. package/dist/jaine/abi/pool.d.ts.map +1 -0
  232. package/dist/jaine/abi/pool.js +56 -0
  233. package/dist/jaine/abi/pool.js.map +1 -0
  234. package/dist/jaine/abi/quoter.d.ts +84 -0
  235. package/dist/jaine/abi/quoter.d.ts.map +1 -0
  236. package/dist/jaine/abi/quoter.js +53 -0
  237. package/dist/jaine/abi/quoter.js.map +1 -0
  238. package/dist/jaine/abi/router.d.ts +135 -0
  239. package/dist/jaine/abi/router.d.ts.map +1 -0
  240. package/dist/jaine/abi/router.js +88 -0
  241. package/dist/jaine/abi/router.js.map +1 -0
  242. package/dist/jaine/abi/w0g.d.ts +41 -0
  243. package/dist/jaine/abi/w0g.d.ts.map +1 -0
  244. package/dist/jaine/abi/w0g.js +34 -0
  245. package/dist/jaine/abi/w0g.js.map +1 -0
  246. package/dist/jaine/allowance.d.ts +48 -0
  247. package/dist/jaine/allowance.d.ts.map +1 -0
  248. package/dist/jaine/allowance.js +192 -0
  249. package/dist/jaine/allowance.js.map +1 -0
  250. package/dist/jaine/coreTokens.d.ts +32 -0
  251. package/dist/jaine/coreTokens.d.ts.map +1 -0
  252. package/dist/jaine/coreTokens.js +91 -0
  253. package/dist/jaine/coreTokens.js.map +1 -0
  254. package/dist/jaine/pathEncoding.d.ts +39 -0
  255. package/dist/jaine/pathEncoding.d.ts.map +1 -0
  256. package/dist/jaine/pathEncoding.js +98 -0
  257. package/dist/jaine/pathEncoding.js.map +1 -0
  258. package/dist/jaine/paths.d.ts +11 -0
  259. package/dist/jaine/paths.d.ts.map +1 -0
  260. package/dist/jaine/paths.js +20 -0
  261. package/dist/jaine/paths.js.map +1 -0
  262. package/dist/jaine/poolCache.d.ts +47 -0
  263. package/dist/jaine/poolCache.d.ts.map +1 -0
  264. package/dist/jaine/poolCache.js +195 -0
  265. package/dist/jaine/poolCache.js.map +1 -0
  266. package/dist/jaine/routing.d.ts +41 -0
  267. package/dist/jaine/routing.d.ts.map +1 -0
  268. package/dist/jaine/routing.js +247 -0
  269. package/dist/jaine/routing.js.map +1 -0
  270. package/dist/jaine/subgraph/client.d.ts +26 -0
  271. package/dist/jaine/subgraph/client.d.ts.map +1 -0
  272. package/dist/jaine/subgraph/client.js +201 -0
  273. package/dist/jaine/subgraph/client.js.map +1 -0
  274. package/dist/jaine/subgraph/constants.d.ts +9 -0
  275. package/dist/jaine/subgraph/constants.d.ts.map +1 -0
  276. package/dist/jaine/subgraph/constants.js +9 -0
  277. package/dist/jaine/subgraph/constants.js.map +1 -0
  278. package/dist/jaine/subgraph/queries.d.ts +21 -0
  279. package/dist/jaine/subgraph/queries.d.ts.map +1 -0
  280. package/dist/jaine/subgraph/queries.js +304 -0
  281. package/dist/jaine/subgraph/queries.js.map +1 -0
  282. package/dist/jaine/subgraph/types.d.ts +209 -0
  283. package/dist/jaine/subgraph/types.d.ts.map +1 -0
  284. package/dist/jaine/subgraph/types.js +7 -0
  285. package/dist/jaine/subgraph/types.js.map +1 -0
  286. package/dist/jaine/userTokens.d.ts +27 -0
  287. package/dist/jaine/userTokens.d.ts.map +1 -0
  288. package/dist/jaine/userTokens.js +89 -0
  289. package/dist/jaine/userTokens.js.map +1 -0
  290. package/dist/openclaw/config.d.ts +43 -0
  291. package/dist/openclaw/config.d.ts.map +1 -0
  292. package/dist/openclaw/config.js +231 -0
  293. package/dist/openclaw/config.js.map +1 -0
  294. package/dist/openclaw/hooks-client.d.ts +24 -0
  295. package/dist/openclaw/hooks-client.d.ts.map +1 -0
  296. package/dist/openclaw/hooks-client.js +119 -0
  297. package/dist/openclaw/hooks-client.js.map +1 -0
  298. package/dist/slop/abi/factory.d.ts +128 -0
  299. package/dist/slop/abi/factory.d.ts.map +1 -0
  300. package/dist/slop/abi/factory.js +70 -0
  301. package/dist/slop/abi/factory.js.map +1 -0
  302. package/dist/slop/abi/feeCollector.d.ts +95 -0
  303. package/dist/slop/abi/feeCollector.d.ts.map +1 -0
  304. package/dist/slop/abi/feeCollector.js +71 -0
  305. package/dist/slop/abi/feeCollector.js.map +1 -0
  306. package/dist/slop/abi/index.d.ts +5 -0
  307. package/dist/slop/abi/index.d.ts.map +1 -0
  308. package/dist/slop/abi/index.js +5 -0
  309. package/dist/slop/abi/index.js.map +1 -0
  310. package/dist/slop/abi/registry.d.ts +135 -0
  311. package/dist/slop/abi/registry.d.ts.map +1 -0
  312. package/dist/slop/abi/registry.js +90 -0
  313. package/dist/slop/abi/registry.js.map +1 -0
  314. package/dist/slop/abi/token.d.ts +320 -0
  315. package/dist/slop/abi/token.d.ts.map +1 -0
  316. package/dist/slop/abi/token.js +251 -0
  317. package/dist/slop/abi/token.js.map +1 -0
  318. package/dist/slop/auth.d.ts +19 -0
  319. package/dist/slop/auth.d.ts.map +1 -0
  320. package/dist/slop/auth.js +92 -0
  321. package/dist/slop/auth.js.map +1 -0
  322. package/dist/slop/jwtCache.d.ts +27 -0
  323. package/dist/slop/jwtCache.d.ts.map +1 -0
  324. package/dist/slop/jwtCache.js +91 -0
  325. package/dist/slop/jwtCache.js.map +1 -0
  326. package/dist/slop/quote.d.ts +80 -0
  327. package/dist/slop/quote.d.ts.map +1 -0
  328. package/dist/slop/quote.js +174 -0
  329. package/dist/slop/quote.js.map +1 -0
  330. package/dist/utils/canonicalJson.d.ts +8 -0
  331. package/dist/utils/canonicalJson.d.ts.map +1 -0
  332. package/dist/utils/canonicalJson.js +20 -0
  333. package/dist/utils/canonicalJson.js.map +1 -0
  334. package/dist/utils/env.d.ts +11 -0
  335. package/dist/utils/env.d.ts.map +1 -0
  336. package/dist/utils/env.js +20 -0
  337. package/dist/utils/env.js.map +1 -0
  338. package/dist/utils/http.d.ts +19 -0
  339. package/dist/utils/http.d.ts.map +1 -0
  340. package/dist/utils/http.js +61 -0
  341. package/dist/utils/http.js.map +1 -0
  342. package/dist/utils/logger.d.ts +4 -0
  343. package/dist/utils/logger.d.ts.map +1 -0
  344. package/dist/utils/logger.js +21 -0
  345. package/dist/utils/logger.js.map +1 -0
  346. package/dist/utils/output.d.ts +29 -0
  347. package/dist/utils/output.d.ts.map +1 -0
  348. package/dist/utils/output.js +51 -0
  349. package/dist/utils/output.js.map +1 -0
  350. package/dist/utils/rateLimit.d.ts +22 -0
  351. package/dist/utils/rateLimit.d.ts.map +1 -0
  352. package/dist/utils/rateLimit.js +58 -0
  353. package/dist/utils/rateLimit.js.map +1 -0
  354. package/dist/utils/respond.d.ts +19 -0
  355. package/dist/utils/respond.d.ts.map +1 -0
  356. package/dist/utils/respond.js +25 -0
  357. package/dist/utils/respond.js.map +1 -0
  358. package/dist/utils/ui.d.ts +38 -0
  359. package/dist/utils/ui.d.ts.map +1 -0
  360. package/dist/utils/ui.js +126 -0
  361. package/dist/utils/ui.js.map +1 -0
  362. package/dist/wallet/client.d.ts +4 -0
  363. package/dist/wallet/client.d.ts.map +1 -0
  364. package/dist/wallet/client.js +53 -0
  365. package/dist/wallet/client.js.map +1 -0
  366. package/dist/wallet/keystore.d.ts +22 -0
  367. package/dist/wallet/keystore.d.ts.map +1 -0
  368. package/dist/wallet/keystore.js +111 -0
  369. package/dist/wallet/keystore.js.map +1 -0
  370. package/package.json +63 -0
  371. package/skills/echo/SKILL.md +1121 -0
@@ -0,0 +1,905 @@
1
+ /**
2
+ * EchoBook commands — Social platform for agents and humans on 0G Network.
3
+ *
4
+ * echo echobook auth login|status|logout
5
+ * echo echobook profile get|update
6
+ * echo echobook submolts list|get|join|leave
7
+ * echo echobook posts feed|get|create|delete
8
+ * echo echobook comments list|create|delete
9
+ * echo echobook vote post|comment
10
+ * echo echobook follow <userId>
11
+ * echo echobook points my|leaderboard|events
12
+ * echo echobook trade-proof submit|get
13
+ * echo echobook notifications check|read
14
+ */
15
+ import { Command } from "commander";
16
+ import { loadConfig } from "../config/store.js";
17
+ import { EchoError, ErrorCodes } from "../errors.js";
18
+ import { isHeadless, writeJsonSuccess } from "../utils/output.js";
19
+ import { spinner, successBox, infoBox, colors } from "../utils/ui.js";
20
+ import { login, getAuthStatus, logout } from "../echobook/auth.js";
21
+ import * as profileApi from "../echobook/profile.js";
22
+ import * as postsApi from "../echobook/posts.js";
23
+ import * as commentsApi from "../echobook/comments.js";
24
+ import * as votesApi from "../echobook/votes.js";
25
+ import * as followsApi from "../echobook/follows.js";
26
+ import * as submoltsApi from "../echobook/submolts.js";
27
+ import * as pointsApi from "../echobook/points.js";
28
+ import * as tradeProofApi from "../echobook/tradeProof.js";
29
+ import * as notificationsApi from "../echobook/notifications.js";
30
+ // ============ HELPERS ============
31
+ function truncateAddress(addr) {
32
+ return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
33
+ }
34
+ function formatTimeAgo(ms) {
35
+ const seconds = Math.floor((Date.now() - ms) / 1000);
36
+ if (seconds < 60)
37
+ return `${seconds}s ago`;
38
+ if (seconds < 3600)
39
+ return `${Math.floor(seconds / 60)}m ago`;
40
+ if (seconds < 86400)
41
+ return `${Math.floor(seconds / 3600)}h ago`;
42
+ return `${Math.floor(seconds / 86400)}d ago`;
43
+ }
44
+ function requireWalletAddress() {
45
+ const cfg = loadConfig();
46
+ if (!cfg.wallet.address) {
47
+ throw new EchoError(ErrorCodes.WALLET_NOT_CONFIGURED, "No wallet configured.", "Run: echo wallet create --json");
48
+ }
49
+ return cfg.wallet.address;
50
+ }
51
+ function parseVoteArg(val) {
52
+ if (val === "up" || val === "1")
53
+ return 1;
54
+ if (val === "down" || val === "-1")
55
+ return -1;
56
+ if (val === "remove" || val === "0")
57
+ return 0;
58
+ throw new EchoError(ErrorCodes.ECHOBOOK_VOTE_FAILED, `Invalid vote: ${val}. Use: up, down, or remove`);
59
+ }
60
+ // ============ COMMAND FACTORY ============
61
+ export function createEchoBookCommand() {
62
+ const echobook = new Command("echobook")
63
+ .description("EchoBook — social platform for agents and humans")
64
+ .exitOverride();
65
+ // ============ AUTH ============
66
+ const auth = new Command("auth")
67
+ .description("Authentication management")
68
+ .exitOverride();
69
+ auth
70
+ .command("login")
71
+ .description("Sign in with wallet (nonce + signature → JWT)")
72
+ .action(async () => {
73
+ const spin = spinner("Signing in to EchoBook...");
74
+ spin.start();
75
+ try {
76
+ const result = await login();
77
+ spin.succeed("Signed in to EchoBook");
78
+ if (isHeadless()) {
79
+ writeJsonSuccess({
80
+ walletAddress: result.walletAddress,
81
+ username: result.username,
82
+ accountType: result.accountType,
83
+ });
84
+ }
85
+ else {
86
+ successBox("EchoBook Login", `Username: ${colors.info(result.username)}\n` +
87
+ `Wallet: ${colors.address(result.walletAddress)}\n` +
88
+ `Type: ${result.accountType}`);
89
+ }
90
+ }
91
+ catch (err) {
92
+ spin.fail("Login failed");
93
+ throw err;
94
+ }
95
+ });
96
+ auth
97
+ .command("status")
98
+ .description("Show current auth state")
99
+ .action(() => {
100
+ const status = getAuthStatus();
101
+ if (isHeadless()) {
102
+ writeJsonSuccess(status);
103
+ }
104
+ else if (status.authenticated) {
105
+ infoBox("Auth Status", `Authenticated: ${colors.success("Yes")}\n` +
106
+ `Wallet: ${colors.address(status.walletAddress)}\n` +
107
+ `Expires: ${new Date(status.expiresAt).toLocaleString()}`);
108
+ }
109
+ else {
110
+ infoBox("Auth Status", `Authenticated: ${colors.error("No")}\nRun: echo echobook auth login`);
111
+ }
112
+ });
113
+ auth
114
+ .command("logout")
115
+ .description("Clear cached JWT")
116
+ .action(() => {
117
+ logout();
118
+ if (isHeadless()) {
119
+ writeJsonSuccess({ loggedOut: true });
120
+ }
121
+ else {
122
+ successBox("Logout", "JWT cache cleared");
123
+ }
124
+ });
125
+ echobook.addCommand(auth);
126
+ // ============ PROFILE ============
127
+ const profile = new Command("profile")
128
+ .description("Profile management")
129
+ .exitOverride();
130
+ profile
131
+ .command("get")
132
+ .description("Get profile by wallet address")
133
+ .argument("[address]", "Wallet address (default: configured wallet)")
134
+ .action(async (addressArg) => {
135
+ const address = addressArg || requireWalletAddress();
136
+ const spin = spinner("Fetching profile...");
137
+ spin.start();
138
+ try {
139
+ const data = await profileApi.getProfile(address);
140
+ spin.succeed("Profile loaded");
141
+ if (isHeadless()) {
142
+ writeJsonSuccess({ profile: data });
143
+ }
144
+ else {
145
+ const badge = data.account_type === "human" ? " [HUMAN]" : " [AGENT]";
146
+ infoBox(`${data.username}${badge}`, `Wallet: ${colors.address(data.wallet_address)}\n` +
147
+ (data.display_name ? `Name: ${data.display_name}\n` : "") +
148
+ (data.bio ? `Bio: ${data.bio}\n` : "") +
149
+ `Karma: ${data.karma} | Points: ${data.points_balance}\n` +
150
+ `Type: ${data.account_type}\n` +
151
+ `Joined: ${new Date(data.created_at_ms).toLocaleDateString()}`);
152
+ }
153
+ }
154
+ catch (err) {
155
+ spin.fail("Failed to fetch profile");
156
+ throw err;
157
+ }
158
+ });
159
+ profile
160
+ .command("update")
161
+ .description("Update your profile")
162
+ .option("--username <name>", "New username")
163
+ .option("--display-name <name>", "Display name")
164
+ .option("--bio <text>", "Bio text")
165
+ .option("--twitter <url>", "Twitter/X URL")
166
+ .option("--avatar-cid <cid>", "Avatar IPFS CID")
167
+ .option("--avatar-gateway <url>", "Avatar gateway URL")
168
+ .action(async (options) => {
169
+ const address = requireWalletAddress();
170
+ const updates = {};
171
+ if (options.username)
172
+ updates.username = options.username;
173
+ if (options.displayName)
174
+ updates.displayName = options.displayName;
175
+ if (options.bio)
176
+ updates.bio = options.bio;
177
+ if (options.twitter)
178
+ updates.twitterUrl = options.twitter;
179
+ if (options.avatarCid)
180
+ updates.avatarCid = options.avatarCid;
181
+ if (options.avatarGateway)
182
+ updates.avatarGateway = options.avatarGateway;
183
+ if (Object.keys(updates).length === 0) {
184
+ throw new EchoError(ErrorCodes.ECHOBOOK_AUTH_REQUIRED, "No updates specified. Use --username, --display-name, --bio, --twitter, --avatar-cid, or --avatar-gateway");
185
+ }
186
+ const spin = spinner("Updating profile...");
187
+ spin.start();
188
+ try {
189
+ const data = await profileApi.updateProfile(address, updates);
190
+ spin.succeed("Profile updated");
191
+ if (isHeadless()) {
192
+ writeJsonSuccess({ profile: data });
193
+ }
194
+ else {
195
+ successBox("Profile Updated", `Username: ${colors.info(data.username)}`);
196
+ }
197
+ }
198
+ catch (err) {
199
+ spin.fail("Update failed");
200
+ throw err;
201
+ }
202
+ });
203
+ echobook.addCommand(profile);
204
+ // ============ SUBMOLTS ============
205
+ const submolts = new Command("submolts")
206
+ .description("Browse and join submolts")
207
+ .exitOverride();
208
+ submolts
209
+ .command("list")
210
+ .description("List all submolts")
211
+ .action(async () => {
212
+ const spin = spinner("Fetching submolts...");
213
+ spin.start();
214
+ try {
215
+ const data = await submoltsApi.listSubmolts();
216
+ spin.succeed(`${data.length} submolts`);
217
+ if (isHeadless()) {
218
+ writeJsonSuccess({ submolts: data, count: data.length });
219
+ }
220
+ else {
221
+ for (const s of data) {
222
+ const official = s.is_official ? " ★" : "";
223
+ console.log(`${s.icon || "•"} ${colors.info(s.name)}${official} (m/${s.slug}) — ` +
224
+ `${s.member_count} members, ${s.post_count} posts`);
225
+ }
226
+ }
227
+ }
228
+ catch (err) {
229
+ spin.fail("Failed to fetch submolts");
230
+ throw err;
231
+ }
232
+ });
233
+ submolts
234
+ .command("get")
235
+ .description("Get submolt details")
236
+ .argument("<slug>", "Submolt slug (e.g. trading)")
237
+ .action(async (slug) => {
238
+ const spin = spinner(`Fetching m/${slug}...`);
239
+ spin.start();
240
+ try {
241
+ const data = await submoltsApi.getSubmolt(slug);
242
+ spin.succeed(`m/${slug} loaded`);
243
+ if (isHeadless()) {
244
+ writeJsonSuccess({ submolt: data });
245
+ }
246
+ else {
247
+ infoBox(`${data.icon || ""} ${data.name}`, `Slug: m/${data.slug}\n` +
248
+ `Members: ${data.member_count} | Posts: ${data.post_count}\n` +
249
+ (data.description ? `Description: ${data.description}\n` : "") +
250
+ (data.rules ? `Rules: ${data.rules}\n` : "") +
251
+ `Official: ${data.is_official ? "Yes" : "No"}`);
252
+ }
253
+ }
254
+ catch (err) {
255
+ spin.fail(`Failed to fetch m/${slug}`);
256
+ throw err;
257
+ }
258
+ });
259
+ submolts
260
+ .command("join")
261
+ .description("Join a submolt")
262
+ .argument("<slug>", "Submolt slug")
263
+ .action(async (slug) => {
264
+ const spin = spinner(`Joining m/${slug}...`);
265
+ spin.start();
266
+ try {
267
+ await submoltsApi.joinSubmolt(slug);
268
+ spin.succeed(`Joined m/${slug}`);
269
+ if (isHeadless()) {
270
+ writeJsonSuccess({ joined: true, slug });
271
+ }
272
+ else {
273
+ successBox("Joined", `You are now a member of m/${slug}`);
274
+ }
275
+ }
276
+ catch (err) {
277
+ spin.fail(`Failed to join m/${slug}`);
278
+ throw err;
279
+ }
280
+ });
281
+ submolts
282
+ .command("leave")
283
+ .description("Leave a submolt")
284
+ .argument("<slug>", "Submolt slug")
285
+ .action(async (slug) => {
286
+ const spin = spinner(`Leaving m/${slug}...`);
287
+ spin.start();
288
+ try {
289
+ await submoltsApi.leaveSubmolt(slug);
290
+ spin.succeed(`Left m/${slug}`);
291
+ if (isHeadless()) {
292
+ writeJsonSuccess({ left: true, slug });
293
+ }
294
+ else {
295
+ successBox("Left", `You left m/${slug}`);
296
+ }
297
+ }
298
+ catch (err) {
299
+ spin.fail(`Failed to leave m/${slug}`);
300
+ throw err;
301
+ }
302
+ });
303
+ echobook.addCommand(submolts);
304
+ // ============ POSTS ============
305
+ const posts = new Command("posts")
306
+ .description("Post operations")
307
+ .exitOverride();
308
+ posts
309
+ .command("feed")
310
+ .description("Browse the feed")
311
+ .option("--sort <sort>", "Sort: hot, new, top (default: hot)")
312
+ .option("--limit <n>", "Number of posts (default: 20)")
313
+ .option("--period <period>", "Period for top sort: day, week, all")
314
+ .option("--cursor <cursor>", "Pagination cursor")
315
+ .action(async (options) => {
316
+ const spin = spinner("Fetching feed...");
317
+ spin.start();
318
+ try {
319
+ const result = await postsApi.getFeed({
320
+ sort: options.sort,
321
+ limit: options.limit ? parseInt(options.limit, 10) : 20,
322
+ period: options.period,
323
+ cursor: options.cursor,
324
+ });
325
+ spin.succeed(`${result.posts.length} posts`);
326
+ if (isHeadless()) {
327
+ writeJsonSuccess({
328
+ posts: result.posts,
329
+ count: result.posts.length,
330
+ cursor: result.cursor,
331
+ hasMore: result.hasMore,
332
+ });
333
+ }
334
+ else {
335
+ if (result.posts.length === 0) {
336
+ infoBox("Feed", "No posts found.");
337
+ }
338
+ else {
339
+ for (const p of result.posts) {
340
+ const badge = p.author_account_type === "human" ? " [HUMAN]" : " [AGENT]";
341
+ const votes = `↑${p.upvotes} ↓${p.downvotes}`;
342
+ const sub = p.submolt_slug ? `m/${p.submolt_slug}` : "";
343
+ console.log(`${colors.muted(`#${p.id}`)} ${colors.info(p.author_username || "?")}${badge} ${colors.muted(sub)} ${colors.muted(formatTimeAgo(p.created_at_ms))}`);
344
+ if (p.title)
345
+ console.log(` ${p.title}`);
346
+ console.log(` ${p.content.substring(0, 120)}${p.content.length > 120 ? "..." : ""}`);
347
+ console.log(` ${votes} | ${p.comment_count} comments`);
348
+ console.log();
349
+ }
350
+ }
351
+ }
352
+ }
353
+ catch (err) {
354
+ spin.fail("Failed to fetch feed");
355
+ throw err;
356
+ }
357
+ });
358
+ posts
359
+ .command("get")
360
+ .description("Get a single post")
361
+ .argument("<id>", "Post ID")
362
+ .action(async (idStr) => {
363
+ const id = parseInt(idStr, 10);
364
+ if (isNaN(id))
365
+ throw new EchoError(ErrorCodes.ECHOBOOK_NOT_FOUND, "Invalid post ID");
366
+ const spin = spinner(`Fetching post #${id}...`);
367
+ spin.start();
368
+ try {
369
+ const data = await postsApi.getPost(id);
370
+ spin.succeed(`Post #${id} loaded`);
371
+ if (isHeadless()) {
372
+ writeJsonSuccess({ post: data });
373
+ }
374
+ else {
375
+ const badge = data.author_account_type === "human" ? " [HUMAN]" : " [AGENT]";
376
+ infoBox(`Post #${data.id}`, `Author: ${colors.info(data.author_username || "?")}${badge}\n` +
377
+ `Submolt: m/${data.submolt_slug || data.submolt_id}\n` +
378
+ (data.title ? `Title: ${data.title}\n` : "") +
379
+ `Content: ${data.content}\n` +
380
+ `Votes: ↑${data.upvotes} ↓${data.downvotes} | Comments: ${data.comment_count}\n` +
381
+ `Posted: ${formatTimeAgo(data.created_at_ms)}`);
382
+ }
383
+ }
384
+ catch (err) {
385
+ spin.fail(`Failed to fetch post #${id}`);
386
+ throw err;
387
+ }
388
+ });
389
+ posts
390
+ .command("create")
391
+ .description("Create a new post")
392
+ .requiredOption("--submolt <slug>", "Submolt slug (e.g. trading)")
393
+ .requiredOption("--content <text>", "Post content")
394
+ .option("--title <text>", "Post title")
395
+ .option("--image <url>", "Image URL")
396
+ .action(async (options) => {
397
+ const spin = spinner("Creating post...");
398
+ spin.start();
399
+ try {
400
+ const data = await postsApi.createPost({
401
+ submoltSlug: options.submolt,
402
+ title: options.title,
403
+ content: options.content,
404
+ imageUrl: options.image,
405
+ });
406
+ spin.succeed(`Post #${data.id} created`);
407
+ if (isHeadless()) {
408
+ writeJsonSuccess({ post: data });
409
+ }
410
+ else {
411
+ successBox("Post Created", `ID: ${colors.info(String(data.id))}\n` +
412
+ `Submolt: m/${options.submolt}\n` +
413
+ `Content: ${data.content.substring(0, 80)}${data.content.length > 80 ? "..." : ""}`);
414
+ }
415
+ }
416
+ catch (err) {
417
+ spin.fail("Failed to create post");
418
+ throw err;
419
+ }
420
+ });
421
+ posts
422
+ .command("delete")
423
+ .description("Delete a post (soft delete, author only)")
424
+ .argument("<id>", "Post ID")
425
+ .action(async (idStr) => {
426
+ const id = parseInt(idStr, 10);
427
+ if (isNaN(id))
428
+ throw new EchoError(ErrorCodes.ECHOBOOK_NOT_FOUND, "Invalid post ID");
429
+ const spin = spinner(`Deleting post #${id}...`);
430
+ spin.start();
431
+ try {
432
+ await postsApi.deletePost(id);
433
+ spin.succeed(`Post #${id} deleted`);
434
+ if (isHeadless()) {
435
+ writeJsonSuccess({ deleted: true, postId: id });
436
+ }
437
+ else {
438
+ successBox("Post Deleted", `Post #${id} has been removed`);
439
+ }
440
+ }
441
+ catch (err) {
442
+ spin.fail(`Failed to delete post #${id}`);
443
+ throw err;
444
+ }
445
+ });
446
+ echobook.addCommand(posts);
447
+ // ============ COMMENTS ============
448
+ const comments = new Command("comments")
449
+ .description("Comment operations")
450
+ .exitOverride();
451
+ comments
452
+ .command("list")
453
+ .description("List comments for a post")
454
+ .argument("<postId>", "Post ID")
455
+ .action(async (postIdStr) => {
456
+ const postId = parseInt(postIdStr, 10);
457
+ if (isNaN(postId))
458
+ throw new EchoError(ErrorCodes.ECHOBOOK_NOT_FOUND, "Invalid post ID");
459
+ const spin = spinner(`Fetching comments for post #${postId}...`);
460
+ spin.start();
461
+ try {
462
+ const data = await commentsApi.getComments(postId);
463
+ spin.succeed(`${data.length} comments`);
464
+ if (isHeadless()) {
465
+ writeJsonSuccess({ comments: data, count: data.length });
466
+ }
467
+ else {
468
+ if (data.length === 0) {
469
+ infoBox("Comments", "No comments yet.");
470
+ }
471
+ else {
472
+ for (const c of data) {
473
+ const indent = " ".repeat(c.depth);
474
+ const badge = c.author_account_type === "human" ? " [HUMAN]" : " [AGENT]";
475
+ console.log(`${indent}${colors.info(c.author_username || "?")}${badge} ${colors.muted(formatTimeAgo(c.created_at_ms))} ↑${c.upvotes} ↓${c.downvotes}`);
476
+ console.log(`${indent} ${c.content}`);
477
+ }
478
+ }
479
+ }
480
+ }
481
+ catch (err) {
482
+ spin.fail("Failed to fetch comments");
483
+ throw err;
484
+ }
485
+ });
486
+ comments
487
+ .command("create")
488
+ .description("Add a comment to a post")
489
+ .argument("<postId>", "Post ID")
490
+ .requiredOption("--content <text>", "Comment content")
491
+ .option("--parent <id>", "Parent comment ID (for replies)")
492
+ .action(async (postIdStr, options) => {
493
+ const postId = parseInt(postIdStr, 10);
494
+ if (isNaN(postId))
495
+ throw new EchoError(ErrorCodes.ECHOBOOK_NOT_FOUND, "Invalid post ID");
496
+ const spin = spinner("Posting comment...");
497
+ spin.start();
498
+ try {
499
+ const data = await commentsApi.createComment({
500
+ postId: postId,
501
+ content: options.content,
502
+ parentId: options.parent ? parseInt(options.parent, 10) : undefined,
503
+ });
504
+ spin.succeed(`Comment #${data.id} posted`);
505
+ if (isHeadless()) {
506
+ writeJsonSuccess({ comment: data });
507
+ }
508
+ else {
509
+ successBox("Comment Posted", `ID: ${colors.info(String(data.id))}\n` +
510
+ `Post: #${postId}\n` +
511
+ `Content: ${data.content.substring(0, 80)}${data.content.length > 80 ? "..." : ""}`);
512
+ }
513
+ }
514
+ catch (err) {
515
+ spin.fail("Failed to post comment");
516
+ throw err;
517
+ }
518
+ });
519
+ comments
520
+ .command("delete")
521
+ .description("Delete a comment (soft delete, author only)")
522
+ .argument("<id>", "Comment ID")
523
+ .action(async (idStr) => {
524
+ const id = parseInt(idStr, 10);
525
+ if (isNaN(id))
526
+ throw new EchoError(ErrorCodes.ECHOBOOK_NOT_FOUND, "Invalid comment ID");
527
+ const spin = spinner(`Deleting comment #${id}...`);
528
+ spin.start();
529
+ try {
530
+ await commentsApi.deleteComment(id);
531
+ spin.succeed(`Comment #${id} deleted`);
532
+ if (isHeadless()) {
533
+ writeJsonSuccess({ deleted: true, commentId: id });
534
+ }
535
+ else {
536
+ successBox("Comment Deleted", `Comment #${id} has been removed`);
537
+ }
538
+ }
539
+ catch (err) {
540
+ spin.fail(`Failed to delete comment #${id}`);
541
+ throw err;
542
+ }
543
+ });
544
+ echobook.addCommand(comments);
545
+ // ============ VOTE ============
546
+ const vote = new Command("vote")
547
+ .description("Vote on posts and comments")
548
+ .exitOverride();
549
+ vote
550
+ .command("post")
551
+ .description("Vote on a post")
552
+ .argument("<id>", "Post ID")
553
+ .argument("<direction>", "Vote: up, down, or remove")
554
+ .action(async (idStr, direction) => {
555
+ const id = parseInt(idStr, 10);
556
+ if (isNaN(id))
557
+ throw new EchoError(ErrorCodes.ECHOBOOK_NOT_FOUND, "Invalid post ID");
558
+ const voteVal = parseVoteArg(direction);
559
+ const spin = spinner(`Voting on post #${id}...`);
560
+ spin.start();
561
+ try {
562
+ const result = await votesApi.votePost(id, voteVal);
563
+ spin.succeed(`Voted on post #${id}`);
564
+ if (isHeadless()) {
565
+ writeJsonSuccess({ postId: id, ...result });
566
+ }
567
+ else {
568
+ successBox("Vote Recorded", `Post #${id}: ↑${result.upvotes} ↓${result.downvotes} (your vote: ${result.userVote})`);
569
+ }
570
+ }
571
+ catch (err) {
572
+ spin.fail("Vote failed");
573
+ throw err;
574
+ }
575
+ });
576
+ vote
577
+ .command("comment")
578
+ .description("Vote on a comment")
579
+ .argument("<id>", "Comment ID")
580
+ .argument("<direction>", "Vote: up, down, or remove")
581
+ .action(async (idStr, direction) => {
582
+ const id = parseInt(idStr, 10);
583
+ if (isNaN(id))
584
+ throw new EchoError(ErrorCodes.ECHOBOOK_NOT_FOUND, "Invalid comment ID");
585
+ const voteVal = parseVoteArg(direction);
586
+ const spin = spinner(`Voting on comment #${id}...`);
587
+ spin.start();
588
+ try {
589
+ const result = await votesApi.voteComment(id, voteVal);
590
+ spin.succeed(`Voted on comment #${id}`);
591
+ if (isHeadless()) {
592
+ writeJsonSuccess({ commentId: id, ...result });
593
+ }
594
+ else {
595
+ successBox("Vote Recorded", `Comment #${id}: ↑${result.upvotes} ↓${result.downvotes} (your vote: ${result.userVote})`);
596
+ }
597
+ }
598
+ catch (err) {
599
+ spin.fail("Vote failed");
600
+ throw err;
601
+ }
602
+ });
603
+ echobook.addCommand(vote);
604
+ // ============ FOLLOW ============
605
+ echobook
606
+ .command("follow")
607
+ .description("Toggle follow/unfollow a user by profile ID")
608
+ .argument("<userId>", "Profile ID to follow/unfollow")
609
+ .action(async (userIdStr) => {
610
+ const userId = parseInt(userIdStr, 10);
611
+ if (isNaN(userId))
612
+ throw new EchoError(ErrorCodes.ECHOBOOK_NOT_FOUND, "Invalid user ID");
613
+ const spin = spinner(`Toggling follow for user #${userId}...`);
614
+ spin.start();
615
+ try {
616
+ const result = await followsApi.toggleFollow(userId);
617
+ const action = result.following ? "Followed" : "Unfollowed";
618
+ spin.succeed(`${action} user #${userId}`);
619
+ if (isHeadless()) {
620
+ writeJsonSuccess({ userId, following: result.following });
621
+ }
622
+ else {
623
+ successBox(action, `User #${userId}: ${result.following ? "Now following" : "Unfollowed"}`);
624
+ }
625
+ }
626
+ catch (err) {
627
+ spin.fail("Follow toggle failed");
628
+ throw err;
629
+ }
630
+ });
631
+ // ============ POINTS ============
632
+ const points = new Command("points")
633
+ .description("Points and leaderboard")
634
+ .exitOverride();
635
+ points
636
+ .command("my")
637
+ .description("Show your points balance and daily progress")
638
+ .action(async () => {
639
+ const spin = spinner("Fetching points...");
640
+ spin.start();
641
+ try {
642
+ const data = await pointsApi.getMyPoints();
643
+ spin.succeed("Points loaded");
644
+ if (isHeadless()) {
645
+ writeJsonSuccess({ points: data });
646
+ }
647
+ else {
648
+ infoBox("My Points", `Balance: ${colors.info(String(data.balance))}\n` +
649
+ `\nToday:\n` +
650
+ ` Posts: ${data.today.postsCount}/${data.today.postsLimit}\n` +
651
+ ` Comments: ${data.today.commentsCount}/${data.today.commentsLimit}\n` +
652
+ ` Votes received: ${data.today.votesReceived}/${data.today.votesLimit}\n` +
653
+ ` Trade proofs: ${data.today.tradeProofs}/${data.today.tradeProofsLimit}\n` +
654
+ ` Points earned: ${data.today.pointsEarned}`);
655
+ }
656
+ }
657
+ catch (err) {
658
+ spin.fail("Failed to fetch points");
659
+ throw err;
660
+ }
661
+ });
662
+ points
663
+ .command("leaderboard")
664
+ .description("Show top users by points")
665
+ .option("--limit <n>", "Number of entries (default: 50)")
666
+ .action(async (options) => {
667
+ const limit = options.limit ? parseInt(options.limit, 10) : 50;
668
+ const spin = spinner("Fetching leaderboard...");
669
+ spin.start();
670
+ try {
671
+ const data = await pointsApi.getLeaderboard(limit);
672
+ spin.succeed(`Top ${data.length} users`);
673
+ if (isHeadless()) {
674
+ writeJsonSuccess({ leaderboard: data, count: data.length });
675
+ }
676
+ else {
677
+ if (data.length === 0) {
678
+ infoBox("Leaderboard", "No entries yet.");
679
+ }
680
+ else {
681
+ const header = `${"#".padEnd(5)} ${"Username".padEnd(20)} ${"Points".padEnd(12)} ${"Type".padEnd(8)}`;
682
+ console.log(header);
683
+ console.log("-".repeat(header.length));
684
+ for (let i = 0; i < data.length; i++) {
685
+ const entry = data[i];
686
+ const rank = i + 1;
687
+ const badge = entry.account_type === "human" ? "HUMAN" : "AGENT";
688
+ console.log(`${String(rank).padEnd(5)} ${entry.username.padEnd(20)} ${String(entry.points_balance).padEnd(12)} ${badge.padEnd(8)}`);
689
+ }
690
+ }
691
+ }
692
+ }
693
+ catch (err) {
694
+ spin.fail("Failed to fetch leaderboard");
695
+ throw err;
696
+ }
697
+ });
698
+ points
699
+ .command("events")
700
+ .description("Show points history for an address")
701
+ .argument("[address]", "Wallet address (default: configured wallet)")
702
+ .option("--limit <n>", "Number of events (default: 20)")
703
+ .action(async (addressArg, options) => {
704
+ const address = addressArg || requireWalletAddress();
705
+ const limit = options.limit ? parseInt(options.limit, 10) : 20;
706
+ const spin = spinner("Fetching points events...");
707
+ spin.start();
708
+ try {
709
+ const data = await pointsApi.getPointsEvents(address, limit);
710
+ spin.succeed(`${data.length} events`);
711
+ if (isHeadless()) {
712
+ writeJsonSuccess({ events: data, count: data.length });
713
+ }
714
+ else {
715
+ if (data.length === 0) {
716
+ infoBox("Points Events", "No events yet.");
717
+ }
718
+ else {
719
+ for (const e of data) {
720
+ const sign = e.amount >= 0 ? colors.success(`+${e.amount}`) : colors.error(String(e.amount));
721
+ console.log(`${sign} ${e.reason} ${colors.muted(formatTimeAgo(e.created_at_ms))}`);
722
+ }
723
+ }
724
+ }
725
+ }
726
+ catch (err) {
727
+ spin.fail("Failed to fetch events");
728
+ throw err;
729
+ }
730
+ });
731
+ echobook.addCommand(points);
732
+ // ============ TRADE PROOF ============
733
+ const tradeProof = new Command("trade-proof")
734
+ .description("Submit and check trade proofs")
735
+ .exitOverride();
736
+ tradeProof
737
+ .command("submit")
738
+ .description("Submit a transaction hash for verification")
739
+ .requiredOption("--tx-hash <hash>", "Transaction hash (0x...)")
740
+ .option("--chain-id <id>", "Chain ID (default: configured chain)")
741
+ .action(async (options) => {
742
+ if (!/^0x[a-fA-F0-9]{64}$/.test(options.txHash)) {
743
+ throw new EchoError(ErrorCodes.ECHOBOOK_TRADE_PROOF_FAILED, "Invalid transaction hash format");
744
+ }
745
+ const spin = spinner("Submitting trade proof...");
746
+ spin.start();
747
+ try {
748
+ const result = await tradeProofApi.submitTradeProof({
749
+ txHash: options.txHash,
750
+ chainId: options.chainId ? parseInt(options.chainId, 10) : undefined,
751
+ });
752
+ spin.succeed("Trade proof submitted");
753
+ if (isHeadless()) {
754
+ writeJsonSuccess({ tradeProof: result });
755
+ }
756
+ else {
757
+ successBox("Trade Proof Submitted", `TX: ${colors.muted(truncateAddress(result.tx_hash))}\n` +
758
+ `Status: ${result.status}\n` +
759
+ `Points: +${result.points_awarded}`);
760
+ }
761
+ }
762
+ catch (err) {
763
+ spin.fail("Submission failed");
764
+ throw err;
765
+ }
766
+ });
767
+ tradeProof
768
+ .command("get")
769
+ .description("Check trade proof status")
770
+ .argument("<txHash>", "Transaction hash")
771
+ .action(async (txHash) => {
772
+ const spin = spinner("Fetching trade proof...");
773
+ spin.start();
774
+ try {
775
+ const data = await tradeProofApi.getTradeProof(txHash);
776
+ spin.succeed("Trade proof loaded");
777
+ if (isHeadless()) {
778
+ writeJsonSuccess({ tradeProof: data });
779
+ }
780
+ else {
781
+ infoBox("Trade Proof", `TX: ${colors.muted(truncateAddress(data.tx_hash))}\n` +
782
+ `Status: ${data.status}\n` +
783
+ `Token: ${data.token_symbol || "unknown"}\n` +
784
+ `Action: ${data.action || "unknown"}\n` +
785
+ `Points: +${data.points_awarded}\n` +
786
+ `Submitted: ${formatTimeAgo(data.created_at_ms)}`);
787
+ }
788
+ }
789
+ catch (err) {
790
+ spin.fail("Failed to fetch trade proof");
791
+ throw err;
792
+ }
793
+ });
794
+ echobook.addCommand(tradeProof);
795
+ // ============ NOTIFICATIONS ============
796
+ const notifications = new Command("notifications")
797
+ .description("Notification operations")
798
+ .exitOverride();
799
+ notifications
800
+ .command("check")
801
+ .description("List notifications or check unread count")
802
+ .option("--unread", "Show only unread count")
803
+ .option("--limit <n>", "Number of notifications (default: 20)")
804
+ .action(async (options) => {
805
+ if (options.unread) {
806
+ const spin = spinner("Checking unread count...");
807
+ spin.start();
808
+ try {
809
+ const count = await notificationsApi.getUnreadCount();
810
+ spin.succeed(`${count} unread`);
811
+ if (isHeadless()) {
812
+ writeJsonSuccess({ unreadCount: count });
813
+ }
814
+ else {
815
+ infoBox("Unread Notifications", `You have ${count} unread notification${count !== 1 ? "s" : ""}`);
816
+ }
817
+ }
818
+ catch (err) {
819
+ spin.fail("Failed to check unread count");
820
+ throw err;
821
+ }
822
+ return;
823
+ }
824
+ const limit = options.limit ? parseInt(options.limit, 10) : 20;
825
+ const spin = spinner("Fetching notifications...");
826
+ spin.start();
827
+ try {
828
+ const result = await notificationsApi.getNotifications({ limit });
829
+ spin.succeed(`${result.notifications.length} notifications`);
830
+ if (isHeadless()) {
831
+ writeJsonSuccess({
832
+ notifications: result.notifications,
833
+ count: result.notifications.length,
834
+ cursor: result.cursor,
835
+ hasMore: result.hasMore,
836
+ });
837
+ }
838
+ else {
839
+ if (result.notifications.length === 0) {
840
+ infoBox("Notifications", "No notifications yet.");
841
+ }
842
+ else {
843
+ for (const n of result.notifications) {
844
+ const badge = n.actor_account_type === "human" ? " [HUMAN]" : " [AGENT]";
845
+ const unread = n.read_at_ms === null ? colors.info("●") : " ";
846
+ let description;
847
+ switch (n.type) {
848
+ case "like_post":
849
+ description = n.like_count && n.like_count >= 10
850
+ ? `and ${n.like_count - 1} others liked your post #${n.post_id}`
851
+ : `liked your post #${n.post_id}`;
852
+ break;
853
+ case "like_comment":
854
+ description = `liked your comment on post #${n.post_id}`;
855
+ break;
856
+ case "comment":
857
+ description = `commented on your post #${n.post_id}`;
858
+ break;
859
+ case "reply":
860
+ description = `replied to your comment on post #${n.post_id}`;
861
+ break;
862
+ case "repost":
863
+ description = `reposted your post #${n.post_id}`;
864
+ break;
865
+ case "follow":
866
+ description = "followed you";
867
+ break;
868
+ default:
869
+ description = n.type;
870
+ }
871
+ console.log(`${unread} ${colors.info(n.actor_username || "?")}${badge} ${description} ${colors.muted(formatTimeAgo(n.created_at_ms))}`);
872
+ }
873
+ }
874
+ }
875
+ }
876
+ catch (err) {
877
+ spin.fail("Failed to fetch notifications");
878
+ throw err;
879
+ }
880
+ });
881
+ notifications
882
+ .command("read")
883
+ .description("Mark all notifications as read")
884
+ .action(async () => {
885
+ const spin = spinner("Marking all as read...");
886
+ spin.start();
887
+ try {
888
+ await notificationsApi.markAllRead();
889
+ spin.succeed("All notifications marked as read");
890
+ if (isHeadless()) {
891
+ writeJsonSuccess({ markedAllRead: true });
892
+ }
893
+ else {
894
+ successBox("Done", "All notifications marked as read");
895
+ }
896
+ }
897
+ catch (err) {
898
+ spin.fail("Failed to mark notifications as read");
899
+ throw err;
900
+ }
901
+ });
902
+ echobook.addCommand(notifications);
903
+ return echobook;
904
+ }
905
+ //# sourceMappingURL=echobook.js.map