agentix-cli 0.26.0 → 0.28.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 (300) hide show
  1. package/README.md +67 -118
  2. package/apps/mac-helper/Sources/AgentXHelper/AXTree.swift +159 -0
  3. package/apps/mac-helper/Sources/AgentXHelper/Brand.swift +63 -0
  4. package/apps/mac-helper/Sources/AgentXHelper/Focus.swift +97 -0
  5. package/apps/mac-helper/Sources/AgentXHelper/HUD.swift +179 -0
  6. package/apps/mac-helper/Sources/AgentXHelper/OCR.swift +117 -0
  7. package/apps/mac-helper/Sources/AgentXHelper/Pointer.swift +309 -0
  8. package/apps/mac-helper/Sources/AgentXHelper/Screens.swift +67 -0
  9. package/apps/mac-helper/Sources/AgentXHelper/Typer.swift +185 -0
  10. package/apps/mac-helper/Sources/AgentXHelper/Vision.swift +201 -0
  11. package/apps/mac-helper/Sources/AgentXHelper/main.swift +265 -0
  12. package/apps/mac-helper/build.sh +39 -0
  13. package/apps/mac-voice/Resources/Info.plist +18 -0
  14. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +108 -0
  15. package/apps/mac-voice/Sources/AgentXVoice/Brand.swift +123 -0
  16. package/apps/mac-voice/Sources/AgentXVoice/Config.swift +68 -0
  17. package/apps/mac-voice/Sources/AgentXVoice/Hold.swift +74 -0
  18. package/apps/mac-voice/Sources/AgentXVoice/Hotkey.swift +76 -0
  19. package/apps/mac-voice/Sources/AgentXVoice/Markdown.swift +140 -0
  20. package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +275 -0
  21. package/apps/mac-voice/Sources/AgentXVoice/Progress.swift +147 -0
  22. package/apps/mac-voice/Sources/AgentXVoice/Recorder.swift +139 -0
  23. package/apps/mac-voice/Sources/AgentXVoice/ResultCard.swift +162 -0
  24. package/apps/mac-voice/Sources/AgentXVoice/SmartPaste.swift +72 -0
  25. package/apps/mac-voice/Sources/AgentXVoice/Speech.swift +192 -0
  26. package/apps/mac-voice/Sources/AgentXVoice/StepLabel.swift +159 -0
  27. package/apps/mac-voice/Sources/AgentXVoice/main.swift +380 -0
  28. package/apps/mac-voice/build.sh +32 -0
  29. package/dist/{App-BPNHIGUX.js → App-CKPH3HHF.js} +2 -2
  30. package/dist/{ChatApp-6XSSZB4U.js → ChatApp-MX7IG4K2.js} +2 -2
  31. package/dist/agent-2U574KBZ.js +2 -0
  32. package/dist/article-fields-L4D2BOGY.js +2 -0
  33. package/dist/article-quality-44ONVGLN.js +3 -0
  34. package/dist/article-quality-44ONVGLN.js.map +1 -0
  35. package/dist/{audit-JU3HWISB.js → audit-4245CX6P.js} +2 -2
  36. package/dist/backfill-MTD2B62I.js +6 -0
  37. package/dist/backfill-MTD2B62I.js.map +1 -0
  38. package/dist/{board-dashboard-M3372FLO.js → board-dashboard-QR7CVTKA.js} +1411 -453
  39. package/dist/board-dashboard-QR7CVTKA.js.map +1 -0
  40. package/dist/{builtin-FRKAQMFS.js → builtin-B67DMJ3M.js} +2 -2
  41. package/dist/calibration-ILS5OI44.js +2 -0
  42. package/dist/{chunk-V3U4OF7L.js → chunk-3LYM22IV.js} +46 -48
  43. package/dist/chunk-3LYM22IV.js.map +1 -0
  44. package/dist/chunk-3SCPRYJB.js +5 -0
  45. package/dist/chunk-3SCPRYJB.js.map +1 -0
  46. package/dist/chunk-4BR7D2OU.js +36 -0
  47. package/dist/chunk-4BR7D2OU.js.map +1 -0
  48. package/dist/{chunk-P4VWIVKN.js → chunk-52ZPELD2.js} +403 -29
  49. package/dist/chunk-52ZPELD2.js.map +1 -0
  50. package/dist/chunk-5IA2CYHN.js +5 -0
  51. package/dist/{chunk-O6YPEOQF.js.map → chunk-5IA2CYHN.js.map} +1 -1
  52. package/dist/chunk-64CAIB3C.js +9 -0
  53. package/dist/chunk-64CAIB3C.js.map +1 -0
  54. package/dist/{chunk-5U35WAZF.js → chunk-77OV7346.js} +6 -6
  55. package/dist/chunk-77OV7346.js.map +1 -0
  56. package/dist/chunk-AJ5RMU7X.js +2 -0
  57. package/dist/chunk-AJ5RMU7X.js.map +1 -0
  58. package/dist/chunk-AJONZWLT.js +2 -0
  59. package/dist/chunk-AJONZWLT.js.map +1 -0
  60. package/dist/chunk-BH3GTCZ6.js +2 -0
  61. package/dist/chunk-BH3GTCZ6.js.map +1 -0
  62. package/dist/chunk-E3UJHQGN.js +1189 -0
  63. package/dist/chunk-E3UJHQGN.js.map +1 -0
  64. package/dist/chunk-EVWEG3VD.js +3 -0
  65. package/dist/chunk-EVWEG3VD.js.map +1 -0
  66. package/dist/chunk-F4HZTZHW.js +2 -0
  67. package/dist/chunk-F4HZTZHW.js.map +1 -0
  68. package/dist/chunk-F6CNF7V3.js +25 -0
  69. package/dist/chunk-F6CNF7V3.js.map +1 -0
  70. package/dist/chunk-FIOB3MEX.js +2 -0
  71. package/dist/{chunk-H5D7ATXS.js → chunk-FKBGJYWQ.js} +5 -5
  72. package/dist/chunk-FKBGJYWQ.js.map +1 -0
  73. package/dist/{chunk-BVGJSPW4.js → chunk-G2HMRDUQ.js} +7 -7
  74. package/dist/chunk-G2HMRDUQ.js.map +1 -0
  75. package/dist/chunk-H2QQU2XV.js +109 -0
  76. package/dist/chunk-H2QQU2XV.js.map +1 -0
  77. package/dist/{chunk-LDHPMJ7M.js → chunk-IWMW5432.js} +3 -3
  78. package/dist/chunk-IWMW5432.js.map +1 -0
  79. package/dist/chunk-K33GIBSE.js +2 -0
  80. package/dist/chunk-K33GIBSE.js.map +1 -0
  81. package/dist/chunk-L2WSPY6S.js +2 -0
  82. package/dist/chunk-L2WSPY6S.js.map +1 -0
  83. package/dist/chunk-LCIREYTD.js +54 -0
  84. package/dist/chunk-LCIREYTD.js.map +1 -0
  85. package/dist/chunk-MHIMGM4G.js +126 -0
  86. package/dist/chunk-MHIMGM4G.js.map +1 -0
  87. package/dist/chunk-P5ZBBEPL.js +13 -0
  88. package/dist/chunk-P5ZBBEPL.js.map +1 -0
  89. package/dist/{chunk-JTJIPNXY.js → chunk-PNWNVOEZ.js} +1 -1
  90. package/dist/chunk-PNWNVOEZ.js.map +1 -0
  91. package/dist/chunk-QHTQPBHU.js +45 -0
  92. package/dist/chunk-QHTQPBHU.js.map +1 -0
  93. package/dist/chunk-S2MNL5V6.js +2 -0
  94. package/dist/chunk-S2MNL5V6.js.map +1 -0
  95. package/dist/chunk-SCTIUFL4.js +5 -0
  96. package/dist/{chunk-K2CAPYES.js.map → chunk-SCTIUFL4.js.map} +1 -1
  97. package/dist/chunk-ULLAE6LE.js +157 -0
  98. package/dist/chunk-ULLAE6LE.js.map +1 -0
  99. package/dist/{chunk-DJEZ2HVU.js → chunk-VDMYBPYB.js} +1 -1
  100. package/dist/chunk-VDMYBPYB.js.map +1 -0
  101. package/dist/chunk-VIH7ARPF.js +547 -0
  102. package/dist/chunk-VIH7ARPF.js.map +1 -0
  103. package/dist/chunk-W5C2GDNC.js +32 -0
  104. package/dist/{chunk-JP5IEXR7.js.map → chunk-W5C2GDNC.js.map} +1 -1
  105. package/dist/chunk-WPMX3VGI.js +3 -0
  106. package/dist/chunk-WPMX3VGI.js.map +1 -0
  107. package/dist/chunk-YLBERT7N.js +24 -0
  108. package/dist/chunk-YLBERT7N.js.map +1 -0
  109. package/dist/chunk-YLGIZRHV.js +114 -0
  110. package/dist/chunk-YLGIZRHV.js.map +1 -0
  111. package/dist/chunk-ZMYGXHPT.js +121 -0
  112. package/dist/chunk-ZMYGXHPT.js.map +1 -0
  113. package/dist/chunk-ZTEU5JVQ.js +2 -0
  114. package/dist/chunk-ZTEU5JVQ.js.map +1 -0
  115. package/dist/{chunk-2WVX3LEC.js → chunk-ZVP6GB4G.js} +1 -1
  116. package/dist/chunk-ZVP6GB4G.js.map +1 -0
  117. package/dist/cli.js +1 -1
  118. package/dist/cli.js.map +1 -1
  119. package/dist/client-ZLSXGPTV.js +2 -0
  120. package/dist/{compaction-NWEMOSHA.js → compaction-FD4R23HZ.js} +4 -4
  121. package/dist/config-HMW3EBZL.js +2 -0
  122. package/dist/config-mutator-4SZ5YEVD.js +2 -0
  123. package/dist/{conflict-detector-D7VIYPDI.js → conflict-detector-ZPOT3M2C.js} +2 -2
  124. package/dist/conflict-detector-ZPOT3M2C.js.map +1 -0
  125. package/dist/context-planner-AQHJMVY2.js +17 -0
  126. package/dist/context-planner-AQHJMVY2.js.map +1 -0
  127. package/dist/debug-3MEUO2P2.js +2 -0
  128. package/dist/{discover-XJXQXXIO.js → discover-D4VVRNAI.js} +2 -2
  129. package/dist/discover-D4VVRNAI.js.map +1 -0
  130. package/dist/doctor-LRYV3QLK.js +2 -0
  131. package/dist/editor-chat-OB7IQOB5.js +137 -0
  132. package/dist/editor-chat-OB7IQOB5.js.map +1 -0
  133. package/dist/entry-triage-LK34TSQD.js +2 -0
  134. package/dist/entry-triage-LK34TSQD.js.map +1 -0
  135. package/dist/facts-YVEJOM7O.js +22 -0
  136. package/dist/facts-YVEJOM7O.js.map +1 -0
  137. package/dist/heal-FVLBHYP7.js +2 -0
  138. package/dist/index.d.ts +1040 -64
  139. package/dist/index.js +12 -19
  140. package/dist/index.js.map +1 -1
  141. package/dist/{ingest-whatsapp-LZE62INA.js → ingest-whatsapp-NS4OWDOU.js} +2 -2
  142. package/dist/loader-4ZGTK7LD.js +2 -0
  143. package/dist/loader-BOKE5M5S.js +2 -0
  144. package/dist/memory-extract-F5ZHWY5E.js +2 -0
  145. package/dist/{migrate-v2-QO7SLKIM.js → migrate-v2-V4WN2VVM.js} +2 -2
  146. package/dist/ntfy-X7EPB3TG.js +4 -0
  147. package/dist/ntfy-X7EPB3TG.js.map +1 -0
  148. package/dist/processes-WU23U2OJ.js +2 -0
  149. package/dist/program-GSZ5AYAM.js +2 -0
  150. package/dist/{projects-api-KRME2YKZ.js → projects-api-QE6L65SK.js} +2 -2
  151. package/dist/{projects-mutate-3NHVG437.js → projects-mutate-MY7JTLSZ.js} +2 -2
  152. package/dist/providers-KO2DODIO.js +2 -0
  153. package/dist/query-SQDHE4HV.js +2 -0
  154. package/dist/questions-4B4KSPXG.js +3 -0
  155. package/dist/questions-4B4KSPXG.js.map +1 -0
  156. package/dist/recipes-7S4MC5OJ.js +2 -0
  157. package/dist/registry-LE6FWYZC.js +2 -0
  158. package/dist/review-candidates-RQU5KNZM.js +4 -0
  159. package/dist/review-candidates-RQU5KNZM.js.map +1 -0
  160. package/dist/review-store-B4YE3EFK.js +4 -0
  161. package/dist/review-store-B4YE3EFK.js.map +1 -0
  162. package/dist/{rotation-memo-B3EXXSEH.js → rotation-memo-7T75BLEX.js} +2 -2
  163. package/dist/routing-ILERJXB6.js +2 -0
  164. package/dist/runner-YRBVNYTY.js +2 -0
  165. package/dist/scheduler-BFM3EINS.js +2 -0
  166. package/dist/seat-7WM7II2B.js +2 -0
  167. package/dist/select-X4KVMOP5.js +2 -0
  168. package/dist/select-X4KVMOP5.js.map +1 -0
  169. package/dist/store-MCOA6N4F.js +2 -0
  170. package/dist/{subagent-WV7QKQ3L.js → subagent-2SJHSSJS.js} +2 -2
  171. package/dist/{surface-usage-PRL6Z4U3.js → surface-usage-NS74AXSE.js} +2 -2
  172. package/dist/timers-PCB4ZIBD.js +2 -0
  173. package/dist/triage-backtest-JNO7RKX7.js +2 -0
  174. package/dist/triage-backtest-JNO7RKX7.js.map +1 -0
  175. package/dist/types-H5ENIUIT.js +2 -0
  176. package/dist/types-OIFU3KSQ.js +2 -0
  177. package/dist/types-OIFU3KSQ.js.map +1 -0
  178. package/dist/web/workflow-editor.global.js +1542 -0
  179. package/dist/web/workflow-editor.global.js.map +1 -0
  180. package/dist/{whatsapp-state-XMTCSRXU.js → whatsapp-state-6D6FHHD2.js} +2 -2
  181. package/dist/wiki-2AUFSIKO.js +2 -0
  182. package/dist/wiki-2AUFSIKO.js.map +1 -0
  183. package/dist/workflows/templates/branching.yaml +1 -1
  184. package/dist/{workspace-setup-TBQAHYHJ.js → workspace-setup-B3F3TUDE.js} +2 -2
  185. package/dist/workspace-setup-B3F3TUDE.js.map +1 -0
  186. package/package.json +14 -3
  187. package/scripts/postinstall.mjs +80 -0
  188. package/dist/agent-5QMITZT6.js +0 -2
  189. package/dist/architect-SEPWMWS4.js +0 -2
  190. package/dist/board-dashboard-M3372FLO.js.map +0 -1
  191. package/dist/chunk-2WVX3LEC.js.map +0 -1
  192. package/dist/chunk-3WQFCIB6.js +0 -24
  193. package/dist/chunk-3WQFCIB6.js.map +0 -1
  194. package/dist/chunk-4YCH6IZV.js +0 -2
  195. package/dist/chunk-53NRX2NA.js +0 -2
  196. package/dist/chunk-53NRX2NA.js.map +0 -1
  197. package/dist/chunk-5U35WAZF.js.map +0 -1
  198. package/dist/chunk-BFXONUUN.js +0 -108
  199. package/dist/chunk-BFXONUUN.js.map +0 -1
  200. package/dist/chunk-BFZUG66X.js +0 -45
  201. package/dist/chunk-BFZUG66X.js.map +0 -1
  202. package/dist/chunk-BTHHSY7D.js +0 -13
  203. package/dist/chunk-BTHHSY7D.js.map +0 -1
  204. package/dist/chunk-BVGJSPW4.js.map +0 -1
  205. package/dist/chunk-D46VZ7K7.js +0 -116
  206. package/dist/chunk-D46VZ7K7.js.map +0 -1
  207. package/dist/chunk-DJEZ2HVU.js.map +0 -1
  208. package/dist/chunk-E5CXCI33.js +0 -155
  209. package/dist/chunk-E5CXCI33.js.map +0 -1
  210. package/dist/chunk-FDWOECJY.js +0 -54
  211. package/dist/chunk-FDWOECJY.js.map +0 -1
  212. package/dist/chunk-H5D7ATXS.js.map +0 -1
  213. package/dist/chunk-HR4JSRZS.js +0 -3
  214. package/dist/chunk-HR4JSRZS.js.map +0 -1
  215. package/dist/chunk-ISEBLHWX.js +0 -118
  216. package/dist/chunk-ISEBLHWX.js.map +0 -1
  217. package/dist/chunk-JP5IEXR7.js +0 -32
  218. package/dist/chunk-JTJIPNXY.js.map +0 -1
  219. package/dist/chunk-K2CAPYES.js +0 -5
  220. package/dist/chunk-LDHPMJ7M.js.map +0 -1
  221. package/dist/chunk-METYYZKQ.js +0 -1115
  222. package/dist/chunk-METYYZKQ.js.map +0 -1
  223. package/dist/chunk-MTAPOCGA.js +0 -47
  224. package/dist/chunk-MTAPOCGA.js.map +0 -1
  225. package/dist/chunk-O6YPEOQF.js +0 -5
  226. package/dist/chunk-P4VWIVKN.js.map +0 -1
  227. package/dist/chunk-SR3X25TN.js +0 -3
  228. package/dist/chunk-SR3X25TN.js.map +0 -1
  229. package/dist/chunk-UHE5FCPB.js +0 -431
  230. package/dist/chunk-UHE5FCPB.js.map +0 -1
  231. package/dist/chunk-V3U4OF7L.js.map +0 -1
  232. package/dist/chunk-VJUTQAPJ.js +0 -2
  233. package/dist/chunk-VJUTQAPJ.js.map +0 -1
  234. package/dist/chunk-VT5XYFDD.js +0 -126
  235. package/dist/chunk-VT5XYFDD.js.map +0 -1
  236. package/dist/client-HGKAC6VP.js +0 -2
  237. package/dist/config-A4RWEJHV.js +0 -2
  238. package/dist/config-mutator-MNSFUN7D.js +0 -2
  239. package/dist/conflict-detector-D7VIYPDI.js.map +0 -1
  240. package/dist/context-planner-45GEWKGL.js +0 -17
  241. package/dist/context-planner-45GEWKGL.js.map +0 -1
  242. package/dist/debug-N3B5NVJU.js +0 -2
  243. package/dist/discover-XJXQXXIO.js.map +0 -1
  244. package/dist/doctor-3Q36ZFE6.js +0 -2
  245. package/dist/heal-HZ3EQD52.js +0 -2
  246. package/dist/loader-5CA3O5GM.js +0 -2
  247. package/dist/loader-6IHUCPVO.js +0 -2
  248. package/dist/memory-extract-AENFVVO5.js +0 -2
  249. package/dist/processes-UIC4XXA6.js +0 -2
  250. package/dist/program-SAUNRCCD.js +0 -2
  251. package/dist/providers-L27BJ5A6.js +0 -2
  252. package/dist/query-HZYYRCLU.js +0 -2
  253. package/dist/recipes-EPLE2SNT.js +0 -2
  254. package/dist/registry-HK3WQ7BS.js +0 -2
  255. package/dist/runner-BFYDWPUO.js +0 -2
  256. package/dist/scheduler-6T2SU5ST.js +0 -2
  257. package/dist/store-I3HEWVLQ.js +0 -2
  258. package/dist/timers-5LPZN55J.js +0 -2
  259. package/dist/types-VECTMYTO.js +0 -2
  260. package/dist/types-Y6ZCZ52Z.js +0 -2
  261. package/dist/wiki-M6YNRFC4.js +0 -2
  262. /package/dist/{App-BPNHIGUX.js.map → App-CKPH3HHF.js.map} +0 -0
  263. /package/dist/{ChatApp-6XSSZB4U.js.map → ChatApp-MX7IG4K2.js.map} +0 -0
  264. /package/dist/{agent-5QMITZT6.js.map → agent-2U574KBZ.js.map} +0 -0
  265. /package/dist/{architect-SEPWMWS4.js.map → article-fields-L4D2BOGY.js.map} +0 -0
  266. /package/dist/{audit-JU3HWISB.js.map → audit-4245CX6P.js.map} +0 -0
  267. /package/dist/{builtin-FRKAQMFS.js.map → builtin-B67DMJ3M.js.map} +0 -0
  268. /package/dist/{chunk-4YCH6IZV.js.map → calibration-ILS5OI44.js.map} +0 -0
  269. /package/dist/{client-HGKAC6VP.js.map → chunk-FIOB3MEX.js.map} +0 -0
  270. /package/dist/{config-A4RWEJHV.js.map → client-ZLSXGPTV.js.map} +0 -0
  271. /package/dist/{compaction-NWEMOSHA.js.map → compaction-FD4R23HZ.js.map} +0 -0
  272. /package/dist/{config-mutator-MNSFUN7D.js.map → config-HMW3EBZL.js.map} +0 -0
  273. /package/dist/{debug-N3B5NVJU.js.map → config-mutator-4SZ5YEVD.js.map} +0 -0
  274. /package/dist/{doctor-3Q36ZFE6.js.map → debug-3MEUO2P2.js.map} +0 -0
  275. /package/dist/{heal-HZ3EQD52.js.map → doctor-LRYV3QLK.js.map} +0 -0
  276. /package/dist/{loader-5CA3O5GM.js.map → heal-FVLBHYP7.js.map} +0 -0
  277. /package/dist/{ingest-whatsapp-LZE62INA.js.map → ingest-whatsapp-NS4OWDOU.js.map} +0 -0
  278. /package/dist/{loader-6IHUCPVO.js.map → loader-4ZGTK7LD.js.map} +0 -0
  279. /package/dist/{memory-extract-AENFVVO5.js.map → loader-BOKE5M5S.js.map} +0 -0
  280. /package/dist/{processes-UIC4XXA6.js.map → memory-extract-F5ZHWY5E.js.map} +0 -0
  281. /package/dist/{migrate-v2-QO7SLKIM.js.map → migrate-v2-V4WN2VVM.js.map} +0 -0
  282. /package/dist/{program-SAUNRCCD.js.map → processes-WU23U2OJ.js.map} +0 -0
  283. /package/dist/{providers-L27BJ5A6.js.map → program-GSZ5AYAM.js.map} +0 -0
  284. /package/dist/{projects-api-KRME2YKZ.js.map → projects-api-QE6L65SK.js.map} +0 -0
  285. /package/dist/{projects-mutate-3NHVG437.js.map → projects-mutate-MY7JTLSZ.js.map} +0 -0
  286. /package/dist/{query-HZYYRCLU.js.map → providers-KO2DODIO.js.map} +0 -0
  287. /package/dist/{recipes-EPLE2SNT.js.map → query-SQDHE4HV.js.map} +0 -0
  288. /package/dist/{registry-HK3WQ7BS.js.map → recipes-7S4MC5OJ.js.map} +0 -0
  289. /package/dist/{runner-BFYDWPUO.js.map → registry-LE6FWYZC.js.map} +0 -0
  290. /package/dist/{rotation-memo-B3EXXSEH.js.map → rotation-memo-7T75BLEX.js.map} +0 -0
  291. /package/dist/{scheduler-6T2SU5ST.js.map → routing-ILERJXB6.js.map} +0 -0
  292. /package/dist/{store-I3HEWVLQ.js.map → runner-YRBVNYTY.js.map} +0 -0
  293. /package/dist/{surface-usage-PRL6Z4U3.js.map → scheduler-BFM3EINS.js.map} +0 -0
  294. /package/dist/{timers-5LPZN55J.js.map → seat-7WM7II2B.js.map} +0 -0
  295. /package/dist/{types-VECTMYTO.js.map → store-MCOA6N4F.js.map} +0 -0
  296. /package/dist/{subagent-WV7QKQ3L.js.map → subagent-2SJHSSJS.js.map} +0 -0
  297. /package/dist/{types-Y6ZCZ52Z.js.map → surface-usage-NS74AXSE.js.map} +0 -0
  298. /package/dist/{wiki-M6YNRFC4.js.map → timers-PCB4ZIBD.js.map} +0 -0
  299. /package/dist/{workspace-setup-TBQAHYHJ.js.map → types-H5ENIUIT.js.map} +0 -0
  300. /package/dist/{whatsapp-state-XMTCSRXU.js.map → whatsapp-state-6D6FHHD2.js.map} +0 -0
package/README.md CHANGED
@@ -1,157 +1,106 @@
1
-
2
-
3
1
  <div align="center">
4
2
 
5
- <img src="docs/public/logo.png" alt="AgentX" width="96" />
3
+ <img src="docs/public/agentx-symbol.png" alt="AgentX" width="96" />
6
4
 
7
5
  # AgentX
8
6
 
9
- **A self-hosted mesh of AI agents for your team — A2A-native, auditable, on your machines.**
7
+ **Put an AI teammate on the tools your team already uses.**
10
8
 
11
9
  [![npm](https://img.shields.io/npm/v/agentix-cli?label=npm%20%C2%B7%20agentix-cli)](https://www.npmjs.com/package/agentix-cli)
12
10
  [![CI](https://github.com/anis-marrouchi/agentx/actions/workflows/ci.yml/badge.svg)](https://github.com/anis-marrouchi/agentx/actions/workflows/ci.yml)
13
11
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
14
12
  [![Node](https://img.shields.io/badge/node-22.x-brightgreen)](package.json)
15
- [![A2A](https://img.shields.io/badge/A2A-agent--to--agent%20protocol-blueviolet)](https://github.com/a2aproject/A2A)
16
13
 
17
- [Docs](https://agentx-docs.pages.dev) · [Install](#install) · [Journey (13 chapters)](#docs) · [How it compares](#how-it-compares)
14
+ [See the demo](#see-it-without-an-account) · [Install](#install) · [Read the guide](docs/index.md)
18
15
 
19
- <!-- HERO: replace with the "One message. Three machines." GIF once filmed -->
20
- <img src="docs/public/screenshots/mesh.png" alt="Live mesh graph — agents on different machines delegating over A2A" width="820" />
16
+ </div>
21
17
 
22
- *Agents on different machines discover each other, delegate work, and report back — every dispatch recorded in a replayable ledger.*
18
+ AgentX routes messages and scheduled jobs to AI agents you host. Connect Telegram, WhatsApp, GitLab, GitHub, or a webhook. Give each agent a clear job. Watch the result in a browser dashboard.
23
19
 
24
- </div>
20
+ ![Three agents on three local demo nodes in the Live dashboard](docs/public/screenshots/live.png)
25
21
 
26
- ---
22
+ *The isolated scripted demo, after a cross-node task. No live fleet data is shown.*
27
23
 
28
- Most agent tools give you **one agent in one chat app**. AgentX runs a **network**: agents on your laptop, your VPS, and your teammates' machines pair with one link, talk over the open [A2A protocol](https://github.com/a2aproject/A2A) (Linux Foundation), and answer on the channels your team already uses — Telegram, WhatsApp, GitLab, GitHub.
24
+ ## See it without an account
29
25
 
30
- ```
31
- You (Telegram): "@cx CI is red on gitlab — fix it and ship a patch"
32
- cx (laptop) → resolves @builder via the mesh directory
33
- builder (vps) → fixes, opens the MR, pipeline green
34
- cx → replies in your Telegram thread with the MR link
26
+ From a built source checkout with Node.js 22.x, run:
27
+
28
+ ```sh
29
+ node dist/cli.js demo
35
30
  ```
36
31
 
37
- ## Try it in 60 seconds — no keys, no accounts
32
+ The demo starts three local AgentX daemons and sends a task between them. Routing, network calls, and the event record are real. Model responses are scripted, so the demo makes no billable model calls. The terminal prints the dashboard addresses. [What to expect](docs/see-it-first.md).
38
33
 
39
- ```bash
40
- npx agentix-cli demo
41
- ```
34
+ ## What it does
35
+
36
+ - **Answers where your team works.** Connect a channel and assign its messages to an agent.
37
+ - **Runs scheduled work.** Ask an agent for a daily report or another recurring task.
38
+ - **Shows what happened.** Live shows current work; Activity records completed runs; Monitor shows reviewed work that needs a person and work agents can handle.
39
+ - **Grows across machines.** Start with one host, then pair another when a job needs its tools or files.
40
+
41
+ ## Describe an automation, get an automation
42
+
43
+ The Workflows editor has a chat control. Describe the timing, source, and destination in ordinary language. An authoring agent can propose a workflow and show an **Apply to canvas** action. Read the proposed steps before applying them: this replaces the current graph. Real authoring needs a working model. The demo includes a fixed example for **Build the demo report workflow**. [Walk through it](docs/automations/describe-it.md).
42
44
 
43
- Boots **three real daemons on your machine**, pairs them into a real A2A mesh, and plays a cross-node scenario — delegation, mesh hop, fix, report-back — live in the dashboards. Only the model is scripted; the daemons, mesh protocol, and ledger are the real thing.
45
+ ![The demo workflow assistant proposing a report](docs/public/screenshots/editor-chat-reply.png)
44
46
 
45
47
  ## Install
46
48
 
47
- ```bash
48
- npm install -g agentix-cli # the npm name; the command is `agentx`
49
- agentx setup # web wizard: agents, channels, keys
49
+ A technical teammate needs a terminal for installation, model setup, and starting the services. The web wizard handles the rest of the initial setup; daily inspection is in the browser. AgentX requires **Node.js 22.x**.
50
+
51
+ ```sh
52
+ git clone https://github.com/anis-marrouchi/agentx.git
53
+ cd agentx
54
+ git checkout docs-v2
55
+ cp .env.example .env
56
+ docker compose up --build -d
50
57
  ```
51
58
 
52
- Or one line — installs the CLI and opens the wizard:
59
+ Open **http://127.0.0.1:4202/setup**. Compose builds this checkout and starts both services; its default image uses an API model rather than a preinstalled model CLI.
60
+
61
+ Without Docker, install Node 22.x and pnpm 10, then build and open the wizard:
53
62
 
54
- ```bash
55
- curl -fsSL https://raw.githubusercontent.com/anis-marrouchi/agentx/master/install.sh | bash
63
+ ```sh
64
+ pnpm install
65
+ pnpm build
66
+ node dist/cli.js setup
56
67
  ```
57
68
 
58
- Docker: `git clone https://github.com/anis-marrouchi/agentx.git && cd agentx && docker compose up -d`
59
-
60
- Then open the dashboard at **http://127.0.0.1:4202**. Pair a second machine with `agentx connect mesh` — it prints an `agentx-mesh://join/…` link; run it on the other node and you have a mesh. Full guide: [install docs](docs/install.md).
61
-
62
- ## Why a mesh
63
-
64
- - **One agent can't be everywhere.** Your GitLab runner box, your WhatsApp-paired Mac, and your GPU server each run the agents that belong there; the mesh routes `@mentions` to whichever node owns the agent — cross-machine, cross-channel.
65
- - **A2A-native, not proprietary.** Nodes exchange agent cards and tasks over the Agent2Agent protocol (150+ orgs behind the standard). Your mesh isn't locked to AgentX — anything that speaks A2A can join the conversation.
66
- - **Auditable by construction.** Every dispatch decision — channel message, mesh hop, cron fire, workflow step — lands in an append-only intent ledger. `agentx ledger replay` reproduces the decision deterministically when you need to know *why* an agent did something.
67
- - **Secure by default.** The daemon binds to loopback; mesh endpoints verify bearer tokens; agent-to-agent chains are depth-capped; credentials live in env vars and never enter prompts. Threat model: [SECURITY.md](SECURITY.md).
68
-
69
- ## What you get
70
-
71
- - **Channels** — Telegram, WhatsApp (QR pair in the browser), GitLab, GitHub, generic webhooks. Agents reply where they were asked, or push anywhere.
72
- - **Mesh federation** — pair nodes over Tailscale/VPN with one link; manage any peer's config from one dashboard; GitLab mentions route across nodes with peer-owned tokens.
73
- - **Agents are folders, not code** — persona, knowledge, and tools in plain Markdown (`CLAUDE.md`, skills, references). Bring Claude Code, Codex CLI, OpenCode with any configured provider, Anthropic/OpenAI APIs, or an OpenAI-compatible endpoint.
74
- - **Operator surface** — browser setup wizard + 11-page admin dashboard: live activity, Kanban boards synced two-way with GitLab/GitHub issues, token-cost accounting, scoped API tokens.
75
- - **Scheduled work** — plain-English cron: `agentx schedule "every Monday at 9am" --agent sales`, with failure alerts and auto-disable.
76
- - **Workflows & procedures** — declarative YAML state machines with a visual editor for the flows that must survive restarts; versioned SOPs agents cite at runtime; deterministic no-LLM handlers for canonical answers.
77
- - **Compounding memory** — conversations absorb into a shared wiki (typed articles, `[[wikilinks]]`, versioned) that every agent queries — also exposed to Cursor/Claude Code via MCP (`agentx serve --stdio`).
78
- - **Wearable agents** — `agentx attach <agent>` makes the Claude Code session you already have open answer as that agent, so a Telegram or GitLab message reaches you where you're already working instead of spawning a subprocess. Unclaimed messages fall back to a spawned agent, so it's a preference, never a dependency.
79
- - **Governance** — intent ledger + replay, PM gating, typed capabilities, delegation-depth caps, destructive-action guardrails that hold even under `bypassPermissions`.
80
-
81
- ## How it compares
82
-
83
- | | **AgentX** | OpenClaw | Hermes Agent | Claude Code |
84
- |---|---|---|---|---|
85
- | Agents per install | a mesh of many, across machines | one | one | one session |
86
- | Cross-machine delegation | ✅ A2A protocol | ❌ | ❌ | ❌ (in-process subagents) |
87
- | Open interop standard | ✅ A2A (Linux Foundation) | ❌ | ❌ | MCP for tools |
88
- | Users / operators | team, multi-operator | single-user | single-user | single account |
89
- | Audit ledger + replay | ✅ append-only, deterministic replay | ❌ | ❌ | ❌ |
90
- | GitLab-native (boards, MRs, cross-node mentions) | ✅ | ❌ | ❌ | ❌ |
91
- | Chat channels | 6 + webhooks + plugins | ~24 | multiple | terminal |
92
- | LLM providers | Claude Code, Codex CLI, OpenCode, Anthropic, OpenAI-compatible | Claude-first | model-agnostic | Claude |
93
- | Self-hosted, no telemetry | ✅ | ✅ | ✅ | n/a |
94
-
95
- *(As of July 2026 — corrections welcome via PR.)*
96
-
97
- > **Running solo, just for yourself?** [OpenClaw](https://github.com/openclaw/openclaw) is built for exactly that and has a lighter install. If you outgrow it, we import your config: [Migrate from OpenClaw](docs/migration/from-openclaw.md).
98
-
99
- ## Use cases
100
-
101
- - **Solo founder ops** — run support triage, invoices, and scheduled reports from Telegram ([journey ch. 1–2](docs/journey/01-telegram-qa-bot.md))
102
- - **Dev team** — GitLab MR review agent + WhatsApp escalation + nightly maintenance across two machines ([mesh federation](docs/journey/08-mesh-federation.md))
103
- - **Agency / B2B** — one node per client, scoped tokens, per-agent cost accounting ([tokens](docs/reference/tokens.md), [usage](docs/playbooks/tier2-billing.md))
104
- - **Public-facing service** — citizen/customer intake bot with an auditable trail of every decision ([BPM grant application](docs/journey/12-bpm-grant-application.md))
105
-
106
- ## Screenshots
107
-
108
- | Live activity | Mesh | Boards |
109
- |---|---|---|
110
- | ![Live](docs/public/screenshots/live.png) | ![Mesh](docs/public/screenshots/mesh.png) | ![Boards](docs/public/screenshots/boards.png) |
111
-
112
- | Admin | Intent graph | Setup wizard |
113
- |---|---|---|
114
- | ![Admin](docs/public/screenshots/admin.png) | ![Graph](docs/public/screenshots/graph.png) | ![Setup](docs/public/screenshots/setup.png) |
115
-
116
- ## Architecture
117
-
118
- ```mermaid
119
- graph LR
120
- T[Telegram] --> R(Router)
121
- W[WhatsApp] --> R
122
- G[GitLab] --> R
123
- GH[GitHub] --> R
124
- C[Cron] --> R
125
- H[Webhook] --> R
126
- M[Mesh peer · A2A] --> R
127
- PL[Plugin channels] --> R
128
- R --> LDG[(Intent Ledger<br/>append-only)]
129
- LDG --> CTX[Context Engine]
130
- CTX --> AG[Agent workspace]
131
- AG --> P1[Claude Code]
132
- AG --> P2[Codex CLI / OpenCode]
133
- AG --> P3[Anthropic / OpenAI-compatible API]
134
- AG -.-> MEM[(Wiki + Graph + Memory)]
135
- AG -.-> WF[Workflows + Procedures]
136
- AG -.-> R
69
+ On a source install, the wizard serves the dashboard and **the daemon is a separate process**. Start it in another terminal, or use the wizard's **Start daemon now** button:
70
+
71
+ ```sh
72
+ node dist/cli.js daemon start --detach
73
+ node dist/cli.js daemon status
137
74
  ```
138
75
 
139
- Each agent = a workspace directory (`CLAUDE.md`, `.claude/skills/`, hooks, MCP servers). AgentX decides when and where agents run, records every dispatch in the ledger, and lets plugins extend channels and the event bus. It's the ops layer *above* agent frameworks — if you want a DSL for a single agent, AutoGen/LangGraph/CrewAI are that; AgentX is which agent runs, on which channel, on which machine, at what cost, replayably.
76
+ The npm package is `agentix-cli`; the installed executable is `agentx`. **Published version 0.27.0 has an installation error:** its postinstall script is missing from the package. Version 0.28.0 includes the fix; use 0.28.0 or newer, or build from source/Docker. Follow the [installation guide](docs/install.md) for details.
140
77
 
141
- ## Docs
78
+ ## One machine is enough to start
142
79
 
143
- Full documentation: **[agentx-docs.pages.dev](https://agentx-docs.pages.dev)** — including the worked journey from a one-agent Telegram bot to a hardened multi-node mesh:
144
- [Telegram Q&A bot](docs/journey/01-telegram-qa-bot.md) → [scheduled reports](docs/journey/02-scheduled-reports.md) → [multi-agent groups](docs/journey/03-multi-agent-group.md) → [cross-channel](docs/journey/04-cross-channel.md) → [hooks](docs/journey/05-hooks-webhooks.md) → [shared wiki](docs/journey/06-shared-wiki.md) → [mesh federation](docs/journey/08-mesh-federation.md) → [deterministic services](docs/journey/09-deterministic-services.md) → [MCP server](docs/journey/10-mcp-server.md) → [production hardening](docs/journey/11-production-hardening.md) → [BPM](docs/journey/12-bpm-grant-application.md) → [wearable agents](docs/journey/14-wearable-agent.md)
80
+ When an agent needs to work on another machine, pair the nodes with `agentx connect mesh invite` and `agentx connect mesh join <link>`. A connected agent can pass work to another node. For example:
145
81
 
146
- Reference: [CLI](docs/reference/cli.md) · [Config schema](docs/reference/config-schema.md) · [Dashboard](docs/reference/dashboard/) · [Tokens](docs/reference/tokens.md) · [Tailscale](docs/reference/tailscale-setup.md)
82
+ ```text
83
+ You: "@cx CI is red on GitLab — find the cause"
84
+ CX: sends the build investigation to @builder on another node
85
+ Builder: checks the project and reports the result
86
+ CX: replies in the original thread
87
+ ```
147
88
 
148
- ## Community
89
+ The [second-machine guide](docs/jobs/second-machine.md) covers pairing. You do not need mesh for a one-agent setup.
149
90
 
150
- - **[Share your mesh](../../issues/new?template=share_your_mesh.yml)** — real-world setups shape the roadmap
151
- - [Contributing](CONTRIBUTING.md) · [Security policy](SECURITY.md) · [Changelog](CHANGELOG.md)
91
+ ## The dashboard
152
92
 
153
- If AgentX is useful to you, a ⭐ genuinely helps others find it.
93
+ The six top-level tabs are **Live, Operations, Monitor, Activity, Workflows, and Settings**. [Dashboard guide](docs/dashboard/index.md). The dashboard and daemon must both be running for messages to be handled.
154
94
 
155
- ## License
95
+ ## Channels and models
96
+
97
+ Telegram and WhatsApp have pairing flows. GitLab, GitHub, and generic webhooks can bring in events. **Slack and Discord are not supported as live channel adapters in this build.** [Channel reference](docs/reference/channels.md).
98
+
99
+ Real agents need a configured model. An API provider needs a key; Claude Code, Codex CLI, and OpenCode need their CLI installed and authenticated on the host. Model-provider charges or subscription terms depend on your chosen provider.
100
+
101
+ ## Help and development
102
+
103
+ - [Start here](docs/index.md) · [Troubleshooting](docs/help/its-not-answering.md) · [CLI reference](docs/reference/cli.md)
104
+ - [Contributing](CONTRIBUTING.md) · [Security policy](SECURITY.md) · [Changelog](CHANGELOG.md)
156
105
 
157
- MIT.
106
+ AgentX is released under the [MIT License](LICENSE).
@@ -0,0 +1,159 @@
1
+ import AppKit
2
+ import ApplicationServices
3
+
4
+ /// Reads the focused application's accessibility tree.
5
+ ///
6
+ /// The tree, not a screenshot, is the primary surface. It gives structured,
7
+ /// verifiable elements with real roles and real frames — so a click can be
8
+ /// confirmed to have hit the control that was chosen, and the candidate set
9
+ /// is ENUMERABLE, which is what lets a decision model pick from it rather
10
+ /// than invent coordinates.
11
+ ///
12
+ /// Its honest failure mode is apps that expose nothing: icon-only toolbars,
13
+ /// custom-drawn editors, canvas games. For those the tree comes back thin
14
+ /// and the caller should say so rather than guess — screenshots are the
15
+ /// documented fallback, and are deliberately not in this first cut.
16
+ enum AXTree {
17
+
18
+ struct Element: Codable {
19
+ let id: Int
20
+ let role: String
21
+ /// Best human-readable name, from whichever attribute has one.
22
+ let label: String
23
+ let value: String?
24
+ let enabled: Bool
25
+ let x: Double, y: Double, width: Double, height: Double
26
+ /// Index of the parent in the same flat array; -1 for the root.
27
+ let parent: Int
28
+ }
29
+
30
+ struct Snapshot: Codable {
31
+ let app: String
32
+ let pid: Int32
33
+ let window: String?
34
+ let elements: [Element]
35
+ let truncated: Bool
36
+ let note: String?
37
+ }
38
+
39
+ /// Accessibility is TCC-gated and there is no way to ask politely from
40
+ /// a CLI — the prompt only appears for a bundled, signed app. Report it
41
+ /// clearly instead of returning an empty tree that looks like an app
42
+ /// with no controls.
43
+ static func trusted() -> Bool { AXIsProcessTrusted() }
44
+
45
+ static func snapshot(maxElements: Int = 400) -> Snapshot {
46
+ guard let app = NSWorkspace.shared.frontmostApplication else {
47
+ return Snapshot(app: "unknown", pid: 0, window: nil, elements: [],
48
+ truncated: false, note: "no frontmost application")
49
+ }
50
+ let axApp = AXUIElementCreateApplication(app.processIdentifier)
51
+ var elements: [Element] = []
52
+ var truncated = false
53
+
54
+ // The focused window, not the whole app: an app's tree includes
55
+ // every window it owns, and pointing at a control in a background
56
+ // window is worse than useless.
57
+ var windowRef: CFTypeRef?
58
+ AXUIElementCopyAttributeValue(axApp, kAXFocusedWindowAttribute as CFString, &windowRef)
59
+ let root = (windowRef as! AXUIElement?) ?? axApp
60
+ let windowTitle = string(root, kAXTitleAttribute)
61
+
62
+ // Traverse everything, but only SPEND the budget on elements worth
63
+ // choosing between.
64
+ //
65
+ // A modern web page is mostly containers: x.com fills 273 of 400
66
+ // slots with unlabelled AXGroups before reaching its search field,
67
+ // so a naive cap returns a tree with no inputs in it and the page
68
+ // looks like it exposes nothing. Skipped nodes still have their
69
+ // children walked; `parent` points at the nearest KEPT ancestor, so
70
+ // the hierarchy stays usable for page-scoping.
71
+ var queue: [(AXUIElement, Int)] = [(root, -1)]
72
+ while !queue.isEmpty {
73
+ if elements.count >= maxElements { truncated = true; break }
74
+ let (node, parent) = queue.removeFirst()
75
+
76
+ var keptIndex = parent
77
+ if let e = describe(node, id: elements.count, parent: parent), worthKeeping(e) {
78
+ keptIndex = elements.count
79
+ elements.append(e)
80
+ }
81
+ var childrenRef: CFTypeRef?
82
+ if AXUIElementCopyAttributeValue(node, kAXChildrenAttribute as CFString, &childrenRef) == .success,
83
+ let children = childrenRef as? [AXUIElement] {
84
+ for c in children { queue.append((c, keptIndex)) }
85
+ }
86
+ }
87
+
88
+ return Snapshot(
89
+ app: app.localizedName ?? "unknown",
90
+ pid: app.processIdentifier,
91
+ window: windowTitle,
92
+ elements: elements,
93
+ truncated: truncated,
94
+ note: elements.count < 3
95
+ ? "this app exposes almost no accessibility information — the tree is not usable here"
96
+ : nil)
97
+ }
98
+
99
+ /// Containers earn a slot only when they say something.
100
+ ///
101
+ /// The web area itself is always kept — page-scoping needs it as an
102
+ /// anchor even though it carries no label of its own.
103
+ private static func worthKeeping(_ e: Element) -> Bool {
104
+ if e.role == "AXWebArea" { return true }
105
+ let isContainer = e.role == "AXGroup" || e.role == "AXUnknown"
106
+ || e.role == "AXSplitGroup" || e.role == "AXScrollArea"
107
+ if !isContainer { return true }
108
+ // A labelled group is usually a landmark worth naming; "0" and ""
109
+ // are the DOM showing through.
110
+ let label = e.label.trimmingCharacters(in: .whitespaces)
111
+ return label.count > 1 && label != "0"
112
+ }
113
+
114
+ private static func describe(_ node: AXUIElement, id: Int, parent: Int) -> Element? {
115
+ guard let role = string(node, kAXRoleAttribute) else { return nil }
116
+
117
+ // First attribute that actually names the thing. Title is usually
118
+ // best, but buttons frequently carry only a description, and menu
119
+ // items only a value.
120
+ let label = [kAXTitleAttribute, kAXDescriptionAttribute, kAXLabelValueAttribute,
121
+ kAXHelpAttribute, kAXPlaceholderValueAttribute]
122
+ .compactMap { string(node, $0) }
123
+ .first(where: { !$0.isEmpty }) ?? ""
124
+
125
+ var frame = CGRect.zero
126
+ var posRef: CFTypeRef?, sizeRef: CFTypeRef?
127
+ if AXUIElementCopyAttributeValue(node, kAXPositionAttribute as CFString, &posRef) == .success,
128
+ AXUIElementCopyAttributeValue(node, kAXSizeAttribute as CFString, &sizeRef) == .success {
129
+ var p = CGPoint.zero, s = CGSize.zero
130
+ AXValueGetValue(posRef as! AXValue, .cgPoint, &p)
131
+ AXValueGetValue(sizeRef as! AXValue, .cgSize, &s)
132
+ frame = CGRect(origin: p, size: s)
133
+ }
134
+
135
+ var enabledRef: CFTypeRef?
136
+ AXUIElementCopyAttributeValue(node, kAXEnabledAttribute as CFString, &enabledRef)
137
+ let enabled = (enabledRef as? Bool) ?? true
138
+
139
+ // Invisible and zero-sized nodes cannot be pointed at, and there
140
+ // are a great many of them — dropping them keeps the candidate set
141
+ // small enough to hand to a model.
142
+ if frame.width < 1 || frame.height < 1 { return nil }
143
+
144
+ return Element(id: id, role: role, label: label,
145
+ value: string(node, kAXValueAttribute),
146
+ enabled: enabled,
147
+ x: frame.origin.x, y: frame.origin.y,
148
+ width: frame.size.width, height: frame.size.height,
149
+ parent: parent)
150
+ }
151
+
152
+ private static func string(_ node: AXUIElement, _ attr: String) -> String? {
153
+ var ref: CFTypeRef?
154
+ guard AXUIElementCopyAttributeValue(node, attr as CFString, &ref) == .success else { return nil }
155
+ if let s = ref as? String { return s.trimmingCharacters(in: .whitespacesAndNewlines) }
156
+ if let n = ref as? NSNumber { return n.stringValue }
157
+ return nil
158
+ }
159
+ }
@@ -0,0 +1,63 @@
1
+ import AppKit
2
+
3
+ /// Acme design tokens, transcribed for AppKit.
4
+ ///
5
+ /// Copied from the design system's `tokens/` rather than eyeballed, so the
6
+ /// widget belongs to the same product as example.com instead of merely
7
+ /// looking tidy. Two rules from that system shape most of what follows:
8
+ ///
9
+ /// - Mono is UPPERCASE with 0.06–0.08em tracking, and is reserved for
10
+ /// eyebrows, status and meta. Body stays sentence case.
11
+ /// - No emoji, ever. Texture comes from Unicode punctuation — · → ● ✓ —
12
+ /// and from a single accent colour, not from decoration.
13
+ ///
14
+ /// System colours were the obvious default and the wrong one: .systemRed
15
+ /// and .systemGreen are macOS's voice, not Acme's, and they shift under
16
+ /// the user's accent-colour setting.
17
+ enum Brand {
18
+
19
+ // Teal accent and deep blue primary — the two colours the whole
20
+ // identity rests on.
21
+ static let accent = NSColor(srgbRed: 0.078, green: 0.722, blue: 0.651, alpha: 1) // #14b8a6
22
+ static let accentDeep = NSColor(srgbRed: 0.059, green: 0.463, blue: 0.431, alpha: 1) // #0f766e
23
+ static let primary = NSColor(srgbRed: 0.118, green: 0.227, blue: 0.541, alpha: 1) // #1e3a8a
24
+ static let primaryBright = NSColor(srgbRed: 0.114, green: 0.306, blue: 0.847, alpha: 1) // #1d4ed8
25
+
26
+ static let ink = NSColor(srgbRed: 0.043, green: 0.078, blue: 0.075, alpha: 1) // #0b1413
27
+ static let paper = NSColor(srgbRed: 0.980, green: 0.980, blue: 0.976, alpha: 1) // #fafaf9
28
+
29
+ /// Amber and rose for states the palette has no token for. Muted to
30
+ /// sit beside the teal rather than fight it.
31
+ static let warn = NSColor(srgbRed: 0.851, green: 0.596, blue: 0.180, alpha: 1)
32
+ static let alert = NSColor(srgbRed: 0.804, green: 0.400, blue: 0.376, alpha: 1)
33
+
34
+ enum Radius {
35
+ static let sm: CGFloat = 6
36
+ static let base: CGFloat = 10
37
+ static let md: CGFloat = 12
38
+ static let lg: CGFloat = 18
39
+ static let pill: CGFloat = 999
40
+ }
41
+
42
+ /// `--nq-ease` — the single easing curve the system uses everywhere.
43
+ static let ease = CAMediaTimingFunction(controlPoints: 0.65, 0, 0.35, 1)
44
+ static let durFast: CFTimeInterval = 0.15
45
+
46
+ /// Meta type: mono, uppercase, tracked. Eyebrows and status only.
47
+ static func meta(size: CGFloat = 10.5) -> [NSAttributedString.Key: Any] {
48
+ [
49
+ .font: NSFont.monospacedSystemFont(ofSize: size, weight: .semibold),
50
+ .kern: size * 0.07,
51
+ ]
52
+ }
53
+
54
+ static func metaString(_ text: String, size: CGFloat = 10.5, color: NSColor) -> NSAttributedString {
55
+ var attrs = meta(size: size)
56
+ attrs[.foregroundColor] = color
57
+ return NSAttributedString(string: text.uppercased(), attributes: attrs)
58
+ }
59
+
60
+ static func body(size: CGFloat = 13, weight: NSFont.Weight = .medium) -> NSFont {
61
+ NSFont.systemFont(ofSize: size, weight: weight)
62
+ }
63
+ }
@@ -0,0 +1,97 @@
1
+ import AppKit
2
+ import ApplicationServices
3
+
4
+ /// What currently has keyboard focus.
5
+ ///
6
+ /// This exists because of a near miss. A lesson located "the search box",
7
+ /// clicked it, and typed. The click landed on a navigation LINK rather
8
+ /// than an input, the page changed underneath, focus ended up somewhere
9
+ /// else entirely, and the next step pressed Return. On a social site the
10
+ /// thing that had focus could have been the compose box — so a search
11
+ /// query was one keystroke from being published.
12
+ ///
13
+ /// Synthetic typing goes wherever focus is. It cannot tell a search field
14
+ /// from a post composer from a terminal. So the caller has to look first,
15
+ /// and that means a way to ask.
16
+ enum Focus {
17
+
18
+ struct Focused: Codable {
19
+ let role: String
20
+ let label: String
21
+ let value: String?
22
+ /// True when this can accept typing at all.
23
+ let editable: Bool
24
+ /// True when typing here plausibly PUBLISHES something. Heuristic
25
+ /// and deliberately over-inclusive: the cost of a false alarm is a
26
+ /// refused step, and the cost of a miss is a public post.
27
+ let publishRisk: Bool
28
+ let app: String
29
+ }
30
+
31
+ /// Roles that accept text.
32
+ private static let editableRoles: Set<String> = [
33
+ "AXTextField", "AXTextArea", "AXComboBox", "AXSearchField",
34
+ ]
35
+
36
+ /// Words that suggest the field posts, sends or publishes. Checked
37
+ /// against the label, the placeholder and the nearest container — in
38
+ /// several languages, because the UI under test is not always English.
39
+ private static let publishWords = [
40
+ "post", "tweet", "compose", "reply", "publish", "send", "message",
41
+ "comment", "status", "quoi de neuf", "publier", "répondre", "envoyer",
42
+ "tweeter", "message", "commentaire",
43
+ ]
44
+
45
+ static func current() -> Focused? {
46
+ // Ask the frontmost application directly, the way AXTree does.
47
+ //
48
+ // Going through the system-wide element's focused-application
49
+ // attribute returned nothing here even with a text field focused,
50
+ // and a focus check that always fails is a gate that blocks
51
+ // everything — safe, and useless. Force-casting a CFTypeRef that
52
+ // came back nil also silently yields nil rather than an error,
53
+ // which is how this hid.
54
+ guard let app = NSWorkspace.shared.frontmostApplication else { return nil }
55
+ let axApp = AXUIElementCreateApplication(app.processIdentifier)
56
+
57
+ var elementRef: CFTypeRef?
58
+ guard AXUIElementCopyAttributeValue(axApp, kAXFocusedUIElementAttribute as CFString, &elementRef) == .success,
59
+ CFGetTypeID(elementRef) == AXUIElementGetTypeID()
60
+ else { return nil }
61
+ let element = unsafeBitCast(elementRef, to: AXUIElement.self)
62
+
63
+ let role = string(element, kAXRoleAttribute) ?? ""
64
+ let label = [kAXTitleAttribute, kAXDescriptionAttribute, kAXPlaceholderValueAttribute, kAXHelpAttribute]
65
+ .compactMap { string(element, $0) }
66
+ .first(where: { !$0.isEmpty }) ?? ""
67
+ let value = string(element, kAXValueAttribute)
68
+
69
+ // Also consider the enclosing container: a composer's textbox is
70
+ // often unlabelled while its parent says "Post".
71
+ var context = "\(label) \(string(element, kAXRoleDescriptionAttribute) ?? "")"
72
+ var parentRef: CFTypeRef?
73
+ if AXUIElementCopyAttributeValue(element, kAXParentAttribute as CFString, &parentRef) == .success,
74
+ let parent = parentRef as! AXUIElement? {
75
+ context += " " + ([kAXTitleAttribute, kAXDescriptionAttribute]
76
+ .compactMap { string(parent, $0) }.joined(separator: " "))
77
+ }
78
+ let haystack = context.lowercased()
79
+ let risky = publishWords.contains { haystack.contains($0) }
80
+
81
+ return Focused(
82
+ role: role,
83
+ label: label,
84
+ value: value,
85
+ editable: editableRoles.contains(role)
86
+ || (string(element, kAXRoleDescriptionAttribute)?.lowercased().contains("text") ?? false),
87
+ publishRisk: risky,
88
+ app: app.localizedName ?? "unknown")
89
+ }
90
+
91
+ private static func string(_ node: AXUIElement, _ attr: String) -> String? {
92
+ var ref: CFTypeRef?
93
+ guard AXUIElementCopyAttributeValue(node, attr as CFString, &ref) == .success else { return nil }
94
+ if let s = ref as? String { return s.trimmingCharacters(in: .whitespacesAndNewlines) }
95
+ return nil
96
+ }
97
+ }