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,238 @@
1
+ import {
2
+ addClaudeMcpServer,
3
+ addPusharyToolPermissions,
4
+ isPusharyHook,
5
+ removeClaudeMcpServers
6
+ } from "./chunk-AXPCESYO.js";
7
+ import {
8
+ CLAUDE_HOOK_EVENTS,
9
+ addGeminiMcpServer,
10
+ removeGeminiMcpServer
11
+ } from "./chunk-3EVPNIBE.js";
12
+ import {
13
+ CLAUDE_CODE_AGENT,
14
+ parseApplyPatchFiles
15
+ } from "./chunk-CULUZTWQ.js";
16
+ import {
17
+ guardBinary,
18
+ holdBudgetOf,
19
+ registerHookEntry
20
+ } from "./chunk-A2A2YTMG.js";
21
+ import {
22
+ PHONE_ANSWERED_TOOLS,
23
+ hookCompatibleProfile,
24
+ hookCompatibleProfileBySource,
25
+ toToolLookup,
26
+ withHookHoldBudget
27
+ } from "./chunk-NQTHFF4W.js";
28
+
29
+ // src/compatible-config.ts
30
+ import { homedir } from "os";
31
+ import { isAbsolute, join, resolve } from "path";
32
+ var AGENT_FLAG = "--agent";
33
+ var SOURCE_FLAG = "--source";
34
+ var agentArgument = (profile) => `${AGENT_FLAG}=${profile.agentType}`;
35
+ var profileFromArgv = (argv) => {
36
+ const named = namedAgent(argv);
37
+ if (named !== void 0) return hookCompatibleProfile(named);
38
+ const source = namedSource(argv);
39
+ return source === void 0 ? void 0 : hookCompatibleProfileBySource(source);
40
+ };
41
+ var activeIdentity = (argv) => {
42
+ const profile = profileFromArgv(argv);
43
+ return profile ? { type: profile.agentType, label: profile.label } : CLAUDE_CODE_AGENT;
44
+ };
45
+ var namedAgent = (argv) => {
46
+ const prefix = `${AGENT_FLAG}=`;
47
+ return argv.find((argument) => argument.startsWith(prefix))?.slice(prefix.length);
48
+ };
49
+ var namedSource = (argv) => {
50
+ const inline = argv.find((argument) => argument.startsWith(`${SOURCE_FLAG}=`));
51
+ if (inline) return inline.slice(SOURCE_FLAG.length + 1);
52
+ const index = argv.indexOf(SOURCE_FLAG);
53
+ return index === -1 ? void 0 : argv[index + 1] ?? "";
54
+ };
55
+ var activeHookSource = (argv) => {
56
+ const named = namedAgent(argv);
57
+ if (named !== void 0) return hookCompatibleProfile(named)?.source ?? null;
58
+ const source = namedSource(argv);
59
+ if (source === void 0) return "claude";
60
+ return source === "claude" ? "claude" : hookCompatibleProfileBySource(source)?.source ?? null;
61
+ };
62
+ var PATCH_INPUT_KEYS = ["command", "patch", "input"];
63
+ var patchedFiles = (toolInput) => [...new Set(PATCH_INPUT_KEYS.flatMap((key) => parseApplyPatchFiles(toolInput[key])))];
64
+ var lookupFor = (profile, input) => {
65
+ const absolute = (path) => input.cwd && !isAbsolute(path) ? resolve(input.cwd, path) : path;
66
+ const toolInput = input.tool_input ?? {};
67
+ const lookup = toToolLookup(profile, input.tool_name ?? "", toolInput, absolute);
68
+ if (!lookup.unreadable) return lookup;
69
+ const files = patchedFiles(toolInput);
70
+ return files.length === 1 ? { ...lookup, input: { ...lookup.input, file_path: absolute(files[0]) }, unreadable: false } : lookup;
71
+ };
72
+ var normalizeToolCall = (profile, input) => {
73
+ if (!profile || !input.tool_name) return input;
74
+ const lookup = lookupFor(profile, input);
75
+ return { ...input, tool_name: lookup.tool, tool_input: lookup.input };
76
+ };
77
+ var underHome = (segments, home = homedir()) => join(home, ...segments);
78
+ var compatibleConfigFile = (profile, home) => underHome(profile.configFile, home);
79
+ var compatibleMcpFile = (profile, home) => profile.mcp ? underHome(profile.mcp.file, home) : null;
80
+ var compatibleInstructionsFile = (profile, home) => profile.instructions ? underHome(profile.instructions.file, home) : null;
81
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
82
+ var ensureRecord = (target, key) => {
83
+ const existing = asRecord(target[key]);
84
+ if (existing) return existing;
85
+ const created = {};
86
+ target[key] = created;
87
+ return created;
88
+ };
89
+ var descend = (config, keyPath) => keyPath.reduce(ensureRecord, config);
90
+ var findMap = (config, keyPath) => keyPath.reduce((node, key) => node && asRecord(node[key]), config);
91
+ var pruneEmptyMap = (config, keyPath) => {
92
+ if (keyPath.length === 0) return false;
93
+ const parent = findMap(config, keyPath.slice(0, -1));
94
+ const key = keyPath[keyPath.length - 1];
95
+ const map = asRecord(parent?.[key]);
96
+ if (!parent || !map || Object.keys(map).length > 0) return false;
97
+ delete parent[key];
98
+ return true;
99
+ };
100
+ var aliasesFor = (profile, canonical) => Object.entries(profile.toolAliases).flatMap(([agentName, target]) => target === canonical ? [agentName] : []);
101
+ var carriesPhoneAnswer = (profile, canonical) => profile.wire === "legacy_decision" && PHONE_ANSWERED_TOOLS.has(canonical);
102
+ var translateMatcher = (profile, matcher) => {
103
+ const spellings = matcher.split("|").filter((canonical) => !carriesPhoneAnswer(profile, canonical)).flatMap((canonical) => [canonical, ...aliasesFor(profile, canonical)]);
104
+ return [.../* @__PURE__ */ new Set([...spellings, ...profile.extraMatchers])].join("|");
105
+ };
106
+ var TOOL_MATCHED_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse"]);
107
+ var withoutMatcher = ({ matcher, ...hook }) => hook;
108
+ var compatibleHookEvents = (profile) => CLAUDE_HOOK_EVENTS.filter((hook) => profile.events.includes(hook.event)).map((hook) => {
109
+ if (!hook.matcher) return hook;
110
+ if (!TOOL_MATCHED_EVENTS.has(hook.event)) return withoutMatcher(hook);
111
+ return { ...hook, matcher: translateMatcher(profile, hook.matcher) };
112
+ });
113
+ var WRITTEN = { ok: true };
114
+ var ROOT_KEY = "(root)";
115
+ var dotted = (segments) => segments.join(".") || ROOT_KEY;
116
+ var hooksShapeConflict = (config, profile) => {
117
+ if (!asRecord(config)) return ROOT_KEY;
118
+ const walked = [];
119
+ let node = config;
120
+ for (const key of profile.hooksKeyPath) {
121
+ if (node[key] === void 0) return void 0;
122
+ const nested = asRecord(node[key]);
123
+ if (!nested) return dotted([...walked, key]);
124
+ walked.push(key);
125
+ node = nested;
126
+ }
127
+ const clash = CLAUDE_HOOK_EVENTS.find(({ event }) => node[event] !== void 0 && !Array.isArray(node[event]));
128
+ return clash ? dotted([...walked, clash.event]) : void 0;
129
+ };
130
+ var permissionsShapeConflict = (config) => {
131
+ if (!asRecord(config)) return ROOT_KEY;
132
+ if (config.permissions === void 0) return void 0;
133
+ const permissions = asRecord(config.permissions);
134
+ if (!permissions) return "permissions";
135
+ return permissions.allow !== void 0 && !Array.isArray(permissions.allow) ? "permissions.allow" : void 0;
136
+ };
137
+ var addCompatibleToolPermissions = (config) => {
138
+ const conflictAt = permissionsShapeConflict(config);
139
+ if (conflictAt) return { ok: false, conflictAt };
140
+ addPusharyToolPermissions(config);
141
+ return WRITTEN;
142
+ };
143
+ var entryFor = (profile, definition, resolve2) => ({
144
+ ...definition.matcher ? { matcher: definition.matcher } : {},
145
+ hooks: [{
146
+ type: "command",
147
+ command: withHookHoldBudget(
148
+ `${resolve2(definition.binary)} ${agentArgument(profile)}`,
149
+ holdBudgetOf(definition)
150
+ ),
151
+ timeout: definition.timeout
152
+ }]
153
+ });
154
+ var addCompatibleHooks = (config, profile, binDir) => {
155
+ const conflictAt = hooksShapeConflict(config, profile);
156
+ if (conflictAt) return { ok: false, conflictAt };
157
+ const resolve2 = (name) => guardBinary(binDir ? join(binDir, name) : name);
158
+ const hooks = descend(config, profile.hooksKeyPath);
159
+ const supported = new Map(compatibleHookEvents(profile).map((hook) => [hook.event, hook]));
160
+ for (const known of CLAUDE_HOOK_EVENTS) {
161
+ const current = Array.isArray(hooks[known.event]) ? hooks[known.event] : [];
162
+ const definition = supported.get(known.event);
163
+ if (definition) {
164
+ hooks[known.event] = registerHookEntry(current, isPusharyHook, entryFor(profile, definition, resolve2));
165
+ continue;
166
+ }
167
+ const others = current.filter((item) => !isPusharyHook(item));
168
+ if (others.length > 0) hooks[known.event] = others;
169
+ else delete hooks[known.event];
170
+ }
171
+ return WRITTEN;
172
+ };
173
+ var removeCompatibleHooks = (config, profile, scope) => {
174
+ const hooks = findMap(config, profile.hooksKeyPath);
175
+ if (!hooks) return false;
176
+ let changed = false;
177
+ for (const { event } of CLAUDE_HOOK_EVENTS) {
178
+ const entries = hooks[event];
179
+ if (!Array.isArray(entries)) continue;
180
+ const kept = entries.filter((entry) => !isPusharyHook(entry, scope));
181
+ if (kept.length === entries.length) continue;
182
+ if (kept.length === 0) delete hooks[event];
183
+ else hooks[event] = kept;
184
+ changed = true;
185
+ }
186
+ const pruned = pruneEmptyMap(config, profile.hooksKeyPath);
187
+ return changed || pruned;
188
+ };
189
+ var hasCompatibleHooks = (config, profile) => {
190
+ const hooks = findMap(config, profile.hooksKeyPath);
191
+ if (!hooks) return false;
192
+ return compatibleHookEvents(profile).some(({ event }) => {
193
+ const entries = hooks[event];
194
+ return Array.isArray(entries) && entries.some((entry) => isPusharyHook(entry));
195
+ });
196
+ };
197
+ var missingCompatibleHookEvents = (config, profile) => {
198
+ const hooks = findMap(config, profile.hooksKeyPath);
199
+ return compatibleHookEvents(profile).filter(({ event }) => {
200
+ const entries = hooks?.[event];
201
+ return !Array.isArray(entries) || !entries.some((entry) => isPusharyHook(entry));
202
+ }).map(({ event }) => event);
203
+ };
204
+ var compatiblePermissionRuleFile = (profile, home) => profile.permissionRuleFile ? underHome(profile.permissionRuleFile, home) : null;
205
+ var addCompatibleMcpServer = (config, profile, apiKey) => {
206
+ if (!profile.mcp) return;
207
+ if (profile.mcp.shape === "gemini_http") {
208
+ addGeminiMcpServer(config, apiKey);
209
+ return;
210
+ }
211
+ addClaudeMcpServer(config, apiKey);
212
+ if (!profile.mcp.trusted) return;
213
+ const server = asRecord(asRecord(config.mcpServers)?.pushary);
214
+ if (server) server.trust = true;
215
+ };
216
+ var removeCompatibleMcpServer = (config, profile) => {
217
+ if (!profile.mcp) return false;
218
+ return profile.mcp.shape === "gemini_http" ? removeGeminiMcpServer(config) : removeClaudeMcpServers(config);
219
+ };
220
+
221
+ export {
222
+ profileFromArgv,
223
+ activeIdentity,
224
+ activeHookSource,
225
+ normalizeToolCall,
226
+ compatibleConfigFile,
227
+ compatibleMcpFile,
228
+ compatibleInstructionsFile,
229
+ compatibleHookEvents,
230
+ addCompatibleToolPermissions,
231
+ addCompatibleHooks,
232
+ removeCompatibleHooks,
233
+ hasCompatibleHooks,
234
+ missingCompatibleHookEvents,
235
+ compatiblePermissionRuleFile,
236
+ addCompatibleMcpServer,
237
+ removeCompatibleMcpServer
238
+ };
@@ -0,0 +1,64 @@
1
+ import {
2
+ isSafeReadOnlyCommand
3
+ } from "./chunk-NQTHFF4W.js";
4
+
5
+ // src/ledger.ts
6
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from "fs";
7
+ import { join, dirname } from "path";
8
+ import { homedir } from "os";
9
+ var ledgerPath = () => process.env.PUSHARY_LEDGER_FILE?.trim() || join(homedir(), ".pushary", "local-ledger.jsonl");
10
+ var MAX_LEDGER_BYTES = 1e6;
11
+ var isGatingMoment = (toolName, toolInput) => {
12
+ const command = typeof toolInput?.command === "string" ? toolInput.command : void 0;
13
+ if (toolName === "Bash" && command && isSafeReadOnlyCommand(command)) return false;
14
+ return true;
15
+ };
16
+ var recordKeylessMoment = (entry) => {
17
+ try {
18
+ const path = ledgerPath();
19
+ const dir = dirname(path);
20
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
21
+ if (existsSync(path) && statSync(path).size > MAX_LEDGER_BYTES) {
22
+ rmSync(`${path}.1`, { force: true });
23
+ renameSync(path, `${path}.1`);
24
+ }
25
+ appendFileSync(path, `${JSON.stringify(entry)}
26
+ `, "utf-8");
27
+ } catch {
28
+ }
29
+ };
30
+ var readLedgerSummary = (days = 7) => {
31
+ const sinceMs = Date.now() - days * 864e5;
32
+ const summary = { count: 0, sessions: 0, byTool: {}, sinceMs };
33
+ const sessions = /* @__PURE__ */ new Set();
34
+ for (const path of [`${ledgerPath()}.1`, ledgerPath()]) {
35
+ let raw;
36
+ try {
37
+ raw = readFileSync(path, "utf-8");
38
+ } catch {
39
+ continue;
40
+ }
41
+ for (const line of raw.split("\n")) {
42
+ if (!line.trim()) continue;
43
+ let entry;
44
+ try {
45
+ entry = JSON.parse(line);
46
+ } catch {
47
+ continue;
48
+ }
49
+ if (typeof entry.ts !== "number" || entry.ts < sinceMs) continue;
50
+ summary.count += 1;
51
+ const tool = typeof entry.tool === "string" && entry.tool ? entry.tool : "Other";
52
+ summary.byTool[tool] = (summary.byTool[tool] ?? 0) + 1;
53
+ sessions.add(entry.sessionId || `${entry.project ?? "unknown"}:no-session`);
54
+ }
55
+ }
56
+ summary.sessions = sessions.size;
57
+ return summary;
58
+ };
59
+
60
+ export {
61
+ isGatingMoment,
62
+ recordKeylessMoment,
63
+ readLedgerSummary
64
+ };
@@ -0,0 +1,227 @@
1
+ import {
2
+ canOpenBrowser,
3
+ openInBrowser
4
+ } from "./chunk-PUZZELE6.js";
5
+ import {
6
+ generateKeypair,
7
+ openSealedKey,
8
+ publicKeyFingerprint
9
+ } from "./chunk-3EGEA4KH.js";
10
+ import {
11
+ getBaseUrl
12
+ } from "./chunk-GPEGOGRJ.js";
13
+
14
+ // src/cli/login-ui.ts
15
+ import { hostname } from "os";
16
+
17
+ // src/cli/login.ts
18
+ var START_PATH = "/api/cli/auth/start";
19
+ var CLAIM_PATH = "/api/cli/auth/claim";
20
+ var DEFAULT_TIMEOUT_MS = 1e4;
21
+ var post = async (url, body, fetchImpl) => fetchImpl(url, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json" },
24
+ body: JSON.stringify(body),
25
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
26
+ });
27
+ var asString = (value) => typeof value === "string" && value ? value : null;
28
+ var asNumber = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
29
+ var startLogin = async (machineLabel, cliVersion, deps = {}) => {
30
+ const fetchImpl = deps.fetchImpl ?? fetch;
31
+ const baseUrl = deps.baseUrl ?? getBaseUrl();
32
+ const keypair = generateKeypair();
33
+ let response;
34
+ try {
35
+ response = await post(
36
+ `${baseUrl}${START_PATH}`,
37
+ { cliPublicKey: keypair.publicKeyB64, machineLabel, cliVersion },
38
+ fetchImpl
39
+ );
40
+ } catch (err) {
41
+ return { kind: "unavailable", detail: err instanceof Error ? err.message : "network error" };
42
+ }
43
+ if (response.status === 429) {
44
+ const retry = Number(response.headers.get("retry-after") ?? "60");
45
+ return { kind: "rate-limited", retryAfterSeconds: Number.isFinite(retry) ? retry : 60 };
46
+ }
47
+ if (!response.ok) {
48
+ return { kind: "unavailable", detail: `HTTP ${response.status}` };
49
+ }
50
+ const body = await response.json().catch(() => null);
51
+ const loginId = body ? asString(body.loginId) : null;
52
+ const userCode = body ? asString(body.userCode) : null;
53
+ if (!body || !loginId || !userCode) {
54
+ return { kind: "unavailable", detail: "the server did not return a login code" };
55
+ }
56
+ return {
57
+ kind: "ok",
58
+ start: {
59
+ loginId,
60
+ userCode,
61
+ verificationUrl: asString(body.verificationUrl) ?? `${baseUrl}/cli/authorize`,
62
+ // The cadence comes from the server so a later change needs no CLI release.
63
+ intervalMs: Math.max(1, asNumber(body.interval, 2)) * 1e3,
64
+ expiresInSeconds: Math.max(1, asNumber(body.expiresInSeconds, 900)),
65
+ fingerprint: asString(body.fingerprint) ?? publicKeyFingerprint(keypair.publicKeyB64),
66
+ keypair
67
+ }
68
+ };
69
+ };
70
+ var pollOnce = async (start, deps = {}) => {
71
+ const fetchImpl = deps.fetchImpl ?? fetch;
72
+ const baseUrl = deps.baseUrl ?? getBaseUrl();
73
+ let response;
74
+ try {
75
+ response = await fetchImpl(`${baseUrl}${CLAIM_PATH}?id=${encodeURIComponent(start.loginId)}`, {
76
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
77
+ });
78
+ } catch (err) {
79
+ return { kind: "unreachable", detail: err instanceof Error ? err.message : "network error" };
80
+ }
81
+ if (!response.ok) return { kind: "unreachable", detail: `HTTP ${response.status}` };
82
+ const body = await response.json().catch(() => null);
83
+ if (!body) return { kind: "unreachable", detail: "unreadable response" };
84
+ switch (body.status) {
85
+ case "authorized": {
86
+ const sealed = asString(body.sealed);
87
+ if (!sealed) return { kind: "unreachable", detail: "authorized without a sealed key" };
88
+ try {
89
+ return {
90
+ kind: "authorized",
91
+ apiKey: openSealedKey(sealed, start.keypair),
92
+ siteSlug: asString(body.siteSlug) ?? "",
93
+ keyName: asString(body.keyName) ?? "CLI"
94
+ };
95
+ } catch {
96
+ return { kind: "unreachable", detail: "the key did not decrypt" };
97
+ }
98
+ }
99
+ case "payment_required":
100
+ return {
101
+ kind: "payment-required",
102
+ checkoutUrl: asString(body.checkoutUrl) ?? `${baseUrl}/dashboard/settings?tab=billing`,
103
+ plan: asString(body.plan) ?? "agent"
104
+ };
105
+ case "rejected":
106
+ return { kind: "rejected" };
107
+ case "expired":
108
+ return { kind: "expired" };
109
+ case "needs_workspace":
110
+ return { kind: "needs-workspace" };
111
+ default:
112
+ return { kind: "pending", expiresInSeconds: asNumber(body.expiresInSeconds, 0) };
113
+ }
114
+ };
115
+ var waitForLogin = async (start, handlers = {}, deps = {}) => {
116
+ const now = deps.now ?? Date.now;
117
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
118
+ const deadline = now() + start.expiresInSeconds * 1e3;
119
+ let polls = 0;
120
+ let consecutiveFailures = 0;
121
+ const noticed = /* @__PURE__ */ new Set();
122
+ while (now() < deadline) {
123
+ const state = await pollOnce(start, deps);
124
+ polls += 1;
125
+ if (state.kind === "authorized" || state.kind === "rejected" || state.kind === "expired") {
126
+ return { state, polls };
127
+ }
128
+ if (state.kind === "unreachable") {
129
+ consecutiveFailures += 1;
130
+ if (consecutiveFailures === 3 && !noticed.has("unreachable")) {
131
+ noticed.add("unreachable");
132
+ handlers.onNotice?.(state);
133
+ }
134
+ } else {
135
+ consecutiveFailures = 0;
136
+ if ((state.kind === "needs-workspace" || state.kind === "payment-required") && !noticed.has(state.kind)) {
137
+ noticed.add(state.kind);
138
+ handlers.onNotice?.(state);
139
+ }
140
+ }
141
+ handlers.onTick?.(state, Math.max(0, Math.ceil((deadline - now()) / 1e3)));
142
+ await sleep(start.intervalMs);
143
+ }
144
+ return { state: { kind: "expired" }, polls };
145
+ };
146
+ var cancelLogin = async (start, deps = {}) => {
147
+ const fetchImpl = deps.fetchImpl ?? fetch;
148
+ const baseUrl = deps.baseUrl ?? getBaseUrl();
149
+ try {
150
+ await fetchImpl(`${baseUrl}${START_PATH}`, {
151
+ method: "DELETE",
152
+ headers: { "Content-Type": "application/json" },
153
+ body: JSON.stringify({ loginId: start.loginId, cliPublicKey: start.keypair.publicKeyB64 }),
154
+ signal: AbortSignal.timeout(5e3)
155
+ });
156
+ } catch {
157
+ }
158
+ };
159
+
160
+ // src/cli/login-ui.ts
161
+ var runBrowserLogin = async (ui, options, deps = {}) => {
162
+ const started = await startLogin(hostname(), options.cliVersion, deps);
163
+ if (started.kind === "rate-limited") {
164
+ return { kind: "unavailable", detail: `too many attempts, retry in ${started.retryAfterSeconds}s` };
165
+ }
166
+ if (started.kind === "unavailable") {
167
+ return { kind: "unavailable", detail: started.detail };
168
+ }
169
+ const start = started.start;
170
+ let cancelled = false;
171
+ const onSignal = () => {
172
+ if (cancelled) return;
173
+ cancelled = true;
174
+ void cancelLogin(start, deps).finally(() => {
175
+ ui.say("");
176
+ ui.say(" Cancelled. Nothing was written.");
177
+ process.exit(130);
178
+ });
179
+ };
180
+ process.on("SIGINT", onSignal);
181
+ process.on("SIGHUP", onSignal);
182
+ try {
183
+ ui.say("");
184
+ ui.say(` ${ui.bold("Sign in to Pushary")}`);
185
+ ui.say("");
186
+ ui.say(` 1. Open ${ui.cyan(start.verificationUrl)}`);
187
+ ui.say(` 2. Enter this code: ${ui.bold(start.userCode)}`);
188
+ ui.say("");
189
+ ui.say(` ${ui.dim(`This terminal shows fingerprint ${start.fingerprint}. The page shows the same one.`)}`);
190
+ ui.say(` ${ui.dim("The code is only on your screen. Nobody can send you a link that fills it in.")}`);
191
+ ui.say("");
192
+ if (!options.noBrowser && canOpenBrowser() && openInBrowser(start.verificationUrl)) {
193
+ ui.say(` ${ui.dim("Opened your browser.")}`);
194
+ }
195
+ const notice = (state2) => {
196
+ if (state2.kind === "needs-workspace") {
197
+ ui.say(` ${ui.yellow("!")} Waiting on a workspace. Finish creating one in the browser and this continues on its own.`);
198
+ } else if (state2.kind === "payment-required") {
199
+ ui.say(` ${ui.yellow("!")} A plan is needed before a key can be issued. Nothing was created yet.`);
200
+ ui.say(` ${ui.dim("Choose one at")} ${ui.cyan(state2.checkoutUrl)}`);
201
+ ui.say(` ${ui.dim("Keep the browser tab open and this finishes on its own once the plan is active.")}`);
202
+ ui.say(` ${ui.dim("If you closed it, reopen the page and enter the same code again.")}`);
203
+ if (!options.noBrowser && canOpenBrowser()) openInBrowser(state2.checkoutUrl);
204
+ } else if (state2.kind === "unreachable") {
205
+ ui.say(` ${ui.yellow("!")} Still trying. pushary.com is not answering ${ui.dim(`(${state2.detail})`)}`);
206
+ }
207
+ };
208
+ const { state } = await waitForLogin(start, { onNotice: notice }, deps);
209
+ if (state.kind === "authorized") {
210
+ return {
211
+ kind: "ok",
212
+ apiKey: state.apiKey,
213
+ siteSlug: state.siteSlug,
214
+ keyName: state.keyName
215
+ };
216
+ }
217
+ if (state.kind === "rejected") return { kind: "rejected" };
218
+ return { kind: "expired" };
219
+ } finally {
220
+ process.off("SIGINT", onSignal);
221
+ process.off("SIGHUP", onSignal);
222
+ }
223
+ };
224
+
225
+ export {
226
+ runBrowserLogin
227
+ };