privateer-agent 0.15.4 → 0.15.6

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 (229) hide show
  1. package/README.md +11 -0
  2. package/bin/boot-bundle.d.mts +2 -0
  3. package/bin/boot-bundle.mjs +44 -0
  4. package/bin/fresh-supervisor.d.mts +5 -1
  5. package/bin/fresh-supervisor.mjs +47 -10
  6. package/bin/privateer-launch.mjs +27 -3
  7. package/bin/privateer-subagent.mjs +5 -2
  8. package/bin/privateer-verify.mjs +12 -0
  9. package/bin/session-recovery.d.mts +53 -0
  10. package/bin/session-recovery.mjs +240 -0
  11. package/bin/session-seal-preload.mjs +17 -0
  12. package/bin/session-seal.d.mts +25 -0
  13. package/bin/session-seal.mjs +242 -0
  14. package/dist-boot/boot.mjs +3 -0
  15. package/dist-boot/c-27JKSMWL.mjs +1460 -0
  16. package/dist-boot/c-2BMRFV24.mjs +269 -0
  17. package/dist-boot/c-2CTF6JA3.mjs +166 -0
  18. package/dist-boot/c-2G3BBHRJ.mjs +84 -0
  19. package/dist-boot/c-2IDLKXX6.mjs +250 -0
  20. package/dist-boot/c-2NJD5VYM.mjs +8 -0
  21. package/dist-boot/c-2S5EEQPW.mjs +1602 -0
  22. package/dist-boot/c-2WRDCUVI.mjs +25 -0
  23. package/dist-boot/c-2YN3M256.mjs +14 -0
  24. package/dist-boot/c-37GM3GMU.mjs +187 -0
  25. package/dist-boot/c-3STKRSVO.mjs +2480 -0
  26. package/dist-boot/c-3TPV6TQQ.mjs +2320 -0
  27. package/dist-boot/c-3UVHLQDJ.mjs +225 -0
  28. package/dist-boot/c-42PUUZYC.mjs +697 -0
  29. package/dist-boot/c-4D5UMWVW.mjs +683 -0
  30. package/dist-boot/c-4ETZRMTA.mjs +46 -0
  31. package/dist-boot/c-4QT25BD5.mjs +1482 -0
  32. package/dist-boot/c-4WVR32AP.mjs +2579 -0
  33. package/dist-boot/c-5EMM567G.mjs +152 -0
  34. package/dist-boot/c-5FA4CV63.mjs +145 -0
  35. package/dist-boot/c-5GV3X3RB.mjs +14 -0
  36. package/dist-boot/c-5JCEPRIY.mjs +52 -0
  37. package/dist-boot/c-5NDKIIWT.mjs +16 -0
  38. package/dist-boot/c-5QL3AR26.mjs +4665 -0
  39. package/dist-boot/c-5XY6MFKN.mjs +5142 -0
  40. package/dist-boot/c-65UZIZC7.mjs +2897 -0
  41. package/dist-boot/c-6FUXKHOK.mjs +1969 -0
  42. package/dist-boot/c-6JHWQE4V.mjs +18242 -0
  43. package/dist-boot/c-6LOBR4I6.mjs +5280 -0
  44. package/dist-boot/c-6QHOZXKQ.mjs +14394 -0
  45. package/dist-boot/c-6ZO2SADQ.mjs +178 -0
  46. package/dist-boot/c-6ZORVVZP.mjs +1449 -0
  47. package/dist-boot/c-76QH4UCP.mjs +2441 -0
  48. package/dist-boot/c-77GO5L5Y.mjs +20982 -0
  49. package/dist-boot/c-7M23XFTN.mjs +5052 -0
  50. package/dist-boot/c-7TC367E2.mjs +32 -0
  51. package/dist-boot/c-7UOG7POX.mjs +1524 -0
  52. package/dist-boot/c-7VHP3FLN.mjs +376 -0
  53. package/dist-boot/c-A73JHZP2.mjs +13207 -0
  54. package/dist-boot/c-ABXULAFL.mjs +71 -0
  55. package/dist-boot/c-ANDD2AQO.mjs +74 -0
  56. package/dist-boot/c-AOY77AZL.mjs +1430 -0
  57. package/dist-boot/c-AXEPZ22I.mjs +108 -0
  58. package/dist-boot/c-B2Y7VNHU.mjs +427 -0
  59. package/dist-boot/c-B7VJUH73.mjs +77 -0
  60. package/dist-boot/c-BFW6CDVL.mjs +24 -0
  61. package/dist-boot/c-BHNIF2AR.mjs +188 -0
  62. package/dist-boot/c-BKF2O6BL.mjs +23 -0
  63. package/dist-boot/c-BOZXFTZN.mjs +8 -0
  64. package/dist-boot/c-BRNYJWZL.mjs +28 -0
  65. package/dist-boot/c-C3N4Z2HO.mjs +93 -0
  66. package/dist-boot/c-C4IV7ZFG.mjs +717 -0
  67. package/dist-boot/c-C5NK6UYV.mjs +793 -0
  68. package/dist-boot/c-C67644MB.mjs +1659 -0
  69. package/dist-boot/c-C6S2YB53.mjs +620 -0
  70. package/dist-boot/c-CDBEJSBF.mjs +37 -0
  71. package/dist-boot/c-CG47HBKU.mjs +435 -0
  72. package/dist-boot/c-CKCTWCT5.mjs +51 -0
  73. package/dist-boot/c-CN77DCGF.mjs +60 -0
  74. package/dist-boot/c-CRBXNKFO.mjs +1795 -0
  75. package/dist-boot/c-CRIHASQ6.mjs +973 -0
  76. package/dist-boot/c-CU4NGAUO.mjs +3786 -0
  77. package/dist-boot/c-CWRFLGQU.mjs +1883 -0
  78. package/dist-boot/c-CYQB2USO.mjs +32 -0
  79. package/dist-boot/c-DEE67DJO.mjs +379 -0
  80. package/dist-boot/c-DIFDBYOP.mjs +1258 -0
  81. package/dist-boot/c-DOSGVXDZ.mjs +34879 -0
  82. package/dist-boot/c-DR7XQI32.mjs +381 -0
  83. package/dist-boot/c-DTGPSB46.mjs +14666 -0
  84. package/dist-boot/c-DTHDNA4P.mjs +320 -0
  85. package/dist-boot/c-E4Z3Y5B5.mjs +3864 -0
  86. package/dist-boot/c-E66Z2TI7.mjs +203 -0
  87. package/dist-boot/c-EBU6RLRV.mjs +1527 -0
  88. package/dist-boot/c-EBZZNGSF.mjs +432 -0
  89. package/dist-boot/c-EC3LJ2KF.mjs +1032 -0
  90. package/dist-boot/c-EQBKXWNX.mjs +15829 -0
  91. package/dist-boot/c-EUEPIW4S.mjs +574 -0
  92. package/dist-boot/c-EX4YVWAT.mjs +378 -0
  93. package/dist-boot/c-EYZGSAJU.mjs +98 -0
  94. package/dist-boot/c-F75VRKY4.mjs +172 -0
  95. package/dist-boot/c-FH4NXKHV.mjs +177 -0
  96. package/dist-boot/c-FHNUCTEM.mjs +140 -0
  97. package/dist-boot/c-FJ5T42DS.mjs +5349 -0
  98. package/dist-boot/c-FPCNRL7G.mjs +1728 -0
  99. package/dist-boot/c-G4DLN7Q4.mjs +8 -0
  100. package/dist-boot/c-G56GYQFZ.mjs +2507 -0
  101. package/dist-boot/c-GLIOERDQ.mjs +1200 -0
  102. package/dist-boot/c-GMJ5HUBM.mjs +14 -0
  103. package/dist-boot/c-H3HSGMAQ.mjs +461 -0
  104. package/dist-boot/c-HIKTO6P7.mjs +275 -0
  105. package/dist-boot/c-I2MCJ34O.mjs +464 -0
  106. package/dist-boot/c-I3C6LBW4.mjs +765 -0
  107. package/dist-boot/c-I54OKQHW.mjs +387 -0
  108. package/dist-boot/c-IFSP3R4D.mjs +80 -0
  109. package/dist-boot/c-IZK77VB7.mjs +171 -0
  110. package/dist-boot/c-J3Y25UGE.mjs +54 -0
  111. package/dist-boot/c-J5IESS7W.mjs +27750 -0
  112. package/dist-boot/c-JJWZBY7L.mjs +254 -0
  113. package/dist-boot/c-JKTFXTGF.mjs +463 -0
  114. package/dist-boot/c-JN57AXQ4.mjs +77 -0
  115. package/dist-boot/c-KFIO6LUW.mjs +1096 -0
  116. package/dist-boot/c-KIEMEFWW.mjs +478 -0
  117. package/dist-boot/c-KIVXWBYD.mjs +28 -0
  118. package/dist-boot/c-KP6CRIP4.mjs +580 -0
  119. package/dist-boot/c-L26VOJX4.mjs +11558 -0
  120. package/dist-boot/c-L5GHWG5G.mjs +5988 -0
  121. package/dist-boot/c-M7NSQAKK.mjs +353 -0
  122. package/dist-boot/c-MCEWOPBW.mjs +56 -0
  123. package/dist-boot/c-MGPIEG7I.mjs +20 -0
  124. package/dist-boot/c-NREXJV3M.mjs +393 -0
  125. package/dist-boot/c-O5PP6CTX.mjs +162 -0
  126. package/dist-boot/c-OBQDHSXM.mjs +1774 -0
  127. package/dist-boot/c-OLQHHDD4.mjs +21 -0
  128. package/dist-boot/c-OXW4JQQH.mjs +319 -0
  129. package/dist-boot/c-P3D7ZUWN.mjs +1288 -0
  130. package/dist-boot/c-P4PQZVV5.mjs +948 -0
  131. package/dist-boot/c-P63T3XL2.mjs +26373 -0
  132. package/dist-boot/c-P6N5DQGZ.mjs +194 -0
  133. package/dist-boot/c-P7MLKQFC.mjs +58 -0
  134. package/dist-boot/c-PB3PZJL5.mjs +1276 -0
  135. package/dist-boot/c-PTN4S6G2.mjs +1259 -0
  136. package/dist-boot/c-PYCE5J7U.mjs +1083 -0
  137. package/dist-boot/c-Q4HYCRLB.mjs +19410 -0
  138. package/dist-boot/c-QUXMETGG.mjs +68 -0
  139. package/dist-boot/c-RDMJZCOX.mjs +356 -0
  140. package/dist-boot/c-ROGJK76M.mjs +17 -0
  141. package/dist-boot/c-RQJJ26T4.mjs +708 -0
  142. package/dist-boot/c-RSKCUPJF.mjs +7559 -0
  143. package/dist-boot/c-RVJU4O6P.mjs +37 -0
  144. package/dist-boot/c-S2RVHR3M.mjs +165 -0
  145. package/dist-boot/c-SKMK3PEE.mjs +91120 -0
  146. package/dist-boot/c-TI7SM4EG.mjs +20 -0
  147. package/dist-boot/c-TILJ6GXD.mjs +1235 -0
  148. package/dist-boot/c-TT6BCUJD.mjs +114 -0
  149. package/dist-boot/c-U4ITOXAN.mjs +2107 -0
  150. package/dist-boot/c-UGERXVO2.mjs +7120 -0
  151. package/dist-boot/c-UPBH5AXY.mjs +1363 -0
  152. package/dist-boot/c-US2PUNZL.mjs +597 -0
  153. package/dist-boot/c-UU4WZPPE.mjs +20 -0
  154. package/dist-boot/c-UWVIYPQS.mjs +39 -0
  155. package/dist-boot/c-V4RHQBPQ.mjs +17683 -0
  156. package/dist-boot/c-V5MRVK47.mjs +351 -0
  157. package/dist-boot/c-V6CL266R.mjs +1755 -0
  158. package/dist-boot/c-VJTMFKYE.mjs +14975 -0
  159. package/dist-boot/c-VJTRUKIB.mjs +773 -0
  160. package/dist-boot/c-W4IFSHPW.mjs +43 -0
  161. package/dist-boot/c-WETGB2CF.mjs +113 -0
  162. package/dist-boot/c-WO3QSZUU.mjs +1337 -0
  163. package/dist-boot/c-WQLH73TY.mjs +242 -0
  164. package/dist-boot/c-XL735JW5.mjs +500 -0
  165. package/dist-boot/c-Y7HADL5B.mjs +1107 -0
  166. package/dist-boot/c-YGQHSVSK.mjs +41 -0
  167. package/dist-boot/c-YMP2AMEB.mjs +10350 -0
  168. package/dist-boot/c-YRDO2WEF.mjs +12012 -0
  169. package/dist-boot/c-YUSTRATJ.mjs +8 -0
  170. package/dist-boot/c-Z2262R22.mjs +2697 -0
  171. package/dist-boot/c-Z4ALQMJC.mjs +709 -0
  172. package/dist-boot/c-Z5ZUQVUY.mjs +62 -0
  173. package/dist-boot/c-ZDZUKXF7.mjs +874 -0
  174. package/dist-boot/c-ZJYLGIC3.mjs +32 -0
  175. package/dist-boot/c-ZNA5CDMI.mjs +77 -0
  176. package/dist-boot/c-ZSO6DI3M.mjs +204 -0
  177. package/dist-boot/c-ZWUVHKMK.mjs +100 -0
  178. package/dist-boot/c-ZY4MESEW.mjs +832 -0
  179. package/dist-boot/c-ZZ5REI4L.mjs +329 -0
  180. package/dist-boot/cli.mjs +51 -0
  181. package/dist-boot/ext-anthropic-attribution.mjs +207 -0
  182. package/dist-boot/ext-pi-background-tasks.mjs +7234 -0
  183. package/dist-boot/ext-pi-subagents.mjs +37 -0
  184. package/dist-boot/ext-privateer-account.mjs +4426 -0
  185. package/dist-boot/ext-privateer-brand.mjs +5274 -0
  186. package/dist-boot/ext-privateer-codemode.mjs +37 -0
  187. package/dist-boot/ext-privateer-computer.mjs +626 -0
  188. package/dist-boot/ext-privateer-connect.mjs +1862 -0
  189. package/dist-boot/ext-privateer-context.mjs +323 -0
  190. package/dist-boot/ext-privateer-desktop.mjs +92 -0
  191. package/dist-boot/ext-privateer-final-audit.mjs +48 -0
  192. package/dist-boot/ext-privateer-fresh.mjs +81 -0
  193. package/dist-boot/ext-privateer-gate.mjs +5959 -0
  194. package/dist-boot/ext-privateer-hints.mjs +236 -0
  195. package/dist-boot/ext-privateer-media.mjs +2626 -0
  196. package/dist-boot/ext-privateer-models.mjs +3397 -0
  197. package/dist-boot/ext-privateer-posture.mjs +3006 -0
  198. package/dist-boot/ext-privateer-privacy.mjs +4578 -0
  199. package/dist-boot/ext-privateer-recovery.mjs +287 -0
  200. package/dist-boot/ext-privateer-speak.mjs +4754 -0
  201. package/dist-boot/ext-privateer-terminals.mjs +725 -0
  202. package/dist-boot/ext-privateer-tools.mjs +528 -0
  203. package/dist-boot/ext-privateer-truncation.mjs +74 -0
  204. package/dist-boot/ext-privateer-update.mjs +210 -0
  205. package/dist-boot/ext-privateer-vision.mjs +148 -0
  206. package/dist-boot/ext-privateer-web.mjs +543 -0
  207. package/dist-boot/ext-rpiv-ask-user-question.mjs +710 -0
  208. package/dist-boot/manifest.json +9 -0
  209. package/extensions/privateer-brand.ts +3 -1
  210. package/extensions/privateer-context.ts +4 -2
  211. package/extensions/privateer-final-audit.ts +17 -0
  212. package/extensions/privateer-gate.ts +55 -9
  213. package/extensions/privateer-recovery.ts +175 -0
  214. package/extensions/privateer-web.ts +4 -4
  215. package/package.json +3 -1
  216. package/patches/@earendil-works+pi-coding-agent+1.3.0.patch +199 -2
  217. package/patches/pi-background-tasks+2.6.10.patch +91 -7
  218. package/src/cli/chat.ts +7 -1
  219. package/src/config/moatManifest.json +2 -0
  220. package/src/config/whatsNew.json +4 -4
  221. package/src/context.ts +33 -5
  222. package/src/engine/finalAudit.ts +38 -0
  223. package/src/engine/stallWatchdog.ts +8 -1
  224. package/src/providers/account.ts +34 -3
  225. package/src/remote/attentionPush.ts +40 -0
  226. package/src/remote/liveTaskSession.ts +7 -1
  227. package/src/remote/relayClient.ts +38 -4
  228. package/src/remote/remoteBridge.ts +17 -0
  229. package/src/remote/transcriptSnapshot.ts +104 -0
@@ -0,0 +1,1862 @@
1
+ import{createRequire as __pvCR}from"node:module";import{fileURLToPath as __pvF}from"node:url";import{dirname as __pvD}from"node:path";const require=__pvCR(import.meta.url);const __pvUrl=(r)=>new URL(r,import.meta.url).href;const __pvFile=(r)=>__pvF(new URL(r,import.meta.url));
2
+ import {
3
+ Container,
4
+ Input,
5
+ Spacer,
6
+ Text,
7
+ fuzzyFilter,
8
+ getKeybindings
9
+ } from "./c-EQBKXWNX.mjs";
10
+ import "./c-4WVR32AP.mjs";
11
+ import {
12
+ __name
13
+ } from "./c-CN77DCGF.mjs";
14
+
15
+ // src/remote/mcpControl.ts?ext=privateer-connect
16
+ import { readFileSync, mkdirSync } from "node:fs";
17
+ import { join as join2, dirname } from "node:path";
18
+
19
+ // src/config/paths.ts?ext=privateer-connect
20
+ import { homedir } from "node:os";
21
+ import { join } from "node:path";
22
+ function globalDir() {
23
+ return process.env.PRIVATEER_HOME ?? join(homedir(), ".privateer");
24
+ }
25
+ __name(globalDir, "globalDir");
26
+ function agentDir() {
27
+ return join(globalDir(), "agent");
28
+ }
29
+ __name(agentDir, "agentDir");
30
+
31
+ // src/util/atomicWrite.ts?ext=privateer-connect
32
+ import { chmodSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
33
+ import { randomBytes } from "node:crypto";
34
+ function writeFileAtomic(path, data, opts = {}) {
35
+ let target = path;
36
+ let mode = opts.mode ?? 384;
37
+ try {
38
+ target = realpathSync(path);
39
+ if (opts.mode === void 0) mode = statSync(target).mode & 511;
40
+ } catch {
41
+ }
42
+ const tmp = `${target}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
43
+ try {
44
+ writeFileSync(tmp, data, { encoding: "utf8", mode });
45
+ try {
46
+ chmodSync(tmp, mode);
47
+ } catch {
48
+ }
49
+ renameSync(tmp, target);
50
+ } catch (e) {
51
+ try {
52
+ unlinkSync(tmp);
53
+ } catch {
54
+ }
55
+ throw e;
56
+ }
57
+ }
58
+ __name(writeFileAtomic, "writeFileAtomic");
59
+
60
+ // src/remote/mcpControl.ts?ext=privateer-connect
61
+ var AUTHS = ["oauth", "bearer", "none"];
62
+ function isAuth(v) {
63
+ return typeof v === "string" && AUTHS.includes(v);
64
+ }
65
+ __name(isAuth, "isAuth");
66
+ function authOf(e, transport) {
67
+ if (transport !== "http") return "none";
68
+ if (isAuth(e.auth)) return e.auth;
69
+ if (e.bearerToken || e.bearerTokenEnv) return "bearer";
70
+ if (e.oauth === false) return "none";
71
+ if (e.headers && Object.keys(e.headers).length > 0) return "none";
72
+ return "oauth";
73
+ }
74
+ __name(authOf, "authOf");
75
+ var TRANSPORTS = ["stdio", "http"];
76
+ function isTransport(v) {
77
+ return typeof v === "string" && TRANSPORTS.includes(v);
78
+ }
79
+ __name(isTransport, "isTransport");
80
+ function hostOf(url) {
81
+ try {
82
+ return new URL(url).host || void 0;
83
+ } catch {
84
+ return void 0;
85
+ }
86
+ }
87
+ __name(hostOf, "hostOf");
88
+ function cleanArgs(v) {
89
+ if (!Array.isArray(v)) return void 0;
90
+ return v.map((x) => String(x ?? "")).filter((s) => s.length > 0);
91
+ }
92
+ __name(cleanArgs, "cleanArgs");
93
+ var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
94
+ var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
95
+ function isSseUrl(url) {
96
+ try {
97
+ return /\/sse\/?$/i.test(new URL(url).pathname);
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ __name(isSseUrl, "isSseUrl");
103
+ function toPiConfigValue(value) {
104
+ if (value.startsWith("!!")) return `$!${references(value.slice(2))}`;
105
+ if (value.startsWith("!")) return value;
106
+ return references(value);
107
+ }
108
+ __name(toPiConfigValue, "toPiConfigValue");
109
+ function references(value) {
110
+ const literal = /* @__PURE__ */ __name((text) => text.replace(/\$/g, "$$$$"), "literal");
111
+ let out = "";
112
+ let last = 0;
113
+ for (const m of value.matchAll(/\$\{(\w+)\}|\$env:(\w+)|\{env:(\w+)\}/g)) {
114
+ out += literal(value.slice(last, m.index)) + "${" + (m[1] ?? m[2] ?? m[3]) + "}";
115
+ last = m.index + m[0].length;
116
+ }
117
+ return out + literal(value.slice(last));
118
+ }
119
+ __name(references, "references");
120
+ function piConfigValues(values) {
121
+ const out = {};
122
+ for (const [k, v] of Object.entries(values)) out[k] = toPiConfigValue(String(v ?? ""));
123
+ return out;
124
+ }
125
+ __name(piConfigValues, "piConfigValues");
126
+ function bearerHeader(token) {
127
+ if (token.startsWith("!") && !token.startsWith("!!")) return `!printf 'Bearer %s' "$(${token.slice(1)})"`;
128
+ return `Bearer ${toPiConfigValue(token)}`;
129
+ }
130
+ __name(bearerHeader, "bearerHeader");
131
+ function migrateMcpProjection(dir = agentDir) {
132
+ let raw;
133
+ try {
134
+ raw = JSON.parse(readFileSync(join2(dir(), "mcp.json"), "utf8"));
135
+ if (raw?.settings?.toolPrefix === void 0) return false;
136
+ } catch {
137
+ return false;
138
+ }
139
+ makeMcpControl({ dir }).reproject(raw?.mcpServers ?? {});
140
+ return true;
141
+ }
142
+ __name(migrateMcpProjection, "migrateMcpProjection");
143
+ var LEGACY_SSE_URLS = {
144
+ "https://mcp.atlassian.com/v1/sse": "https://mcp.atlassian.com/v1/mcp",
145
+ "https://mcp.asana.com/sse": "https://mcp.asana.com/mcp",
146
+ "https://mcp.linear.app/sse": "https://mcp.linear.app/mcp"
147
+ };
148
+ function upgradeLegacyUrl(url) {
149
+ return LEGACY_SSE_URLS[url.replace(/\/$/, "")] ?? url;
150
+ }
151
+ __name(upgradeLegacyUrl, "upgradeLegacyUrl");
152
+ function toPiServerEntry(e) {
153
+ const transport = e.transport ?? (e.url ? "http" : "stdio");
154
+ const std = {};
155
+ if (transport === "stdio") {
156
+ if (e.command) std.command = e.command;
157
+ if (e.args?.length) std.args = e.args;
158
+ if (e.env && Object.keys(e.env).length > 0) std.env = piConfigValues(e.env);
159
+ } else {
160
+ if (e.url) std.url = upgradeLegacyUrl(e.url);
161
+ const headers = piConfigValues(e.headers ?? {});
162
+ if (authOf(e, transport) === "bearer") {
163
+ if (e.bearerToken) headers.Authorization = bearerHeader(e.bearerToken);
164
+ else if (e.bearerTokenEnv && ENV_NAME.test(e.bearerTokenEnv)) headers.Authorization = `Bearer \${${e.bearerTokenEnv}}`;
165
+ }
166
+ if (Object.keys(headers).length > 0) std.headers = headers;
167
+ }
168
+ return std;
169
+ }
170
+ __name(toPiServerEntry, "toPiServerEntry");
171
+ function makeMcpControl(opts) {
172
+ const dir = opts?.dir ?? agentDir;
173
+ const sourcePath = /* @__PURE__ */ __name(() => join2(dir(), "mcp-desktop.json"), "sourcePath");
174
+ const projectionPath = /* @__PURE__ */ __name(() => join2(dir(), "mcp.json"), "projectionPath");
175
+ function readSource() {
176
+ try {
177
+ const raw = JSON.parse(readFileSync(sourcePath(), "utf8"));
178
+ if (raw && typeof raw === "object" && raw.servers) return { servers: raw.servers };
179
+ } catch {
180
+ }
181
+ const servers = {};
182
+ try {
183
+ const proj = JSON.parse(readFileSync(projectionPath(), "utf8"));
184
+ for (const [name, entry] of Object.entries(proj?.mcpServers ?? {})) {
185
+ servers[name] = { ...entry, enabled: true };
186
+ }
187
+ } catch {
188
+ }
189
+ return { servers };
190
+ }
191
+ __name(readSource, "readSource");
192
+ function writeSource(src) {
193
+ mkdirSync(dirname(sourcePath()), { recursive: true });
194
+ writeFileAtomic(sourcePath(), JSON.stringify(src, null, 2) + "\n");
195
+ project(src);
196
+ }
197
+ __name(writeSource, "writeSource");
198
+ function project(src) {
199
+ const mcpServers = {};
200
+ for (const [name, e] of Object.entries(src.servers)) {
201
+ mcpServers[name] = e.enabled === false ? { ...toPiServerEntry(e), enabled: false } : toPiServerEntry(e);
202
+ }
203
+ mkdirSync(dirname(projectionPath()), { recursive: true });
204
+ writeFileAtomic(projectionPath(), JSON.stringify({ mcpServers }, null, 2) + "\n");
205
+ }
206
+ __name(project, "project");
207
+ function toRemote(name, e) {
208
+ const transport = e.transport ?? (e.url ? "http" : "stdio");
209
+ const env = e.env ?? {};
210
+ const envKeys = Object.keys(env);
211
+ const headers = transport === "http" ? e.headers ?? {} : {};
212
+ const headerKeys = Object.keys(headers);
213
+ const auth = authOf(e, transport);
214
+ return {
215
+ name,
216
+ transport,
217
+ enabled: e.enabled !== false,
218
+ command: transport === "stdio" ? e.command : void 0,
219
+ argsPreview: transport === "stdio" && e.args?.length ? e.args.join(" ") : void 0,
220
+ url: transport === "http" ? e.url : void 0,
221
+ host: transport === "http" && e.url ? hostOf(e.url) : void 0,
222
+ auth,
223
+ oauth: auth === "oauth",
224
+ envKeys,
225
+ secretsSet: envKeys.filter((k) => String(env[k] ?? "").length > 0),
226
+ headerKeys,
227
+ headersSet: headerKeys.filter((k) => String(headers[k] ?? "").length > 0),
228
+ bearerTokenSet: transport === "http" && String(e.bearerToken ?? "").length > 0,
229
+ bearerTokenEnv: transport === "http" ? e.bearerTokenEnv : void 0
230
+ };
231
+ }
232
+ __name(toRemote, "toRemote");
233
+ function mergeSecrets(prev, submitted) {
234
+ const merged = { ...prev ?? {} };
235
+ for (const [k, v] of Object.entries(submitted)) {
236
+ const key = String(k).trim();
237
+ if (!key) continue;
238
+ const val = String(v ?? "");
239
+ if (val.length > 0) merged[key] = val;
240
+ else delete merged[key];
241
+ }
242
+ return Object.keys(merged).length > 0 ? merged : void 0;
243
+ }
244
+ __name(mergeSecrets, "mergeSecrets");
245
+ return {
246
+ reproject(adopt) {
247
+ const src = readSource();
248
+ let added = false;
249
+ for (const [name, entry] of Object.entries(adopt ?? {})) {
250
+ if (src.servers[name] || !entry || typeof entry !== "object") continue;
251
+ src.servers[name] = { ...entry, enabled: true };
252
+ added = true;
253
+ }
254
+ if (added) writeSource(src);
255
+ else project(src);
256
+ },
257
+ list() {
258
+ const src = readSource();
259
+ return Object.entries(src.servers).map(([name, e]) => toRemote(name, e));
260
+ },
261
+ save(draft) {
262
+ const name = String(draft?.name ?? "").trim();
263
+ if (!name) return { ok: false, message: "A connector needs a name." };
264
+ if (!SERVER_NAME.test(name))
265
+ return { ok: false, message: 'A connector name may use only letters, digits, "-" and "_".' };
266
+ if (draft.transport !== void 0 && !isTransport(draft.transport))
267
+ return { ok: false, message: "Unknown transport." };
268
+ if (draft.auth !== void 0 && !isAuth(draft.auth))
269
+ return { ok: false, message: "Unknown authentication type." };
270
+ const src = readSource();
271
+ const prev = src.servers[name] ?? {};
272
+ const entry = { ...prev };
273
+ const transport = draft.transport ?? prev.transport ?? (draft.url || prev.url ? "http" : "stdio");
274
+ entry.transport = transport;
275
+ if (transport === "stdio") {
276
+ if (draft.command !== void 0) entry.command = String(draft.command).trim();
277
+ const args = cleanArgs(draft.args);
278
+ if (args !== void 0) entry.args = args;
279
+ delete entry.url;
280
+ delete entry.headers;
281
+ delete entry.auth;
282
+ delete entry.bearerToken;
283
+ delete entry.bearerTokenEnv;
284
+ delete entry.oauth;
285
+ if (!entry.command) return { ok: false, message: "A local (stdio) connector needs a command." };
286
+ } else {
287
+ if (draft.url !== void 0) entry.url = String(draft.url).trim();
288
+ if (entry.url) entry.url = upgradeLegacyUrl(entry.url);
289
+ if (draft.auth !== void 0) entry.auth = draft.auth;
290
+ else if (draft.oauth !== void 0) entry.auth = draft.oauth ? "oauth" : "none";
291
+ if (draft.bearerTokenEnv !== void 0) {
292
+ const v = String(draft.bearerTokenEnv).trim();
293
+ if (v) entry.bearerTokenEnv = v;
294
+ else delete entry.bearerTokenEnv;
295
+ }
296
+ if (draft.bearerToken !== void 0) {
297
+ const v = String(draft.bearerToken);
298
+ if (v.length > 0) entry.bearerToken = v;
299
+ else delete entry.bearerToken;
300
+ }
301
+ if (draft.headers !== void 0) {
302
+ const merged = mergeSecrets(prev.headers, draft.headers);
303
+ if (merged) entry.headers = merged;
304
+ else delete entry.headers;
305
+ }
306
+ if (draft.auth === void 0 && (entry.bearerToken || entry.bearerTokenEnv)) entry.auth = "bearer";
307
+ if (entry.auth !== void 0) delete entry.oauth;
308
+ delete entry.command;
309
+ delete entry.args;
310
+ if (!entry.url) return { ok: false, message: "A remote (http) connector needs a URL." };
311
+ if (isSseUrl(entry.url))
312
+ return {
313
+ ok: false,
314
+ message: "That looks like a legacy SSE endpoint, which isn't supported. Use the server's streamable HTTP URL (often ending in /mcp)."
315
+ };
316
+ if (entry.bearerTokenEnv && !ENV_NAME.test(entry.bearerTokenEnv))
317
+ return { ok: false, message: "The token variable must be an environment variable name, like GITHUB_TOKEN." };
318
+ if (authOf(entry, "http") === "bearer" && !entry.bearerToken && !entry.bearerTokenEnv)
319
+ return { ok: false, message: "A bearer connector needs a token, or the name of an env var holding one." };
320
+ }
321
+ if (draft.env !== void 0) {
322
+ const merged = mergeSecrets(prev.env, draft.env);
323
+ if (merged) entry.env = merged;
324
+ else delete entry.env;
325
+ }
326
+ entry.enabled = prev.enabled ?? true;
327
+ src.servers[name] = entry;
328
+ try {
329
+ writeSource(src);
330
+ } catch (e) {
331
+ return { ok: false, message: `Couldn't write MCP config: ${e instanceof Error ? e.message : String(e)}` };
332
+ }
333
+ return { ok: true, message: `Saved "${name}".` };
334
+ },
335
+ setEnabled(name, enabled) {
336
+ const src = readSource();
337
+ if (!src.servers[name]) return { ok: false, message: "No such connector." };
338
+ src.servers[name].enabled = !!enabled;
339
+ try {
340
+ writeSource(src);
341
+ } catch (e) {
342
+ return { ok: false, message: `Couldn't write MCP config: ${e instanceof Error ? e.message : String(e)}` };
343
+ }
344
+ return { ok: true, message: `${enabled ? "Enabled" : "Disabled"} "${name}".` };
345
+ },
346
+ remove(name) {
347
+ const src = readSource();
348
+ if (!src.servers[name]) return { ok: false, message: "Not configured." };
349
+ delete src.servers[name];
350
+ try {
351
+ writeSource(src);
352
+ } catch (e) {
353
+ return { ok: false, message: `Couldn't write MCP config: ${e instanceof Error ? e.message : String(e)}` };
354
+ }
355
+ return { ok: true, message: `Removed "${name}".` };
356
+ }
357
+ };
358
+ }
359
+ __name(makeMcpControl, "makeMcpControl");
360
+
361
+ // src/mcp/catalog.ts?ext=privateer-connect
362
+ function hostedCapable(e) {
363
+ if (e.hosted !== void 0) return e.hosted;
364
+ return e.transport === "http" && e.oauth === true;
365
+ }
366
+ __name(hostedCapable, "hostedCapable");
367
+ var MCP_CATALOG = [
368
+ {
369
+ id: "github",
370
+ name: "github",
371
+ label: "GitHub",
372
+ blurb: "Repos, issues, and pull requests.",
373
+ transport: "stdio",
374
+ command: "npx",
375
+ args: ["-y", "@modelcontextprotocol/server-github"],
376
+ env: { GITHUB_PERSONAL_ACCESS_TOKEN: "" },
377
+ needs: "token",
378
+ fill: "GITHUB_PERSONAL_ACCESS_TOKEN",
379
+ credUrl: "https://github.com/settings/tokens"
380
+ },
381
+ {
382
+ id: "filesystem",
383
+ name: "filesystem",
384
+ label: "Filesystem",
385
+ blurb: "Read and write files in a folder you pick.",
386
+ transport: "stdio",
387
+ command: "npx",
388
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/folder"],
389
+ needs: "path",
390
+ fill: "/path/to/folder"
391
+ },
392
+ {
393
+ id: "notion",
394
+ name: "notion",
395
+ label: "Notion",
396
+ blurb: "Pages, databases, and blocks.",
397
+ transport: "stdio",
398
+ command: "npx",
399
+ args: ["-y", "@notionhq/notion-mcp-server"],
400
+ env: { NOTION_TOKEN: "" },
401
+ needs: "token",
402
+ fill: "NOTION_TOKEN",
403
+ credUrl: "https://www.notion.so/my-integrations"
404
+ },
405
+ {
406
+ id: "linear",
407
+ name: "linear",
408
+ label: "Linear",
409
+ blurb: "Issues and projects. Sign in via browser.",
410
+ transport: "http",
411
+ // /sse is GONE — it 404s on both GET and POST (checked 2026-07-31). Linear moved
412
+ // to the Streamable HTTP endpoint; the old URL silently failed for anyone who
413
+ // added Linear from this picker. Mirrored from the client copy on 2026-08-06.
414
+ url: "https://mcp.linear.app/mcp",
415
+ oauth: true,
416
+ needs: "oauth"
417
+ },
418
+ {
419
+ id: "slack",
420
+ name: "slack",
421
+ label: "Slack",
422
+ blurb: "Read and post to channels.",
423
+ transport: "stdio",
424
+ command: "npx",
425
+ args: ["-y", "@modelcontextprotocol/server-slack"],
426
+ env: { SLACK_BOT_TOKEN: "", SLACK_TEAM_ID: "" },
427
+ needs: "token",
428
+ fill: "SLACK_BOT_TOKEN",
429
+ credUrl: "https://api.slack.com/apps"
430
+ },
431
+ {
432
+ id: "postgres",
433
+ name: "postgres",
434
+ label: "PostgreSQL",
435
+ blurb: "Query a Postgres database (read-only).",
436
+ transport: "stdio",
437
+ command: "npx",
438
+ args: ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
439
+ needs: "path",
440
+ fill: "postgresql://localhost/mydb"
441
+ },
442
+ {
443
+ id: "playwright",
444
+ name: "playwright",
445
+ label: "Browser (Playwright)",
446
+ blurb: "Drive a real browser to fetch and click.",
447
+ transport: "stdio",
448
+ command: "npx",
449
+ args: ["-y", "@playwright/mcp@latest"],
450
+ needs: "none"
451
+ },
452
+ {
453
+ id: "memory",
454
+ name: "memory",
455
+ label: "Memory",
456
+ blurb: "A local knowledge-graph scratchpad.",
457
+ transport: "stdio",
458
+ command: "npx",
459
+ args: ["-y", "@modelcontextprotocol/server-memory"],
460
+ needs: "none"
461
+ },
462
+ // ── Remote, browser-authorized (OAuth) ──────────────────────────────────────
463
+ {
464
+ id: "sentry",
465
+ name: "sentry",
466
+ label: "Sentry",
467
+ blurb: "Errors, issues, and releases. Sign in via browser.",
468
+ transport: "http",
469
+ url: "https://mcp.sentry.dev/mcp",
470
+ oauth: true,
471
+ needs: "oauth"
472
+ },
473
+ {
474
+ id: "atlassian",
475
+ name: "atlassian",
476
+ label: "Jira & Confluence",
477
+ blurb: "Atlassian issues and pages. Sign in via browser.",
478
+ transport: "http",
479
+ // Streamable HTTP; /v1/sse is the legacy SSE endpoint, which Pi does not speak.
480
+ url: "https://mcp.atlassian.com/v1/mcp",
481
+ oauth: true,
482
+ needs: "oauth"
483
+ },
484
+ {
485
+ id: "stripe",
486
+ name: "stripe",
487
+ label: "Stripe",
488
+ blurb: "Payments, customers, and invoices. Sign in via browser.",
489
+ transport: "http",
490
+ url: "https://mcp.stripe.com",
491
+ oauth: true,
492
+ needs: "oauth"
493
+ },
494
+ {
495
+ id: "asana",
496
+ name: "asana",
497
+ label: "Asana",
498
+ blurb: "Tasks and projects. Sign in via browser.",
499
+ transport: "http",
500
+ // Streamable HTTP on the same OAuth server as the legacy /sse endpoint, which Pi does
501
+ // not speak. (/v2/mcp is a separate app-registration flow.)
502
+ url: "https://mcp.asana.com/mcp",
503
+ oauth: true,
504
+ needs: "oauth"
505
+ },
506
+ // ── More remote OAuth servers (added 2026-10-20) ───────────────────────────
507
+ // Mirrors the block of the same name in treeview client/components/mcpCatalog.ts,
508
+ // minus the rows whose registration refuses a loopback redirect (Calendly). Each
509
+ // was probed on 2026-10-20: initialize → 401, OAuth discovery, and a dynamic
510
+ // client registration with http://127.0.0.1:<port>/callback. A full consent →
511
+ // tools/list round trip has not been run.
512
+ {
513
+ // Distinct id from the stdio `notion` entry: the two coexist, and this one
514
+ // needs no integration token.
515
+ id: "notion-remote",
516
+ name: "notion-remote",
517
+ label: "Notion",
518
+ blurb: "Pages, databases, and search. Sign in via browser.",
519
+ transport: "http",
520
+ url: "https://mcp.notion.com/mcp",
521
+ oauth: true,
522
+ needs: "oauth"
523
+ },
524
+ {
525
+ id: "supabase-remote",
526
+ name: "supabase-remote",
527
+ label: "Supabase",
528
+ blurb: "Projects, tables, and SQL. Sign in via browser.",
529
+ transport: "http",
530
+ url: "https://mcp.supabase.com/mcp",
531
+ oauth: true,
532
+ needs: "oauth"
533
+ },
534
+ {
535
+ id: "gitlab",
536
+ name: "gitlab",
537
+ label: "GitLab",
538
+ blurb: "Projects, issues, and merge requests. Sign in via browser.",
539
+ transport: "http",
540
+ url: "https://gitlab.com/api/v4/mcp",
541
+ oauth: true,
542
+ needs: "oauth"
543
+ },
544
+ {
545
+ id: "vercel",
546
+ name: "vercel",
547
+ label: "Vercel",
548
+ blurb: "Deployments, logs, and projects. Sign in via browser.",
549
+ transport: "http",
550
+ url: "https://mcp.vercel.com/",
551
+ oauth: true,
552
+ needs: "oauth"
553
+ },
554
+ {
555
+ id: "netlify",
556
+ name: "netlify",
557
+ label: "Netlify",
558
+ blurb: "Sites, deploys, and forms. Sign in via browser.",
559
+ transport: "http",
560
+ url: "https://netlify-mcp.netlify.app/mcp",
561
+ oauth: true,
562
+ needs: "oauth"
563
+ },
564
+ {
565
+ id: "cloudflare",
566
+ name: "cloudflare",
567
+ label: "Cloudflare",
568
+ blurb: "Your whole Cloudflare account through its API. Sign in via browser.",
569
+ transport: "http",
570
+ url: "https://mcp.cloudflare.com/mcp",
571
+ oauth: true,
572
+ needs: "oauth"
573
+ },
574
+ {
575
+ id: "cloudflare-workers",
576
+ name: "cloudflare-workers",
577
+ label: "Cloudflare Workers",
578
+ blurb: "Workers, KV, R2, and D1. Sign in via browser.",
579
+ transport: "http",
580
+ url: "https://bindings.mcp.cloudflare.com/mcp",
581
+ oauth: true,
582
+ needs: "oauth"
583
+ },
584
+ {
585
+ id: "cloudflare-observability",
586
+ name: "cloudflare-observability",
587
+ label: "Cloudflare Observability",
588
+ blurb: "Worker logs and analytics. Sign in via browser.",
589
+ transport: "http",
590
+ url: "https://observability.mcp.cloudflare.com/mcp",
591
+ oauth: true,
592
+ needs: "oauth"
593
+ },
594
+ {
595
+ id: "neon",
596
+ name: "neon",
597
+ label: "Neon",
598
+ blurb: "Serverless Postgres projects, branches, and SQL. Sign in via browser.",
599
+ transport: "http",
600
+ url: "https://mcp.neon.tech/mcp",
601
+ oauth: true,
602
+ needs: "oauth"
603
+ },
604
+ {
605
+ id: "prisma",
606
+ name: "prisma",
607
+ label: "Prisma Postgres",
608
+ blurb: "Databases, migrations, and queries. Sign in via browser.",
609
+ transport: "http",
610
+ url: "https://mcp.prisma.io/mcp",
611
+ oauth: true,
612
+ needs: "oauth"
613
+ },
614
+ {
615
+ id: "instantdb",
616
+ name: "instantdb",
617
+ label: "InstantDB",
618
+ blurb: "Apps, schemas, and data. Sign in via browser.",
619
+ transport: "http",
620
+ url: "https://mcp.instantdb.com/mcp",
621
+ oauth: true,
622
+ needs: "oauth"
623
+ },
624
+ {
625
+ id: "buildkite",
626
+ name: "buildkite",
627
+ label: "Buildkite",
628
+ blurb: "Pipelines, builds, and jobs. Sign in via browser.",
629
+ transport: "http",
630
+ url: "https://mcp.buildkite.com/mcp",
631
+ oauth: true,
632
+ needs: "oauth"
633
+ },
634
+ {
635
+ id: "grafana",
636
+ name: "grafana",
637
+ label: "Grafana",
638
+ blurb: "Dashboards, metrics, and alerts. Sign in via browser.",
639
+ transport: "http",
640
+ url: "https://mcp.grafana.com/mcp",
641
+ oauth: true,
642
+ needs: "oauth"
643
+ },
644
+ {
645
+ id: "honeycomb",
646
+ name: "honeycomb",
647
+ label: "Honeycomb",
648
+ blurb: "Traces, queries, and SLOs. Sign in via browser.",
649
+ transport: "http",
650
+ url: "https://mcp.honeycomb.io/mcp",
651
+ oauth: true,
652
+ needs: "oauth"
653
+ },
654
+ {
655
+ id: "posthog",
656
+ name: "posthog",
657
+ label: "PostHog",
658
+ blurb: "Product analytics, flags, and errors. Sign in via browser.",
659
+ transport: "http",
660
+ url: "https://mcp.posthog.com/mcp",
661
+ oauth: true,
662
+ needs: "oauth"
663
+ },
664
+ {
665
+ id: "amplitude",
666
+ name: "amplitude",
667
+ label: "Amplitude",
668
+ blurb: "Charts, cohorts, and experiments. Sign in via browser.",
669
+ transport: "http",
670
+ url: "https://mcp.amplitude.com/mcp",
671
+ oauth: true,
672
+ needs: "oauth"
673
+ },
674
+ {
675
+ id: "mixpanel",
676
+ name: "mixpanel",
677
+ label: "Mixpanel",
678
+ blurb: "Events, funnels, and reports. Sign in via browser.",
679
+ transport: "http",
680
+ url: "https://mcp.mixpanel.com/mcp",
681
+ oauth: true,
682
+ needs: "oauth"
683
+ },
684
+ {
685
+ id: "semgrep",
686
+ name: "semgrep",
687
+ label: "Semgrep",
688
+ blurb: "Scan code for security issues. Sign in via browser.",
689
+ transport: "http",
690
+ url: "https://mcp.semgrep.ai/mcp",
691
+ oauth: true,
692
+ needs: "oauth"
693
+ },
694
+ {
695
+ id: "port",
696
+ name: "port",
697
+ label: "Port",
698
+ blurb: "Your software catalog and developer portal. Sign in via browser.",
699
+ transport: "http",
700
+ url: "https://mcp.port.io/v1",
701
+ oauth: true,
702
+ needs: "oauth"
703
+ },
704
+ {
705
+ id: "stytch",
706
+ name: "stytch",
707
+ label: "Stytch",
708
+ blurb: "Auth projects, users, and settings. Sign in via browser.",
709
+ transport: "http",
710
+ url: "https://mcp.stytch.dev/mcp",
711
+ oauth: true,
712
+ needs: "oauth"
713
+ },
714
+ {
715
+ id: "globalping",
716
+ name: "globalping",
717
+ label: "Globalping",
718
+ blurb: "Ping, traceroute, and DNS from probes worldwide. Sign in via browser.",
719
+ transport: "http",
720
+ url: "https://mcp.globalping.dev/mcp",
721
+ oauth: true,
722
+ needs: "oauth"
723
+ },
724
+ {
725
+ id: "apify",
726
+ name: "apify",
727
+ label: "Apify",
728
+ blurb: "Run scrapers and actors. Sign in via browser.",
729
+ transport: "http",
730
+ url: "https://mcp.apify.com",
731
+ oauth: true,
732
+ needs: "oauth"
733
+ },
734
+ {
735
+ id: "parallel-search",
736
+ name: "parallel-search",
737
+ label: "Parallel Search",
738
+ blurb: "Web search built for agents. Sign in via browser.",
739
+ transport: "http",
740
+ url: "https://search-mcp.parallel.ai/mcp",
741
+ oauth: true,
742
+ needs: "oauth"
743
+ },
744
+ {
745
+ id: "clickup",
746
+ name: "clickup",
747
+ label: "ClickUp",
748
+ blurb: "Tasks, docs, and spaces. Sign in via browser.",
749
+ transport: "http",
750
+ url: "https://mcp.clickup.com/mcp",
751
+ oauth: true,
752
+ needs: "oauth"
753
+ },
754
+ {
755
+ id: "monday",
756
+ name: "monday",
757
+ label: "monday.com",
758
+ blurb: "Boards, items, and updates. Sign in via browser.",
759
+ transport: "http",
760
+ url: "https://mcp.monday.com/mcp",
761
+ oauth: true,
762
+ needs: "oauth"
763
+ },
764
+ {
765
+ id: "todoist",
766
+ name: "todoist",
767
+ label: "Todoist",
768
+ blurb: "Tasks, projects, and due dates. Sign in via browser.",
769
+ transport: "http",
770
+ url: "https://ai.todoist.net/mcp",
771
+ oauth: true,
772
+ needs: "oauth"
773
+ },
774
+ {
775
+ id: "airtable",
776
+ name: "airtable",
777
+ label: "Airtable",
778
+ blurb: "Bases, tables, and records. Sign in via browser.",
779
+ transport: "http",
780
+ url: "https://mcp.airtable.com/mcp",
781
+ oauth: true,
782
+ needs: "oauth"
783
+ },
784
+ {
785
+ id: "miro",
786
+ name: "miro",
787
+ label: "Miro",
788
+ blurb: "Boards, sticky notes, and diagrams. Sign in via browser.",
789
+ transport: "http",
790
+ url: "https://mcp.miro.com/",
791
+ oauth: true,
792
+ needs: "oauth"
793
+ },
794
+ {
795
+ id: "canva",
796
+ name: "canva",
797
+ label: "Canva",
798
+ blurb: "Find, create, and export designs. Sign in via browser.",
799
+ transport: "http",
800
+ url: "https://mcp.canva.com/mcp",
801
+ oauth: true,
802
+ needs: "oauth"
803
+ },
804
+ {
805
+ id: "webflow",
806
+ name: "webflow",
807
+ label: "Webflow",
808
+ blurb: "Sites, pages, and CMS collections. Sign in via browser.",
809
+ transport: "http",
810
+ url: "https://mcp.webflow.com/mcp",
811
+ oauth: true,
812
+ needs: "oauth"
813
+ },
814
+ {
815
+ id: "wix",
816
+ name: "wix",
817
+ label: "Wix",
818
+ blurb: "Sites, stores, and bookings. Sign in via browser.",
819
+ transport: "http",
820
+ url: "https://mcp.wix.com/mcp",
821
+ oauth: true,
822
+ needs: "oauth"
823
+ },
824
+ {
825
+ id: "cloudinary",
826
+ name: "cloudinary",
827
+ label: "Cloudinary",
828
+ blurb: "Upload, search, and transform media assets. Sign in via browser.",
829
+ transport: "http",
830
+ url: "https://asset-management.mcp.cloudinary.com/mcp",
831
+ oauth: true,
832
+ needs: "oauth"
833
+ },
834
+ {
835
+ id: "attio",
836
+ name: "attio",
837
+ label: "Attio",
838
+ blurb: "CRM records, lists, and notes. Sign in via browser.",
839
+ transport: "http",
840
+ url: "https://mcp.attio.com/mcp",
841
+ oauth: true,
842
+ needs: "oauth"
843
+ },
844
+ {
845
+ id: "intercom",
846
+ name: "intercom",
847
+ label: "Intercom",
848
+ blurb: "Conversations, contacts, and tickets. Sign in via browser.",
849
+ transport: "http",
850
+ url: "https://mcp.intercom.com/mcp",
851
+ oauth: true,
852
+ needs: "oauth"
853
+ },
854
+ {
855
+ id: "zapier",
856
+ name: "zapier",
857
+ label: "Zapier",
858
+ blurb: "Run actions across thousands of apps. Sign in via browser.",
859
+ transport: "http",
860
+ url: "https://mcp.zapier.com/api/mcp/mcp",
861
+ oauth: true,
862
+ needs: "oauth"
863
+ },
864
+ {
865
+ id: "paypal",
866
+ name: "paypal",
867
+ label: "PayPal",
868
+ blurb: "Invoices, orders, and transactions. Sign in via browser.",
869
+ transport: "http",
870
+ url: "https://mcp.paypal.com/mcp",
871
+ oauth: true,
872
+ needs: "oauth"
873
+ },
874
+ {
875
+ id: "square",
876
+ name: "square",
877
+ label: "Square",
878
+ blurb: "Payments, catalog, and orders. Sign in via browser.",
879
+ transport: "http",
880
+ url: "https://mcp.squareup.com/mcp",
881
+ oauth: true,
882
+ needs: "oauth"
883
+ },
884
+ {
885
+ id: "ramp",
886
+ name: "ramp",
887
+ label: "Ramp",
888
+ blurb: "Spend, cards, and reimbursements. Sign in via browser.",
889
+ transport: "http",
890
+ url: "https://ramp-mcp-remote.ramp.com/mcp",
891
+ oauth: true,
892
+ needs: "oauth"
893
+ },
894
+ {
895
+ id: "morningstar",
896
+ name: "morningstar",
897
+ label: "Morningstar",
898
+ blurb: "Fund and stock research data. Sign in via browser.",
899
+ transport: "http",
900
+ url: "https://mcp.morningstar.com/mcp",
901
+ oauth: true,
902
+ needs: "oauth"
903
+ },
904
+ {
905
+ id: "thoughtspot",
906
+ name: "thoughtspot",
907
+ label: "ThoughtSpot",
908
+ blurb: "Ask questions of your analytics data. Sign in via browser.",
909
+ transport: "http",
910
+ url: "https://agent.thoughtspot.app/mcp",
911
+ oauth: true,
912
+ needs: "oauth"
913
+ },
914
+ {
915
+ id: "jam",
916
+ name: "jam",
917
+ label: "Jam",
918
+ blurb: "Bug reports with recordings and logs. Sign in via browser.",
919
+ transport: "http",
920
+ url: "https://mcp.jam.dev/mcp",
921
+ oauth: true,
922
+ needs: "oauth"
923
+ },
924
+ {
925
+ id: "granola",
926
+ name: "granola",
927
+ label: "Granola",
928
+ blurb: "Your meeting notes. Sign in via browser.",
929
+ transport: "http",
930
+ url: "https://mcp.granola.ai/mcp",
931
+ oauth: true,
932
+ needs: "oauth"
933
+ },
934
+ {
935
+ id: "fathom",
936
+ name: "fathom",
937
+ label: "Fathom",
938
+ blurb: "Meeting recordings, transcripts, and summaries. Sign in via browser.",
939
+ transport: "http",
940
+ url: "https://api.fathom.ai/mcp",
941
+ oauth: true,
942
+ needs: "oauth"
943
+ },
944
+ {
945
+ id: "fireflies",
946
+ name: "fireflies",
947
+ label: "Fireflies.ai",
948
+ blurb: "Meeting transcripts and summaries. Sign in via browser.",
949
+ transport: "http",
950
+ url: "https://api.fireflies.ai/mcp",
951
+ oauth: true,
952
+ needs: "oauth"
953
+ },
954
+ {
955
+ id: "otter",
956
+ name: "otter",
957
+ label: "Otter.ai",
958
+ blurb: "Meeting transcripts and notes. Sign in via browser.",
959
+ transport: "http",
960
+ url: "https://mcp.otter.ai/mcp",
961
+ oauth: true,
962
+ needs: "oauth"
963
+ },
964
+ {
965
+ id: "read-ai",
966
+ name: "read-ai",
967
+ label: "Read AI",
968
+ blurb: "Meeting reports and recaps. Sign in via browser.",
969
+ transport: "http",
970
+ url: "https://api.read.ai/mcp",
971
+ oauth: true,
972
+ needs: "oauth"
973
+ },
974
+ {
975
+ id: "tldv",
976
+ name: "tldv",
977
+ label: "tl;dv",
978
+ blurb: "Meeting recordings and highlights. Sign in via browser.",
979
+ transport: "http",
980
+ url: "https://mcp.tldv.io/mcp",
981
+ oauth: true,
982
+ needs: "oauth"
983
+ },
984
+ // ── Local, token-authorized ─────────────────────────────────────────────────
985
+ {
986
+ id: "brave-search",
987
+ name: "brave-search",
988
+ label: "Brave Search",
989
+ blurb: "Web and local search results.",
990
+ transport: "stdio",
991
+ command: "npx",
992
+ args: ["-y", "@modelcontextprotocol/server-brave-search"],
993
+ env: { BRAVE_API_KEY: "" },
994
+ needs: "token",
995
+ fill: "BRAVE_API_KEY",
996
+ credUrl: "https://brave.com/search/api/"
997
+ },
998
+ {
999
+ id: "google-maps",
1000
+ name: "google-maps",
1001
+ label: "Google Maps",
1002
+ blurb: "Places, directions, and geocoding.",
1003
+ transport: "stdio",
1004
+ command: "npx",
1005
+ args: ["-y", "@modelcontextprotocol/server-google-maps"],
1006
+ env: { GOOGLE_MAPS_API_KEY: "" },
1007
+ needs: "token",
1008
+ fill: "GOOGLE_MAPS_API_KEY",
1009
+ credUrl: "https://console.cloud.google.com/google/maps-apis/credentials"
1010
+ },
1011
+ {
1012
+ id: "supabase",
1013
+ name: "supabase",
1014
+ label: "Supabase",
1015
+ blurb: "Query and manage your Supabase project.",
1016
+ transport: "stdio",
1017
+ command: "npx",
1018
+ args: ["-y", "@supabase/mcp-server-supabase@latest"],
1019
+ env: { SUPABASE_ACCESS_TOKEN: "" },
1020
+ needs: "token",
1021
+ fill: "SUPABASE_ACCESS_TOKEN",
1022
+ credUrl: "https://supabase.com/dashboard/account/tokens"
1023
+ },
1024
+ {
1025
+ id: "figma",
1026
+ name: "figma",
1027
+ label: "Figma",
1028
+ blurb: "Read designs, frames, and components.",
1029
+ transport: "stdio",
1030
+ command: "npx",
1031
+ args: ["-y", "figma-developer-mcp", "--stdio"],
1032
+ env: { FIGMA_API_KEY: "" },
1033
+ needs: "token",
1034
+ fill: "FIGMA_API_KEY",
1035
+ credUrl: "https://www.figma.com/developers/api#access-tokens"
1036
+ },
1037
+ // ── Google Workspace (needs a Google Cloud OAuth client, then browser sign-in) ─
1038
+ // One server (workspace-mcp) scoped per service via --tools. Runs on uv (uvx),
1039
+ // not npx. Create an OAuth client in Google Cloud once and paste its ID + secret;
1040
+ // the first request opens Google's consent screen on THIS machine.
1041
+ {
1042
+ id: "gmail",
1043
+ name: "gmail",
1044
+ label: "Gmail",
1045
+ blurb: "Read, search, and send email. Google sign-in.",
1046
+ transport: "stdio",
1047
+ command: "uvx",
1048
+ args: ["workspace-mcp", "--tools", "gmail"],
1049
+ env: { GOOGLE_OAUTH_CLIENT_ID: "", GOOGLE_OAUTH_CLIENT_SECRET: "" },
1050
+ needs: "token",
1051
+ fill: "GOOGLE_OAUTH_CLIENT_ID",
1052
+ credUrl: "https://console.cloud.google.com/apis/credentials"
1053
+ },
1054
+ {
1055
+ id: "google-calendar",
1056
+ name: "google-calendar",
1057
+ label: "Google Calendar",
1058
+ blurb: "Events and scheduling. Google sign-in.",
1059
+ transport: "stdio",
1060
+ command: "uvx",
1061
+ args: ["workspace-mcp", "--tools", "calendar"],
1062
+ env: { GOOGLE_OAUTH_CLIENT_ID: "", GOOGLE_OAUTH_CLIENT_SECRET: "" },
1063
+ needs: "token",
1064
+ fill: "GOOGLE_OAUTH_CLIENT_ID",
1065
+ credUrl: "https://console.cloud.google.com/apis/credentials"
1066
+ },
1067
+ {
1068
+ id: "google-drive",
1069
+ name: "google-drive",
1070
+ label: "Google Drive",
1071
+ blurb: "Files and folders in your Drive. Google sign-in.",
1072
+ transport: "stdio",
1073
+ command: "uvx",
1074
+ args: ["workspace-mcp", "--tools", "drive"],
1075
+ env: { GOOGLE_OAUTH_CLIENT_ID: "", GOOGLE_OAUTH_CLIENT_SECRET: "" },
1076
+ needs: "token",
1077
+ fill: "GOOGLE_OAUTH_CLIENT_ID",
1078
+ credUrl: "https://console.cloud.google.com/apis/credentials"
1079
+ },
1080
+ {
1081
+ id: "google-docs",
1082
+ name: "google-docs",
1083
+ label: "Google Docs",
1084
+ blurb: "Read and edit your documents. Google sign-in.",
1085
+ transport: "stdio",
1086
+ command: "uvx",
1087
+ args: ["workspace-mcp", "--tools", "docs"],
1088
+ env: { GOOGLE_OAUTH_CLIENT_ID: "", GOOGLE_OAUTH_CLIENT_SECRET: "" },
1089
+ needs: "token",
1090
+ fill: "GOOGLE_OAUTH_CLIENT_ID",
1091
+ credUrl: "https://console.cloud.google.com/apis/credentials"
1092
+ },
1093
+ {
1094
+ id: "sequential-thinking",
1095
+ name: "sequential-thinking",
1096
+ label: "Sequential Thinking",
1097
+ blurb: "A step-by-step reasoning scratchpad.",
1098
+ transport: "stdio",
1099
+ command: "npx",
1100
+ args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
1101
+ needs: "none"
1102
+ },
1103
+ // ── Local apps that host their own MCP server ───────────────────────────────
1104
+ // http, but on 127.0.0.1: nothing to install, nothing to authorize, nothing
1105
+ // leaving the machine. See `localHttp` on CatalogEntry for why that needs a flag.
1106
+ {
1107
+ // Unreal Engine 5.8 embeds an MCP server in the EDITOR PROCESS (plugin
1108
+ // `ModelContextProtocol`, surfaced as "Unreal MCP"; the tools come from the
1109
+ // "All Toolsets" plugin, which has to be enabled too). Three facts shape this:
1110
+ //
1111
+ // 1. NO AUTHENTICATION, of any kind. Hence localHttp + auth:"none".
1112
+ // 2. LOOPBACK ONLY. It binds per [HTTPServer.Listeners] DefaultBindAddress
1113
+ // (default `localhost`) AND rejects non-loopback `Origin` headers — so the
1114
+ // agent has to be on the same machine as the editor. True for this CLI and
1115
+ // for the desktop app; not true for a hosted agent, hence hosted: false.
1116
+ // 3. IT IS ONLY UP WHILE THE EDITOR IS. A connector that fails here usually
1117
+ // means "Unreal isn't running", not "this is misconfigured".
1118
+ //
1119
+ // The port and path are editable in Editor Preferences → Model Context
1120
+ // Protocol, so `needs: "url"`: the one setup step is confirming the endpoint
1121
+ // rather than pasting a secret. `ModelContextProtocol.GenerateClientConfig` in
1122
+ // the UE console prints the URL the editor is actually serving.
1123
+ id: "unreal",
1124
+ name: "unreal",
1125
+ label: "Unreal Engine",
1126
+ blurb: "Drive the Unreal Editor \u2014 actors, lighting, materials, tests.",
1127
+ transport: "http",
1128
+ url: "http://127.0.0.1:8000/mcp",
1129
+ localHttp: true,
1130
+ needs: "url",
1131
+ hosted: false,
1132
+ docsUrl: "https://dev.epicgames.com/documentation/unreal-engine/unreal-mcp-in-unreal-editor"
1133
+ },
1134
+ {
1135
+ // Godot ships no MCP server of its own. This is the "Godot MCP Native" editor
1136
+ // plugin (MIT, Godot Asset Library) — chosen because it serves streamable HTTP
1137
+ // from INSIDE the editor process, which is the same shape as the Unreal entry
1138
+ // above and needs nothing installed on the machine outside Godot itself.
1139
+ //
1140
+ // Deliberately NOT one of the npx-launched Godot servers: those shell out to the
1141
+ // `godot` BINARY headlessly (a second, non-interactive copy of the project rather
1142
+ // than the editor the user is looking at), and every one of them is configured by
1143
+ // a GODOT_PATH env var — a filesystem path, which is neither a token nor a
1144
+ // placeholder arg, so this catalog has no `needs` that could ask for it honestly.
1145
+ //
1146
+ // Two things differ from Unreal, and they are the only two:
1147
+ // 1. THE PLUGIN IS NOT PART OF THE ENGINE. Install "Godot MCP Native" from the
1148
+ // Asset Library and enable it in Project → Project Settings → Plugins. Until
1149
+ // that is done the port is simply closed, which looks identical to "Godot is
1150
+ // not running" — so the setup guide below matters more here than it does for
1151
+ // Unreal, where the plugin ships with 5.8.
1152
+ // 2. ITS BIND ADDRESS IS NOT DOCUMENTED UPSTREAM. Unreal's is (loopback, plus an
1153
+ // Origin check); this one's is not, so the honest claim is only that WE dial
1154
+ // 127.0.0.1 — never that the editor cannot be reached from the LAN. Its own
1155
+ // `auth_enabled` is off by default (user://mcp_settings.cfg), which is the
1156
+ // knob to turn on when the machine sits on a network you don't trust.
1157
+ //
1158
+ // Everything else matches Unreal: nothing to authorize on the wire we use (hence
1159
+ // localHttp + auth:"none"), it answers only while the editor is open, and the port
1160
+ // is editable (`http_port`) — hence needs:"url", confirm the endpoint.
1161
+ id: "godot",
1162
+ name: "godot",
1163
+ label: "Godot Engine",
1164
+ blurb: "Drive the Godot editor \u2014 scenes, nodes, scripts, resources.",
1165
+ transport: "http",
1166
+ url: "http://127.0.0.1:9080/mcp",
1167
+ localHttp: true,
1168
+ needs: "url",
1169
+ // Same reason as Unreal: hostedCapable() derives its answer from `oauth`, so
1170
+ // http-with-no-auth would otherwise read as hostable. An enclave cannot reach a
1171
+ // loopback port on the user's desk.
1172
+ hosted: false,
1173
+ docsUrl: "https://github.com/yurineko73/Godot-MCP-Native"
1174
+ }
1175
+ ];
1176
+ function promptOrder(e) {
1177
+ const keys = Object.keys(e.env ?? {});
1178
+ if (!e.fill || !keys.includes(e.fill)) return keys;
1179
+ return [e.fill, ...keys.filter((k) => k !== e.fill)];
1180
+ }
1181
+ __name(promptOrder, "promptOrder");
1182
+ function draftFromCatalog(e, input = {}) {
1183
+ const draft = { name: e.name, transport: e.transport };
1184
+ if (e.transport === "stdio") {
1185
+ draft.command = e.command;
1186
+ const filled = input.fill?.trim();
1187
+ draft.args = (e.args ?? []).map((a) => e.fill && a === e.fill && filled ? filled : a);
1188
+ } else {
1189
+ draft.url = input.url?.trim() || e.url;
1190
+ draft.auth = e.localHttp ? "none" : e.oauth ?? true ? "oauth" : "none";
1191
+ }
1192
+ const keys = Object.keys(e.env ?? {});
1193
+ if (keys.length > 0) {
1194
+ const env = {};
1195
+ for (const k of keys) env[k] = input.env?.[k] ?? "";
1196
+ draft.env = env;
1197
+ }
1198
+ return draft;
1199
+ }
1200
+ __name(draftFromCatalog, "draftFromCatalog");
1201
+
1202
+ // src/config/hosted.ts?ext=privateer-connect
1203
+ function isHosted() {
1204
+ return process.env.HARBOR_HOSTED === "1";
1205
+ }
1206
+ __name(isHosted, "isHosted");
1207
+
1208
+ // extensions/privateer-connect.ts?ext=privateer-connect
1209
+ var MAX_VISIBLE = 10;
1210
+ var SecretField = class {
1211
+ static {
1212
+ __name(this, "SecretField");
1213
+ }
1214
+ value = "";
1215
+ // Returns true when the keystroke was consumed. Escape sequences (arrows, F-keys)
1216
+ // are swallowed rather than inserted — the naive "strip control chars" approach
1217
+ // would leave "[A" behind from an up-arrow and silently corrupt the token.
1218
+ handleInput(data) {
1219
+ const paste = /^\x1b\[200~([\s\S]*?)\x1b\[201~$/.exec(data);
1220
+ if (paste) {
1221
+ this.value += paste[1].replace(/[\x00-\x1f\x7f]/g, "");
1222
+ return true;
1223
+ }
1224
+ if (data.startsWith("\x1B")) return true;
1225
+ if (data === "\x7F" || data === "\b") {
1226
+ this.value = this.value.slice(0, -1);
1227
+ return true;
1228
+ }
1229
+ if (data === "") {
1230
+ this.value = "";
1231
+ return true;
1232
+ }
1233
+ const printable = data.replace(/[\x00-\x1f\x7f]/g, "");
1234
+ if (!printable) return false;
1235
+ this.value += printable;
1236
+ return true;
1237
+ }
1238
+ // Dots, capped so a long PAT can't wrap the panel. Length is intentionally NOT
1239
+ // exact past the cap — it isn't information the user needs, and it leaks the
1240
+ // token's length to anyone watching.
1241
+ display() {
1242
+ if (!this.value) return "";
1243
+ return "\u2022".repeat(Math.min(this.value.length, 32));
1244
+ }
1245
+ };
1246
+ var isHttpTarget = /* @__PURE__ */ __name((answers) => /^https?:\/\//i.test((answers.target ?? "").trim()), "isHttpTarget");
1247
+ function customSteps(existing) {
1248
+ const hosted = isHosted();
1249
+ const target = existing ? existing.transport === "http" ? existing.url : [existing.command, existing.argsPreview].filter(Boolean).join(" ") : void 0;
1250
+ return [
1251
+ {
1252
+ key: "name",
1253
+ prompt: "Connector name",
1254
+ hint: "Lowercase, no spaces \u2014 it prefixes every tool, e.g. github__list_issues",
1255
+ initial: existing?.name
1256
+ },
1257
+ {
1258
+ key: "target",
1259
+ prompt: hosted ? "Server URL" : "Launch command, or an https:// URL",
1260
+ hint: hosted ? "e.g. https://mcp.example.com/mcp \u2014 a hosted agent can't run a local command." : "e.g. npx -y @modelcontextprotocol/server-memory \xB7 or https://mcp.example.com/mcp",
1261
+ initial: target,
1262
+ validate: hosted ? (v) => /^https?:\/\//i.test(v.trim()) ? void 0 : "A hosted agent can only reach remote connectors \u2014 this needs an https:// URL." : void 0
1263
+ },
1264
+ {
1265
+ key: "env",
1266
+ prompt: "Environment variables (optional)",
1267
+ hint: "KEY=value, comma-separated. Leave blank for none.",
1268
+ optional: true,
1269
+ // Nothing to set them ON when there is no local process to spawn, and their
1270
+ // values are credentials at rest — both reasons a hosted agent skips this.
1271
+ when: /* @__PURE__ */ __name(() => !hosted, "when")
1272
+ },
1273
+ {
1274
+ key: "bearerToken",
1275
+ prompt: "Bearer token (optional)",
1276
+ hint: existing?.bearerTokenSet ? "Leave blank to keep the token already saved." : "Leave blank to sign in through the browser instead (OAuth).",
1277
+ secret: true,
1278
+ optional: true,
1279
+ // Never asked on a hosted agent: the home is tmpfs and a stored token would have
1280
+ // to rest somewhere we can read. OAuth is the only credential it may hold.
1281
+ when: /* @__PURE__ */ __name((a) => !hosted && isHttpTarget(a), "when")
1282
+ },
1283
+ {
1284
+ key: "headers",
1285
+ prompt: "Extra HTTP headers (optional)",
1286
+ hint: "KEY=value, comma-separated \u2014 e.g. X-Api-Version=2. Leave blank for none.",
1287
+ // Header values are credentials as often as not, so this is masked like a token
1288
+ // — and for the same reason a hosted agent is never offered it.
1289
+ secret: true,
1290
+ optional: true,
1291
+ when: /* @__PURE__ */ __name((a) => !hosted && isHttpTarget(a), "when")
1292
+ }
1293
+ ];
1294
+ }
1295
+ __name(customSteps, "customSteps");
1296
+ function parseEnv(answer) {
1297
+ const env = {};
1298
+ for (const pair of answer.split(",")) {
1299
+ const eq = pair.indexOf("=");
1300
+ if (eq <= 0) continue;
1301
+ const key = pair.slice(0, eq).trim();
1302
+ const val = pair.slice(eq + 1).trim();
1303
+ if (key) env[key] = val;
1304
+ }
1305
+ return env;
1306
+ }
1307
+ __name(parseEnv, "parseEnv");
1308
+ function buildCustomDraft(answers) {
1309
+ const name = (answers.name ?? "").trim();
1310
+ const target = (answers.target ?? "").trim();
1311
+ const draft = { name };
1312
+ if (/^https?:\/\//i.test(target)) {
1313
+ draft.transport = "http";
1314
+ draft.url = target;
1315
+ const token = (answers.bearerToken ?? "").trim();
1316
+ const headers = parseEnv(answers.headers ?? "");
1317
+ const hasHeaders = Object.keys(headers).length > 0;
1318
+ if (hasHeaders) draft.headers = headers;
1319
+ if (token) {
1320
+ draft.auth = "bearer";
1321
+ draft.bearerToken = token;
1322
+ } else if (hasHeaders) {
1323
+ draft.auth = "none";
1324
+ } else {
1325
+ draft.auth = "oauth";
1326
+ }
1327
+ } else {
1328
+ draft.transport = "stdio";
1329
+ const parts = target.split(/\s+/).filter(Boolean);
1330
+ draft.command = parts[0] ?? "";
1331
+ draft.args = parts.slice(1);
1332
+ }
1333
+ const env = parseEnv(answers.env ?? "");
1334
+ if (Object.keys(env).length > 0) draft.env = env;
1335
+ return draft;
1336
+ }
1337
+ __name(buildCustomDraft, "buildCustomDraft");
1338
+ function pendingFromCatalog(e) {
1339
+ const steps = [];
1340
+ if (e.needs === "token") {
1341
+ for (const key of promptOrder(e)) {
1342
+ steps.push({
1343
+ key: `env:${key}`,
1344
+ prompt: `Paste your ${key}`,
1345
+ hint: e.credUrl ? `Get one at ${e.credUrl}` : void 0,
1346
+ secret: true,
1347
+ // Only the primary credential is required; the rest can be filled later.
1348
+ optional: key !== e.fill
1349
+ });
1350
+ }
1351
+ } else if (e.needs === "path" && e.fill) {
1352
+ steps.push({
1353
+ key: "fill",
1354
+ prompt: e.fill.startsWith("postgres") ? "Connection string" : "Folder path",
1355
+ hint: `Replaces the placeholder ${e.fill}`,
1356
+ initial: e.fill
1357
+ });
1358
+ } else if (e.needs === "url") {
1359
+ steps.push({
1360
+ key: "url",
1361
+ prompt: "Server URL",
1362
+ hint: `Default is ${e.url} \u2014 change it only if you moved it.`,
1363
+ initial: e.url,
1364
+ validate: /* @__PURE__ */ __name((v) => /^https?:\/\//i.test(v.trim()) ? void 0 : "That needs to be an http:// or https:// URL.", "validate")
1365
+ });
1366
+ }
1367
+ return {
1368
+ title: e.label,
1369
+ steps,
1370
+ origin: "catalog",
1371
+ build: /* @__PURE__ */ __name((answers) => {
1372
+ const env = {};
1373
+ for (const [k, v] of Object.entries(answers)) {
1374
+ if (k.startsWith("env:")) env[k.slice(4)] = v;
1375
+ }
1376
+ return draftFromCatalog(e, { env, fill: answers.fill, url: answers.url });
1377
+ }, "build"),
1378
+ note: e.needs === "oauth" ? `Authorize it in a browser on THIS machine: /mcp login ${e.name}` : e.localHttp ? `Works whenever ${e.label} is running on this machine. Nothing to authorize.` : void 0
1379
+ };
1380
+ }
1381
+ __name(pendingFromCatalog, "pendingFromCatalog");
1382
+ var NEEDS_LABEL = {
1383
+ token: "needs a token",
1384
+ path: "needs a path",
1385
+ oauth: "browser sign-in",
1386
+ url: "confirm the URL",
1387
+ none: "no setup"
1388
+ };
1389
+ function catalogRows() {
1390
+ const hosted = isHosted();
1391
+ const entries = hosted ? MCP_CATALOG.filter(hostedCapable) : MCP_CATALOG;
1392
+ const rows = entries.map((e) => ({
1393
+ entry: e,
1394
+ label: e.label,
1395
+ blurb: e.blurb,
1396
+ needsLabel: NEEDS_LABEL[e.needs] ?? ""
1397
+ }));
1398
+ rows.push({
1399
+ label: "Custom\u2026",
1400
+ // Custom survives the filter because a custom REMOTE OAuth connector is fine here;
1401
+ // it's stdio and stored tokens that aren't. customSteps enforces that.
1402
+ blurb: hosted ? "Any https:// URL with browser sign-in." : "Any stdio command or http URL.",
1403
+ needsLabel: ""
1404
+ });
1405
+ return rows;
1406
+ }
1407
+ __name(catalogRows, "catalogRows");
1408
+ var ConnectPanel = class extends Container {
1409
+ static {
1410
+ __name(this, "ConnectPanel");
1411
+ }
1412
+ tui;
1413
+ theme;
1414
+ control = makeMcpControl();
1415
+ close;
1416
+ view = "list";
1417
+ servers = [];
1418
+ changed = false;
1419
+ status = "";
1420
+ // list view
1421
+ index = 0;
1422
+ // Name of the connector a first `d` has armed for removal; see removeSelected.
1423
+ armedRemoval;
1424
+ // catalog view
1425
+ rows = [];
1426
+ filtered = [];
1427
+ catalogIndex = 0;
1428
+ search = new Input();
1429
+ // form view
1430
+ pending;
1431
+ stepIndex = 0;
1432
+ answers = {};
1433
+ field = new Input();
1434
+ secret = new SecretField();
1435
+ body = new Container();
1436
+ footer = new Text("", 1, 0);
1437
+ _focused = false;
1438
+ constructor(opts) {
1439
+ super();
1440
+ this.tui = opts.tui;
1441
+ this.theme = opts.theme;
1442
+ this.close = opts.close;
1443
+ this.addChild(this.body);
1444
+ this.addChild(new Spacer(1));
1445
+ this.addChild(this.footer);
1446
+ this.search.onSubmit = () => this.chooseCatalogRow();
1447
+ this.field.onSubmit = () => this.submitStep();
1448
+ this.reload();
1449
+ }
1450
+ get focused() {
1451
+ return this._focused;
1452
+ }
1453
+ set focused(v) {
1454
+ this._focused = v;
1455
+ this.search.focused = v && this.view === "catalog";
1456
+ this.field.focused = v && this.view === "form" && !this.currentStep()?.secret;
1457
+ }
1458
+ reload() {
1459
+ try {
1460
+ this.servers = this.control.list().sort((a, b) => a.name.localeCompare(b.name));
1461
+ } catch (e) {
1462
+ this.servers = [];
1463
+ this.status = `Couldn't read MCP config: ${e.message}`;
1464
+ }
1465
+ if (this.index >= this.servers.length) this.index = Math.max(0, this.servers.length - 1);
1466
+ this.refresh();
1467
+ }
1468
+ currentStep() {
1469
+ return this.pending?.steps[this.stepIndex];
1470
+ }
1471
+ // A step is asked only when its `when` predicate (if any) passes against the answers
1472
+ // collected so far. Navigation walks over the hidden ones in both directions, and
1473
+ // the position counter reports the VISIBLE steps so "2/3" doesn't count a question
1474
+ // the user will never see.
1475
+ stepVisible(i) {
1476
+ const step = this.pending?.steps[i];
1477
+ if (!step) return false;
1478
+ return step.when ? step.when(this.answers) : true;
1479
+ }
1480
+ seekStep(from, dir) {
1481
+ const total = this.pending?.steps.length ?? 0;
1482
+ for (let i = from; i >= 0 && i < total; i += dir) {
1483
+ if (this.stepVisible(i)) return i;
1484
+ }
1485
+ return void 0;
1486
+ }
1487
+ visiblePosition() {
1488
+ const total = this.pending?.steps.length ?? 0;
1489
+ let at = 0;
1490
+ let of = 0;
1491
+ for (let i = 0; i < total; i++) {
1492
+ if (!this.stepVisible(i)) continue;
1493
+ of++;
1494
+ if (i <= this.stepIndex) at = of;
1495
+ }
1496
+ return { at, of };
1497
+ }
1498
+ // -- rendering ------------------------------------------------------------
1499
+ refresh() {
1500
+ const t = this.theme;
1501
+ this.body.clear();
1502
+ if (this.view === "list") this.renderList();
1503
+ else if (this.view === "catalog") this.renderCatalog();
1504
+ else this.renderForm();
1505
+ if (this.status) {
1506
+ this.body.addChild(new Spacer(1));
1507
+ this.body.addChild(new Text(t.fg("muted", ` ${this.status}`), 1, 0));
1508
+ }
1509
+ this.tui.requestRender();
1510
+ }
1511
+ // The one-line summary of a connector's readiness. This is the whole reason the
1512
+ // list exists: "saved" and "usable" are different states, and an http/OAuth server
1513
+ // that was never authorized fails only at call time otherwise.
1514
+ statusOf(s) {
1515
+ const t = this.theme;
1516
+ if (s.transport === "http" && s.oauth) {
1517
+ return t.fg("warning", `\u26A0 authorize: /mcp login ${s.name}`);
1518
+ }
1519
+ if (s.envKeys.length > 0) {
1520
+ const missing = s.envKeys.filter((k) => !s.secretsSet.includes(k));
1521
+ if (missing.length > 0) return t.fg("warning", `\u26A0 needs ${missing.join(", ")}`);
1522
+ return t.fg("success", "\u26BF credentials set");
1523
+ }
1524
+ return t.fg("muted", s.argsPreview ?? s.url ?? "");
1525
+ }
1526
+ renderList() {
1527
+ const t = this.theme;
1528
+ this.body.addChild(new Text(t.fg("accent", t.bold("Connect a service")), 1, 0));
1529
+ this.body.addChild(
1530
+ new Text(t.fg("muted", "MCP connectors on this machine \u2014 shared with the app and the harbor."), 1, 0)
1531
+ );
1532
+ this.body.addChild(new Spacer(1));
1533
+ if (this.servers.length === 0) {
1534
+ this.body.addChild(new Text(t.fg("muted", " No connectors yet."), 1, 0));
1535
+ } else {
1536
+ const width = Math.max(...this.servers.map((s) => s.name.length));
1537
+ this.servers.forEach((s, i) => {
1538
+ const sel = i === this.index;
1539
+ const prefix = sel ? t.fg("accent", "\u2192 ") : " ";
1540
+ const dot = s.enabled ? t.fg("success", "\u25CF") : t.fg("muted", "\u25CB");
1541
+ const name = sel ? t.fg("accent", s.name.padEnd(width)) : t.fg("text", s.name.padEnd(width));
1542
+ const transport = t.fg("muted", s.transport.padEnd(5));
1543
+ this.body.addChild(new Text(`${prefix}${dot} ${name} ${transport} ${this.statusOf(s)}`, 1, 0));
1544
+ });
1545
+ }
1546
+ this.body.addChild(new Spacer(1));
1547
+ const addSel = this.index === this.servers.length;
1548
+ this.body.addChild(
1549
+ new Text(
1550
+ addSel ? t.fg("accent", "\u2192 + Add a connector\u2026") : t.fg("muted", " + Add a connector\u2026"),
1551
+ 1,
1552
+ 0
1553
+ )
1554
+ );
1555
+ this.footer.setText(
1556
+ t.fg("muted", "\u2191\u2193 move space enable/disable \u23CE edit a add d remove esc done")
1557
+ );
1558
+ }
1559
+ renderCatalog() {
1560
+ const t = this.theme;
1561
+ this.body.addChild(new Text(t.fg("accent", t.bold("Add a connector")), 1, 0));
1562
+ if (isHosted()) {
1563
+ this.body.addChild(
1564
+ new Text(t.fg("muted", " Hosted agent \u2014 remote connectors with browser sign-in only."), 1, 0)
1565
+ );
1566
+ }
1567
+ this.body.addChild(new Spacer(1));
1568
+ this.body.addChild(this.search);
1569
+ this.body.addChild(new Spacer(1));
1570
+ if (this.filtered.length === 0) {
1571
+ this.body.addChild(new Text(t.fg("muted", " No matches"), 1, 0));
1572
+ } else {
1573
+ const width = Math.max(...this.filtered.map((r) => r.label.length));
1574
+ const start = Math.max(
1575
+ 0,
1576
+ Math.min(this.catalogIndex - Math.floor(MAX_VISIBLE / 2), this.filtered.length - MAX_VISIBLE)
1577
+ );
1578
+ const end = Math.min(start + MAX_VISIBLE, this.filtered.length);
1579
+ for (let i = start; i < end; i++) {
1580
+ const row = this.filtered[i];
1581
+ const sel = i === this.catalogIndex;
1582
+ const prefix = sel ? t.fg("accent", "\u2192 ") : " ";
1583
+ const label = sel ? t.fg("accent", row.label.padEnd(width)) : t.fg("text", row.label.padEnd(width));
1584
+ const needs = row.needsLabel ? t.fg("muted", ` ${row.needsLabel}`) : "";
1585
+ this.body.addChild(new Text(`${prefix}${label} ${t.fg("muted", row.blurb)}${needs}`, 1, 0));
1586
+ }
1587
+ }
1588
+ this.footer.setText(t.fg("muted", "\u2191\u2193 move \u23CE choose type to search esc back"));
1589
+ }
1590
+ renderForm() {
1591
+ const t = this.theme;
1592
+ const p = this.pending;
1593
+ const step = this.currentStep();
1594
+ const pos = this.visiblePosition();
1595
+ const counter = pos.of > 1 ? ` ${pos.at}/${pos.of}` : "";
1596
+ this.body.addChild(new Text(t.fg("accent", t.bold(p.title)) + t.fg("muted", counter), 1, 0));
1597
+ this.body.addChild(new Spacer(1));
1598
+ this.body.addChild(new Text(` ${t.fg("text", step.prompt)}`, 1, 0));
1599
+ if (step.hint) this.body.addChild(new Text(` ${t.fg("muted", step.hint)}`, 1, 0));
1600
+ this.body.addChild(new Spacer(1));
1601
+ if (step.secret) {
1602
+ const shown = this.secret.display();
1603
+ this.body.addChild(
1604
+ new Text(` ${shown || t.fg("muted", "(nothing typed yet)")}`, 1, 0)
1605
+ );
1606
+ } else {
1607
+ this.body.addChild(this.field);
1608
+ }
1609
+ this.footer.setText(
1610
+ t.fg(
1611
+ "muted",
1612
+ step.optional ? "\u23CE next (blank to skip) esc back" : "\u23CE next esc back"
1613
+ )
1614
+ );
1615
+ }
1616
+ // -- actions --------------------------------------------------------------
1617
+ openCatalog() {
1618
+ this.view = "catalog";
1619
+ this.rows = catalogRows();
1620
+ this.filtered = this.rows;
1621
+ this.catalogIndex = 0;
1622
+ this.search.setValue("");
1623
+ this.search.focused = this._focused;
1624
+ this.field.focused = false;
1625
+ this.status = "";
1626
+ this.refresh();
1627
+ }
1628
+ applySearch() {
1629
+ const q = this.search.getValue();
1630
+ this.filtered = q ? fuzzyFilter(this.rows, q, (r) => `${r.label} ${r.blurb}`) : this.rows;
1631
+ this.catalogIndex = 0;
1632
+ this.refresh();
1633
+ }
1634
+ chooseCatalogRow() {
1635
+ const row = this.filtered[this.catalogIndex];
1636
+ if (!row) return;
1637
+ const pending = row.entry ? pendingFromCatalog(row.entry) : {
1638
+ title: "Custom connector",
1639
+ steps: customSteps(),
1640
+ origin: "catalog",
1641
+ build: buildCustomDraft
1642
+ };
1643
+ this.startForm(pending);
1644
+ }
1645
+ startForm(pending) {
1646
+ this.pending = pending;
1647
+ this.stepIndex = 0;
1648
+ this.answers = {};
1649
+ if (pending.steps.length === 0) {
1650
+ this.save();
1651
+ return;
1652
+ }
1653
+ this.view = "form";
1654
+ this.loadStep();
1655
+ }
1656
+ loadStep() {
1657
+ const step = this.currentStep();
1658
+ this.secret.value = "";
1659
+ this.field.setValue(step.initial ?? "");
1660
+ this.field.focused = this._focused && !step.secret;
1661
+ this.search.focused = false;
1662
+ this.refresh();
1663
+ }
1664
+ submitStep() {
1665
+ const step = this.currentStep();
1666
+ const value = step.secret ? this.secret.value : this.field.getValue().trim();
1667
+ if (!value && !step.optional) {
1668
+ this.status = "That one's required.";
1669
+ this.refresh();
1670
+ return;
1671
+ }
1672
+ const bad = value && step.validate ? step.validate(value) : void 0;
1673
+ if (bad) {
1674
+ this.status = bad;
1675
+ this.refresh();
1676
+ return;
1677
+ }
1678
+ this.answers[step.key] = value;
1679
+ this.status = "";
1680
+ const next = this.seekStep(this.stepIndex + 1, 1);
1681
+ if (next !== void 0) {
1682
+ this.stepIndex = next;
1683
+ this.loadStep();
1684
+ return;
1685
+ }
1686
+ this.save();
1687
+ }
1688
+ save() {
1689
+ const p = this.pending;
1690
+ let res;
1691
+ try {
1692
+ res = this.control.save(p.build(this.answers));
1693
+ } catch (e) {
1694
+ res = { ok: false, message: e.message };
1695
+ }
1696
+ if (!res.ok) {
1697
+ this.status = res.message ?? "Couldn't save that connector.";
1698
+ if (this.view === "form") this.refresh();
1699
+ else {
1700
+ this.view = "list";
1701
+ this.reload();
1702
+ }
1703
+ return;
1704
+ }
1705
+ this.changed = true;
1706
+ this.status = p.note ? `${res.message} ${p.note}` : res.message ?? "Saved.";
1707
+ this.pending = void 0;
1708
+ this.view = "list";
1709
+ this.reload();
1710
+ }
1711
+ toggleSelected() {
1712
+ const s = this.servers[this.index];
1713
+ if (!s) return;
1714
+ const res = this.control.setEnabled(s.name, !s.enabled);
1715
+ if (res.ok) this.changed = true;
1716
+ this.status = res.message ?? "";
1717
+ this.reload();
1718
+ }
1719
+ // Removal is the one irreversible action here — a deleted connector takes its
1720
+ // credential with it, and there is no undo. So `d` ARMS and a second `d` confirms,
1721
+ // rather than a single keystroke next to the navigation keys wiping a token you'd
1722
+ // have to go re-mint. Any other key (including moving the cursor) disarms.
1723
+ removeSelected() {
1724
+ const s = this.servers[this.index];
1725
+ if (!s) return;
1726
+ if (this.armedRemoval !== s.name) {
1727
+ this.armedRemoval = s.name;
1728
+ this.status = `Press d again to remove "${s.name}"${s.secretsSet.length > 0 ? " and its stored credentials" : ""}.`;
1729
+ this.refresh();
1730
+ return;
1731
+ }
1732
+ this.armedRemoval = void 0;
1733
+ const res = this.control.remove(s.name);
1734
+ if (res.ok) this.changed = true;
1735
+ this.status = res.message ?? "";
1736
+ this.reload();
1737
+ }
1738
+ editSelected() {
1739
+ const s = this.servers[this.index];
1740
+ if (!s) return;
1741
+ this.startForm({
1742
+ title: `Edit ${s.name}`,
1743
+ steps: customSteps(s),
1744
+ origin: "list",
1745
+ build: buildCustomDraft
1746
+ });
1747
+ }
1748
+ // -- input ----------------------------------------------------------------
1749
+ handleInput(data) {
1750
+ if (this.view === "list") return this.handleListInput(data);
1751
+ if (this.view === "catalog") return this.handleCatalogInput(data);
1752
+ return this.handleFormInput(data);
1753
+ }
1754
+ handleListInput(data) {
1755
+ const kb = getKeybindings();
1756
+ const last = this.servers.length;
1757
+ if (data !== "d" && data !== "x" && this.armedRemoval) {
1758
+ this.armedRemoval = void 0;
1759
+ this.status = "";
1760
+ }
1761
+ if (kb.matches(data, "tui.select.up")) {
1762
+ this.index = this.index === 0 ? last : this.index - 1;
1763
+ this.refresh();
1764
+ } else if (kb.matches(data, "tui.select.down")) {
1765
+ this.index = this.index === last ? 0 : this.index + 1;
1766
+ this.refresh();
1767
+ } else if (kb.matches(data, "tui.select.confirm")) {
1768
+ if (this.index === last) this.openCatalog();
1769
+ else this.editSelected();
1770
+ } else if (kb.matches(data, "tui.select.cancel")) {
1771
+ this.close({
1772
+ changed: this.changed,
1773
+ message: this.changed ? this.status || "MCP connectors updated." : void 0
1774
+ });
1775
+ } else if (data === " ") {
1776
+ if (this.index !== last) this.toggleSelected();
1777
+ } else if (data === "a" || data === "A") {
1778
+ this.openCatalog();
1779
+ } else if (data === "d" || data === "x") {
1780
+ if (this.index !== last) this.removeSelected();
1781
+ }
1782
+ }
1783
+ handleCatalogInput(data) {
1784
+ const kb = getKeybindings();
1785
+ if (kb.matches(data, "tui.select.up")) {
1786
+ if (!this.filtered.length) return;
1787
+ this.catalogIndex = this.catalogIndex === 0 ? this.filtered.length - 1 : this.catalogIndex - 1;
1788
+ this.refresh();
1789
+ } else if (kb.matches(data, "tui.select.down")) {
1790
+ if (!this.filtered.length) return;
1791
+ this.catalogIndex = this.catalogIndex === this.filtered.length - 1 ? 0 : this.catalogIndex + 1;
1792
+ this.refresh();
1793
+ } else if (kb.matches(data, "tui.select.confirm")) {
1794
+ this.chooseCatalogRow();
1795
+ } else if (kb.matches(data, "tui.select.cancel")) {
1796
+ this.view = "list";
1797
+ this.search.focused = false;
1798
+ this.status = "";
1799
+ this.refresh();
1800
+ } else {
1801
+ this.search.handleInput(data);
1802
+ this.applySearch();
1803
+ }
1804
+ }
1805
+ handleFormInput(data) {
1806
+ const kb = getKeybindings();
1807
+ const step = this.currentStep();
1808
+ if (kb.matches(data, "tui.select.cancel")) {
1809
+ const prev = this.stepIndex > 0 ? this.seekStep(this.stepIndex - 1, -1) : void 0;
1810
+ if (prev !== void 0) {
1811
+ this.stepIndex = prev;
1812
+ this.loadStep();
1813
+ } else {
1814
+ this.view = this.pending?.origin ?? "list";
1815
+ this.pending = void 0;
1816
+ this.status = "";
1817
+ this.field.focused = false;
1818
+ this.search.focused = this._focused && this.view === "catalog";
1819
+ this.refresh();
1820
+ }
1821
+ return;
1822
+ }
1823
+ if (kb.matches(data, "tui.select.confirm")) {
1824
+ this.submitStep();
1825
+ return;
1826
+ }
1827
+ if (step.secret) {
1828
+ this.secret.handleInput(data);
1829
+ this.refresh();
1830
+ return;
1831
+ }
1832
+ this.field.handleInput(data);
1833
+ this.refresh();
1834
+ }
1835
+ };
1836
+ function privateerConnect(pi) {
1837
+ if (typeof pi.registerCommand !== "function") return;
1838
+ migrateMcpProjection();
1839
+ pi.registerCommand("connect", {
1840
+ description: "Add, enable, or remove MCP connectors (GitHub, Notion, Linear, \u2026)",
1841
+ handler: /* @__PURE__ */ __name(async (_args, ctx) => {
1842
+ const ui = ctx?.ui;
1843
+ if (!ui?.custom) {
1844
+ ui?.notify?.("/connect needs an interactive terminal.", "warning");
1845
+ return;
1846
+ }
1847
+ const result = await ui.custom(
1848
+ (tui, theme, _kb, close) => new ConnectPanel({ tui, theme, close: /* @__PURE__ */ __name((r) => close(r), "close") })
1849
+ );
1850
+ if (!result?.changed) return;
1851
+ try {
1852
+ await ctx.reload?.();
1853
+ } catch {
1854
+ }
1855
+ ui.notify?.(`${result.message ?? "MCP connectors updated."} Check it with /mcp.`, "info");
1856
+ }, "handler")
1857
+ });
1858
+ }
1859
+ __name(privateerConnect, "privateerConnect");
1860
+ export {
1861
+ privateerConnect as default
1862
+ };