pushary 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/CHANGELOG.md +1943 -0
  2. package/LICENSE +21 -0
  3. package/README.md +330 -0
  4. package/data/SKILL.md +591 -0
  5. package/data/cowork/SKILL.md +77 -0
  6. package/data/cursor-plugin/.cursor-plugin/plugin.json +25 -0
  7. package/data/cursor-plugin/CHANGELOG.md +70 -0
  8. package/data/cursor-plugin/CONTRIBUTING.md +40 -0
  9. package/data/cursor-plugin/LICENSE +21 -0
  10. package/data/cursor-plugin/README.md +113 -0
  11. package/data/cursor-plugin/SECURITY.md +33 -0
  12. package/data/cursor-plugin/assets/logo.png +0 -0
  13. package/data/cursor-plugin/commands/notify-when-done.md +14 -0
  14. package/data/cursor-plugin/commands/pushary-test.md +13 -0
  15. package/data/cursor-plugin/hooks/hooks.json +75 -0
  16. package/data/cursor-plugin/mcp.json +11 -0
  17. package/data/cursor-plugin/rules/pushary.mdc +40 -0
  18. package/data/cursor-plugin/scripts/pushary-gate.mjs +724 -0
  19. package/data/cursor-plugin/scripts/pushary-gate.test.mjs +137 -0
  20. package/data/cursor-plugin/scripts/redaction.mjs +51 -0
  21. package/data/cursor-plugin/skills/pushary/SKILL.md +584 -0
  22. package/data/vscode-plugin/.claude-plugin/plugin.json +28 -0
  23. package/data/vscode-plugin/.mcp.json +11 -0
  24. package/data/vscode-plugin/CHANGELOG.md +49 -0
  25. package/data/vscode-plugin/CONTRIBUTING.md +40 -0
  26. package/data/vscode-plugin/LICENSE +21 -0
  27. package/data/vscode-plugin/README.md +128 -0
  28. package/data/vscode-plugin/SECURITY.md +56 -0
  29. package/data/vscode-plugin/assets/logo.png +0 -0
  30. package/data/vscode-plugin/commands/notify-when-done.md +14 -0
  31. package/data/vscode-plugin/commands/pushary-test.md +13 -0
  32. package/data/vscode-plugin/hooks/hooks.json +60 -0
  33. package/data/vscode-plugin/scripts/pushary-gate.mjs +762 -0
  34. package/data/vscode-plugin/scripts/redaction.mjs +51 -0
  35. package/data/vscode-plugin/skills/pushary/SKILL.md +584 -0
  36. package/dist/bin/pushary-bell-hook.d.ts +1 -0
  37. package/dist/bin/pushary-bell-hook.js +46 -0
  38. package/dist/bin/pushary-bell.d.ts +1 -0
  39. package/dist/bin/pushary-bell.js +146 -0
  40. package/dist/bin/pushary-claude.d.ts +1 -0
  41. package/dist/bin/pushary-claude.js +1916 -0
  42. package/dist/bin/pushary-clean.d.ts +1 -0
  43. package/dist/bin/pushary-clean.js +1300 -0
  44. package/dist/bin/pushary-codex-bridge.d.ts +1 -0
  45. package/dist/bin/pushary-codex-bridge.js +599 -0
  46. package/dist/bin/pushary-codex-hook.d.ts +1 -0
  47. package/dist/bin/pushary-codex-hook.js +886 -0
  48. package/dist/bin/pushary-codex.d.ts +1 -0
  49. package/dist/bin/pushary-codex.js +125 -0
  50. package/dist/bin/pushary-connect.d.ts +1 -0
  51. package/dist/bin/pushary-connect.js +100 -0
  52. package/dist/bin/pushary-cowork.d.ts +1 -0
  53. package/dist/bin/pushary-cowork.js +36 -0
  54. package/dist/bin/pushary-daemon-supervisor.d.ts +1 -0
  55. package/dist/bin/pushary-daemon-supervisor.js +27 -0
  56. package/dist/bin/pushary-daemon.d.ts +1 -0
  57. package/dist/bin/pushary-daemon.js +878 -0
  58. package/dist/bin/pushary-disconnect.d.ts +1 -0
  59. package/dist/bin/pushary-disconnect.js +264 -0
  60. package/dist/bin/pushary-doctor.d.ts +1 -0
  61. package/dist/bin/pushary-doctor.js +1433 -0
  62. package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
  63. package/dist/bin/pushary-elicitation-hook.js +149 -0
  64. package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
  65. package/dist/bin/pushary-gemini-bridge.js +339 -0
  66. package/dist/bin/pushary-gemini-hook.d.ts +1 -0
  67. package/dist/bin/pushary-gemini-hook.js +381 -0
  68. package/dist/bin/pushary-hook.d.ts +1 -0
  69. package/dist/bin/pushary-hook.js +95 -0
  70. package/dist/bin/pushary-login.d.ts +1 -0
  71. package/dist/bin/pushary-login.js +198 -0
  72. package/dist/bin/pushary-logout.d.ts +1 -0
  73. package/dist/bin/pushary-logout.js +147 -0
  74. package/dist/bin/pushary-mcp.d.ts +1 -0
  75. package/dist/bin/pushary-mcp.js +114 -0
  76. package/dist/bin/pushary-mode.d.ts +1 -0
  77. package/dist/bin/pushary-mode.js +130 -0
  78. package/dist/bin/pushary-notification-hook.d.ts +1 -0
  79. package/dist/bin/pushary-notification-hook.js +56 -0
  80. package/dist/bin/pushary-opencode-hook.d.ts +1 -0
  81. package/dist/bin/pushary-opencode-hook.js +338 -0
  82. package/dist/bin/pushary-permission-denied-hook.d.ts +1 -0
  83. package/dist/bin/pushary-permission-denied-hook.js +60 -0
  84. package/dist/bin/pushary-permission-hook.d.ts +1 -0
  85. package/dist/bin/pushary-permission-hook.js +65 -0
  86. package/dist/bin/pushary-post-hook.d.ts +1 -0
  87. package/dist/bin/pushary-post-hook.js +70 -0
  88. package/dist/bin/pushary-prompt-hook.d.ts +1 -0
  89. package/dist/bin/pushary-prompt-hook.js +61 -0
  90. package/dist/bin/pushary-session-end-hook.d.ts +1 -0
  91. package/dist/bin/pushary-session-end-hook.js +59 -0
  92. package/dist/bin/pushary-session-start-hook.d.ts +1 -0
  93. package/dist/bin/pushary-session-start-hook.js +72 -0
  94. package/dist/bin/pushary-setup.d.ts +1 -0
  95. package/dist/bin/pushary-setup.js +2660 -0
  96. package/dist/bin/pushary-stats.d.ts +1 -0
  97. package/dist/bin/pushary-stats.js +44 -0
  98. package/dist/bin/pushary-status.d.ts +1 -0
  99. package/dist/bin/pushary-status.js +262 -0
  100. package/dist/bin/pushary-stop-hook.d.ts +1 -0
  101. package/dist/bin/pushary-stop-hook.js +63 -0
  102. package/dist/bin/pushary-stopfailure-hook.d.ts +1 -0
  103. package/dist/bin/pushary-stopfailure-hook.js +56 -0
  104. package/dist/bin/pushary-suggestions.d.ts +1 -0
  105. package/dist/bin/pushary-suggestions.js +100 -0
  106. package/dist/bin/pushary-transcript-register.d.ts +1 -0
  107. package/dist/bin/pushary-transcript-register.js +40 -0
  108. package/dist/bin/pushary-transcripts.d.ts +1 -0
  109. package/dist/bin/pushary-transcripts.js +77 -0
  110. package/dist/bin/pushary-upgrade.d.ts +1 -0
  111. package/dist/bin/pushary-upgrade.js +599 -0
  112. package/dist/bin/pushary-wait.d.ts +1 -0
  113. package/dist/bin/pushary-wait.js +122 -0
  114. package/dist/bin/pushary.d.ts +1 -0
  115. package/dist/bin/pushary.js +74 -0
  116. package/dist/chunk-2ABTSGFT.js +173 -0
  117. package/dist/chunk-2OY3ZZ5N.js +124 -0
  118. package/dist/chunk-3EGEA4KH.js +44 -0
  119. package/dist/chunk-3EVPNIBE.js +185 -0
  120. package/dist/chunk-5OP5MQI7.js +45 -0
  121. package/dist/chunk-6J2JC2RT.js +149 -0
  122. package/dist/chunk-7QLSKOSU.js +19 -0
  123. package/dist/chunk-7Z7Q4GGS.js +88 -0
  124. package/dist/chunk-A2A2YTMG.js +835 -0
  125. package/dist/chunk-AHV4LNB5.js +377 -0
  126. package/dist/chunk-AXPCESYO.js +197 -0
  127. package/dist/chunk-C2WCPBF7.js +86 -0
  128. package/dist/chunk-CJVVKOSR.js +73 -0
  129. package/dist/chunk-CULUZTWQ.js +2117 -0
  130. package/dist/chunk-CVQYGMSR.js +142 -0
  131. package/dist/chunk-CVWG2N3Y.js +16 -0
  132. package/dist/chunk-D2FX3EPW.js +15 -0
  133. package/dist/chunk-DB3ONZB4.js +44 -0
  134. package/dist/chunk-DE4Y6TCC.js +160 -0
  135. package/dist/chunk-E4FJTUH4.js +249 -0
  136. package/dist/chunk-EPCXUP3P.js +41 -0
  137. package/dist/chunk-EX3GAPA4.js +44 -0
  138. package/dist/chunk-EYK3GSRH.js +143 -0
  139. package/dist/chunk-GMXKITVA.js +70 -0
  140. package/dist/chunk-GPEGOGRJ.js +49 -0
  141. package/dist/chunk-GU3EJCVV.js +43 -0
  142. package/dist/chunk-HQ6G3B5R.js +59 -0
  143. package/dist/chunk-HZVVNGEW.js +22 -0
  144. package/dist/chunk-IPVKH2ST.js +231 -0
  145. package/dist/chunk-J5TIV3J6.js +238 -0
  146. package/dist/chunk-JU6W3XAU.js +64 -0
  147. package/dist/chunk-JYG4B33G.js +227 -0
  148. package/dist/chunk-JZTHGAPA.js +503 -0
  149. package/dist/chunk-KGDWDSNL.js +102 -0
  150. package/dist/chunk-KVQBWI5T.js +99 -0
  151. package/dist/chunk-KZERVKTD.js +16 -0
  152. package/dist/chunk-LXQ5FBBD.js +47 -0
  153. package/dist/chunk-M5ICYGHT.js +138 -0
  154. package/dist/chunk-M5JDZ45W.js +943 -0
  155. package/dist/chunk-MDEPU45E.js +1226 -0
  156. package/dist/chunk-MPWXGONV.js +157 -0
  157. package/dist/chunk-N6IOSZOE.js +185 -0
  158. package/dist/chunk-NGDD6TXY.js +192 -0
  159. package/dist/chunk-NQTHFF4W.js +2830 -0
  160. package/dist/chunk-OKXS7WDZ.js +68 -0
  161. package/dist/chunk-OLQRRFBX.js +21 -0
  162. package/dist/chunk-P7WQEVWA.js +307 -0
  163. package/dist/chunk-POFBE6ZY.js +12 -0
  164. package/dist/chunk-PQ4K74WL.js +30 -0
  165. package/dist/chunk-PUZZELE6.js +37 -0
  166. package/dist/chunk-Q7E4I6UA.js +767 -0
  167. package/dist/chunk-QCGNGEGE.js +15 -0
  168. package/dist/chunk-R7437YI3.js +151 -0
  169. package/dist/chunk-RHR6NARM.js +305 -0
  170. package/dist/chunk-SAXBC4AZ.js +60 -0
  171. package/dist/chunk-SGZJ5LYH.js +76 -0
  172. package/dist/chunk-SIFVFP6T.js +70 -0
  173. package/dist/chunk-SLFABHW3.js +150 -0
  174. package/dist/chunk-T7E75EKA.js +67 -0
  175. package/dist/chunk-TY4JT7W3.js +38 -0
  176. package/dist/chunk-U4TMAANR.js +111 -0
  177. package/dist/chunk-UI36QSBI.js +177 -0
  178. package/dist/chunk-VQKRG3AC.js +339 -0
  179. package/dist/chunk-WE2J62NC.js +1131 -0
  180. package/dist/chunk-XHKBHWLX.js +14 -0
  181. package/dist/chunk-ZSZSSJCV.js +275 -0
  182. package/dist/chunk-ZVSKMPBZ.js +135 -0
  183. package/dist/reapply-CW3MA66V.js +34 -0
  184. package/dist/src/index.d.ts +204 -0
  185. package/dist/src/index.js +64 -0
  186. package/package.json +108 -0
@@ -0,0 +1,1300 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PUSHARY_SKILL_NAME,
4
+ PUSHARY_SKILL_SOURCE,
5
+ createGuards,
6
+ withoutPusharyCursorHooks
7
+ } from "../chunk-KVQBWI5T.js";
8
+ import {
9
+ HERMES_PIP_PACKAGE,
10
+ planFxRemoval,
11
+ readHermesStatus,
12
+ removeInstructionBlock,
13
+ resolveHermesPython,
14
+ runHermesConfigScript,
15
+ shortenHome,
16
+ unregisterPluginLocation,
17
+ vscodeSettingsTargets
18
+ } from "../chunk-MDEPU45E.js";
19
+ import "../chunk-SLFABHW3.js";
20
+ import {
21
+ OPENCODE_PLUGIN_SIBLING,
22
+ OPENCODE_PLUGIN_SIBLING_CONTENTS,
23
+ hasForeignPlugins,
24
+ isOpenCodePluginOurs,
25
+ openCodeConfigDir,
26
+ openCodeConfigFile,
27
+ openCodeInstructionsFile,
28
+ openCodePluginDir,
29
+ openCodePluginFile,
30
+ removeOpenCodeMcpServer
31
+ } from "../chunk-AHV4LNB5.js";
32
+ import {
33
+ compatibleConfigFile,
34
+ compatibleInstructionsFile,
35
+ compatibleMcpFile,
36
+ compatiblePermissionRuleFile,
37
+ removeCompatibleHooks,
38
+ removeCompatibleMcpServer
39
+ } from "../chunk-J5TIV3J6.js";
40
+ import {
41
+ isClaudeMcpSnapshot,
42
+ removeClaudeMcpServers,
43
+ removePusharySettings,
44
+ removePusharyToolPermissions,
45
+ restoreClaudeMcpServer
46
+ } from "../chunk-AXPCESYO.js";
47
+ import {
48
+ hookOwnerOf,
49
+ isHookTakeoverReceipt,
50
+ restoreHookTakeover,
51
+ restoreMcpServer
52
+ } from "../chunk-EYK3GSRH.js";
53
+ import "../chunk-ZVSKMPBZ.js";
54
+ import {
55
+ liveBridgeProbe,
56
+ resolveBridgeStub
57
+ } from "../chunk-C2WCPBF7.js";
58
+ import "../chunk-6J2JC2RT.js";
59
+ import {
60
+ disableDaemonService
61
+ } from "../chunk-WE2J62NC.js";
62
+ import {
63
+ removeGeminiSettings
64
+ } from "../chunk-3EVPNIBE.js";
65
+ import "../chunk-OKXS7WDZ.js";
66
+ import "../chunk-XHKBHWLX.js";
67
+ import "../chunk-CULUZTWQ.js";
68
+ import "../chunk-N6IOSZOE.js";
69
+ import "../chunk-EPCXUP3P.js";
70
+ import "../chunk-NGDD6TXY.js";
71
+ import "../chunk-CVQYGMSR.js";
72
+ import "../chunk-POFBE6ZY.js";
73
+ import {
74
+ ansiPainter,
75
+ useColor
76
+ } from "../chunk-U4TMAANR.js";
77
+ import {
78
+ exitOnHelpFlag
79
+ } from "../chunk-HZVVNGEW.js";
80
+ import {
81
+ rejectUnknownFlags
82
+ } from "../chunk-GMXKITVA.js";
83
+ import {
84
+ CANDIDATE_RCS,
85
+ isDisabledExport,
86
+ removeClaudeAlias
87
+ } from "../chunk-P7WQEVWA.js";
88
+ import "../chunk-M5ICYGHT.js";
89
+ import "../chunk-2ABTSGFT.js";
90
+ import "../chunk-2OY3ZZ5N.js";
91
+ import "../chunk-LXQ5FBBD.js";
92
+ import "../chunk-7QLSKOSU.js";
93
+ import {
94
+ EXIT
95
+ } from "../chunk-KZERVKTD.js";
96
+ import "../chunk-DB3ONZB4.js";
97
+ import {
98
+ bridgeAgentKey,
99
+ claudeJson,
100
+ claudeMcpReceipt,
101
+ claudeSettings,
102
+ claudeSettingsLocal,
103
+ claudeSkillDir,
104
+ cleanNpmEnv,
105
+ codexAgentsMd,
106
+ codexConfigToml,
107
+ codexHookPositions,
108
+ codexHooksJson,
109
+ codexSkillDir,
110
+ cursorPluginDir,
111
+ cursorUserHooks,
112
+ cursorUserMcp,
113
+ fxAgentsMd,
114
+ fxConfigDir,
115
+ fxMcpProfile,
116
+ fxQuestionRuleReceipt,
117
+ fxSettings,
118
+ geminiMd,
119
+ geminiSettings,
120
+ globalPackageState,
121
+ hermesConfig,
122
+ hermesConfigReceipt,
123
+ hookTakeoverReceipt,
124
+ npmIsOnPath,
125
+ pusharyDir,
126
+ readCodexMcpAuth,
127
+ readGlobalFolder,
128
+ readGlobalPrefix,
129
+ removeCodexHookTrust,
130
+ removeCodexHooks,
131
+ restoreCodexHookTrust,
132
+ vscodePluginDir,
133
+ vscodePluginMcp
134
+ } from "../chunk-A2A2YTMG.js";
135
+ import "../chunk-T7E75EKA.js";
136
+ import "../chunk-GPEGOGRJ.js";
137
+ import "../chunk-E4FJTUH4.js";
138
+ import {
139
+ CLI_PACKAGE_NAMES,
140
+ HOOK_COMPATIBLE_PROFILES,
141
+ HOOK_LOCATOR_BINARY,
142
+ SUPPORT_DISCORD,
143
+ SUPPORT_EMAIL,
144
+ globalPackageFolder,
145
+ globalPackagesToUninstall,
146
+ globalRootFor,
147
+ isValidApiKey,
148
+ redactSecrets
149
+ } from "../chunk-NQTHFF4W.js";
150
+
151
+ // bin/pushary-clean.ts
152
+ import { existsSync as existsSync6, readFileSync as readFileSync4, copyFileSync, readdirSync as readdirSync4 } from "fs";
153
+ import { dirname, join as join6 } from "path";
154
+ import { homedir as homedir5, tmpdir } from "os";
155
+ import { confirm } from "@inquirer/prompts";
156
+ import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
157
+
158
+ // src/cli/mac-leftovers.ts
159
+ import { execSync, execFileSync } from "child_process";
160
+ import { existsSync } from "fs";
161
+ import { homedir } from "os";
162
+ import { join } from "path";
163
+ var MAC_APP_BUNDLE = "/Applications/Pushary.app";
164
+ var installedPusharyBundles = (home = homedir()) => [MAC_APP_BUNDLE, join(home, "Applications", "Pushary.app")];
165
+ var LSREGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
166
+ var TEST_DEFAULTS_PREFIXES = [
167
+ "com.pushary.mac.tests.",
168
+ "com.pushary.tests.",
169
+ "pushary.tests.",
170
+ "pushary.quiet-mode-test.",
171
+ "pushary-optin-"
172
+ ];
173
+ var isTestDefaultsDomain = (domain) => TEST_DEFAULTS_PREFIXES.some((prefix) => domain.startsWith(prefix));
174
+ var leakedTestDefaultsDomains = () => {
175
+ try {
176
+ return execSync("defaults domains", { encoding: "utf-8", timeout: 2e4 }).split(",").map((entry) => entry.trim()).filter(isTestDefaultsDomain).filter(liveMacStatePresence.defaultsDomain);
177
+ } catch {
178
+ return [];
179
+ }
180
+ };
181
+ var BUNDLE_NAME = "Pushary.app";
182
+ var DUMP_PATH_LINE = /^\s*path:\s+(.+?)(?: \(0x[0-9a-f]+\))?\s*$/;
183
+ var bundlesInLaunchServicesDump = (dump) => {
184
+ const found = /* @__PURE__ */ new Set();
185
+ for (const line of dump.split("\n")) {
186
+ const path = DUMP_PATH_LINE.exec(line)?.[1];
187
+ const bundle = path?.indexOf(`/${BUNDLE_NAME}`);
188
+ if (path === void 0 || bundle === void 0 || bundle === -1) continue;
189
+ found.add(path.slice(0, bundle + BUNDLE_NAME.length + 1));
190
+ }
191
+ return [...found];
192
+ };
193
+ var registeredPusharyBundles = (installed = installedPusharyBundles()) => {
194
+ if (!existsSync(LSREGISTER)) return [];
195
+ try {
196
+ const dump = execSync(`${LSREGISTER} -dump`, {
197
+ encoding: "utf-8",
198
+ timeout: 6e4,
199
+ maxBuffer: 256 * 1024 * 1024
200
+ });
201
+ return bundlesInLaunchServicesDump(dump).filter((path) => !installed.includes(path));
202
+ } catch {
203
+ return [];
204
+ }
205
+ };
206
+ var MAC_KEYCHAIN_SERVICE = "com.pushary.app.mac";
207
+ var MAC_KEYCHAIN_ACCOUNTS = [
208
+ "pushary.site-api-key",
209
+ "pushary.site-id",
210
+ "clerk.device-token",
211
+ "clerk.session-id",
212
+ "clerkDeviceToken",
213
+ "cachedClient",
214
+ "cachedClientServerDate",
215
+ "cachedEnvironment"
216
+ ];
217
+ var MAC_DEFAULTS_DOMAINS = ["com.pushary.app", "pushary-bridge", "pushary.render"];
218
+ var PRESENCE_PROBE_TIMEOUT_MS = 1e4;
219
+ var commandSucceeds = (file, args) => {
220
+ try {
221
+ execFileSync(file, args, { stdio: "ignore", timeout: PRESENCE_PROBE_TIMEOUT_MS });
222
+ return true;
223
+ } catch {
224
+ return false;
225
+ }
226
+ };
227
+ var KEYCHAIN_ITEM_NOT_FOUND = 44;
228
+ var keychainItemMayExist = (service, account) => {
229
+ try {
230
+ execFileSync("security", ["find-generic-password", "-s", service, "-a", account], { stdio: "ignore" });
231
+ return true;
232
+ } catch (error) {
233
+ return !(error instanceof Error && "status" in error && error.status === KEYCHAIN_ITEM_NOT_FOUND);
234
+ }
235
+ };
236
+ var liveMacStatePresence = {
237
+ keychainItem: keychainItemMayExist,
238
+ defaultsDomain: (domain) => commandSucceeds("defaults", ["read", domain])
239
+ };
240
+ var macLibraryState = (home = homedir()) => [
241
+ join(home, "Library", "Application Support", "Pushary"),
242
+ join(home, "Library", "Caches", "com.pushary.app"),
243
+ join(home, "Library", "Caches", "com.pushary.app.sparkle"),
244
+ join(home, "Library", "Saved Application State", "com.pushary.app.savedState"),
245
+ join(home, "Library", "Caches", "pushary-bridge"),
246
+ join(home, "Library", "HTTPStorages", "com.pushary.app"),
247
+ join(home, "Library", "HTTPStorages", "com.pushary.app.binarycookies"),
248
+ join(home, "Library", "HTTPStorages", "pushary-bridge")
249
+ ];
250
+ var DUMP_SERVICE_LINE = /^\s*"svce"<blob>="(.+)"\s*$/;
251
+ var pusharyKeychainServices = (dump) => {
252
+ const found = /* @__PURE__ */ new Set();
253
+ for (const line of dump.split("\n")) {
254
+ const service = DUMP_SERVICE_LINE.exec(line)?.[1];
255
+ if (service?.startsWith(MAC_KEYCHAIN_SERVICE)) found.add(service);
256
+ }
257
+ return [...found];
258
+ };
259
+ var storedPusharyKeychainServices = () => {
260
+ const dump = execFileSync("security", ["dump-keychain"], {
261
+ encoding: "utf-8",
262
+ timeout: 3e4,
263
+ maxBuffer: 64 * 1024 * 1024,
264
+ stdio: ["ignore", "pipe", "ignore"]
265
+ });
266
+ const stored = pusharyKeychainServices(dump);
267
+ return stored.includes(MAC_KEYCHAIN_SERVICE) ? stored : [MAC_KEYCHAIN_SERVICE, ...stored];
268
+ };
269
+ var MAC_APP_BUNDLE_ID = "com.pushary.app";
270
+ var runningMacAppPIDs = () => {
271
+ if (process.platform !== "darwin") return [];
272
+ try {
273
+ return execSync('pgrep -f "Pushary.app/Contents/MacOS/Pushary"', {
274
+ encoding: "utf-8",
275
+ timeout: 1e4,
276
+ stdio: ["ignore", "pipe", "ignore"]
277
+ }).split("\n").map((line) => Number(line.trim())).filter((pid) => Number.isInteger(pid) && pid > 0);
278
+ } catch {
279
+ return [];
280
+ }
281
+ };
282
+ var waitForMacAppExit = (attempts = 20, everyMs = 250) => {
283
+ const idle = new Int32Array(new SharedArrayBuffer(4));
284
+ for (let attempt = 0; attempt < attempts; attempt++) {
285
+ if (runningMacAppPIDs().length === 0) return true;
286
+ Atomics.wait(idle, 0, 0, everyMs);
287
+ }
288
+ return runningMacAppPIDs().length === 0;
289
+ };
290
+
291
+ // src/cli/clean-mac.ts
292
+ import { existsSync as existsSync2 } from "fs";
293
+ var removeDeveloperLeftovers = (guards2, domains, bundles) => {
294
+ let removed = 0;
295
+ for (const domain of domains) {
296
+ try {
297
+ guards2.execFile("defaults", ["delete", domain], { stdio: "ignore", timeout: 1e4 }, `delete preferences ${domain}`);
298
+ removed++;
299
+ } catch {
300
+ continue;
301
+ }
302
+ }
303
+ for (const bundle of bundles) {
304
+ try {
305
+ guards2.execFile(LSREGISTER, ["-u", bundle], { stdio: "ignore", timeout: 2e4 }, `unregister ${bundle}`);
306
+ removed++;
307
+ } catch {
308
+ continue;
309
+ }
310
+ }
311
+ return removed;
312
+ };
313
+ var removeMacAppState = (guards2, readServices, home, present) => {
314
+ let removed = 0;
315
+ let failed = 0;
316
+ let services;
317
+ try {
318
+ services = readServices();
319
+ } catch {
320
+ return { removed, failed: 1 };
321
+ }
322
+ for (const service of services) {
323
+ for (const account of MAC_KEYCHAIN_ACCOUNTS.filter((account2) => present.keychainItem(service, account2))) {
324
+ try {
325
+ guards2.execFile(
326
+ "security",
327
+ ["delete-generic-password", "-s", service, "-a", account],
328
+ { stdio: "ignore" },
329
+ `remove keychain item ${service} (${account})`
330
+ );
331
+ removed++;
332
+ } catch (error) {
333
+ if (!(error instanceof Error && "status" in error && error.status === 44)) failed++;
334
+ }
335
+ }
336
+ }
337
+ for (const domain of MAC_DEFAULTS_DOMAINS.filter(present.defaultsDomain)) {
338
+ try {
339
+ guards2.execFile("defaults", ["delete", domain], { stdio: "ignore", timeout: 1e4 }, `delete preferences ${domain}`);
340
+ removed++;
341
+ } catch {
342
+ continue;
343
+ }
344
+ }
345
+ for (const path of macLibraryState(home)) {
346
+ if (!existsSync2(path)) continue;
347
+ guards2.remove(path, { force: true });
348
+ removed++;
349
+ }
350
+ return { removed, failed };
351
+ };
352
+ var quitMacApp = (guards2, state) => {
353
+ if (state.platform !== "darwin" || state.running().length === 0) return "not-running";
354
+ try {
355
+ guards2.execFile(
356
+ "osascript",
357
+ ["-e", `tell application id "${MAC_APP_BUNDLE_ID}" to quit`],
358
+ { stdio: "ignore", timeout: 3e4 },
359
+ "quit Pushary"
360
+ );
361
+ } catch {
362
+ }
363
+ return state.dryRun || state.waitForExit() ? "quit" : "running";
364
+ };
365
+
366
+ // src/cli/editor-extension.ts
367
+ import { existsSync as existsSync3, readFileSync, readdirSync } from "fs";
368
+ import { homedir as homedir2 } from "os";
369
+ import { join as join2 } from "path";
370
+ var EDITOR_EXTENSION_ID = "pushary.pushary";
371
+ var EDITOR_EXTENSION_DIRS = [
372
+ join2(".cursor", "extensions"),
373
+ join2(".vscode", "extensions")
374
+ ];
375
+ var EXTENSIONS_JSON = "extensions.json";
376
+ var isOurEntry = (entry) => {
377
+ if (typeof entry !== "object" || entry === null) return false;
378
+ const identifier = entry.identifier;
379
+ if (typeof identifier !== "object" || identifier === null) return false;
380
+ const id = identifier.id;
381
+ return typeof id === "string" && id.toLowerCase() === EDITOR_EXTENSION_ID;
382
+ };
383
+ var recordWithoutUs = (directory) => {
384
+ const path = join2(directory, EXTENSIONS_JSON);
385
+ try {
386
+ const entries = JSON.parse(readFileSync(path, "utf-8"));
387
+ if (!Array.isArray(entries)) return void 0;
388
+ const kept = entries.filter((entry) => !isOurEntry(entry));
389
+ return kept.length === entries.length ? void 0 : { path, contents: JSON.stringify(kept) };
390
+ } catch {
391
+ return void 0;
392
+ }
393
+ };
394
+ var editorExtensionRemovals = (home = homedir2()) => EDITOR_EXTENSION_DIRS.map((relative) => join2(home, relative)).filter(existsSync3).map((directory) => {
395
+ const folders = readdirSync(directory).filter((name) => name.startsWith(`${EDITOR_EXTENSION_ID}-`)).map((name) => join2(directory, name));
396
+ return { directory, folders, record: recordWithoutUs(directory) };
397
+ }).filter((removal) => removal.folders.length > 0 || removal.record !== void 0);
398
+
399
+ // src/cli/skills-store.ts
400
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
401
+ import { homedir as homedir3 } from "os";
402
+ import { join as join3 } from "path";
403
+ var SKILLS_STORE_DIR = ".agents";
404
+ var LOCK_FILE = ".skill-lock.json";
405
+ var lockWithoutUs = (path) => {
406
+ try {
407
+ const lock = JSON.parse(readFileSync2(path, "utf-8"));
408
+ if (!lock || typeof lock !== "object" || Array.isArray(lock) || !("skills" in lock)) return void 0;
409
+ const entries = lock.skills;
410
+ if (!entries || typeof entries !== "object" || Array.isArray(entries) || !(PUSHARY_SKILL_NAME in entries)) return void 0;
411
+ const entry = entries[PUSHARY_SKILL_NAME];
412
+ if (!entry || typeof entry !== "object" || !("source" in entry) || entry.source !== PUSHARY_SKILL_SOURCE) return void 0;
413
+ const { [PUSHARY_SKILL_NAME]: _ours, ...skills } = entries;
414
+ return { path, contents: `${JSON.stringify({ ...lock, skills }, null, 2)}
415
+ ` };
416
+ } catch {
417
+ return void 0;
418
+ }
419
+ };
420
+ var skillsStoreRemoval = (home = homedir3()) => {
421
+ const store = join3(home, SKILLS_STORE_DIR);
422
+ const skillDir = join3(store, "skills", PUSHARY_SKILL_NAME);
423
+ const lock = lockWithoutUs(join3(store, LOCK_FILE));
424
+ return {
425
+ skillDir: lock && existsSync4(skillDir) ? skillDir : void 0,
426
+ lock
427
+ };
428
+ };
429
+
430
+ // src/cli/claude-backups.ts
431
+ import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
432
+ import { homedir as homedir4 } from "os";
433
+ import { join as join4 } from "path";
434
+ var BACKUP_PREFIX = ".claude.json.backup";
435
+ var claudeJsonBackups = (home = homedir4()) => {
436
+ const rollingDir = join4(home, ".claude", "backups");
437
+ const legacy = join4(home, BACKUP_PREFIX);
438
+ const rolling = existsSync5(rollingDir) ? readdirSync2(rollingDir).filter((name) => name.startsWith(BACKUP_PREFIX)).sort().map((name) => join4(rollingDir, name)) : [];
439
+ return existsSync5(legacy) ? [...rolling, legacy] : rolling;
440
+ };
441
+ var parsed = (path) => {
442
+ try {
443
+ const value = JSON.parse(readFileSync3(path, "utf-8"));
444
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
445
+ } catch {
446
+ return null;
447
+ }
448
+ };
449
+ var claudeBackupRewrites = (receipt, home = homedir4()) => claudeJsonBackups(home).flatMap((path) => {
450
+ const backup = parsed(path);
451
+ if (!backup) return [];
452
+ const changed = receipt ? restoreClaudeMcpServer(backup, receipt) : removeClaudeMcpServers(backup);
453
+ return changed ? [{ path, contents: `${JSON.stringify(backup, null, 2)}
454
+ ` }] : [];
455
+ });
456
+
457
+ // src/cli/clean-global-package.ts
458
+ var UNINSTALL_TIMEOUT_MS = 3e4;
459
+ var readRemovalEvidence = (prefix, platform) => CLI_PACKAGE_NAMES.map((name) => ({
460
+ name,
461
+ listing: globalPackageState(name),
462
+ folder: prefix === void 0 ? "unseen" : readGlobalFolder(globalRootFor(prefix, platform), name, platform).kind
463
+ }));
464
+ var uninstallGlobalPackage = (guards2, name) => {
465
+ guards2.execFile(
466
+ `npm uninstall -g --no-workspaces ${name}`,
467
+ [],
468
+ { stdio: "ignore", timeout: UNINSTALL_TIMEOUT_MS, env: cleanNpmEnv(), shell: true },
469
+ `npm uninstall -g ${name}`
470
+ );
471
+ };
472
+ var removeLeftoverFolders = (guards2, prefix, platform) => {
473
+ const root = globalRootFor(prefix, platform);
474
+ for (const name of CLI_PACKAGE_NAMES) {
475
+ if (readGlobalFolder(root, name, platform).kind === "ours") {
476
+ guards2.remove(globalPackageFolder(root, name, platform), { force: true });
477
+ }
478
+ }
479
+ };
480
+ var removeGlobalCliPackages = (guards2, request) => {
481
+ const platform = request.platform ?? process.platform;
482
+ const prefix = readGlobalPrefix();
483
+ const names = globalPackagesToUninstall(readRemovalEvidence(prefix, platform));
484
+ if (names.length === 0) return { kind: "not_installed" };
485
+ try {
486
+ for (const name of names) uninstallGlobalPackage(guards2, name);
487
+ if (!request.dryRun && prefix !== void 0) removeLeftoverFolders(guards2, prefix, platform);
488
+ return { kind: "removed", names };
489
+ } catch {
490
+ return { kind: "failed", names };
491
+ }
492
+ };
493
+
494
+ // src/cli/config-temp-leftovers.ts
495
+ import { lstatSync, readdirSync as readdirSync3 } from "fs";
496
+ import { join as join5 } from "path";
497
+ var MAC_APP_WRITE_TEMP = /^\..+\.pushary-[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/;
498
+ var PID_STAMPED_WRITE_TEMP = /^\.\d+-[^/]+\.pushary-tmp$/;
499
+ var CONFIG_TEMP_MIN_AGE_MS = 6e4;
500
+ var isLeakedConfigTempName = (name) => MAC_APP_WRITE_TEMP.test(name) || PID_STAMPED_WRITE_TEMP.test(name);
501
+ var leakedConfigTemps = (entries, nowMs) => entries.filter((entry) => entry.isFile && isLeakedConfigTempName(entry.name) && nowMs - entry.modifiedAtMs >= CONFIG_TEMP_MIN_AGE_MS).map((entry) => entry.name);
502
+ var directoryEntries = (directory) => {
503
+ try {
504
+ return readdirSync3(directory).flatMap((name) => {
505
+ const stats = lstatSync(join5(directory, name), { throwIfNoEntry: false });
506
+ return stats ? [{ name, isFile: stats.isFile(), modifiedAtMs: stats.mtimeMs }] : [];
507
+ });
508
+ } catch {
509
+ return [];
510
+ }
511
+ };
512
+ var configTempLeftovers = (directories, nowMs) => [...new Set(directories)].flatMap((directory) => leakedConfigTemps(directoryEntries(directory), nowMs).map((name) => join5(directory, name)));
513
+
514
+ // src/app-owned-paths.ts
515
+ var APP_OWNED_ENTRIES = [
516
+ // PusharyPaths.bin: bridgeStub, bridgeCache
517
+ "bin",
518
+ // PusharyPaths.run: pushary.sock, pushary.lastrun, gate-state.json, pending-commands.json
519
+ "run",
520
+ // PusharyPaths.spool
521
+ "spool",
522
+ // Shared: AgentInstaller.vscodePlugin writes .pushary/plugins/vscode, and so
523
+ // does the CLI. Not attributable, so it is kept whenever the app is installed
524
+ // rather than removed and left registered against a path that is gone.
525
+ "plugins",
526
+ "bridge-auth"
527
+ ];
528
+ var isAppOwned = (name) => APP_OWNED_ENTRIES.includes(name);
529
+
530
+ // src/shared-mcp-key.ts
531
+ var asRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
532
+ var pusharyKey = (value) => typeof value === "string" && isValidApiKey(value.trim()) ? value.trim() : null;
533
+ var bearerKey = (authorization) => typeof authorization === "string" && authorization.startsWith("Bearer ") ? pusharyKey(authorization.slice("Bearer ".length)) : null;
534
+ var mcpServerBearer = (mcpServers) => bearerKey(asRecord(asRecord(asRecord(mcpServers)?.pushary)?.headers)?.Authorization);
535
+ var appAgentKey = (bridgeAgentKey2) => pusharyKey(asRecord(bridgeAgentKey2)?.key);
536
+ var setupReceiptKey = (claudeMcpReceipt2) => bearerKey(asRecord(asRecord(asRecord(claudeMcpReceipt2)?.installedPushary)?.headers)?.Authorization);
537
+ var macAppKeepsMcpEntry = (entryKey, keys) => {
538
+ if (!entryKey) return true;
539
+ if (keys.appKey) return entryKey === keys.appKey;
540
+ return entryKey !== keys.setupKey;
541
+ };
542
+
543
+ // bin/pushary-clean.ts
544
+ exitOnHelpFlag("clean");
545
+ var paint = ansiPainter(() => useColor());
546
+ var dim = paint("\x1B[2m");
547
+ var bold = paint("\x1B[1m");
548
+ var green = paint("\x1B[32m");
549
+ var yellow = paint("\x1B[33m");
550
+ var red = paint("\x1B[31m");
551
+ var check = green("\u2713");
552
+ var skip = yellow("\u2013");
553
+ var cross = red("\u2717");
554
+ var incompleteClean = false;
555
+ var keepCliRuntime = false;
556
+ var CLAUDE_SETTINGS = claudeSettings();
557
+ var CLAUDE_SETTINGS_LOCAL = claudeSettingsLocal();
558
+ var CLAUDE_JSON = claudeJson();
559
+ var CLAUDE_MCP_RECEIPT = claudeMcpReceipt();
560
+ var HERMES_CONFIG_RECEIPT = hermesConfigReceipt();
561
+ var HERMES_CONFIG_FILE = hermesConfig();
562
+ var SKILL_DIR = claudeSkillDir();
563
+ var CURSOR_MCP = cursorUserMcp();
564
+ var CURSOR_PLUGIN_DIR = cursorPluginDir();
565
+ var CURSOR_USER_HOOKS = cursorUserHooks();
566
+ var VSCODE_PLUGIN_DIR = vscodePluginDir();
567
+ var PUSHARY_DIR = pusharyDir();
568
+ var SHELL_FILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"].map((f) => join6(homedir5(), f));
569
+ var PRESENCE_BY_RESOLUTION = {
570
+ bundle: "present",
571
+ missing: "gone",
572
+ "no-stub": "absent"
573
+ };
574
+ var macAppPresence = () => PRESENCE_BY_RESOLUTION[resolveBridgeStub(join6(PUSHARY_DIR, "bin", HOOK_LOCATOR_BINARY), liveBridgeProbe()).kind];
575
+ var removalScope = (keepMcpServer, keepAppWiring) => ({
576
+ keepAppWiring,
577
+ keepMcpServer
578
+ });
579
+ var codexMcpKey = (config) => {
580
+ const auth = readCodexMcpAuth(config);
581
+ return auth?.method === "embedded" ? pusharyKey(auth.key) : null;
582
+ };
583
+ var readJson = (path) => {
584
+ try {
585
+ return JSON.parse(readFileSync4(path, "utf-8"));
586
+ } catch {
587
+ return null;
588
+ }
589
+ };
590
+ var dryRun = false;
591
+ var did = (past) => dryRun ? `(would ${past === "cleaned" ? "clean" : past === "removed" ? "remove" : past === "uninstalled" ? "uninstall" : past})` : `(${past})`;
592
+ var wouldNote = (action, path) => {
593
+ console.log(` ${dim("would")} ${action} ${path}`);
594
+ };
595
+ var guards = createGuards(() => dryRun, wouldNote);
596
+ var guardedWrite = (path, contents) => guards.write(path, contents);
597
+ var guardedRemove = (path, options) => guards.remove(path, options);
598
+ var CURSOR_PLUGIN_STAGING = ".pushary-staging-";
599
+ var cursorPluginLeftovers = () => {
600
+ const parent = dirname(CURSOR_PLUGIN_DIR);
601
+ const backupPrefix = `${CURSOR_PLUGIN_DIR.split("/").pop()}.pushary-backup-`;
602
+ try {
603
+ return readdirSync4(parent).filter((name) => name.startsWith(backupPrefix) || name.startsWith(CURSOR_PLUGIN_STAGING)).map((name) => join6(parent, name));
604
+ } catch {
605
+ return [];
606
+ }
607
+ };
608
+ var guardedDaemonRemoval = async () => {
609
+ if (dryRun) {
610
+ wouldNote("stop and uninstall", "Pushary background service");
611
+ return true;
612
+ }
613
+ return (await disableDaemonService({ requireAbsenceProof: true })).kind === "ok";
614
+ };
615
+ var guardedInstructionBlockRemoval = (path) => {
616
+ if (!dryRun) return removeInstructionBlock(path);
617
+ if (!existsSync6(path)) return false;
618
+ const hasBlock = readFileSync4(path, "utf-8").includes("pushary");
619
+ if (hasBlock) wouldNote("rewrite", path);
620
+ return hasBlock;
621
+ };
622
+ var guardedAliasRemoval = () => {
623
+ if (!dryRun) return removeClaudeAlias();
624
+ const shells = CANDIDATE_RCS.map((name) => join6(homedir5(), name)).filter((path) => existsSync6(path) && readFileSync4(path, "utf-8").includes("pushary claude"));
625
+ for (const path of shells) wouldNote("rewrite", path);
626
+ return shells;
627
+ };
628
+ var writeJson = (path, data) => {
629
+ guardedWrite(path, JSON.stringify(data, null, 2) + "\n");
630
+ };
631
+ var removeFxConfiguration = () => {
632
+ for (const action of planFxRemoval(fxMcpProfile(), fxSettings(), fxQuestionRuleReceipt())) {
633
+ if (action.kind === "write") {
634
+ guardedWrite(action.path, action.text);
635
+ console.log(` ${check} ${action.label} ${dim(did("removed"))}`);
636
+ } else if (action.kind === "remove-receipt") {
637
+ guardedRemove(action.path, { force: true });
638
+ } else {
639
+ console.log(` ${skip} ${action.label} ${dim(`(${action.detail})`)}`);
640
+ }
641
+ }
642
+ };
643
+ var removeFxInstructions = () => {
644
+ if (guardedInstructionBlockRemoval(fxAgentsMd())) {
645
+ console.log(` ${check} fx instructions ${dim(did("removed"))}`);
646
+ }
647
+ };
648
+ var removeCursorUserHook = (scope) => {
649
+ if (!existsSync6(CURSOR_USER_HOOKS)) return "absent";
650
+ let parsed2;
651
+ try {
652
+ parsed2 = JSON.parse(readFileSync4(CURSOR_USER_HOOKS, "utf-8"));
653
+ } catch {
654
+ return "failed";
655
+ }
656
+ if (!parsed2 || typeof parsed2 !== "object") return "failed";
657
+ const removal = withoutPusharyCursorHooks(parsed2.hooks ?? {}, scope);
658
+ if (removal.removed === 0) return "absent";
659
+ parsed2.hooks = removal.hooks;
660
+ try {
661
+ writeJson(CURSOR_USER_HOOKS, parsed2);
662
+ } catch {
663
+ return "failed";
664
+ }
665
+ if (dryRun) return "removed";
666
+ try {
667
+ const after = JSON.parse(readFileSync4(CURSOR_USER_HOOKS, "utf-8"));
668
+ if (withoutPusharyCursorHooks(after?.hooks ?? {}, scope).removed > 0) return "failed";
669
+ } catch {
670
+ return "failed";
671
+ }
672
+ return "removed";
673
+ };
674
+ var cleanSettingsFile = (path, label, scope) => {
675
+ const data = readJson(path);
676
+ if (!data) {
677
+ console.log(` ${skip} ${label} ${dim("(not found)")}`);
678
+ return;
679
+ }
680
+ const changed = removePusharySettings(data, scope);
681
+ if (changed) {
682
+ writeJson(path, data);
683
+ console.log(` ${check} ${label} ${dim(did("cleaned"))}`);
684
+ return;
685
+ }
686
+ const wouldHaveChanged = removePusharySettings(
687
+ JSON.parse(JSON.stringify(data))
688
+ );
689
+ console.log(
690
+ ` ${skip} ${label} ${dim(wouldHaveChanged ? "(kept: written by the Mac app)" : "(no pushary entries)")}`
691
+ );
692
+ };
693
+ var agentConfigDirectories = () => [
694
+ homedir5(),
695
+ dirname(CLAUDE_SETTINGS),
696
+ dirname(codexConfigToml()),
697
+ dirname(geminiSettings()),
698
+ dirname(CURSOR_USER_HOOKS),
699
+ openCodeConfigDir(),
700
+ fxConfigDir(),
701
+ ...vscodeSettingsTargets(existsSync6).map((settings) => dirname(settings))
702
+ ];
703
+ var restoreTakeoverReceipts = () => {
704
+ const targets = [
705
+ ["claude_code", CLAUDE_SETTINGS, CLAUDE_JSON, "mcpServers", "json"],
706
+ ["codex", codexHooksJson(), codexConfigToml(), "mcp_servers", "toml"],
707
+ ["gemini_cli", geminiSettings(), geminiSettings(), "mcpServers", "json"],
708
+ ["cursor", CURSOR_USER_HOOKS, null, null, null],
709
+ ["vscode", join6(VSCODE_PLUGIN_DIR, "hooks", "hooks.json"), vscodePluginMcp(), "mcpServers", "json"]
710
+ ];
711
+ for (const [agent, hookFile, mcpFile, mcpKey, mcpFormat] of targets) {
712
+ const receiptPath = hookTakeoverReceipt(agent);
713
+ if (!existsSync6(receiptPath)) continue;
714
+ try {
715
+ const receipt = readJson(receiptPath);
716
+ if (!isHookTakeoverReceipt(receipt) || receipt.agent !== agent) throw new Error("invalid receipt");
717
+ const document = existsSync6(hookFile) ? JSON.parse(readFileSync4(hookFile, "utf-8")) : {};
718
+ if (!document || typeof document !== "object" || Array.isArray(document)) throw new Error("invalid hooks file");
719
+ const appAlreadyOwns = hookOwnerOf(document) === "app";
720
+ const cliPositions = agent === "codex" ? codexHookPositions(document) : {};
721
+ restoreHookTakeover(document, receipt);
722
+ let mcpDocument = null;
723
+ let mcpChanged = false;
724
+ if (!appAlreadyOwns && receipt.mcpServer && mcpFile && mcpKey) {
725
+ if (mcpFile === hookFile) {
726
+ mcpDocument = document;
727
+ } else {
728
+ const raw = existsSync6(mcpFile) ? readFileSync4(mcpFile, "utf-8") : "";
729
+ mcpDocument = raw.trim() ? mcpFormat === "toml" ? parseTOML(raw) : JSON.parse(raw) : {};
730
+ }
731
+ mcpChanged = restoreMcpServer(mcpDocument, mcpKey, receipt.mcpServer);
732
+ }
733
+ let codexConfig = null;
734
+ let codexConfigChanged = false;
735
+ if (!appAlreadyOwns && agent === "codex") {
736
+ if (!mcpDocument) {
737
+ const configPath = codexConfigToml();
738
+ const raw = existsSync6(configPath) ? readFileSync4(configPath, "utf-8") : "";
739
+ mcpDocument = raw.trim() ? parseTOML(raw) : {};
740
+ }
741
+ codexConfig = mcpDocument;
742
+ codexConfigChanged = removeCodexHookTrust(codexConfig, hookFile, cliPositions);
743
+ codexConfigChanged = restoreCodexHookTrust(
744
+ codexConfig,
745
+ hookFile,
746
+ codexHookPositions(document),
747
+ receipt.codexTrust ?? {}
748
+ ) || codexConfigChanged;
749
+ }
750
+ if (mcpDocument && mcpFile !== hookFile && mcpFormat === "json" && mcpChanged) writeJson(mcpFile, mcpDocument);
751
+ if (codexConfig && (codexConfigChanged || mcpChanged)) guardedWrite(codexConfigToml(), stringifyTOML(codexConfig));
752
+ writeJson(hookFile, document);
753
+ guardedRemove(receiptPath, { force: true });
754
+ const status = dryRun ? "would restore Mac app wiring" : "restored Mac app wiring";
755
+ console.log(` ${check} ${agent.replace("_", " ")} hooks ${dim(`(${status})`)}`);
756
+ } catch {
757
+ incompleteClean = true;
758
+ keepCliRuntime = true;
759
+ console.log(` ${cross} ${agent.replace("_", " ")} hooks ${dim("(could not restore Mac app wiring; receipt and CLI kept)")}`);
760
+ }
761
+ }
762
+ };
763
+ var main = async () => {
764
+ console.log();
765
+ console.log(` ${bold("Pushary Clean")}`);
766
+ console.log(` ${dim("Removes all Pushary configuration")}`);
767
+ console.log();
768
+ try {
769
+ rejectUnknownFlags(
770
+ process.argv.slice(2).filter((a) => a !== "clean"),
771
+ ["--dry-run", "--everything", "--yes", "-y", "--help", "-h"]
772
+ );
773
+ } catch (err) {
774
+ process.stderr.write(` ! ${err instanceof Error ? err.message : String(err)}
775
+ `);
776
+ process.exit(EXIT.USAGE);
777
+ }
778
+ dryRun = process.argv.includes("--dry-run");
779
+ const everything = process.argv.includes("--everything");
780
+ if (dryRun) {
781
+ console.log(` ${bold("Dry run")} ${dim("- nothing will be changed")}`);
782
+ console.log();
783
+ }
784
+ const assumeYes = dryRun || process.argv.includes("--yes") || process.argv.includes("-y");
785
+ if (!assumeYes && !process.stdin.isTTY) {
786
+ console.log(` ${yellow("!")} Non-interactive shell. Re-run with --yes to confirm removal.`);
787
+ process.exit(1);
788
+ }
789
+ const proceed = assumeYes || await confirm({ message: "Remove all Pushary configuration?", default: false });
790
+ if (!proceed) {
791
+ console.log(` ${dim("Cancelled.")}`);
792
+ process.exit(0);
793
+ }
794
+ console.log();
795
+ const presence = macAppPresence();
796
+ const keepShared = presence === "present" && !everything;
797
+ const scope = removalScope(keepShared, keepShared);
798
+ if (keepShared) {
799
+ console.log(` ${dim("The Mac app is installed, so its wiring and the state it owns are left alone.")}`);
800
+ console.log(` ${dim("To remove that too, re-run with")} --everything`);
801
+ } else if (presence === "gone") {
802
+ console.log(` ${dim("The Mac app is gone, so the wiring it left behind is removed too.")}`);
803
+ } else if (everything && presence === "present") {
804
+ console.log(` ${dim("Removing the Mac app's wiring and state as well.")}`);
805
+ }
806
+ if (everything) {
807
+ const quit = quitMacApp(guards, { platform: process.platform, dryRun, running: runningMacAppPIDs, waitForExit: waitForMacAppExit });
808
+ if (quit === "quit") {
809
+ console.log(` ${check} Pushary ${dim(did("quit"))} ${dim("(running, and its watcher puts back what this removes)")}`);
810
+ } else if (quit === "running") {
811
+ incompleteClean = true;
812
+ console.log(` ${cross} Pushary ${dim("would not quit, and it puts back what this removes")}`);
813
+ console.log(` ${dim("Quit it from the notch menu, then re-run.")}`);
814
+ process.exit(EXIT.FAILED);
815
+ }
816
+ }
817
+ if (presence !== "absent") console.log();
818
+ const restoredByTakeover = new Set(
819
+ ["claude_code", "codex", "gemini_cli"].filter((agent) => existsSync6(hookTakeoverReceipt(agent)))
820
+ );
821
+ if (keepShared) restoreTakeoverReceipts();
822
+ const sharedMcpKeys = {
823
+ appKey: appAgentKey(readJson(bridgeAgentKey())),
824
+ setupKey: setupReceiptKey(readJson(CLAUDE_MCP_RECEIPT))
825
+ };
826
+ const macAppKeeps = (agent, entryKey) => keepShared && (restoredByTakeover.has(agent) || macAppKeepsMcpEntry(entryKey, sharedMcpKeys));
827
+ if (keepCliRuntime) {
828
+ console.log(` ${yellow(bold("Clean stopped before changing provider configuration."))}`);
829
+ console.log(` ${dim("Fix the hooks file or receipt above, then re-run")} npx @pushary/agent-hooks@latest clean`);
830
+ process.exit(EXIT.FAILED);
831
+ }
832
+ const daemonRemoved = await guardedDaemonRemoval();
833
+ if (!daemonRemoved) {
834
+ console.log(` ${cross} Background service ${dim("(could not stop or uninstall)")}`);
835
+ console.log(` ${yellow(bold("Clean stopped before changing provider configuration or local state."))}`);
836
+ console.log(` ${dim("Fix the background service, then re-run")} npx @pushary/agent-hooks@latest clean`);
837
+ console.log();
838
+ process.exit(EXIT.FAILED);
839
+ }
840
+ console.log(` ${check} Background service ${dim(did("uninstalled"))}`);
841
+ const claudeJson2 = readJson(CLAUDE_JSON);
842
+ const keepClaudeMcp = macAppKeeps("claude_code", mcpServerBearer(claudeJson2?.mcpServers));
843
+ const claudeHasPusharyEntry = () => Boolean(claudeJson2?.mcpServers?.pushary);
844
+ const claudeHadPusharyEntry = claudeHasPusharyEntry();
845
+ const rawReceipt = readJson(CLAUDE_MCP_RECEIPT);
846
+ const receipt = isClaudeMcpSnapshot(rawReceipt) ? rawReceipt : null;
847
+ const receiptUnreadable = existsSync6(CLAUDE_MCP_RECEIPT) && !receipt;
848
+ if (keepClaudeMcp) {
849
+ console.log(` ${skip} Claude Code MCP servers ${dim("(kept: the Mac app uses it)")}`);
850
+ } else if (claudeJson2) {
851
+ if (receiptUnreadable) {
852
+ if (!keepShared) incompleteClean = true;
853
+ console.log(` ${skip} Claude Code MCP servers ${dim("(setup receipt is unreadable; left unchanged)")}`);
854
+ } else if (receipt && restoreClaudeMcpServer(claudeJson2, receipt) || !receipt && removeClaudeMcpServers(claudeJson2)) {
855
+ writeJson(CLAUDE_JSON, claudeJson2);
856
+ console.log(` ${check} Claude Code MCP servers ${dim(receipt ? "(previous entry restored)" : "(removed from ~/.claude.json)")}`);
857
+ if (keepShared && receipt) guardedRemove(CLAUDE_MCP_RECEIPT, { force: true });
858
+ } else {
859
+ if (receipt && !keepShared) incompleteClean = true;
860
+ console.log(` ${skip} Claude Code MCP servers ${dim(receipt ? "(changed after setup; left unchanged)" : "(no pushary entries)")}`);
861
+ }
862
+ } else if (existsSync6(CLAUDE_MCP_RECEIPT)) {
863
+ incompleteClean = true;
864
+ console.log(` ${skip} Claude Code MCP servers ${dim("(config missing; setup receipt kept for retry)")}`);
865
+ } else {
866
+ console.log(` ${skip} Claude Code MCP servers ${dim("(not found)")}`);
867
+ }
868
+ if (!keepClaudeMcp && !receiptUnreadable) {
869
+ const rewrites = claudeBackupRewrites(receipt);
870
+ for (const rewrite of rewrites) guardedWrite(rewrite.path, rewrite.contents);
871
+ if (rewrites.length > 0) {
872
+ console.log(` ${check} Claude Code backups ${dim(did("cleaned"))} ${dim(`(the Pushary entry and its key, in ${rewrites.length})`)}`);
873
+ }
874
+ }
875
+ const claudeScope = removalScope(keepShared && !(claudeHadPusharyEntry && !claudeHasPusharyEntry()), keepShared);
876
+ cleanSettingsFile(CLAUDE_SETTINGS, "Claude Code settings", claudeScope);
877
+ cleanSettingsFile(CLAUDE_SETTINGS_LOCAL, "Claude Code settings.local", claudeScope);
878
+ const cursorData = keepShared ? null : readJson(CURSOR_MCP);
879
+ if (keepShared) {
880
+ console.log(` ${skip} Cursor MCP config ${dim("(kept: the Mac app uses it)")}`);
881
+ } else if (cursorData) {
882
+ const mcpServers = cursorData.mcpServers;
883
+ if (mcpServers?.pushary) {
884
+ delete mcpServers.pushary;
885
+ writeJson(CURSOR_MCP, cursorData);
886
+ console.log(` ${check} Cursor MCP config ${dim(did("cleaned"))}`);
887
+ } else {
888
+ console.log(` ${skip} Cursor MCP config ${dim("(no pushary entries)")}`);
889
+ }
890
+ } else {
891
+ console.log(` ${skip} Cursor MCP config ${dim("(not found)")}`);
892
+ }
893
+ const cursorGate = removeCursorUserHook(scope);
894
+ if (cursorGate === "removed") {
895
+ console.log(` ${check} Cursor gate ${dim("(removed from ~/.cursor/hooks.json)")}`);
896
+ } else if (cursorGate === "absent") {
897
+ console.log(` ${skip} Cursor gate ${dim("(not registered)")}`);
898
+ } else {
899
+ incompleteClean = true;
900
+ console.log(` ${cross} Cursor gate ${dim("(could not read or write ~/.cursor/hooks.json)")}`);
901
+ console.log(` ${yellow("!")} The gate is registered failClosed, so matched commands would be blocked.`);
902
+ console.log(` ${dim("Keeping the plugin in place so Cursor keeps working. Fix the file, then re-run clean.")}`);
903
+ }
904
+ if (cursorGate === "failed") {
905
+ console.log(` ${skip} Cursor plugin ${dim("(kept: removing it now would block matched commands)")}`);
906
+ } else if (existsSync6(CURSOR_PLUGIN_DIR)) {
907
+ guardedRemove(CURSOR_PLUGIN_DIR);
908
+ console.log(` ${check} Cursor plugin ${dim("(removed from ~/.cursor/plugins/local)")}`);
909
+ } else {
910
+ console.log(` ${skip} Cursor plugin ${dim("(not installed)")}`);
911
+ }
912
+ if (cursorGate !== "failed") {
913
+ const stale = cursorPluginLeftovers();
914
+ for (const path of stale) guardedRemove(path);
915
+ if (stale.length > 0) {
916
+ console.log(` ${check} Cursor plugin copies ${dim(did("removed"))} ${dim(`(${stale.length}, each holding the key of an earlier install)`)}`);
917
+ }
918
+ }
919
+ if (keepShared) {
920
+ console.log(` ${skip} VS Code plugin ${dim("(kept: the Mac app writes the same plugin directory)")}`);
921
+ }
922
+ for (const settingsPath of keepShared ? [] : vscodeSettingsTargets(existsSync6)) {
923
+ const label = `VS Code plugin ${dim(`(${shortenHome(settingsPath)})`)}`;
924
+ if (!existsSync6(settingsPath)) {
925
+ console.log(` ${skip} ${label} ${dim("(no settings.json)")}`);
926
+ continue;
927
+ }
928
+ const result = unregisterPluginLocation(readFileSync4(settingsPath, "utf-8"), VSCODE_PLUGIN_DIR);
929
+ if (result.kind === "removed") {
930
+ guardedWrite(settingsPath, result.content);
931
+ console.log(` ${check} ${label} ${dim(did("unregistered"))}`);
932
+ } else if (result.kind === "manual") {
933
+ console.log(` ${skip} ${label} ${yellow("(remove the chat.pluginLocations entry by hand)")}`);
934
+ } else {
935
+ console.log(` ${skip} ${label} ${dim("(no pushary entry)")}`);
936
+ }
937
+ }
938
+ if (!keepShared && existsSync6(VSCODE_PLUGIN_DIR)) {
939
+ guardedRemove(VSCODE_PLUGIN_DIR);
940
+ console.log(` ${check} VS Code plugin files ${dim(did("removed"))}`);
941
+ }
942
+ if (keepShared) {
943
+ console.log(` ${skip} Editor extension ${dim("(kept: installed and updated by the Mac app)")}`);
944
+ } else {
945
+ const removals = editorExtensionRemovals();
946
+ for (const removal of removals) {
947
+ if (removal.record) guardedWrite(removal.record.path, removal.record.contents);
948
+ for (const folder of removal.folders) guardedRemove(folder);
949
+ console.log(` ${check} Editor extension ${dim(`(${did("removed")} from ${shortenHome(removal.directory)})`)}`);
950
+ }
951
+ if (removals.length === 0) {
952
+ console.log(` ${skip} Editor extension ${dim("(not installed in Cursor or VS Code)")}`);
953
+ }
954
+ }
955
+ if (keepShared) {
956
+ console.log(` ${skip} Skill directory ${dim("(kept: the Mac app uses it)")}`);
957
+ } else if (existsSync6(SKILL_DIR)) {
958
+ guardedRemove(SKILL_DIR);
959
+ console.log(` ${check} Skill directory ${dim(did("removed"))}`);
960
+ } else {
961
+ console.log(` ${skip} Skill directory ${dim("(not found)")}`);
962
+ }
963
+ const codexConfig = codexConfigToml();
964
+ const codexHooksJson2 = codexHooksJson();
965
+ const codexHooksData = readJson(codexHooksJson2);
966
+ const codexPositions = codexHookPositions(codexHooksData ?? void 0);
967
+ try {
968
+ const config = readFileSync4(codexConfig, "utf-8");
969
+ const parsed2 = parseTOML(config);
970
+ const trustRemoved = hookOwnerOf(codexHooksData) === "app" && keepShared ? false : removeCodexHookTrust(parsed2, codexHooksJson2, codexPositions);
971
+ const hadPushary = config.includes("pushary");
972
+ if (hadPushary || trustRemoved) {
973
+ const macAppKeepsCodexMcp = macAppKeeps("codex", codexMcpKey(parsed2));
974
+ if (!macAppKeepsCodexMcp) {
975
+ const mcpServers = parsed2.mcp_servers ?? {};
976
+ delete mcpServers.pushary;
977
+ if (Object.keys(mcpServers).length === 0) delete parsed2.mcp_servers;
978
+ else parsed2.mcp_servers = mcpServers;
979
+ }
980
+ if (Array.isArray(parsed2.notify)) {
981
+ parsed2.notify = parsed2.notify.filter((n) => typeof n !== "string" || !n.includes("pushary-codex"));
982
+ if (parsed2.notify.length === 0) delete parsed2.notify;
983
+ }
984
+ guardedWrite(codexConfig, stringifyTOML(parsed2));
985
+ const what = trustRemoved ? "cleaned, hook trust removed" : "cleaned";
986
+ console.log(` ${check} Codex config ${dim(did(what))}`);
987
+ if (macAppKeepsCodexMcp && parsed2.mcp_servers?.pushary) {
988
+ console.log(` ${skip} Codex MCP server ${dim("(kept: the Mac app uses it)")}`);
989
+ }
990
+ } else {
991
+ console.log(` ${skip} Codex config ${dim("(no pushary entries)")}`);
992
+ }
993
+ } catch {
994
+ console.log(` ${skip} Codex config ${dim("(not found)")}`);
995
+ }
996
+ if (codexHooksData) {
997
+ if (removeCodexHooks(codexHooksData, scope)) {
998
+ writeJson(codexHooksJson2, codexHooksData);
999
+ console.log(` ${check} Codex hooks ${dim("(removed from ~/.codex/hooks.json)")}`);
1000
+ } else {
1001
+ console.log(` ${skip} Codex hooks ${dim("(no pushary entries)")}`);
1002
+ }
1003
+ } else {
1004
+ console.log(` ${skip} Codex hooks ${dim("(not found)")}`);
1005
+ }
1006
+ const codexAgentsMd2 = codexAgentsMd();
1007
+ if (keepShared) {
1008
+ console.log(` ${skip} Codex AGENTS.md ${dim("(kept: the Mac app uses it)")}`);
1009
+ } else if (guardedInstructionBlockRemoval(codexAgentsMd2)) {
1010
+ console.log(` ${check} Codex AGENTS.md ${dim("(removed Pushary block)")}`);
1011
+ } else {
1012
+ console.log(` ${skip} Codex AGENTS.md ${dim("(no pushary block)")}`);
1013
+ }
1014
+ const codexSkillDir2 = codexSkillDir();
1015
+ if (keepShared) {
1016
+ console.log(` ${skip} Codex skill directory ${dim("(kept: the Mac app uses it)")}`);
1017
+ } else if (existsSync6(codexSkillDir2)) {
1018
+ guardedRemove(codexSkillDir2);
1019
+ console.log(` ${check} Codex skill directory ${dim(did("removed"))}`);
1020
+ } else {
1021
+ console.log(` ${skip} Codex skill directory ${dim("(not found)")}`);
1022
+ }
1023
+ for (const profile of HOOK_COMPATIBLE_PROFILES) {
1024
+ const edits = [
1025
+ [compatibleConfigFile(profile), (config) => removeCompatibleHooks(config, profile)],
1026
+ ...compatibleMcpFile(profile) ? [[compatibleMcpFile(profile), (config) => removeCompatibleMcpServer(config, profile)]] : [],
1027
+ ...compatiblePermissionRuleFile(profile) ? [[compatiblePermissionRuleFile(profile), removePusharyToolPermissions]] : []
1028
+ ];
1029
+ const byPath = /* @__PURE__ */ new Map();
1030
+ for (const [path, edit] of edits) byPath.set(path, [...byPath.get(path) ?? [], edit]);
1031
+ for (const [path, removals] of byPath) {
1032
+ const config = readJson(path);
1033
+ if (!config) {
1034
+ console.log(` ${skip} ${profile.label} ${shortenHome(path)} ${dim("(not found)")}`);
1035
+ continue;
1036
+ }
1037
+ if (removals.reduce((changed, remove) => remove(config) || changed, false)) {
1038
+ writeJson(path, config);
1039
+ console.log(` ${check} ${profile.label} ${shortenHome(path)} ${dim(did("cleaned"))}`);
1040
+ } else {
1041
+ console.log(` ${skip} ${profile.label} ${shortenHome(path)} ${dim("(no pushary entries)")}`);
1042
+ }
1043
+ }
1044
+ const instructions = compatibleInstructionsFile(profile);
1045
+ if (!instructions) continue;
1046
+ if (guardedInstructionBlockRemoval(instructions)) {
1047
+ console.log(` ${check} ${profile.label} ${shortenHome(instructions)} ${dim("(removed Pushary block)")}`);
1048
+ } else {
1049
+ console.log(` ${skip} ${profile.label} ${shortenHome(instructions)} ${dim("(no pushary block)")}`);
1050
+ }
1051
+ }
1052
+ const geminiSettingsPath = geminiSettings();
1053
+ const geminiSettings2 = readJson(geminiSettingsPath);
1054
+ if (geminiSettings2) {
1055
+ if (removeGeminiSettings(geminiSettings2, removalScope(macAppKeeps("gemini_cli", mcpServerBearer(geminiSettings2.mcpServers)), keepShared))) {
1056
+ writeJson(geminiSettingsPath, geminiSettings2);
1057
+ console.log(` ${check} Gemini CLI settings ${dim(did("cleaned"))}`);
1058
+ } else {
1059
+ console.log(` ${skip} Gemini CLI settings ${dim("(no pushary entries)")}`);
1060
+ }
1061
+ } else {
1062
+ console.log(` ${skip} Gemini CLI settings ${dim("(not found)")}`);
1063
+ }
1064
+ const geminiMd2 = geminiMd();
1065
+ if (keepShared) {
1066
+ console.log(` ${skip} Gemini GEMINI.md ${dim("(kept: the Mac app uses it)")}`);
1067
+ } else if (guardedInstructionBlockRemoval(geminiMd2)) {
1068
+ console.log(` ${check} Gemini GEMINI.md ${dim("(removed Pushary block)")}`);
1069
+ } else {
1070
+ console.log(` ${skip} Gemini GEMINI.md ${dim("(no pushary block)")}`);
1071
+ }
1072
+ const openCodePlugin = openCodePluginFile();
1073
+ const openCodePluginContents = existsSync6(openCodePlugin) ? readFileSync4(openCodePlugin, "utf-8") : null;
1074
+ if (openCodePluginContents === null) {
1075
+ console.log(` ${skip} OpenCode plugin ${dim("(not installed)")}`);
1076
+ } else if (!isOpenCodePluginOurs(openCodePluginContents)) {
1077
+ console.log(` ${skip} OpenCode plugin ${dim("(a pushary.js we did not write)")}`);
1078
+ } else if (openCodePluginContents.includes(HOOK_LOCATOR_BINARY) && keepShared) {
1079
+ console.log(` ${skip} OpenCode plugin ${dim("(kept: written by the Mac app)")}`);
1080
+ } else {
1081
+ guardedRemove(openCodePlugin, { force: true });
1082
+ console.log(` ${check} OpenCode plugin ${dim(did("removed"))}`);
1083
+ const sibling = join6(openCodePluginDir(), OPENCODE_PLUGIN_SIBLING);
1084
+ const remaining = (() => {
1085
+ try {
1086
+ return readdirSync4(openCodePluginDir()).filter((name) => name !== OPENCODE_PLUGIN_SIBLING);
1087
+ } catch {
1088
+ return [];
1089
+ }
1090
+ })();
1091
+ const siblingIsOurs = existsSync6(sibling) && readFileSync4(sibling, "utf-8") === OPENCODE_PLUGIN_SIBLING_CONTENTS;
1092
+ if (siblingIsOurs && !hasForeignPlugins(remaining)) {
1093
+ guardedRemove(sibling, { force: true });
1094
+ console.log(` ${check} OpenCode plugin sibling ${dim(did("removed"))}`);
1095
+ }
1096
+ }
1097
+ const openCodeConfig = keepShared ? null : readJson(openCodeConfigFile());
1098
+ if (keepShared) {
1099
+ console.log(` ${skip} OpenCode MCP ${dim("(kept: the Mac app uses it)")}`);
1100
+ } else if (openCodeConfig) {
1101
+ if (removeOpenCodeMcpServer(openCodeConfig)) {
1102
+ writeJson(openCodeConfigFile(), openCodeConfig);
1103
+ console.log(` ${check} OpenCode MCP ${dim(did("removed"))}`);
1104
+ } else {
1105
+ console.log(` ${skip} OpenCode MCP ${dim("(no pushary entries)")}`);
1106
+ }
1107
+ } else {
1108
+ console.log(` ${skip} OpenCode MCP ${dim("(not found)")}`);
1109
+ }
1110
+ if (keepShared) {
1111
+ console.log(` ${skip} OpenCode AGENTS.md ${dim("(kept: the Mac app uses it)")}`);
1112
+ } else if (guardedInstructionBlockRemoval(openCodeInstructionsFile())) {
1113
+ console.log(` ${check} OpenCode AGENTS.md ${dim("(removed Pushary block)")}`);
1114
+ } else {
1115
+ console.log(` ${skip} OpenCode AGENTS.md ${dim("(no pushary block)")}`);
1116
+ }
1117
+ removeFxConfiguration();
1118
+ removeFxInstructions();
1119
+ const hermesPython = resolveHermesPython();
1120
+ if (hermesPython) {
1121
+ const configRun = runHermesConfigScript(hermesPython, dryRun ? "clean-preview" : "clean", HERMES_CONFIG_RECEIPT);
1122
+ if (configRun === "failed") {
1123
+ incompleteClean = true;
1124
+ console.log(` ${skip} Hermes config ${dim("(could not update config.yaml)")}`);
1125
+ } else {
1126
+ if (configRun === "changed") {
1127
+ if (dryRun) wouldNote("rewrite", HERMES_CONFIG_FILE);
1128
+ console.log(` ${check} Hermes config ${dim(did("removed"))} ${dim("(Pushary settings)")}`);
1129
+ } else {
1130
+ console.log(` ${skip} Hermes config ${dim("(no Pushary settings)")}`);
1131
+ }
1132
+ if (readHermesStatus(hermesPython)?.pluginInstalled === false) {
1133
+ console.log(` ${skip} Hermes plugin ${dim("(not installed)")}`);
1134
+ } else {
1135
+ try {
1136
+ guards.execFile(hermesPython, ["-m", "pip", "uninstall", "-y", HERMES_PIP_PACKAGE], { stdio: "pipe", timeout: 6e4 }, `pip uninstall ${HERMES_PIP_PACKAGE}`);
1137
+ console.log(` ${check} Hermes plugin ${dim(did("uninstalled"))}`);
1138
+ } catch {
1139
+ incompleteClean = true;
1140
+ console.log(` ${skip} Hermes plugin ${dim("(could not uninstall; setup receipt kept for retry)")}`);
1141
+ }
1142
+ }
1143
+ }
1144
+ } else if (existsSync6(HERMES_CONFIG_RECEIPT)) {
1145
+ incompleteClean = true;
1146
+ console.log(` ${skip} Hermes ${dim("(runtime not found; setup receipt kept for retry)")}`);
1147
+ } else {
1148
+ console.log(` ${skip} Hermes ${dim("(not found)")}`);
1149
+ }
1150
+ for (const shellFile of SHELL_FILES) {
1151
+ try {
1152
+ const content = readFileSync4(shellFile, "utf-8");
1153
+ if (content.includes("PUSHARY_API_KEY")) {
1154
+ const isOurExport = (line) => /^\s*export\s+PUSHARY_API_KEY=['"]?pk_[A-Za-z0-9]+\.[A-Za-z0-9]+['"]?\s*$/.test(line) || isDisabledExport(line);
1155
+ const lines = content.split("\n");
1156
+ const declined = lines.filter((l) => l.includes("PUSHARY_API_KEY") && !isOurExport(l));
1157
+ const cleaned = lines.filter((l) => !isOurExport(l)).join("\n");
1158
+ const name = shellFile.split("/").pop();
1159
+ if (cleaned !== content) {
1160
+ const backup = `${shellFile}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
1161
+ if (!dryRun) copyFileSync(shellFile, backup);
1162
+ guardedWrite(shellFile, cleaned);
1163
+ const confirmed = !dryRun && readFileSync4(shellFile, "utf-8") === cleaned;
1164
+ if (confirmed) guardedRemove(backup, { force: true });
1165
+ const kept = confirmed ? "" : `, backup at ${backup}`;
1166
+ console.log(` ${check} ${name} ${dim(`(removed the export${kept})`)}`);
1167
+ }
1168
+ for (const line of declined) {
1169
+ if ((line.match(/pk_[A-Za-z0-9_]+\.[A-Za-z0-9_]+/g) ?? []).some(isValidApiKey)) {
1170
+ incompleteClean = true;
1171
+ console.log(` ${cross} ${name} ${dim("still sets PUSHARY_API_KEY, in a form this command will not rewrite:")}`);
1172
+ console.log(` ${dim(redactSecrets(line.trim()))}`);
1173
+ console.log(` ${dim("Remove it yourself. A setup run would sign this machine in as that account.")}`);
1174
+ } else {
1175
+ console.log(` ${skip} ${name} ${dim(`left a line referencing PUSHARY_API_KEY: ${redactSecrets(line.trim())}`)}`);
1176
+ }
1177
+ }
1178
+ }
1179
+ } catch {
1180
+ }
1181
+ }
1182
+ const aliasRemovedFrom = guardedAliasRemoval();
1183
+ if (aliasRemovedFrom.length > 0) {
1184
+ console.log(` ${check} claude alias ${dim(`(removed from ${aliasRemovedFrom.map((p) => p.split("/").pop()).join(", ")})`)}`);
1185
+ } else {
1186
+ console.log(` ${skip} claude alias ${dim("(not set)")}`);
1187
+ }
1188
+ const leakedTemps = configTempLeftovers(agentConfigDirectories(), Date.now());
1189
+ for (const temp of leakedTemps) guardedRemove(temp, { force: true });
1190
+ if (leakedTemps.length > 0) {
1191
+ console.log(` ${check} Leftover temp files ${dim(`(${dryRun ? "would remove" : "removed"} ${leakedTemps.length} left by an interrupted config write)`)}`);
1192
+ }
1193
+ if (existsSync6(PUSHARY_DIR)) {
1194
+ let removed = 0;
1195
+ const kept = [];
1196
+ for (const entry of readdirSync4(PUSHARY_DIR)) {
1197
+ if (keepShared && isAppOwned(entry) || entry === "setup" && (keepShared || incompleteClean)) {
1198
+ kept.push(entry);
1199
+ continue;
1200
+ }
1201
+ guardedRemove(join6(PUSHARY_DIR, entry), { force: true });
1202
+ removed++;
1203
+ }
1204
+ if (removed > 0) {
1205
+ const what = `${removed} entr${removed === 1 ? "y" : "ies"} from ~/.pushary: stored API key, ledger`;
1206
+ console.log(` ${check} Local state ${dim(dryRun ? `(would remove ${what})` : `(removed ${what})`)}`);
1207
+ } else {
1208
+ console.log(` ${skip} Local state ${dim("(nothing of ours in ~/.pushary)")}`);
1209
+ }
1210
+ if (kept.length > 0) {
1211
+ console.log(` ${dim(`left ${kept.sort().join(", ")} for ${keepShared ? "the Mac app" : "a cleanup retry"}`)}`);
1212
+ }
1213
+ } else {
1214
+ console.log(` ${skip} Local state ${dim("(~/.pushary not found)")}`);
1215
+ }
1216
+ try {
1217
+ let swept = 0;
1218
+ for (const f of readdirSync4(tmpdir())) {
1219
+ if (!f.startsWith("pushary-")) continue;
1220
+ try {
1221
+ guardedRemove(join6(tmpdir(), f), { force: true });
1222
+ swept++;
1223
+ } catch {
1224
+ }
1225
+ }
1226
+ if (swept > 0) console.log(` ${check} Temp state ${dim(`(${swept} pushary entries swept)`)}`);
1227
+ } catch {
1228
+ }
1229
+ if (everything) {
1230
+ const store = skillsStoreRemoval();
1231
+ if (store.skillDir) guardedRemove(store.skillDir);
1232
+ if (store.lock) guardedWrite(store.lock.path, store.lock.contents);
1233
+ if (store.skillDir || store.lock) {
1234
+ console.log(` ${check} skills.sh registration ${dim(did("removed"))} ${dim("(~/.agents)")}`);
1235
+ }
1236
+ const leftovers = process.platform === "darwin" ? removeDeveloperLeftovers(guards, leakedTestDefaultsDomains(), registeredPusharyBundles()) : 0;
1237
+ if (leftovers > 0) {
1238
+ console.log(` ${check} Developer leftovers ${dim(did("removed"))} ${dim(`(${leftovers} leaked test preferences and stale app registrations)`)}`);
1239
+ }
1240
+ const { removed, failed } = process.platform === "darwin" ? removeMacAppState(guards, storedPusharyKeychainServices, homedir5(), liveMacStatePresence) : { removed: 0, failed: 0 };
1241
+ if (failed > 0) {
1242
+ incompleteClean = true;
1243
+ console.log(` ${cross} Mac keychain ${dim(`(${failed} operations failed; unlock your keychain and re-run)`)}`);
1244
+ }
1245
+ if (removed > 0) {
1246
+ console.log(` ${check} Mac app state ${dim(did("removed"))} ${dim(`(${removed} across the keychain, preferences and caches)`)}`);
1247
+ } else if (failed === 0) {
1248
+ console.log(` ${skip} Mac app state ${dim("(nothing of ours in the keychain, preferences or caches)")}`);
1249
+ }
1250
+ }
1251
+ if (!npmIsOnPath()) {
1252
+ console.log(` ${skip} Global package ${dim(`(npm not found; remove ${CLI_PACKAGE_NAMES.join(" or ")} with the tool that installed it)`)}`);
1253
+ } else {
1254
+ const removal = removeGlobalCliPackages(guards, { dryRun });
1255
+ if (removal.kind === "not_installed") {
1256
+ console.log(` ${skip} Global package ${dim("(not installed)")}`);
1257
+ } else if (removal.kind === "removed") {
1258
+ console.log(` ${check} Global package ${dim(did("uninstalled"))} ${dim(`(${removal.names.join(", ")})`)}`);
1259
+ } else {
1260
+ incompleteClean = true;
1261
+ console.log(` ${cross} Global package ${dim(`(npm uninstall failed; run npm uninstall -g ${removal.names.join(" ")})`)}`);
1262
+ }
1263
+ }
1264
+ console.log();
1265
+ if (incompleteClean) {
1266
+ console.log(` ${yellow(bold("Clean finished with something left behind."))}`);
1267
+ console.log(` ${dim("See the \u2717 above. Fix it, then re-run")} npx @pushary/agent-hooks@latest clean`);
1268
+ console.log(` ${dim("Still stuck? Send this output to")} ${SUPPORT_EMAIL} ${dim("or")} ${SUPPORT_DISCORD}`);
1269
+ } else if (everything) {
1270
+ console.log(` ${green(bold("Clean complete. Nothing of ours is left in any agent config."))}`);
1271
+ const bundle = installedPusharyBundles().find(existsSync6);
1272
+ if (bundle) {
1273
+ console.log(` ${dim("The app itself is still installed. Drag")} ${bundle} ${dim("to the Trash to finish.")}`);
1274
+ }
1275
+ console.log(` ${dim("Signing in as someone else? Sign out at pushary.com in your browser first.")}`);
1276
+ console.log(` ${dim("The app and")} pushary login ${dim("both sign in through it, and would pick the old account back up.")}`);
1277
+ } else {
1278
+ console.log(` ${green(bold("Clean complete."))}`);
1279
+ if (keepShared) {
1280
+ console.log(` ${dim("The Mac app's wiring and its stored key are still here. Re-run with")} --everything ${dim("to remove them.")}`);
1281
+ }
1282
+ }
1283
+ console.log(` ${dim("Run")} npx @pushary/agent-hooks@latest setup ${dim("to reinstall.")}`);
1284
+ const exportedKey = process.env.PUSHARY_API_KEY?.trim();
1285
+ if (aliasRemovedFrom.length > 0 || exportedKey) {
1286
+ console.log();
1287
+ console.log(` ${yellow("!")} ${bold("In your current terminal")}`);
1288
+ }
1289
+ if (exportedKey) {
1290
+ console.log(` ${dim("PUSHARY_API_KEY is still exported here. Run")} ${bold("unset PUSHARY_API_KEY")}`);
1291
+ console.log(` ${dim("(or open a new terminal) before setup, or it signs back in as the old account.")}`);
1292
+ }
1293
+ if (aliasRemovedFrom.length > 0) {
1294
+ console.log(` ${dim("It still has the")} ${bold("claude")} ${dim("alias loaded from before. Run")} ${bold("unalias claude")}`);
1295
+ console.log(` ${dim("(or open a new terminal) so")} ${bold("claude")} ${dim("runs Claude Code again.")}`);
1296
+ }
1297
+ console.log();
1298
+ if (incompleteClean) process.exit(EXIT.FAILED);
1299
+ };
1300
+ main();