@rubytech/create-maxy-code 0.1.593 → 0.1.594

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 (256) hide show
  1. package/package.json +2 -2
  2. package/payload/platform/package.json +1 -0
  3. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +5 -3
  4. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +5 -0
  5. package/payload/platform/plugins/cloudflare/PLUGIN.md +2 -0
  6. package/payload/platform/plugins/docs/references/internals.md +1 -1
  7. package/payload/platform/plugins/docs/references/telegram-guide.md +3 -1
  8. package/payload/platform/scripts/__tests__/agent-identity-reconcile.test.sh +110 -0
  9. package/payload/platform/scripts/__tests__/check-orphan-dist.test.sh +87 -0
  10. package/payload/platform/scripts/check-orphan-dist.mjs +143 -0
  11. package/payload/platform/scripts/lib/agent-identity-reconcile.sh +122 -0
  12. package/payload/platform/scripts/lib/provision-account-dir.sh +8 -26
  13. package/payload/platform/scripts/setup-account.sh +15 -0
  14. package/payload/platform/services/claude-session-manager/dist/index.js +5 -0
  15. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  16. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts +5 -0
  17. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.d.ts.map +1 -1
  18. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js +42 -1
  19. package/payload/platform/services/claude-session-manager/dist/public-agent-reachability.js.map +1 -1
  20. package/payload/platform/templates/agents/public/IDENTITY.md +3 -3
  21. package/payload/platform/lib/anthropic-key/dist/index.d.ts +0 -22
  22. package/payload/platform/lib/anthropic-key/dist/index.d.ts.map +0 -1
  23. package/payload/platform/lib/anthropic-key/dist/index.js +0 -232
  24. package/payload/platform/lib/anthropic-key/dist/index.js.map +0 -1
  25. package/payload/platform/lib/graph-search/dist/query-expansion.d.ts +0 -37
  26. package/payload/platform/lib/graph-search/dist/query-expansion.d.ts.map +0 -1
  27. package/payload/platform/lib/graph-search/dist/query-expansion.js +0 -101
  28. package/payload/platform/lib/graph-search/dist/query-expansion.js.map +0 -1
  29. package/payload/platform/lib/oauth-llm/dist/index.d.ts +0 -116
  30. package/payload/platform/lib/oauth-llm/dist/index.d.ts.map +0 -1
  31. package/payload/platform/lib/oauth-llm/dist/index.js +0 -386
  32. package/payload/platform/lib/oauth-llm/dist/index.js.map +0 -1
  33. package/payload/platform/lib/screening-patterns/dist/index.d.ts +0 -29
  34. package/payload/platform/lib/screening-patterns/dist/index.d.ts.map +0 -1
  35. package/payload/platform/lib/screening-patterns/dist/index.js +0 -48
  36. package/payload/platform/lib/screening-patterns/dist/index.js.map +0 -1
  37. package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.d.ts +0 -2
  38. package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.d.ts.map +0 -1
  39. package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.js +0 -34
  40. package/payload/platform/plugins/admin/mcp/dist/__tests__/admin-identity-authenticate.test.js.map +0 -1
  41. package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts +0 -39
  42. package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts.map +0 -1
  43. package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js +0 -249
  44. package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js.map +0 -1
  45. package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.d.ts +0 -6
  46. package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.d.ts.map +0 -1
  47. package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.js +0 -32
  48. package/payload/platform/plugins/admin/mcp/dist/tools/admin-identity-authenticate.js.map +0 -1
  49. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +0 -283
  50. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +0 -1
  51. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +0 -1155
  52. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +0 -1
  53. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts +0 -90
  54. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +0 -1
  55. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +0 -551
  56. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +0 -1
  57. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts +0 -17
  58. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts.map +0 -1
  59. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js +0 -186
  60. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js.map +0 -1
  61. package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts +0 -29
  62. package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts.map +0 -1
  63. package/payload/platform/plugins/email/mcp/dist/lib/screening.js +0 -105
  64. package/payload/platform/plugins/email/mcp/dist/lib/screening.js.map +0 -1
  65. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts +0 -38
  66. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts.map +0 -1
  67. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +0 -817
  68. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +0 -1
  69. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts +0 -30
  70. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts.map +0 -1
  71. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +0 -215
  72. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +0 -1
  73. package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts +0 -19
  74. package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts.map +0 -1
  75. package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js +0 -151
  76. package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js.map +0 -1
  77. package/payload/platform/plugins/email/mcp/dist/tools/email-classify.d.ts +0 -6
  78. package/payload/platform/plugins/email/mcp/dist/tools/email-classify.d.ts.map +0 -1
  79. package/payload/platform/plugins/email/mcp/dist/tools/email-classify.js +0 -89
  80. package/payload/platform/plugins/email/mcp/dist/tools/email-classify.js.map +0 -1
  81. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.d.ts +0 -2
  82. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.d.ts.map +0 -1
  83. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.js +0 -90
  84. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-rewriter-operator-hint.test.js.map +0 -1
  85. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts +0 -2
  86. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts.map +0 -1
  87. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +0 -226
  88. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +0 -1
  89. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +0 -60
  90. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +0 -1
  91. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +0 -169
  92. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +0 -1
  93. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +0 -248
  94. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +0 -1
  95. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +0 -824
  96. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +0 -1
  97. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts +0 -63
  98. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +0 -1
  99. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +0 -210
  100. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +0 -1
  101. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +0 -35
  102. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +0 -1
  103. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js +0 -28
  104. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js.map +0 -1
  105. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.d.ts +0 -2
  106. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.d.ts.map +0 -1
  107. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.js +0 -68
  108. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights-emit.test.js.map +0 -1
  109. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.d.ts +0 -2
  110. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.d.ts.map +0 -1
  111. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.js +0 -88
  112. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write-emit.test.js.map +0 -1
  113. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.d.ts +0 -2
  114. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.d.ts.map +0 -1
  115. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.js +0 -58
  116. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-classify-emit.test.js.map +0 -1
  117. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.d.ts +0 -2
  118. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.d.ts.map +0 -1
  119. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.js +0 -64
  120. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.js.map +0 -1
  121. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.d.ts +0 -2
  122. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.d.ts.map +0 -1
  123. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.js +0 -70
  124. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edit-attachment-emit.test.js.map +0 -1
  125. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.d.ts +0 -2
  126. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.d.ts.map +0 -1
  127. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.js +0 -48
  128. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-emit.test.js.map +0 -1
  129. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.d.ts +0 -2
  130. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.d.ts.map +0 -1
  131. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.js +0 -74
  132. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-emit.test.js.map +0 -1
  133. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.d.ts +0 -2
  134. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.d.ts.map +0 -1
  135. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.js +0 -47
  136. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-extract-emit.test.js.map +0 -1
  137. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.d.ts +0 -2
  138. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.d.ts.map +0 -1
  139. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.js +0 -65
  140. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-web-emit.test.js.map +0 -1
  141. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.d.ts +0 -2
  142. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.d.ts.map +0 -1
  143. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.js +0 -43
  144. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-reindex-emit.test.js.map +0 -1
  145. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.d.ts +0 -2
  146. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.d.ts.map +0 -1
  147. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.js +0 -65
  148. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-rename-attachment-emit.test.js.map +0 -1
  149. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.d.ts +0 -2
  150. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.d.ts.map +0 -1
  151. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.js +0 -49
  152. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-restore-emit.test.js.map +0 -1
  153. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.d.ts +0 -2
  154. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.d.ts.map +0 -1
  155. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +0 -79
  156. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +0 -1
  157. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.d.ts +0 -2
  158. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.d.ts.map +0 -1
  159. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.js +0 -329
  160. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-operator-hint.test.js.map +0 -1
  161. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.d.ts +0 -2
  162. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.d.ts.map +0 -1
  163. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +0 -81
  164. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +0 -1
  165. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts +0 -34
  166. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts.map +0 -1
  167. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js +0 -62
  168. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js.map +0 -1
  169. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts +0 -61
  170. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts.map +0 -1
  171. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js +0 -102
  172. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js.map +0 -1
  173. package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.d.ts +0 -35
  174. package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.d.ts.map +0 -1
  175. package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.js +0 -73
  176. package/payload/platform/plugins/memory/mcp/dist/tools/memory-signals-recent.js.map +0 -1
  177. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +0 -29
  178. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts.map +0 -1
  179. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +0 -22
  180. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js.map +0 -1
  181. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts +0 -30
  182. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts.map +0 -1
  183. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js +0 -130
  184. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js.map +0 -1
  185. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts +0 -65
  186. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +0 -1
  187. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +0 -262
  188. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +0 -1
  189. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.d.ts +0 -2
  190. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.d.ts.map +0 -1
  191. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.js +0 -100
  192. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/normalise-start-date.test.js.map +0 -1
  193. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.d.ts +0 -2
  194. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.d.ts.map +0 -1
  195. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.js +0 -22
  196. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.js.map +0 -1
  197. package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.d.ts +0 -11
  198. package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.d.ts.map +0 -1
  199. package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.js +0 -19
  200. package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.js.map +0 -1
  201. package/payload/platform/plugins/telegram/mcp/dist/__tests__/invoice-tool.test.d.ts +0 -2
  202. package/payload/platform/plugins/telegram/mcp/dist/__tests__/invoice-tool.test.d.ts.map +0 -1
  203. package/payload/platform/plugins/telegram/mcp/dist/__tests__/invoice-tool.test.js +0 -196
  204. package/payload/platform/plugins/telegram/mcp/dist/__tests__/invoice-tool.test.js.map +0 -1
  205. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.d.ts +0 -70
  206. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.d.ts.map +0 -1
  207. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.js +0 -247
  208. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.js.map +0 -1
  209. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +0 -21
  210. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +0 -1
  211. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +0 -40
  212. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +0 -1
  213. package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.d.ts +0 -8
  214. package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.d.ts.map +0 -1
  215. package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.js +0 -83
  216. package/payload/platform/plugins/url-get/mcp/dist/lib/summarise.js.map +0 -1
  217. package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts +0 -151
  218. package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts.map +0 -1
  219. package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js +0 -299
  220. package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js.map +0 -1
  221. package/payload/platform/services/claude-session-manager/dist/account-admin-census.d.ts +0 -39
  222. package/payload/platform/services/claude-session-manager/dist/account-admin-census.d.ts.map +0 -1
  223. package/payload/platform/services/claude-session-manager/dist/account-admin-census.js +0 -122
  224. package/payload/platform/services/claude-session-manager/dist/account-admin-census.js.map +0 -1
  225. package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.d.ts +0 -39
  226. package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.d.ts.map +0 -1
  227. package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.js +0 -133
  228. package/payload/platform/services/claude-session-manager/dist/admin-identity-audit.js.map +0 -1
  229. package/payload/platform/services/claude-session-manager/dist/input-postcondition.d.ts +0 -22
  230. package/payload/platform/services/claude-session-manager/dist/input-postcondition.d.ts.map +0 -1
  231. package/payload/platform/services/claude-session-manager/dist/input-postcondition.js +0 -50
  232. package/payload/platform/services/claude-session-manager/dist/input-postcondition.js.map +0 -1
  233. package/payload/platform/services/claude-session-manager/dist/jsonl-observer.d.ts +0 -30
  234. package/payload/platform/services/claude-session-manager/dist/jsonl-observer.d.ts.map +0 -1
  235. package/payload/platform/services/claude-session-manager/dist/jsonl-observer.js +0 -204
  236. package/payload/platform/services/claude-session-manager/dist/jsonl-observer.js.map +0 -1
  237. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts +0 -80
  238. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts.map +0 -1
  239. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js +0 -306
  240. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js.map +0 -1
  241. package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +0 -49
  242. package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +0 -1
  243. package/payload/platform/services/claude-session-manager/dist/session-store.js +0 -52
  244. package/payload/platform/services/claude-session-manager/dist/session-store.js.map +0 -1
  245. package/payload/platform/services/claude-session-manager/dist/systemd-spawn.d.ts +0 -83
  246. package/payload/platform/services/claude-session-manager/dist/systemd-spawn.d.ts.map +0 -1
  247. package/payload/platform/services/claude-session-manager/dist/systemd-spawn.js +0 -345
  248. package/payload/platform/services/claude-session-manager/dist/systemd-spawn.js.map +0 -1
  249. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +0 -104
  250. package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +0 -1
  251. package/payload/platform/services/telegram-channel/dist/turn-follow.js +0 -408
  252. package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +0 -1
  253. package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts +0 -104
  254. package/payload/platform/services/whatsapp-channel/dist/turn-follow.d.ts.map +0 -1
  255. package/payload/platform/services/whatsapp-channel/dist/turn-follow.js +0 -393
  256. package/payload/platform/services/whatsapp-channel/dist/turn-follow.js.map +0 -1
@@ -1,1155 +0,0 @@
1
- import { execFileSync, spawn } from "node:child_process";
2
- import { existsSync, readFileSync, writeFileSync, mkdirSync, openSync, unlinkSync, copyFileSync, statSync, } from "node:fs";
3
- import { join } from "node:path";
4
- import { homedir } from "node:os";
5
- import { Resolver } from "node:dns/promises";
6
- let cachedBrand = null;
7
- export function loadBrand() {
8
- if (cachedBrand)
9
- return cachedBrand;
10
- const platformRoot = process.env.PLATFORM_ROOT;
11
- if (!platformRoot) {
12
- throw new Error("PLATFORM_ROOT env var is not set — cannot determine brand configuration");
13
- }
14
- const brandPath = join(platformRoot, "config", "brand.json");
15
- if (!existsSync(brandPath)) {
16
- throw new Error(`brand.json not found at ${brandPath} — PLATFORM_ROOT may be incorrect`);
17
- }
18
- const parsed = JSON.parse(readFileSync(brandPath, "utf-8"));
19
- cachedBrand = {
20
- configDir: parsed.configDir ?? ".maxy",
21
- productName: parsed.productName ?? "Maxy",
22
- vncDisplay: typeof parsed.vncDisplay === "number" ? parsed.vncDisplay : 99,
23
- };
24
- return cachedBrand;
25
- }
26
- /**
27
- * Test-only: reset the cached brand so a subsequent loadBrand() re-reads
28
- * the manifest. Production code never calls this.
29
- */
30
- export function _resetBrandCache() {
31
- cachedBrand = null;
32
- }
33
- // ---------------------------------------------------------------------------
34
- // Binary detection
35
- // ---------------------------------------------------------------------------
36
- let cachedBinaryPath = null;
37
- const SEARCH_PATHS = [
38
- "/usr/local/bin/cloudflared",
39
- "/usr/bin/cloudflared",
40
- join(homedir(), ".local/bin/cloudflared"),
41
- ];
42
- export function findBinary() {
43
- if (cachedBinaryPath)
44
- return cachedBinaryPath;
45
- try {
46
- cachedBinaryPath = execFileSync("which", ["cloudflared"], {
47
- encoding: "utf-8",
48
- timeout: 5000,
49
- }).trim();
50
- return cachedBinaryPath;
51
- }
52
- catch {
53
- // not on PATH
54
- }
55
- for (const p of SEARCH_PATHS) {
56
- if (existsSync(p)) {
57
- cachedBinaryPath = p;
58
- return p;
59
- }
60
- }
61
- return null;
62
- }
63
- export function isInstalled() {
64
- return findBinary() !== null;
65
- }
66
- export function version() {
67
- const bin = findBinary();
68
- if (!bin)
69
- return null;
70
- try {
71
- const out = execFileSync(bin, ["--version"], {
72
- encoding: "utf-8",
73
- timeout: 5000,
74
- });
75
- const match = out.match(/cloudflared version (\S+)/);
76
- return match?.[1] ?? out.trim();
77
- }
78
- catch {
79
- return null;
80
- }
81
- }
82
- function bindingDir() {
83
- return join(homedir(), loadBrand().configDir, "cloudflare");
84
- }
85
- function bindingFile() {
86
- return join(bindingDir(), "account-binding.json");
87
- }
88
- export function readAccountBinding() {
89
- try {
90
- const path = bindingFile();
91
- if (!existsSync(path))
92
- return null;
93
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
94
- if (typeof parsed?.accountId !== "string" || typeof parsed?.boundAt !== "string") {
95
- console.error(`[cloudflare:binding] malformed account-binding.json at ${path} — treating as absent`);
96
- return null;
97
- }
98
- return { accountId: parsed.accountId, boundAt: parsed.boundAt };
99
- }
100
- catch (err) {
101
- console.error(`[cloudflare:binding] failed to read ${bindingFile()}: ${err}`);
102
- return null;
103
- }
104
- }
105
- /**
106
- * @internal — production code MUST go through `materializeBinding` so the
107
- * source-of-truth lifecycle (fresh-login vs migration) is preserved and
108
- * logged. Exported only so unit tests can pre-seed bindings without
109
- * re-implementing the file format.
110
- */
111
- export function writeAccountBinding(accountId) {
112
- const dir = bindingDir();
113
- mkdirSync(dir, { recursive: true });
114
- const binding = { accountId, boundAt: new Date().toISOString() };
115
- writeFileSync(bindingFile(), JSON.stringify(binding, null, 2), { mode: 0o600 });
116
- return binding;
117
- }
118
- function resetAccountBinding() {
119
- const path = bindingFile();
120
- try {
121
- unlinkSync(path);
122
- console.error(`[cloudflare:binding] reset ${path}`);
123
- return true;
124
- }
125
- catch (err) {
126
- const code = err.code;
127
- if (code === "ENOENT")
128
- return false;
129
- console.error(`[cloudflare:binding] failed to reset ${path}: ${err}`);
130
- return false;
131
- }
132
- }
133
- export function logRefuse(detail) {
134
- const brand = (() => {
135
- try {
136
- return loadBrand();
137
- }
138
- catch {
139
- return null;
140
- }
141
- })();
142
- const f = detail.fields ?? {};
143
- const fields = {
144
- reason: detail.reason,
145
- brand: brand?.productName ?? "unknown",
146
- boundAccountId: f.boundAccountId ?? null,
147
- certAccountId: f.certAccountId ?? null,
148
- requestedDomain: f.requestedDomain ?? null,
149
- requestedHostname: f.requestedHostname ?? null,
150
- actualFqdn: f.actualFqdn ?? null,
151
- tunnelId: f.tunnelId ?? null,
152
- };
153
- console.error(`[cloudflare:refuse] ${JSON.stringify(fields)}`);
154
- }
155
- /**
156
- * The only recovery instruction — every refusal that involves the wrong
157
- * Cloudflare account points here. Single source of truth so the four
158
- * surfaces that quote it (tunnel-status, tunnel-add-hostname, personal
159
- * assistant template, setup-tunnel skill) cannot drift.
160
- */
161
- export const DASHBOARD_SWITCH_ACCOUNTS_INSTRUCTION = "Open Cloudflare in your browser. Look at the name in the top-left — " +
162
- "that is the Cloudflare account this laptop will talk to. If it is not " +
163
- "the account that owns the domain you want to use, switch accounts using " +
164
- "the top-left dropdown. Once the correct account is showing, tell me and " +
165
- "I will start the sign-in again.";
166
- export function recoveryMessage() {
167
- return ("Recovery: confirm you are signed into the correct Cloudflare account " +
168
- "in your browser, then ask me to run tunnel-login. If you recently switched " +
169
- "accounts, ask me to run tunnel-login with force=true so the current sign-in " +
170
- "is cleared before the new one starts.");
171
- }
172
- // ---------------------------------------------------------------------------
173
- // Reset auth — unlinks cert.pem (both paths) and the account binding
174
- //
175
- // Kill order matters: terminate any in-flight `cloudflared tunnel login`
176
- // process BEFORE deleting cert state, otherwise its browser handshake can
177
- // complete between our unlink and return, writing a fresh cert.pem to the
178
- // legacy path that findCert() would silently re-import on the next read.
179
- // The kill-then-unlink ordering preserves the same race-close pattern.
180
- // ---------------------------------------------------------------------------
181
- export function resetAuth() {
182
- const result = { deletedCert: false, deletedBinding: false };
183
- const loginState = readLoginState();
184
- if (loginState?.pid && isProcessAlive(loginState.pid)) {
185
- try {
186
- process.kill(loginState.pid, "SIGTERM");
187
- console.error(`[tunnel-login] killed stale login process PID ${loginState.pid}`);
188
- }
189
- catch {
190
- // Process may have exited between check and kill
191
- }
192
- }
193
- clearLoginState();
194
- const cp = certPath();
195
- try {
196
- unlinkSync(cp);
197
- result.deletedCert = true;
198
- console.error(`[tunnel-login] deleted: ${cp}`);
199
- }
200
- catch (err) {
201
- const code = err.code;
202
- if (code === "ENOENT") {
203
- // File already gone — no-op
204
- }
205
- else {
206
- console.error(`[tunnel-login] failed to delete cert.pem ${cp}: ${err}`);
207
- }
208
- }
209
- const legacyCp = legacyCertPath();
210
- try {
211
- unlinkSync(legacyCp);
212
- console.error(`[tunnel-login] deleted legacy cert.pem: ${legacyCp}`);
213
- }
214
- catch (err) {
215
- const code = err.code;
216
- if (code === "ENOENT") {
217
- console.error(`[tunnel-login] legacy cert.pem absent: ${legacyCp}`);
218
- }
219
- else {
220
- console.error(`[tunnel-login] failed to delete legacy cert.pem ${legacyCp}: ${err}`);
221
- }
222
- }
223
- result.deletedBinding = resetAccountBinding();
224
- return result;
225
- }
226
- // ---------------------------------------------------------------------------
227
- // cert.pem — written by `cloudflared tunnel login`
228
- //
229
- // Stored under ~/{configDir}/cloudflared/ so each brand has its own cert.
230
- // Function (not const) because loadBrand() requires PLATFORM_ROOT at runtime.
231
- // ---------------------------------------------------------------------------
232
- function certPath() {
233
- return join(homedir(), loadBrand().configDir, "cloudflared/cert.pem");
234
- }
235
- // cloudflared tunnel login always writes here regardless of --origincert.
236
- // Same literal lives in the installer's cloudflared migration block.
237
- function legacyCertPath() {
238
- return join(homedir(), ".cloudflared", "cert.pem");
239
- }
240
- /**
241
- * Find cert.pem, checking the brand-specific path first, then cloudflared's
242
- * default location. cloudflared tunnel login ALWAYS writes to
243
- * ~/.cloudflared/cert.pem regardless of --origincert, so we must check
244
- * there as a fallback.
245
- *
246
- * Move-on-read: when the cert is found at the legacy path, copy it to the
247
- * brand path AND unlink the legacy file. Without the unlink, the legacy
248
- * file would survive `tunnel-login force=true` (which only deletes brand
249
- * paths) and silently re-import on the next read — defeating the recovery
250
- * path operators rely on after switching Cloudflare accounts.
251
- */
252
- function findCert() {
253
- const brandPath = certPath();
254
- if (existsSync(brandPath))
255
- return brandPath;
256
- const defaultPath = legacyCertPath();
257
- if (existsSync(defaultPath)) {
258
- const dir = join(homedir(), loadBrand().configDir, "cloudflared");
259
- mkdirSync(dir, { recursive: true });
260
- copyFileSync(defaultPath, brandPath);
261
- try {
262
- unlinkSync(defaultPath);
263
- console.error(`[tunnel-login] cert.pem migrated from ${defaultPath} to ${brandPath}`);
264
- }
265
- catch (err) {
266
- console.error(`[tunnel-login] WARNING: cert.pem migrated to ${brandPath} but legacy ${defaultPath} could not be removed: ${err}`);
267
- }
268
- return brandPath;
269
- }
270
- return null;
271
- }
272
- export function hasCert() {
273
- return findCert() !== null;
274
- }
275
- /**
276
- * Parse the ARGO TUNNEL TOKEN PEM block from cert.pem to extract the
277
- * AccountTag. Only the account ID is returned — nothing else in cert.pem
278
- * is used by this codebase. The cert itself stays on disk; `cloudflared`
279
- * shell-outs read it via `--origincert` for tunnel CRUD and DNS routing.
280
- */
281
- export function parseCertPem() {
282
- try {
283
- const cert = findCert();
284
- if (!cert)
285
- return null;
286
- const pem = readFileSync(cert, "utf-8");
287
- const beginMarker = "-----BEGIN ARGO TUNNEL TOKEN-----";
288
- const endMarker = "-----END ARGO TUNNEL TOKEN-----";
289
- const beginIdx = pem.indexOf(beginMarker);
290
- const endIdx = pem.indexOf(endMarker);
291
- if (beginIdx === -1 || endIdx === -1)
292
- return null;
293
- const b64 = pem
294
- .slice(beginIdx + beginMarker.length, endIdx)
295
- .replace(/\s+/g, "");
296
- const json = Buffer.from(b64, "base64").toString("utf-8");
297
- const data = JSON.parse(json);
298
- const accountId = data.AccountTag ?? data.accountTag ?? data.AccountID ?? data.accountID ?? data.accountId;
299
- if (!accountId)
300
- return null;
301
- return { accountId };
302
- }
303
- catch {
304
- return null;
305
- }
306
- }
307
- /** Wraps a structured RefusalDetail so call sites can branch on `reason`. */
308
- export class CloudflareRefusalError extends Error {
309
- refusal;
310
- constructor(detail) {
311
- super(detail.message);
312
- this.name = "CloudflareRefusalError";
313
- this.refusal = detail;
314
- }
315
- }
316
- export function validateAuth() {
317
- const creds = parseCertPem();
318
- const binding = readAccountBinding();
319
- return {
320
- hasCert: !!creds,
321
- hasBinding: !!binding,
322
- bound: !!(creds && binding && creds.accountId === binding.accountId),
323
- certAccountId: creds?.accountId ?? null,
324
- boundAccountId: binding?.accountId ?? null,
325
- };
326
- }
327
- /**
328
- * Establish the device-local account binding from cert.pem. Used by:
329
- * (1) tunnel-login fresh-success path — first time creds are derived.
330
- * (2) migration path — when cert.pem exists from a prior install but no
331
- * binding has yet been written (silent materialization, since the
332
- * cert was already trust-established by the operator's prior login).
333
- */
334
- export function materializeBinding(source) {
335
- const creds = parseCertPem();
336
- if (!creds) {
337
- throw new Error("Cannot materialize binding — cert.pem absent or unparseable");
338
- }
339
- const binding = writeAccountBinding(creds.accountId);
340
- console.error(`[cloudflare:binding-materialized] source=${source} accountId=${creds.accountId} boundAt=${binding.boundAt}`);
341
- return binding;
342
- }
343
- // Alias-domains read/write lived here historically; the MCP tool that wrote
344
- // it was removed, and the writer moved to
345
- // platform/ui/app/lib/alias-domains.ts so the reader (server/index.ts
346
- // watchFile loop) and the writer share one process. The file on disk
347
- // (~/{configDir}/alias-domains.json) is still the interchange format.
348
- // ---------------------------------------------------------------------------
349
- // CLI-based tunnel operations
350
- //
351
- // `cloudflared` CLI uses cert.pem directly for tunnel CRUD and DNS routing.
352
- // Shelling out to the CLI is architecturally indistinguishable from the
353
- // operator clicking the same action in the dashboard — both use the
354
- // operator's OAuth-bound cert, nothing more.
355
- // ---------------------------------------------------------------------------
356
- // Name convention: `{product}-{device}` — e.g. "maxy-laptop". Encodes product identity and physical location in one label.
357
- export function createTunnelCli(name) {
358
- const bin = findBinary();
359
- if (!bin)
360
- throw new Error("cloudflared is not installed");
361
- const cert = findCert();
362
- if (!cert)
363
- throw new Error("No cert.pem found — run tunnel-login first");
364
- // Check for existing tunnel with this name first
365
- try {
366
- const listOutput = execFileSync(bin, ["tunnel", "--origincert", cert, "list", "-o", "json"], { encoding: "utf-8", timeout: 30000 });
367
- const tunnels = JSON.parse(listOutput);
368
- const existing = tunnels.find((t) => t.name === name && !t.deleted_at);
369
- if (existing) {
370
- console.error(`[tunnel-create] CLI: tunnel "${name}" already exists (${existing.id})`);
371
- const brandCredPath = join(homedir(), loadBrand().configDir, "cloudflared", `${existing.id}.json`);
372
- const defaultCredPath = join(homedir(), ".cloudflared", `${existing.id}.json`);
373
- if (!existsSync(brandCredPath) && existsSync(defaultCredPath)) {
374
- const dir = join(homedir(), loadBrand().configDir, "cloudflared");
375
- mkdirSync(dir, { recursive: true });
376
- copyFileSync(defaultCredPath, brandCredPath);
377
- console.error(`[tunnel-create] credentials copied to ${brandCredPath}`);
378
- }
379
- return {
380
- tunnelId: existing.id,
381
- tunnelName: name,
382
- credentialsPath: brandCredPath,
383
- };
384
- }
385
- }
386
- catch {
387
- // list failed — proceed to create
388
- }
389
- const output = execFileSync(bin, ["tunnel", "--origincert", cert, "create", name], { encoding: "utf-8", timeout: 30000 });
390
- console.error(`[tunnel-create] CLI: ${output.trim()}`);
391
- // Parse tunnel ID from output: "Created tunnel <name> with id <uuid>"
392
- const idMatch = output.match(/with id ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
393
- if (!idMatch) {
394
- throw new Error(`cloudflared tunnel create succeeded but could not parse tunnel ID from output: ${output.trim()}`);
395
- }
396
- const tunnelId = idMatch[1];
397
- // cloudflared writes credentials to ~/.cloudflared/<id>.json — copy to brand path
398
- const defaultCredPath = join(homedir(), ".cloudflared", `${tunnelId}.json`);
399
- const brandDir = join(homedir(), loadBrand().configDir, "cloudflared");
400
- mkdirSync(brandDir, { recursive: true });
401
- const brandCredPath = join(brandDir, `${tunnelId}.json`);
402
- if (existsSync(defaultCredPath)) {
403
- copyFileSync(defaultCredPath, brandCredPath);
404
- console.error(`[tunnel-create] credentials copied to ${brandCredPath}`);
405
- }
406
- return { tunnelId, tunnelName: name, credentialsPath: brandCredPath };
407
- }
408
- export function getTunnelTokenCli(tunnelId) {
409
- const bin = findBinary();
410
- if (!bin)
411
- throw new Error("cloudflared is not installed");
412
- const cert = findCert();
413
- if (!cert)
414
- throw new Error("No cert.pem found — run tunnel-login first");
415
- const output = execFileSync(bin, ["tunnel", "--origincert", cert, "token", tunnelId], { encoding: "utf-8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] });
416
- const token = output.trim();
417
- if (!token) {
418
- throw new Error("cloudflared tunnel token returned empty output");
419
- }
420
- console.error(`[tunnel-token] CLI: obtained connector token for tunnel ${tunnelId}`);
421
- return token;
422
- }
423
- /**
424
- * Write a local config.yml for cloudflared tunnel run.
425
- * This is the ONLY tunnel startup configuration — no remote config, no API.
426
- * hostnames: the actual FQDNs to route (e.g. ["joel.maxy.bot", "shop.maxy.bot"]).
427
- */
428
- export function writeLocalConfig(tunnelId, credentialsPath, hostnames, port) {
429
- const dir = join(homedir(), loadBrand().configDir, "cloudflared");
430
- mkdirSync(dir, { recursive: true });
431
- const configPath = join(dir, "config.yml");
432
- const ingressLines = hostnames.flatMap((h) => [
433
- ` - hostname: ${h}`,
434
- ` service: http://localhost:${port}`,
435
- ]);
436
- const yaml = [
437
- `tunnel: ${tunnelId}`,
438
- `credentials-file: ${credentialsPath}`,
439
- `ingress:`,
440
- ...ingressLines,
441
- ` - service: http_status:404`,
442
- "",
443
- ].join("\n");
444
- writeFileSync(configPath, yaml, "utf-8");
445
- console.error(`[tunnel-create] config written to ${configPath} hostnames=${JSON.stringify(hostnames)}`);
446
- return configPath;
447
- }
448
- // ---------------------------------------------------------------------------
449
- // Parse configured hostnames from config.yml
450
- //
451
- // `tunnel-status`'s end-to-end probe reads the hostnames from config.yml
452
- // rather than from tunnel.state — config.yml is the authoritative source
453
- // for what `cloudflared` is actually ingressing. State-file hostnames may
454
- // drift from on-disk config; the running tunnel only honours config.yml.
455
- // ---------------------------------------------------------------------------
456
- export function parseConfiguredHostnames(configYmlPath) {
457
- try {
458
- if (!existsSync(configYmlPath))
459
- return [];
460
- const yaml = readFileSync(configYmlPath, "utf-8");
461
- const hostnames = [];
462
- for (const line of yaml.split("\n")) {
463
- const m = line.match(/^\s*-\s*hostname:\s*(\S+)\s*$/);
464
- if (m)
465
- hostnames.push(m[1]);
466
- }
467
- return hostnames;
468
- }
469
- catch (err) {
470
- console.error(`[tunnel-status] failed to parse ${configYmlPath}: ${err}`);
471
- return [];
472
- }
473
- }
474
- // ---------------------------------------------------------------------------
475
- // Nameserver pre-flight — zone-parent routability without any API
476
- //
477
- // Before shelling `cloudflared tunnel route dns`, confirm the hostname's
478
- // registrable parent has its NS records pointing at Cloudflare. When they
479
- // don't, `cloudflared` silently falls through to whichever zone the cert's
480
- // account does own and creates a malformed CNAME (the historical
481
- // `admin.maxy.bot.joelsmalley.xyz` failure mode). Refusing pre-flight is
482
- // the only way to prevent that drift from inside this codebase.
483
- //
484
- // Heuristic for registrable parent: take the last two labels. This fails
485
- // for multi-label public suffixes (e.g. `.co.uk`, `.com.au`) — not worth
486
- // correctness here because Cloudflare's own sign-up flow rejects those
487
- // at zone-add time, so the NS lookup will also fail for them, giving the
488
- // same "not on Cloudflare yet" message.
489
- // ---------------------------------------------------------------------------
490
- function registrableParent(hostname) {
491
- const labels = hostname.toLowerCase().split(".");
492
- if (labels.length <= 2)
493
- return hostname.toLowerCase();
494
- return labels.slice(-2).join(".");
495
- }
496
- export async function checkZoneParentOnCloudflare(hostname) {
497
- const zoneParent = registrableParent(hostname);
498
- const resolver = new Resolver();
499
- resolver.setServers(["1.1.1.1", "8.8.8.8"]);
500
- try {
501
- const ns = await resolver.resolveNs(zoneParent);
502
- const onCloudflare = ns.some((n) => /\.ns\.cloudflare\.com\.?$/i.test(n));
503
- return { zoneParent, nameservers: ns, onCloudflare };
504
- }
505
- catch (err) {
506
- const msg = err instanceof Error ? err.message : String(err);
507
- return { zoneParent, nameservers: [], onCloudflare: false, error: msg };
508
- }
509
- }
510
- // ---------------------------------------------------------------------------
511
- // CLI-based DNS routing — shells `cloudflared tunnel route dns` with defence
512
- // in depth against the known malformed-CNAME fall-through.
513
- //
514
- // (1) Pre-flight: zone-parent NS records must point to Cloudflare.
515
- // Refusal reason `hostname-zone-not-routable` tells the operator
516
- // to add the domain in the dashboard before coming back.
517
- // (2) Post-flight: the FQDN `cloudflared` wrote must equal the requested
518
- // hostname. A mismatch means `cloudflared` routed to a zone owned by
519
- // the currently-bound account instead of the intended zone — usually
520
- // because the device signed into the wrong Cloudflare account.
521
- // Refusal reason `post-flight-fqdn-mismatch` tells the operator to
522
- // check the dashboard account name and re-login if wrong.
523
- //
524
- // Authentication pre-condition is just "cert.pem + binding + they agree" —
525
- // enforced by validateAuth().bound. No API client is constructed anywhere.
526
- // ---------------------------------------------------------------------------
527
- export async function routeDnsCli(tunnelId, hostname) {
528
- const bin = findBinary();
529
- if (!bin)
530
- throw new Error("cloudflared is not installed");
531
- const auth = validateAuth();
532
- if (!auth.bound) {
533
- const reason = auth.hasCert && auth.hasBinding
534
- ? "account-drift"
535
- : "unbound-device";
536
- const detail = {
537
- reason,
538
- message: `This laptop is not signed into Cloudflare yet${auth.hasCert ? " (sign-in has drifted since it was set up)" : ""}. ` +
539
- `${DASHBOARD_SWITCH_ACCOUNTS_INSTRUCTION}`,
540
- fields: {
541
- boundAccountId: auth.boundAccountId,
542
- certAccountId: auth.certAccountId,
543
- requestedHostname: hostname,
544
- },
545
- };
546
- logRefuse(detail);
547
- throw new CloudflareRefusalError(detail);
548
- }
549
- // (1) Pre-flight zone-parent routability.
550
- console.error(`[cloudflare:tunnel-add-hostname:preflight] hostname=${hostname}`);
551
- const preflight = await checkZoneParentOnCloudflare(hostname);
552
- if (!preflight.onCloudflare) {
553
- const detail = {
554
- reason: "hostname-zone-not-routable",
555
- message: `The domain ${preflight.zoneParent} is not on Cloudflare yet — this laptop cannot ` +
556
- `reach it. Open Cloudflare in your browser and add ${preflight.zoneParent} under the ` +
557
- `Cloudflare account you want to use (Websites → Add a site). When the domain shows as ` +
558
- `Active, tell me and I will add the address again.`,
559
- fields: {
560
- requestedHostname: hostname,
561
- requestedDomain: preflight.zoneParent,
562
- },
563
- };
564
- logRefuse(detail);
565
- throw new CloudflareRefusalError(detail);
566
- }
567
- const cert = findCert();
568
- if (!cert)
569
- throw new Error("No cert.pem found — validateAuth() should have refused first");
570
- let output;
571
- try {
572
- output = execFileSync(bin, ["tunnel", "--origincert", cert, "route", "dns", "--overwrite-dns", tunnelId, hostname], { encoding: "utf-8", timeout: 30000 });
573
- }
574
- catch (err) {
575
- const errObj = err;
576
- const rawStderr = errObj.stderr;
577
- const stderrMsg = typeof rawStderr === "string"
578
- ? rawStderr
579
- : Buffer.isBuffer(rawStderr) ? rawStderr.toString("utf-8") : "";
580
- const msg = stderrMsg || errObj.message || String(err);
581
- if (msg.includes("already exists")) {
582
- console.error(`[tunnel-route-dns] WARNING: ${hostname} "already exists" despite --overwrite-dns: ${msg}`);
583
- return { created: false, output: msg, fqdn: hostname };
584
- }
585
- throw new Error(`cloudflared tunnel route dns failed: ${msg}`);
586
- }
587
- // (2) Post-flight FQDN assertion.
588
- const fqdnMatch = output.match(/Added CNAME\s+(\S+?)\s+which will route/);
589
- if (!fqdnMatch) {
590
- console.error(`[tunnel-route-dns] WARNING: could not parse CNAME FQDN from cloudflared output — format may have changed. Output: ${output.trim()}`);
591
- return { created: true, output: output.trim(), fqdn: hostname };
592
- }
593
- const actualFqdn = fqdnMatch[1];
594
- if (actualFqdn !== hostname) {
595
- // cloudflared wrote the address under a different domain than requested.
596
- // That means the laptop is signed into a Cloudflare account that does
597
- // not own the target domain, and `cloudflared` fell through to whichever
598
- // domain the signed-in account does own. The agent cannot clean this up
599
- // (no API access by design) — the operator cleans it up in the dashboard.
600
- console.error(`[cloudflare:post-flight-mismatch] ${JSON.stringify({
601
- requestedHostname: hostname,
602
- actualFqdn,
603
- tunnelId,
604
- boundAccountId: auth.boundAccountId,
605
- })}`);
606
- const detail = {
607
- reason: "post-flight-fqdn-mismatch",
608
- message: `Cloudflare created the address under ${actualFqdn} instead of the requested ${hostname}. ` +
609
- `This laptop is signed into a Cloudflare account that does not own ${registrableParent(hostname)}. ` +
610
- `Two things to do: (a) open Cloudflare in your browser, sign into the account that does own ` +
611
- `${registrableParent(hostname)}, and tell me so I can re-login; (b) in the same browser, delete the ` +
612
- `stray record at ${actualFqdn} from the other account so it does not confuse anything.`,
613
- fields: {
614
- requestedHostname: hostname,
615
- actualFqdn,
616
- tunnelId,
617
- boundAccountId: auth.boundAccountId,
618
- },
619
- };
620
- logRefuse(detail);
621
- throw new CloudflareRefusalError(detail);
622
- }
623
- console.error(`[tunnel-route-dns] CLI: routed ${hostname} → tunnel ${tunnelId} (overwrite) fqdn=${actualFqdn}`);
624
- return { created: true, output: output.trim(), fqdn: actualFqdn };
625
- }
626
- // ---------------------------------------------------------------------------
627
- // tunnel login — spawn `cloudflared tunnel login` and capture the auth URL
628
- // ---------------------------------------------------------------------------
629
- function loginStatePath() {
630
- return join(homedir(), loadBrand().configDir, "cloudflared/login.state");
631
- }
632
- function readLoginState() {
633
- try {
634
- const p = loginStatePath();
635
- if (!existsSync(p))
636
- return null;
637
- return JSON.parse(readFileSync(p, "utf-8"));
638
- }
639
- catch {
640
- return null;
641
- }
642
- }
643
- function writeLoginState(state) {
644
- const dir = join(homedir(), loadBrand().configDir, "cloudflared");
645
- mkdirSync(dir, { recursive: true });
646
- writeFileSync(loginStatePath(), JSON.stringify(state), "utf-8");
647
- }
648
- function clearLoginState() {
649
- try {
650
- const p = loginStatePath();
651
- if (existsSync(p)) {
652
- writeFileSync(p, "", "utf-8");
653
- }
654
- }
655
- catch {
656
- // best-effort
657
- }
658
- }
659
- /**
660
- * Fingerprint emitted by `cloudflared tunnel login` into its log when its
661
- * browser-launch subcommand cannot reach a display. A plain substring — the
662
- * cloudflared log format is stable and regex gymnastics would hide the
663
- * failure mode, not detect it.
664
- *
665
- * Critical semantic distinction: this marker is a *courtesy*
666
- * warning — cloudflared attempted `xdg-open` against the argotunnel auth
667
- * URL, failed, wrote the marker, and then CONTINUED RUNNING. Its local
668
- * OAuth-callback listener is still alive; a user who opens the URL in any
669
- * reachable browser can still complete sign-in and cert.pem will still
670
- * land. Treating the marker as terminal loops the restart path
671
- * hermetically because the respawn will re-emit the same marker the first
672
- * time it tries to open a browser. `getLoginStatus` therefore only treats
673
- * the marker as *advisory* when the PID is alive — liveness + cert are
674
- * the source of truth for terminal-vs-live, not log content.
675
- */
676
- const BROWSER_LAUNCH_FAILED_MARKER = "Failed to fetch resource";
677
- function parseLoginLogForBrowserLaunchFailure(logPath, logOffsetBytes) {
678
- try {
679
- if (!existsSync(logPath))
680
- return false;
681
- // Read as Buffer so the byte offset is honoured exactly — logs are
682
- // mostly ASCII but using a byte-level slice protects against any
683
- // future multi-byte content producing a character-vs-byte drift.
684
- const buf = readFileSync(logPath);
685
- const sliced = buf.length > logOffsetBytes ? buf.subarray(logOffsetBytes) : Buffer.alloc(0);
686
- const content = sliced.toString("utf-8");
687
- return content.includes(BROWSER_LAUNCH_FAILED_MARKER);
688
- }
689
- catch (err) {
690
- console.error(`[cloudflare:tunnel-login:log-parse-error] path=${logPath} err=${err instanceof Error ? err.message : String(err)}`);
691
- return false;
692
- }
693
- }
694
- export function getLoginStatus() {
695
- const loginState = readLoginState();
696
- if (!loginState || !loginState.pid) {
697
- return { state: "idle" };
698
- }
699
- const alive = isProcessAlive(loginState.pid);
700
- const browserLaunchFailed = parseLoginLogForBrowserLaunchFailure(loginState.logPath, loginState.logOffset ?? 0);
701
- if (alive) {
702
- // The OAuth-callback listener is still running. The marker is advisory
703
- // only — the login flow can still complete if a human opens the URL.
704
- return { state: "in-progress", loginState, browserLaunchFailed };
705
- }
706
- // PID is dead. Cert presence decides terminal state vs TOCTOU-idle.
707
- if (findCert() !== null) {
708
- // Upstream (tunnel-login handler) checks cert before calling this, so
709
- // this branch is only reachable on a TOCTOU race where cert appeared
710
- // between the top-level check and here. Treat as idle so the handler
711
- // re-examines from the top on the next call.
712
- return { state: "idle" };
713
- }
714
- // Terminal failure. Reason encodes whether the courtesy marker was
715
- // emitted during the process's lifetime — useful for post-mortem grep
716
- // ("did cloudflared try and fail to open the browser before dying?").
717
- return {
718
- state: "failed",
719
- loginState,
720
- reason: browserLaunchFailed
721
- ? "login-process-exited-with-marker"
722
- : "login-process-exited-without-cert",
723
- };
724
- }
725
- /**
726
- * Resolve the spawn env for `cloudflared tunnel login`. `cloudflared`'s
727
- * first action after printing the auth URL is `xdg-open` against that URL
728
- * so the operator's browser jumps straight to the Authorize screen. The
729
- * MCP server's spawn context does not inherit DISPLAY (the chain platform
730
- * → claude-agent → MCP server drops it along the way), so `xdg-open` has
731
- * no X display to reach and the courtesy-fetch-resource marker lands in
732
- * the login log every time.
733
- *
734
- * When this brand's Xtigervnc Unix socket is live we plumb DISPLAY
735
- * explicitly so the browser launch has a chance of succeeding. The display
736
- * number is per-brand: Maxy=:99, Real Agent=:100. Xtigervnc
737
- * runs `-SecurityTypes None` locally, so XAUTHORITY is not required to
738
- * connect. When this brand's VNC socket is absent (headless /
739
- * not-yet-booted / native-mode device) we return null so the caller
740
- * spawns with the default inherited env, and the new classification
741
- * (marker alone is non-terminal) keeps the session alive via the
742
- * advisory path.
743
- */
744
- function vncSpawnEnv() {
745
- const display = `:${loadBrand().vncDisplay}`;
746
- const socket = `/tmp/.X11-unix/X${display.slice(1)}`;
747
- if (!existsSync(socket))
748
- return null;
749
- return {
750
- env: { ...process.env, DISPLAY: display },
751
- display,
752
- };
753
- }
754
- export function tunnelLogin() {
755
- const bin = findBinary();
756
- if (!bin)
757
- throw new Error("cloudflared is not installed");
758
- const status = getLoginStatus();
759
- if (status.state === "in-progress") {
760
- console.error(`[cloudflare:tunnel-login:waiting] pid=${status.loginState.pid} authUrl=${status.loginState.authUrl}`);
761
- return Promise.resolve({ authUrl: status.loginState.authUrl, alreadyRunning: true });
762
- }
763
- const logDir = join(homedir(), loadBrand().configDir, "logs");
764
- mkdirSync(logDir, { recursive: true });
765
- const logPath = join(logDir, "cloudflared-login.log");
766
- const logFd = openSync(logPath, "a");
767
- // Record the log's byte size before the spawn writes anything. This is
768
- // the watermark for any marker parsing: every subsequent call slices
769
- // the log from this offset forward so historical markers from prior
770
- // attempts cannot be re-attributed to the new process.
771
- // narrowed the marker's role from terminal signal to advisory, so the
772
- // watermark is no longer load-bearing for loop prevention — but it is
773
- // still correct for attribution: a new process's courtesy-marker
774
- // advisory should not count log content written by its predecessor.
775
- const logOffset = existsSync(logPath) ? statSync(logPath).size : 0;
776
- const certDir = join(homedir(), loadBrand().configDir, "cloudflared");
777
- mkdirSync(certDir, { recursive: true });
778
- const originCert = join(certDir, "cert.pem");
779
- const vncEnv = vncSpawnEnv();
780
- const displayField = vncEnv ? vncEnv.display : "inherit";
781
- console.error(`[cloudflare:tunnel-login:spawn] log=${logPath} origincert=${originCert} ` +
782
- `log-offset=${logOffset} display=${displayField}`);
783
- const child = spawn(bin, ["tunnel", "--origincert", originCert, "login"], {
784
- stdio: ["ignore", logFd, logFd],
785
- detached: true,
786
- env: vncEnv?.env,
787
- });
788
- child.unref();
789
- if (!child.pid) {
790
- throw new Error("Failed to spawn cloudflared tunnel login — no PID returned");
791
- }
792
- console.error(`[cloudflare:tunnel-login:spawn] pid=${child.pid}`);
793
- const urlPattern = /https:\/\/dash\.cloudflare\.com[^\s]*/;
794
- return new Promise((resolve, reject) => {
795
- let elapsed = 0;
796
- const interval = 500;
797
- const timeout = 15000;
798
- const poller = setInterval(() => {
799
- elapsed += interval;
800
- if (!isProcessAlive(child.pid)) {
801
- clearInterval(poller);
802
- console.error(`[cloudflare:tunnel-login:failed] reason=process-exited-before-auth-url pid=${child.pid}`);
803
- clearLoginState();
804
- try {
805
- const output = readFileSync(logPath, "utf-8").slice(-500);
806
- reject(new Error(`cloudflared exited before producing an auth URL. Last output: ${output}`));
807
- }
808
- catch {
809
- reject(new Error("cloudflared exited before producing an auth URL"));
810
- }
811
- return;
812
- }
813
- try {
814
- const content = readFileSync(logPath, "utf-8");
815
- const match = content.match(urlPattern);
816
- if (match) {
817
- clearInterval(poller);
818
- const authUrl = match[0];
819
- console.error(`[cloudflare:tunnel-login:waiting] pid=${child.pid} authUrl=${authUrl}`);
820
- writeLoginState({
821
- pid: child.pid,
822
- startedAt: Date.now(),
823
- authUrl,
824
- logPath,
825
- logOffset,
826
- });
827
- resolve({ authUrl });
828
- return;
829
- }
830
- }
831
- catch {
832
- // log file may not be flushed yet — retry
833
- }
834
- if (elapsed >= timeout) {
835
- clearInterval(poller);
836
- console.error(`[cloudflare:tunnel-login:failed] reason=auth-url-timeout pid=${child.pid} timeout-ms=${timeout}`);
837
- try {
838
- process.kill(child.pid, "SIGTERM");
839
- }
840
- catch { /* already gone */ }
841
- clearLoginState();
842
- try {
843
- const output = readFileSync(logPath, "utf-8").slice(-500);
844
- reject(new Error(`cloudflared did not produce an auth URL within ${timeout / 1000} seconds. Output: ${output}`));
845
- }
846
- catch {
847
- reject(new Error(`cloudflared did not produce an auth URL within ${timeout / 1000} seconds`));
848
- }
849
- }
850
- }, interval);
851
- });
852
- }
853
- // ---------------------------------------------------------------------------
854
- // Process management — detached, survives MCP server exit
855
- // ---------------------------------------------------------------------------
856
- function statePath() {
857
- return join(homedir(), loadBrand().configDir, "cloudflared/tunnel.state");
858
- }
859
- function readState() {
860
- try {
861
- const p = statePath();
862
- if (!existsSync(p))
863
- return null;
864
- return JSON.parse(readFileSync(p, "utf-8"));
865
- }
866
- catch {
867
- return null;
868
- }
869
- }
870
- function writeState(state) {
871
- const dir = join(homedir(), loadBrand().configDir, "cloudflared");
872
- mkdirSync(dir, { recursive: true });
873
- writeFileSync(statePath(), JSON.stringify(state), "utf-8");
874
- }
875
- function isProcessAlive(pid) {
876
- if (pid === null || pid <= 0)
877
- return false;
878
- try {
879
- process.kill(pid, 0);
880
- return true;
881
- }
882
- catch {
883
- return false;
884
- }
885
- }
886
- export function getPersistedTunnelId() {
887
- return readState()?.tunnelId ?? null;
888
- }
889
- export function getPersistedDomain() {
890
- return readState()?.domain ?? null;
891
- }
892
- export function getPersistedState() {
893
- return readState();
894
- }
895
- export function saveTunnelIdentity(params) {
896
- writeState({
897
- tunnelId: params.tunnelId,
898
- tunnelName: params.tunnelName,
899
- domain: params.domain,
900
- configPath: params.configPath,
901
- credentialsPath: params.credentialsPath,
902
- adminHostname: params.adminHostname,
903
- publicHostname: params.publicHostname,
904
- pid: null,
905
- startedAt: null,
906
- });
907
- }
908
- /**
909
- * Return the actual hostnames from persisted state (or derive from domain
910
- * for pre-521 state files).
911
- */
912
- export function getPersistedHostnames() {
913
- const state = readState();
914
- if (!state)
915
- return [];
916
- if (state.adminHostname) {
917
- return state.publicHostname
918
- ? [state.adminHostname, state.publicHostname]
919
- : [state.adminHostname];
920
- }
921
- if (state.domain) {
922
- console.error(`[tunnel] backward-compat: deriving hostnames from domain=${state.domain} (no adminHostname in state)`);
923
- return [`admin.${state.domain}`, `public.${state.domain}`];
924
- }
925
- return [];
926
- }
927
- export async function probeHostname(hostname, expectedTunnelId) {
928
- const resolver = new Resolver();
929
- resolver.setServers(["1.1.1.1", "8.8.8.8"]);
930
- let cname = null;
931
- try {
932
- const cnames = await resolver.resolveCname(hostname);
933
- cname = cnames[0] ?? null;
934
- }
935
- catch (err) {
936
- const code = err.code;
937
- if (code === "ENODATA") {
938
- // Proxied records can flatten — try A records as a secondary signal
939
- // that the name exists, but without a CNAME we cannot confirm this
940
- // tunnel. Treat as cname-points-elsewhere (name exists but does not
941
- // point here) only when an A record is present; otherwise dns-missing.
942
- try {
943
- await resolver.resolve4(hostname);
944
- cname = null; // flattened — still need edge probe to confirm
945
- }
946
- catch {
947
- return { hostname, resolves: false, cname: null, httpStatus: null, failureMode: "dns-missing" };
948
- }
949
- }
950
- else if (code === "ENOTFOUND" || code === "ESERVFAIL") {
951
- return { hostname, resolves: false, cname: null, httpStatus: null, failureMode: "dns-missing" };
952
- }
953
- else {
954
- console.error(`[cloudflare:tunnel-status:probe] unexpected DNS error for ${hostname}: ${err}`);
955
- return { hostname, resolves: false, cname: null, httpStatus: null, failureMode: "dns-missing" };
956
- }
957
- }
958
- if (expectedTunnelId && cname) {
959
- const cnameLc = cname.toLowerCase().replace(/\.$/, "");
960
- const expected = `${expectedTunnelId}.cfargotunnel.com`.toLowerCase();
961
- if (cnameLc !== expected) {
962
- return { hostname, resolves: true, cname, httpStatus: null, failureMode: "cname-points-elsewhere" };
963
- }
964
- }
965
- // HTTPS probe — any response proves edge routing is wired up.
966
- let httpStatus = null;
967
- try {
968
- const controller = new AbortController();
969
- const to = setTimeout(() => controller.abort(), 10000);
970
- try {
971
- const res = await fetch(`https://${hostname}/`, {
972
- method: "GET",
973
- redirect: "manual",
974
- signal: controller.signal,
975
- });
976
- httpStatus = res.status;
977
- }
978
- finally {
979
- clearTimeout(to);
980
- }
981
- }
982
- catch (err) {
983
- console.error(`[cloudflare:tunnel-status:probe] HTTPS probe failed for ${hostname}: ${err instanceof Error ? err.message : String(err)}`);
984
- return { hostname, resolves: true, cname, httpStatus: null, failureMode: "edge-unreachable" };
985
- }
986
- // Cloudflare edge error page status codes that indicate the tunnel is
987
- // either not connected (530) or the hostname is not served by any tunnel
988
- // (1033/1016 surface as 530 or 502 at the HTTP layer; Cloudflare sometimes
989
- // uses 502/503 for unreachable origins). Any 5xx from the edge with
990
- // missing CNAME match means the tunnel-edge wiring is wrong.
991
- if (httpStatus >= 500 && httpStatus < 600) {
992
- return { hostname, resolves: true, cname, httpStatus, failureMode: "tunnel-not-matched" };
993
- }
994
- return { hostname, resolves: true, cname, httpStatus, failureMode: "ok" };
995
- }
996
- export async function getStatus(domain) {
997
- const auth = validateAuth();
998
- const state = readState();
999
- const running = state !== null && isProcessAlive(state.pid);
1000
- const effectiveDomain = domain ?? state?.domain ?? null;
1001
- const persistedHostnames = getPersistedHostnames();
1002
- const configuredHostnames = state?.configPath
1003
- ? parseConfiguredHostnames(state.configPath)
1004
- : [];
1005
- // Probe the authoritative set (config.yml). If config.yml is absent, fall
1006
- // back to persisted hostnames so partial-setup states still surface.
1007
- const hostnamesToProbe = configuredHostnames.length > 0
1008
- ? configuredHostnames
1009
- : persistedHostnames;
1010
- let probes = [];
1011
- let boundAccountOwnsHostnames = false;
1012
- let healthy = false;
1013
- let unhealthyReason = null;
1014
- if (!running || !state?.tunnelId || hostnamesToProbe.length === 0) {
1015
- unhealthyReason = !running
1016
- ? "not-running"
1017
- : "no-tunnel-configured";
1018
- }
1019
- else {
1020
- probes = await Promise.all(hostnamesToProbe.map((h) => probeHostname(h, state.tunnelId)));
1021
- // A probe reaching the edge with a non-5xx response proves the signed-in
1022
- // account owns the domain: the edge only routes to this tunnel's origin
1023
- // when the domain's DNS on this account points at this tunnel. The CNAME
1024
- // check inside probeHostname already short-circuits the "points to a
1025
- // different tunnel" case to `cname-points-elsewhere` — so here we trust
1026
- // `ok`. We do not gate on `p.cname !== null` because Cloudflare can flatten
1027
- // CNAMEs (zone-apex records, some proxy configurations) — in that case
1028
- // cname is null but the HTTPS response is still authoritative.
1029
- boundAccountOwnsHostnames = probes.some((p) => p.failureMode === "ok");
1030
- healthy = probes.every((p) => p.failureMode === "ok");
1031
- if (!healthy) {
1032
- // Distinguishing "wrong account" from "right account, tunnel broken":
1033
- // a probe that resolved DNS at all is evidence the domain lives on
1034
- // some Cloudflare account (the NS is Cloudflare's). A probe that
1035
- // resolved AND doesn't point elsewhere is evidence this laptop's
1036
- // account owns it — any other failure mode (edge-unreachable,
1037
- // tunnel-not-matched, or a healthy flattened probe) indicates the
1038
- // DNS points at *something* on the signed-in account, just not
1039
- // working. Only when every probe is `dns-missing` or
1040
- // `cname-points-elsewhere` do we conclude the bound account does
1041
- // not own the hostnames.
1042
- const anyCnamePointsHere = probes.some((p) => p.resolves &&
1043
- p.failureMode !== "cname-points-elsewhere" &&
1044
- p.failureMode !== "dns-missing");
1045
- unhealthyReason = anyCnamePointsHere
1046
- ? "hostname-probes-failed"
1047
- : "bound-account-does-not-own-hostname";
1048
- }
1049
- }
1050
- if (unhealthyReason === "bound-account-does-not-own-hostname") {
1051
- // Surface the structured refusal reason — same channel the
1052
- // agent-facing text uses, but keyed on the enum.
1053
- logRefuse({
1054
- reason: "bound-account-does-not-own-hostname",
1055
- message: "tunnel-status probe: no configured hostname's DNS lands on this tunnel. " +
1056
- `${DASHBOARD_SWITCH_ACCOUNTS_INSTRUCTION}`,
1057
- fields: {
1058
- boundAccountId: auth.boundAccountId,
1059
- certAccountId: auth.certAccountId,
1060
- tunnelId: state?.tunnelId ?? undefined,
1061
- },
1062
- });
1063
- }
1064
- // Single-line branch-selection observability. The handler's trailing text
1065
- // is driven off unhealthyReason + per-hostname failureMode; logging the
1066
- // enum here means the branch taken is post-diagnosable from MCP stderr
1067
- // without having to parse the returned JSON.
1068
- console.error(`[cloudflare:tunnel-status:result] reason=${unhealthyReason ?? "healthy"} ` +
1069
- `boundAccountOwnsHostnames=${boundAccountOwnsHostnames} ` +
1070
- `probes=${probes.map((p) => `${p.hostname}:${p.failureMode}`).join(",") || "none"}`);
1071
- return {
1072
- installed: isInstalled(),
1073
- version: version(),
1074
- hasCert: auth.hasCert,
1075
- hasBinding: auth.hasBinding,
1076
- bound: auth.bound,
1077
- certAccountId: auth.certAccountId,
1078
- boundAccountId: auth.boundAccountId,
1079
- running,
1080
- pid: running ? state.pid : null,
1081
- tunnelId: state?.tunnelId ?? null,
1082
- domain: effectiveDomain,
1083
- configuredHostnames,
1084
- persistedHostnames,
1085
- adminHostname: state?.adminHostname ?? (effectiveDomain ? `admin.${effectiveDomain}` : null),
1086
- publicHostname: state?.publicHostname ?? (effectiveDomain ? `public.${effectiveDomain}` : null),
1087
- upSince: running ? state.startedAt : null,
1088
- probes,
1089
- boundAccountOwnsHostnames,
1090
- healthy,
1091
- unhealthyReason,
1092
- };
1093
- }
1094
- export function startTunnel(params) {
1095
- const bin = findBinary();
1096
- if (!bin)
1097
- throw new Error("cloudflared is not installed");
1098
- const cert = findCert();
1099
- if (!cert)
1100
- throw new Error("No cert.pem found — run tunnel-login first");
1101
- const state = readState();
1102
- if (state && isProcessAlive(state.pid)) {
1103
- throw new Error(`Tunnel is already running (PID ${state.pid})`);
1104
- }
1105
- const logDir = join(homedir(), loadBrand().configDir, "logs");
1106
- mkdirSync(logDir, { recursive: true });
1107
- const logPath = join(logDir, "cloudflared.log");
1108
- const logFd = openSync(logPath, "a");
1109
- const child = spawn(bin, ["--origincert", cert, "--config", params.configPath, "tunnel", "run"], {
1110
- stdio: ["ignore", logFd, logFd],
1111
- detached: true,
1112
- });
1113
- child.unref();
1114
- if (!child.pid)
1115
- throw new Error("Failed to spawn tunnel process");
1116
- console.error(`[tunnel-enable] started PID ${child.pid} with config ${params.configPath}`);
1117
- const existingState = readState();
1118
- writeState({
1119
- pid: child.pid,
1120
- tunnelId: params.tunnelId,
1121
- tunnelName: params.tunnelName,
1122
- domain: params.domain,
1123
- configPath: params.configPath,
1124
- credentialsPath: params.credentialsPath,
1125
- adminHostname: existingState?.adminHostname ?? null,
1126
- publicHostname: existingState?.publicHostname ?? null,
1127
- startedAt: Date.now(),
1128
- });
1129
- }
1130
- export function stopTunnel() {
1131
- const state = readState();
1132
- if (!state)
1133
- return;
1134
- const pid = state.pid;
1135
- if (pid !== null && isProcessAlive(pid)) {
1136
- try {
1137
- process.kill(pid, "SIGTERM");
1138
- }
1139
- catch {
1140
- // process may have exited between check and kill
1141
- }
1142
- setTimeout(() => {
1143
- if (isProcessAlive(pid)) {
1144
- try {
1145
- process.kill(pid, "SIGKILL");
1146
- }
1147
- catch {
1148
- /* already gone */
1149
- }
1150
- }
1151
- }, 5000);
1152
- }
1153
- writeState({ ...state, pid: null, startedAt: null });
1154
- }
1155
- //# sourceMappingURL=cloudflared.js.map