@raidiant/notifai 11.7.1-beta.2 → 12.0.0-beta.11

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 (409) hide show
  1. package/CHANGELOG.md +61 -4
  2. package/NOTICE +16 -0
  3. package/THIRD_PARTY_NOTICES +725 -0
  4. package/bin/notifai.mjs +11420 -0
  5. package/data/install.ps1 +318 -0
  6. package/data/release.json +1 -0
  7. package/inventory.json +1 -0
  8. package/npm-adapter-files.json +52 -0
  9. package/package.json +14 -44
  10. package/README.md +0 -168
  11. package/dist/agent-update-notice.d.ts +0 -7
  12. package/dist/agent-update-notice.js +0 -77
  13. package/dist/agent-update-notice.js.map +0 -1
  14. package/dist/atomic-file.d.ts +0 -30
  15. package/dist/atomic-file.js +0 -148
  16. package/dist/atomic-file.js.map +0 -1
  17. package/dist/attendant-update.d.ts +0 -15
  18. package/dist/attendant-update.js +0 -96
  19. package/dist/attendant-update.js.map +0 -1
  20. package/dist/claude-wake.d.ts +0 -108
  21. package/dist/claude-wake.js +0 -369
  22. package/dist/claude-wake.js.map +0 -1
  23. package/dist/cli-bin.d.ts +0 -26
  24. package/dist/cli-bin.js +0 -220
  25. package/dist/cli-bin.js.map +0 -1
  26. package/dist/cli-contract.d.ts +0 -16
  27. package/dist/cli-contract.js +0 -27
  28. package/dist/cli-contract.js.map +0 -1
  29. package/dist/cli-release.d.ts +0 -36
  30. package/dist/cli-release.js +0 -85
  31. package/dist/cli-release.js.map +0 -1
  32. package/dist/client.d.ts +0 -109
  33. package/dist/client.js +0 -217
  34. package/dist/client.js.map +0 -1
  35. package/dist/codex-answer-control.d.ts +0 -14
  36. package/dist/codex-answer-control.js +0 -41
  37. package/dist/codex-answer-control.js.map +0 -1
  38. package/dist/codex-answer-presentation.d.ts +0 -18
  39. package/dist/codex-answer-presentation.js +0 -113
  40. package/dist/codex-answer-presentation.js.map +0 -1
  41. package/dist/codex-input-lifecycle.d.ts +0 -10
  42. package/dist/codex-input-lifecycle.js +0 -63
  43. package/dist/codex-input-lifecycle.js.map +0 -1
  44. package/dist/codex-native-control.d.ts +0 -13
  45. package/dist/codex-native-control.js +0 -120
  46. package/dist/codex-native-control.js.map +0 -1
  47. package/dist/codex-native-turn.d.ts +0 -37
  48. package/dist/codex-native-turn.js +0 -155
  49. package/dist/codex-native-turn.js.map +0 -1
  50. package/dist/codex-question-bindings.d.ts +0 -76
  51. package/dist/codex-question-bindings.js +0 -151
  52. package/dist/codex-question-bindings.js.map +0 -1
  53. package/dist/codex-queue-control.d.ts +0 -21
  54. package/dist/codex-queue-control.js +0 -59
  55. package/dist/codex-queue-control.js.map +0 -1
  56. package/dist/codex-tool-messages.d.ts +0 -7
  57. package/dist/codex-tool-messages.js +0 -126
  58. package/dist/codex-tool-messages.js.map +0 -1
  59. package/dist/codex-wake.d.ts +0 -58
  60. package/dist/codex-wake.js +0 -135
  61. package/dist/codex-wake.js.map +0 -1
  62. package/dist/command-session.d.ts +0 -14
  63. package/dist/command-session.js +0 -31
  64. package/dist/command-session.js.map +0 -1
  65. package/dist/commands-acknowledge.d.ts +0 -13
  66. package/dist/commands-acknowledge.js +0 -116
  67. package/dist/commands-acknowledge.js.map +0 -1
  68. package/dist/commands-agent-sessions.d.ts +0 -6
  69. package/dist/commands-agent-sessions.js +0 -51
  70. package/dist/commands-agent-sessions.js.map +0 -1
  71. package/dist/commands-ask.d.ts +0 -62
  72. package/dist/commands-ask.js +0 -503
  73. package/dist/commands-ask.js.map +0 -1
  74. package/dist/commands-auth.d.ts +0 -76
  75. package/dist/commands-auth.js +0 -421
  76. package/dist/commands-auth.js.map +0 -1
  77. package/dist/commands-close.d.ts +0 -6
  78. package/dist/commands-close.js +0 -273
  79. package/dist/commands-close.js.map +0 -1
  80. package/dist/commands-config.d.ts +0 -31
  81. package/dist/commands-config.js +0 -272
  82. package/dist/commands-config.js.map +0 -1
  83. package/dist/commands-core.d.ts +0 -145
  84. package/dist/commands-core.js +0 -190
  85. package/dist/commands-core.js.map +0 -1
  86. package/dist/commands-devices.d.ts +0 -41
  87. package/dist/commands-devices.js +0 -144
  88. package/dist/commands-devices.js.map +0 -1
  89. package/dist/commands-doctor.d.ts +0 -25
  90. package/dist/commands-doctor.js +0 -1553
  91. package/dist/commands-doctor.js.map +0 -1
  92. package/dist/commands-guidance.d.ts +0 -27
  93. package/dist/commands-guidance.js +0 -150
  94. package/dist/commands-guidance.js.map +0 -1
  95. package/dist/commands-harness-context.d.ts +0 -57
  96. package/dist/commands-harness-context.js +0 -102
  97. package/dist/commands-harness-context.js.map +0 -1
  98. package/dist/commands-hook-attend.d.ts +0 -70
  99. package/dist/commands-hook-attend.js +0 -655
  100. package/dist/commands-hook-attend.js.map +0 -1
  101. package/dist/commands-hook-diagnostics.d.ts +0 -37
  102. package/dist/commands-hook-diagnostics.js +0 -136
  103. package/dist/commands-hook-diagnostics.js.map +0 -1
  104. package/dist/commands-hook-install.d.ts +0 -26
  105. package/dist/commands-hook-install.js +0 -784
  106. package/dist/commands-hook-install.js.map +0 -1
  107. package/dist/commands-hook-run.d.ts +0 -29
  108. package/dist/commands-hook-run.js +0 -933
  109. package/dist/commands-hook-run.js.map +0 -1
  110. package/dist/commands-hook-shape.d.ts +0 -19
  111. package/dist/commands-hook-shape.js +0 -48
  112. package/dist/commands-hook-shape.js.map +0 -1
  113. package/dist/commands-init.d.ts +0 -42
  114. package/dist/commands-init.js +0 -901
  115. package/dist/commands-init.js.map +0 -1
  116. package/dist/commands-io.d.ts +0 -2
  117. package/dist/commands-io.js +0 -117
  118. package/dist/commands-io.js.map +0 -1
  119. package/dist/commands-logs.d.ts +0 -30
  120. package/dist/commands-logs.js +0 -201
  121. package/dist/commands-logs.js.map +0 -1
  122. package/dist/commands-native-acknowledge.d.ts +0 -8
  123. package/dist/commands-native-acknowledge.js +0 -114
  124. package/dist/commands-native-acknowledge.js.map +0 -1
  125. package/dist/commands-project.d.ts +0 -4
  126. package/dist/commands-project.js +0 -41
  127. package/dist/commands-project.js.map +0 -1
  128. package/dist/commands-receive.d.ts +0 -3
  129. package/dist/commands-receive.js +0 -59
  130. package/dist/commands-receive.js.map +0 -1
  131. package/dist/commands-send-support.d.ts +0 -59
  132. package/dist/commands-send-support.js +0 -258
  133. package/dist/commands-send-support.js.map +0 -1
  134. package/dist/commands-send.d.ts +0 -34
  135. package/dist/commands-send.js +0 -900
  136. package/dist/commands-send.js.map +0 -1
  137. package/dist/commands-setup-proof.d.ts +0 -43
  138. package/dist/commands-setup-proof.js +0 -134
  139. package/dist/commands-setup-proof.js.map +0 -1
  140. package/dist/commands-skill.d.ts +0 -21
  141. package/dist/commands-skill.js +0 -199
  142. package/dist/commands-skill.js.map +0 -1
  143. package/dist/commands-sounds.d.ts +0 -12
  144. package/dist/commands-sounds.js +0 -35
  145. package/dist/commands-sounds.js.map +0 -1
  146. package/dist/commands-update-check.d.ts +0 -6
  147. package/dist/commands-update-check.js +0 -102
  148. package/dist/commands-update-check.js.map +0 -1
  149. package/dist/commands-update-resume.d.ts +0 -7
  150. package/dist/commands-update-resume.js +0 -143
  151. package/dist/commands-update-resume.js.map +0 -1
  152. package/dist/commands-update-skill.d.ts +0 -5
  153. package/dist/commands-update-skill.js +0 -36
  154. package/dist/commands-update-skill.js.map +0 -1
  155. package/dist/commands-update.d.ts +0 -11
  156. package/dist/commands-update.js +0 -286
  157. package/dist/commands-update.js.map +0 -1
  158. package/dist/commands.d.ts +0 -26
  159. package/dist/commands.js +0 -27
  160. package/dist/commands.js.map +0 -1
  161. package/dist/config-schema.d.ts +0 -62
  162. package/dist/config-schema.js +0 -302
  163. package/dist/config-schema.js.map +0 -1
  164. package/dist/config.d.ts +0 -181
  165. package/dist/config.js +0 -357
  166. package/dist/config.js.map +0 -1
  167. package/dist/credentials.d.ts +0 -96
  168. package/dist/credentials.js +0 -342
  169. package/dist/credentials.js.map +0 -1
  170. package/dist/file-lock.d.ts +0 -34
  171. package/dist/file-lock.js +0 -367
  172. package/dist/file-lock.js.map +0 -1
  173. package/dist/generated-session-label.d.ts +0 -2
  174. package/dist/generated-session-label.js +0 -152
  175. package/dist/generated-session-label.js.map +0 -1
  176. package/dist/guidance-content.d.ts +0 -53
  177. package/dist/guidance-content.js +0 -248
  178. package/dist/guidance-content.js.map +0 -1
  179. package/dist/guidance-render.d.ts +0 -25
  180. package/dist/guidance-render.js +0 -47
  181. package/dist/guidance-render.js.map +0 -1
  182. package/dist/guidance.d.ts +0 -69
  183. package/dist/guidance.js +0 -173
  184. package/dist/guidance.js.map +0 -1
  185. package/dist/harness-session-title.d.ts +0 -22
  186. package/dist/harness-session-title.js +0 -74
  187. package/dist/harness-session-title.js.map +0 -1
  188. package/dist/harnesses.d.ts +0 -65
  189. package/dist/harnesses.js +0 -159
  190. package/dist/harnesses.js.map +0 -1
  191. package/dist/hermes-attendant.d.ts +0 -38
  192. package/dist/hermes-attendant.js +0 -337
  193. package/dist/hermes-attendant.js.map +0 -1
  194. package/dist/hermes-plugin.d.ts +0 -11
  195. package/dist/hermes-plugin.js +0 -299
  196. package/dist/hermes-plugin.js.map +0 -1
  197. package/dist/hook-acknowledgements.d.ts +0 -70
  198. package/dist/hook-acknowledgements.js +0 -490
  199. package/dist/hook-acknowledgements.js.map +0 -1
  200. package/dist/hook-adapter.d.ts +0 -48
  201. package/dist/hook-adapter.js +0 -419
  202. package/dist/hook-adapter.js.map +0 -1
  203. package/dist/hook-events.d.ts +0 -105
  204. package/dist/hook-events.js +0 -130
  205. package/dist/hook-events.js.map +0 -1
  206. package/dist/hook-gates.d.ts +0 -7
  207. package/dist/hook-gates.js +0 -20
  208. package/dist/hook-gates.js.map +0 -1
  209. package/dist/hook-input.d.ts +0 -10
  210. package/dist/hook-input.js +0 -64
  211. package/dist/hook-input.js.map +0 -1
  212. package/dist/hook-lifecycle.d.ts +0 -112
  213. package/dist/hook-lifecycle.js +0 -2031
  214. package/dist/hook-lifecycle.js.map +0 -1
  215. package/dist/hook-project-sessions.d.ts +0 -28
  216. package/dist/hook-project-sessions.js +0 -141
  217. package/dist/hook-project-sessions.js.map +0 -1
  218. package/dist/hook-question-lock.d.ts +0 -29
  219. package/dist/hook-question-lock.js +0 -217
  220. package/dist/hook-question-lock.js.map +0 -1
  221. package/dist/hook-question-retirement.d.ts +0 -74
  222. package/dist/hook-question-retirement.js +0 -314
  223. package/dist/hook-question-retirement.js.map +0 -1
  224. package/dist/hook-question-state.d.ts +0 -58
  225. package/dist/hook-question-state.js +0 -239
  226. package/dist/hook-question-state.js.map +0 -1
  227. package/dist/hook-session-state.d.ts +0 -127
  228. package/dist/hook-session-state.js +0 -488
  229. package/dist/hook-session-state.js.map +0 -1
  230. package/dist/hook-types.d.ts +0 -476
  231. package/dist/hook-types.js +0 -4
  232. package/dist/hook-types.js.map +0 -1
  233. package/dist/injection-render.d.ts +0 -36
  234. package/dist/injection-render.js +0 -61
  235. package/dist/injection-render.js.map +0 -1
  236. package/dist/install-hooks.d.ts +0 -470
  237. package/dist/install-hooks.js +0 -1622
  238. package/dist/install-hooks.js.map +0 -1
  239. package/dist/integration-health.d.ts +0 -26
  240. package/dist/integration-health.js +0 -133
  241. package/dist/integration-health.js.map +0 -1
  242. package/dist/interactive.d.ts +0 -22
  243. package/dist/interactive.js +0 -644
  244. package/dist/interactive.js.map +0 -1
  245. package/dist/invocation-context.d.ts +0 -47
  246. package/dist/invocation-context.js +0 -164
  247. package/dist/invocation-context.js.map +0 -1
  248. package/dist/local-path.d.ts +0 -7
  249. package/dist/local-path.js +0 -27
  250. package/dist/local-path.js.map +0 -1
  251. package/dist/logging.d.ts +0 -157
  252. package/dist/logging.js +0 -599
  253. package/dist/logging.js.map +0 -1
  254. package/dist/main-run.d.ts +0 -1
  255. package/dist/main-run.js +0 -42
  256. package/dist/main-run.js.map +0 -1
  257. package/dist/main.d.ts +0 -2
  258. package/dist/main.js +0 -12
  259. package/dist/main.js.map +0 -1
  260. package/dist/native-answer-operation.d.ts +0 -70
  261. package/dist/native-answer-operation.js +0 -247
  262. package/dist/native-answer-operation.js.map +0 -1
  263. package/dist/native-skills.d.ts +0 -64
  264. package/dist/native-skills.js +0 -167
  265. package/dist/native-skills.js.map +0 -1
  266. package/dist/node-floor.d.ts +0 -19
  267. package/dist/node-floor.js +0 -32
  268. package/dist/node-floor.js.map +0 -1
  269. package/dist/npm-invocation.d.ts +0 -34
  270. package/dist/npm-invocation.js +0 -53
  271. package/dist/npm-invocation.js.map +0 -1
  272. package/dist/openclaw-continuation-bridge.d.ts +0 -7
  273. package/dist/openclaw-continuation-bridge.js +0 -64
  274. package/dist/openclaw-continuation-bridge.js.map +0 -1
  275. package/dist/openclaw-gateway-readiness.d.ts +0 -1
  276. package/dist/openclaw-gateway-readiness.js +0 -24
  277. package/dist/openclaw-gateway-readiness.js.map +0 -1
  278. package/dist/openclaw-generation.d.ts +0 -21
  279. package/dist/openclaw-generation.js +0 -115
  280. package/dist/openclaw-generation.js.map +0 -1
  281. package/dist/openclaw-message-bridge.d.ts +0 -8
  282. package/dist/openclaw-message-bridge.js +0 -93
  283. package/dist/openclaw-message-bridge.js.map +0 -1
  284. package/dist/openclaw-pending.d.ts +0 -8
  285. package/dist/openclaw-pending.js +0 -47
  286. package/dist/openclaw-pending.js.map +0 -1
  287. package/dist/openclaw-plugin.d.ts +0 -75
  288. package/dist/openclaw-plugin.js +0 -1353
  289. package/dist/openclaw-plugin.js.map +0 -1
  290. package/dist/openclaw-session-access.d.ts +0 -12
  291. package/dist/openclaw-session-access.js +0 -79
  292. package/dist/openclaw-session-access.js.map +0 -1
  293. package/dist/opencode-plugin.d.ts +0 -88
  294. package/dist/opencode-plugin.js +0 -286
  295. package/dist/opencode-plugin.js.map +0 -1
  296. package/dist/orca-session-title.d.ts +0 -11
  297. package/dist/orca-session-title.js +0 -111
  298. package/dist/orca-session-title.js.map +0 -1
  299. package/dist/pack-feedback-slice.d.ts +0 -35
  300. package/dist/pack-feedback-slice.js +0 -126
  301. package/dist/pack-feedback-slice.js.map +0 -1
  302. package/dist/pairing-qr.d.ts +0 -5
  303. package/dist/pairing-qr.js +0 -24
  304. package/dist/pairing-qr.js.map +0 -1
  305. package/dist/pending-pairing.d.ts +0 -38
  306. package/dist/pending-pairing.js +0 -70
  307. package/dist/pending-pairing.js.map +0 -1
  308. package/dist/platform.d.ts +0 -67
  309. package/dist/platform.js +0 -140
  310. package/dist/platform.js.map +0 -1
  311. package/dist/process-identity.d.ts +0 -26
  312. package/dist/process-identity.js +0 -82
  313. package/dist/process-identity.js.map +0 -1
  314. package/dist/program.d.ts +0 -59
  315. package/dist/program.js +0 -691
  316. package/dist/program.js.map +0 -1
  317. package/dist/project-enablement.d.ts +0 -21
  318. package/dist/project-enablement.js +0 -71
  319. package/dist/project-enablement.js.map +0 -1
  320. package/dist/question-settlement-process.d.ts +0 -17
  321. package/dist/question-settlement-process.js +0 -35
  322. package/dist/question-settlement-process.js.map +0 -1
  323. package/dist/question-timing.d.ts +0 -46
  324. package/dist/question-timing.js +0 -48
  325. package/dist/question-timing.js.map +0 -1
  326. package/dist/readiness.d.ts +0 -153
  327. package/dist/readiness.js +0 -149
  328. package/dist/readiness.js.map +0 -1
  329. package/dist/release.d.ts +0 -31
  330. package/dist/release.js +0 -52
  331. package/dist/release.js.map +0 -1
  332. package/dist/send-attempts.d.ts +0 -26
  333. package/dist/send-attempts.js +0 -139
  334. package/dist/send-attempts.js.map +0 -1
  335. package/dist/send.d.ts +0 -115
  336. package/dist/send.js +0 -421
  337. package/dist/send.js.map +0 -1
  338. package/dist/session-activation.d.ts +0 -13
  339. package/dist/session-activation.js +0 -64
  340. package/dist/session-activation.js.map +0 -1
  341. package/dist/session-attendant-probe.d.ts +0 -85
  342. package/dist/session-attendant-probe.js +0 -173
  343. package/dist/session-attendant-probe.js.map +0 -1
  344. package/dist/session-attendant-state.d.ts +0 -70
  345. package/dist/session-attendant-state.js +0 -256
  346. package/dist/session-attendant-state.js.map +0 -1
  347. package/dist/session-attendant.d.ts +0 -143
  348. package/dist/session-attendant.js +0 -520
  349. package/dist/session-attendant.js.map +0 -1
  350. package/dist/session-delivery.d.ts +0 -249
  351. package/dist/session-delivery.js +0 -576
  352. package/dist/session-delivery.js.map +0 -1
  353. package/dist/session-handoff.d.ts +0 -98
  354. package/dist/session-handoff.js +0 -118
  355. package/dist/session-handoff.js.map +0 -1
  356. package/dist/session-input-wakes.d.ts +0 -78
  357. package/dist/session-input-wakes.js +0 -239
  358. package/dist/session-input-wakes.js.map +0 -1
  359. package/dist/session-inputs.d.ts +0 -57
  360. package/dist/session-inputs.js +0 -434
  361. package/dist/session-inputs.js.map +0 -1
  362. package/dist/session-labels.d.ts +0 -56
  363. package/dist/session-labels.js +0 -396
  364. package/dist/session-labels.js.map +0 -1
  365. package/dist/session-message-handoff.d.ts +0 -39
  366. package/dist/session-message-handoff.js +0 -107
  367. package/dist/session-message-handoff.js.map +0 -1
  368. package/dist/setup-destinations.d.ts +0 -23
  369. package/dist/setup-destinations.js +0 -44
  370. package/dist/setup-destinations.js.map +0 -1
  371. package/dist/skill-integrity.d.ts +0 -54
  372. package/dist/skill-integrity.js +0 -164
  373. package/dist/skill-integrity.js.map +0 -1
  374. package/dist/skill-source/manifest.json +0 -40
  375. package/dist/skill-source/notifai/SKILL.md +0 -350
  376. package/dist/skill-source/notifai/references/diagnostics.md +0 -36
  377. package/dist/skill-source/notifai/references/harness-setup.md +0 -384
  378. package/dist/skill-source/notifai/references/native-questions.md +0 -51
  379. package/dist/skill-source/notifai/references/notes-and-edits.md +0 -54
  380. package/dist/skill-source/notifai/references/send-details.md +0 -14
  381. package/dist/skill-source/notifai/references/updates.md +0 -116
  382. package/dist/skill-source/notifai/references/writing-examples.md +0 -56
  383. package/dist/sound-ref.d.ts +0 -23
  384. package/dist/sound-ref.js +0 -28
  385. package/dist/sound-ref.js.map +0 -1
  386. package/dist/ui/banner.d.ts +0 -22
  387. package/dist/ui/banner.js +0 -87
  388. package/dist/ui/banner.js.map +0 -1
  389. package/dist/ui/config-view.d.ts +0 -30
  390. package/dist/ui/config-view.js +0 -108
  391. package/dist/ui/config-view.js.map +0 -1
  392. package/dist/ui/help.d.ts +0 -44
  393. package/dist/ui/help.js +0 -107
  394. package/dist/ui/help.js.map +0 -1
  395. package/dist/ui/theme.d.ts +0 -90
  396. package/dist/ui/theme.js +0 -244
  397. package/dist/ui/theme.js.map +0 -1
  398. package/dist/update-handoff.d.ts +0 -29
  399. package/dist/update-handoff.js +0 -69
  400. package/dist/update-handoff.js.map +0 -1
  401. package/dist/url-policy.d.ts +0 -85
  402. package/dist/url-policy.js +0 -369
  403. package/dist/url-policy.js.map +0 -1
  404. package/dist/version.d.ts +0 -40
  405. package/dist/version.js +0 -132
  406. package/dist/version.js.map +0 -1
  407. package/dist/wake-support.d.ts +0 -40
  408. package/dist/wake-support.js +0 -95
  409. package/dist/wake-support.js.map +0 -1
@@ -1,784 +0,0 @@
1
- /** Hook installation and uninstallation across supported harnesses. */
2
- import { existsSync, lstatSync, readFileSync, readdirSync, rmSync, rmdirSync } from 'node:fs';
3
- import path from 'node:path';
4
- import { parse as parseToml } from 'smol-toml';
5
- import { atomicWriteFileSync } from './atomic-file.js';
6
- import { EXIT } from './commands-core.js';
7
- import { stopShapeProblems } from './commands-hook-shape.js';
8
- import { withTargetFileLock } from './file-lock.js';
9
- import { HARNESS_LABELS, HOOK_INSTALLABLE_HARNESSES, } from './harnesses.js';
10
- import { installHookAdapter, isNpxAdapterTarget } from './hook-adapter.js';
11
- import { HOOK_EVENT_COMMAND_RE, HOOK_EVENT_TABLE, HOOK_EVENTS, requiredHookEvents } from './hook-events.js';
12
- import { NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS, applyPlan, buildCursorHookConfig, buildHookConfig, cleanupEmptiedCodexLayer, codexCoexistenceNotes, codexHomeNote, codexLegacyProjectLayers, codexMachineLayerPaths, codexRepresentationProblems, codexTrustProblems, detectHarness, detectedHarnesses, findInstallations, findLegacyProjectInstallations, findObsoleteNotifaiPluginWiring, handlerEvent, isTomlSettingsPath, loadCursorSettings, loadSettings, machineHookFiles, mergeCursorHooks, mergeHooks, prepareCodexPluginCleanup, preparePlan, removeCursorHooks, removeHooks, settingsFile, stripObsoleteNotifaiPluginEnablement, withCodexLayerTransaction, } from './install-hooks.js';
13
- import { OPENCLAW_PLUGIN_MANIFEST, OPENCLAW_PLUGIN_PACKAGE, enableOpenclawNotifaiConfigText, isOurOpenclawPlugin, openclawPluginLoadBlockers, openclawPluginLoadWarning, openclawConfigPath, openclawPluginManifest, openclawPluginPackage, openclawPluginSource, parseOpenclawConfig, removeOpenclawLoadPathConfigText, removeOpenclawNotifaiConfigText, } from './openclaw-plugin.js';
14
- import { isOurOpencodePlugin, opencodePluginSource } from './opencode-plugin.js';
15
- import { installHermesPlugin, preflightHermesPlugin, uninstallHermesPlugin } from './hermes-plugin.js';
16
- import { packageVersion } from './release.js';
17
- import { CLI_PACKAGE_NAME, cliPackageSpec } from './cli-contract.js';
18
- import { activeNpmCli } from './npm-invocation.js';
19
- /** True when this process is `npx` / `npm exec`, not a global or linked install. */
20
- export function runningViaNpx(env, scriptPath) {
21
- if (env['npm_command'] === 'exec')
22
- return true;
23
- return scriptPath.includes(`${path.sep}_npx${path.sep}`);
24
- }
25
- function fileHookInstallTarget(target) {
26
- if (target === undefined || isNpxAdapterTarget(target))
27
- return undefined;
28
- return target;
29
- }
30
- function resolveHookAdapterTarget(deps, flags) {
31
- if (deps.hookInstallTarget !== undefined && isNpxAdapterTarget(deps.hookInstallTarget)) {
32
- return deps.hookInstallTarget;
33
- }
34
- const fileTarget = fileHookInstallTarget(deps.hookInstallTarget);
35
- const execPath = flags.execPath ?? fileTarget?.execPath ?? process.execPath;
36
- const scriptPath = flags.scriptPath ?? fileTarget?.scriptPath ?? process.argv[1] ?? 'notifai';
37
- if (runningViaNpx(deps.env, scriptPath)) {
38
- const version = packageVersion();
39
- const npmCli = activeNpmCli(deps.env);
40
- if (version === null) {
41
- throw new Error(`Could not read this CLI version, so an npx hook target cannot be pinned. Install \`${CLI_PACKAGE_NAME}\` globally and rerun \`notifai hooks install\`.`);
42
- }
43
- if (npmCli === null) {
44
- throw new Error(`This process looks like npx but npm_execpath is missing. Install \`${CLI_PACKAGE_NAME}\` globally and rerun \`notifai hooks install\`.`);
45
- }
46
- return { kind: 'npx', execPath, npmCli, spec: cliPackageSpec(version) };
47
- }
48
- return { execPath, scriptPath };
49
- }
50
- function installationBytes(files) {
51
- return JSON.stringify(files.map(file => {
52
- try {
53
- return [file, readFileSync(file, 'utf8')];
54
- }
55
- catch {
56
- return [file, null];
57
- }
58
- }));
59
- }
60
- function printHooksInstallClose(deps, harness, file, changed = true) {
61
- const label = HARNESS_LABELS[harness];
62
- const approvalNote = harness === 'codex' && codexTrustProblems(findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform).filter(entry => entry.harness === 'codex'), deps.env).length === 0
63
- ? 'Your existing Codex hook approvals still match. ' : '';
64
- const activation = !changed
65
- ? approvalNote + 'Hook installation is unchanged; this write needs no restart. Continue the current Agent Session if its lifecycle and trust are ready; `notifai update --check --json` reports any remaining session requirement.'
66
- : harness === 'codex'
67
- ? codexTrustProblems(findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform).filter((installation) => installation.harness === 'codex'), deps.env).length === 0
68
- ? 'Your existing Codex hook approvals still match. Start one fresh Codex session, send one prompt, and run `notifai doctor`.'
69
- : 'The changed Notifai handlers need approval. Open `/hooks` in Codex and approve or enable them, then start one fresh Codex session, send one prompt, and run `notifai doctor`.'
70
- : harness === 'cursor'
71
- ? 'Start one fresh Cursor conversation, send one prompt, finish its first turn, then run `notifai doctor`.'
72
- : harness === 'opencode'
73
- ? 'Restart OpenCode, start one fresh session, send one prompt, then run `notifai doctor`.'
74
- : harness === 'openclaw'
75
- ? 'Restart the OpenClaw Gateway, start one fresh Agent Session, send one prompt, then run `notifai doctor`.'
76
- : `Start one fresh ${label} session, send one prompt, then run \`notifai doctor\`.`;
77
- if (deps.io.interactive === true && deps.io.note) {
78
- void deps.io.note(`${file}\n${activation}`, `${label} hooks installed`);
79
- return;
80
- }
81
- deps.io.out(`Installed ${harness} hooks in ${file}`);
82
- deps.io.out(activation);
83
- }
84
- export function hooksInstallCommand(deps, flags) {
85
- if (flags.harness === undefined) {
86
- const detected = detectedHarnesses(deps.cwd, deps.env);
87
- if (detected.length === 0) {
88
- deps.io.err(`Could not tell which harness you mean — pass --harness <${HOOK_INSTALLABLE_HARNESSES.join('|')}>.`);
89
- return EXIT.usage;
90
- }
91
- let ok = true;
92
- for (const harness of detected) {
93
- if (hooksInstallCommand(deps, { ...flags, harness }) !== EXIT.ok)
94
- ok = false;
95
- }
96
- return ok ? EXIT.ok : EXIT.failed;
97
- }
98
- const harness = resolveHarness(deps, flags.harness);
99
- if (!harness)
100
- return EXIT.usage;
101
- if (harness === 'hermes') {
102
- try {
103
- preflightHermesPlugin(deps.env);
104
- const target = resolveHookAdapterTarget(deps, flags);
105
- const adapter = installHookAdapter(target, deps.hookAdapterHome, deps.hookPlatform, deps.env);
106
- const file = installHermesPlugin(adapter.path, deps.env, (deps.hookPlatform ?? process.platform) === 'win32' ? target.execPath : undefined);
107
- deps.io.out(`Installed the Notifai Hermes plugin at ${file}. Start a fresh local classic CLI Agent Session; its live attendant can route questions after activation.`);
108
- return EXIT.ok;
109
- }
110
- catch (err) {
111
- deps.io.err(`Could not install Hermes plugin: ${String(err)}`);
112
- return EXIT.failed;
113
- }
114
- }
115
- const adapterTarget = resolveHookAdapterTarget(deps, flags);
116
- const scriptPath = flags.scriptPath ?? fileHookInstallTarget(adapterTarget)?.scriptPath ?? process.argv[1] ?? 'notifai';
117
- const hookPlatform = deps.hookPlatform ?? process.platform;
118
- const nodePath = adapterTarget.execPath;
119
- const codexPaths = harness === 'codex' ? codexMachineLayerPaths(deps.env, hookPlatform) : null;
120
- let adapterPath;
121
- try {
122
- // Refuse unsupported plugin configuration before even updating the adapter.
123
- // The authoritative plan is made again under the layer lock below.
124
- if (codexPaths !== null)
125
- prepareCodexPluginCleanup(codexPaths.configToml);
126
- if (harness === 'openclaw') {
127
- const configFile = openclawConfigPath(deps.env, hookPlatform);
128
- if (existsSync(configFile)) {
129
- assertOwnedRegularFile(configFile);
130
- parseOpenclawConfig(readFileSync(configFile, 'utf8'), configFile);
131
- }
132
- }
133
- adapterPath = installHookAdapter(adapterTarget, deps.hookAdapterHome, hookPlatform, deps.env).path;
134
- }
135
- catch (err) {
136
- deps.io.err(`Could not prepare hook installation: ${String(err)}`);
137
- return EXIT.failed;
138
- }
139
- // Codex is the one harness whose layer holds two candidate files, so its
140
- // transaction anchors on `config.toml` and the inspection names the target.
141
- const settingsTarget = codexPaths?.configToml ?? settingsFile(harness, deps.env, hookPlatform);
142
- const comparedFiles = codexPaths === null ? [settingsTarget] : [codexPaths.configToml, codexPaths.hooksJson];
143
- const beforeInstall = installationBytes(comparedFiles);
144
- // OpenCode's adapter is a generated plugin module rather than a handler
145
- // merged into a settings document, so it owns the whole file.
146
- if (harness === 'opencode') {
147
- return finishInstall(deps, harness, scriptPath, installOpencodePlugin(deps, settingsTarget, {
148
- adapterPath,
149
- timeoutSeconds: NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS,
150
- platform: hookPlatform,
151
- nodePath,
152
- ...(flags.narrate === undefined ? {} : { narrate: flags.narrate }),
153
- }));
154
- }
155
- if (harness === 'openclaw') {
156
- return finishInstall(deps, harness, scriptPath, installOpenclawPlugin(deps, settingsTarget, {
157
- adapterPath,
158
- timeoutSeconds: NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS,
159
- platform: hookPlatform,
160
- nodePath,
161
- ...(flags.narrate === undefined ? {} : { narrate: flags.narrate }),
162
- }));
163
- }
164
- if (harness === 'grok') {
165
- return finishInstall(deps, harness, scriptPath, installGrokHooks(deps, settingsTarget, { adapterPath, platform: hookPlatform, nodePath }, flags.narrate));
166
- }
167
- if (harness === 'cursor') {
168
- try {
169
- withTargetFileLock(settingsTarget, () => {
170
- const document = loadCursorSettings(settingsTarget);
171
- const result = mergeCursorHooks(document, buildCursorHookConfig({
172
- adapterPath,
173
- harness: 'cursor',
174
- platform: hookPlatform,
175
- nodePath,
176
- }), scriptPath);
177
- applyPlan(settingsTarget, result.document);
178
- return result;
179
- });
180
- }
181
- catch (err) {
182
- deps.io.err(String(err));
183
- return EXIT.failed;
184
- }
185
- if (flags.narrate !== false)
186
- printHooksInstallClose(deps, harness, settingsTarget, beforeInstall !== installationBytes(comparedFiles));
187
- return finishInstall(deps, harness, scriptPath, EXIT.ok);
188
- }
189
- const prepareInstall = (file, document = loadSettings(file), previous) => {
190
- const foreignStopCount = foreignStopHandlers(document).length;
191
- const result = mergeHooks(document, buildHookConfig({
192
- adapterPath,
193
- harness,
194
- platform: hookPlatform,
195
- nodePath,
196
- }), scriptPath, { preserveIdentities: harness === 'codex' });
197
- // JSON settings can carry leftover native plugin enablement in the same
198
- // document as hooks. Strip it in this write so a crash cannot leave both
199
- // firing. Codex plugin cleanup is planned and persisted first below.
200
- const next = isTomlSettingsPath(file)
201
- ? result.document
202
- : stripObsoleteNotifaiPluginEnablement(result.document).document;
203
- return { file, foreignStopCount, apply: preparePlan(file, next, previous) };
204
- };
205
- let installed;
206
- let migratedOwnedInline = false;
207
- let disabledPlugins = [];
208
- try {
209
- installed =
210
- codexPaths === null
211
- ? withTargetFileLock(settingsTarget, () => {
212
- const plan = prepareInstall(settingsTarget);
213
- plan.apply();
214
- return plan;
215
- })
216
- : withCodexLayerTransaction(codexPaths, (inspection) => {
217
- const cleanup = prepareCodexPluginCleanup(inspection.paths.configToml);
218
- const documentAfterCleanup = (file) => file === inspection.paths.configToml
219
- ? cleanup.source === null ? {} : parseToml(cleanup.source)
220
- : loadSettings(file);
221
- const previousAfterCleanup = (file) => file === inspection.paths.configToml ? cleanup.source : undefined;
222
- const target = inspection.writeTarget;
223
- const plan = prepareInstall(target, documentAfterCleanup(target), previousAfterCleanup(target));
224
- const staleTarget = target === inspection.paths.hooksJson
225
- ? inspection.paths.configToml : inspection.paths.hooksJson;
226
- const stale = removeHooks(documentAfterCleanup(staleTarget), scriptPath);
227
- const removeStale = stale.replaced.length === 0 ? null : preparePlan(staleTarget, stale.document, previousAfterCleanup(staleTarget));
228
- // All syntax/preservation plans are validated before writing. Retire
229
- // the native plugin FIRST and never restore it on a later failure:
230
- // an interruption can leave no new wiring, never two firing paths.
231
- cleanup.apply();
232
- disabledPlugins = cleanup.removed;
233
- const before = existsSync(target) ? readFileSync(target, 'utf8') : null;
234
- plan.apply();
235
- const written = readFileSync(target, 'utf8');
236
- try {
237
- removeStale?.();
238
- }
239
- catch (err) {
240
- // Roll back only our document write, never native enablement or
241
- // an external writer's configuration.
242
- if (readFileSync(target, 'utf8') === written) {
243
- if (before === null)
244
- rmSync(target, { force: true });
245
- else
246
- atomicWriteFileSync(target, before);
247
- }
248
- throw err;
249
- }
250
- migratedOwnedInline = removeStale !== null && staleTarget === inspection.paths.configToml;
251
- return plan;
252
- });
253
- }
254
- catch (err) {
255
- deps.io.err(String(err));
256
- return EXIT.failed;
257
- }
258
- if (disabledPlugins.length > 0) {
259
- deps.io.out(`Disabled leftover Notifai native plugin wiring (${disabledPlugins.join(', ')}) so it cannot fire beside document hooks`);
260
- }
261
- if (migratedOwnedInline) {
262
- deps.io.out('Moved Notifai Codex handlers from config.toml to hooks.json. Codex keys approval by source path, so open `/hooks` and approve the new handlers.');
263
- }
264
- if (flags.narrate !== false)
265
- printHooksInstallClose(deps, harness, installed.file, beforeInstall !== installationBytes(comparedFiles));
266
- if (installed.foreignStopCount > 0) {
267
- const label = HARNESS_LABELS[harness];
268
- deps.io.out(`This layer already has a Stop handler that Notifai does not own. ${label} may run it alongside Notifai's handler; Notifai preserves it but has not assessed its behavior.`);
269
- }
270
- if (harness === 'codex') {
271
- for (const problem of codexRepresentationProblems(deps.env, hookPlatform)) {
272
- deps.io.out(problem);
273
- }
274
- for (const note of codexCoexistenceNotes(deps.env, hookPlatform)) {
275
- deps.io.out(note);
276
- }
277
- const home = codexHomeNote(deps.env, hookPlatform);
278
- if (home !== null)
279
- deps.io.out(home);
280
- }
281
- return finishInstall(deps, harness, scriptPath, EXIT.ok);
282
- }
283
- /**
284
- * Clear this Project's leftover Notifai wiring, once the Machine installation
285
- * this run just wrote is proven current.
286
- *
287
- * Order matters and is the whole reason this is not part of the write above: a
288
- * legacy Project handler is still routing questions until the Machine one can
289
- * be shown to have every required event, a sound Stop shape, and no structural
290
- * problem. Removing it first would leave a window with no working route, and
291
- * removing it after a failed install would leave none at all.
292
- */
293
- function finishInstall(deps, harness, scriptPath, code) {
294
- if (code !== EXIT.ok)
295
- return code;
296
- if (!machineInstallationIsCurrent(deps, harness))
297
- return code;
298
- removeLegacyProjectInstallations(deps, harness, scriptPath);
299
- return code;
300
- }
301
- /** Whether exactly one complete, undamaged Machine installation is in place. */
302
- function machineInstallationIsCurrent(deps, harness) {
303
- const installations = findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform).filter((installation) => installation.harness === harness);
304
- if (installations.length !== 1)
305
- return false;
306
- const installation = installations[0];
307
- if ((installation.problems?.length ?? 0) > 0)
308
- return false;
309
- if (stopShapeProblems(installation, deps.hookPlatform).length > 0)
310
- return false;
311
- const installed = new Set(installation.handlers
312
- .map((handler) => handlerEvent(handler.command))
313
- .filter((event) => event !== null && HOOK_EVENTS.includes(event)));
314
- return requiredHookEvents(harness, deps.hookPlatform).every((event) => installed.has(event));
315
- }
316
- function foreignStopHandlers(document) {
317
- const groups = document.hooks?.['Stop'];
318
- if (!Array.isArray(groups))
319
- return [];
320
- return groups
321
- .flatMap((group) => group.hooks ?? [])
322
- .filter((handler) => !HOOK_EVENT_COMMAND_RE.test(handler.command));
323
- }
324
- /**
325
- * Writes the OpenCode plugin, replacing any Notifai plugin already there —
326
- * including one a different checkout wrote, matched on the managed marker for
327
- * the same reason command hooks are.
328
- */
329
- function installOpencodePlugin(deps, file, options) {
330
- const beforeInstall = installationBytes([file]);
331
- try {
332
- withTargetFileLock(file, () => {
333
- if (existsSync(file)) {
334
- assertOwnedRegularFile(file);
335
- const existing = readFileSync(file, 'utf8');
336
- if (!isOurOpencodePlugin(existing)) {
337
- throw new Error(`${file} exists and was not written by Notifai; move it aside first.`);
338
- }
339
- }
340
- atomicWriteFileSync(file, opencodePluginSource(options), {
341
- mode: 0o600,
342
- preserveMode: false,
343
- requireCurrentUserOwner: true,
344
- });
345
- });
346
- }
347
- catch (err) {
348
- deps.io.err(String(err));
349
- return EXIT.failed;
350
- }
351
- if (options.narrate !== false)
352
- printHooksInstallClose(deps, 'opencode', file, beforeInstall !== installationBytes([file]));
353
- return EXIT.ok;
354
- }
355
- /** Grok merges global hook files. This entire file is ours; leave foreign files untouched. */
356
- function installGrokHooks(deps, file, options, narrate) {
357
- const before = installationBytes([file]);
358
- try {
359
- withTargetFileLock(file, () => {
360
- if (existsSync(file)) {
361
- assertOwnedRegularFile(file);
362
- if (!isOurGrokHookFile(readFileSync(file, 'utf8'))) {
363
- throw new Error(`${file} exists and was not written by Notifai; move it aside first.`);
364
- }
365
- }
366
- atomicWriteFileSync(file, `${JSON.stringify({ hooks: buildHookConfig({
367
- adapterPath: options.adapterPath,
368
- harness: 'grok',
369
- platform: options.platform,
370
- nodePath: options.nodePath,
371
- }) }, null, 2)}\n`, {
372
- mode: 0o600,
373
- preserveMode: false,
374
- requireCurrentUserOwner: true,
375
- });
376
- });
377
- }
378
- catch (err) {
379
- deps.io.err(String(err));
380
- return EXIT.failed;
381
- }
382
- if (narrate !== false)
383
- printHooksInstallClose(deps, 'grok', file, before !== installationBytes([file]));
384
- return EXIT.ok;
385
- }
386
- function isOurGrokHookFile(source) {
387
- let parsed;
388
- try {
389
- parsed = JSON.parse(source);
390
- }
391
- catch {
392
- return false;
393
- }
394
- if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
395
- return false;
396
- const document = parsed;
397
- if (Object.keys(document).length !== 1 || typeof document['hooks'] !== 'object' || document['hooks'] === null || Array.isArray(document['hooks']))
398
- return false;
399
- const events = Object.entries(document['hooks']);
400
- const knownEvents = new Set(HOOK_EVENT_TABLE.flatMap((row) => row.document === null ? [] : [row.document]));
401
- return events.length > 0 && events.every(([event, groups]) => knownEvents.has(event) && Array.isArray(groups) && groups.length > 0 && groups.every((group) => typeof group === 'object' && group !== null && !Array.isArray(group) &&
402
- Object.keys(group).every((key) => key === 'matcher' || key === 'hooks') &&
403
- (group.matcher === undefined || typeof group.matcher === 'string') &&
404
- Array.isArray(group.hooks) && group.hooks.length > 0 && group.hooks.every((handler) => isOurGrokHookHandler(handler))));
405
- }
406
- function isOurGrokHookHandler(handler) {
407
- if (typeof handler !== 'object' || handler === null || Array.isArray(handler))
408
- return false;
409
- const fields = handler;
410
- const command = fields['command'];
411
- return Object.keys(fields).every((key) => key === 'type' || key === 'command' || key === 'timeout') &&
412
- fields['type'] === 'command' && typeof command === 'string' &&
413
- (fields['timeout'] === undefined || typeof fields['timeout'] === 'number') &&
414
- HOOK_EVENT_COMMAND_RE.test(command) && command.includes('--owner notifai --harness grok');
415
- }
416
- function installOpenclawPlugin(deps, file, options) {
417
- const pluginDir = path.dirname(file);
418
- const comparedFiles = [file, path.join(pluginDir, OPENCLAW_PLUGIN_MANIFEST), path.join(pluginDir, OPENCLAW_PLUGIN_PACKAGE), openclawConfigPath(deps.env, deps.hookPlatform)];
419
- const beforeInstall = installationBytes(comparedFiles);
420
- let blockers = [];
421
- try {
422
- withTargetFileLock(file, () => {
423
- if (existsSync(file)) {
424
- assertOwnedRegularFile(file);
425
- const existing = readFileSync(file, 'utf8');
426
- if (!isOurOpenclawPlugin(existing)) {
427
- throw new Error(`${file} exists and was not written by Notifai; move it aside first.`);
428
- }
429
- }
430
- atomicWriteFileSync(file, openclawPluginSource(options), {
431
- mode: 0o600,
432
- preserveMode: false,
433
- requireCurrentUserOwner: true,
434
- });
435
- atomicWriteFileSync(path.join(pluginDir, OPENCLAW_PLUGIN_MANIFEST), openclawPluginManifest(), {
436
- mode: 0o600,
437
- preserveMode: false,
438
- requireCurrentUserOwner: true,
439
- });
440
- atomicWriteFileSync(path.join(pluginDir, OPENCLAW_PLUGIN_PACKAGE), openclawPluginPackage(), {
441
- mode: 0o600,
442
- preserveMode: false,
443
- requireCurrentUserOwner: true,
444
- });
445
- });
446
- blockers = writeOpenclawEnablement(deps);
447
- }
448
- catch (err) {
449
- deps.io.err(String(err));
450
- return EXIT.failed;
451
- }
452
- const warning = openclawPluginLoadWarning(blockers);
453
- if (warning !== null)
454
- deps.io.err(warning);
455
- if (options.narrate !== false)
456
- printHooksInstallClose(deps, 'openclaw', file, beforeInstall !== installationBytes(comparedFiles));
457
- return EXIT.ok;
458
- }
459
- function writeOpenclawEnablement(deps) {
460
- const configFile = openclawConfigPath(deps.env, deps.hookPlatform);
461
- return withTargetFileLock(configFile, () => {
462
- if (existsSync(configFile))
463
- assertOwnedRegularFile(configFile);
464
- const source = existsSync(configFile) ? readFileSync(configFile, 'utf8') : '{}\n';
465
- const config = parseOpenclawConfig(source, configFile);
466
- const nextSource = enableOpenclawNotifaiConfigText(source, configFile);
467
- atomicWriteFileSync(configFile, nextSource, {
468
- mode: 0o600,
469
- preserveMode: true,
470
- requireCurrentUserOwner: true,
471
- });
472
- return openclawPluginLoadBlockers(config);
473
- });
474
- }
475
- function removeEmptyOpenclawPluginDirectory(pluginDir) {
476
- try {
477
- if (!lstatSync(pluginDir).isDirectory() || readdirSync(pluginDir).length !== 0)
478
- return;
479
- rmdirSync(pluginDir);
480
- }
481
- catch (err) {
482
- const code = err.code;
483
- if (code === 'ENOENT' || code === 'ENOTEMPTY' || code === 'EEXIST')
484
- return;
485
- throw err;
486
- }
487
- }
488
- /**
489
- * Delete a Notifai-written plugin module and everything generated beside it.
490
- *
491
- * Returns what happened rather than narrating, because uninstall and migration
492
- * cleanup say different things about the same outcome.
493
- */
494
- function removeNotifaiPluginFile(harness, file) {
495
- const pluginDir = path.dirname(file);
496
- const outcome = withTargetFileLock(file, () => {
497
- if (!existsSync(file))
498
- return 'absent';
499
- assertOwnedRegularFile(file);
500
- const source = readFileSync(file, 'utf8');
501
- // We own the whole file, but only if we wrote it.
502
- const ours = harness === 'openclaw' ? isOurOpenclawPlugin(source) : isOurOpencodePlugin(source);
503
- if (!ours)
504
- return 'foreign';
505
- rmSync(file, { force: true });
506
- if (harness === 'openclaw') {
507
- rmSync(path.join(pluginDir, OPENCLAW_PLUGIN_MANIFEST), { force: true });
508
- rmSync(path.join(pluginDir, OPENCLAW_PLUGIN_PACKAGE), { force: true });
509
- }
510
- return 'removed';
511
- });
512
- if (harness === 'openclaw' && outcome === 'removed') {
513
- removeEmptyOpenclawPluginDirectory(pluginDir);
514
- }
515
- return outcome;
516
- }
517
- /** Apply a minimal JSONC edit to OpenClaw's config. */
518
- function editOpenclawConfig(deps, edit) {
519
- const configFile = openclawConfigPath(deps.env, deps.hookPlatform);
520
- if (!existsSync(configFile))
521
- return;
522
- withTargetFileLock(configFile, () => {
523
- assertOwnedRegularFile(configFile);
524
- const source = readFileSync(configFile, 'utf8');
525
- const next = edit(source, configFile);
526
- if (next === source)
527
- return;
528
- atomicWriteFileSync(configFile, next, {
529
- mode: 0o600,
530
- preserveMode: true,
531
- requireCurrentUserOwner: true,
532
- });
533
- });
534
- }
535
- /** Strip only Notifai's handlers from each settings document that exists. */
536
- function stripNotifaiHandlers(files, scriptPath, cursor) {
537
- const existing = files.filter((candidate) => existsSync(candidate));
538
- const removed = [];
539
- for (const candidate of existing) {
540
- const strip = () => {
541
- if (cursor) {
542
- const result = removeCursorHooks(loadCursorSettings(candidate), scriptPath);
543
- if (result.replaced.length > 0)
544
- applyPlan(candidate, result.document);
545
- return result;
546
- }
547
- const result = removeHooks(loadSettings(candidate), scriptPath);
548
- if (isTomlSettingsPath(candidate)) {
549
- if (result.replaced.length > 0)
550
- applyPlan(candidate, result.document);
551
- return result;
552
- }
553
- const cleaned = stripObsoleteNotifaiPluginEnablement(result.document);
554
- if (result.replaced.length > 0 || cleaned.removed.length > 0) {
555
- applyPlan(candidate, cleaned.document);
556
- }
557
- return result;
558
- };
559
- const result = withTargetFileLock(candidate, strip);
560
- if (result.replaced.length > 0)
561
- removed.push({ file: candidate, events: result.replaced });
562
- }
563
- return { existing, removed };
564
- }
565
- export function hooksUninstallCommand(deps, flags) {
566
- const harness = resolveHarness(deps, flags.harness);
567
- if (!harness)
568
- return EXIT.usage;
569
- if (harness === 'hermes') {
570
- try {
571
- deps.io.out(uninstallHermesPlugin(deps.env)
572
- ? 'Removed the Notifai Hermes plugin through Hermes.'
573
- : 'No Notifai Hermes plugin is installed.');
574
- return EXIT.ok;
575
- }
576
- catch (err) {
577
- deps.io.err(`Could not remove Hermes plugin: ${String(err)}`);
578
- return EXIT.failed;
579
- }
580
- }
581
- const scriptPath = flags.scriptPath ?? process.argv[1] ?? 'notifai';
582
- const codexPaths = harness === 'codex' ? codexMachineLayerPaths(deps.env, deps.hookPlatform) : null;
583
- const file = codexPaths?.configToml ?? settingsFile(harness, deps.env, deps.hookPlatform);
584
- try {
585
- if (harness === 'openclaw') {
586
- const configFile = openclawConfigPath(deps.env, deps.hookPlatform);
587
- if (existsSync(configFile)) {
588
- assertOwnedRegularFile(configFile);
589
- parseOpenclawConfig(readFileSync(configFile, 'utf8'), configFile);
590
- }
591
- }
592
- if (harness === 'grok') {
593
- const outcome = withTargetFileLock(file, () => {
594
- if (!existsSync(file))
595
- return 'absent';
596
- assertOwnedRegularFile(file);
597
- if (!isOurGrokHookFile(readFileSync(file, 'utf8')))
598
- return 'foreign';
599
- rmSync(file, { force: true });
600
- return 'removed';
601
- });
602
- if (outcome === 'removed')
603
- deps.io.out(`Removed the Notifai Grok hooks at ${file}`);
604
- else if (outcome === 'foreign')
605
- deps.io.out(`Left ${file} alone: Notifai did not write it.`);
606
- else
607
- deps.io.out(`Nothing to remove: ${file} does not exist.`);
608
- }
609
- else if (harness === 'opencode' || harness === 'openclaw') {
610
- const outcome = removeNotifaiPluginFile(harness, file);
611
- if (outcome === 'removed') {
612
- if (harness === 'openclaw') {
613
- editOpenclawConfig(deps, (source, configFile) => removeOpenclawNotifaiConfigText(source, configFile, path.dirname(file)));
614
- }
615
- deps.io.out(`Removed the Notifai ${HARNESS_LABELS[harness]} plugin at ${file}`);
616
- }
617
- else if (outcome === 'foreign') {
618
- deps.io.out(`Left ${file} alone: Notifai did not write it.`);
619
- }
620
- else {
621
- deps.io.out(`Nothing to remove: ${file} does not exist.`);
622
- }
623
- }
624
- else if (harness === 'cursor') {
625
- const result = stripNotifaiHandlers([file], scriptPath, true);
626
- if (result.existing.length === 0) {
627
- deps.io.out(`Nothing to remove: ${file} does not exist.`);
628
- }
629
- else if (result.removed.length > 0) {
630
- for (const entry of result.removed) {
631
- deps.io.out(`Removed Notifai hooks (${entry.events.join(', ')}) from ${entry.file}`);
632
- }
633
- }
634
- else {
635
- deps.io.out(`No Notifai hooks found in ${file}`);
636
- }
637
- }
638
- else {
639
- const files = machineHookFiles(harness, deps.env, deps.hookPlatform);
640
- const result = codexPaths === null
641
- ? stripNotifaiHandlers(files, scriptPath, false)
642
- : withCodexLayerTransaction(codexPaths, (inspection) => {
643
- const cleanup = prepareCodexPluginCleanup(inspection.paths.configToml);
644
- const existing = files.filter((candidate) => existsSync(candidate));
645
- const plans = existing.map((candidate) => {
646
- const source = candidate === inspection.paths.configToml ? cleanup.source : undefined;
647
- const document = source === undefined ? loadSettings(candidate)
648
- : source === null ? {} : parseToml(source);
649
- const result = removeHooks(document, scriptPath);
650
- return {
651
- file: candidate,
652
- events: result.replaced,
653
- apply: result.replaced.length === 0 ? () => { } : preparePlan(candidate, result.document, source),
654
- };
655
- });
656
- cleanup.apply();
657
- for (const plan of plans)
658
- plan.apply();
659
- cleanupEmptiedCodexLayer(inspection.paths);
660
- return { existing, removed: plans.filter((plan) => plan.events.length > 0) };
661
- });
662
- if (result.existing.length === 0) {
663
- deps.io.out(`Nothing to remove: ${file} does not exist.`);
664
- }
665
- else if (result.removed.length > 0) {
666
- for (const entry of result.removed) {
667
- deps.io.out(`Removed Notifai hooks (${entry.events.join(', ')}) from ${entry.file}`);
668
- }
669
- }
670
- else {
671
- deps.io.out(`No Notifai hooks found in ${result.existing.join(', ')}`);
672
- }
673
- }
674
- }
675
- catch (err) {
676
- deps.io.err(String(err));
677
- return EXIT.failed;
678
- }
679
- // Uninstall means no Notifai lifecycle wiring is left anywhere this Project
680
- // can reach, not just in the one file the Machine installation used.
681
- removeLegacyProjectInstallations(deps, harness, scriptPath);
682
- const leftoverPlugin = findObsoleteNotifaiPluginWiring(deps.env, deps.hookPlatform).filter((entry) => entry.harness === harness);
683
- if (leftoverPlugin.length > 0) {
684
- deps.io.err(leftoverPlugin
685
- .map((entry) => `Obsolete native plugin wiring still enabled (${entry.keys.join(', ')}) in ${entry.file}; document hooks and a leftover plugin must never both fire. Uninstall is not complete while that plugin remains. Disable that Notifai plugin and rerun \`notifai hooks uninstall --harness ${entry.harness}\`.`)
686
- .join('\n'));
687
- return EXIT.failed;
688
- }
689
- return EXIT.ok;
690
- }
691
- /**
692
- * Remove Notifai's own Project-scoped definitions discoverable from this
693
- * Project. Foreign handlers sharing those files are never touched, and a file
694
- * Notifai did not write is never deleted.
695
- */
696
- export function removeLegacyProjectInstallations(deps, harness, scriptPath) {
697
- const legacy = findLegacyProjectInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform).filter((installation) => installation.harness === harness);
698
- if (legacy.length === 0)
699
- return;
700
- try {
701
- if (harness === 'opencode' || harness === 'openclaw') {
702
- for (const installation of legacy) {
703
- if (removeNotifaiPluginFile(harness, installation.file) !== 'removed')
704
- continue;
705
- if (harness === 'openclaw') {
706
- editOpenclawConfig(deps, (source, configFile) => removeOpenclawLoadPathConfigText(source, configFile, path.dirname(installation.file)));
707
- }
708
- deps.io.out(`Removed the leftover Project-scoped Notifai ${HARNESS_LABELS[harness]} plugin at ${installation.file}`);
709
- }
710
- return;
711
- }
712
- const files = legacy.map((installation) => installation.file);
713
- const result = stripNotifaiHandlers(files, scriptPath, harness === 'cursor');
714
- for (const entry of result.removed) {
715
- deps.io.out(`Removed leftover Project-scoped Notifai hooks (${entry.events.join(', ')}) from ${entry.file}`);
716
- }
717
- if (harness === 'codex') {
718
- for (const paths of codexLegacyProjectLayers(deps.cwd))
719
- cleanupEmptiedCodexLayer(paths);
720
- }
721
- }
722
- catch (err) {
723
- deps.io.err(`Could not remove leftover Project-scoped ${harness} hooks: ${String(err)}`);
724
- }
725
- }
726
- function assertOwnedRegularFile(file) {
727
- const stat = lstatSync(file);
728
- if (stat.isSymbolicLink() || !stat.isFile()) {
729
- throw new Error(`${file} is not a regular file; refusing to read or replace it.`);
730
- }
731
- const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
732
- if (uid !== undefined && stat.uid !== uid) {
733
- throw new Error(`${file} is owned by uid ${stat.uid}, not the current user.`);
734
- }
735
- }
736
- function resolveHarness(deps, requested) {
737
- if (requested !== undefined) {
738
- if (HOOK_INSTALLABLE_HARNESSES.includes(requested))
739
- return requested;
740
- deps.io.err(`Unknown harness "${requested}". Supported: ${HOOK_INSTALLABLE_HARNESSES.join(', ')}.`);
741
- return null;
742
- }
743
- const detected = detectHarness(deps.cwd, deps.env);
744
- if (!detected) {
745
- deps.io.err(`Could not tell which harness to install for — pass --harness <${HOOK_INSTALLABLE_HARNESSES.join('|')}>.`);
746
- return null;
747
- }
748
- return detected;
749
- }
750
- /**
751
- * Which harnesses to wire. An explicit `--harness` still wins as a singleton.
752
- * Otherwise: every detected harness, or a human picker when detection is empty
753
- * or names more than one.
754
- */
755
- export async function pickHarnessesToInstall(deps, requested) {
756
- if (requested !== undefined) {
757
- const harness = resolveHarness(deps, requested);
758
- return harness === null ? null : [harness];
759
- }
760
- const detected = detectedHarnesses(deps.cwd, deps.env);
761
- if (detected.length === 1)
762
- return detected;
763
- if (deps.io.interactive === true && deps.io.multiselect) {
764
- const picked = await deps.io.multiselect('Which agent harnesses should Notifai wire here?', HOOK_INSTALLABLE_HARNESSES.map((name) => ({
765
- value: name,
766
- label: HARNESS_LABELS[name],
767
- ...(detected.includes(name) ? { hint: 'detected on this machine' } : {}),
768
- })), detected);
769
- if (picked === null)
770
- return null;
771
- const unknown = picked.filter((name) => !HOOK_INSTALLABLE_HARNESSES.includes(name));
772
- if (unknown.length > 0) {
773
- deps.io.err(`Unknown harness "${unknown[0]}". Supported: ${HOOK_INSTALLABLE_HARNESSES.join(', ')}.`);
774
- return null;
775
- }
776
- return picked;
777
- }
778
- if (detected.length === 0) {
779
- deps.io.err(`Could not tell which harness to wire. Run: notifai hooks install --harness <${HOOK_INSTALLABLE_HARNESSES.join('|')}>`);
780
- return null;
781
- }
782
- return detected;
783
- }
784
- //# sourceMappingURL=commands-hook-install.js.map