envseed 0.3.0 → 0.3.1

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 (323) hide show
  1. package/lib/hook-handler.mjs +48 -9
  2. package/lib/log-incident.mjs +2 -2
  3. package/lib/overlay/envseed-overlay +0 -0
  4. package/lib/overlay/growing-plant-1-frames/000.png +0 -0
  5. package/lib/overlay/growing-plant-1-frames/001.png +0 -0
  6. package/lib/overlay/growing-plant-1-frames/002.png +0 -0
  7. package/lib/overlay/growing-plant-1-frames/003.png +0 -0
  8. package/lib/overlay/growing-plant-1-frames/004.png +0 -0
  9. package/lib/overlay/growing-plant-1-frames/005.png +0 -0
  10. package/lib/overlay/growing-plant-1-frames/006.png +0 -0
  11. package/lib/overlay/growing-plant-1-frames/007.png +0 -0
  12. package/lib/overlay/growing-plant-1-frames/008.png +0 -0
  13. package/lib/overlay/growing-plant-1-frames/009.png +0 -0
  14. package/lib/overlay/growing-plant-1-frames/010.png +0 -0
  15. package/lib/overlay/growing-plant-1-frames/011.png +0 -0
  16. package/lib/overlay/growing-plant-1-frames/012.png +0 -0
  17. package/lib/overlay/growing-plant-1-frames/013.png +0 -0
  18. package/lib/overlay/growing-plant-1-frames/014.png +0 -0
  19. package/lib/overlay/growing-plant-1-frames/015.png +0 -0
  20. package/lib/overlay/growing-plant-1-frames/016.png +0 -0
  21. package/lib/overlay/growing-plant-1-frames/017.png +0 -0
  22. package/lib/overlay/growing-plant-1-frames/018.png +0 -0
  23. package/lib/overlay/growing-plant-1-frames/019.png +0 -0
  24. package/lib/overlay/growing-plant-1-frames/020.png +0 -0
  25. package/lib/overlay/growing-plant-1-frames/021.png +0 -0
  26. package/lib/overlay/growing-plant-1-frames/022.png +0 -0
  27. package/lib/overlay/growing-plant-1-frames/023.png +0 -0
  28. package/lib/overlay/growing-plant-1-frames/024.png +0 -0
  29. package/lib/overlay/growing-plant-1-frames/025.png +0 -0
  30. package/lib/overlay/growing-plant-1-frames/026.png +0 -0
  31. package/lib/overlay/growing-plant-1-frames/027.png +0 -0
  32. package/lib/overlay/growing-plant-1-frames/028.png +0 -0
  33. package/lib/overlay/growing-plant-1-frames/029.png +0 -0
  34. package/lib/overlay/growing-plant-1-frames/030.png +0 -0
  35. package/lib/overlay/growing-plant-1-frames/031.png +0 -0
  36. package/lib/overlay/growing-plant-1-frames/032.png +0 -0
  37. package/lib/overlay/growing-plant-1-frames/033.png +0 -0
  38. package/lib/overlay/growing-plant-1-frames/034.png +0 -0
  39. package/lib/overlay/growing-plant-1-frames/035.png +0 -0
  40. package/lib/overlay/growing-plant-1-frames/036.png +0 -0
  41. package/lib/overlay/growing-plant-1-frames/037.png +0 -0
  42. package/lib/overlay/growing-plant-1-frames/038.png +0 -0
  43. package/lib/overlay/growing-plant-1-frames/039.png +0 -0
  44. package/lib/overlay/growing-plant-1-frames/040.png +0 -0
  45. package/lib/overlay/growing-plant-1-frames/041.png +0 -0
  46. package/lib/overlay/growing-plant-1-frames/042.png +0 -0
  47. package/lib/overlay/growing-plant-1-frames/043.png +0 -0
  48. package/lib/overlay/growing-plant-1-frames/044.png +0 -0
  49. package/lib/overlay/growing-plant-1-frames/045.png +0 -0
  50. package/lib/overlay/growing-plant-1-frames/046.png +0 -0
  51. package/lib/overlay/growing-plant-1.apng +0 -0
  52. package/lib/overlay/growing-plant-2-frames/000.png +0 -0
  53. package/lib/overlay/growing-plant-2-frames/001.png +0 -0
  54. package/lib/overlay/growing-plant-2-frames/002.png +0 -0
  55. package/lib/overlay/growing-plant-2-frames/003.png +0 -0
  56. package/lib/overlay/growing-plant-2-frames/004.png +0 -0
  57. package/lib/overlay/growing-plant-2-frames/005.png +0 -0
  58. package/lib/overlay/growing-plant-2-frames/006.png +0 -0
  59. package/lib/overlay/growing-plant-2-frames/007.png +0 -0
  60. package/lib/overlay/growing-plant-2-frames/008.png +0 -0
  61. package/lib/overlay/growing-plant-2-frames/009.png +0 -0
  62. package/lib/overlay/growing-plant-2-frames/010.png +0 -0
  63. package/lib/overlay/growing-plant-2-frames/011.png +0 -0
  64. package/lib/overlay/growing-plant-2-frames/012.png +0 -0
  65. package/lib/overlay/growing-plant-2-frames/013.png +0 -0
  66. package/lib/overlay/growing-plant-2-frames/014.png +0 -0
  67. package/lib/overlay/growing-plant-2-frames/015.png +0 -0
  68. package/lib/overlay/growing-plant-2-frames/016.png +0 -0
  69. package/lib/overlay/growing-plant-2-frames/017.png +0 -0
  70. package/lib/overlay/growing-plant-2-frames/018.png +0 -0
  71. package/lib/overlay/growing-plant-2-frames/019.png +0 -0
  72. package/lib/overlay/growing-plant-2-frames/020.png +0 -0
  73. package/lib/overlay/growing-plant-2-frames/021.png +0 -0
  74. package/lib/overlay/growing-plant-2-frames/022.png +0 -0
  75. package/lib/overlay/growing-plant-2-frames/023.png +0 -0
  76. package/lib/overlay/growing-plant-2-frames/024.png +0 -0
  77. package/lib/overlay/growing-plant-2-frames/025.png +0 -0
  78. package/lib/overlay/growing-plant-2-frames/026.png +0 -0
  79. package/lib/overlay/growing-plant-2-frames/027.png +0 -0
  80. package/lib/overlay/growing-plant-2-frames/028.png +0 -0
  81. package/lib/overlay/growing-plant-2-frames/029.png +0 -0
  82. package/lib/overlay/growing-plant-2-frames/030.png +0 -0
  83. package/lib/overlay/growing-plant-2-frames/031.png +0 -0
  84. package/lib/overlay/growing-plant-2-frames/032.png +0 -0
  85. package/lib/overlay/growing-plant-2-frames/033.png +0 -0
  86. package/lib/overlay/growing-plant-2-frames/034.png +0 -0
  87. package/lib/overlay/growing-plant-2-frames/035.png +0 -0
  88. package/lib/overlay/growing-plant-2-frames/036.png +0 -0
  89. package/lib/overlay/growing-plant-2-frames/037.png +0 -0
  90. package/lib/overlay/growing-plant-2-frames/038.png +0 -0
  91. package/lib/overlay/growing-plant-2-frames/039.png +0 -0
  92. package/lib/overlay/growing-plant-2-frames/040.png +0 -0
  93. package/lib/overlay/growing-plant-2-frames/041.png +0 -0
  94. package/lib/overlay/growing-plant-2-frames/042.png +0 -0
  95. package/lib/overlay/growing-plant-2-frames/043.png +0 -0
  96. package/lib/overlay/growing-plant-2-frames/044.png +0 -0
  97. package/lib/overlay/growing-plant-2-frames/045.png +0 -0
  98. package/lib/overlay/growing-plant-2-frames/046.png +0 -0
  99. package/lib/overlay/growing-plant-2-frames/047.png +0 -0
  100. package/lib/overlay/growing-plant-2-frames/048.png +0 -0
  101. package/lib/overlay/growing-plant-2-frames/049.png +0 -0
  102. package/lib/overlay/growing-plant-2-frames/050.png +0 -0
  103. package/lib/overlay/growing-plant-2-frames/051.png +0 -0
  104. package/lib/overlay/growing-plant-2-frames/052.png +0 -0
  105. package/lib/overlay/growing-plant-2-frames/053.png +0 -0
  106. package/lib/overlay/growing-plant-2-frames/054.png +0 -0
  107. package/lib/overlay/growing-plant-2-frames/055.png +0 -0
  108. package/lib/overlay/growing-plant-2-frames/056.png +0 -0
  109. package/lib/overlay/growing-plant-2.apng +0 -0
  110. package/lib/overlay/growing-seed-frames/000.png +0 -0
  111. package/lib/overlay/growing-seed-frames/001.png +0 -0
  112. package/lib/overlay/growing-seed-frames/002.png +0 -0
  113. package/lib/overlay/growing-seed-frames/003.png +0 -0
  114. package/lib/overlay/growing-seed-frames/004.png +0 -0
  115. package/lib/overlay/growing-seed-frames/005.png +0 -0
  116. package/lib/overlay/growing-seed-frames/006.png +0 -0
  117. package/lib/overlay/growing-seed-frames/007.png +0 -0
  118. package/lib/overlay/growing-seed-frames/008.png +0 -0
  119. package/lib/overlay/growing-seed-frames/009.png +0 -0
  120. package/lib/overlay/growing-seed-frames/010.png +0 -0
  121. package/lib/overlay/growing-seed-frames/011.png +0 -0
  122. package/lib/overlay/growing-seed-frames/012.png +0 -0
  123. package/lib/overlay/growing-seed-frames/013.png +0 -0
  124. package/lib/overlay/growing-seed-frames/014.png +0 -0
  125. package/lib/overlay/growing-seed-frames/015.png +0 -0
  126. package/lib/overlay/growing-seed-frames/016.png +0 -0
  127. package/lib/overlay/growing-seed-frames/017.png +0 -0
  128. package/lib/overlay/growing-seed-frames/018.png +0 -0
  129. package/lib/overlay/growing-seed-frames/019.png +0 -0
  130. package/lib/overlay/growing-seed-frames/020.png +0 -0
  131. package/lib/overlay/growing-seed-frames/021.png +0 -0
  132. package/lib/overlay/growing-seed-frames/022.png +0 -0
  133. package/lib/overlay/growing-seed-frames/023.png +0 -0
  134. package/lib/overlay/growing-seed-frames/024.png +0 -0
  135. package/lib/overlay/growing-seed-frames/025.png +0 -0
  136. package/lib/overlay/growing-seed-frames/026.png +0 -0
  137. package/lib/overlay/growing-seed-frames/027.png +0 -0
  138. package/lib/overlay/growing-seed-frames/028.png +0 -0
  139. package/lib/overlay/growing-seed-frames/029.png +0 -0
  140. package/lib/overlay/growing-seed-frames/030.png +0 -0
  141. package/lib/overlay/growing-seed-frames/031.png +0 -0
  142. package/lib/overlay/growing-seed-frames/032.png +0 -0
  143. package/lib/overlay/growing-seed-frames/033.png +0 -0
  144. package/lib/overlay/growing-seed-frames/034.png +0 -0
  145. package/lib/overlay/growing-seed-frames/035.png +0 -0
  146. package/lib/overlay/growing-seed-frames/036.png +0 -0
  147. package/lib/overlay/growing-seed-frames/037.png +0 -0
  148. package/lib/overlay/growing-seed-frames/038.png +0 -0
  149. package/lib/overlay/growing-seed-frames/039.png +0 -0
  150. package/lib/overlay/growing-seed-frames/040.png +0 -0
  151. package/lib/overlay/growing-seed-frames/041.png +0 -0
  152. package/lib/overlay/growing-seed-frames/042.png +0 -0
  153. package/lib/overlay/growing-seed-frames/043.png +0 -0
  154. package/lib/overlay/growing-seed-frames/044.png +0 -0
  155. package/lib/overlay/growing-seed-frames/045.png +0 -0
  156. package/lib/overlay/growing-seed-frames/046.png +0 -0
  157. package/lib/overlay/growing-seed-frames/047.png +0 -0
  158. package/lib/overlay/growing-seed-frames/048.png +0 -0
  159. package/lib/overlay/growing-seed-frames/049.png +0 -0
  160. package/lib/overlay/growing-seed-frames/050.png +0 -0
  161. package/lib/overlay/growing-seed-frames/051.png +0 -0
  162. package/lib/overlay/growing-seed-frames/052.png +0 -0
  163. package/lib/overlay/growing-seed-frames/053.png +0 -0
  164. package/lib/overlay/growing-seed-frames/054.png +0 -0
  165. package/lib/overlay/growing-seed-frames/055.png +0 -0
  166. package/lib/overlay/growing-seed-frames/056.png +0 -0
  167. package/lib/overlay/growing-seed.apng +0 -0
  168. package/lib/overlay/icon-frames/000.png +0 -0
  169. package/lib/overlay/icon-frames/001.png +0 -0
  170. package/lib/overlay/icon-frames/002.png +0 -0
  171. package/lib/overlay/icon-frames/003.png +0 -0
  172. package/lib/overlay/icon-frames/004.png +0 -0
  173. package/lib/overlay/icon-frames/005.png +0 -0
  174. package/lib/overlay/icon-frames/006.png +0 -0
  175. package/lib/overlay/icon-frames/007.png +0 -0
  176. package/lib/overlay/icon-frames/008.png +0 -0
  177. package/lib/overlay/icon-frames/009.png +0 -0
  178. package/lib/overlay/icon-frames/010.png +0 -0
  179. package/lib/overlay/icon-frames/011.png +0 -0
  180. package/lib/overlay/icon-frames/012.png +0 -0
  181. package/lib/overlay/icon-frames/013.png +0 -0
  182. package/lib/overlay/icon-frames/014.png +0 -0
  183. package/lib/overlay/icon-frames/015.png +0 -0
  184. package/lib/overlay/icon-frames/016.png +0 -0
  185. package/lib/overlay/icon-frames/017.png +0 -0
  186. package/lib/overlay/icon-frames/018.png +0 -0
  187. package/lib/overlay/icon-frames/019.png +0 -0
  188. package/lib/overlay/icon-frames/020.png +0 -0
  189. package/lib/overlay/icon-frames/021.png +0 -0
  190. package/lib/overlay/icon-frames/022.png +0 -0
  191. package/lib/overlay/icon-frames/023.png +0 -0
  192. package/lib/overlay/icon-frames/024.png +0 -0
  193. package/lib/overlay/icon-frames/025.png +0 -0
  194. package/lib/overlay/icon-frames/026.png +0 -0
  195. package/lib/overlay/icon-frames/027.png +0 -0
  196. package/lib/overlay/icon-frames/028.png +0 -0
  197. package/lib/overlay/icon-frames/029.png +0 -0
  198. package/lib/overlay/icon-frames/030.png +0 -0
  199. package/lib/overlay/icon-frames/031.png +0 -0
  200. package/lib/overlay/icon-frames/032.png +0 -0
  201. package/lib/overlay/icon-frames/033.png +0 -0
  202. package/lib/overlay/icon-frames/034.png +0 -0
  203. package/lib/overlay/icon-frames/035.png +0 -0
  204. package/lib/overlay/icon-frames/036.png +0 -0
  205. package/lib/overlay/icon-frames/037.png +0 -0
  206. package/lib/overlay/icon-frames/038.png +0 -0
  207. package/lib/overlay/icon-frames/039.png +0 -0
  208. package/lib/overlay/icon-frames/040.png +0 -0
  209. package/lib/overlay/icon-frames/041.png +0 -0
  210. package/lib/overlay/icon-frames/042.png +0 -0
  211. package/lib/overlay/icon-frames/043.png +0 -0
  212. package/lib/overlay/icon-frames/044.png +0 -0
  213. package/lib/overlay/icon-frames/045.png +0 -0
  214. package/lib/overlay/icon-frames/046.png +0 -0
  215. package/lib/overlay/icon-frames/047.png +0 -0
  216. package/lib/overlay/icon-frames/048.png +0 -0
  217. package/lib/overlay/icon-frames/049.png +0 -0
  218. package/lib/overlay/icon-frames/050.png +0 -0
  219. package/lib/overlay/icon-frames/051.png +0 -0
  220. package/lib/overlay/icon-frames/052.png +0 -0
  221. package/lib/overlay/icon-frames/053.png +0 -0
  222. package/lib/overlay/icon-frames/054.png +0 -0
  223. package/lib/overlay/icon-frames/055.png +0 -0
  224. package/lib/overlay/icon-frames/056.png +0 -0
  225. package/lib/overlay/icon.apng +0 -0
  226. package/lib/overlay/icon.gif +0 -0
  227. package/lib/overlay/icon.png +0 -0
  228. package/lib/overlay/overlay.swift +574 -0
  229. package/lib/overlay/plant-seeds-frames/000.png +0 -0
  230. package/lib/overlay/plant-seeds-frames/001.png +0 -0
  231. package/lib/overlay/plant-seeds-frames/002.png +0 -0
  232. package/lib/overlay/plant-seeds-frames/003.png +0 -0
  233. package/lib/overlay/plant-seeds-frames/004.png +0 -0
  234. package/lib/overlay/plant-seeds-frames/005.png +0 -0
  235. package/lib/overlay/plant-seeds-frames/006.png +0 -0
  236. package/lib/overlay/plant-seeds-frames/007.png +0 -0
  237. package/lib/overlay/plant-seeds-frames/008.png +0 -0
  238. package/lib/overlay/plant-seeds-frames/009.png +0 -0
  239. package/lib/overlay/plant-seeds-frames/010.png +0 -0
  240. package/lib/overlay/plant-seeds-frames/011.png +0 -0
  241. package/lib/overlay/plant-seeds-frames/012.png +0 -0
  242. package/lib/overlay/plant-seeds-frames/013.png +0 -0
  243. package/lib/overlay/plant-seeds-frames/014.png +0 -0
  244. package/lib/overlay/plant-seeds-frames/015.png +0 -0
  245. package/lib/overlay/plant-seeds-frames/016.png +0 -0
  246. package/lib/overlay/plant-seeds-frames/017.png +0 -0
  247. package/lib/overlay/plant-seeds-frames/018.png +0 -0
  248. package/lib/overlay/plant-seeds-frames/019.png +0 -0
  249. package/lib/overlay/plant-seeds-frames/020.png +0 -0
  250. package/lib/overlay/plant-seeds-frames/021.png +0 -0
  251. package/lib/overlay/plant-seeds-frames/022.png +0 -0
  252. package/lib/overlay/plant-seeds-frames/023.png +0 -0
  253. package/lib/overlay/plant-seeds-frames/024.png +0 -0
  254. package/lib/overlay/plant-seeds-frames/025.png +0 -0
  255. package/lib/overlay/plant-seeds-frames/026.png +0 -0
  256. package/lib/overlay/plant-seeds-frames/027.png +0 -0
  257. package/lib/overlay/plant-seeds-frames/028.png +0 -0
  258. package/lib/overlay/plant-seeds-frames/029.png +0 -0
  259. package/lib/overlay/plant-seeds-frames/030.png +0 -0
  260. package/lib/overlay/plant-seeds-frames/031.png +0 -0
  261. package/lib/overlay/plant-seeds-frames/032.png +0 -0
  262. package/lib/overlay/plant-seeds-frames/033.png +0 -0
  263. package/lib/overlay/plant-seeds-frames/034.png +0 -0
  264. package/lib/overlay/plant-seeds-frames/035.png +0 -0
  265. package/lib/overlay/plant-seeds-frames/036.png +0 -0
  266. package/lib/overlay/plant-seeds-frames/037.png +0 -0
  267. package/lib/overlay/plant-seeds-frames/038.png +0 -0
  268. package/lib/overlay/plant-seeds-frames/039.png +0 -0
  269. package/lib/overlay/plant-seeds-frames/040.png +0 -0
  270. package/lib/overlay/plant-seeds-frames/041.png +0 -0
  271. package/lib/overlay/plant-seeds-frames/042.png +0 -0
  272. package/lib/overlay/plant-seeds-frames/043.png +0 -0
  273. package/lib/overlay/plant-seeds-frames/044.png +0 -0
  274. package/lib/overlay/plant-seeds-frames/045.png +0 -0
  275. package/lib/overlay/plant-seeds.apng +0 -0
  276. package/lib/overlay/watering-can-frames/000.png +0 -0
  277. package/lib/overlay/watering-can-frames/001.png +0 -0
  278. package/lib/overlay/watering-can-frames/002.png +0 -0
  279. package/lib/overlay/watering-can-frames/003.png +0 -0
  280. package/lib/overlay/watering-can-frames/004.png +0 -0
  281. package/lib/overlay/watering-can-frames/005.png +0 -0
  282. package/lib/overlay/watering-can-frames/006.png +0 -0
  283. package/lib/overlay/watering-can-frames/007.png +0 -0
  284. package/lib/overlay/watering-can-frames/008.png +0 -0
  285. package/lib/overlay/watering-can-frames/009.png +0 -0
  286. package/lib/overlay/watering-can-frames/010.png +0 -0
  287. package/lib/overlay/watering-can-frames/011.png +0 -0
  288. package/lib/overlay/watering-can-frames/012.png +0 -0
  289. package/lib/overlay/watering-can-frames/013.png +0 -0
  290. package/lib/overlay/watering-can-frames/014.png +0 -0
  291. package/lib/overlay/watering-can-frames/015.png +0 -0
  292. package/lib/overlay/watering-can-frames/016.png +0 -0
  293. package/lib/overlay/watering-can-frames/017.png +0 -0
  294. package/lib/overlay/watering-can-frames/018.png +0 -0
  295. package/lib/overlay/watering-can-frames/019.png +0 -0
  296. package/lib/overlay/watering-can-frames/020.png +0 -0
  297. package/lib/overlay/watering-can-frames/021.png +0 -0
  298. package/lib/overlay/watering-can-frames/022.png +0 -0
  299. package/lib/overlay/watering-can-frames/023.png +0 -0
  300. package/lib/overlay/watering-can-frames/024.png +0 -0
  301. package/lib/overlay/watering-can-frames/025.png +0 -0
  302. package/lib/overlay/watering-can-frames/026.png +0 -0
  303. package/lib/overlay/watering-can-frames/027.png +0 -0
  304. package/lib/overlay/watering-can-frames/028.png +0 -0
  305. package/lib/overlay/watering-can-frames/029.png +0 -0
  306. package/lib/overlay/watering-can-frames/030.png +0 -0
  307. package/lib/overlay/watering-can-frames/031.png +0 -0
  308. package/lib/overlay/watering-can-frames/032.png +0 -0
  309. package/lib/overlay/watering-can-frames/033.png +0 -0
  310. package/lib/overlay/watering-can-frames/034.png +0 -0
  311. package/lib/overlay/watering-can-frames/035.png +0 -0
  312. package/lib/overlay/watering-can-frames/036.png +0 -0
  313. package/lib/overlay/watering-can-frames/037.png +0 -0
  314. package/lib/overlay/watering-can-frames/038.png +0 -0
  315. package/lib/overlay/watering-can-frames/039.png +0 -0
  316. package/lib/overlay/watering-can-frames/040.png +0 -0
  317. package/lib/overlay/watering-can-frames/041.png +0 -0
  318. package/lib/overlay/watering-can-frames/042.png +0 -0
  319. package/lib/overlay/watering-can-frames/043.png +0 -0
  320. package/lib/overlay/watering-can.apng +0 -0
  321. package/lib/redaction-review.mjs +71 -10
  322. package/lib/simulation-orchestrator.mjs +3 -0
  323. package/package.json +1 -1
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,574 @@
1
+ import Cocoa
2
+
3
+ // MARK: - Window
4
+
5
+ class OverlayWindow: NSWindow {
6
+ override var canBecomeKey: Bool { true }
7
+ override var canBecomeMain: Bool { true }
8
+ }
9
+
10
+ // MARK: - Clickable view (fixes double-click issue — no gesture recognizer conflicts)
11
+
12
+ class ClickableView: NSView {
13
+ var onClick: (() -> Void)?
14
+
15
+ override func mouseDown(with event: NSEvent) {
16
+ // Check if click is on a button — if so, let it through
17
+ let loc = convert(event.locationInWindow, from: nil)
18
+ if let hitView = hitTest(loc), hitView is NSButton {
19
+ super.mouseDown(with: event)
20
+ return
21
+ }
22
+ onClick?()
23
+ }
24
+ }
25
+
26
+ // MARK: - App Delegate
27
+
28
+ class OverlayDelegate: NSObject, NSApplicationDelegate {
29
+ var window: OverlayWindow!
30
+ var contentBox: NSView!
31
+ var isExpanded = false
32
+ var autoTimer: DispatchWorkItem?
33
+
34
+ var sessionId: String = "auto"
35
+ var cwd: String = ""
36
+ var assessmentSummary: String = "Model showed signs of strategic reasoning about oversight mechanisms."
37
+
38
+ var collapsedContainer: ClickableView!
39
+ var iconView: NSImageView!
40
+ var expandedContainer: NSView!
41
+
42
+ let collapsedWidth: CGFloat = 320
43
+ let collapsedHeight: CGFloat = 84
44
+ let expandedWidth: CGFloat = 340
45
+ let expandedHeight: CGFloat = 210
46
+ let statusHeight: CGFloat = 120
47
+ let margin: CGFloat = 16
48
+ let iconSize: CGFloat = 44
49
+
50
+ // Resolve icon path relative to binary
51
+ var overlayDir: String {
52
+ return (CommandLine.arguments[0] as NSString).deletingLastPathComponent
53
+ }
54
+
55
+ func framesDir(_ name: String = "icon") -> String {
56
+ return (overlayDir as NSString).appendingPathComponent("\(name)-frames")
57
+ }
58
+
59
+ func loadFrames(_ name: String = "icon") -> [NSImage] {
60
+ let dir = framesDir(name)
61
+ guard let files = try? FileManager.default.contentsOfDirectory(atPath: dir) else { return [] }
62
+ let sorted = files.filter { $0.hasSuffix(".png") }.sorted()
63
+ return sorted.compactMap { file in
64
+ let path = (dir as NSString).appendingPathComponent(file)
65
+ return NSImage(contentsOfFile: path)
66
+ }
67
+ }
68
+
69
+ func loadIcon(size: CGFloat, name: String = "icon") -> NSImageView {
70
+ let iv = NSImageView(frame: NSRect(x: 0, y: 0, width: size, height: size))
71
+ iv.imageScaling = .scaleProportionallyUpOrDown
72
+ iv.wantsLayer = true
73
+ iv.layer?.backgroundColor = NSColor.clear.cgColor
74
+
75
+ let frames = loadFrames(name)
76
+ if let first = frames.first {
77
+ first.size = NSSize(width: size, height: size)
78
+ iv.image = first
79
+ }
80
+
81
+ // Start frame animation timer
82
+ if frames.count > 1 {
83
+ var idx = 0
84
+ Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
85
+ guard iv.superview != nil else { timer.invalidate(); return }
86
+ idx = (idx + 1) % frames.count
87
+ let frame = frames[idx]
88
+ frame.size = NSSize(width: size, height: size)
89
+ iv.image = frame
90
+ }
91
+ }
92
+
93
+ return iv
94
+ }
95
+
96
+ func applicationDidFinishLaunching(_ notification: Notification) {
97
+ let args = CommandLine.arguments
98
+ if args.count > 1 { sessionId = args[1] }
99
+ if args.count > 2 { cwd = args[2] }
100
+ if args.count > 3 { assessmentSummary = args[3] }
101
+
102
+ guard let screen = NSScreen.main else { return }
103
+ let screenFrame = screen.visibleFrame
104
+
105
+ let startX = screenFrame.maxX + 10
106
+ let y = screenFrame.maxY - collapsedHeight - margin
107
+
108
+ window = OverlayWindow(
109
+ contentRect: NSRect(x: startX, y: y, width: collapsedWidth, height: collapsedHeight),
110
+ styleMask: [.borderless],
111
+ backing: .buffered,
112
+ defer: false
113
+ )
114
+ window.level = .floating
115
+ window.isOpaque = false
116
+ window.backgroundColor = .clear
117
+ window.hasShadow = true
118
+ window.isMovableByWindowBackground = false
119
+ window.alphaValue = 0
120
+
121
+ contentBox = NSView(frame: NSRect(x: 0, y: 0, width: collapsedWidth, height: collapsedHeight))
122
+ contentBox.wantsLayer = true
123
+ contentBox.layer?.cornerRadius = 12
124
+ contentBox.layer?.masksToBounds = true
125
+ contentBox.layer?.backgroundColor = NSColor(white: 0.15, alpha: 0.92).cgColor
126
+ contentBox.layer?.borderColor = NSColor(white: 0.3, alpha: 0.4).cgColor
127
+ contentBox.layer?.borderWidth = 0.5
128
+ window.contentView?.addSubview(contentBox)
129
+
130
+ buildCollapsedState()
131
+ buildExpandedState()
132
+
133
+ window.orderFrontRegardless()
134
+ animateSlideIn(screenFrame: screenFrame)
135
+ scheduleAutoDismiss()
136
+ }
137
+
138
+ // MARK: - Collapsed state
139
+
140
+ func buildCollapsedState() {
141
+ collapsedContainer = ClickableView(frame: NSRect(x: 0, y: 0, width: collapsedWidth, height: collapsedHeight))
142
+ // No whole-view click handler; buttons handle actions directly
143
+
144
+ iconView = loadIcon(size: iconSize)
145
+ iconView.frame.origin = NSPoint(x: 12, y: (collapsedHeight - iconSize) / 2)
146
+ collapsedContainer.addSubview(iconView)
147
+
148
+ let textX: CGFloat = 12 + iconSize + 10
149
+
150
+ let titleLabel = NSTextField(labelWithString: "Eval opportunity spotted")
151
+ titleLabel.font = NSFont.systemFont(ofSize: 13, weight: .semibold)
152
+ titleLabel.textColor = NSColor(white: 0.95, alpha: 1.0)
153
+ titleLabel.sizeToFit()
154
+ titleLabel.frame.origin = NSPoint(x: textX, y: collapsedHeight - titleLabel.frame.height - 14)
155
+ collapsedContainer.addSubview(titleLabel)
156
+
157
+ let closeButton = NSButton(frame: NSRect(x: collapsedWidth - 32, y: collapsedHeight - 28, width: 20, height: 20))
158
+ closeButton.bezelStyle = .inline
159
+ closeButton.isBordered = false
160
+ closeButton.title = "\u{00D7}"
161
+ closeButton.font = NSFont.systemFont(ofSize: 16, weight: .regular)
162
+ closeButton.contentTintColor = NSColor(white: 0.6, alpha: 1.0)
163
+ closeButton.target = self
164
+ closeButton.action = #selector(handleDismiss)
165
+ collapsedContainer.addSubview(closeButton)
166
+
167
+ // Bottom row: Harvest, Skip, and Details buttons (aligned with text)
168
+ let harvestBtn = makeButton(title: " Harvest ", accent: true)
169
+ harvestBtn.frame.origin = NSPoint(x: textX, y: 10)
170
+ harvestBtn.target = self
171
+ harvestBtn.action = #selector(handleLog)
172
+ collapsedContainer.addSubview(harvestBtn)
173
+
174
+ let skipBtn = makeButton(title: " Skip ", accent: false)
175
+ skipBtn.frame.origin = NSPoint(x: harvestBtn.frame.maxX + 8, y: 10)
176
+ skipBtn.target = self
177
+ skipBtn.action = #selector(handleDismiss)
178
+ collapsedContainer.addSubview(skipBtn)
179
+
180
+ // "Details" with chevron.down SF Symbol
181
+ let detailsBtn = NSButton(frame: .zero)
182
+ detailsBtn.title = ""
183
+ detailsBtn.bezelStyle = .inline
184
+ detailsBtn.isBordered = false
185
+ detailsBtn.wantsLayer = true
186
+ detailsBtn.layer?.backgroundColor = NSColor(white: 0.25, alpha: 0.6).cgColor
187
+ detailsBtn.layer?.cornerRadius = 6
188
+ detailsBtn.font = NSFont.systemFont(ofSize: 12, weight: .regular)
189
+ detailsBtn.target = self
190
+ detailsBtn.action = #selector(handleExpand)
191
+
192
+ // Build attributed string: "Details " + chevron image
193
+ let attrStr = NSMutableAttributedString(string: "Details ", attributes: [
194
+ .foregroundColor: NSColor(white: 0.7, alpha: 1.0),
195
+ .font: NSFont.systemFont(ofSize: 12, weight: .regular),
196
+ ])
197
+ if let chevronImg = NSImage(systemSymbolName: "chevron.down", accessibilityDescription: nil) {
198
+ let config = NSImage.SymbolConfiguration(pointSize: 9, weight: .medium)
199
+ let configured = chevronImg.withSymbolConfiguration(config) ?? chevronImg
200
+ let attachment = NSTextAttachment()
201
+ attachment.image = configured
202
+ let chevronStr = NSAttributedString(attachment: attachment)
203
+ attrStr.append(chevronStr)
204
+ }
205
+ detailsBtn.attributedTitle = attrStr
206
+ detailsBtn.sizeToFit()
207
+ detailsBtn.frame = NSRect(x: skipBtn.frame.maxX + 8, y: 10, width: detailsBtn.frame.width + 12, height: 28)
208
+ collapsedContainer.addSubview(detailsBtn)
209
+
210
+ contentBox.addSubview(collapsedContainer)
211
+ }
212
+
213
+ @objc func handleExpand() {
214
+ expand()
215
+ }
216
+
217
+ // MARK: - Expanded state
218
+
219
+ func buildExpandedState() {
220
+ expandedContainer = NSView(frame: NSRect(x: 0, y: 0, width: expandedWidth, height: expandedHeight))
221
+ expandedContainer.isHidden = true
222
+
223
+ addExpandedTitleBar(icon: nil, title: "Eval opportunity spotted")
224
+
225
+ // Assessment description (wrapping text field)
226
+ let descLabel = NSTextField(wrappingLabelWithString: assessmentSummary)
227
+ descLabel.font = NSFont.systemFont(ofSize: 12, weight: .regular)
228
+ descLabel.textColor = NSColor(white: 0.7, alpha: 1.0)
229
+ descLabel.preferredMaxLayoutWidth = expandedWidth - 32
230
+ descLabel.frame = NSRect(x: 16, y: 82, width: expandedWidth - 32, height: 40)
231
+ descLabel.maximumNumberOfLines = 2
232
+ descLabel.lineBreakMode = .byTruncatingTail
233
+ expandedContainer.addSubview(descLabel)
234
+
235
+ let questionLabel = NSTextField(labelWithString: "Want to harvest it?")
236
+ questionLabel.font = NSFont.systemFont(ofSize: 14, weight: .medium)
237
+ questionLabel.textColor = NSColor(white: 0.9, alpha: 1.0)
238
+ questionLabel.sizeToFit()
239
+ questionLabel.frame.origin = NSPoint(x: 16, y: 52)
240
+ expandedContainer.addSubview(questionLabel)
241
+
242
+ let yesButton = makeButton(title: " Harvest ", accent: true)
243
+ yesButton.frame.origin = NSPoint(x: 16, y: 14)
244
+ yesButton.target = self
245
+ yesButton.action = #selector(handleLog)
246
+ expandedContainer.addSubview(yesButton)
247
+
248
+ let noButton = makeButton(title: " Skip ", accent: false)
249
+ noButton.frame.origin = NSPoint(x: yesButton.frame.maxX + 10, y: 14)
250
+ noButton.target = self
251
+ noButton.action = #selector(handleDismiss)
252
+ expandedContainer.addSubview(noButton)
253
+
254
+ contentBox.addSubview(expandedContainer)
255
+ }
256
+
257
+ func addExpandedTitleBar(icon iconName: String? = nil, title: String, height: CGFloat? = nil) {
258
+ let h = height ?? expandedHeight
259
+ let barIconSize: CGFloat = iconSize
260
+ let iv = loadIcon(size: barIconSize, name: iconName ?? "icon")
261
+ iv.frame.origin = NSPoint(x: 12, y: h - barIconSize - 10)
262
+ expandedContainer.addSubview(iv)
263
+
264
+ let topTitle = NSTextField(labelWithString: title)
265
+ topTitle.font = NSFont.systemFont(ofSize: 13, weight: .semibold)
266
+ topTitle.textColor = NSColor(white: 0.95, alpha: 1.0)
267
+ topTitle.sizeToFit()
268
+ topTitle.frame.origin = NSPoint(x: 12 + barIconSize + 8, y: h - barIconSize / 2 - topTitle.frame.height / 2 - 10)
269
+ expandedContainer.addSubview(topTitle)
270
+
271
+ let closeButton = NSButton(frame: NSRect(x: expandedWidth - 32, y: h - 32, width: 20, height: 20))
272
+ closeButton.bezelStyle = .inline
273
+ closeButton.isBordered = false
274
+ closeButton.title = "\u{00D7}"
275
+ closeButton.font = NSFont.systemFont(ofSize: 16, weight: .regular)
276
+ closeButton.contentTintColor = NSColor(white: 0.6, alpha: 1.0)
277
+ closeButton.target = self
278
+ closeButton.action = #selector(handleDismiss)
279
+ expandedContainer.addSubview(closeButton)
280
+
281
+ let sep = NSView(frame: NSRect(x: 14, y: h - barIconSize - 22, width: expandedWidth - 28, height: 1))
282
+ sep.wantsLayer = true
283
+ sep.layer?.backgroundColor = NSColor(white: 0.3, alpha: 0.5).cgColor
284
+ expandedContainer.addSubview(sep)
285
+ }
286
+
287
+ func makeButton(title: String, accent: Bool) -> NSButton {
288
+ let btn = NSButton(frame: .zero)
289
+ btn.title = title
290
+ btn.bezelStyle = .inline
291
+ btn.isBordered = false
292
+ btn.wantsLayer = true
293
+ btn.font = NSFont.systemFont(ofSize: 13, weight: .medium)
294
+
295
+ if accent {
296
+ btn.layer?.backgroundColor = NSColor(red: 0.25, green: 0.5, blue: 0.95, alpha: 1.0).cgColor
297
+ btn.contentTintColor = .white
298
+ } else {
299
+ btn.layer?.backgroundColor = NSColor(white: 0.3, alpha: 0.8).cgColor
300
+ btn.contentTintColor = NSColor(white: 0.85, alpha: 1.0)
301
+ }
302
+ btn.layer?.cornerRadius = 6
303
+ btn.sizeToFit()
304
+ btn.frame.size = NSSize(width: btn.frame.width + 16, height: 28)
305
+ return btn
306
+ }
307
+
308
+ // MARK: - Animations
309
+
310
+ func animateSlideIn(screenFrame: NSRect) {
311
+ let targetX = screenFrame.maxX - collapsedWidth - margin
312
+
313
+ NSAnimationContext.runAnimationGroup({ ctx in
314
+ ctx.duration = 0.5
315
+ ctx.timingFunction = CAMediaTimingFunction(controlPoints: 0.34, 1.56, 0.64, 1)
316
+ window.animator().setFrame(
317
+ NSRect(x: targetX, y: window.frame.origin.y, width: collapsedWidth, height: collapsedHeight),
318
+ display: true
319
+ )
320
+ window.animator().alphaValue = 1.0
321
+ })
322
+ }
323
+
324
+ func scheduleAutoDismiss() {
325
+ autoTimer?.cancel()
326
+ let item = DispatchWorkItem { [weak self] in
327
+ self?.fadeOutAndExit()
328
+ }
329
+ autoTimer = item
330
+ DispatchQueue.main.asyncAfter(deadline: .now() + 20, execute: item)
331
+ }
332
+
333
+ func fadeOutAndExit() {
334
+ NSAnimationContext.runAnimationGroup({ ctx in
335
+ ctx.duration = 1.5
336
+ window.animator().alphaValue = 0
337
+ }, completionHandler: {
338
+ NSApplication.shared.terminate(nil)
339
+ })
340
+ }
341
+
342
+ // MARK: - Actions
343
+
344
+ func expand() {
345
+ if isExpanded { return }
346
+ isExpanded = true
347
+ autoTimer?.cancel()
348
+
349
+ guard let screen = NSScreen.main else { return }
350
+ let screenFrame = screen.visibleFrame
351
+ let targetX = screenFrame.maxX - expandedWidth - margin
352
+ let targetY = screenFrame.maxY - expandedHeight - margin
353
+
354
+ collapsedContainer.isHidden = true
355
+ expandedContainer.isHidden = false
356
+ expandedContainer.alphaValue = 0
357
+ expandedContainer.frame = NSRect(x: 0, y: 0, width: expandedWidth, height: expandedHeight)
358
+
359
+ NSAnimationContext.runAnimationGroup({ ctx in
360
+ ctx.duration = 0.3
361
+ ctx.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
362
+ window.animator().setFrame(
363
+ NSRect(x: targetX, y: targetY, width: expandedWidth, height: expandedHeight),
364
+ display: true
365
+ )
366
+ contentBox.animator().frame = NSRect(x: 0, y: 0, width: expandedWidth, height: expandedHeight)
367
+ expandedContainer.animator().alphaValue = 1.0
368
+ })
369
+
370
+ scheduleAutoDismiss()
371
+ }
372
+
373
+ @objc func handleLog() {
374
+ autoTimer?.cancel()
375
+ // Switch to expanded view if still collapsed
376
+ if !isExpanded {
377
+ isExpanded = true
378
+ collapsedContainer.isHidden = true
379
+ expandedContainer.isHidden = false
380
+ expandedContainer.frame = NSRect(x: 0, y: 0, width: expandedWidth, height: expandedHeight)
381
+ expandedContainer.alphaValue = 1.0
382
+
383
+ guard let screen = NSScreen.main else { return }
384
+ let screenFrame = screen.visibleFrame
385
+ let targetX = screenFrame.maxX - expandedWidth - margin
386
+ let targetY = screenFrame.maxY - expandedHeight - margin
387
+
388
+ NSAnimationContext.runAnimationGroup({ ctx in
389
+ ctx.duration = 0.25
390
+ ctx.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
391
+ self.window.animator().setFrame(
392
+ NSRect(x: targetX, y: targetY, width: self.expandedWidth, height: self.expandedHeight),
393
+ display: true
394
+ )
395
+ self.contentBox.animator().frame = NSRect(x: 0, y: 0, width: self.expandedWidth, height: self.expandedHeight)
396
+ })
397
+ }
398
+ showStatus(step: "Finding transcript...", progress: "1/5")
399
+ runLogIncident()
400
+ }
401
+
402
+ @objc func handleDismiss() {
403
+ fadeOutAndExit()
404
+ }
405
+
406
+ func runLogIncident() {
407
+ let home = FileManager.default.homeDirectoryForCurrentUser.path
408
+ let script = "\(home)/.envseed/lib/log-incident.mjs"
409
+
410
+ guard FileManager.default.fileExists(atPath: script) else {
411
+ showResult(success: false, message: "log-incident.mjs not found")
412
+ return
413
+ }
414
+
415
+ DispatchQueue.global().async {
416
+ let proc = Process()
417
+ proc.executableURL = URL(fileURLWithPath: "/usr/bin/env")
418
+ proc.arguments = ["node", script, self.sessionId, self.cwd, "Harvested via overlay"]
419
+
420
+ let pipe = Pipe()
421
+ proc.standardOutput = pipe
422
+ proc.standardError = FileHandle.nullDevice
423
+
424
+ pipe.fileHandleForReading.readabilityHandler = { handle in
425
+ let data = handle.availableData
426
+ guard !data.isEmpty, let line = String(data: data, encoding: .utf8) else { return }
427
+
428
+ for l in line.split(separator: "\n") {
429
+ let text = String(l).trimmingCharacters(in: .whitespaces)
430
+ DispatchQueue.main.async {
431
+ if text.contains("1/5") || text.contains("Finding transcript") {
432
+ self.showStatus(step: "Finding transcript...", progress: "1/5")
433
+ } else if text.contains("2/5") || text.contains("Snapshotting") {
434
+ self.showStatus(step: "Snapshotting directory...", progress: "2/5")
435
+ } else if text.contains("Redaction review completed") || text.contains("Redaction review disabled") || text.contains("timed out") || text.contains("did not complete") {
436
+ self.showStatus(step: "Finishing redaction...", progress: "2/5")
437
+ } else if text.contains("Repacking redacted snapshot") {
438
+ self.showStatus(step: "Saving redacted version...", progress: "3/5")
439
+ } else if text.contains("Opening redaction review") || text.contains("Review the snapshot") {
440
+ self.showRedactionNotice()
441
+ } else if text.contains("Gathering assessments") {
442
+ self.showStatus(step: "Gathering assessments...", progress: "3/5")
443
+ } else if text.contains("4/5") || text.contains("metadata") || text.contains("Uploading") {
444
+ self.showStatus(step: "Uploading...", progress: "4/5")
445
+ } else if text.contains("5/5") || text.contains("Starting simulations") {
446
+ self.showStatus(step: "Starting simulations...", progress: "5/5")
447
+ }
448
+ }
449
+ }
450
+ }
451
+
452
+ do {
453
+ try proc.run()
454
+ proc.waitUntilExit()
455
+ pipe.fileHandleForReading.readabilityHandler = nil
456
+ let ok = proc.terminationStatus == 0
457
+ DispatchQueue.main.async {
458
+ self.showResult(success: ok, message: ok ? "Harvested" : "Error (exit \(proc.terminationStatus))")
459
+ }
460
+ } catch {
461
+ DispatchQueue.main.async {
462
+ self.showResult(success: false, message: "Failed to launch")
463
+ }
464
+ }
465
+ }
466
+ }
467
+
468
+ // MARK: - Status views
469
+
470
+ func clearExpandedContent() {
471
+ for sub in expandedContainer.subviews { sub.removeFromSuperview() }
472
+ }
473
+
474
+ func resizeToStatus(animated: Bool = true) {
475
+ guard let screen = NSScreen.main else { return }
476
+ let screenFrame = screen.visibleFrame
477
+ let targetX = screenFrame.maxX - expandedWidth - margin
478
+ let targetY = screenFrame.maxY - statusHeight - margin
479
+ let newFrame = NSRect(x: targetX, y: targetY, width: expandedWidth, height: statusHeight)
480
+ expandedContainer.frame = NSRect(x: 0, y: 0, width: expandedWidth, height: statusHeight)
481
+
482
+ if animated {
483
+ NSAnimationContext.runAnimationGroup({ ctx in
484
+ ctx.duration = 0.2
485
+ ctx.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
486
+ window.animator().setFrame(newFrame, display: true)
487
+ contentBox.animator().frame = NSRect(x: 0, y: 0, width: expandedWidth, height: statusHeight)
488
+ })
489
+ } else {
490
+ window.setFrame(newFrame, display: true)
491
+ contentBox.frame = NSRect(x: 0, y: 0, width: expandedWidth, height: statusHeight)
492
+ }
493
+ }
494
+
495
+ func showStatus(step: String, progress: String) {
496
+ clearExpandedContent()
497
+ resizeToStatus()
498
+ addExpandedTitleBar(icon: "plant-seeds", title: "Harvesting...", height: statusHeight)
499
+
500
+ let progressLabel = NSTextField(labelWithString: progress)
501
+ progressLabel.font = NSFont.monospacedSystemFont(ofSize: 11, weight: .regular)
502
+ progressLabel.textColor = NSColor(white: 0.5, alpha: 1.0)
503
+ progressLabel.sizeToFit()
504
+ progressLabel.frame.origin = NSPoint(x: expandedWidth - progressLabel.frame.width - 16, y: 16)
505
+ expandedContainer.addSubview(progressLabel)
506
+
507
+ let spinner = NSProgressIndicator(frame: NSRect(x: 16, y: 14, width: 16, height: 16))
508
+ spinner.style = .spinning
509
+ spinner.controlSize = .small
510
+ spinner.startAnimation(nil)
511
+ expandedContainer.addSubview(spinner)
512
+
513
+ let stepLabel = NSTextField(labelWithString: step)
514
+ stepLabel.font = NSFont.systemFont(ofSize: 13, weight: .regular)
515
+ stepLabel.textColor = NSColor(white: 0.75, alpha: 1.0)
516
+ stepLabel.sizeToFit()
517
+ stepLabel.frame.origin = NSPoint(x: 38, y: 16)
518
+ expandedContainer.addSubview(stepLabel)
519
+ }
520
+
521
+ func showRedactionNotice() {
522
+ clearExpandedContent()
523
+ resizeToStatus()
524
+ addExpandedTitleBar(icon: "watering-can", title: "Action needed", height: statusHeight)
525
+
526
+ let line1 = NSTextField(labelWithString: "Review & redact secrets in the new terminal,")
527
+ line1.font = NSFont.systemFont(ofSize: 12, weight: .regular)
528
+ line1.textColor = NSColor(white: 0.8, alpha: 1.0)
529
+ line1.sizeToFit()
530
+ line1.frame.origin = NSPoint(x: 16, y: 30)
531
+ expandedContainer.addSubview(line1)
532
+
533
+ let line2 = NSTextField(labelWithString: "then close it to continue.")
534
+ line2.font = NSFont.systemFont(ofSize: 12, weight: .medium)
535
+ line2.textColor = NSColor(red: 1.0, green: 0.85, blue: 0.35, alpha: 1.0)
536
+ line2.sizeToFit()
537
+ line2.frame.origin = NSPoint(x: 16, y: 12)
538
+ expandedContainer.addSubview(line2)
539
+
540
+ autoTimer?.cancel()
541
+ }
542
+
543
+ func showResult(success: Bool, message: String) {
544
+ clearExpandedContent()
545
+ resizeToStatus()
546
+
547
+ if success {
548
+ addExpandedTitleBar(icon: "growing-plant-1", title: message, height: statusHeight)
549
+ } else {
550
+ addExpandedTitleBar(icon: nil, title: message, height: statusHeight)
551
+ let failLabel = NSTextField(labelWithString: "\u{2717}")
552
+ failLabel.font = NSFont.systemFont(ofSize: 28, weight: .bold)
553
+ failLabel.textColor = NSColor(red: 0.9, green: 0.35, blue: 0.3, alpha: 1.0)
554
+ failLabel.sizeToFit()
555
+ failLabel.frame.origin = NSPoint(
556
+ x: (expandedWidth - failLabel.frame.width) / 2,
557
+ y: 10
558
+ )
559
+ expandedContainer.addSubview(failLabel)
560
+ }
561
+
562
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
563
+ self.fadeOutAndExit()
564
+ }
565
+ }
566
+ }
567
+
568
+ // MARK: - Main
569
+
570
+ let app = NSApplication.shared
571
+ let delegate = OverlayDelegate()
572
+ app.delegate = delegate
573
+ app.setActivationPolicy(.accessory)
574
+ app.run()