@rubytech/create-maxy-code 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1821) hide show
  1. package/dist/__tests__/account-id-env.test.js +48 -0
  2. package/dist/__tests__/apt-resolve.test.js +179 -0
  3. package/dist/__tests__/brew-install.test.js +141 -0
  4. package/dist/__tests__/brew-resolve.test.js +103 -0
  5. package/dist/__tests__/cdp-port-no-silent-fallback.test.js +53 -0
  6. package/dist/__tests__/launchd-plist.test.js +149 -0
  7. package/dist/__tests__/macos-version.test.js +96 -0
  8. package/dist/__tests__/peer-brand-detect.test.js +103 -0
  9. package/dist/__tests__/platform-detect.test.js +50 -0
  10. package/dist/__tests__/port-canonicalisation.test.js +200 -0
  11. package/dist/__tests__/preflight-port-classifier.test.js +330 -0
  12. package/dist/__tests__/snap-chromium.test.js +115 -0
  13. package/dist/apt-resolve.js +73 -0
  14. package/dist/brew-install.js +175 -0
  15. package/dist/brew-resolve.js +68 -0
  16. package/dist/index.js +3325 -0
  17. package/dist/launchd-plist.js +68 -0
  18. package/dist/macos-version.js +53 -0
  19. package/dist/peer-brand-detect.js +39 -0
  20. package/dist/pinned-binaries.js +12 -0
  21. package/dist/platform-detect.js +36 -0
  22. package/dist/port-resolution.js +153 -0
  23. package/dist/preflight-port-classifier.js +222 -0
  24. package/dist/snap-chromium.js +89 -0
  25. package/dist/uninstall.js +861 -0
  26. package/package.json +32 -0
  27. package/payload/platform/config/brand-registry.json +20 -0
  28. package/payload/platform/config/brand.json +54 -0
  29. package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.d.ts +2 -0
  30. package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.d.ts.map +1 -0
  31. package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.js +88 -0
  32. package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.js.map +1 -0
  33. package/payload/platform/lib/account-enumeration/dist/__tests__/validate-env.test.d.ts +2 -0
  34. package/payload/platform/lib/account-enumeration/dist/__tests__/validate-env.test.d.ts.map +1 -0
  35. package/payload/platform/lib/account-enumeration/dist/__tests__/validate-env.test.js +55 -0
  36. package/payload/platform/lib/account-enumeration/dist/__tests__/validate-env.test.js.map +1 -0
  37. package/payload/platform/lib/account-enumeration/dist/index.d.ts +49 -0
  38. package/payload/platform/lib/account-enumeration/dist/index.d.ts.map +1 -0
  39. package/payload/platform/lib/account-enumeration/dist/index.js +109 -0
  40. package/payload/platform/lib/account-enumeration/dist/index.js.map +1 -0
  41. package/payload/platform/lib/account-enumeration/src/__tests__/enumerate.test.ts +94 -0
  42. package/payload/platform/lib/account-enumeration/src/__tests__/validate-env.test.ts +57 -0
  43. package/payload/platform/lib/account-enumeration/src/index.ts +140 -0
  44. package/payload/platform/lib/account-enumeration/tsconfig.json +8 -0
  45. package/payload/platform/lib/admins-write/dist/index.d.ts +87 -0
  46. package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -0
  47. package/payload/platform/lib/admins-write/dist/index.js +248 -0
  48. package/payload/platform/lib/admins-write/dist/index.js.map +1 -0
  49. package/payload/platform/lib/admins-write/src/index.ts +311 -0
  50. package/payload/platform/lib/admins-write/tsconfig.json +8 -0
  51. package/payload/platform/lib/anthropic-key/dist/index.d.ts +22 -0
  52. package/payload/platform/lib/anthropic-key/dist/index.d.ts.map +1 -0
  53. package/payload/platform/lib/anthropic-key/dist/index.js +232 -0
  54. package/payload/platform/lib/anthropic-key/dist/index.js.map +1 -0
  55. package/payload/platform/lib/brand-templating/dist/index.d.ts +18 -0
  56. package/payload/platform/lib/brand-templating/dist/index.d.ts.map +1 -0
  57. package/payload/platform/lib/brand-templating/dist/index.js +69 -0
  58. package/payload/platform/lib/brand-templating/dist/index.js.map +1 -0
  59. package/payload/platform/lib/brand-templating/src/index.ts +76 -0
  60. package/payload/platform/lib/brand-templating/tsconfig.json +8 -0
  61. package/payload/platform/lib/device-url/dist/index.d.ts +44 -0
  62. package/payload/platform/lib/device-url/dist/index.d.ts.map +1 -0
  63. package/payload/platform/lib/device-url/dist/index.js +68 -0
  64. package/payload/platform/lib/device-url/dist/index.js.map +1 -0
  65. package/payload/platform/lib/device-url/src/index.ts +78 -0
  66. package/payload/platform/lib/device-url/tsconfig.json +8 -0
  67. package/payload/platform/lib/entitlement/PUBKEY-HASH.txt +1 -0
  68. package/payload/platform/lib/entitlement/dist/canonicalize.d.ts +26 -0
  69. package/payload/platform/lib/entitlement/dist/canonicalize.d.ts.map +1 -0
  70. package/payload/platform/lib/entitlement/dist/canonicalize.js +54 -0
  71. package/payload/platform/lib/entitlement/dist/canonicalize.js.map +1 -0
  72. package/payload/platform/lib/entitlement/dist/index.d.ts +76 -0
  73. package/payload/platform/lib/entitlement/dist/index.d.ts.map +1 -0
  74. package/payload/platform/lib/entitlement/dist/index.js +293 -0
  75. package/payload/platform/lib/entitlement/dist/index.js.map +1 -0
  76. package/payload/platform/lib/entitlement/rubytech-pubkey.pem +3 -0
  77. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.d.ts +2 -0
  78. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.d.ts.map +1 -0
  79. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.js +97 -0
  80. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate-write.test.js.map +1 -0
  81. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts +2 -0
  82. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts.map +1 -0
  83. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js +112 -0
  84. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js.map +1 -0
  85. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts +2 -0
  86. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts.map +1 -0
  87. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js +163 -0
  88. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js.map +1 -0
  89. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts +2 -0
  90. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.d.ts.map +1 -0
  91. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js +89 -0
  92. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cypher-parser.test.js.map +1 -0
  93. package/payload/platform/lib/graph-mcp/dist/__tests__/warnings-envelope.test.d.ts +2 -0
  94. package/payload/platform/lib/graph-mcp/dist/__tests__/warnings-envelope.test.d.ts.map +1 -0
  95. package/payload/platform/lib/graph-mcp/dist/__tests__/warnings-envelope.test.js +140 -0
  96. package/payload/platform/lib/graph-mcp/dist/__tests__/warnings-envelope.test.js.map +1 -0
  97. package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.d.ts +37 -0
  98. package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.d.ts.map +1 -0
  99. package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.js +333 -0
  100. package/payload/platform/lib/graph-mcp/dist/cypher-rewrite-stamp.js.map +1 -0
  101. package/payload/platform/lib/graph-mcp/dist/cypher-shim-read.d.ts +85 -0
  102. package/payload/platform/lib/graph-mcp/dist/cypher-shim-read.d.ts.map +1 -0
  103. package/payload/platform/lib/graph-mcp/dist/cypher-shim-read.js +93 -0
  104. package/payload/platform/lib/graph-mcp/dist/cypher-shim-read.js.map +1 -0
  105. package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.d.ts +71 -0
  106. package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.d.ts.map +1 -0
  107. package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.js +168 -0
  108. package/payload/platform/lib/graph-mcp/dist/cypher-shim-write.js.map +1 -0
  109. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts +50 -0
  110. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts.map +1 -0
  111. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js +197 -0
  112. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js.map +1 -0
  113. package/payload/platform/lib/graph-mcp/dist/index.d.ts +26 -0
  114. package/payload/platform/lib/graph-mcp/dist/index.d.ts.map +1 -0
  115. package/payload/platform/lib/graph-mcp/dist/index.js +847 -0
  116. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -0
  117. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +75 -0
  118. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -0
  119. package/payload/platform/lib/graph-mcp/dist/schema-cache.js +217 -0
  120. package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -0
  121. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts +42 -0
  122. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.d.ts.map +1 -0
  123. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js +87 -0
  124. package/payload/platform/lib/graph-mcp/dist/schema-cypher-parser.js.map +1 -0
  125. package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate-write.test.ts +150 -0
  126. package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate.test.ts +141 -0
  127. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cache.test.ts +169 -0
  128. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cypher-parser.test.ts +99 -0
  129. package/payload/platform/lib/graph-mcp/src/__tests__/warnings-envelope.test.ts +151 -0
  130. package/payload/platform/lib/graph-mcp/src/cypher-rewrite-stamp.ts +349 -0
  131. package/payload/platform/lib/graph-mcp/src/cypher-shim-read.ts +141 -0
  132. package/payload/platform/lib/graph-mcp/src/cypher-shim-write.ts +240 -0
  133. package/payload/platform/lib/graph-mcp/src/cypher-validate.ts +249 -0
  134. package/payload/platform/lib/graph-mcp/src/index.ts +1076 -0
  135. package/payload/platform/lib/graph-mcp/src/schema-cache.ts +242 -0
  136. package/payload/platform/lib/graph-mcp/src/schema-cypher-parser.ts +84 -0
  137. package/payload/platform/lib/graph-mcp/tsconfig.json +8 -0
  138. package/payload/platform/lib/graph-search/dist/index.d.ts +227 -0
  139. package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -0
  140. package/payload/platform/lib/graph-search/dist/index.js +525 -0
  141. package/payload/platform/lib/graph-search/dist/index.js.map +1 -0
  142. package/payload/platform/lib/graph-search/src/__tests__/bm25-label-gate.test.ts +88 -0
  143. package/payload/platform/lib/graph-search/src/__tests__/bm25-only.test.ts +129 -0
  144. package/payload/platform/lib/graph-search/src/__tests__/bm25-strong-bypass-threshold.test.ts +126 -0
  145. package/payload/platform/lib/graph-search/src/__tests__/brochure-threshold.test.ts +136 -0
  146. package/payload/platform/lib/graph-search/src/__tests__/escape-and-normalise.test.ts +53 -0
  147. package/payload/platform/lib/graph-search/src/__tests__/expand-batch.test.ts +206 -0
  148. package/payload/platform/lib/graph-search/src/__tests__/fulltext-coverage.test.ts +280 -0
  149. package/payload/platform/lib/graph-search/src/__tests__/hybrid.test.ts +262 -0
  150. package/payload/platform/lib/graph-search/src/__tests__/vector-threshold.test.ts +170 -0
  151. package/payload/platform/lib/graph-search/src/index.ts +718 -0
  152. package/payload/platform/lib/graph-search/tsconfig.json +9 -0
  153. package/payload/platform/lib/graph-search/vitest.config.ts +9 -0
  154. package/payload/platform/lib/graph-trash/dist/index.d.ts +99 -0
  155. package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -0
  156. package/payload/platform/lib/graph-trash/dist/index.js +333 -0
  157. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -0
  158. package/payload/platform/lib/graph-trash/src/index.ts +475 -0
  159. package/payload/platform/lib/graph-trash/tsconfig.json +8 -0
  160. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.d.ts +2 -0
  161. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.d.ts.map +1 -0
  162. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js +165 -0
  163. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js.map +1 -0
  164. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.d.ts +2 -0
  165. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.d.ts.map +1 -0
  166. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +226 -0
  167. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -0
  168. package/payload/platform/lib/graph-write/dist/__tests__/audit.test.d.ts +2 -0
  169. package/payload/platform/lib/graph-write/dist/__tests__/audit.test.d.ts.map +1 -0
  170. package/payload/platform/lib/graph-write/dist/__tests__/audit.test.js +147 -0
  171. package/payload/platform/lib/graph-write/dist/__tests__/audit.test.js.map +1 -0
  172. package/payload/platform/lib/graph-write/dist/audit.d.ts +84 -0
  173. package/payload/platform/lib/graph-write/dist/audit.d.ts.map +1 -0
  174. package/payload/platform/lib/graph-write/dist/audit.js +129 -0
  175. package/payload/platform/lib/graph-write/dist/audit.js.map +1 -0
  176. package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts +26 -0
  177. package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts.map +1 -0
  178. package/payload/platform/lib/graph-write/dist/conversation-provenance.js +81 -0
  179. package/payload/platform/lib/graph-write/dist/conversation-provenance.js.map +1 -0
  180. package/payload/platform/lib/graph-write/dist/index.d.ts +124 -0
  181. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -0
  182. package/payload/platform/lib/graph-write/dist/index.js +288 -0
  183. package/payload/platform/lib/graph-write/dist/index.js.map +1 -0
  184. package/payload/platform/lib/graph-write/src/__tests__/account-id-gate.test.ts +189 -0
  185. package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +252 -0
  186. package/payload/platform/lib/graph-write/src/__tests__/audit.test.ts +162 -0
  187. package/payload/platform/lib/graph-write/src/audit.ts +182 -0
  188. package/payload/platform/lib/graph-write/src/conversation-provenance.ts +140 -0
  189. package/payload/platform/lib/graph-write/src/index.ts +386 -0
  190. package/payload/platform/lib/graph-write/tsconfig.json +8 -0
  191. package/payload/platform/lib/mcp-eager/dist/index.d.ts +61 -0
  192. package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -0
  193. package/payload/platform/lib/mcp-eager/dist/index.js +49 -0
  194. package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -0
  195. package/payload/platform/lib/mcp-eager/src/index.ts +78 -0
  196. package/payload/platform/lib/mcp-eager/tsconfig.json +8 -0
  197. package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts +53 -0
  198. package/payload/platform/lib/mcp-spawn-tee/dist/index.d.ts.map +1 -0
  199. package/payload/platform/lib/mcp-spawn-tee/dist/index.js +132 -0
  200. package/payload/platform/lib/mcp-spawn-tee/dist/index.js.map +1 -0
  201. package/payload/platform/lib/mcp-spawn-tee/src/index.ts +134 -0
  202. package/payload/platform/lib/mcp-spawn-tee/tsconfig.json +8 -0
  203. package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts +51 -0
  204. package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -0
  205. package/payload/platform/lib/mcp-stderr-tee/dist/index.js +196 -0
  206. package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +1 -0
  207. package/payload/platform/lib/mcp-stderr-tee/src/index.ts +206 -0
  208. package/payload/platform/lib/mcp-stderr-tee/tsconfig.json +8 -0
  209. package/payload/platform/lib/models/dist/index.d.ts +7 -0
  210. package/payload/platform/lib/models/dist/index.d.ts.map +1 -0
  211. package/payload/platform/lib/models/dist/index.js +20 -0
  212. package/payload/platform/lib/models/dist/index.js.map +1 -0
  213. package/payload/platform/lib/models/src/index.ts +18 -0
  214. package/payload/platform/lib/models/tsconfig.json +8 -0
  215. package/payload/platform/lib/oauth-llm/dist/index.d.ts +118 -0
  216. package/payload/platform/lib/oauth-llm/dist/index.d.ts.map +1 -0
  217. package/payload/platform/lib/oauth-llm/dist/index.js +388 -0
  218. package/payload/platform/lib/oauth-llm/dist/index.js.map +1 -0
  219. package/payload/platform/lib/oauth-llm/src/index.ts +585 -0
  220. package/payload/platform/lib/oauth-llm/tsconfig.json +8 -0
  221. package/payload/platform/lib/persistent-components/dist/index.d.ts +21 -0
  222. package/payload/platform/lib/persistent-components/dist/index.d.ts.map +1 -0
  223. package/payload/platform/lib/persistent-components/dist/index.js +32 -0
  224. package/payload/platform/lib/persistent-components/dist/index.js.map +1 -0
  225. package/payload/platform/lib/persistent-components/src/index.ts +28 -0
  226. package/payload/platform/lib/persistent-components/tsconfig.json +8 -0
  227. package/payload/platform/lib/screening-patterns/dist/index.d.ts +29 -0
  228. package/payload/platform/lib/screening-patterns/dist/index.d.ts.map +1 -0
  229. package/payload/platform/lib/screening-patterns/dist/index.js +48 -0
  230. package/payload/platform/lib/screening-patterns/dist/index.js.map +1 -0
  231. package/payload/platform/lib/screening-patterns/src/index.ts +51 -0
  232. package/payload/platform/lib/screening-patterns/tsconfig.json +8 -0
  233. package/payload/platform/lib/task-secrets/dist/index.d.ts +40 -0
  234. package/payload/platform/lib/task-secrets/dist/index.d.ts.map +1 -0
  235. package/payload/platform/lib/task-secrets/dist/index.js +44 -0
  236. package/payload/platform/lib/task-secrets/dist/index.js.map +1 -0
  237. package/payload/platform/lib/task-secrets/src/__tests__/redact-secrets.test.ts +127 -0
  238. package/payload/platform/lib/task-secrets/src/index.ts +77 -0
  239. package/payload/platform/lib/task-secrets/tsconfig.json +9 -0
  240. package/payload/platform/lib/task-secrets/vitest.config.ts +9 -0
  241. package/payload/platform/neo4j/edge-annotations.json +154 -0
  242. package/payload/platform/neo4j/schema.cypher +1104 -0
  243. package/payload/platform/package-lock.json +3576 -0
  244. package/payload/platform/package.json +25 -0
  245. package/payload/platform/plugins/admin/PLUGIN.md +81 -0
  246. package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-surface-gate.test.sh +191 -0
  247. package/payload/platform/plugins/admin/hooks/__tests__/playwright-file-guard.test.sh +278 -0
  248. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-base64-guard.test.sh +204 -0
  249. package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +225 -0
  250. package/payload/platform/plugins/admin/hooks/playwright-file-guard.sh +214 -0
  251. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +294 -0
  252. package/payload/platform/plugins/admin/hooks/webfetch-preflight.mjs +363 -0
  253. package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.d.ts +2 -0
  254. package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.d.ts.map +1 -0
  255. package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.js +91 -0
  256. package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.js.map +1 -0
  257. package/payload/platform/plugins/admin/mcp/dist/__tests__/public-hostname.test.d.ts +2 -0
  258. package/payload/platform/plugins/admin/mcp/dist/__tests__/public-hostname.test.d.ts.map +1 -0
  259. package/payload/platform/plugins/admin/mcp/dist/__tests__/public-hostname.test.js +98 -0
  260. package/payload/platform/plugins/admin/mcp/dist/__tests__/public-hostname.test.js.map +1 -0
  261. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load-required-inputs.test.d.ts +2 -0
  262. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load-required-inputs.test.d.ts.map +1 -0
  263. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load-required-inputs.test.js +141 -0
  264. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load-required-inputs.test.js.map +1 -0
  265. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.d.ts +2 -0
  266. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.d.ts.map +1 -0
  267. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.js +88 -0
  268. package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.js.map +1 -0
  269. package/payload/platform/plugins/admin/mcp/dist/index.d.ts +2 -0
  270. package/payload/platform/plugins/admin/mcp/dist/index.d.ts.map +1 -0
  271. package/payload/platform/plugins/admin/mcp/dist/index.js +3495 -0
  272. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -0
  273. package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.d.ts +5 -0
  274. package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.d.ts.map +1 -0
  275. package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.js +40 -0
  276. package/payload/platform/plugins/admin/mcp/dist/lib/neo4j.js.map +1 -0
  277. package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts +39 -0
  278. package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.d.ts.map +1 -0
  279. package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js +249 -0
  280. package/payload/platform/plugins/admin/mcp/dist/lib/onboarding.js.map +1 -0
  281. package/payload/platform/plugins/admin/mcp/dist/lib/public-hostname.d.ts +15 -0
  282. package/payload/platform/plugins/admin/mcp/dist/lib/public-hostname.d.ts.map +1 -0
  283. package/payload/platform/plugins/admin/mcp/dist/lib/public-hostname.js +73 -0
  284. package/payload/platform/plugins/admin/mcp/dist/lib/public-hostname.js.map +1 -0
  285. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +35 -0
  286. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -0
  287. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +140 -0
  288. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -0
  289. package/payload/platform/plugins/admin/mcp/package.json +23 -0
  290. package/payload/platform/plugins/admin/mcp/vitest.config.ts +9 -0
  291. package/payload/platform/plugins/admin/references/chat-ui-guide.md +31 -0
  292. package/payload/platform/plugins/admin/references/contextual-ui.md +107 -0
  293. package/payload/platform/plugins/admin/skills/a4-print-documents/SKILL.md +241 -0
  294. package/payload/platform/plugins/admin/skills/access-manager/SKILL.md +28 -0
  295. package/payload/platform/plugins/admin/skills/access-manager/references/operations.md +197 -0
  296. package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +53 -0
  297. package/payload/platform/plugins/admin/skills/datetime/SKILL.md +91 -0
  298. package/payload/platform/plugins/admin/skills/deck-pages/SKILL.md +418 -0
  299. package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +255 -0
  300. package/payload/platform/plugins/admin/skills/plainly/SKILL.md +105 -0
  301. package/payload/platform/plugins/admin/skills/plainly/references/worked-examples.md +301 -0
  302. package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +99 -0
  303. package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +277 -0
  304. package/payload/platform/plugins/admin/skills/publish-site/SKILL.md +72 -0
  305. package/payload/platform/plugins/admin/skills/qr-code/SKILL.md +35 -0
  306. package/payload/platform/plugins/admin/skills/qr-code/references/data-formats.md +113 -0
  307. package/payload/platform/plugins/admin/skills/skill-builder/SKILL.md +113 -0
  308. package/payload/platform/plugins/admin/skills/skill-builder/references/lean-pattern.md +110 -0
  309. package/payload/platform/plugins/admin/skills/skill-builder/references/pdf-generation.md +30 -0
  310. package/payload/platform/plugins/admin/skills/specialist-management/SKILL.md +44 -0
  311. package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +71 -0
  312. package/payload/platform/plugins/admin/skills/stream-log-review/references/analysis-patterns.md +189 -0
  313. package/payload/platform/plugins/admin/skills/unzip-attachment/SKILL.md +79 -0
  314. package/payload/platform/plugins/admin/skills/unzip-attachment/__tests__/preflight.sh +148 -0
  315. package/payload/platform/plugins/admin/skills/unzip-attachment/references/safety.md +116 -0
  316. package/payload/platform/plugins/admin/skills/update-knowledge/SKILL.md +47 -0
  317. package/payload/platform/plugins/anthropic/PLUGIN.md +40 -0
  318. package/payload/platform/plugins/anthropic/references/console-api.md +186 -0
  319. package/payload/platform/plugins/anthropic/references/setup-guide.md +36 -0
  320. package/payload/platform/plugins/anthropic/skills/get-api-key/SKILL.md +138 -0
  321. package/payload/platform/plugins/business-assistant/PLUGIN.md +59 -0
  322. package/payload/platform/plugins/business-assistant/references/crm.md +112 -0
  323. package/payload/platform/plugins/business-assistant/references/document-management.md +96 -0
  324. package/payload/platform/plugins/business-assistant/references/escalation.md +126 -0
  325. package/payload/platform/plugins/business-assistant/references/invoicing.md +163 -0
  326. package/payload/platform/plugins/business-assistant/references/profiling.md +50 -0
  327. package/payload/platform/plugins/business-assistant/references/quoting.md +56 -0
  328. package/payload/platform/plugins/business-assistant/references/scheduling.md +127 -0
  329. package/payload/platform/plugins/business-assistant/references/task-management.md +163 -0
  330. package/payload/platform/plugins/cloudflare/PLUGIN.md +73 -0
  331. package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts +2 -0
  332. package/payload/platform/plugins/cloudflare/mcp/dist/index.d.ts.map +1 -0
  333. package/payload/platform/plugins/cloudflare/mcp/dist/index.js +29 -0
  334. package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -0
  335. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +283 -0
  336. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -0
  337. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +1155 -0
  338. package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -0
  339. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts +90 -0
  340. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +1 -0
  341. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +550 -0
  342. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +1 -0
  343. package/payload/platform/plugins/cloudflare/mcp/package.json +18 -0
  344. package/payload/platform/plugins/cloudflare/mcp/vitest.config.ts +10 -0
  345. package/payload/platform/plugins/cloudflare/references/dashboard-guide.md +108 -0
  346. package/payload/platform/plugins/cloudflare/references/manual-setup.md +481 -0
  347. package/payload/platform/plugins/cloudflare/references/reset-guide.md +118 -0
  348. package/payload/platform/plugins/cloudflare/scripts/_stream-log.sh +154 -0
  349. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.sh +98 -0
  350. package/payload/platform/plugins/cloudflare/scripts/list-cf-domains.ts +751 -0
  351. package/payload/platform/plugins/cloudflare/scripts/reset-tunnel.sh +107 -0
  352. package/payload/platform/plugins/cloudflare/scripts/setup-tunnel.sh +826 -0
  353. package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +107 -0
  354. package/payload/platform/plugins/contacts/PLUGIN.md +31 -0
  355. package/payload/platform/plugins/contacts/mcp/dist/index.d.ts +2 -0
  356. package/payload/platform/plugins/contacts/mcp/dist/index.d.ts.map +1 -0
  357. package/payload/platform/plugins/contacts/mcp/dist/index.js +433 -0
  358. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -0
  359. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts +5 -0
  360. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.d.ts.map +1 -0
  361. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js +40 -0
  362. package/payload/platform/plugins/contacts/mcp/dist/lib/neo4j.js.map +1 -0
  363. package/payload/platform/plugins/contacts/mcp/dist/lib/resolve-person.d.ts +33 -0
  364. package/payload/platform/plugins/contacts/mcp/dist/lib/resolve-person.d.ts.map +1 -0
  365. package/payload/platform/plugins/contacts/mcp/dist/lib/resolve-person.js +53 -0
  366. package/payload/platform/plugins/contacts/mcp/dist/lib/resolve-person.js.map +1 -0
  367. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +23 -0
  368. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -0
  369. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +123 -0
  370. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -0
  371. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.d.ts +28 -0
  372. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.d.ts.map +1 -0
  373. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.js +39 -0
  374. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-delete.js.map +1 -0
  375. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts +41 -0
  376. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts.map +1 -0
  377. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +142 -0
  378. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js.map +1 -0
  379. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts +52 -0
  380. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts.map +1 -0
  381. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js +119 -0
  382. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js.map +1 -0
  383. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts +23 -0
  384. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.d.ts.map +1 -0
  385. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js +49 -0
  386. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-list.js.map +1 -0
  387. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +21 -0
  388. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -0
  389. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +70 -0
  390. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -0
  391. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts +14 -0
  392. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.d.ts.map +1 -0
  393. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js +43 -0
  394. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-update.js.map +1 -0
  395. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts +18 -0
  396. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts.map +1 -0
  397. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js +95 -0
  398. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js.map +1 -0
  399. package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.d.ts +15 -0
  400. package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.d.ts.map +1 -0
  401. package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.js +72 -0
  402. package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.js.map +1 -0
  403. package/payload/platform/plugins/contacts/mcp/package.json +19 -0
  404. package/payload/platform/plugins/deep-research/PLUGIN.md +13 -0
  405. package/payload/platform/plugins/deep-research/skills/deep-research/SKILL.md +46 -0
  406. package/payload/platform/plugins/deep-research/skills/deep-research/references/citation-styles.md +52 -0
  407. package/payload/platform/plugins/deep-research/skills/deep-research/references/research-modes.md +22 -0
  408. package/payload/platform/plugins/deep-research/skills/deep-research/references/search-strategy.md +24 -0
  409. package/payload/platform/plugins/docs/PLUGIN.md +52 -0
  410. package/payload/platform/plugins/docs/references/access-control.md +73 -0
  411. package/payload/platform/plugins/docs/references/admin-session.md +80 -0
  412. package/payload/platform/plugins/docs/references/attachments.md +44 -0
  413. package/payload/platform/plugins/docs/references/cloudflare.md +111 -0
  414. package/payload/platform/plugins/docs/references/contacts-guide.md +102 -0
  415. package/payload/platform/plugins/docs/references/deployment.md +150 -0
  416. package/payload/platform/plugins/docs/references/getting-started.md +82 -0
  417. package/payload/platform/plugins/docs/references/graph.md +149 -0
  418. package/payload/platform/plugins/docs/references/internals.md +512 -0
  419. package/payload/platform/plugins/docs/references/memory-guide.md +119 -0
  420. package/payload/platform/plugins/docs/references/migration-guide.md +90 -0
  421. package/payload/platform/plugins/docs/references/outlook-guide.md +69 -0
  422. package/payload/platform/plugins/docs/references/platform.md +111 -0
  423. package/payload/platform/plugins/docs/references/plugins-guide.md +174 -0
  424. package/payload/platform/plugins/docs/references/projects-guide.md +73 -0
  425. package/payload/platform/plugins/docs/references/settings.md +82 -0
  426. package/payload/platform/plugins/docs/references/telegram-guide.md +58 -0
  427. package/payload/platform/plugins/docs/references/troubleshooting.md +532 -0
  428. package/payload/platform/plugins/email/PLUGIN.md +49 -0
  429. package/payload/platform/plugins/email/mcp/dist/index.d.ts +2 -0
  430. package/payload/platform/plugins/email/mcp/dist/index.d.ts.map +1 -0
  431. package/payload/platform/plugins/email/mcp/dist/index.js +291 -0
  432. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -0
  433. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts +118 -0
  434. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -0
  435. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +364 -0
  436. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -0
  437. package/payload/platform/plugins/email/mcp/dist/lib/embedding.d.ts +2 -0
  438. package/payload/platform/plugins/email/mcp/dist/lib/embedding.d.ts.map +1 -0
  439. package/payload/platform/plugins/email/mcp/dist/lib/embedding.js +24 -0
  440. package/payload/platform/plugins/email/mcp/dist/lib/embedding.js.map +1 -0
  441. package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts +87 -0
  442. package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts.map +1 -0
  443. package/payload/platform/plugins/email/mcp/dist/lib/graph.js +324 -0
  444. package/payload/platform/plugins/email/mcp/dist/lib/graph.js.map +1 -0
  445. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +215 -0
  446. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -0
  447. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +735 -0
  448. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -0
  449. package/payload/platform/plugins/email/mcp/dist/lib/neo4j.d.ts +5 -0
  450. package/payload/platform/plugins/email/mcp/dist/lib/neo4j.d.ts.map +1 -0
  451. package/payload/platform/plugins/email/mcp/dist/lib/neo4j.js +40 -0
  452. package/payload/platform/plugins/email/mcp/dist/lib/neo4j.js.map +1 -0
  453. package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts +32 -0
  454. package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts.map +1 -0
  455. package/payload/platform/plugins/email/mcp/dist/lib/providers.js +569 -0
  456. package/payload/platform/plugins/email/mcp/dist/lib/providers.js.map +1 -0
  457. package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts +29 -0
  458. package/payload/platform/plugins/email/mcp/dist/lib/screening.d.ts.map +1 -0
  459. package/payload/platform/plugins/email/mcp/dist/lib/screening.js +105 -0
  460. package/payload/platform/plugins/email/mcp/dist/lib/screening.js.map +1 -0
  461. package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts +21 -0
  462. package/payload/platform/plugins/email/mcp/dist/lib/smtp.d.ts.map +1 -0
  463. package/payload/platform/plugins/email/mcp/dist/lib/smtp.js +77 -0
  464. package/payload/platform/plugins/email/mcp/dist/lib/smtp.js.map +1 -0
  465. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts +38 -0
  466. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.d.ts.map +1 -0
  467. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js +894 -0
  468. package/payload/platform/plugins/email/mcp/dist/scripts/email-auto-respond.js.map +1 -0
  469. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts +25 -0
  470. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.d.ts.map +1 -0
  471. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +227 -0
  472. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +1 -0
  473. package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts +19 -0
  474. package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.d.ts.map +1 -0
  475. package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js +151 -0
  476. package/payload/platform/plugins/email/mcp/dist/tools/email-auto-respond-config.js.map +1 -0
  477. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts +22 -0
  478. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts.map +1 -0
  479. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js +188 -0
  480. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js.map +1 -0
  481. package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.d.ts +15 -0
  482. package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.d.ts.map +1 -0
  483. package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.js +142 -0
  484. package/payload/platform/plugins/email/mcp/dist/tools/email-otp-extract.js.map +1 -0
  485. package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts +14 -0
  486. package/payload/platform/plugins/email/mcp/dist/tools/email-read.d.ts.map +1 -0
  487. package/payload/platform/plugins/email/mcp/dist/tools/email-read.js +75 -0
  488. package/payload/platform/plugins/email/mcp/dist/tools/email-read.js.map +1 -0
  489. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts +10 -0
  490. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -0
  491. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +83 -0
  492. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -0
  493. package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts +15 -0
  494. package/payload/platform/plugins/email/mcp/dist/tools/email-search.d.ts.map +1 -0
  495. package/payload/platform/plugins/email/mcp/dist/tools/email-search.js +63 -0
  496. package/payload/platform/plugins/email/mcp/dist/tools/email-search.js.map +1 -0
  497. package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts +10 -0
  498. package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts.map +1 -0
  499. package/payload/platform/plugins/email/mcp/dist/tools/email-send.js +31 -0
  500. package/payload/platform/plugins/email/mcp/dist/tools/email-send.js.map +1 -0
  501. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts +22 -0
  502. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts.map +1 -0
  503. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +183 -0
  504. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -0
  505. package/payload/platform/plugins/email/mcp/dist/tools/email-status.d.ts +6 -0
  506. package/payload/platform/plugins/email/mcp/dist/tools/email-status.d.ts.map +1 -0
  507. package/payload/platform/plugins/email/mcp/dist/tools/email-status.js +43 -0
  508. package/payload/platform/plugins/email/mcp/dist/tools/email-status.js.map +1 -0
  509. package/payload/platform/plugins/email/mcp/package.json +23 -0
  510. package/payload/platform/plugins/email/references/email-reference.md +204 -0
  511. package/payload/platform/plugins/linkedin-import/PLUGIN.md +27 -0
  512. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +142 -0
  513. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md +135 -0
  514. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/profile.md +95 -0
  515. package/payload/platform/plugins/memory/PLUGIN.md +146 -0
  516. package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +879 -0
  517. package/payload/platform/plugins/memory/bin/conversation-archive-ingest.sh +138 -0
  518. package/payload/platform/plugins/memory/mcp/dist/index.d.ts +2 -0
  519. package/payload/platform/plugins/memory/mcp/dist/index.d.ts.map +1 -0
  520. package/payload/platform/plugins/memory/mcp/dist/index.js +1813 -0
  521. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -0
  522. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts +2 -0
  523. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.d.ts.map +1 -0
  524. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js +92 -0
  525. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/live-schema-source.test.js.map +1 -0
  526. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts +2 -0
  527. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.d.ts.map +1 -0
  528. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js +225 -0
  529. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/llm-classifier.test.js.map +1 -0
  530. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts +2 -0
  531. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.d.ts.map +1 -0
  532. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +100 -0
  533. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -0
  534. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts +2 -0
  535. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.d.ts.map +1 -0
  536. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +448 -0
  537. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -0
  538. package/payload/platform/plugins/memory/mcp/dist/lib/attachments.d.ts +37 -0
  539. package/payload/platform/plugins/memory/mcp/dist/lib/attachments.d.ts.map +1 -0
  540. package/payload/platform/plugins/memory/mcp/dist/lib/attachments.js +69 -0
  541. package/payload/platform/plugins/memory/mcp/dist/lib/attachments.js.map +1 -0
  542. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts +5 -0
  543. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts.map +1 -0
  544. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js +30 -0
  545. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js.map +1 -0
  546. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/timestamp-scanner.d.ts +49 -0
  547. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/timestamp-scanner.d.ts.map +1 -0
  548. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/timestamp-scanner.js +35 -0
  549. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/timestamp-scanner.js.map +1 -0
  550. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts +47 -0
  551. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts.map +1 -0
  552. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js +31 -0
  553. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js.map +1 -0
  554. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.d.ts +3 -0
  555. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.d.ts.map +1 -0
  556. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.js +155 -0
  557. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/whatsapp-text.js.map +1 -0
  558. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.d.ts +11 -0
  559. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.d.ts.map +1 -0
  560. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.js +20 -0
  561. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/delta-cursor.js.map +1 -0
  562. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts +14 -0
  563. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts.map +1 -0
  564. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js +38 -0
  565. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js.map +1 -0
  566. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.d.ts +16 -0
  567. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.d.ts.map +1 -0
  568. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.js +59 -0
  569. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sender-bind.js.map +1 -0
  570. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.d.ts +9 -0
  571. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.d.ts.map +1 -0
  572. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.js +32 -0
  573. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/sessionize.js.map +1 -0
  574. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.d.ts +3 -0
  575. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.d.ts.map +1 -0
  576. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.js +29 -0
  577. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/to-turn-text.js.map +1 -0
  578. package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.d.ts +45 -0
  579. package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.d.ts.map +1 -0
  580. package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.js +125 -0
  581. package/payload/platform/plugins/memory/mcp/dist/lib/document-chunker.js.map +1 -0
  582. package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.d.ts +9 -0
  583. package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.d.ts.map +1 -0
  584. package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.js +61 -0
  585. package/payload/platform/plugins/memory/mcp/dist/lib/document-hierarchy.js.map +1 -0
  586. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts +3 -0
  587. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.d.ts.map +1 -0
  588. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js +29 -0
  589. package/payload/platform/plugins/memory/mcp/dist/lib/embeddings.js.map +1 -0
  590. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
  591. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
  592. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
  593. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
  594. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts +42 -0
  595. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts.map +1 -0
  596. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js +114 -0
  597. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js.map +1 -0
  598. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +38 -0
  599. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -0
  600. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +89 -0
  601. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -0
  602. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts +136 -0
  603. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.d.ts.map +1 -0
  604. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js +180 -0
  605. package/payload/platform/plugins/memory/mcp/dist/lib/live-schema-source.js.map +1 -0
  606. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +246 -0
  607. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -0
  608. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +828 -0
  609. package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -0
  610. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts +63 -0
  611. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.d.ts.map +1 -0
  612. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js +210 -0
  613. package/payload/platform/plugins/memory/mcp/dist/lib/llm-ranker.js.map +1 -0
  614. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +5 -0
  615. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts.map +1 -0
  616. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +40 -0
  617. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -0
  618. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +113 -0
  619. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -0
  620. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +455 -0
  621. package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -0
  622. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +83 -0
  623. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -0
  624. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +209 -0
  625. package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -0
  626. package/payload/platform/plugins/memory/mcp/dist/lib/uuid.d.ts +3 -0
  627. package/payload/platform/plugins/memory/mcp/dist/lib/uuid.d.ts.map +1 -0
  628. package/payload/platform/plugins/memory/mcp/dist/lib/uuid.js +12 -0
  629. package/payload/platform/plugins/memory/mcp/dist/lib/uuid.js.map +1 -0
  630. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.d.ts +2 -0
  631. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.d.ts.map +1 -0
  632. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +97 -0
  633. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -0
  634. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.d.ts +2 -0
  635. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.d.ts.map +1 -0
  636. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js +184 -0
  637. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js.map +1 -0
  638. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.d.ts +2 -0
  639. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.d.ts.map +1 -0
  640. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js +73 -0
  641. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js.map +1 -0
  642. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.d.ts +2 -0
  643. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.d.ts.map +1 -0
  644. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.js +109 -0
  645. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-whatsapp-text.test.js.map +1 -0
  646. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.d.ts +2 -0
  647. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.d.ts.map +1 -0
  648. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +84 -0
  649. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -0
  650. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.d.ts +2 -0
  651. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.d.ts.map +1 -0
  652. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +106 -0
  653. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -0
  654. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.d.ts +2 -0
  655. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.d.ts.map +1 -0
  656. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.js +87 -0
  657. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-not-applicable.test.js.map +1 -0
  658. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts +2 -0
  659. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.d.ts.map +1 -0
  660. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js +148 -0
  661. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-open.test.js.map +1 -0
  662. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +89 -0
  663. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -0
  664. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +542 -0
  665. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -0
  666. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts +41 -0
  667. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.d.ts.map +1 -0
  668. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js +116 -0
  669. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-enrich-rejection.js.map +1 -0
  670. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts +8 -0
  671. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts.map +1 -0
  672. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js +7 -0
  673. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js.map +1 -0
  674. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts +7 -0
  675. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.d.ts.map +1 -0
  676. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js +28 -0
  677. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-add.js.map +1 -0
  678. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts +7 -0
  679. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.d.ts.map +1 -0
  680. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js +7 -0
  681. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-list.js.map +1 -0
  682. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts +7 -0
  683. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.d.ts.map +1 -0
  684. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js +27 -0
  685. package/payload/platform/plugins/memory/mcp/dist/tools/graph-prune-denylist-remove.js.map +1 -0
  686. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +54 -0
  687. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -0
  688. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +231 -0
  689. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -0
  690. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts +34 -0
  691. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.d.ts.map +1 -0
  692. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js +58 -0
  693. package/payload/platform/plugins/memory/mcp/dist/tools/memory-classify.js.map +1 -0
  694. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +57 -0
  695. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -0
  696. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +106 -0
  697. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -0
  698. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.d.ts +16 -0
  699. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.d.ts.map +1 -0
  700. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js +91 -0
  701. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edit-attachment.js.map +1 -0
  702. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +22 -0
  703. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -0
  704. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +36 -0
  705. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -0
  706. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
  707. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
  708. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
  709. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
  710. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts +28 -0
  711. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.d.ts.map +1 -0
  712. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js +93 -0
  713. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-extract.js.map +1 -0
  714. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts +20 -0
  715. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.d.ts.map +1 -0
  716. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js +87 -0
  717. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest-web.js.map +1 -0
  718. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +129 -0
  719. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -0
  720. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +827 -0
  721. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -0
  722. package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.d.ts +19 -0
  723. package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.d.ts.map +1 -0
  724. package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.js +125 -0
  725. package/payload/platform/plugins/memory/mcp/dist/tools/memory-list-attachments.js.map +1 -0
  726. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts +61 -0
  727. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.d.ts.map +1 -0
  728. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js +102 -0
  729. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rank.js.map +1 -0
  730. package/payload/platform/plugins/memory/mcp/dist/tools/memory-read-attachment.d.ts +12 -0
  731. package/payload/platform/plugins/memory/mcp/dist/tools/memory-read-attachment.d.ts.map +1 -0
  732. package/payload/platform/plugins/memory/mcp/dist/tools/memory-read-attachment.js +100 -0
  733. package/payload/platform/plugins/memory/mcp/dist/tools/memory-read-attachment.js.map +1 -0
  734. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts +9 -0
  735. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -0
  736. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +84 -0
  737. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -0
  738. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.d.ts +13 -0
  739. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.d.ts.map +1 -0
  740. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.js +63 -0
  741. package/payload/platform/plugins/memory/mcp/dist/tools/memory-rename-attachment.js.map +1 -0
  742. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts +24 -0
  743. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts.map +1 -0
  744. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js +40 -0
  745. package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js.map +1 -0
  746. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts +5 -0
  747. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -0
  748. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +42 -0
  749. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -0
  750. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +13 -0
  751. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -0
  752. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +77 -0
  753. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -0
  754. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +47 -0
  755. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -0
  756. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +173 -0
  757. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -0
  758. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.d.ts +24 -0
  759. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.d.ts.map +1 -0
  760. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js +31 -0
  761. package/payload/platform/plugins/memory/mcp/dist/tools/profile-delete.js.map +1 -0
  762. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts +44 -0
  763. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts.map +1 -0
  764. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +322 -0
  765. package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -0
  766. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +65 -0
  767. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -0
  768. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +369 -0
  769. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -0
  770. package/payload/platform/plugins/memory/mcp/package.json +24 -0
  771. package/payload/platform/plugins/memory/mcp/scripts/boot-smoke.sh +69 -0
  772. package/payload/platform/plugins/memory/mcp/scripts/graph/accept.sh +217 -0
  773. package/payload/platform/plugins/memory/mcp/scripts/graph/fixture.cypher +59 -0
  774. package/payload/platform/plugins/memory/mcp/vitest.config.ts +15 -0
  775. package/payload/platform/plugins/memory/references/graph-primitives.md +342 -0
  776. package/payload/platform/plugins/memory/references/schema-base.md +234 -0
  777. package/payload/platform/plugins/memory/references/schema-creator.md +35 -0
  778. package/payload/platform/plugins/memory/references/schema-estate-agent.md +35 -0
  779. package/payload/platform/plugins/memory/references/schema-food-beverage.md +32 -0
  780. package/payload/platform/plugins/memory/references/schema-hospitality.md +31 -0
  781. package/payload/platform/plugins/memory/references/schema-logistics.md +30 -0
  782. package/payload/platform/plugins/memory/references/schema-professional-services.md +33 -0
  783. package/payload/platform/plugins/memory/references/schema-retail.md +33 -0
  784. package/payload/platform/plugins/memory/references/schema-trades.md +36 -0
  785. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +202 -0
  786. package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +159 -0
  787. package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +108 -0
  788. package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +267 -0
  789. package/payload/platform/plugins/outlook/PLUGIN.md +48 -0
  790. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts +2 -0
  791. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts.map +1 -0
  792. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +94 -0
  793. package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -0
  794. package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts +2 -0
  795. package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts.map +1 -0
  796. package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js +31 -0
  797. package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js.map +1 -0
  798. package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +2 -0
  799. package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts.map +1 -0
  800. package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +213 -0
  801. package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +1 -0
  802. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts +2 -0
  803. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts.map +1 -0
  804. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +130 -0
  805. package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -0
  806. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts +65 -0
  807. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +1 -0
  808. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +261 -0
  809. package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +1 -0
  810. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +61 -0
  811. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -0
  812. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +170 -0
  813. package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -0
  814. package/payload/platform/plugins/outlook/mcp/dist/index.d.ts +18 -0
  815. package/payload/platform/plugins/outlook/mcp/dist/index.d.ts.map +1 -0
  816. package/payload/platform/plugins/outlook/mcp/dist/index.js +152 -0
  817. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -0
  818. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +60 -0
  819. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -0
  820. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +189 -0
  821. package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -0
  822. package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts +23 -0
  823. package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts.map +1 -0
  824. package/payload/platform/plugins/outlook/mcp/dist/lib/log.js +53 -0
  825. package/payload/platform/plugins/outlook/mcp/dist/lib/log.js.map +1 -0
  826. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +26 -0
  827. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -0
  828. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +50 -0
  829. package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -0
  830. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts +12 -0
  831. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts.map +1 -0
  832. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js +32 -0
  833. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js.map +1 -0
  834. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts +59 -0
  835. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts.map +1 -0
  836. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js +54 -0
  837. package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js.map +1 -0
  838. package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts +14 -0
  839. package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts.map +1 -0
  840. package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js +45 -0
  841. package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js.map +1 -0
  842. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +15 -0
  843. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -0
  844. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +48 -0
  845. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -0
  846. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts +8 -0
  847. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -0
  848. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +49 -0
  849. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -0
  850. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts +19 -0
  851. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts.map +1 -0
  852. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js +58 -0
  853. package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js.map +1 -0
  854. package/payload/platform/plugins/outlook/mcp/package.json +20 -0
  855. package/payload/platform/plugins/outlook/mcp/scripts/verify-doc-impl.sh +109 -0
  856. package/payload/platform/plugins/outlook/references/auth.md +118 -0
  857. package/payload/platform/plugins/outlook/references/graph-surfaces.md +114 -0
  858. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +65 -0
  859. package/payload/platform/plugins/projects/PLUGIN.md +90 -0
  860. package/payload/platform/plugins/projects/references/investigation.md +63 -0
  861. package/payload/platform/plugins/projects/references/retrospective.md +71 -0
  862. package/payload/platform/plugins/projects/references/review.md +51 -0
  863. package/payload/platform/plugins/projects/references/sprint.md +168 -0
  864. package/payload/platform/plugins/replicate/PLUGIN.md +23 -0
  865. package/payload/platform/plugins/replicate/mcp/dist/index.d.ts +2 -0
  866. package/payload/platform/plugins/replicate/mcp/dist/index.d.ts.map +1 -0
  867. package/payload/platform/plugins/replicate/mcp/dist/index.js +106 -0
  868. package/payload/platform/plugins/replicate/mcp/dist/index.js.map +1 -0
  869. package/payload/platform/plugins/replicate/mcp/dist/lib/replicate-key.d.ts +15 -0
  870. package/payload/platform/plugins/replicate/mcp/dist/lib/replicate-key.d.ts.map +1 -0
  871. package/payload/platform/plugins/replicate/mcp/dist/lib/replicate-key.js +73 -0
  872. package/payload/platform/plugins/replicate/mcp/dist/lib/replicate-key.js.map +1 -0
  873. package/payload/platform/plugins/replicate/mcp/dist/tools/image-generate.d.ts +14 -0
  874. package/payload/platform/plugins/replicate/mcp/dist/tools/image-generate.d.ts.map +1 -0
  875. package/payload/platform/plugins/replicate/mcp/dist/tools/image-generate.js +191 -0
  876. package/payload/platform/plugins/replicate/mcp/dist/tools/image-generate.js.map +1 -0
  877. package/payload/platform/plugins/replicate/mcp/package.json +21 -0
  878. package/payload/platform/plugins/sales/PLUGIN.md +106 -0
  879. package/payload/platform/plugins/sales/references/close-tracking.md +69 -0
  880. package/payload/platform/plugins/sales/references/comparisons.md +99 -0
  881. package/payload/platform/plugins/sales/references/competitive-positioning.md +51 -0
  882. package/payload/platform/plugins/sales/references/faq.md +77 -0
  883. package/payload/platform/plugins/sales/references/objection-handling.md +157 -0
  884. package/payload/platform/plugins/sales/references/pricing.md +101 -0
  885. package/payload/platform/plugins/scheduling/PLUGIN.md +103 -0
  886. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/time-resolve-description.test.d.ts +2 -0
  887. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/time-resolve-description.test.d.ts.map +1 -0
  888. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/time-resolve-description.test.js +16 -0
  889. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/time-resolve-description.test.js.map +1 -0
  890. package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts +2 -0
  891. package/payload/platform/plugins/scheduling/mcp/dist/index.d.ts.map +1 -0
  892. package/payload/platform/plugins/scheduling/mcp/dist/index.js +315 -0
  893. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -0
  894. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.d.ts +2 -0
  895. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.d.ts.map +1 -0
  896. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js +119 -0
  897. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/getUserTimezone.test.js.map +1 -0
  898. package/payload/platform/plugins/scheduling/mcp/dist/lib/embedding.d.ts +2 -0
  899. package/payload/platform/plugins/scheduling/mcp/dist/lib/embedding.d.ts.map +1 -0
  900. package/payload/platform/plugins/scheduling/mcp/dist/lib/embedding.js +19 -0
  901. package/payload/platform/plugins/scheduling/mcp/dist/lib/embedding.js.map +1 -0
  902. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.d.ts +47 -0
  903. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.d.ts.map +1 -0
  904. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.js +362 -0
  905. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.js.map +1 -0
  906. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts +30 -0
  907. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.d.ts.map +1 -0
  908. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js +89 -0
  909. package/payload/platform/plugins/scheduling/mcp/dist/lib/neo4j.js.map +1 -0
  910. package/payload/platform/plugins/scheduling/mcp/dist/lib/time-format.d.ts +48 -0
  911. package/payload/platform/plugins/scheduling/mcp/dist/lib/time-format.d.ts.map +1 -0
  912. package/payload/platform/plugins/scheduling/mcp/dist/lib/time-format.js +140 -0
  913. package/payload/platform/plugins/scheduling/mcp/dist/lib/time-format.js.map +1 -0
  914. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts +20 -0
  915. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.d.ts.map +1 -0
  916. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +568 -0
  917. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -0
  918. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts +7 -0
  919. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts.map +1 -0
  920. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js +23 -0
  921. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js.map +1 -0
  922. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +25 -0
  923. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -0
  924. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +121 -0
  925. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -0
  926. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.d.ts +9 -0
  927. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.d.ts.map +1 -0
  928. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.js +76 -0
  929. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.js.map +1 -0
  930. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts +25 -0
  931. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -0
  932. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +53 -0
  933. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -0
  934. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-import-ics.d.ts +8 -0
  935. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-import-ics.d.ts.map +1 -0
  936. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-import-ics.js +48 -0
  937. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-import-ics.js.map +1 -0
  938. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts +20 -0
  939. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts.map +1 -0
  940. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js +76 -0
  941. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js.map +1 -0
  942. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts +18 -0
  943. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -0
  944. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +167 -0
  945. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -0
  946. package/payload/platform/plugins/scheduling/mcp/package.json +23 -0
  947. package/payload/platform/plugins/scheduling/mcp/vitest.config.ts +9 -0
  948. package/payload/platform/plugins/tasks/.claude-plugin/plugin.json +17 -0
  949. package/payload/platform/plugins/tasks/.mcp.json +13 -0
  950. package/payload/platform/plugins/tasks/PLUGIN.md +81 -0
  951. package/payload/platform/plugins/tasks/mcp/dist/index.d.ts +2 -0
  952. package/payload/platform/plugins/tasks/mcp/dist/index.d.ts.map +1 -0
  953. package/payload/platform/plugins/tasks/mcp/dist/index.js +503 -0
  954. package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -0
  955. package/payload/platform/plugins/tasks/mcp/dist/lib/embeddings.d.ts +7 -0
  956. package/payload/platform/plugins/tasks/mcp/dist/lib/embeddings.d.ts.map +1 -0
  957. package/payload/platform/plugins/tasks/mcp/dist/lib/embeddings.js +24 -0
  958. package/payload/platform/plugins/tasks/mcp/dist/lib/embeddings.js.map +1 -0
  959. package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.d.ts +5 -0
  960. package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.d.ts.map +1 -0
  961. package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.js +40 -0
  962. package/payload/platform/plugins/tasks/mcp/dist/lib/neo4j.js.map +1 -0
  963. package/payload/platform/plugins/tasks/mcp/dist/tools/project-complete.d.ts +17 -0
  964. package/payload/platform/plugins/tasks/mcp/dist/tools/project-complete.d.ts.map +1 -0
  965. package/payload/platform/plugins/tasks/mcp/dist/tools/project-complete.js +76 -0
  966. package/payload/platform/plugins/tasks/mcp/dist/tools/project-complete.js.map +1 -0
  967. package/payload/platform/plugins/tasks/mcp/dist/tools/project-create.d.ts +29 -0
  968. package/payload/platform/plugins/tasks/mcp/dist/tools/project-create.d.ts.map +1 -0
  969. package/payload/platform/plugins/tasks/mcp/dist/tools/project-create.js +235 -0
  970. package/payload/platform/plugins/tasks/mcp/dist/tools/project-create.js.map +1 -0
  971. package/payload/platform/plugins/tasks/mcp/dist/tools/project-get.d.ts +40 -0
  972. package/payload/platform/plugins/tasks/mcp/dist/tools/project-get.d.ts.map +1 -0
  973. package/payload/platform/plugins/tasks/mcp/dist/tools/project-get.js +125 -0
  974. package/payload/platform/plugins/tasks/mcp/dist/tools/project-get.js.map +1 -0
  975. package/payload/platform/plugins/tasks/mcp/dist/tools/project-list.d.ts +26 -0
  976. package/payload/platform/plugins/tasks/mcp/dist/tools/project-list.d.ts.map +1 -0
  977. package/payload/platform/plugins/tasks/mcp/dist/tools/project-list.js +81 -0
  978. package/payload/platform/plugins/tasks/mcp/dist/tools/project-list.js.map +1 -0
  979. package/payload/platform/plugins/tasks/mcp/dist/tools/project-update.d.ts +19 -0
  980. package/payload/platform/plugins/tasks/mcp/dist/tools/project-update.d.ts.map +1 -0
  981. package/payload/platform/plugins/tasks/mcp/dist/tools/project-update.js +102 -0
  982. package/payload/platform/plugins/tasks/mcp/dist/tools/project-update.js.map +1 -0
  983. package/payload/platform/plugins/tasks/mcp/dist/tools/session-list.d.ts +20 -0
  984. package/payload/platform/plugins/tasks/mcp/dist/tools/session-list.d.ts.map +1 -0
  985. package/payload/platform/plugins/tasks/mcp/dist/tools/session-list.js +37 -0
  986. package/payload/platform/plugins/tasks/mcp/dist/tools/session-list.js.map +1 -0
  987. package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.d.ts +12 -0
  988. package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.d.ts.map +1 -0
  989. package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.js +28 -0
  990. package/payload/platform/plugins/tasks/mcp/dist/tools/session-name.js.map +1 -0
  991. package/payload/platform/plugins/tasks/mcp/dist/tools/task-complete.d.ts +16 -0
  992. package/payload/platform/plugins/tasks/mcp/dist/tools/task-complete.d.ts.map +1 -0
  993. package/payload/platform/plugins/tasks/mcp/dist/tools/task-complete.js +33 -0
  994. package/payload/platform/plugins/tasks/mcp/dist/tools/task-complete.js.map +1 -0
  995. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts +63 -0
  996. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.d.ts.map +1 -0
  997. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js +141 -0
  998. package/payload/platform/plugins/tasks/mcp/dist/tools/task-create.js.map +1 -0
  999. package/payload/platform/plugins/tasks/mcp/dist/tools/task-get.d.ts +19 -0
  1000. package/payload/platform/plugins/tasks/mcp/dist/tools/task-get.d.ts.map +1 -0
  1001. package/payload/platform/plugins/tasks/mcp/dist/tools/task-get.js +51 -0
  1002. package/payload/platform/plugins/tasks/mcp/dist/tools/task-get.js.map +1 -0
  1003. package/payload/platform/plugins/tasks/mcp/dist/tools/task-list.d.ts +18 -0
  1004. package/payload/platform/plugins/tasks/mcp/dist/tools/task-list.d.ts.map +1 -0
  1005. package/payload/platform/plugins/tasks/mcp/dist/tools/task-list.js +66 -0
  1006. package/payload/platform/plugins/tasks/mcp/dist/tools/task-list.js.map +1 -0
  1007. package/payload/platform/plugins/tasks/mcp/dist/tools/task-ready.d.ts +21 -0
  1008. package/payload/platform/plugins/tasks/mcp/dist/tools/task-ready.d.ts.map +1 -0
  1009. package/payload/platform/plugins/tasks/mcp/dist/tools/task-ready.js +54 -0
  1010. package/payload/platform/plugins/tasks/mcp/dist/tools/task-ready.js.map +1 -0
  1011. package/payload/platform/plugins/tasks/mcp/dist/tools/task-relate.d.ts +12 -0
  1012. package/payload/platform/plugins/tasks/mcp/dist/tools/task-relate.d.ts.map +1 -0
  1013. package/payload/platform/plugins/tasks/mcp/dist/tools/task-relate.js +59 -0
  1014. package/payload/platform/plugins/tasks/mcp/dist/tools/task-relate.js.map +1 -0
  1015. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.d.ts +32 -0
  1016. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.d.ts.map +1 -0
  1017. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js +112 -0
  1018. package/payload/platform/plugins/tasks/mcp/dist/tools/task-update.js.map +1 -0
  1019. package/payload/platform/plugins/tasks/mcp/package.json +20 -0
  1020. package/payload/platform/plugins/telegram/PLUGIN.md +35 -0
  1021. package/payload/platform/plugins/telegram/mcp/dist/index.d.ts +2 -0
  1022. package/payload/platform/plugins/telegram/mcp/dist/index.d.ts.map +1 -0
  1023. package/payload/platform/plugins/telegram/mcp/dist/index.js +198 -0
  1024. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -0
  1025. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts +41 -0
  1026. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts.map +1 -0
  1027. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js +70 -0
  1028. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js.map +1 -0
  1029. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts +16 -0
  1030. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.d.ts.map +1 -0
  1031. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js +68 -0
  1032. package/payload/platform/plugins/telegram/mcp/dist/tools/message-history.js.map +1 -0
  1033. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts +20 -0
  1034. package/payload/platform/plugins/telegram/mcp/dist/tools/message.d.ts.map +1 -0
  1035. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js +34 -0
  1036. package/payload/platform/plugins/telegram/mcp/dist/tools/message.js.map +1 -0
  1037. package/payload/platform/plugins/telegram/mcp/package.json +19 -0
  1038. package/payload/platform/plugins/telegram/references/setup-guide.md +50 -0
  1039. package/payload/platform/plugins/waitlist/PLUGIN.md +63 -0
  1040. package/payload/platform/plugins/waitlist/mcp/dist/index.d.ts +2 -0
  1041. package/payload/platform/plugins/waitlist/mcp/dist/index.d.ts.map +1 -0
  1042. package/payload/platform/plugins/waitlist/mcp/dist/index.js +328 -0
  1043. package/payload/platform/plugins/waitlist/mcp/dist/index.js.map +1 -0
  1044. package/payload/platform/plugins/waitlist/mcp/dist/lib/embedding.d.ts +3 -0
  1045. package/payload/platform/plugins/waitlist/mcp/dist/lib/embedding.d.ts.map +1 -0
  1046. package/payload/platform/plugins/waitlist/mcp/dist/lib/embedding.js +48 -0
  1047. package/payload/platform/plugins/waitlist/mcp/dist/lib/embedding.js.map +1 -0
  1048. package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.d.ts +5 -0
  1049. package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.d.ts.map +1 -0
  1050. package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.js +40 -0
  1051. package/payload/platform/plugins/waitlist/mcp/dist/lib/neo4j.js.map +1 -0
  1052. package/payload/platform/plugins/waitlist/mcp/dist/tools/__tests__/waitlist-persist.test.d.ts +2 -0
  1053. package/payload/platform/plugins/waitlist/mcp/dist/tools/__tests__/waitlist-persist.test.d.ts.map +1 -0
  1054. package/payload/platform/plugins/waitlist/mcp/dist/tools/__tests__/waitlist-persist.test.js +70 -0
  1055. package/payload/platform/plugins/waitlist/mcp/dist/tools/__tests__/waitlist-persist.test.js.map +1 -0
  1056. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.d.ts +33 -0
  1057. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.d.ts.map +1 -0
  1058. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.js +124 -0
  1059. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.js.map +1 -0
  1060. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-list.d.ts +23 -0
  1061. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-list.d.ts.map +1 -0
  1062. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-list.js +58 -0
  1063. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-list.js.map +1 -0
  1064. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-persist.d.ts +83 -0
  1065. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-persist.d.ts.map +1 -0
  1066. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-persist.js +433 -0
  1067. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-persist.js.map +1 -0
  1068. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-review.d.ts +19 -0
  1069. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-review.d.ts.map +1 -0
  1070. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-review.js +81 -0
  1071. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-review.js.map +1 -0
  1072. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-scan.d.ts +28 -0
  1073. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-scan.d.ts.map +1 -0
  1074. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-scan.js +50 -0
  1075. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-scan.js.map +1 -0
  1076. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.d.ts +33 -0
  1077. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.d.ts.map +1 -0
  1078. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.js +335 -0
  1079. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.js.map +1 -0
  1080. package/payload/platform/plugins/waitlist/mcp/package.json +23 -0
  1081. package/payload/platform/plugins/waitlist/mcp/vitest.config.ts +9 -0
  1082. package/payload/platform/plugins/whatsapp/PLUGIN.md +71 -0
  1083. package/payload/platform/plugins/whatsapp/mcp/dist/call-api.d.ts +14 -0
  1084. package/payload/platform/plugins/whatsapp/mcp/dist/call-api.d.ts.map +1 -0
  1085. package/payload/platform/plugins/whatsapp/mcp/dist/call-api.js +42 -0
  1086. package/payload/platform/plugins/whatsapp/mcp/dist/call-api.js.map +1 -0
  1087. package/payload/platform/plugins/whatsapp/mcp/dist/index.d.ts +8 -0
  1088. package/payload/platform/plugins/whatsapp/mcp/dist/index.d.ts.map +1 -0
  1089. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +469 -0
  1090. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -0
  1091. package/payload/platform/plugins/whatsapp/mcp/package.json +19 -0
  1092. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +257 -0
  1093. package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +82 -0
  1094. package/payload/platform/plugins/whatsapp/skills/manage-whatsapp-config/SKILL.md +99 -0
  1095. package/payload/platform/plugins/workflows/.claude-plugin/plugin.json +16 -0
  1096. package/payload/platform/plugins/workflows/.mcp.json +12 -0
  1097. package/payload/platform/plugins/workflows/PLUGIN.md +297 -0
  1098. package/payload/platform/plugins/workflows/mcp/dist/index.d.ts +2 -0
  1099. package/payload/platform/plugins/workflows/mcp/dist/index.d.ts.map +1 -0
  1100. package/payload/platform/plugins/workflows/mcp/dist/index.js +536 -0
  1101. package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -0
  1102. package/payload/platform/plugins/workflows/mcp/dist/lib/active-runs.d.ts +38 -0
  1103. package/payload/platform/plugins/workflows/mcp/dist/lib/active-runs.d.ts.map +1 -0
  1104. package/payload/platform/plugins/workflows/mcp/dist/lib/active-runs.js +83 -0
  1105. package/payload/platform/plugins/workflows/mcp/dist/lib/active-runs.js.map +1 -0
  1106. package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.d.ts +2 -0
  1107. package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.d.ts.map +1 -0
  1108. package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.js +19 -0
  1109. package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.js.map +1 -0
  1110. package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts +151 -0
  1111. package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.d.ts.map +1 -0
  1112. package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js +299 -0
  1113. package/payload/platform/plugins/workflows/mcp/dist/lib/llm-call.js.map +1 -0
  1114. package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.d.ts +5 -0
  1115. package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.d.ts.map +1 -0
  1116. package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.js +40 -0
  1117. package/payload/platform/plugins/workflows/mcp/dist/lib/neo4j.js.map +1 -0
  1118. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts +153 -0
  1119. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts.map +1 -0
  1120. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js +273 -0
  1121. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js.map +1 -0
  1122. package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.d.ts +32 -0
  1123. package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.d.ts.map +1 -0
  1124. package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.js +97 -0
  1125. package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.js.map +1 -0
  1126. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts +48 -0
  1127. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts.map +1 -0
  1128. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js +166 -0
  1129. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js.map +1 -0
  1130. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-delete.d.ts +28 -0
  1131. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-delete.d.ts.map +1 -0
  1132. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-delete.js +57 -0
  1133. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-delete.js.map +1 -0
  1134. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts +64 -0
  1135. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts.map +1 -0
  1136. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +605 -0
  1137. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -0
  1138. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.d.ts +29 -0
  1139. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.d.ts.map +1 -0
  1140. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.js +55 -0
  1141. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.js.map +1 -0
  1142. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-list.d.ts +16 -0
  1143. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-list.d.ts.map +1 -0
  1144. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-list.js +44 -0
  1145. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-list.js.map +1 -0
  1146. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-runs.d.ts +28 -0
  1147. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-runs.d.ts.map +1 -0
  1148. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-runs.js +71 -0
  1149. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-runs.js.map +1 -0
  1150. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.d.ts +11 -0
  1151. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.d.ts.map +1 -0
  1152. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js +198 -0
  1153. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js.map +1 -0
  1154. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.d.ts +13 -0
  1155. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.d.ts.map +1 -0
  1156. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.js +71 -0
  1157. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.js.map +1 -0
  1158. package/payload/platform/plugins/workflows/mcp/package.json +20 -0
  1159. package/payload/platform/plugins/workflows/mcp/test-runner.mjs +710 -0
  1160. package/payload/platform/plugins/workflows/mcp/test-workflows.sh +77 -0
  1161. package/payload/platform/plugins/workflows/skills/workflow-manager/SKILL.md +88 -0
  1162. package/payload/platform/scripts/__tests__/admin-persist-audit.test.ts +182 -0
  1163. package/payload/platform/scripts/__tests__/first-token-creates-stream-log.test.sh +37 -0
  1164. package/payload/platform/scripts/__tests__/logs-read-prefix.sh +237 -0
  1165. package/payload/platform/scripts/admin-conversation-recover.mjs +386 -0
  1166. package/payload/platform/scripts/admin-persist-audit.ts +217 -0
  1167. package/payload/platform/scripts/check-no-conversation-id-leaks.mjs +165 -0
  1168. package/payload/platform/scripts/check-no-task-id-leaks.mjs +110 -0
  1169. package/payload/platform/scripts/check-sdk-oauth.mjs +185 -0
  1170. package/payload/platform/scripts/check-skill-load-coverage.mjs +100 -0
  1171. package/payload/platform/scripts/component-knowledgedoc-backfill.ts +214 -0
  1172. package/payload/platform/scripts/conversation-id-allowlist.txt +151 -0
  1173. package/payload/platform/scripts/dedupe-userprofile-ghosts.sh +388 -0
  1174. package/payload/platform/scripts/generate-entitlement-fixture.mjs +152 -0
  1175. package/payload/platform/scripts/installer-device-verify.sh +249 -0
  1176. package/payload/platform/scripts/lib/resolve-account-dir.sh +186 -0
  1177. package/payload/platform/scripts/log-adherence-check.sh +125 -0
  1178. package/payload/platform/scripts/logs-read.sh +577 -0
  1179. package/payload/platform/scripts/logs-read.test.sh +159 -0
  1180. package/payload/platform/scripts/migrate-import.sh +437 -0
  1181. package/payload/platform/scripts/redact-install-logs.sh +87 -0
  1182. package/payload/platform/scripts/resume-tunnel.sh +117 -0
  1183. package/payload/platform/scripts/seed-neo4j.sh +590 -0
  1184. package/payload/platform/scripts/taskmaster-export.sh +388 -0
  1185. package/payload/platform/scripts/test-laptop-vnc-boot.sh +88 -0
  1186. package/payload/platform/scripts/verify-skill-tool-surface.sh +255 -0
  1187. package/payload/platform/scripts/vnc.sh +475 -0
  1188. package/payload/platform/scripts/wifi-provision-server/server.js +743 -0
  1189. package/payload/platform/scripts/wifi-provision.sh +492 -0
  1190. package/payload/platform/services/claude-session-manager/dist/config.d.ts +12 -0
  1191. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -0
  1192. package/payload/platform/services/claude-session-manager/dist/config.js +27 -0
  1193. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -0
  1194. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +10 -0
  1195. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -0
  1196. package/payload/platform/services/claude-session-manager/dist/http-server.js +186 -0
  1197. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -0
  1198. package/payload/platform/services/claude-session-manager/dist/index.d.ts +2 -0
  1199. package/payload/platform/services/claude-session-manager/dist/index.d.ts.map +1 -0
  1200. package/payload/platform/services/claude-session-manager/dist/index.js +64 -0
  1201. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -0
  1202. package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts +4 -0
  1203. package/payload/platform/services/claude-session-manager/dist/jsonl-path.d.ts.map +1 -0
  1204. package/payload/platform/services/claude-session-manager/dist/jsonl-path.js +31 -0
  1205. package/payload/platform/services/claude-session-manager/dist/jsonl-path.js.map +1 -0
  1206. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +34 -0
  1207. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -0
  1208. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +91 -0
  1209. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -0
  1210. package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +23 -0
  1211. package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +1 -0
  1212. package/payload/platform/services/claude-session-manager/dist/session-store.js +31 -0
  1213. package/payload/platform/services/claude-session-manager/dist/session-store.js.map +1 -0
  1214. package/payload/platform/services/claude-session-manager/dist/types.d.ts +33 -0
  1215. package/payload/platform/services/claude-session-manager/dist/types.d.ts.map +1 -0
  1216. package/payload/platform/services/claude-session-manager/dist/types.js +2 -0
  1217. package/payload/platform/services/claude-session-manager/dist/types.js.map +1 -0
  1218. package/payload/platform/services/claude-session-manager/dist/url-capture.d.ts +12 -0
  1219. package/payload/platform/services/claude-session-manager/dist/url-capture.d.ts.map +1 -0
  1220. package/payload/platform/services/claude-session-manager/dist/url-capture.js +68 -0
  1221. package/payload/platform/services/claude-session-manager/dist/url-capture.js.map +1 -0
  1222. package/payload/platform/services/claude-session-manager/package.json +22 -0
  1223. package/payload/platform/templates/account.json +12 -0
  1224. package/payload/platform/templates/agents/admin/AGENTS.md +12 -0
  1225. package/payload/platform/templates/agents/admin/IDENTITY.md +320 -0
  1226. package/payload/platform/templates/agents/admin/LEARNINGS.md +3 -0
  1227. package/payload/platform/templates/agents/admin/SOUL.md +23 -0
  1228. package/payload/platform/templates/agents/public/IDENTITY.md +56 -0
  1229. package/payload/platform/templates/agents/public/SOUL.md +19 -0
  1230. package/payload/platform/templates/agents/public/config.json +9 -0
  1231. package/payload/platform/templates/specialists/.claude-plugin/plugin.json +4 -0
  1232. package/payload/platform/templates/specialists/agents/content-producer.md +104 -0
  1233. package/payload/platform/templates/specialists/agents/database-operator.md +199 -0
  1234. package/payload/platform/templates/specialists/agents/personal-assistant.md +209 -0
  1235. package/payload/platform/templates/specialists/agents/project-manager.md +132 -0
  1236. package/payload/platform/templates/specialists/agents/research-assistant.md +115 -0
  1237. package/payload/platform/templates/systemd/edge.service.template +38 -0
  1238. package/payload/platform/tsconfig.base.json +18 -0
  1239. package/payload/premium-plugins/real-agency/BUNDLE.md +44 -0
  1240. package/payload/premium-plugins/real-agency/agents/buyer-enquiry/IDENTITY.md +13 -0
  1241. package/payload/premium-plugins/real-agency/agents/buyer-enquiry/SOUL.md +9 -0
  1242. package/payload/premium-plugins/real-agency/agents/buyer-enquiry/template.json +9 -0
  1243. package/payload/premium-plugins/real-agency/agents/compliance.md +303 -0
  1244. package/payload/premium-plugins/real-agency/agents/negotiator.md +145 -0
  1245. package/payload/premium-plugins/real-agency/agents/valuer.md +140 -0
  1246. package/payload/premium-plugins/real-agency/plugins/brochures/PLUGIN.md +36 -0
  1247. package/payload/premium-plugins/real-agency/plugins/brochures/commands/make-brochure.md +11 -0
  1248. package/payload/premium-plugins/real-agency/plugins/brochures/skills/a4-print-documents/SKILL.md +478 -0
  1249. package/payload/premium-plugins/real-agency/plugins/brochures/skills/brand-design/SKILL.md +192 -0
  1250. package/payload/premium-plugins/real-agency/plugins/brochures/skills/make-brochure/SKILL.md +354 -0
  1251. package/payload/premium-plugins/real-agency/plugins/brochures/skills/make-brochure/references/seller-brief-template.md +115 -0
  1252. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/SKILL.md +119 -0
  1253. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/build.md +270 -0
  1254. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/copy.md +211 -0
  1255. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/images.md +166 -0
  1256. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/page-landing.md +376 -0
  1257. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/page.html +1288 -0
  1258. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/placeholders.md +250 -0
  1259. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/registers.md +47 -0
  1260. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/seller-brief.md +56 -0
  1261. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/structure.md +249 -0
  1262. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-brochure/references/template.html +2370 -0
  1263. package/payload/premium-plugins/real-agency/plugins/brochures/skills/property-extract/SKILL.md +372 -0
  1264. package/payload/premium-plugins/real-agency/plugins/buyers/PLUGIN.md +35 -0
  1265. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-feedback/SKILL.md +109 -0
  1266. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/SKILL.md +42 -0
  1267. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/buyer-qualification-questions.md +16 -0
  1268. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/buyer-qualification.md +59 -0
  1269. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/buyer-scripts.md +63 -0
  1270. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/buyer-working-scripts.md +54 -0
  1271. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/feedback-collection.md +42 -0
  1272. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/offer-capture.md +38 -0
  1273. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/viewing-booking.md +32 -0
  1274. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-management/references/viewing-management.md +52 -0
  1275. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/SKILL.md +407 -0
  1276. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/care-fees-guide.md +68 -0
  1277. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/divorce-sales-guide.md +61 -0
  1278. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/downsizing-guide.md +45 -0
  1279. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/first-time-buyers.md +92 -0
  1280. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/first-time-sellers.md +78 -0
  1281. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/probate-guide.md +53 -0
  1282. package/payload/premium-plugins/real-agency/plugins/buyers/skills/buyer-seller-guides/references/upsizing-guide.md +41 -0
  1283. package/payload/premium-plugins/real-agency/plugins/buyers/skills/property-enquiry/SKILL.md +126 -0
  1284. package/payload/premium-plugins/real-agency/plugins/buyers/skills/viewing-management/SKILL.md +111 -0
  1285. package/payload/premium-plugins/real-agency/plugins/estate-business/PLUGIN.md +34 -0
  1286. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/SKILL.md +133 -0
  1287. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/buy-back-your-time.md +37 -0
  1288. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/firewave-gost-scorecards.md +14 -0
  1289. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/keller-org-model.md +17 -0
  1290. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/lencioni-team-models.md +22 -0
  1291. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/listing-management-system.md +11 -0
  1292. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/net-figure-form.md +11 -0
  1293. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/serhant-bizinbox-notes.md +13 -0
  1294. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/team-roles-commission.md +14 -0
  1295. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/va-2026-ops.md +43 -0
  1296. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-growth/references/wingman-structure.md +13 -0
  1297. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/SKILL.md +32 -0
  1298. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/crm-systems.md +57 -0
  1299. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/hiring-guide.md +59 -0
  1300. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/impact-framework.md +47 -0
  1301. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/minutes-equal-money.md +55 -0
  1302. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/business-operations/references/team-management.md +48 -0
  1303. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/SKILL.md +52 -0
  1304. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/12-reasons.md +39 -0
  1305. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/95-5-system.md +66 -0
  1306. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/agent-attraction-scripts.md +90 -0
  1307. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/business-partnership.md +92 -0
  1308. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/exp-model-overview.md +66 -0
  1309. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/model-comparison.md +66 -0
  1310. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/exp-partnership/references/revenue-share-explained.md +57 -0
  1311. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/SKILL.md +117 -0
  1312. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/attraction-agent-notes.md +31 -0
  1313. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/attraction-agent.md +58 -0
  1314. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/authenticity-boundaries.md +28 -0
  1315. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/become-a-brand-leader-notes.md +19 -0
  1316. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/blast-formula.md +42 -0
  1317. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/brand-leader.md +48 -0
  1318. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/brand-strategy-system.md +59 -0
  1319. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/content-engine.md +49 -0
  1320. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/firewave-blast-and-blogging.md +23 -0
  1321. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/gary-v-content.md +52 -0
  1322. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/gary-v-principles.md +20 -0
  1323. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/oversubscribed-positioning.md +18 -0
  1324. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/platforms.md +41 -0
  1325. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/priestley-oversubscribed.md +54 -0
  1326. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/storeys-style-examples.md +25 -0
  1327. package/payload/premium-plugins/real-agency/plugins/estate-business/skills/personal-branding/references/visual-identity.md +27 -0
  1328. package/payload/premium-plugins/real-agency/plugins/estate-coaching/PLUGIN.md +55 -0
  1329. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/SKILL.md +371 -0
  1330. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/references/atomic-habits.md +52 -0
  1331. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/references/daily-routine-scorecard.md +104 -0
  1332. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/references/hp6-model.md +63 -0
  1333. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/agent-performance/references/twelve-week-year.md +71 -0
  1334. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/bespoke-coaching/SKILL.md +36 -0
  1335. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/bespoke-coaching/references/coaching-boundaries.md +56 -0
  1336. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/bespoke-coaching/references/feedback-framework.md +61 -0
  1337. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/bespoke-coaching/references/performance-framework.md +109 -0
  1338. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/SKILL.md +421 -0
  1339. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/references/coaching-exercises.md +86 -0
  1340. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/references/goal-setting.md +78 -0
  1341. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/references/one-to-one-framework.md +92 -0
  1342. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/coaching-toolkit/references/soi-workbook.md +103 -0
  1343. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/SKILL.md +410 -0
  1344. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/agent-training-guide.md +70 -0
  1345. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/business-in-a-box.md +72 -0
  1346. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/buyers-guide.md +53 -0
  1347. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/codo-method.md +72 -0
  1348. package/payload/premium-plugins/real-agency/plugins/estate-coaching/skills/serhant-training/references/website-planning-guide.md +79 -0
  1349. package/payload/premium-plugins/real-agency/plugins/estate-onboarding/PLUGIN.md +31 -0
  1350. package/payload/premium-plugins/real-agency/plugins/estate-onboarding/skills/bootstrap/SKILL.md +26 -0
  1351. package/payload/premium-plugins/real-agency/plugins/estate-onboarding/skills/bootstrap/references/onboarding-flow.md +63 -0
  1352. package/payload/premium-plugins/real-agency/plugins/estate-sales/PLUGIN.md +34 -0
  1353. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/SKILL.md +35 -0
  1354. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/deal-saving.md +47 -0
  1355. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/negotiation-deep-guide.md +64 -0
  1356. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/negotiation-prep-principles.md +29 -0
  1357. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/negotiation-techniques.md +42 -0
  1358. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/negotiation/references/offer-presentation.md +43 -0
  1359. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-closer/SKILL.md +24 -0
  1360. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-closer/references/serhant-emotion-stages.md +36 -0
  1361. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/SKILL.md +30 -0
  1362. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/chris-voss-discovery.md +88 -0
  1363. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/firewave-gost-journey.md +68 -0
  1364. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/phil-jones-openers.md +78 -0
  1365. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/pre-listing-checklist.md +77 -0
  1366. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/serhant-improv.md +22 -0
  1367. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/tom-ferry-discovery.md +103 -0
  1368. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-discovery/references/vendor-motivation-competitor.md +52 -0
  1369. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/SKILL.md +29 -0
  1370. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/chris-voss-negotiation.md +70 -0
  1371. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/phil-jones-price-words.md +40 -0
  1372. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/serhant-negotiation-plus.md +55 -0
  1373. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/tom-panos-commission-pricing.md +57 -0
  1374. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-negotiation/references/tony-morris-questioning.md +54 -0
  1375. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-progression/SKILL.md +27 -0
  1376. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-progression/references/conveyancing-guide.md +54 -0
  1377. package/payload/premium-plugins/real-agency/plugins/estate-sales/skills/sales-progression/references/transaction-tracking.md +66 -0
  1378. package/payload/premium-plugins/real-agency/plugins/estate-teaching/PLUGIN.md +31 -0
  1379. package/payload/premium-plugins/real-agency/plugins/estate-teaching/skills/content-directory/SKILL.md +39 -0
  1380. package/payload/premium-plugins/real-agency/plugins/estate-teaching/skills/content-directory/references/module-delivery.md +65 -0
  1381. package/payload/premium-plugins/real-agency/plugins/estate-teaching/skills/content-directory/references/progress-tracking.md +47 -0
  1382. package/payload/premium-plugins/real-agency/plugins/leads/PLUGIN.md +32 -0
  1383. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/SKILL.md +137 -0
  1384. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/buyer-search-letter.md +28 -0
  1385. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/buyer-search-letters.md +37 -0
  1386. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/database-reactivation.md +30 -0
  1387. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/email-nurture-sequences.md +45 -0
  1388. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/facebook-referrals.md +30 -0
  1389. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/firewave-email-nurture-sequences.md +41 -0
  1390. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/keller-33-touch.md +34 -0
  1391. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/neighbour-letters.md +31 -0
  1392. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/neighbour-notification-letter.md +20 -0
  1393. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/ofi-follow-up-dialogue.md +22 -0
  1394. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/ofi-follow-up.md +26 -0
  1395. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/serhant-three-fs-plus.md +21 -0
  1396. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/sharran-10x10x10.md +18 -0
  1397. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/sms-templates.md +40 -0
  1398. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/sphere-of-influence-notes.md +34 -0
  1399. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/sphere-of-influence.md +60 -0
  1400. package/payload/premium-plugins/real-agency/plugins/leads/skills/lead-nurturing/references/tom-panos-sms-templates.md +59 -0
  1401. package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/SKILL.md +33 -0
  1402. package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/references/database-matching.md +30 -0
  1403. package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/references/database-value.md +53 -0
  1404. package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/references/prospecting-dialogues.md +24 -0
  1405. package/payload/premium-plugins/real-agency/plugins/leads/skills/prospecting/references/reactivation.md +34 -0
  1406. package/payload/premium-plugins/real-agency/plugins/listings/PLUGIN.md +33 -0
  1407. package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/SKILL.md +28 -0
  1408. package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/references/kerb-appeal.md +38 -0
  1409. package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/references/photo-day.md +59 -0
  1410. package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/references/situational-tips.md +50 -0
  1411. package/payload/premium-plugins/real-agency/plugins/listings/skills/home-preparation/references/staging-guide.md +52 -0
  1412. package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/SKILL.md +286 -0
  1413. package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/booking-script.md +51 -0
  1414. package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/objection-scripts.md +193 -0
  1415. package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/penhaul-presentation.md +123 -0
  1416. package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/pre-listing-kit.md +139 -0
  1417. package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/set-to-sell.md +55 -0
  1418. package/payload/premium-plugins/real-agency/plugins/listings/skills/listing-presentation/references/sharran-frameworks.md +107 -0
  1419. package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/SKILL.md +337 -0
  1420. package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/auction-report-template.md +41 -0
  1421. package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/coming-soon-campaign.md +43 -0
  1422. package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/direct-mail-templates.md +121 -0
  1423. package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/eoi-form-template.md +62 -0
  1424. package/payload/premium-plugins/real-agency/plugins/listings/skills/property-marketing/references/monthly-scorecard.md +63 -0
  1425. package/payload/premium-plugins/real-agency/plugins/loop/PLUGIN.md +73 -0
  1426. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/index.d.ts +2 -0
  1427. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/index.d.ts.map +1 -0
  1428. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/index.js +293 -0
  1429. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/index.js.map +1 -0
  1430. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/crypto.d.ts +10 -0
  1431. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/crypto.d.ts.map +1 -0
  1432. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/crypto.js +88 -0
  1433. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/crypto.js.map +1 -0
  1434. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/loop-api.d.ts +82 -0
  1435. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/loop-api.d.ts.map +1 -0
  1436. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/loop-api.js +427 -0
  1437. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/loop-api.js.map +1 -0
  1438. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/neo4j.d.ts +5 -0
  1439. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/neo4j.d.ts.map +1 -0
  1440. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/neo4j.js +40 -0
  1441. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/lib/neo4j.js.map +1 -0
  1442. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/customer-preferences.d.ts +10 -0
  1443. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/customer-preferences.d.ts.map +1 -0
  1444. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/customer-preferences.js +24 -0
  1445. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/customer-preferences.js.map +1 -0
  1446. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/feedback.d.ts +16 -0
  1447. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/feedback.d.ts.map +1 -0
  1448. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/feedback.js +35 -0
  1449. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/feedback.js.map +1 -0
  1450. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-deregister.d.ts +5 -0
  1451. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-deregister.d.ts.map +1 -0
  1452. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-deregister.js +19 -0
  1453. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-deregister.js.map +1 -0
  1454. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-list.d.ts +4 -0
  1455. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-list.d.ts.map +1 -0
  1456. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-list.js +14 -0
  1457. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-list.js.map +1 -0
  1458. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-register.d.ts +9 -0
  1459. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-register.d.ts.map +1 -0
  1460. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-register.js +60 -0
  1461. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/key-register.js.map +1 -0
  1462. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts +13 -0
  1463. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-enquiry.d.ts.map +1 -0
  1464. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-enquiry.js +41 -0
  1465. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-enquiry.js.map +1 -0
  1466. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-batch.d.ts +9 -0
  1467. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-batch.d.ts.map +1 -0
  1468. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-batch.js +16 -0
  1469. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-batch.js.map +1 -0
  1470. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-request.d.ts +15 -0
  1471. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-request.d.ts.map +1 -0
  1472. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-request.js +11 -0
  1473. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match-request.js.map +1 -0
  1474. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match.d.ts +10 -0
  1475. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match.d.ts.map +1 -0
  1476. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match.js +39 -0
  1477. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/marketing-match.js.map +1 -0
  1478. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-detail.d.ts +9 -0
  1479. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-detail.d.ts.map +1 -0
  1480. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-detail.js +125 -0
  1481. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-detail.js.map +1 -0
  1482. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-search.d.ts +18 -0
  1483. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-search.d.ts.map +1 -0
  1484. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-search.js +87 -0
  1485. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/people-search.js.map +1 -0
  1486. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-detail.d.ts +10 -0
  1487. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-detail.d.ts.map +1 -0
  1488. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-detail.js +82 -0
  1489. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-detail.js.map +1 -0
  1490. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-listed.d.ts +12 -0
  1491. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-listed.d.ts.map +1 -0
  1492. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-listed.js +32 -0
  1493. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-listed.js.map +1 -0
  1494. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-request.d.ts +15 -0
  1495. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-request.d.ts.map +1 -0
  1496. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-request.js +11 -0
  1497. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-request.js.map +1 -0
  1498. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-search.d.ts +16 -0
  1499. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-search.d.ts.map +1 -0
  1500. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-search.js +41 -0
  1501. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/property-search.js.map +1 -0
  1502. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/supplier.d.ts +13 -0
  1503. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/supplier.d.ts.map +1 -0
  1504. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/supplier.js +49 -0
  1505. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/supplier.js.map +1 -0
  1506. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-availability.d.ts +7 -0
  1507. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-availability.d.ts.map +1 -0
  1508. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-availability.js +19 -0
  1509. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-availability.js.map +1 -0
  1510. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-info.d.ts +5 -0
  1511. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-info.d.ts.map +1 -0
  1512. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-info.js +32 -0
  1513. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/team-info.js.map +1 -0
  1514. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-create.d.ts +14 -0
  1515. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-create.d.ts.map +1 -0
  1516. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-create.js +11 -0
  1517. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-create.js.map +1 -0
  1518. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-detail.d.ts +9 -0
  1519. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-detail.d.ts.map +1 -0
  1520. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-detail.js +85 -0
  1521. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-detail.js.map +1 -0
  1522. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-search.d.ts +13 -0
  1523. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-search.d.ts.map +1 -0
  1524. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-search.js +44 -0
  1525. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-search.js.map +1 -0
  1526. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-update.d.ts +14 -0
  1527. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-update.d.ts.map +1 -0
  1528. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-update.js +18 -0
  1529. package/payload/premium-plugins/real-agency/plugins/loop/mcp/dist/tools/viewing-update.js.map +1 -0
  1530. package/payload/premium-plugins/real-agency/plugins/loop/mcp/package-lock.json +2549 -0
  1531. package/payload/premium-plugins/real-agency/plugins/loop/mcp/package.json +21 -0
  1532. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/__tests__/loop-swagger.snapshot.json +26467 -0
  1533. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/__tests__/swagger-write-coverage.test.ts +153 -0
  1534. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/index.ts +444 -0
  1535. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/lib/crypto.ts +105 -0
  1536. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/lib/loop-api.ts +604 -0
  1537. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/lib/neo4j.ts +51 -0
  1538. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/customer-preferences.ts +66 -0
  1539. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/feedback.ts +86 -0
  1540. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/key-deregister.ts +27 -0
  1541. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/key-list.ts +19 -0
  1542. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/key-register.ts +95 -0
  1543. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/marketing-enquiry.ts +113 -0
  1544. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/marketing-match-batch.ts +53 -0
  1545. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/marketing-match-request.ts +42 -0
  1546. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/marketing-match.ts +84 -0
  1547. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/people-detail.ts +245 -0
  1548. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/people-search.ts +180 -0
  1549. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/property-detail.ts +145 -0
  1550. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/property-listed.ts +88 -0
  1551. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/property-request.ts +42 -0
  1552. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/property-search.ts +92 -0
  1553. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/supplier.ts +129 -0
  1554. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/team-availability.ts +52 -0
  1555. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/team-info.ts +95 -0
  1556. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/viewing-create.ts +41 -0
  1557. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/viewing-detail.ts +171 -0
  1558. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/viewing-search.ts +92 -0
  1559. package/payload/premium-plugins/real-agency/plugins/loop/mcp/src/tools/viewing-update.ts +53 -0
  1560. package/payload/premium-plugins/real-agency/plugins/loop/mcp/tsconfig.json +20 -0
  1561. package/payload/premium-plugins/real-agency/plugins/loop/mcp/vitest.config.ts +9 -0
  1562. package/payload/premium-plugins/real-agency/plugins/vendors/PLUGIN.md +34 -0
  1563. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/SKILL.md +42 -0
  1564. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/fee-protection-and-agenda.md +28 -0
  1565. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/listing-scripts.md +44 -0
  1566. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/negotiation-deep-guide.md +70 -0
  1567. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/price-alignment-scripts.md +33 -0
  1568. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/price-alignment.md +34 -0
  1569. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/scenario-scripts.md +38 -0
  1570. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/seller-engagement.md +51 -0
  1571. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/valuation-booking.md +76 -0
  1572. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/vendor-scripts.md +63 -0
  1573. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-communication/references/vendor-updates.md +41 -0
  1574. package/payload/premium-plugins/real-agency/plugins/vendors/skills/vendor-updates/SKILL.md +153 -0
  1575. package/payload/premium-plugins/teaching/PLUGIN.md +57 -0
  1576. package/payload/premium-plugins/teaching/skills/interactive-tutor/SKILL.md +59 -0
  1577. package/payload/premium-plugins/teaching/skills/interactive-tutor/references/assessment.md +70 -0
  1578. package/payload/premium-plugins/teaching/skills/interactive-tutor/references/classroom-conduct.md +43 -0
  1579. package/payload/premium-plugins/teaching/skills/interactive-tutor/references/teaching-modes.md +83 -0
  1580. package/payload/premium-plugins/teaching/skills/lesson-planner/SKILL.md +48 -0
  1581. package/payload/premium-plugins/teaching/skills/lesson-planner/references/context-gathering.md +41 -0
  1582. package/payload/premium-plugins/teaching/skills/lesson-planner/references/plan-structure.md +94 -0
  1583. package/payload/premium-plugins/teaching/skills/study-pack-builder/SKILL.md +52 -0
  1584. package/payload/premium-plugins/teaching/skills/study-pack-builder/references/disaggregation.md +49 -0
  1585. package/payload/premium-plugins/teaching/skills/study-pack-builder/references/materials.md +116 -0
  1586. package/payload/premium-plugins/writer-craft/PLUGIN.md +87 -0
  1587. package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +92 -0
  1588. package/payload/premium-plugins/writer-craft/skills/citation-style/SKILL.md +94 -0
  1589. package/payload/premium-plugins/writer-craft/skills/citation-style/references/book-and-chapter-models.md +77 -0
  1590. package/payload/premium-plugins/writer-craft/skills/citation-style/references/citation-rules.md +103 -0
  1591. package/payload/premium-plugins/writer-craft/skills/citation-style/references/journal-article-models.md +74 -0
  1592. package/payload/premium-plugins/writer-craft/skills/citation-style/references/other-source-models.md +146 -0
  1593. package/payload/premium-plugins/writer-craft/skills/citation-style/references/reference-list-rules.md +70 -0
  1594. package/payload/premium-plugins/writer-craft/skills/editorial-practice/SKILL.md +108 -0
  1595. package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/copyediting.md +73 -0
  1596. package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/developmental-editing.md +85 -0
  1597. package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/genre-specific-editing.md +78 -0
  1598. package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/line-editing.md +55 -0
  1599. package/payload/premium-plugins/writer-craft/skills/editorial-practice/references/self-editing.md +89 -0
  1600. package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/SKILL.md +114 -0
  1601. package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/references/audience-analysis.md +73 -0
  1602. package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/references/crafting-persuasive-story.md +76 -0
  1603. package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/references/persuasion-case-studies.md +67 -0
  1604. package/payload/premium-plugins/writer-craft/skills/persuasive-storytelling/references/transformation-framework.md +86 -0
  1605. package/payload/premium-plugins/writer-craft/skills/point-of-view/SKILL.md +97 -0
  1606. package/payload/premium-plugins/writer-craft/skills/point-of-view/references/indirect-narration.md +72 -0
  1607. package/payload/premium-plugins/writer-craft/skills/point-of-view/references/pov-types-and-voice.md +91 -0
  1608. package/payload/premium-plugins/writer-craft/skills/point-of-view/references/protagonist-filter.md +71 -0
  1609. package/payload/premium-plugins/writer-craft/skills/point-of-view/references/tense-and-person.md +85 -0
  1610. package/payload/premium-plugins/writer-craft/skills/prose-craft/SKILL.md +100 -0
  1611. package/payload/premium-plugins/writer-craft/skills/prose-craft/references/punctuation-and-grammar.md +72 -0
  1612. package/payload/premium-plugins/writer-craft/skills/prose-craft/references/repetition.md +71 -0
  1613. package/payload/premium-plugins/writer-craft/skills/prose-craft/references/sound-and-rhythm.md +64 -0
  1614. package/payload/premium-plugins/writer-craft/skills/prose-craft/references/word-economy.md +93 -0
  1615. package/payload/premium-plugins/writer-craft/skills/reader-engagement/SKILL.md +100 -0
  1616. package/payload/premium-plugins/writer-craft/skills/reader-engagement/references/cause-effect-setup-payoff.md +79 -0
  1617. package/payload/premium-plugins/writer-craft/skills/reader-engagement/references/conflict-escalation.md +81 -0
  1618. package/payload/premium-plugins/writer-craft/skills/reader-engagement/references/hooking-readers.md +67 -0
  1619. package/payload/premium-plugins/writer-craft/skills/reader-engagement/references/neurochemistry-of-engagement.md +94 -0
  1620. package/payload/premium-plugins/writer-craft/skills/review-manuscript/SKILL.md +111 -0
  1621. package/payload/premium-plugins/writer-craft/skills/review-manuscript/references/review-manuscript-checklist.md +119 -0
  1622. package/payload/premium-plugins/writer-craft/skills/review-prose/SKILL.md +99 -0
  1623. package/payload/premium-plugins/writer-craft/skills/review-prose/references/prose-review-checklist.md +112 -0
  1624. package/payload/premium-plugins/writer-craft/skills/review-scene/SKILL.md +99 -0
  1625. package/payload/premium-plugins/writer-craft/skills/review-scene/references/scene-analysis-framework.md +95 -0
  1626. package/payload/premium-plugins/writer-craft/skills/story-architecture/SKILL.md +106 -0
  1627. package/payload/premium-plugins/writer-craft/skills/story-architecture/references/blueprinting-and-scene-cards.md +118 -0
  1628. package/payload/premium-plugins/writer-craft/skills/story-architecture/references/inner-issue-and-protagonist-goal.md +66 -0
  1629. package/payload/premium-plugins/writer-craft/skills/story-architecture/references/misbelief-desire-worldview.md +87 -0
  1630. package/payload/premium-plugins/writer-craft/skills/story-architecture/references/origin-scenes-and-escalation.md +82 -0
  1631. package/payload/premium-plugins/writer-craft/skills/story-blueprint/SKILL.md +133 -0
  1632. package/payload/premium-plugins/writer-craft/skills/story-blueprint/references/blueprinting-exercises.md +118 -0
  1633. package/payload/premium-plugins/writer-craft/skills/story-blueprint/references/blueprinting-process.md +128 -0
  1634. package/payload/server/adminuser-self-heal-QAWOZ3JV.js +45 -0
  1635. package/payload/server/chunk-5FM432JB.js +4148 -0
  1636. package/payload/server/chunk-6S5JTXAN.js +1544 -0
  1637. package/payload/server/chunk-JSBRDJBE.js +30 -0
  1638. package/payload/server/chunk-RNW625CL.js +759 -0
  1639. package/payload/server/cloudflare-task-tracker-VC7QVU5H.js +22 -0
  1640. package/payload/server/maxy-edge.js +1021 -0
  1641. package/payload/server/package.json +13 -0
  1642. package/payload/server/public/assets/Checkbox-C6ZCsPvl.js +1 -0
  1643. package/payload/server/public/assets/_baseFor-BHtDrjIo.js +1 -0
  1644. package/payload/server/public/assets/admin-CWMpccrR.css +1 -0
  1645. package/payload/server/public/assets/admin-DVGJmN-k.js +216 -0
  1646. package/payload/server/public/assets/arc-DMDAZHAN.js +1 -0
  1647. package/payload/server/public/assets/architecture-YZFGNWBL-COhEvUpo.js +1 -0
  1648. package/payload/server/public/assets/architectureDiagram-Q4EWVU46-DwN6H0y2.js +36 -0
  1649. package/payload/server/public/assets/array-DetWRiSa.js +1 -0
  1650. package/payload/server/public/assets/blockDiagram-DXYQGD6D-TUk_F7H6.js +132 -0
  1651. package/payload/server/public/assets/c4Diagram-AHTNJAMY-CTjGko0X.js +10 -0
  1652. package/payload/server/public/assets/channel-Cv-65bLZ.js +1 -0
  1653. package/payload/server/public/assets/chunk-2KRD3SAO-Di4bO8ir.js +1 -0
  1654. package/payload/server/public/assets/chunk-336JU56O-DulT46bV.js +2 -0
  1655. package/payload/server/public/assets/chunk-426QAEUC-BwKj8yqp.js +1 -0
  1656. package/payload/server/public/assets/chunk-4BX2VUAB-DyEhFk-Z.js +1 -0
  1657. package/payload/server/public/assets/chunk-4TB4RGXK-CewO8YaZ.js +206 -0
  1658. package/payload/server/public/assets/chunk-55IACEB6-BRJOZLpU.js +1 -0
  1659. package/payload/server/public/assets/chunk-5FUZZQ4R-B3IWYz-k.js +62 -0
  1660. package/payload/server/public/assets/chunk-5PVQY5BW-DyiDEtXY.js +2 -0
  1661. package/payload/server/public/assets/chunk-67CJDMHE-BG6-9r6c.js +1 -0
  1662. package/payload/server/public/assets/chunk-7N4EOEYR-BvMbitX7.js +1 -0
  1663. package/payload/server/public/assets/chunk-AA7GKIK3-CE8mGBD5.js +1 -0
  1664. package/payload/server/public/assets/chunk-BSJP7CBP-DP7LTBll.js +1 -0
  1665. package/payload/server/public/assets/chunk-CIAEETIT-VfnIdN-h.js +1 -0
  1666. package/payload/server/public/assets/chunk-DD-I1_y5.js +1 -0
  1667. package/payload/server/public/assets/chunk-EDXVE4YY-B3u7wU36.js +1 -0
  1668. package/payload/server/public/assets/chunk-ENJZ2VHE-BesS5YY4.js +10 -0
  1669. package/payload/server/public/assets/chunk-FMBD7UC4-w-yBZsN2.js +15 -0
  1670. package/payload/server/public/assets/chunk-FOC6F5B3-f9cFywhd.js +1 -0
  1671. package/payload/server/public/assets/chunk-ICPOFSXX-C5_hbB6H.js +122 -0
  1672. package/payload/server/public/assets/chunk-K5T4RW27-B_ZUrFUq.js +94 -0
  1673. package/payload/server/public/assets/chunk-KGLVRYIC-CcWTvRlI.js +1 -0
  1674. package/payload/server/public/assets/chunk-LIHQZDEY-D-5-peQw.js +1 -0
  1675. package/payload/server/public/assets/chunk-ORNJ4GCN-B4Z5L25I.js +1 -0
  1676. package/payload/server/public/assets/chunk-OYMX7WX6-CrL4rhBa.js +231 -0
  1677. package/payload/server/public/assets/chunk-QZHKN3VN-CWh_0JsP.js +1 -0
  1678. package/payload/server/public/assets/chunk-U2HBQHQK-CQpbcqRS.js +70 -0
  1679. package/payload/server/public/assets/chunk-X2U36JSP-1HG7T4gX.js +1 -0
  1680. package/payload/server/public/assets/chunk-XPW4576I-m1Y_r88I.js +32 -0
  1681. package/payload/server/public/assets/chunk-YZCP3GAM-boN5wjX9.js +1 -0
  1682. package/payload/server/public/assets/chunk-ZZ45TVLE-D6CiPO0Q.js +1 -0
  1683. package/payload/server/public/assets/classDiagram-6PBFFD2Q-wQ2-BRyB.js +1 -0
  1684. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-B_YLNNHy.js +1 -0
  1685. package/payload/server/public/assets/clone-VLK-GPZZ.js +1 -0
  1686. package/payload/server/public/assets/cormorant-cyrillic-300-normal-CzPHYadL.woff +0 -0
  1687. package/payload/server/public/assets/cormorant-cyrillic-300-normal-DFUoTmrg.woff2 +0 -0
  1688. package/payload/server/public/assets/cormorant-cyrillic-400-normal-C8QS47vb.woff2 +0 -0
  1689. package/payload/server/public/assets/cormorant-cyrillic-400-normal-D3EsxgFc.woff +0 -0
  1690. package/payload/server/public/assets/cormorant-cyrillic-500-normal-B7dJQtg-.woff +0 -0
  1691. package/payload/server/public/assets/cormorant-cyrillic-500-normal-BLlg2W5x.woff2 +0 -0
  1692. package/payload/server/public/assets/cormorant-cyrillic-ext-300-normal-BXl3lXsi.woff2 +0 -0
  1693. package/payload/server/public/assets/cormorant-cyrillic-ext-300-normal-DmxSOTe3.woff +0 -0
  1694. package/payload/server/public/assets/cormorant-cyrillic-ext-400-normal-Bgrpe4p1.woff +0 -0
  1695. package/payload/server/public/assets/cormorant-cyrillic-ext-400-normal-BlcaxZtM.woff2 +0 -0
  1696. package/payload/server/public/assets/cormorant-cyrillic-ext-500-normal-CdQuyvtc.woff +0 -0
  1697. package/payload/server/public/assets/cormorant-cyrillic-ext-500-normal-pZw22qtS.woff2 +0 -0
  1698. package/payload/server/public/assets/cormorant-latin-300-normal-CJ5dfen0.woff2 +0 -0
  1699. package/payload/server/public/assets/cormorant-latin-300-normal-DQZObO_3.woff +0 -0
  1700. package/payload/server/public/assets/cormorant-latin-400-normal-BGH8Vunh.woff2 +0 -0
  1701. package/payload/server/public/assets/cormorant-latin-400-normal-C3_-2Ua-.woff +0 -0
  1702. package/payload/server/public/assets/cormorant-latin-500-normal-Dj3SQ6fR.woff +0 -0
  1703. package/payload/server/public/assets/cormorant-latin-500-normal-EBdSCOD3.woff2 +0 -0
  1704. package/payload/server/public/assets/cormorant-latin-ext-300-normal-CkiUx0UG.woff +0 -0
  1705. package/payload/server/public/assets/cormorant-latin-ext-300-normal-De3D72RL.woff2 +0 -0
  1706. package/payload/server/public/assets/cormorant-latin-ext-400-normal-DuQ88yz3.woff2 +0 -0
  1707. package/payload/server/public/assets/cormorant-latin-ext-400-normal-DuXFa1Dr.woff +0 -0
  1708. package/payload/server/public/assets/cormorant-latin-ext-500-normal-AH9qog1s.woff2 +0 -0
  1709. package/payload/server/public/assets/cormorant-latin-ext-500-normal-DAuUCO41.woff +0 -0
  1710. package/payload/server/public/assets/cormorant-vietnamese-300-normal-BVqIp_mg.woff2 +0 -0
  1711. package/payload/server/public/assets/cormorant-vietnamese-300-normal-CEMS9Pw-.woff +0 -0
  1712. package/payload/server/public/assets/cormorant-vietnamese-400-normal-C-RiYxEf.woff2 +0 -0
  1713. package/payload/server/public/assets/cormorant-vietnamese-400-normal-DmUuA7Y2.woff +0 -0
  1714. package/payload/server/public/assets/cormorant-vietnamese-500-normal-DsPuwQHi.woff2 +0 -0
  1715. package/payload/server/public/assets/cormorant-vietnamese-500-normal-tGBW_mI7.woff +0 -0
  1716. package/payload/server/public/assets/cose-bilkent-S5V4N54A-BhtgY3T7.js +1 -0
  1717. package/payload/server/public/assets/cytoscape.esm-C9yNhe1u.js +321 -0
  1718. package/payload/server/public/assets/dagre-CncXYNX1.js +1 -0
  1719. package/payload/server/public/assets/dagre-KV5264BT-C0CcgCHW.js +4 -0
  1720. package/payload/server/public/assets/data-Bt4Wsocg.js +1 -0
  1721. package/payload/server/public/assets/defaultLocale-_WRwicXn.js +1 -0
  1722. package/payload/server/public/assets/diagram-5BDNPKRD-CkUlWbsI.js +10 -0
  1723. package/payload/server/public/assets/diagram-G4DWMVQ6-DVZBG1Ul.js +24 -0
  1724. package/payload/server/public/assets/diagram-MMDJMWI5-BIb06ZkK.js +43 -0
  1725. package/payload/server/public/assets/diagram-TYMM5635-BcT0_WR8.js +24 -0
  1726. package/payload/server/public/assets/dist-Bd4S37oi.js +1 -0
  1727. package/payload/server/public/assets/dm-sans-latin-400-normal-BwCSEQnW.woff +0 -0
  1728. package/payload/server/public/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 +0 -0
  1729. package/payload/server/public/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 +0 -0
  1730. package/payload/server/public/assets/dm-sans-latin-500-normal-Dr3UlScf.woff +0 -0
  1731. package/payload/server/public/assets/dm-sans-latin-ext-400-normal-BjWJ59Pq.woff +0 -0
  1732. package/payload/server/public/assets/dm-sans-latin-ext-400-normal-BtiwyxMk.woff2 +0 -0
  1733. package/payload/server/public/assets/dm-sans-latin-ext-500-normal-BJfUCQsA.woff2 +0 -0
  1734. package/payload/server/public/assets/dm-sans-latin-ext-500-normal-DR84L5F-.woff +0 -0
  1735. package/payload/server/public/assets/erDiagram-SMLLAGMA-BHk6lxIT.js +85 -0
  1736. package/payload/server/public/assets/flatten-BsWEYbBB.js +1 -0
  1737. package/payload/server/public/assets/flowDiagram-DWJPFMVM-CjeJn490.js +162 -0
  1738. package/payload/server/public/assets/ganttDiagram-T4ZO3ILL-BGYvX3Lv.js +292 -0
  1739. package/payload/server/public/assets/gitGraph-7Q5UKJZL-DeTNsAO0.js +1 -0
  1740. package/payload/server/public/assets/gitGraphDiagram-UUTBAWPF-Br4WLGzW.js +106 -0
  1741. package/payload/server/public/assets/graph-CRSLozxc.js +1 -0
  1742. package/payload/server/public/assets/graph-labels-CQyZQ0u6.js +1 -0
  1743. package/payload/server/public/assets/graphlib-BWd9sMeP.js +1 -0
  1744. package/payload/server/public/assets/info-OMHHGYJF-DJJ9GlS6.js +1 -0
  1745. package/payload/server/public/assets/infoDiagram-42DDH7IO-BjZeQoNZ.js +2 -0
  1746. package/payload/server/public/assets/init-sTEcj9YX.js +1 -0
  1747. package/payload/server/public/assets/isEmpty-BWl67LAZ.js +1 -0
  1748. package/payload/server/public/assets/ishikawaDiagram-UXIWVN3A-POMae6Ni.js +70 -0
  1749. package/payload/server/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  1750. package/payload/server/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  1751. package/payload/server/public/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  1752. package/payload/server/public/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  1753. package/payload/server/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  1754. package/payload/server/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  1755. package/payload/server/public/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  1756. package/payload/server/public/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  1757. package/payload/server/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  1758. package/payload/server/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  1759. package/payload/server/public/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  1760. package/payload/server/public/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  1761. package/payload/server/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  1762. package/payload/server/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  1763. package/payload/server/public/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  1764. package/payload/server/public/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  1765. package/payload/server/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  1766. package/payload/server/public/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  1767. package/payload/server/public/assets/journeyDiagram-VCZTEJTY-ledtLV6x.js +139 -0
  1768. package/payload/server/public/assets/jsx-runtime-DvanDPKm.css +1 -0
  1769. package/payload/server/public/assets/jsx-runtime-vPsBTwUp.js +9 -0
  1770. package/payload/server/public/assets/kanban-definition-6JOO6SKY-DI0T4W9z.js +89 -0
  1771. package/payload/server/public/assets/katex-s61Rgv6l.js +257 -0
  1772. package/payload/server/public/assets/lib-W5Jcz4p8.js +33 -0
  1773. package/payload/server/public/assets/line-D1281H12.js +1 -0
  1774. package/payload/server/public/assets/linear-0O14Y6uf.js +1 -0
  1775. package/payload/server/public/assets/mermaid-parser.core-Dt95U7zk.js +4 -0
  1776. package/payload/server/public/assets/mermaid.core-BuYSs1fU.js +11 -0
  1777. package/payload/server/public/assets/mindmap-definition-QFDTVHPH-CFE1lmfX.js +96 -0
  1778. package/payload/server/public/assets/ordinal-krseTxxN.js +1 -0
  1779. package/payload/server/public/assets/packet-4T2RLAQJ-CGbvGkvF.js +1 -0
  1780. package/payload/server/public/assets/page-CSUcuVW0.js +1 -0
  1781. package/payload/server/public/assets/page-TARBO-Yr.js +50 -0
  1782. package/payload/server/public/assets/path-B0Ik7Tu9.js +1 -0
  1783. package/payload/server/public/assets/pie-ZZUOXDRM-BZy8rjFn.js +1 -0
  1784. package/payload/server/public/assets/pieDiagram-DEJITSTG-BSd9xa7v.js +30 -0
  1785. package/payload/server/public/assets/public-5r6aRXrb.js +8 -0
  1786. package/payload/server/public/assets/quadrantDiagram-34T5L4WZ-xYehPVw5.js +7 -0
  1787. package/payload/server/public/assets/radar-PYXPWWZC-DcfWIVXr.js +1 -0
  1788. package/payload/server/public/assets/reduce-tk-xY6Fv.js +1 -0
  1789. package/payload/server/public/assets/requirementDiagram-MS252O5E-C6n77V1S.js +84 -0
  1790. package/payload/server/public/assets/rough.esm-DKRO8IF-.js +1 -0
  1791. package/payload/server/public/assets/sankeyDiagram-XADWPNL6-CWIfeO1M.js +10 -0
  1792. package/payload/server/public/assets/sequenceDiagram-FGHM5R23-DDv2DuMo.js +157 -0
  1793. package/payload/server/public/assets/src-B6XdH6xq.js +1 -0
  1794. package/payload/server/public/assets/stateDiagram-FHFEXIEX-BPZdmsww.js +1 -0
  1795. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-33eC4TwE.js +1 -0
  1796. package/payload/server/public/assets/timeline-definition-GMOUNBTQ-BpRT_wSX.js +120 -0
  1797. package/payload/server/public/assets/treeView-SZITEDCU-3WugwVdj.js +1 -0
  1798. package/payload/server/public/assets/treemap-W4RFUUIX-Cf5mDLlu.js +1 -0
  1799. package/payload/server/public/assets/vennDiagram-DHZGUBPP-BG8ubucH.js +34 -0
  1800. package/payload/server/public/assets/wardley-RL74JXVD-Bv4md4b3.js +1 -0
  1801. package/payload/server/public/assets/wardleyDiagram-NUSXRM2D-D4E7PU1B.js +20 -0
  1802. package/payload/server/public/assets/xychartDiagram-5P7HB3ND-Ur2xVM-c.js +7 -0
  1803. package/payload/server/public/brand/claude.png +0 -0
  1804. package/payload/server/public/brand/favicon.ico +0 -0
  1805. package/payload/server/public/brand/maxy-black.png +0 -0
  1806. package/payload/server/public/brand/maxy-horizontal.png +0 -0
  1807. package/payload/server/public/brand/maxy-monochrome.png +0 -0
  1808. package/payload/server/public/brand/maxy-square.png +0 -0
  1809. package/payload/server/public/brand/maxy.png +0 -0
  1810. package/payload/server/public/brand/star.png +0 -0
  1811. package/payload/server/public/brand-constants.json +8 -0
  1812. package/payload/server/public/brand-defaults.css +12 -0
  1813. package/payload/server/public/data.html +18 -0
  1814. package/payload/server/public/favicon.ico +0 -0
  1815. package/payload/server/public/graph.html +19 -0
  1816. package/payload/server/public/index.html +23 -0
  1817. package/payload/server/public/public.html +19 -0
  1818. package/payload/server/public/robots.txt +5 -0
  1819. package/payload/server/public/vnc-popout.html +63 -0
  1820. package/payload/server/server-init.cjs +115 -0
  1821. package/payload/server/server.js +14960 -0
package/dist/index.js ADDED
@@ -0,0 +1,3325 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync, spawn, spawnSync } from "node:child_process";
3
+ import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, statSync, readlinkSync, realpathSync, accessSync, constants as fsConstants } from "node:fs";
4
+ import { resolve, join, dirname } from "node:path";
5
+ import { randomBytes } from "node:crypto";
6
+ import { resolveInstallPortFromFs, buildMaxyUnitFile, buildClaudeSessionManagerUnitFile } from "./port-resolution.js";
7
+ import { parseOsRelease, isUbuntuLike as isUbuntuLikePure, parseAptCacheCandidate, decideAptResolution, } from "./apt-resolve.js";
8
+ import { findPeerBrandOnDefaultNeo4jPort } from "./peer-brand-detect.js";
9
+ import { requireSupportedPlatform } from "./platform-detect.js";
10
+ import { renderPlist } from "./launchd-plist.js";
11
+ import { installAllBrewPackages } from "./brew-install.js";
12
+ import { parseSwVers, isSupportedMacosVersion } from "./macos-version.js";
13
+ import { decideChromiumAction, isSnapConfinedPath } from "./snap-chromium.js";
14
+ import { classifyPortHolder } from "./preflight-port-classifier.js";
15
+ const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
16
+ // Brand manifest — read from payload to derive all brand-specific installation values.
17
+ // The bundler stamps brand.json into the payload at build time.
18
+ const BRAND_PATH = join(PAYLOAD_DIR, "platform", "config", "brand.json");
19
+ if (!existsSync(BRAND_PATH)) {
20
+ console.error("Setup failed: brand.json not found in payload (package may be corrupted)");
21
+ console.error("FATAL: brand.json not found in payload. Package may be corrupted.");
22
+ process.exit(1);
23
+ }
24
+ let BRAND;
25
+ try {
26
+ BRAND = JSON.parse(readFileSync(BRAND_PATH, "utf-8"));
27
+ }
28
+ catch (err) {
29
+ console.error(`Setup failed: failed to parse brand.json: ${err.message}`);
30
+ console.error(`FATAL: Failed to parse brand.json: ${err.message}`);
31
+ process.exit(1);
32
+ }
33
+ const INSTALL_DIR = resolve(process.env.HOME ?? "/root", BRAND.installDir);
34
+ const LOG_DIR = resolve(process.env.HOME ?? "/root", BRAND.configDir, "logs");
35
+ const LOG_FILE = join(LOG_DIR, `install-${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
36
+ /** Known brand hostnames in the Maxy ecosystem. Each brand ships a main unit
37
+ * (`<hostname>.service`) and a per-brand edge unit (`<hostname>-edge.service`,
38
+ * Task 662). Peer-brand detection matches only these filenames — stale units,
39
+ * gnome-keyring disable markers, and unrelated user services are not peer
40
+ * evidence. When a third brand is added under `brands/`, append its hostname
41
+ * here AND in the matching constant in `uninstall.ts` (intentional duplication
42
+ * per `uninstall.ts:` "Shell helpers (duplicated from index.ts ...)" policy). */
43
+ const KNOWN_BRAND_HOSTNAMES = ["maxy", "realagent", "maxy-2", "maxy-3", "maxy-4"];
44
+ // The device's actual hostname — may differ from BRAND.hostname if the user customized it.
45
+ // Updated by installSystemDeps() after hostname setup; used for user-facing URLs.
46
+ let DEVICE_HOSTNAME = BRAND.hostname;
47
+ // Task 929 — absolute path to the non-snap Chromium binary chosen during
48
+ // installSystemDeps(). Defaults to /usr/bin/chromium so non-Linux installs
49
+ // (which never call ensureNonSnapChromium) and the systemd unit's
50
+ // PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH still see a sensible value. On Linux,
51
+ // installSystemDeps() always overwrites this — either /usr/bin/chromium (Pi
52
+ // Bookworm: real .deb) or /usr/bin/google-chrome-stable (Ubuntu Noble laptop:
53
+ // snap-confined chromium replaced). Read by writeChromiumBinaryPathFile()
54
+ // and threaded into buildMaxyUnitFile() so the chromium-binary.path config
55
+ // file, the systemd unit's PLAYWRIGHT env var, and vnc.sh's runtime resolver
56
+ // all agree on one absolute path.
57
+ let RESOLVED_CHROMIUM_BIN = "/usr/bin/chromium";
58
+ // npm flags tuned for Raspberry Pi — reduce parallelism, increase patience
59
+ const NPM_NET_FLAGS = [
60
+ "--fetch-retries=5",
61
+ "--fetch-retry-mintimeout=30000",
62
+ "--fetch-retry-maxtimeout=120000",
63
+ "--maxsockets=3",
64
+ ];
65
+ // ---------------------------------------------------------------------------
66
+ // Logging — timestamped to console AND persistent log file
67
+ // ---------------------------------------------------------------------------
68
+ function initLogging() {
69
+ mkdirSync(LOG_DIR, { recursive: true });
70
+ appendFileSync(LOG_FILE, [
71
+ "",
72
+ "=".repeat(64),
73
+ ` ${BRAND.productName} Install Log — ${new Date().toISOString()}`,
74
+ ` Node ${process.version} | ${process.platform} ${process.arch}`,
75
+ "=".repeat(64),
76
+ "",
77
+ ].join("\n") + "\n");
78
+ }
79
+ function logFile(msg) {
80
+ try {
81
+ appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
82
+ }
83
+ catch { /* fs full */ }
84
+ }
85
+ // Mirror all console output into the log file
86
+ const _log = console.log;
87
+ const _err = console.error;
88
+ console.log = (...args) => { _log(...args); logFile(args.map(String).join(" ")); };
89
+ console.error = (...args) => { _err(...args); logFile(`[ERROR] ${args.map(String).join(" ")}`); };
90
+ // ---------------------------------------------------------------------------
91
+ // Diagnostics — system state snapshot for post-mortem analysis
92
+ // ---------------------------------------------------------------------------
93
+ function logDiagnostics(label) {
94
+ logFile(`\n--- Diagnostics: ${label} ---`);
95
+ if (!isLinux()) {
96
+ logFile(" (not Linux — limited diagnostics)");
97
+ return;
98
+ }
99
+ const run = (cmd, args) => {
100
+ const r = spawnSync(cmd, args, { encoding: "utf-8", stdio: "pipe", timeout: 10_000 });
101
+ return (r.stdout || r.stderr || "").trim();
102
+ };
103
+ logFile(`Disk:\n${run("df", ["-h", "/", "/tmp"])}`);
104
+ logFile(`Memory:\n${run("free", ["-h"])}`);
105
+ logFile(`Uptime: ${run("uptime", [])}`);
106
+ for (const host of ["registry.npmjs.org", "github.com"]) {
107
+ const dns = spawnSync("host", ["-W", "5", host], { encoding: "utf-8", stdio: "pipe", timeout: 10_000 });
108
+ logFile(`DNS ${host}: ${dns.status === 0 ? "OK" : "FAIL"} — ${(dns.stdout || dns.stderr || "").trim().split("\n")[0]}`);
109
+ }
110
+ const curl = spawnSync("curl", [
111
+ "-sf", "-o", "/dev/null",
112
+ "-w", "HTTP %{http_code} in %{time_total}s (dns: %{time_namelookup}s, connect: %{time_connect}s)",
113
+ "--connect-timeout", "10",
114
+ "https://registry.npmjs.org/",
115
+ ], { encoding: "utf-8", stdio: "pipe", timeout: 15_000 });
116
+ logFile(`Registry HTTP: ${curl.stdout?.trim() || `FAIL — ${curl.stderr?.trim()}`}`);
117
+ logFile(`--- End Diagnostics ---\n`);
118
+ }
119
+ /** Append the most recent npm debug log into our install log for post-mortem. */
120
+ function captureNpmDebugLog() {
121
+ const logsDir = resolve(process.env.HOME ?? "/root", ".npm/_logs");
122
+ if (!existsSync(logsDir))
123
+ return;
124
+ try {
125
+ const files = readdirSync(logsDir).filter(f => f.endsWith("-debug-0.log")).sort().reverse();
126
+ if (files[0]) {
127
+ const content = readFileSync(join(logsDir, files[0]), "utf-8");
128
+ logFile(`\n--- npm debug log: ${files[0]} (last 200 lines) ---`);
129
+ const lines = content.split("\n");
130
+ logFile(lines.slice(Math.max(0, lines.length - 200)).join("\n"));
131
+ logFile(`--- end npm debug log ---\n`);
132
+ }
133
+ }
134
+ catch { /* ignore */ }
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // Helpers
138
+ // ---------------------------------------------------------------------------
139
+ function log(step, total, message) {
140
+ console.log(`[${step}/${total}] ${message}`);
141
+ }
142
+ function shell(command, args, options) {
143
+ const cmd = options?.sudo ? "sudo" : command;
144
+ const cmdArgs = options?.sudo ? [command, ...args] : args;
145
+ const start = Date.now();
146
+ // Redaction (Task 744): callers handling secrets pass redact: true so the
147
+ // wrapper records the command name only, not the secret-bearing args. The
148
+ // child process still receives the real args via spawnSync below; only the
149
+ // install log line is sanitised. The grep-able audit shape stays:
150
+ // > sudo neo4j-admin dbms set-initial-password [REDACTED]
151
+ const loggedArgs = options?.redact
152
+ ? `${cmdArgs.slice(0, options?.sudo ? 4 : 3).join(" ")} [REDACTED]`
153
+ : cmdArgs.join(" ");
154
+ logFile(`> ${cmd} ${loggedArgs}${options?.cwd ? ` [cwd: ${options.cwd}]` : ""}`);
155
+ const result = spawnSync(cmd, cmdArgs, {
156
+ stdio: "inherit",
157
+ timeout: options?.timeout ?? 300_000,
158
+ cwd: options?.cwd,
159
+ env: options?.env,
160
+ });
161
+ const dur = ((Date.now() - start) / 1000).toFixed(1);
162
+ // bestEffort (Task 787): tear-down ops on units/state that may or may not
163
+ // exist (stop/disable a system service we may never have started, reset-failed
164
+ // a freshly-created unit) log the non-zero exit but do not throw. Reserved
165
+ // for the "may not exist" pattern only — never use for ops that must succeed.
166
+ if (result.signal) {
167
+ logFile(` KILLED (${result.signal}) after ${dur}s`);
168
+ if (options?.bestEffort)
169
+ return;
170
+ throw new Error(`Command killed (${result.signal}) after ${dur}s: ${cmd} ${cmdArgs.join(" ")}`);
171
+ }
172
+ if (result.status !== 0) {
173
+ logFile(` ${options?.bestEffort ? "best-effort non-zero" : "FAILED"} (exit ${result.status}) after ${dur}s`);
174
+ if (options?.bestEffort)
175
+ return;
176
+ throw new Error(`Command failed (exit ${result.status}) after ${dur}s: ${cmd} ${cmdArgs.join(" ")}`);
177
+ }
178
+ logFile(` OK in ${dur}s`);
179
+ }
180
+ /** Wait until DNS resolves for a host. Returns true if recovered. */
181
+ function waitForDns(host, maxSec = 60) {
182
+ for (let elapsed = 0; elapsed < maxSec; elapsed += 5) {
183
+ const r = spawnSync("host", ["-W", "3", host], { stdio: "pipe", timeout: 5_000 });
184
+ if (r.status === 0) {
185
+ logFile(` DNS ${host}: recovered after ${elapsed}s`);
186
+ return true;
187
+ }
188
+ console.log(` DNS ${host} not resolving — waiting (${elapsed}s/${maxSec}s)...`);
189
+ spawnSync("sleep", ["5"]);
190
+ }
191
+ return false;
192
+ }
193
+ /** Retry a shell command with exponential backoff. Runs network diagnostics between attempts. */
194
+ function shellRetry(command, args, options, maxAttempts = 3, backoffSec = 20) {
195
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
196
+ try {
197
+ shell(command, args, options);
198
+ return;
199
+ }
200
+ catch (err) {
201
+ const msg = err instanceof Error ? err.message : String(err);
202
+ logFile(` Attempt ${attempt}/${maxAttempts} error: ${msg}`);
203
+ if (attempt === maxAttempts) {
204
+ console.error(` All ${maxAttempts} attempts failed.`);
205
+ logDiagnostics(`final failure — ${command}`);
206
+ if (command === "npm")
207
+ captureNpmDebugLog();
208
+ throw err;
209
+ }
210
+ const wait = backoffSec * attempt;
211
+ console.log(` Attempt ${attempt}/${maxAttempts} failed. Retrying in ${wait}s...`);
212
+ // On network errors, diagnose and wait for DNS recovery before retrying
213
+ if (/ETIMEDOUT|EAI_AGAIN|ECONNRESET|ENOTFOUND|ENETUNREACH/.test(msg)) {
214
+ console.log(" Network error — running diagnostics...");
215
+ logDiagnostics(`network failure — attempt ${attempt}`);
216
+ waitForDns("registry.npmjs.org");
217
+ }
218
+ if (command === "npm")
219
+ captureNpmDebugLog();
220
+ spawnSync("sleep", [String(wait)]);
221
+ }
222
+ }
223
+ }
224
+ function commandExists(cmd) {
225
+ try {
226
+ execFileSync("which", [cmd], { stdio: "pipe" });
227
+ return true;
228
+ }
229
+ catch {
230
+ return false;
231
+ }
232
+ }
233
+ function nodeVersion() {
234
+ try {
235
+ const v = execFileSync("node", ["-v"], { encoding: "utf-8" }).trim();
236
+ return parseInt(v.replace("v", "").split(".")[0], 10);
237
+ }
238
+ catch {
239
+ return 0;
240
+ }
241
+ }
242
+ function isLinux() {
243
+ return process.platform === "linux";
244
+ }
245
+ function isArm64() {
246
+ return process.arch === "arm64";
247
+ }
248
+ /** Check whether non-interactive sudo is available (passwordless or cached credentials). */
249
+ function canSudo() {
250
+ const result = spawnSync("sudo", ["-n", "true"], { stdio: "pipe", timeout: 5_000 });
251
+ return result.status === 0;
252
+ }
253
+ // Task 634 — verified-not-asserted apt-dep reconciliation.
254
+ // Task 637 — resolve package-name aliases before probing dpkg, so the
255
+ // post-install check no longer false-negatives when apt resolves a virtual
256
+ // name (e.g. Noble's `chromium` → `chromium-browser`).
257
+ // UBUNTU_ALIASES, parseOsRelease, isUbuntuLike, and decideAptResolution moved
258
+ // to ./apt-resolve.ts (Task 638). The pure logic lives there so a unit test
259
+ // can hit every branch without spawning real apt/dpkg or reading
260
+ // /etc/os-release. The thin wrappers below feed real spawn + fs results into
261
+ // that pure decision.
262
+ function readOsRelease() {
263
+ try {
264
+ return parseOsRelease(readFileSync("/etc/os-release", "utf-8"));
265
+ }
266
+ catch {
267
+ return {};
268
+ }
269
+ }
270
+ /** Summarise `apt-cache policy` output for diagnostics — one token per package. */
271
+ function aptCachePolicySummary(pkg) {
272
+ const r = spawnSync("apt-cache", ["policy", pkg], { stdio: "pipe", encoding: "utf-8", timeout: 5_000 });
273
+ if (r.status !== 0)
274
+ return "policy-spawn-failed";
275
+ const cand = parseAptCacheCandidate(r.stdout ?? "");
276
+ if (cand === null)
277
+ return "no-candidate-line";
278
+ return cand === "(none)" ? "candidate-none" : `candidate=${cand}`;
279
+ }
280
+ /**
281
+ * Task 637 — map an apt-level package name to the concrete name dpkg will
282
+ * record after install. Resolution order:
283
+ * 1. `dpkg -s pkg` exits 0 → name is concrete and installed; return pkg.
284
+ * 2. `apt-cache policy pkg` reports a real Candidate → concrete-but-missing;
285
+ * return pkg (apt-get install + post-check will use the same name).
286
+ * 3. `apt-cache policy pkg` reports `Candidate: (none)` and `pkg` is in
287
+ * UBUNTU_ALIASES on an Ubuntu-like host → return the alias. Log the
288
+ * resolution so the install log answers "what did apt resolve this to?".
289
+ * 4. Otherwise return pkg unchanged — the post-check will throw loudly,
290
+ * preserving Task 634's fail-loud contract for genuinely missing packages.
291
+ */
292
+ function resolveAptName(pkg) {
293
+ const dpkg = spawnSync("dpkg", ["-s", pkg], { stdio: "pipe", timeout: 5_000 });
294
+ const dpkgInstalled = dpkg.status === 0;
295
+ // Short-circuit: when dpkg already records the name as installed, skip the
296
+ // apt-cache + os-release work — `decideAptResolution` returns pkg unchanged
297
+ // anyway and the extra spawn would burn ~10 ms per already-installed
298
+ // package across every `pkgsMissing` pass.
299
+ if (dpkgInstalled)
300
+ return pkg;
301
+ const policy = spawnSync("apt-cache", ["policy", pkg], {
302
+ stdio: "pipe", encoding: "utf-8", timeout: 5_000,
303
+ });
304
+ const aptCandidate = policy.status === 0
305
+ ? parseAptCacheCandidate(policy.stdout ?? "")
306
+ : null;
307
+ const os = readOsRelease();
308
+ const decision = decideAptResolution({
309
+ pkg,
310
+ dpkgInstalled,
311
+ aptCandidate,
312
+ ubuntuLike: isUbuntuLikePure(os),
313
+ distro: `${os.ID ?? "unknown"}-${os.VERSION_CODENAME ?? "unknown"}`,
314
+ });
315
+ if (decision.log)
316
+ logFile(decision.log);
317
+ return decision.resolved;
318
+ }
319
+ /** Probe runtime binary presence on PATH (independent of dpkg-recorded state). */
320
+ function commandVPath(pkg) {
321
+ const r = spawnSync("which", [pkg], { stdio: "pipe", encoding: "utf-8", timeout: 5_000 });
322
+ return r.status === 0 ? (r.stdout ?? "").trim() || "missing" : "missing";
323
+ }
324
+ /** Probe snap-recorded state for a name (snap lives outside dpkg). */
325
+ function snapStatus(pkg) {
326
+ const r = spawnSync("snap", ["list", pkg], { stdio: "pipe", encoding: "utf-8", timeout: 5_000 });
327
+ if (r.status !== 0)
328
+ return "none";
329
+ const line = (r.stdout ?? "").split("\n").find((l) => l.startsWith(pkg));
330
+ return line ? line.split(/\s+/).slice(0, 2).join(" ") : "none";
331
+ }
332
+ /**
333
+ * Returns the subset of `pkgs` that are not currently installed, after
334
+ * alias resolution. Uses `dpkg -s <resolved>` per package (exit 0 = installed,
335
+ * any non-zero = missing) so we never parse dpkg's prose output for control
336
+ * flow (feedback_no_stdout_parsing_for_control_flow.md). The operator-facing
337
+ * name stays the original `pkg` — the resolution is logged but not renamed
338
+ * in the returned list, so diagnostics match what the installer declared.
339
+ */
340
+ function pkgsMissing(pkgs) {
341
+ return pkgs.filter((p) => {
342
+ const resolved = resolveAptName(p);
343
+ const r = spawnSync("dpkg", ["-s", resolved], { stdio: "pipe", timeout: 5_000 });
344
+ return r.status !== 0;
345
+ });
346
+ }
347
+ /**
348
+ * Install a logical group of apt packages, then verify each one landed in
349
+ * dpkg's installed state. The post-install `dpkg -s` probe catches the
350
+ * partial-failure class where `apt-get install` returns 0 but a package did
351
+ * not actually install — silent regressions that would otherwise only
352
+ * surface at runtime when the binary is missing.
353
+ *
354
+ * Resolves each package through `resolveAptName` before both the install
355
+ * command and the post-check, so alias packages (Task 637) pass through
356
+ * cleanly on distros where the apt-level name differs from the dpkg-recorded
357
+ * name (e.g. Noble's chromium → chromium-browser). Resolution is computed
358
+ * once per package and reused across install, post-check, and error
359
+ * diagnostics — three spawnSync chains per package, not nine on failure.
360
+ */
361
+ function installAptGroup(label, pkgs) {
362
+ const pairs = pkgs.map((original) => ({ original, resolved: resolveAptName(original) }));
363
+ logFile(` apt install (${label}): ${pairs.map((x) => x.resolved).join(" ")}`);
364
+ console.log(" [privileged] apt-get install");
365
+ shell("apt-get", ["install", "-y", ...pairs.map((x) => x.resolved)], { sudo: true });
366
+ const stillMissing = pairs.filter(({ resolved }) => {
367
+ const r = spawnSync("dpkg", ["-s", resolved], { stdio: "pipe", timeout: 5_000 });
368
+ return r.status !== 0;
369
+ });
370
+ if (stillMissing.length > 0) {
371
+ const diag = stillMissing.map(({ original, resolved }) => `${original} (resolved-name=${resolved}, apt-cache-policy=${aptCachePolicySummary(original)}, command-v=${commandVPath(original)}, snap-status=${snapStatus(resolved)})`).join("; ");
372
+ throw new Error(`apt-get install (${label}) returned 0 but packages are still not installed per dpkg -s: ${diag}`);
373
+ }
374
+ }
375
+ // ---------------------------------------------------------------------------
376
+ // Installation steps
377
+ // ---------------------------------------------------------------------------
378
+ const TOTAL = "11";
379
+ /**
380
+ * Task 840 — set macOS hostname via scutil. Three sequential `sudo scutil
381
+ * --set` calls (HostName, LocalHostName, ComputerName) — `hostnamectl` is
382
+ * Linux-only and `--hostname <h>` silently no-ops on darwin. All-or-nothing
383
+ * rollback within the 3-call batch: if any call fails, we restore the
384
+ * pre-batch values for the keys we already changed and re-throw. Full
385
+ * system-state rollback (avahi, /etc/hosts) is out of scope per the brief.
386
+ */
387
+ function setMacosHostnameViaScutil(hostname) {
388
+ const keys = ["HostName", "LocalHostName", "ComputerName"];
389
+ // Capture pre-batch values for rollback. Empty string is a legitimate
390
+ // scutil --get value (the key was never set) — preserve that as "" so
391
+ // rollback writes empty back rather than failing.
392
+ const previous = {};
393
+ for (const k of keys) {
394
+ const r = spawnSync("scutil", ["--get", k], { encoding: "utf-8", stdio: "pipe", timeout: 5_000 });
395
+ previous[k] = (r.stdout ?? "").trim();
396
+ }
397
+ const applied = [];
398
+ for (const k of keys) {
399
+ const r = spawnSync("sudo", ["scutil", "--set", k, hostname], { encoding: "utf-8", stdio: "inherit", timeout: 30_000 });
400
+ if (r.status !== 0) {
401
+ // Roll back any keys we already changed within this batch.
402
+ for (const done of applied) {
403
+ spawnSync("sudo", ["scutil", "--set", done, previous[done] ?? ""], { stdio: "inherit", timeout: 30_000 });
404
+ }
405
+ const stderr = r.stderr ? `: ${r.stderr.toString().trim()}` : "";
406
+ console.error(` [scutil] ${k} failed${stderr}`);
407
+ throw new Error(`scutil --set ${k} ${hostname} exited ${r.status}`);
408
+ }
409
+ applied.push(k);
410
+ }
411
+ console.log(` [scutil] HostName=${hostname} LocalHostName=${hostname} ComputerName=${hostname} set ok`);
412
+ }
413
+ /**
414
+ * Task 929 — detect snap-confined Chromium on Linux and install Google Chrome
415
+ * stable as the non-snap replacement. Runs after `installAptGroup("VNC stack")`
416
+ * inside `installSystemDeps`. Resolution rules live in `snap-chromium.ts`
417
+ * (pure decision); this wrapper does the spawnSync + apt-repo writes + post-
418
+ * install gate. Skipped on darwin (Maxy uses Playwright-managed Chromium per
419
+ * brew-install.ts) — RESOLVED_CHROMIUM_BIN keeps its `/usr/bin/chromium`
420
+ * default which is unused on darwin (no systemd unit).
421
+ *
422
+ * Detection: `command -v chromium` + `realpath`; an extra probe for
423
+ * `google-chrome-stable` covers re-run installs where a previous run already
424
+ * landed Chrome. Snap detection is the literal `snap` segment in the realpath
425
+ * (see isSnapConfinedPath in snap-chromium.ts) — covers the three real-world
426
+ * shapes (`/snap/bin/chromium`, `/snap/<rev>/usr/...`, `/usr/bin/snap` which
427
+ * is the snap launcher binary that `readlink -f` terminates at on Noble).
428
+ *
429
+ * Replacement: Google's signed apt repo (cryptographic verification via
430
+ * `signed-by=` GPG key) — the canonical pinned-deterministic source for
431
+ * Chrome stable. Pinning a specific Chrome version would require an out-of-
432
+ * band SHA-bump cadence and contradicts the apt-repo trust model.
433
+ *
434
+ * Post-install gate: spawn the resolved binary headless against a throwaway
435
+ * profile dir under persistDir, assert exit 0. The AppArmor denial that
436
+ * triggered Task 929 was on SingletonLock writes which `--headless=new` still
437
+ * attempts, so the headless probe fires the same EACCES path that production
438
+ * VNC headed launches do — closing the post-fix-sibling-audit-skipped gap.
439
+ */
440
+ function ensureNonSnapChromium() {
441
+ if (process.platform !== "linux") {
442
+ logFile(` ensureNonSnapChromium skipped: platform=${process.platform}`);
443
+ return;
444
+ }
445
+ const which = (cmd) => {
446
+ const r = spawnSync("command", ["-v", cmd], { stdio: "pipe", encoding: "utf-8", shell: "/bin/bash", timeout: 5_000 });
447
+ if (r.status !== 0)
448
+ return null;
449
+ const out = (r.stdout ?? "").trim();
450
+ return out || null;
451
+ };
452
+ const realpath = (path) => {
453
+ if (!path)
454
+ return null;
455
+ try {
456
+ return realpathSync(path);
457
+ }
458
+ catch {
459
+ return null;
460
+ }
461
+ };
462
+ const whichChromium = which("chromium");
463
+ const whichGoogleChrome = which("google-chrome-stable");
464
+ const decision = decideChromiumAction({
465
+ platform: "linux",
466
+ whichChromium,
467
+ realpathChromium: realpath(whichChromium),
468
+ whichGoogleChrome,
469
+ realpathGoogleChrome: realpath(whichGoogleChrome),
470
+ });
471
+ logFile(` [snap-chromium] decision: ${decision.action} reason="${decision.reason}"`);
472
+ if (decision.action === "fail") {
473
+ throw new Error(`ensureNonSnapChromium: ${decision.reason}. apt install of \`chromium\` ran in installAptGroup(VNC stack) above; if its post-check passed but no chromium binary is on PATH, the system PATH is misconfigured.`);
474
+ }
475
+ if (decision.action === "install-google-chrome") {
476
+ console.log(" Detected snap-confined Chromium — installing Google Chrome stable...");
477
+ logFile(` [snap-chromium] installing google-chrome-stable from Google's signed apt repo`);
478
+ // Fetch + dearmor the signing key, write to /etc/apt/trusted.gpg.d/. Pipe
479
+ // composition runs through bash -c so the curl|gpg pipeline is one
480
+ // privileged command rather than two separate sudo escalations.
481
+ console.log(" [privileged] curl + gpg --dearmor (Google Chrome signing key)");
482
+ shell("bash", ["-c",
483
+ "set -euo pipefail; " +
484
+ "curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | " +
485
+ "gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/google-chrome.gpg",
486
+ ], { sudo: true });
487
+ // Add the apt source list with `signed-by=` scoping so the key only
488
+ // verifies google-chrome-* packages, not arbitrary repo overrides. arch
489
+ // pinned to amd64 — Google does not ship arm64 Chrome for Linux.
490
+ console.log(" [privileged] tee /etc/apt/sources.list.d/google-chrome.list");
491
+ shell("bash", ["-c",
492
+ "echo 'deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/google-chrome.gpg] " +
493
+ "http://dl.google.com/linux/chrome/deb/ stable main' " +
494
+ "> /etc/apt/sources.list.d/google-chrome.list",
495
+ ], { sudo: true });
496
+ console.log(" [privileged] apt-get update");
497
+ shell("apt-get", ["update"], { sudo: true });
498
+ installAptGroup("Google Chrome stable", ["google-chrome-stable"]);
499
+ // Re-resolve after install to capture the now-installed absolute path.
500
+ const postInstallWhich = which("google-chrome-stable");
501
+ if (!postInstallWhich) {
502
+ throw new Error("ensureNonSnapChromium: apt install of google-chrome-stable returned 0 and dpkg -s passed, but `command -v google-chrome-stable` is empty — PATH is broken.");
503
+ }
504
+ RESOLVED_CHROMIUM_BIN = postInstallWhich;
505
+ }
506
+ else {
507
+ // action === "use" — decision.resolvedPath is the existing non-snap binary
508
+ // (chromium or google-chrome-stable already installed).
509
+ if (!decision.resolvedPath) {
510
+ throw new Error(`ensureNonSnapChromium: action=use returned without resolvedPath — bug in snap-chromium.ts (input: chromium=${whichChromium} google-chrome=${whichGoogleChrome})`);
511
+ }
512
+ RESOLVED_CHROMIUM_BIN = decision.resolvedPath;
513
+ }
514
+ // Defensive: never persist a snap-confined path. If realpath of the resolved
515
+ // binary still lands under /snap/ (e.g. apt landed a snap package by mistake
516
+ // on a misconfigured device), throw before writeChromiumBinaryPathFile sees
517
+ // it — the runtime gate in vnc.sh would refuse anyway, but failing here
518
+ // surfaces the contract breach with the install context still in scope.
519
+ const finalRealpath = realpath(RESOLVED_CHROMIUM_BIN);
520
+ if (isSnapConfinedPath(finalRealpath)) {
521
+ throw new Error(`ensureNonSnapChromium: resolved Chromium binary ${RESOLVED_CHROMIUM_BIN} realpaths to ${finalRealpath} which is under /snap/ — refusing to persist.`);
522
+ }
523
+ console.log(` Chromium binary: ${RESOLVED_CHROMIUM_BIN} (realpath=${finalRealpath ?? "?"})`);
524
+ logFile(` [snap-chromium] resolved bin=${RESOLVED_CHROMIUM_BIN} realpath=${finalRealpath ?? "null"}`);
525
+ runChromiumPostInstallGate(RESOLVED_CHROMIUM_BIN);
526
+ }
527
+ /**
528
+ * Task 929 post-install gate. Spawns the resolved Chromium binary headless
529
+ * against a throwaway profile dir under persistDir (`~/.{brand}/chromium-
530
+ * gate-profile/`). The AppArmor denial that triggered the task was on
531
+ * SingletonLock writes which `--headless=new` still attempts, so this probe
532
+ * fires the same EACCES path the headed VNC stack would. Cleans up the gate
533
+ * profile afterward — the live profile (`chromium-profile/`) is owned by
534
+ * vnc.sh's start_chrome and not touched here.
535
+ */
536
+ function runChromiumPostInstallGate(chromiumBin) {
537
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
538
+ const gateProfileDir = join(persistDir, "chromium-gate-profile");
539
+ mkdirSync(gateProfileDir, { recursive: true });
540
+ console.log(` Verifying ${chromiumBin} can write to ${gateProfileDir} (Task 929 post-install gate)...`);
541
+ const r = spawnSync(chromiumBin, [
542
+ `--user-data-dir=${gateProfileDir}`,
543
+ "--headless=new",
544
+ "--disable-gpu",
545
+ "--no-sandbox",
546
+ "--disable-dev-shm-usage",
547
+ "--dump-dom",
548
+ "about:blank",
549
+ ], { stdio: "pipe", encoding: "utf-8", timeout: 30_000 });
550
+ // Cleanup before throwing on failure so successive runs start clean.
551
+ try {
552
+ rmSync(gateProfileDir, { recursive: true, force: true });
553
+ }
554
+ catch { /* best-effort */ }
555
+ if (r.status !== 0) {
556
+ const stderr = (r.stderr ?? "").slice(-2000);
557
+ const eaccesHit = /Permission denied/i.test(stderr) || /EACCES/i.test(stderr);
558
+ const taskRef = eaccesHit
559
+ ? "Task 929: chromium-profile not writable (likely AppArmor denial on snap-confined binary). "
560
+ : "";
561
+ throw new Error(`${taskRef}Chromium post-install gate failed: ${chromiumBin} exited ${r.status} signal=${r.signal ?? "none"}. stderr:\n${stderr}`);
562
+ }
563
+ console.log(" Chromium post-install gate passed.");
564
+ logFile(` [snap-chromium] post-install gate ok: ${chromiumBin} exit=0`);
565
+ }
566
+ /**
567
+ * Task 929 — write the resolved Chromium absolute path to
568
+ * `<INSTALL_DIR>/platform/config/chromium-binary.path` so vnc.sh,
569
+ * writeChromiumWrapper, and setup-tunnel.sh all read the same value. Called
570
+ * after deployPayload so the platform/config/ directory exists. Idempotent:
571
+ * re-running the installer with the same RESOLVED_CHROMIUM_BIN is a no-op
572
+ * write (writeFileSync overwrites in place).
573
+ */
574
+ function writeChromiumBinaryPathFile() {
575
+ if (process.platform !== "linux") {
576
+ logFile(` writeChromiumBinaryPathFile skipped: platform=${process.platform}`);
577
+ return;
578
+ }
579
+ const configDir = resolve(INSTALL_DIR, "platform/config");
580
+ mkdirSync(configDir, { recursive: true });
581
+ const target = join(configDir, "chromium-binary.path");
582
+ writeFileSync(target, RESOLVED_CHROMIUM_BIN + "\n", { mode: 0o644 });
583
+ console.log(` Wrote ${target} → ${RESOLVED_CHROMIUM_BIN}`);
584
+ logFile(` [snap-chromium] wrote ${target} contents=${RESOLVED_CHROMIUM_BIN}`);
585
+ }
586
+ function installSystemDeps() {
587
+ log("1", TOTAL, "System dependencies and network...");
588
+ const platform = requireSupportedPlatform(process.platform);
589
+ if (platform === "darwin") {
590
+ // Task 840 — darwin hostname via scutil when --hostname is supplied,
591
+ // otherwise preserve the existing system hostname.
592
+ if (HOSTNAME_FLAG) {
593
+ console.log(` Hostname: ${HOSTNAME_FLAG} (from --hostname flag)`);
594
+ try {
595
+ setMacosHostnameViaScutil(HOSTNAME_FLAG);
596
+ DEVICE_HOSTNAME = HOSTNAME_FLAG;
597
+ }
598
+ catch (err) {
599
+ console.error(` WARNING: Failed to set hostname to '${HOSTNAME_FLAG}': ${err instanceof Error ? err.message : String(err)}`);
600
+ }
601
+ }
602
+ else {
603
+ try {
604
+ DEVICE_HOSTNAME = execFileSync("hostname", [], { encoding: "utf-8" }).trim();
605
+ console.log(` Hostname: ${DEVICE_HOSTNAME} (preserved — no --hostname flag on darwin)`);
606
+ }
607
+ catch { /* fallback to brand — set at declaration */ }
608
+ }
609
+ // Task 839 — macOS has no apt analogue for the VNC/WiFi-AP stacks
610
+ // (kiosk display + hostapd/dnsmasq are Pi-specific per Task 836's
611
+ // out-of-scope note). mDNS is provided by the OS, so avahi-* drop out.
612
+ // Translate the remaining apt names through decideBrewResolution and
613
+ // let installAllBrewPackages handle the install + verify pattern.
614
+ const DARWIN_BASE_DEPS = ["curl", "git", "unzip", "jq", "poppler", "ffmpeg"];
615
+ installAllBrewPackages(DARWIN_BASE_DEPS, logFile);
616
+ return;
617
+ }
618
+ const BASE_DEPS = ["curl", "git", "unzip", "jq", "avahi-daemon", "avahi-utils", "poppler-utils", "ffmpeg", "bind9-dnsutils"];
619
+ // xterm is the *preferred* terminal-emulator binary for the VNC-rendered
620
+ // Terminal surface (Task 632 — gnome-terminal is a D-Bus launcher that
621
+ // delegates window creation to the session's gnome-terminal-server,
622
+ // opening windows on the wrong display; xterm has no IPC layer and
623
+ // honours DISPLAY directly). Kept in the apt list unconditionally so
624
+ // vnc.sh's resolve_terminal_bin has a display-safe binary on every
625
+ // supported distro (Ubuntu 24.04 noble/universe, Debian 12 bookworm/main
626
+ // verified). xdotool (Task 632) backs the post-spawn display-membership
627
+ // assertion in vnc.sh check_window_on_display, closing the silent-fail
628
+ // class where PID is alive but no window is mapped on the target display.
629
+ const VNC_DEPS = ["tigervnc-standalone-server", "python3-websockify", "novnc", "xdg-utils", "chromium", "xterm", "xdotool"];
630
+ // Task 664 retired the ttyd/tmux admin terminal stack — upgrades run via
631
+ // the action runner (systemd-run --user transient units) and no longer
632
+ // need a shared tmux session. `tmux` was only required by the retired
633
+ // byte-stream terminal; removing it shrinks the apt footprint.
634
+ const WIFI_DEPS = ["hostapd", "dnsmasq"];
635
+ const ALL_APT_DEPS = [...BASE_DEPS, ...VNC_DEPS, ...WIFI_DEPS];
636
+ // Task 634 — verify the "deps are present" assumption with `dpkg -s` instead
637
+ // of asserting it (feedback_loud_failures.md). The previous silent-skip
638
+ // branch was benign until Task 632 added xdotool (the first new apt dep
639
+ // since the skip path became load-bearing on user-password-sudo devices).
640
+ const missing = pkgsMissing(ALL_APT_DEPS);
641
+ if (missing.length === 0) {
642
+ logFile(` all system deps present per dpkg -s (${ALL_APT_DEPS.length} packages) — skipping apt install`);
643
+ console.log(` All ${ALL_APT_DEPS.length} system deps already installed — skipping apt install.`);
644
+ }
645
+ else {
646
+ const canEscalate = canSudo() || process.stdout.isTTY === true;
647
+ if (!canEscalate) {
648
+ const repair = `sudo apt-get install -y ${missing.join(" ")}`;
649
+ console.error(` MISSING ${missing.length} system deps per dpkg -s: ${missing.join(" ")}`);
650
+ console.error(` Non-interactive sudo is unavailable; cannot prompt for password from a non-TTY shell.`);
651
+ console.error(` Re-run this installer from an interactive shell, or repair manually:`);
652
+ console.error(` ${repair}`);
653
+ logFile(` FAIL: missing apt deps without interactive sudo: ${missing.join(",")}`);
654
+ throw new Error(`installSystemDeps: missing packages (${missing.join(", ")}) and sudo is unavailable non-interactively — repair with: ${repair}`);
655
+ }
656
+ console.log(` Missing apt deps (${missing.length}): ${missing.join(", ")}`);
657
+ console.log(` Installing via sudo apt-get — sudo may prompt for your password...`);
658
+ console.log(" [privileged] apt-get update");
659
+ shell("apt-get", ["update"], { sudo: true });
660
+ installAptGroup("base utilities", BASE_DEPS);
661
+ installAptGroup("VNC stack", VNC_DEPS);
662
+ installAptGroup("WiFi AP", WIFI_DEPS);
663
+ }
664
+ // Task 929 — replace snap-confined Chromium with Google Chrome stable on
665
+ // Linux laptops (Ubuntu Noble) where `/usr/bin/chromium` realpaths to the
666
+ // snap launcher. The snap AppArmor profile denies writes to hidden top-level
667
+ // paths under $HOME, so any write to `~/.{brand}/chromium-profile/SingletonLock`
668
+ // hits EACCES and Chromium never starts the CDP listener. Always sets
669
+ // RESOLVED_CHROMIUM_BIN even on Pi Bookworm (where the path is unchanged),
670
+ // so deployPayload's writeChromiumBinaryPathFile and installService's
671
+ // buildMaxyUnitFile both have a real absolute path to thread through.
672
+ ensureNonSnapChromium();
673
+ // Hostname resolution — four sources, in priority order:
674
+ // 1. --hostname flag (unconditional — the caller is the authority)
675
+ // 2. OS detection on same-brand upgrade (service exists → keep whatever is currently set)
676
+ // 3. OS preservation on multi-brand device (another brand's service detected → keep hostname)
677
+ // 4. BRAND.hostname on fresh install (brand default)
678
+ if (HOSTNAME_FLAG) {
679
+ // --hostname flag: set unconditionally, no detection, no preservation logic.
680
+ console.log(` Hostname: ${HOSTNAME_FLAG} (from --hostname flag)`);
681
+ try {
682
+ console.log(" [privileged] hostnamectl set-hostname");
683
+ shell("hostnamectl", ["set-hostname", HOSTNAME_FLAG], { sudo: true });
684
+ console.log(" [privileged] sed -i");
685
+ shell("sed", ["-i", `s/127\\.0\\.1\\.1.*$/127.0.1.1\\t${HOSTNAME_FLAG}/`, "/etc/hosts"], { sudo: true });
686
+ try {
687
+ console.log(" [privileged] sed -i");
688
+ shell("sed", ["-i", `s/^[#]*host-name=.*/host-name=${HOSTNAME_FLAG}/`, "/etc/avahi/avahi-daemon.conf"], { sudo: true });
689
+ console.log(` Avahi host-name: ${HOSTNAME_FLAG} (updated avahi-daemon.conf)`);
690
+ }
691
+ catch { /* avahi-daemon.conf may not exist — non-critical */ }
692
+ // Restart avahi-daemon so the new hostname takes effect immediately
693
+ // and any stale "maxytest-2" auto-renamed records from a previous
694
+ // boot's hostname-conflict cycle are withdrawn. Without this,
695
+ // avahi-resolve -n <hostname>.local times out from the device itself
696
+ // because the daemon is still advertising the previous identity.
697
+ try {
698
+ console.log(" [privileged] systemctl restart avahi-daemon");
699
+ shell("systemctl", ["restart", "avahi-daemon"], { sudo: true });
700
+ }
701
+ catch (err) {
702
+ console.error(` WARNING: avahi-daemon restart failed: ${err instanceof Error ? err.message : String(err)}`);
703
+ }
704
+ }
705
+ catch (err) {
706
+ console.error(` WARNING: Failed to set hostname to '${HOSTNAME_FLAG}': ${err instanceof Error ? err.message : String(err)}`);
707
+ }
708
+ DEVICE_HOSTNAME = HOSTNAME_FLAG;
709
+ }
710
+ else {
711
+ // No flag — fall back to detection (upgrade) or brand default (fresh install).
712
+ // Check for this brand's service (same-brand upgrade) and any other brand's service
713
+ // (multi-brand device). On a multi-brand device, hostname is a device-level concern
714
+ // set by the user or the first installer — subsequent brands must not overwrite it.
715
+ const systemdUserDir = resolve(process.env.HOME ?? "/root", ".config/systemd/user");
716
+ const serviceExists = existsSync(join(systemdUserDir, BRAND.serviceName));
717
+ // Task 690: narrow peer detection to KNOWN_BRAND_HOSTNAMES. The previous
718
+ // predicate ("any *.service that isn't ours") matched stray user units
719
+ // such as a `gnome-keyring-daemon.service -> /dev/null` disable marker,
720
+ // silently flipping single-brand fresh installs into the "preserve hostname"
721
+ // branch. Mirrors Task 683's `peerBrandPresent` allowlist in `uninstall.ts`.
722
+ let otherBrandService = false;
723
+ if (!serviceExists) {
724
+ const peerUnits = KNOWN_BRAND_HOSTNAMES
725
+ .filter((h) => h !== BRAND.hostname)
726
+ .flatMap((h) => [`${h}.service`, `${h}-edge.service`]);
727
+ try {
728
+ const files = new Set(readdirSync(systemdUserDir));
729
+ otherBrandService = peerUnits.some((unit) => files.has(unit));
730
+ }
731
+ catch { /* directory may not exist on a fresh device — not an error */ }
732
+ }
733
+ // "raspberrypi" is the Pi factory default — it means "never configured,"
734
+ // not "the admin chose this hostname." Treat it the same as a fresh install.
735
+ const FACTORY_HOSTNAMES = ["raspberrypi", "localhost"];
736
+ let hostnameSetAttempted = false;
737
+ try {
738
+ const currentHostname = execFileSync("hostname", [], { encoding: "utf-8" }).trim();
739
+ const isFactory = FACTORY_HOSTNAMES.includes(currentHostname);
740
+ if (currentHostname === BRAND.hostname) {
741
+ console.log(` Hostname: ${currentHostname} (detected from OS)`);
742
+ }
743
+ else if (serviceExists && !isFactory) {
744
+ // Upgrade: admin-chosen hostname — preserve it.
745
+ console.log(` Hostname: ${currentHostname} (detected from OS — differs from brand '${BRAND.hostname}')`);
746
+ }
747
+ else if (otherBrandService && !isFactory) {
748
+ // Multi-brand: another brand set the hostname — preserve it.
749
+ console.log(` Hostname preserved: ${currentHostname} (another brand service detected)`);
750
+ }
751
+ else {
752
+ // Fresh install, OR a factory default that was never changed.
753
+ const reason = isFactory && serviceExists
754
+ ? `factory default '${currentHostname}' was never changed`
755
+ : `brand default — fresh install, was '${currentHostname}'`;
756
+ console.log(` Hostname: ${BRAND.hostname} (${reason})`);
757
+ hostnameSetAttempted = true;
758
+ try {
759
+ console.log(" [privileged] hostnamectl set-hostname");
760
+ shell("hostnamectl", ["set-hostname", BRAND.hostname], { sudo: true });
761
+ console.log(" [privileged] sed -i");
762
+ shell("sed", ["-i", `s/127\\.0\\.1\\.1.*$/127.0.1.1\\t${BRAND.hostname}/`, "/etc/hosts"], { sudo: true });
763
+ try {
764
+ console.log(" [privileged] sed -i");
765
+ shell("sed", ["-i", `s/^[#]*host-name=.*/host-name=${BRAND.hostname}/`, "/etc/avahi/avahi-daemon.conf"], { sudo: true });
766
+ console.log(` Avahi host-name: ${BRAND.hostname} (updated avahi-daemon.conf)`);
767
+ }
768
+ catch { /* avahi-daemon.conf may not exist — non-critical */ }
769
+ }
770
+ catch (err) {
771
+ console.error(` WARNING: Could not set hostname to '${BRAND.hostname}': ${err instanceof Error ? err.message : String(err)}`);
772
+ console.error(` After install, run: sudo hostnamectl set-hostname ${BRAND.hostname}`);
773
+ }
774
+ }
775
+ }
776
+ catch (err) {
777
+ console.error(` WARNING: Could not detect hostname: ${err instanceof Error ? err.message : String(err)}`);
778
+ }
779
+ // Read the actual hostname after any changes — used for user-facing URLs.
780
+ try {
781
+ DEVICE_HOSTNAME = execFileSync("hostname", [], { encoding: "utf-8" }).trim();
782
+ }
783
+ catch { /* fallback to brand — set at declaration */ }
784
+ // If we attempted to set the hostname but it didn't take, use the brand
785
+ // hostname for the completion URL. The user needs the URL that will work
786
+ // after a reboot or manual hostname fix — not the stale factory default.
787
+ if (hostnameSetAttempted && DEVICE_HOSTNAME !== BRAND.hostname) {
788
+ console.error(` WARNING: Hostname is still '${DEVICE_HOSTNAME}' — using '${BRAND.hostname}' for the completion URL.`);
789
+ DEVICE_HOSTNAME = BRAND.hostname;
790
+ }
791
+ }
792
+ // Avahi service file for LAN discovery
793
+ const avahiService = `<?xml version="1.0" standalone='no'?>
794
+ <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
795
+ <service-group>
796
+ <name replace-wildcards="yes">${BRAND.productName} on %h</name>
797
+ <service>
798
+ <type>_http._tcp</type>
799
+ <port>${PORT}</port>
800
+ <txt-record>role=${BRAND.hostname}</txt-record>
801
+ </service>
802
+ </service-group>`;
803
+ const avahiTmpPath = `/tmp/${BRAND.hostname}-avahi.service`;
804
+ const avahiDestPath = `/etc/avahi/services/${BRAND.hostname}.service`;
805
+ try {
806
+ writeFileSync(avahiTmpPath, avahiService);
807
+ console.log(" [privileged] cp");
808
+ shell("cp", [avahiTmpPath, avahiDestPath], { sudo: true });
809
+ console.log(" [privileged] systemctl enable");
810
+ shell("systemctl", ["enable", "avahi-daemon"], { sudo: true });
811
+ console.log(" [privileged] systemctl restart");
812
+ shell("systemctl", ["restart", "avahi-daemon"], { sudo: true });
813
+ }
814
+ catch { /* not critical */ }
815
+ // Hostname collision detection: check if another device already claims this hostname.
816
+ // Avahi appends -2 on collision, which causes the agent to construct URLs to the wrong device.
817
+ try {
818
+ const resolveResult = spawnSync("avahi-resolve", ["-n", `${BRAND.hostname}.local`], {
819
+ encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
820
+ });
821
+ if (resolveResult.status === 0 && resolveResult.stdout.trim()) {
822
+ const resolvedIp = resolveResult.stdout.trim().split(/\s+/)[1];
823
+ // Get this device's IP addresses to compare
824
+ const hostnameResult = spawnSync("hostname", ["-I"], { encoding: "utf-8", timeout: 3000 });
825
+ const localIps = hostnameResult.stdout?.trim().split(/\s+/) ?? [];
826
+ if (resolvedIp && !localIps.includes(resolvedIp)) {
827
+ console.log(` WARNING: ${BRAND.hostname}.local already resolves to ${resolvedIp} — this device may get ${BRAND.hostname}-2.local`);
828
+ logFile(` WARNING: hostname collision detected — ${BRAND.hostname}.local resolves to ${resolvedIp}, local IPs: ${localIps.join(", ")}`);
829
+ }
830
+ }
831
+ }
832
+ catch { /* non-critical — avahi-resolve may not be ready yet */ }
833
+ // Disable WiFi power management — the Pi's WiFi driver aggressively sleeps
834
+ // the radio between transmissions, causing dropped DNS/mDNS packets and
835
+ // intermittent connection resets on LAN.
836
+ try {
837
+ const nmConfDir = "/etc/NetworkManager/conf.d";
838
+ const nmConfFile = join(nmConfDir, `${BRAND.hostname}-no-powersave.conf`);
839
+ if (existsSync("/usr/bin/nmcli") && !existsSync(nmConfFile)) {
840
+ console.log(" Disabling WiFi power save...");
841
+ writeFileSync(`/tmp/${BRAND.hostname}-no-powersave.conf`, "[connection]\nwifi.powersave = 2\n");
842
+ console.log(" [privileged] cp");
843
+ shell("cp", [`/tmp/${BRAND.hostname}-no-powersave.conf`, nmConfFile], { sudo: true });
844
+ spawnSync("sudo", ["systemctl", "restart", "NetworkManager"], { stdio: "pipe" });
845
+ }
846
+ }
847
+ catch { /* not critical — wired connections unaffected */ }
848
+ console.log(` Device reachable at http://${DEVICE_HOSTNAME}.local:${PORT}`);
849
+ }
850
+ function installNodejs() {
851
+ if (commandExists("node") && nodeVersion() >= 20) {
852
+ log("2", TOTAL, `Node.js v${nodeVersion()} already installed.`);
853
+ return;
854
+ }
855
+ log("2", TOTAL, "Installing Node.js...");
856
+ const platform = requireSupportedPlatform(process.platform);
857
+ if (platform === "darwin") {
858
+ // Pass the apt-side `nodejs` name; decideBrewResolution maps it to
859
+ // `node@22` per DARWIN_ALIASES so the cellar formula matches Maxy's
860
+ // pinned-binaries floor (Task 836).
861
+ installAllBrewPackages(["nodejs"], logFile);
862
+ return;
863
+ }
864
+ spawnSync("bash", ["-c", "curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"], { stdio: "inherit" });
865
+ console.log(" [privileged] apt-get install");
866
+ shell("apt-get", ["install", "-y", "nodejs"], { sudo: true });
867
+ }
868
+ function installClaudeCode() {
869
+ let needsInstall = true;
870
+ if (commandExists("claude")) {
871
+ try {
872
+ // `claude --version` prints "2.1.114 (Claude Code)" — extract the semver so
873
+ // the equality check against `npm view` (which returns bare "2.1.114") works.
874
+ const rawVersion = execFileSync("claude", ["--version"], { encoding: "utf-8", timeout: 10_000 }).trim();
875
+ const installed = rawVersion.match(/^(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/)?.[1] ?? rawVersion;
876
+ let latest = null;
877
+ try {
878
+ latest = execFileSync("npm", ["view", "@anthropic-ai/claude-code", "version"], { encoding: "utf-8", timeout: 30_000 }).trim();
879
+ }
880
+ catch {
881
+ logFile(" Could not check latest version — will attempt install anyway");
882
+ }
883
+ if (latest && installed === latest) {
884
+ log("3", TOTAL, `Claude Code v${installed} already up to date.`);
885
+ needsInstall = false;
886
+ }
887
+ else {
888
+ log("3", TOTAL, `Upgrading Claude Code (${installed}${latest ? ` → ${latest}` : ""})...`);
889
+ }
890
+ }
891
+ catch {
892
+ log("3", TOTAL, "Claude Code found but version check failed. Reinstalling...");
893
+ }
894
+ }
895
+ else {
896
+ log("3", TOTAL, "Installing Claude Code...");
897
+ }
898
+ if (needsInstall) {
899
+ // `npm install -g` needs write access to the global prefix, which on Linux is
900
+ // root-owned by default — so we run it under sudo. When sudo requires a password
901
+ // and the installer is running non-interactively (e.g. systemd-run --scope on
902
+ // upgrade), sudo fails instantly. Skip the upgrade in that case; the running
903
+ // installation is assumed adequate. Matches the apt-get skip in step 1.
904
+ if (isLinux() && !canSudo()) {
905
+ console.log(" Skipping Claude Code upgrade (sudo unavailable non-interactively — keeping installed version)");
906
+ }
907
+ else {
908
+ console.log(" This may take 15–30 minutes on Raspberry Pi...");
909
+ console.log(" [privileged] npm install -g @anthropic-ai/claude-code@latest");
910
+ shellRetry("npm", ["install", "-g", ...NPM_NET_FLAGS, "--loglevel", "verbose", "@anthropic-ai/claude-code@latest"], { sudo: true, timeout: 2_400_000 }, // 40 min — Pi downloads can take 25+ min
911
+ 3, 30);
912
+ }
913
+ }
914
+ console.log(" Registering Claude plugin marketplaces...");
915
+ const marketplaceList = spawnSync("claude", ["plugin", "marketplace", "list"], { stdio: "pipe", encoding: "utf-8" });
916
+ if (marketplaceList.stderr)
917
+ process.stderr.write(marketplaceList.stderr);
918
+ const listed = marketplaceList.stdout ?? "";
919
+ // Task 976: register three Anthropic marketplaces uniformly with
920
+ // [plugin-marketplace] observability. Each slug is grep-checked against
921
+ // `marketplace list` for idempotence; failures log exit + first stderr
922
+ // line but do not abort the installer (one marketplace's outage must not
923
+ // block the others).
924
+ // `slug` is the GitHub repo path passed to `marketplace add`. `marketplaceName`
925
+ // is the marketplace's declared `name` from its .claude-plugin/marketplace.json —
926
+ // what `marketplace list` prints and what `<plugin>@<marketplace>` resolves on
927
+ // install. For FSI the two diverge: repo `anthropics/financial-services`,
928
+ // marketplace name `claude-for-financial-services`. The idempotence check
929
+ // greps the declared name to avoid substring false-positives.
930
+ const MARKETPLACES = [
931
+ { slug: "anthropics/claude-plugins-official", marketplaceName: "claude-plugins-official" },
932
+ { slug: "anthropics/knowledge-work-plugins", marketplaceName: "knowledge-work-plugins" },
933
+ { slug: "anthropics/financial-services", marketplaceName: "claude-for-financial-services" },
934
+ ];
935
+ for (const { slug, marketplaceName } of MARKETPLACES) {
936
+ if (listed.includes(marketplaceName)) {
937
+ logFile(`[plugin-marketplace] added ${slug} idempotent=true`);
938
+ continue;
939
+ }
940
+ const add = spawnSync("claude", ["plugin", "marketplace", "add", slug], { stdio: "pipe", encoding: "utf-8", timeout: 60_000 });
941
+ if (add.status === 0) {
942
+ logFile(`[plugin-marketplace] added ${slug} idempotent=false`);
943
+ }
944
+ else {
945
+ const stderrShort = (add.stderr ?? "").split("\n")[0]?.slice(0, 200) ?? "";
946
+ logFile(`[plugin-marketplace] ERROR add ${slug} exit=${add.status} stderr=${JSON.stringify(stderrShort)}`);
947
+ }
948
+ }
949
+ console.log(" Configuring git to use HTTPS for GitHub (plugin install support)...");
950
+ shell("git", ["config", "--global", "url.https://github.com/.insteadOf", "git@github.com:"]);
951
+ // Remove the Playwright plugin if previously installed. The programmatic MCP
952
+ // server in claude-agent.ts (with --cdp-endpoint) is the single correct path.
953
+ // The plugin creates a shadow server that intermittently handles tool calls
954
+ // without --cdp-endpoint, causing Chrome launch failures on ARM64.
955
+ const pluginList = spawnSync("claude", ["plugin", "list"], { stdio: "pipe", encoding: "utf-8" });
956
+ if (pluginList.stdout?.includes("playwright")) {
957
+ console.log(" Removing Playwright plugin (replaced by programmatic CDP server)...");
958
+ spawnSync("claude", ["plugin", "uninstall", "playwright"], { stdio: "inherit" });
959
+ }
960
+ // Ensure @playwright/mcp and all its dependencies (including playwright-core)
961
+ // are cached. Wipe any stale npx cache for it first — killed installs on Pi
962
+ // leave corrupt temp dirs that block subsequent npm operations.
963
+ console.log(" Pre-caching Playwright MCP server...");
964
+ const npxDir = resolve(process.env.HOME ?? "/root", ".npm/_npx");
965
+ const findResult = spawnSync("find", [npxDir, "-path", "*/@playwright/mcp/package.json", "-print", "-quit"], {
966
+ encoding: "utf-8", stdio: "pipe",
967
+ });
968
+ const existingCache = findResult.stdout?.trim();
969
+ if (existingCache) {
970
+ // Nuke the entire npx cache entry to avoid ENOTEMPTY errors
971
+ const cacheEntry = resolve(existingCache, "../../..");
972
+ spawnSync("rm", ["-rf", cacheEntry], { stdio: "pipe" });
973
+ }
974
+ // Fresh install — npx creates a clean cache with all deps
975
+ const npxResult = spawnSync("npx", ["-y", "@playwright/mcp@latest", "--version"], {
976
+ stdio: "pipe",
977
+ timeout: 180000,
978
+ encoding: "utf-8",
979
+ });
980
+ // Verify playwright-core landed
981
+ const verifyResult = spawnSync("find", [npxDir, "-path", "*/playwright-core/package.json", "-print", "-quit"], {
982
+ encoding: "utf-8", stdio: "pipe",
983
+ });
984
+ if (verifyResult.stdout?.trim()) {
985
+ console.log(" Playwright MCP server cached with all dependencies.");
986
+ }
987
+ else {
988
+ console.log(` Warning: Playwright MCP cache may be incomplete (browser automation may not work).${npxResult.stderr ? ` npx stderr: ${npxResult.stderr.slice(0, 200)}` : ""}`);
989
+ }
990
+ }
991
+ function resetNeo4jAuth(port = DEFAULT_NEO4J_PORT, dataDir = "/var/lib/neo4j") {
992
+ const password = randomBytes(24).toString("base64url");
993
+ const dedicated = port !== DEFAULT_NEO4J_PORT;
994
+ const serviceName = dedicated ? `neo4j-${BRAND.hostname}` : "neo4j";
995
+ console.log(` Resetting Neo4j auth with fresh password (${serviceName})...`);
996
+ spawnSync("sudo", ["systemctl", "stop", serviceName], { stdio: "inherit" });
997
+ // Clear the system database (stores auth/roles) and dbms auth config.
998
+ // The neo4j user database (graph data) is preserved.
999
+ // set-initial-password only works before the system DB's first start,
1000
+ // so we must delete it to make Neo4j treat the next start as initial.
1001
+ spawnSync("sudo", ["rm", "-rf",
1002
+ `${dataDir}/data/dbms`,
1003
+ `${dataDir}/data/databases/system`,
1004
+ `${dataDir}/data/transactions/system`,
1005
+ ], { stdio: "inherit" });
1006
+ if (dedicated) {
1007
+ const confDir = `/etc/neo4j-${BRAND.hostname}`;
1008
+ // sudo env VAR=val passes the variable through sudo's env_reset
1009
+ spawnSync("sudo", ["env", `NEO4J_CONF=${confDir}`, "neo4j-admin", "dbms", "set-initial-password", "--", password], {
1010
+ stdio: "inherit",
1011
+ });
1012
+ }
1013
+ else {
1014
+ console.log(" [privileged] neo4j-admin dbms");
1015
+ shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { sudo: true, redact: true });
1016
+ }
1017
+ console.log(" [privileged] systemctl start");
1018
+ shell("systemctl", ["start", serviceName], { sudo: true });
1019
+ console.log(" Waiting for Neo4j to start...");
1020
+ for (let i = 0; i < 15; i++) {
1021
+ const check = spawnSync("cypher-shell", [
1022
+ "-u", "neo4j", "-p", password,
1023
+ "-a", `bolt://localhost:${port}`,
1024
+ "RETURN 1",
1025
+ ], { stdio: "pipe", timeout: 5000 });
1026
+ if (check.status === 0)
1027
+ break;
1028
+ spawnSync("sleep", ["2"]);
1029
+ }
1030
+ return password;
1031
+ }
1032
+ /**
1033
+ * Task 744 — scrub plaintext neo4j passwords from pre-fix install-*.log files.
1034
+ * Calls platform/scripts/redact-install-logs.sh against the installer's LOG_DIR.
1035
+ * The script is idempotent; re-running on clean logs is a no-op. Failures here
1036
+ * are non-fatal — credential redaction is best-effort cleanup, not a blocker
1037
+ * for installation.
1038
+ */
1039
+ function redactInstallLogs() {
1040
+ const script = resolve(INSTALL_DIR, "platform/scripts/redact-install-logs.sh");
1041
+ if (!existsSync(script)) {
1042
+ logFile("[redact-install-logs] script not found at " + script + " — skipping");
1043
+ return;
1044
+ }
1045
+ const r = spawnSync("bash", [script, "--dir", LOG_DIR], {
1046
+ stdio: "pipe",
1047
+ encoding: "utf-8",
1048
+ timeout: 30_000,
1049
+ });
1050
+ if (r.stdout)
1051
+ logFile(r.stdout.trim());
1052
+ if (r.status !== 0 && r.stderr)
1053
+ logFile("[redact-install-logs] WARN " + r.stderr.trim());
1054
+ }
1055
+ /** Check Neo4j has a working password. Called AFTER deploy so config is in place. */
1056
+ function ensureNeo4jPassword() {
1057
+ const passwordFile = join(INSTALL_DIR, "platform/config/.neo4j-password");
1058
+ const configDir = resolve(INSTALL_DIR, "platform/config");
1059
+ mkdirSync(configDir, { recursive: true });
1060
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
1061
+ const persistentPasswordFile = join(persistDir, ".neo4j-password");
1062
+ // Dedicated instances have their own auth database — password checks and resets
1063
+ // must target the dedicated port and data directory, not the shared instance.
1064
+ const dataDir = NEO4J_DEDICATED ? `/var/lib/neo4j-${BRAND.hostname}` : "/var/lib/neo4j";
1065
+ // 1. Same-brand check: if our own password file exists and works, done (upgrade path).
1066
+ if (existsSync(passwordFile)) {
1067
+ const existingPassword = readFileSync(passwordFile, "utf-8").trim();
1068
+ if (neo4jPasswordWorks(existingPassword, NEO4J_PORT)) {
1069
+ logFile(` Neo4j password: already configured (port ${NEO4J_PORT})`);
1070
+ return;
1071
+ }
1072
+ if (NEO4J_DEDICATED) {
1073
+ console.log(" Stored password doesn't match dedicated Neo4j instance.");
1074
+ }
1075
+ else {
1076
+ console.log(" Stored password doesn't match Neo4j. Resetting auth.");
1077
+ }
1078
+ }
1079
+ // 2. Fresh install or recovery: no working same-brand password. Generate and set a new one.
1080
+ // Brand isolation (Task 659): never read another brand's .neo4j-password.
1081
+ if (!existsSync(passwordFile)) {
1082
+ console.log(" No Neo4j password file found. Setting initial password...");
1083
+ }
1084
+ // Reset auth and set a new password. Graph data is preserved —
1085
+ // only the auth config ({dataDir}/data/dbms/) is cleared.
1086
+ const hasData = existsSync(`${dataDir}/data/databases/neo4j`);
1087
+ if (hasData) {
1088
+ console.log(" Neo4j has existing data. Resetting auth only (data preserved)...");
1089
+ logFile(` Neo4j auth reset — clearing dbms auth in ${dataDir}, preserving databases + transactions`);
1090
+ }
1091
+ const password = resetNeo4jAuth(NEO4J_PORT, dataDir);
1092
+ writeFileSync(passwordFile, password, { mode: 0o600 });
1093
+ mkdirSync(persistDir, { recursive: true });
1094
+ writeFileSync(persistentPasswordFile, password, { mode: 0o600 });
1095
+ logFile(" Neo4j password: generated new");
1096
+ }
1097
+ /** Test a Neo4j password against the running instance on the given port. */
1098
+ function neo4jPasswordWorks(password, port = DEFAULT_NEO4J_PORT) {
1099
+ const check = spawnSync("cypher-shell", [
1100
+ "-u", "neo4j", "-p", password,
1101
+ "-a", `bolt://localhost:${port}`,
1102
+ "RETURN 1",
1103
+ ], { stdio: "pipe", timeout: 10000 });
1104
+ return check.status === 0;
1105
+ }
1106
+ function installNeo4j() {
1107
+ if (commandExists("neo4j")) {
1108
+ log("4", TOTAL, "Neo4j already installed.");
1109
+ return;
1110
+ }
1111
+ log("4", TOTAL, "Installing Neo4j Community Edition 5...");
1112
+ const platform = requireSupportedPlatform(process.platform);
1113
+ if (platform === "darwin") {
1114
+ // Homebrew's neo4j formula declares openjdk as a runtime dependency, so
1115
+ // Java is pulled in transitively — no separate openjdk install step.
1116
+ // Process supervision (launchd LaunchAgent) is owned by Task 838; this
1117
+ // step ends after `brew install neo4j` + initial-password seeding so the
1118
+ // payload deploy step finds the shared password file.
1119
+ installAllBrewPackages(["neo4j"], logFile);
1120
+ // Generate strong random password — stored in persistent location (~/{configDir}/)
1121
+ const password = randomBytes(24).toString("base64url");
1122
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
1123
+ mkdirSync(persistDir, { recursive: true });
1124
+ writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
1125
+ const configDir = resolve(INSTALL_DIR, "platform/config");
1126
+ mkdirSync(configDir, { recursive: true });
1127
+ writeFileSync(join(configDir, ".neo4j-password"), password, { mode: 0o600 });
1128
+ // Persist Neo4j data under ~/.maxy/neo4j-data/ per the task brief, so a
1129
+ // brew uninstall (which removes the cellar) does not destroy the graph.
1130
+ const neo4jDataDir = resolve(process.env.HOME ?? "/root", BRAND.configDir, "neo4j-data");
1131
+ mkdirSync(neo4jDataDir, { recursive: true });
1132
+ shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { redact: true });
1133
+ console.log(" Neo4j installed via Homebrew. Password stored securely.");
1134
+ return;
1135
+ }
1136
+ // Neo4j 5.x supports Java 17 and 21. Debian Bookworm ships 17, Trixie ships 21.
1137
+ // apt-cache policy shows "Candidate: (none)" when no installable version exists.
1138
+ const policyResult = spawnSync("apt-cache", ["policy", "openjdk-17-jre-headless"], { stdio: "pipe" });
1139
+ const policyOutput = policyResult.stdout?.toString() ?? "";
1140
+ const has17 = policyResult.status === 0 && !policyOutput.includes("Candidate: (none)");
1141
+ const javaPackage = has17 ? "openjdk-17-jre-headless" : "openjdk-21-jre-headless";
1142
+ console.log(` Installing Java (${javaPackage})...`);
1143
+ console.log(" [privileged] apt-get install");
1144
+ shell("apt-get", ["install", "-y", javaPackage], { sudo: true });
1145
+ spawnSync("bash", ["-c", "curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key | sudo gpg --yes --dearmor -o /usr/share/keyrings/neo4j.gpg 2>/dev/null"], { stdio: "inherit" });
1146
+ spawnSync("bash", ["-c", 'echo "deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable 5" | sudo tee /etc/apt/sources.list.d/neo4j.list'], { stdio: "inherit" });
1147
+ console.log(" [privileged] apt-get update");
1148
+ shell("apt-get", ["update"], { sudo: true });
1149
+ console.log(" [privileged] apt-get install");
1150
+ shell("apt-get", ["install", "-y", "neo4j"], { sudo: true });
1151
+ console.log(" [privileged] sed -i");
1152
+ shell("sed", ["-i", "s/#server.default_listen_address=0.0.0.0/server.default_listen_address=127.0.0.1/", "/etc/neo4j/neo4j.conf"], { sudo: true });
1153
+ // Generate strong random password — stored in persistent location (~/{configDir}/)
1154
+ const password = randomBytes(24).toString("base64url");
1155
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
1156
+ mkdirSync(persistDir, { recursive: true });
1157
+ writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
1158
+ // Also write to install dir (will be there when deploy step runs)
1159
+ const configDir = resolve(INSTALL_DIR, "platform/config");
1160
+ mkdirSync(configDir, { recursive: true });
1161
+ writeFileSync(join(configDir, ".neo4j-password"), password, { mode: 0o600 });
1162
+ console.log(" [privileged] neo4j-admin dbms");
1163
+ shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { sudo: true, redact: true });
1164
+ console.log(" [privileged] systemctl enable");
1165
+ shell("systemctl", ["enable", "neo4j"], { sudo: true });
1166
+ console.log(" [privileged] systemctl start");
1167
+ shell("systemctl", ["start", "neo4j"], { sudo: true });
1168
+ console.log(" Neo4j started. Password stored securely.");
1169
+ }
1170
+ /**
1171
+ * Task 800 — does any peer brand on this host pin `NEO4J_URI=bolt://localhost:<default>`
1172
+ * in its `.env`? If yes, the apt-installed `neo4j.service` is its database and the
1173
+ * dedicated-unit installer must NOT stop+disable it. Returns the first matching
1174
+ * peer's hostname, or `null` when no peer pins the default port.
1175
+ *
1176
+ * Wraps the pure decision in `peer-brand-detect.ts` with the fs reads of
1177
+ * `~/.<peer>/.env`. Bias on read errors: if `.env` exists but is unreadable
1178
+ * (permissions, transient I/O), the wrapper treats that peer as a *potential*
1179
+ * dependency and short-circuits to the kept-active path. Disabling the system
1180
+ * unit on faulty evidence would silently kill the peer's database (the exact
1181
+ * failure Task 800 prevents); a conservatively-skipped disable is recoverable
1182
+ * because the dedicated-unit bind check at the end of `setupDedicatedNeo4j`
1183
+ * fails loud if the system unit is actually free.
1184
+ */
1185
+ function peerBrandUsingSystemUnit() {
1186
+ const home = process.env.HOME ?? "/root";
1187
+ const peerEnvContents = [];
1188
+ for (const hostname of KNOWN_BRAND_HOSTNAMES) {
1189
+ if (hostname === BRAND.hostname) {
1190
+ peerEnvContents.push([hostname, null]);
1191
+ continue;
1192
+ }
1193
+ const envPath = resolve(home, `.${hostname}`, ".env");
1194
+ if (!existsSync(envPath)) {
1195
+ peerEnvContents.push([hostname, null]);
1196
+ continue;
1197
+ }
1198
+ try {
1199
+ peerEnvContents.push([hostname, readFileSync(envPath, "utf-8")]);
1200
+ }
1201
+ catch (err) {
1202
+ console.error(` WARNING: unable to read peer brand .env at ${envPath} — treating as potential dependency to avoid data loss: ${err instanceof Error ? err.message : String(err)}`);
1203
+ return hostname;
1204
+ }
1205
+ }
1206
+ return findPeerBrandOnDefaultNeo4jPort({
1207
+ currentBrandHostname: BRAND.hostname,
1208
+ defaultNeo4jPort: DEFAULT_NEO4J_PORT,
1209
+ peerEnvContents,
1210
+ });
1211
+ }
1212
+ /**
1213
+ * Create a dedicated Neo4j instance for this brand when NEO4J_DEDICATED is true.
1214
+ * Produces: separate config dir, data dir, log dir, systemd service, and password.
1215
+ * On upgrade (config already exists), skips conf creation — but always runs the
1216
+ * Task 787 state-remediation block (stop/disable system unit, reset-failed
1217
+ * dedicated, start, verify) so a half-installed Pi recovers in-place without
1218
+ * manual systemctl. ensureNeo4jPassword() handles password verification on the
1219
+ * recovery path.
1220
+ *
1221
+ * Task 800: on multi-brand hosts where a peer brand still depends on the apt
1222
+ * `neo4j.service` (port 7687), the stop+disable step is skipped — disabling
1223
+ * the system unit would kill the peer's database.
1224
+ */
1225
+ function setupDedicatedNeo4j() {
1226
+ if (!NEO4J_DEDICATED)
1227
+ return;
1228
+ const brandSuffix = BRAND.hostname; // e.g., "realagent"
1229
+ const confDir = `/etc/neo4j-${brandSuffix}`;
1230
+ const dataDir = `/var/lib/neo4j-${brandSuffix}`;
1231
+ const logDir = `/var/log/neo4j-${brandSuffix}`;
1232
+ const serviceName = `neo4j-${brandSuffix}`;
1233
+ const httpPort = NEO4J_PORT - 213; // Preserve standard 7687/7474 offset
1234
+ // Per-brand state (sed, mkdir, chown, unit-write) is idempotent and runs on
1235
+ // every install. Only the base-config copy and initial-password rotation are
1236
+ // gated to first install — Task 787 established that state remediation must
1237
+ // run every install; Task 979 extended the same principle to the conf and
1238
+ // unit emission so a half-installed host (broken unit missing NEO4J_HOME)
1239
+ // recovers on retry without an out-of-band manual reset.
1240
+ const confExists = spawnSync("test", ["-f", `${confDir}/neo4j.conf`], { stdio: "pipe" }).status === 0;
1241
+ if (confExists) {
1242
+ console.log(` Dedicated Neo4j instance for ${BRAND.productName} already configured at ${confDir} — re-applying per-brand state`);
1243
+ logFile(` Neo4j dedicated: existing config at ${confDir}, re-applying sed/mkdir/chown/unit on every install`);
1244
+ }
1245
+ else {
1246
+ console.log(` Setting up dedicated Neo4j instance for ${BRAND.productName} on bolt://localhost:${NEO4J_PORT}...`);
1247
+ // Pre-check: neo4j user must exist (created by the apt package)
1248
+ const neo4jUserCheck = spawnSync("id", ["neo4j"], { stdio: "pipe" });
1249
+ if (neo4jUserCheck.status !== 0) {
1250
+ throw new Error("Neo4j system user 'neo4j' not found. Is Neo4j installed via apt?");
1251
+ }
1252
+ // Pre-check: source config must exist
1253
+ if (!existsSync("/etc/neo4j/neo4j.conf")) {
1254
+ throw new Error("/etc/neo4j/neo4j.conf not found. Cannot create dedicated instance without base config.");
1255
+ }
1256
+ // Copy base config (first install only — sed runs unconditionally below)
1257
+ console.log(" [privileged] cp -r");
1258
+ shell("cp", ["-r", "/etc/neo4j", confDir], { sudo: true });
1259
+ }
1260
+ // Idempotent per-brand state — runs on every install (Task 979).
1261
+ // sed `s|^#?key=.*|key=value|` no-ops when the file already has the target
1262
+ // value; mkdir -p, chown -R, and unit-write are idempotent by definition.
1263
+ // NEO4J_HOME=${dataDir} makes server.directories.run resolve per-brand to
1264
+ // ${dataDir}/run, so the launcher's pre-flight does not collide with the
1265
+ // system unit's /var/lib/neo4j/run/neo4j.pid (Task 979 root cause). Plugins
1266
+ // and import are sed-overridden because the apt base conf pins them to
1267
+ // absolute /var/lib/neo4j/plugins and /var/lib/neo4j/import (shared).
1268
+ console.log(" [privileged] sed -i");
1269
+ shell("sed", ["-i", `s/^#\\?server\\.bolt\\.listen_address=.*/server.bolt.listen_address=:${NEO4J_PORT}/`, `${confDir}/neo4j.conf`], { sudo: true });
1270
+ console.log(" [privileged] sed -i");
1271
+ shell("sed", ["-i", `s/^#\\?server\\.http\\.listen_address=.*/server.http.listen_address=:${httpPort}/`, `${confDir}/neo4j.conf`], { sudo: true });
1272
+ console.log(" [privileged] sed -i");
1273
+ shell("sed", ["-i", `s|^#\\?server\\.directories\\.data=.*|server.directories.data=${dataDir}/data|`, `${confDir}/neo4j.conf`], { sudo: true });
1274
+ console.log(" [privileged] sed -i");
1275
+ shell("sed", ["-i", `s|^#\\?server\\.directories\\.logs=.*|server.directories.logs=${logDir}|`, `${confDir}/neo4j.conf`], { sudo: true });
1276
+ console.log(" [privileged] sed -i");
1277
+ shell("sed", ["-i", `s|^#\\?server\\.directories\\.plugins=.*|server.directories.plugins=${dataDir}/plugins|`, `${confDir}/neo4j.conf`], { sudo: true });
1278
+ console.log(" [privileged] sed -i");
1279
+ shell("sed", ["-i", `s|^#\\?server\\.directories\\.import=.*|server.directories.import=${dataDir}/import|`, `${confDir}/neo4j.conf`], { sudo: true });
1280
+ // Verify config was updated — sed silently no-ops if the key format changed
1281
+ const confContent = spawnSync("grep", [`server.bolt.listen_address=:${NEO4J_PORT}`, `${confDir}/neo4j.conf`], { stdio: "pipe" });
1282
+ if (confContent.status !== 0) {
1283
+ console.error(` WARNING: neo4j.conf may not have been updated correctly — bolt port ${NEO4J_PORT} not found in config`);
1284
+ logFile(` WARNING: sed verification failed — bolt port ${NEO4J_PORT} not found in ${confDir}/neo4j.conf`);
1285
+ }
1286
+ console.log(" [privileged] mkdir -p");
1287
+ shell("mkdir", ["-p", `${dataDir}/data`, `${dataDir}/plugins`, `${dataDir}/import`, logDir], { sudo: true });
1288
+ console.log(" [privileged] chown -R");
1289
+ shell("chown", ["-R", "neo4j:neo4j", dataDir, logDir, confDir], { sudo: true });
1290
+ const serviceContent = `[Unit]
1291
+ Description=Neo4j Graph Database (${BRAND.productName})
1292
+ After=network-online.target
1293
+ Wants=network-online.target
1294
+
1295
+ [Service]
1296
+ ExecStart=/usr/bin/neo4j console
1297
+ Restart=on-failure
1298
+ User=neo4j
1299
+ Group=neo4j
1300
+ Environment="NEO4J_CONF=${confDir}" "NEO4J_HOME=${dataDir}"
1301
+ LimitNOFILE=60000
1302
+
1303
+ [Install]
1304
+ WantedBy=multi-user.target
1305
+ `;
1306
+ const tmpServicePath = `/tmp/${serviceName}.service`;
1307
+ writeFileSync(tmpServicePath, serviceContent);
1308
+ console.log(" [privileged] cp");
1309
+ shell("cp", [tmpServicePath, `/etc/systemd/system/${serviceName}.service`], { sudo: true });
1310
+ spawnSync("rm", ["-f", tmpServicePath]);
1311
+ logFile(` [neo4j] dedicated unit env: NEO4J_CONF=${confDir} NEO4J_HOME=${dataDir}`);
1312
+ if (!confExists) {
1313
+ // Set initial password before first start (first install only — rotation
1314
+ // on retry would brick an existing DB whose password is already stored).
1315
+ const password = randomBytes(24).toString("base64url");
1316
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
1317
+ mkdirSync(persistDir, { recursive: true });
1318
+ writeFileSync(join(persistDir, ".neo4j-password"), password, { mode: 0o600 });
1319
+ const configDir = resolve(INSTALL_DIR, "platform/config");
1320
+ mkdirSync(configDir, { recursive: true });
1321
+ writeFileSync(join(configDir, ".neo4j-password"), password, { mode: 0o600 });
1322
+ // sudo env VAR=val passes NEO4J_CONF through sudo's env_reset policy
1323
+ spawnSync("sudo", ["env", `NEO4J_CONF=${confDir}`, "neo4j-admin", "dbms", "set-initial-password", "--", password], {
1324
+ stdio: "inherit",
1325
+ });
1326
+ }
1327
+ // ============================================================================
1328
+ // Task 787 — unified state remediation + start + verify.
1329
+ //
1330
+ // Runs on both fresh and recovery paths. The dedicated unit and the apt
1331
+ // package's system unit both exec /usr/bin/neo4j console without overriding
1332
+ // NEO4J_HOME, so server.directories.run resolves to /var/lib/neo4j/run for
1333
+ // both — the launcher refuses with "Neo4j is already running (pid:N)" if
1334
+ // the system unit holds the PID file. Stopping the system unit first frees
1335
+ // the run-state; disabling prevents it returning at next boot. reset-failed
1336
+ // clears any prior start-limit-hit from a half-installed Pi.
1337
+ // ============================================================================
1338
+ spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
1339
+ console.log(" [privileged] systemctl enable");
1340
+ shell("systemctl", ["enable", serviceName], { sudo: true });
1341
+ // Task 800: skip stop+disable when a peer brand on this host still depends
1342
+ // on the apt `neo4j.service` (port 7687). Disabling it would kill the peer's
1343
+ // database — Task 797 reproducer on Neo's laptop. The kept-active path is
1344
+ // mutually exclusive with the disable path: exactly one log line per install.
1345
+ const peerOnSystemUnit = peerBrandUsingSystemUnit();
1346
+ if (peerOnSystemUnit !== null) {
1347
+ const keptActiveMsg = ` [neo4j] system unit kept active — peer brand ${peerOnSystemUnit} depends on port ${DEFAULT_NEO4J_PORT}`;
1348
+ console.log(keptActiveMsg);
1349
+ logFile(keptActiveMsg);
1350
+ }
1351
+ else {
1352
+ console.log(` [neo4j] disabling system unit (brand-dedicated active on port ${NEO4J_PORT})`);
1353
+ logFile(` [neo4j] disabling system unit (brand-dedicated active on port ${NEO4J_PORT})`);
1354
+ shell("systemctl", ["stop", "neo4j"], { sudo: true });
1355
+ shell("systemctl", ["disable", "neo4j"], { sudo: true });
1356
+ }
1357
+ console.log(` [neo4j] reset-failed ${serviceName} before start`);
1358
+ logFile(` [neo4j] reset-failed ${serviceName} before start`);
1359
+ shell("systemctl", ["reset-failed", serviceName], { sudo: true, bestEffort: true });
1360
+ console.log(" [privileged] systemctl start");
1361
+ shell("systemctl", ["start", serviceName], { sudo: true });
1362
+ // Verify the dedicated unit bound its port. Password verification is
1363
+ // ensureNeo4jPassword()'s job (called next in the install pipeline) — that
1364
+ // function tests the stored password against this port and resets auth if
1365
+ // the password no longer matches the running instance.
1366
+ console.log(` Waiting for dedicated Neo4j instance on port ${NEO4J_PORT}...`);
1367
+ let listening = false;
1368
+ const portMatch = new RegExp(`:${NEO4J_PORT}\\b`);
1369
+ for (let i = 0; i < 15; i++) {
1370
+ const portCheck = spawnSync("ss", ["-tln"], { stdio: "pipe", timeout: 5000 });
1371
+ if (portMatch.test(portCheck.stdout?.toString() ?? "")) {
1372
+ listening = true;
1373
+ break;
1374
+ }
1375
+ spawnSync("sleep", ["2"]);
1376
+ }
1377
+ if (!listening) {
1378
+ // Loud failure — no silent fallback to the system instance (Task 787).
1379
+ const portCheck = spawnSync("ss", ["-tlnp"], { stdio: "pipe", timeout: 5000 });
1380
+ const portLines = (portCheck.stdout?.toString() ?? "").split("\n").filter((l) => l.includes(String(NEO4J_PORT)));
1381
+ const diagnostic = portLines.length > 0 ? portLines.join("; ") : "nothing listening on port";
1382
+ const journal = spawnSync("journalctl", ["-u", serviceName, "--since", "5 min ago"], { stdio: "pipe", timeout: 5000 });
1383
+ const journalTail = (journal.stdout?.toString() ?? "").split("\n").slice(-20).join("\n");
1384
+ logFile(` Neo4j dedicated: failed to bind port ${NEO4J_PORT} — ${diagnostic}`);
1385
+ throw new Error(`Dedicated Neo4j instance ${serviceName} did not bind bolt://localhost:${NEO4J_PORT} within 30s.\n` +
1386
+ `Port ${NEO4J_PORT}: ${diagnostic}\n` +
1387
+ `journalctl -u ${serviceName} --since "5 min ago" | tail -20:\n${journalTail}`);
1388
+ }
1389
+ logFile(` Neo4j dedicated: config=${confDir} data=${dataDir} service=${serviceName} bolt=:${NEO4J_PORT} http=:${httpPort}`);
1390
+ console.log(` Dedicated Neo4j instance ready on bolt://localhost:${NEO4J_PORT}`);
1391
+ }
1392
+ function ensureOllamaServing() {
1393
+ const check = () => spawnSync("curl", ["-sf", "http://localhost:11434/api/tags"], {
1394
+ stdio: "pipe", timeout: 5_000,
1395
+ });
1396
+ if (check().status === 0)
1397
+ return;
1398
+ // Log which ollama binary we're using and its version
1399
+ const which = spawnSync("which", ["ollama"], { stdio: "pipe" });
1400
+ const ollamaPath = which.stdout?.toString().trim() || "not found";
1401
+ logFile(` Ollama binary: ${ollamaPath}`);
1402
+ const version = spawnSync("ollama", ["--version"], { stdio: "pipe", timeout: 5_000 });
1403
+ logFile(` Ollama version: ${version.stdout?.toString().trim() || version.stderr?.toString().trim() || `exit ${version.status}`}`);
1404
+ // Check if systemd service exists
1405
+ const svcCheck = spawnSync("systemctl", ["cat", "ollama"], { stdio: "pipe", timeout: 5_000 });
1406
+ logFile(` Ollama systemd service: ${svcCheck.status === 0 ? "exists" : "not found"}`);
1407
+ // Check if port 11434 is already in use by something else
1408
+ const portCheck = spawnSync("ss", ["-tlnp"], { stdio: "pipe", timeout: 5_000 });
1409
+ const portLines = portCheck.stdout?.toString().split("\n").filter((l) => l.includes("11434")) || [];
1410
+ if (portLines.length > 0)
1411
+ logFile(` Port 11434 in use: ${portLines.join("; ")}`);
1412
+ console.log(" Starting Ollama server...");
1413
+ logFile(" Ollama server not responding — starting ollama serve");
1414
+ // Capture ollama serve output to a log file for diagnostics
1415
+ const ollamaLog = join(LOG_DIR, "ollama-serve.log");
1416
+ const logFd = openSync(ollamaLog, "a");
1417
+ const child = spawn("ollama", ["serve"], {
1418
+ stdio: ["ignore", logFd, logFd], detached: true,
1419
+ });
1420
+ child.unref();
1421
+ closeSync(logFd);
1422
+ logFile(` Spawned ollama serve (PID ${child.pid}), log: ${ollamaLog}`);
1423
+ for (let elapsed = 0; elapsed < 30; elapsed += 3) {
1424
+ spawnSync("sleep", ["3"]);
1425
+ // Check if process is still alive
1426
+ const alive = child.pid && spawnSync("kill", ["-0", String(child.pid)], { stdio: "pipe" }).status === 0;
1427
+ logFile(` Poll ${elapsed + 3}s: pid ${child.pid} alive=${alive}`);
1428
+ if (check().status === 0) {
1429
+ logFile(` Ollama server ready after ${elapsed + 3}s`);
1430
+ return;
1431
+ }
1432
+ if (!alive) {
1433
+ // Process died — read the log to find out why
1434
+ const serveLog = readFileSync(ollamaLog, "utf-8").slice(-2000);
1435
+ logFile(` Ollama serve exited. Log tail:\n${serveLog}`);
1436
+ console.error(` Ollama serve exited unexpectedly. Log: ${ollamaLog}`);
1437
+ throw new Error(`Ollama serve failed to start. Log: ${ollamaLog}\n${serveLog}`);
1438
+ }
1439
+ }
1440
+ // Timed out — include diagnostics
1441
+ const serveLog = readFileSync(ollamaLog, "utf-8").slice(-2000);
1442
+ logFile(` Ollama serve timed out after 30s. Log tail:\n${serveLog}`);
1443
+ throw new Error(`Ollama server did not respond within 30s. Log: ${ollamaLog}\n${serveLog}`);
1444
+ }
1445
+ function installOllama(embedModel) {
1446
+ if (!commandExists("ollama")) {
1447
+ log("5", TOTAL, "Installing Ollama...");
1448
+ spawnSync("bash", ["-c", "curl -fsSL https://ollama.ai/install.sh | sh"], { stdio: "inherit" });
1449
+ }
1450
+ else {
1451
+ log("5", TOTAL, "Ollama already installed.");
1452
+ }
1453
+ ensureOllamaServing();
1454
+ console.log(` Pulling ${embedModel} embedding model...`);
1455
+ logFile(` Pulling embedding model: ${embedModel}`);
1456
+ shellRetry("ollama", ["pull", embedModel], { timeout: 600_000 }, 3, 15);
1457
+ }
1458
+ // Task 557 — uv/uvx bootstrap. Required by the `graph` MCP server which
1459
+ // spawns `uvx mcp-neo4j-cypher@0.6.0 --transport stdio`. Idempotent: when
1460
+ // uvx is already on PATH (or under $HOME/.local/bin) we skip. Non-fatal on
1461
+ // failure — the installer continues; the graph server will loudly fail
1462
+ // at session start with a clear "uvx not found" error, which is retriable
1463
+ // via a second installer run with network access.
1464
+ function installUv() {
1465
+ if (commandExists("uvx")) {
1466
+ logFile(" uv: already installed");
1467
+ console.log(" uv/uvx already installed.");
1468
+ return;
1469
+ }
1470
+ console.log(" Installing uv (Python tool runner — required by Neo4j MCP server)...");
1471
+ logFile(" uv: installing via astral.sh installer");
1472
+ // astral.sh installer auto-confirms when stdin is not a TTY (our case under
1473
+ // systemd-run). Historically we passed `-y`, which the script rejects with
1474
+ // "unknown option -y" and causes uv to never install on upgrade.
1475
+ const result = spawnSync("bash", ["-c", "curl -LsSf https://astral.sh/uv/install.sh | sh"], { stdio: "inherit" });
1476
+ if (result.status !== 0) {
1477
+ console.error(` WARNING: uv install exited ${result.status} — graph MCP server will fail at session start until this is retried`);
1478
+ logFile(` WARNING: uv install failed with status ${result.status}`);
1479
+ return;
1480
+ }
1481
+ // The installer writes uvx to $HOME/.local/bin — add it to PATH for the
1482
+ // remainder of this install so commandExists("uvx") works downstream.
1483
+ const localBin = `${process.env.HOME}/.local/bin`;
1484
+ if (process.env.PATH && !process.env.PATH.includes(localBin)) {
1485
+ process.env.PATH = `${localBin}:${process.env.PATH}`;
1486
+ }
1487
+ if (commandExists("uvx")) {
1488
+ logFile(" uv: install succeeded; uvx on PATH");
1489
+ }
1490
+ else {
1491
+ console.error(" WARNING: uv installed but uvx not on PATH — check $HOME/.local/bin");
1492
+ }
1493
+ }
1494
+ function installCloudflared() {
1495
+ if (commandExists("cloudflared")) {
1496
+ log("6", TOTAL, "Cloudflared already installed.");
1497
+ return;
1498
+ }
1499
+ log("6", TOTAL, "Installing cloudflared...");
1500
+ const platform = requireSupportedPlatform(process.platform);
1501
+ if (platform === "darwin") {
1502
+ // Homebrew's `cloudflared` formula tracks the Cloudflare release stream;
1503
+ // tunnel-login flow stays unchanged (`feedback_no_api_token_route.md`).
1504
+ // `cloudflared service install` is invoked by the launchd supervisor in
1505
+ // Task 838 — out of scope here.
1506
+ installAllBrewPackages(["cloudflared"], logFile);
1507
+ return;
1508
+ }
1509
+ const arch = isArm64() ? "arm64" : "amd64";
1510
+ const debPath = "/tmp/cloudflared.deb";
1511
+ shellRetry("curl", ["-fSL", "--progress-bar", `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${arch}.deb`, "-o", debPath], { timeout: 120_000 }, 3, 10);
1512
+ console.log(" [privileged] dpkg -i");
1513
+ shell("dpkg", ["-i", debPath], { sudo: true });
1514
+ spawnSync("rm", ["-f", debPath]);
1515
+ }
1516
+ function installWhisperCpp() {
1517
+ const WHISPER_DIR = "/opt/whisper.cpp";
1518
+ const WHISPER_BINARY = join(WHISPER_DIR, "build/bin/whisper-cli");
1519
+ const WHISPER_MODEL = join(WHISPER_DIR, "models", "ggml-base.bin");
1520
+ if (existsSync(WHISPER_BINARY) && existsSync(WHISPER_MODEL)) {
1521
+ log("7", TOTAL, "whisper.cpp already installed.");
1522
+ return;
1523
+ }
1524
+ log("7", TOTAL, "Installing whisper.cpp (speech-to-text)...");
1525
+ if (!isLinux()) {
1526
+ console.log(" Skipping — install manually for your platform.");
1527
+ return;
1528
+ }
1529
+ // Build dependencies — cmake is required since whisper.cpp migrated from plain make
1530
+ console.log(" [privileged] apt-get install");
1531
+ shell("apt-get", ["install", "-y", "build-essential", "cmake"], { sudo: true });
1532
+ // Clone or update the repository
1533
+ if (!existsSync(WHISPER_DIR)) {
1534
+ console.log(" Cloning whisper.cpp...");
1535
+ console.log(" [privileged] git clone");
1536
+ shell("git", ["clone", "--depth", "1", "https://github.com/ggerganov/whisper.cpp.git", WHISPER_DIR], { sudo: true });
1537
+ }
1538
+ // Compile via cmake (whisper.cpp's Makefile is a thin cmake wrapper)
1539
+ console.log(" Compiling whisper.cpp (this takes a few minutes on Pi)...");
1540
+ console.log(" [privileged] cmake -B");
1541
+ shell("cmake", ["-B", "build"], { cwd: WHISPER_DIR, sudo: true, timeout: 120_000 });
1542
+ console.log(" [privileged] cmake --build");
1543
+ shell("cmake", ["--build", "build", "--config", "Release", "-j2"], { cwd: WHISPER_DIR, sudo: true, timeout: 600_000 });
1544
+ // Download the base model (~150MB)
1545
+ if (!existsSync(WHISPER_MODEL)) {
1546
+ console.log(" Downloading ggml-base model (~150MB)...");
1547
+ console.log(" [privileged] bash -c");
1548
+ shellRetry("bash", ["-c", `cd ${WHISPER_DIR} && bash models/download-ggml-model.sh base`], { sudo: true, timeout: 300_000 }, 3, 15);
1549
+ }
1550
+ console.log(" whisper.cpp installed successfully.");
1551
+ }
1552
+ /**
1553
+ * Provision the shared HMAC secret used to sign remote-session cookies
1554
+ * (Task 653). Both `maxy-edge` and `maxy-ui` read this file; without it
1555
+ * they independently mint ephemeral secrets on first use and the
1556
+ * cross-process session namespace silently diverges again.
1557
+ *
1558
+ * First install: create the file (0600, 32-byte hex).
1559
+ * Upgrade: leave the existing file untouched — invalidating it here
1560
+ * would log every operator out on every upgrade.
1561
+ */
1562
+ function provisionRemoteSessionSecret() {
1563
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
1564
+ const credentialsDir = join(persistDir, "credentials");
1565
+ const secretFile = join(credentialsDir, "remote-session-secret");
1566
+ if (existsSync(secretFile)) {
1567
+ console.log(` [install] remote-session-secret exists — preserved`);
1568
+ return;
1569
+ }
1570
+ mkdirSync(credentialsDir, { recursive: true, mode: 0o700 });
1571
+ writeFileSync(secretFile, randomBytes(32).toString("hex"), { mode: 0o600 });
1572
+ console.log(` [install] remote-session-secret provisioned path=${secretFile}`);
1573
+ }
1574
+ // Task 904 — install-time admin-auth invariant. Walks every account.json
1575
+ // under accountsDir and compares its admins[] to the persistent users.json,
1576
+ // emitting one [install-invariant] line per divergence and one summary line.
1577
+ // Log-only (no install abort) so pre-Task-904 devices with already-divergent
1578
+ // state still upgrade; future sprint can promote to refuse-or-degrade once
1579
+ // the deployed fleet is audited clean. Mirror of
1580
+ // checkAdminAuthInvariant() in platform/lib/admins-write/src/index.ts.
1581
+ function runInstallInvariantCheck(usersFile, accountsDir) {
1582
+ const TAG = "[install-invariant]";
1583
+ const usersUserIds = new Set();
1584
+ if (existsSync(usersFile)) {
1585
+ try {
1586
+ const raw = readFileSync(usersFile, "utf-8").trim();
1587
+ if (raw) {
1588
+ const users = JSON.parse(raw);
1589
+ for (const u of users) {
1590
+ if (typeof u.userId === "string")
1591
+ usersUserIds.add(u.userId);
1592
+ }
1593
+ }
1594
+ }
1595
+ catch (err) {
1596
+ const msg = err instanceof Error ? err.message : String(err);
1597
+ console.log(` ${TAG} users.json unreadable usersFile=${usersFile} error=${msg}`);
1598
+ }
1599
+ }
1600
+ const accountUserIds = new Set();
1601
+ let divergences = 0;
1602
+ if (existsSync(accountsDir)) {
1603
+ let entries = [];
1604
+ try {
1605
+ entries = readdirSync(accountsDir);
1606
+ }
1607
+ catch (err) {
1608
+ const msg = err instanceof Error ? err.message : String(err);
1609
+ console.log(` ${TAG} accounts-dir unreadable accountsDir=${accountsDir} error=${msg}`);
1610
+ console.log(` ${TAG} check complete divergences=0 (accounts-dir unreadable)`);
1611
+ return;
1612
+ }
1613
+ for (const entry of entries) {
1614
+ if (entry.startsWith("."))
1615
+ continue;
1616
+ const accountDir = join(accountsDir, entry);
1617
+ try {
1618
+ if (!statSync(accountDir).isDirectory())
1619
+ continue;
1620
+ }
1621
+ catch {
1622
+ continue;
1623
+ }
1624
+ const accountJsonPath = join(accountDir, "account.json");
1625
+ if (!existsSync(accountJsonPath))
1626
+ continue;
1627
+ let admins = [];
1628
+ try {
1629
+ const config = JSON.parse(readFileSync(accountJsonPath, "utf-8"));
1630
+ admins = (config.admins ?? []);
1631
+ }
1632
+ catch (err) {
1633
+ const msg = err instanceof Error ? err.message : String(err);
1634
+ console.log(` ${TAG} account.json unreadable source=${accountJsonPath} error=${msg}`);
1635
+ continue;
1636
+ }
1637
+ for (const a of admins) {
1638
+ if (typeof a.userId !== "string")
1639
+ continue;
1640
+ accountUserIds.add(a.userId);
1641
+ if (!usersUserIds.has(a.userId)) {
1642
+ const userIdShort = a.userId.slice(0, 8);
1643
+ console.log(` ${TAG} direction=account-without-users userId=${userIdShort} source=${accountJsonPath}`);
1644
+ divergences += 1;
1645
+ }
1646
+ }
1647
+ }
1648
+ }
1649
+ for (const uid of usersUserIds) {
1650
+ if (!accountUserIds.has(uid)) {
1651
+ const userIdShort = uid.slice(0, 8);
1652
+ console.log(` ${TAG} direction=users-without-account userId=${userIdShort} source=${usersFile}`);
1653
+ divergences += 1;
1654
+ }
1655
+ }
1656
+ console.log(` ${TAG} check complete divergences=${divergences}`);
1657
+ }
1658
+ function deployPayload() {
1659
+ log("8", TOTAL, `Deploying ${BRAND.productName}...`);
1660
+ if (!existsSync(PAYLOAD_DIR)) {
1661
+ throw new Error(`Payload not found at ${PAYLOAD_DIR}. Package may be corrupted.`);
1662
+ }
1663
+ // Persistent config lives at ~/{configDir}/ — survives rm -rf ~/{installDir}
1664
+ const persistentDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
1665
+ const persistentPasswordFile = join(persistentDir, ".neo4j-password");
1666
+ const persistentAccountsDir = join(persistentDir, "accounts");
1667
+ // Migrate: if password is in old location, move to persistent
1668
+ const oldPasswordFile = join(INSTALL_DIR, "platform/config/.neo4j-password");
1669
+ if (existsSync(oldPasswordFile) && !existsSync(persistentPasswordFile)) {
1670
+ mkdirSync(persistentDir, { recursive: true });
1671
+ cpSync(oldPasswordFile, persistentPasswordFile);
1672
+ }
1673
+ const oldAccountsDir = join(INSTALL_DIR, "platform/config/accounts");
1674
+ if (existsSync(oldAccountsDir) && !existsSync(persistentAccountsDir)) {
1675
+ mkdirSync(persistentDir, { recursive: true });
1676
+ cpSync(oldAccountsDir, persistentAccountsDir, { recursive: true });
1677
+ }
1678
+ // Task 904 — users.json lives at <persistentDir>/users.json. Pre-Task-904
1679
+ // installs wrote to <INSTALL_DIR>/platform/config/users.json (the wipe zone)
1680
+ // and relied on a one-shot copy-up at first install plus a copy-back after
1681
+ // every wipe. The copy-back overwrote LIVE with the FROZEN-AT-FIRST-INSTALL
1682
+ // backup, dropping every admin row added since first install. Writers (paths.ts,
1683
+ // admin-add, set-pin, seed-neo4j.sh, migrate-import.sh) now target the
1684
+ // persistent file directly — see comment in platform/ui/app/lib/paths.ts.
1685
+ //
1686
+ // Legacy pickup: if a pre-Task-904 install left users.json inside the wipe
1687
+ // zone, copy it up once. Idempotent — guarded by !existsSync(persistentUsersFile).
1688
+ // Safe to delete this block one release after Task 904 has been deployed
1689
+ // everywhere; until then it absorbs upgrades from any pre-Task-904 version.
1690
+ const persistentUsersFile = join(persistentDir, "users.json");
1691
+ const oldUsersFile = join(INSTALL_DIR, "platform/config/users.json");
1692
+ if (existsSync(oldUsersFile) && !existsSync(persistentUsersFile)) {
1693
+ mkdirSync(persistentDir, { recursive: true });
1694
+ cpSync(oldUsersFile, persistentUsersFile);
1695
+ console.log(" Migrated pre-Task-904 users.json to persistent storage.");
1696
+ }
1697
+ // Task 923 — Claude Code OAuth credentials legacy pickup. Pre-Task-923
1698
+ // installs shared `~/.claude/.credentials.json` across brands; the brand
1699
+ // main service's `claude` SDK subprocess and admin server now read from
1700
+ // `${persistDir}/.claude/.credentials.json` per the new
1701
+ // Environment=CLAUDE_CONFIG_DIR= block in port-resolution.ts.
1702
+ //
1703
+ // Move-semantics: the FIRST brand to install grabs the legacy file. The
1704
+ // operation is cpSync + unlinkSync (not renameSync) so it survives cross-
1705
+ // volume `~/` mounts that would EXDEV-fail rename. Subsequent brand
1706
+ // installs find no legacy file and require a fresh `claude /login`.
1707
+ //
1708
+ // Idempotency stamp at `${persistentDir}/.claude/.credentials-migrated`
1709
+ // prevents re-pickup on re-installs of the same brand even if an operator
1710
+ // later runs `claude /login` with no CLAUDE_CONFIG_DIR set, which would
1711
+ // re-create the legacy path.
1712
+ const persistentClaudeDir = join(persistentDir, ".claude");
1713
+ const persistentCredsFile = join(persistentClaudeDir, ".credentials.json");
1714
+ const legacyCredsFile = join(process.env.HOME ?? "/root", ".claude", ".credentials.json");
1715
+ const credsMigratedStamp = join(persistentClaudeDir, ".credentials-migrated");
1716
+ if (!existsSync(credsMigratedStamp)) {
1717
+ mkdirSync(persistentClaudeDir, { recursive: true });
1718
+ if (existsSync(legacyCredsFile) && !existsSync(persistentCredsFile)) {
1719
+ cpSync(legacyCredsFile, persistentCredsFile);
1720
+ try {
1721
+ unlinkSync(legacyCredsFile);
1722
+ }
1723
+ catch (err) {
1724
+ const msg = err instanceof Error ? err.message : String(err);
1725
+ console.log(` [install] claude-creds pickup: legacy unlink warn=${msg}`);
1726
+ }
1727
+ console.log(` [install] claude-creds pickup: brand=${BRAND.hostname} source=${legacyCredsFile} target=${persistentCredsFile}`);
1728
+ }
1729
+ writeFileSync(credsMigratedStamp, new Date().toISOString() + "\n");
1730
+ }
1731
+ // Brand isolation: installer does not read ~/.maxy/, ~/.cloudflared/, or
1732
+ // ~/.cloudflare/ on non-default brands. These are peer-brand or shared-singleton
1733
+ // paths (Task 659). Pre-Task-659 installs that need to recover legacy state
1734
+ // follow the manual recovery paragraph in .docs/deployment.md.
1735
+ // Stop the running service before wiping directories (upgrade path).
1736
+ // The server holds open files in platform/ — rmSync fails with ENOTEMPTY if it's running.
1737
+ // Task 838 — darwin uses `launchctl bootout` instead of systemctl; bootout
1738
+ // returns synchronously when the agent has exited.
1739
+ if (requireSupportedPlatform(process.platform) === "darwin") {
1740
+ spawnSync("launchctl", ["bootout", `${gui()}/${launchdLabel()}`], { stdio: "pipe", timeout: 15_000 });
1741
+ }
1742
+ else {
1743
+ // systemctl stop returns when the main process exits, but ExecStopPost (e.g. VNC cleanup)
1744
+ // may still hold file handles. Poll is-active to wait for full deactivation.
1745
+ const svcName = BRAND.serviceName.replace(".service", "");
1746
+ spawnSync("systemctl", ["--user", "stop", svcName], { stdio: "pipe" });
1747
+ const MAX_STOP_WAIT = 5;
1748
+ for (let i = 0; i < MAX_STOP_WAIT; i++) {
1749
+ const result = spawnSync("systemctl", ["--user", "is-active", svcName], { stdio: "pipe" });
1750
+ const status = result.stdout?.toString().trim();
1751
+ if (status !== "active" && status !== "deactivating") {
1752
+ console.log(` Service stopped (${status || "not found"}).`);
1753
+ break;
1754
+ }
1755
+ if (i === MAX_STOP_WAIT - 1) {
1756
+ console.log(` [WARN] Service still ${status} after ${MAX_STOP_WAIT}s — proceeding with directory wipe.`);
1757
+ break;
1758
+ }
1759
+ spawnSync("sleep", ["1"]);
1760
+ }
1761
+ }
1762
+ // Migrate user data to {installDir}/data/ — outside the platform/ wipe zone.
1763
+ // Runs after service stop to avoid copying files while the agent is writing.
1764
+ // Once data/ is populated, subsequent reinstalls skip this entirely.
1765
+ const dataDir = join(INSTALL_DIR, "data");
1766
+ const dataAccountsDir = join(dataDir, "accounts");
1767
+ const dataUploadsDir = join(dataDir, "uploads");
1768
+ const oldLiveAccounts = join(INSTALL_DIR, "platform/config/accounts");
1769
+ if (!existsSync(dataAccountsDir)) {
1770
+ if (existsSync(oldLiveAccounts)) {
1771
+ mkdirSync(dataDir, { recursive: true });
1772
+ cpSync(oldLiveAccounts, dataAccountsDir, { recursive: true });
1773
+ console.log(" Migrated accounts to data/.");
1774
+ }
1775
+ else if (existsSync(persistentAccountsDir)) {
1776
+ mkdirSync(dataDir, { recursive: true });
1777
+ cpSync(persistentAccountsDir, dataAccountsDir, { recursive: true });
1778
+ console.log(" Restored accounts from persistent backup to data/.");
1779
+ }
1780
+ }
1781
+ const oldLiveUploads = join(INSTALL_DIR, "platform/data/attachments");
1782
+ if (!existsSync(dataUploadsDir) && existsSync(oldLiveUploads)) {
1783
+ mkdirSync(dataDir, { recursive: true });
1784
+ cpSync(oldLiveUploads, dataUploadsDir, { recursive: true });
1785
+ console.log(" Migrated uploads to data/.");
1786
+ }
1787
+ // Wipe deployment directories to prevent stale files.
1788
+ // data/ is NOT wiped — it contains user data (accounts, uploads) that must survive.
1789
+ for (const dir of ["platform", "server", "maxy", "premium-plugins", "docs", ".claude"]) {
1790
+ const target = join(INSTALL_DIR, dir);
1791
+ if (existsSync(target)) {
1792
+ rmSync(target, { recursive: true, force: true });
1793
+ }
1794
+ }
1795
+ mkdirSync(INSTALL_DIR, { recursive: true });
1796
+ // Deploy payload
1797
+ cpSync(PAYLOAD_DIR, INSTALL_DIR, {
1798
+ recursive: true,
1799
+ force: true,
1800
+ });
1801
+ // Link persistent config into install directory
1802
+ const configDir = join(INSTALL_DIR, "platform/config");
1803
+ mkdirSync(configDir, { recursive: true });
1804
+ if (existsSync(persistentPasswordFile)) {
1805
+ cpSync(persistentPasswordFile, join(configDir, ".neo4j-password"));
1806
+ console.log(" Restored Neo4j password.");
1807
+ }
1808
+ // Task 904 — users.json is read directly from persistentDir by both
1809
+ // platform/ui/app/lib/paths.ts (USERS_FILE = MAXY_DIR/users.json) and the
1810
+ // admin MCP plugin (USERS_FILE = CONFIG_DIR/users.json). No copy into the
1811
+ // wipe zone — the pre-Task-904 cpSync to platform/config/users.json was the
1812
+ // copy that overwrote new admin rows on every install. The line below
1813
+ // observes the row count + userId prefixes so a future regression is grep-
1814
+ // detectable in the install log without any runtime change (singular
1815
+ // "userId preserved" hid the multi-row failure mode).
1816
+ if (existsSync(persistentUsersFile)) {
1817
+ try {
1818
+ const raw = readFileSync(persistentUsersFile, "utf-8").trim();
1819
+ const rows = raw ? JSON.parse(raw) : [];
1820
+ const ids = rows
1821
+ .map(r => (typeof r.userId === "string" ? r.userId.slice(0, 8) : "?"))
1822
+ .join(",");
1823
+ console.log(` [install] users.json preserved: rows=${rows.length} userIds=${ids}`);
1824
+ }
1825
+ catch (err) {
1826
+ const msg = err instanceof Error ? err.message : String(err);
1827
+ console.log(` [install] users.json preserved: rows=? parse-failed error=${msg}`);
1828
+ }
1829
+ }
1830
+ else {
1831
+ console.log(" [install] users.json: no persistent file (fresh install — set-pin will create it)");
1832
+ }
1833
+ // Task 904 item 3 — install-time admin-auth invariant check. Walks every
1834
+ // data/accounts/*/account.json admins[] and compares to the persistent
1835
+ // users.json. Log-only (does NOT refuse install) so pre-Task-904 devices
1836
+ // with already-divergent state still upgrade; the [install-invariant]
1837
+ // line surfaces the bug to the operator without bricking the device.
1838
+ // Inline rather than imported from platform/lib/admins-write because the
1839
+ // installer is its own npm package and doesn't bundle the platform lib.
1840
+ // Mirrors checkAdminAuthInvariant() in platform/lib/admins-write/src/index.ts;
1841
+ // future divergence between the two should be caught by the test suite.
1842
+ runInstallInvariantCheck(persistentUsersFile, join(INSTALL_DIR, "data", "accounts"));
1843
+ // Write version marker so the running platform knows which create-maxy produced this deployment
1844
+ writeFileSync(join(configDir, `.${BRAND.hostname}-version`), PKG_VERSION, "utf-8");
1845
+ console.log(` Deployed to ${INSTALL_DIR}`);
1846
+ }
1847
+ function buildPlatform() {
1848
+ log("9", TOTAL, "Installing dependencies and building...");
1849
+ console.log(` Installing platform dependencies (${join(INSTALL_DIR, "platform")})...`);
1850
+ shellRetry("npm", ["install", ...NPM_NET_FLAGS], { cwd: join(INSTALL_DIR, "platform") }, 3, 15);
1851
+ // MCP server dist/ files are pre-compiled in the payload — no build step needed.
1852
+ // Server external dependencies (neo4j-driver, @anthropic-ai/sdk) are listed in
1853
+ // server/package.json but NOT shipped as pre-built node_modules — npm pack silently
1854
+ // strips files from nested node_modules (e.g. rxjs/package.json), breaking require().
1855
+ // Install fresh on device to guarantee a complete dependency tree.
1856
+ //
1857
+ // On upgrade, wipe `node_modules` first so npm extracts a clean tree. Without
1858
+ // this, an interrupted previous install (network blip, operator cancellation,
1859
+ // power loss) can leave nested package.json files half-truncated — the most
1860
+ // common manifestation is `Error: Invalid package config .../rxjs/package.json`
1861
+ // at server startup, which loops the brand service indefinitely. The wipe
1862
+ // adds ~30 s to upgrades but eliminates a class of unrecoverable customer
1863
+ // states; reliability wins over speed for a one-shot install path.
1864
+ const serverNodeModules = join(INSTALL_DIR, "server", "node_modules");
1865
+ if (existsSync(serverNodeModules)) {
1866
+ console.log(" Wiping previous server/node_modules for a clean reinstall...");
1867
+ rmSync(serverNodeModules, { recursive: true, force: true });
1868
+ }
1869
+ console.log(` Installing server dependencies (${join(INSTALL_DIR, "server")})...`);
1870
+ shellRetry("npm", ["install", "--omit=dev", ...NPM_NET_FLAGS], { cwd: join(INSTALL_DIR, "server") }, 3, 15);
1871
+ // Task 001 (maxy-code) — claude-session-manager has its own package.json
1872
+ // declaring hono + @hono/node-server + node-pty. node-pty is a native
1873
+ // binding; it MUST be installed on the Pi, not shipped pre-built (different
1874
+ // architecture between the build host and the Pi). Wipe + reinstall on
1875
+ // upgrade so a half-extracted previous install does not loop the unit.
1876
+ const csmDir = join(INSTALL_DIR, "platform", "services", "claude-session-manager");
1877
+ if (existsSync(csmDir)) {
1878
+ const csmNodeModules = join(csmDir, "node_modules");
1879
+ if (existsSync(csmNodeModules)) {
1880
+ console.log(" Wiping previous claude-session-manager/node_modules for a clean reinstall...");
1881
+ rmSync(csmNodeModules, { recursive: true, force: true });
1882
+ }
1883
+ console.log(` Installing claude-session-manager dependencies (${csmDir})...`);
1884
+ shellRetry("npm", ["install", "--omit=dev", ...NPM_NET_FLAGS], { cwd: csmDir }, 3, 15);
1885
+ }
1886
+ }
1887
+ function setupVncViewer() {
1888
+ if (!isLinux())
1889
+ return;
1890
+ const novncSrc = "/usr/share/novnc";
1891
+ const novncDest = join(INSTALL_DIR, "server/public/novnc");
1892
+ if (!existsSync(join(novncSrc, "core"))) {
1893
+ console.log(" noVNC not found — skipping VNC viewer setup.");
1894
+ return;
1895
+ }
1896
+ console.log(" Installing VNC viewer...");
1897
+ // Copy core/ and vendor/ (pako compression library) — both required by rfb.js
1898
+ cpSync(join(novncSrc, "core"), join(novncDest, "core"), { recursive: true, force: true });
1899
+ const vendorSrc = join(novncSrc, "vendor");
1900
+ if (existsSync(vendorSrc)) {
1901
+ cpSync(vendorSrc, join(novncDest, "vendor"), { recursive: true, force: true });
1902
+ }
1903
+ // Custom viewer: no toolbar, scales to fit, auto-connects with retry.
1904
+ //
1905
+ // Transport: same-origin WebSocket proxied through the Maxy server's
1906
+ // /websockify upgrade handler. The URL is built from location.protocol
1907
+ // and location.host so that:
1908
+ // - HTTP origin → ws://<host>:<port>/websockify (LAN)
1909
+ // - HTTPS origin → wss://<host>:<port>/websockify (Cloudflare tunnel)
1910
+ // This eliminates the mixed-content block that previously killed the
1911
+ // viewer when accessed via https://admin.maxy.bot.
1912
+ //
1913
+ // The viewer does NOT read a host/port from query string — those
1914
+ // parameters are ignored if present (kept for backward compatibility
1915
+ // with any cached callers) and the connection is always same-origin.
1916
+ //
1917
+ // A per-session correlation ID is generated client-side and included
1918
+ // in the WebSocket URL (?corrId=X) and in the POST to
1919
+ // /api/vnc/client-event so the server-side log can correlate the
1920
+ // noVNC disconnect reason to the specific WS upgrade entry.
1921
+ const html = `<!DOCTYPE html>
1922
+ <html>
1923
+ <head>
1924
+ <meta charset="utf-8">
1925
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
1926
+ <title>Connect Claude</title>
1927
+ <style>
1928
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
1929
+ html, body { width: 100%; height: 100%; background: #111; overflow: hidden; }
1930
+ #screen { position: relative; width: 100%; height: 100%; }
1931
+ #status {
1932
+ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
1933
+ color: #888; font-family: -apple-system, BlinkMacSystemFont, sans-serif;
1934
+ font-size: 14px; text-align: center; z-index: 10;
1935
+ transition: opacity 0.3s;
1936
+ }
1937
+ #status.hidden { opacity: 0; pointer-events: none; }
1938
+ .status-spinner {
1939
+ display: inline-block; width: 20px; height: 20px;
1940
+ border: 2px solid #444; border-top-color: #888; border-radius: 50%;
1941
+ animation: spin 0.8s linear infinite; margin-bottom: 8px;
1942
+ }
1943
+ @keyframes spin { to { transform: rotate(360deg); } }
1944
+ .status-reason { font-size: 12px; color: #666; margin-top: 6px; }
1945
+ </style>
1946
+ </head>
1947
+ <body>
1948
+ <div id="screen"></div>
1949
+ <div id="status">
1950
+ <div class="status-spinner"></div>
1951
+ <div>Connecting to browser…</div>
1952
+ <div class="status-reason"></div>
1953
+ </div>
1954
+ <script type="module">
1955
+ import RFB from '/novnc/core/rfb.js';
1956
+
1957
+ // Build a same-origin WebSocket URL. Protocol auto-matches the
1958
+ // parent page so https pages use wss and http pages use ws.
1959
+ const wsScheme = location.protocol === 'https:' ? 'wss:' : 'ws:';
1960
+ // Generate a client-side correlation ID. This ID is used only for
1961
+ // client-side log correlation (the server assigns its own corrId
1962
+ // on the upgrade handler so an attacker cannot forge server
1963
+ // internal ids). Base-36 random concatenation yields up to 16
1964
+ // chars — collision-free in practice for the 1-session-at-a-time
1965
+ // VNC use case.
1966
+ const corrId = (Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 10))
1967
+ .replace(/[^a-z0-9]/gi, '') || 'c' + Date.now().toString(36);
1968
+ const wsUrl = wsScheme + '//' + location.host + '/websockify?corrId=' + encodeURIComponent(corrId);
1969
+
1970
+ const screen = document.getElementById('screen');
1971
+ const status = document.getElementById('status');
1972
+ let retryCount = 0;
1973
+ const MAX_RETRIES = 30;
1974
+
1975
+ // Best-effort POST of a disconnect/error reason to the server so
1976
+ // the noVNC-observed failure mode ends up in vnc-boot.log alongside
1977
+ // the server-side proxy events. Network failures are swallowed —
1978
+ // this is a telemetry side channel, not a critical path.
1979
+ function reportClientEvent(phase, reason) {
1980
+ try {
1981
+ fetch('/api/vnc/client-event', {
1982
+ method: 'POST',
1983
+ credentials: 'same-origin',
1984
+ headers: { 'Content-Type': 'application/json' },
1985
+ body: JSON.stringify({ corrId: corrId, phase: phase, reason: reason || '' }),
1986
+ keepalive: true,
1987
+ }).catch(function() { /* swallow */ });
1988
+ } catch (e) { /* swallow */ }
1989
+ }
1990
+
1991
+ // Layer-6 beacon (Task 958): emit event=rfb-connected and
1992
+ // event=rfb-error to the operator-grep lifecycle endpoint so
1993
+ // server.log shows the noVNC outcome alongside [setup-tunnel] /
1994
+ // [cloudflare-setup] / [device-url:click] / [http] / [websockify].
1995
+ // Same-origin POST works whether the iframe is parented by the
1996
+ // React BrowserViewer or by vnc-popout.html.
1997
+ function reportBrowserViewerEvent(event, fields) {
1998
+ try {
1999
+ var body = JSON.stringify(Object.assign(
2000
+ { event: event, surface: 'iframe' },
2001
+ fields || {},
2002
+ ));
2003
+ fetch('/api/admin/browser-iframe/event', {
2004
+ method: 'POST',
2005
+ credentials: 'same-origin',
2006
+ headers: { 'Content-Type': 'application/json' },
2007
+ body: body,
2008
+ keepalive: true,
2009
+ }).catch(function() { /* swallow */ });
2010
+ } catch (e) { /* swallow */ }
2011
+ }
2012
+ var connectStartedAt = 0;
2013
+
2014
+ function connect() {
2015
+ status.classList.remove('hidden');
2016
+ status.querySelector('.status-spinner').style.display = '';
2017
+ status.querySelector('div:nth-child(2)').textContent =
2018
+ retryCount > 0 ? 'Reconnecting… (' + retryCount + ')' : 'Connecting to browser…';
2019
+ status.querySelector('.status-reason').textContent = '';
2020
+ connectStartedAt = Date.now();
2021
+
2022
+ const rfb = new RFB(screen, wsUrl);
2023
+ rfb.scaleViewport = true;
2024
+ rfb.clipViewport = true;
2025
+ rfb.resizeSession = false;
2026
+ window.rfb = rfb;
2027
+
2028
+ rfb.addEventListener('connect', () => {
2029
+ status.classList.add('hidden');
2030
+ retryCount = 0;
2031
+ reportBrowserViewerEvent('rfb-connected', {
2032
+ durationMs: Date.now() - connectStartedAt,
2033
+ });
2034
+ });
2035
+
2036
+ rfb.addEventListener('disconnect', (e) => {
2037
+ status.classList.remove('hidden');
2038
+ const detail = e.detail || {};
2039
+ const reason = detail.reason || (detail.clean === false ? 'Connection refused' : '');
2040
+ reportClientEvent('disconnect', reason || (detail.clean === false ? 'unclean-close' : 'normal'));
2041
+ reportBrowserViewerEvent('rfb-error', {
2042
+ durationMs: Date.now() - connectStartedAt,
2043
+ errorCode: detail.clean === false ? 'unclean-close' : 'normal',
2044
+ message: reason || '',
2045
+ });
2046
+ const reasonEl = status.querySelector('.status-reason');
2047
+ if (retryCount < MAX_RETRIES) {
2048
+ retryCount++;
2049
+ const delay = Math.min(1000 * retryCount, 5000);
2050
+ status.querySelector('div:nth-child(2)').textContent = 'Reconnecting in ' + Math.ceil(delay/1000) + 's…';
2051
+ reasonEl.textContent = reason;
2052
+ setTimeout(connect, delay);
2053
+ } else {
2054
+ status.querySelector('.status-spinner').style.display = 'none';
2055
+ status.querySelector('div:nth-child(2)').textContent = 'Connection lost. Reload the page to retry.';
2056
+ reasonEl.textContent = reason;
2057
+ }
2058
+ });
2059
+
2060
+ // --- Clipboard bridge (local ↔ remote) ---
2061
+
2062
+ // Paste bridge: intercept Cmd/Ctrl+V in the capturing phase before
2063
+ // noVNC's keydown handler can call preventDefault(). noVNC v1.3.0
2064
+ // binds its handler with .bind(this) in the Keyboard constructor and
2065
+ // registers the bound reference via addEventListener — so monkey-
2066
+ // patching the unbound method on the instance is a no-op. A capturing-
2067
+ // phase listener on document fires before noVNC's target-phase handler
2068
+ // regardless of registration timing or internal structure.
2069
+ document.addEventListener('keydown', (e) => {
2070
+ if ((e.ctrlKey || e.metaKey) && (e.key === 'v' || e.key === 'V')) {
2071
+ e.stopImmediatePropagation();
2072
+ reportClientEvent('paste-bridge', 'keydown-intercepted');
2073
+ }
2074
+ }, true);
2075
+
2076
+ // Catch the paste event, sync text to VNC server clipboard, then
2077
+ // send Ctrl+V keystrokes so the remote app pastes the synced content.
2078
+ document.addEventListener('paste', (e) => {
2079
+ e.preventDefault();
2080
+ const text = (e.clipboardData || window.clipboardData)?.getData('text');
2081
+ if (!text || !window.rfb) {
2082
+ reportClientEvent('paste-bridge', 'empty-clipboard');
2083
+ return;
2084
+ }
2085
+ reportClientEvent('paste-bridge', 'paste-event text-length=' + text.length);
2086
+ window.rfb.clipboardPasteFrom(text);
2087
+ reportClientEvent('paste-bridge', 'clipboard-synced');
2088
+ setTimeout(() => {
2089
+ window.rfb.sendKey(0xFFE3, 'ControlLeft', true);
2090
+ window.rfb.sendKey(0x0076, 'v', true);
2091
+ window.rfb.sendKey(0x0076, 'v', false);
2092
+ window.rfb.sendKey(0xFFE3, 'ControlLeft', false);
2093
+ }, 50);
2094
+ });
2095
+
2096
+ // Copy bridge: when the remote clipboard changes, notify the parent frame.
2097
+ rfb.addEventListener('clipboard', (e) => {
2098
+ window.parent.postMessage({ type: 'vnc-clipboard', text: e.detail.text }, '*');
2099
+ });
2100
+ }
2101
+
2102
+ connect();
2103
+ </script>
2104
+ </body>
2105
+ </html>`;
2106
+ writeFileSync(join(INSTALL_DIR, "server/public/vnc-viewer.html"), html);
2107
+ console.log(" VNC viewer ready at /vnc-viewer.html");
2108
+ }
2109
+ function setupAccount() {
2110
+ log("10", TOTAL, "Setting up...");
2111
+ // Task 787 — seed-neo4j.sh hard-exits without NEO4J_URI. The installer
2112
+ // owns the brand-correct URI and password, so we derive them once.
2113
+ // Missing password file is a hard error: ensureNeo4jPassword() ran
2114
+ // upstream and would have thrown already if it couldn't reach the
2115
+ // brand's Neo4j.
2116
+ const passwordFile = join(INSTALL_DIR, "platform/config/.neo4j-password");
2117
+ if (!existsSync(passwordFile)) {
2118
+ throw new Error(`Neo4j password file missing at ${passwordFile} — required by setup step.`);
2119
+ }
2120
+ const password = readFileSync(passwordFile, "utf-8").trim();
2121
+ const neo4jUri = `bolt://localhost:${NEO4J_PORT}`;
2122
+ const neo4jEnv = { ...process.env, NEO4J_URI: neo4jUri, NEO4J_PASSWORD: password };
2123
+ const seedScript = join(INSTALL_DIR, "platform/scripts/seed-neo4j.sh");
2124
+ if (existsSync(seedScript)) {
2125
+ console.log(` [neo4j] passing NEO4J_URI=${neo4jUri} to seed`);
2126
+ logFile(` [neo4j] passing NEO4J_URI=${neo4jUri} to seed`);
2127
+ shell("bash", [seedScript], { cwd: INSTALL_DIR, env: neo4jEnv });
2128
+ }
2129
+ }
2130
+ // ---------------------------------------------------------------------------
2131
+ // Tunnel script shortcuts
2132
+ //
2133
+ // The cloudflare plugin's SKILL.md, PLUGIN.md, and reference docs encode the
2134
+ // invocation `~/setup-tunnel.sh` (and `~/reset-tunnel.sh`). The filesystem
2135
+ // reality is <INSTALL_DIR>/platform/plugins/cloudflare/scripts/*.sh. Without
2136
+ // the symlink the agent's first SKILL-compliant invocation fails with exit
2137
+ // 127 — the discipline-violation loop Task 555 exists to close.
2138
+ //
2139
+ // Collision discipline (Task 659 — last-writer-wins across brands):
2140
+ // - absent path → create
2141
+ // - regular file → exit 1 (operator-owned file, do not clobber)
2142
+ // - symlink → same target → no-op
2143
+ // - symlink → any other target → overwrite (unlink + symlink)
2144
+ // covers stale-same-brand, dangling,
2145
+ // unreadable, and peer-brand cases.
2146
+ // setup-tunnel.sh takes <brand> as argv
2147
+ // so the script still operates on the
2148
+ // correct brand regardless of who owns
2149
+ // the shortcut symlink.
2150
+ // ---------------------------------------------------------------------------
2151
+ function createTunnelSymlink(linkPath, target) {
2152
+ const targetAbs = resolve(target);
2153
+ let lstat = null;
2154
+ try {
2155
+ lstat = lstatSync(linkPath);
2156
+ }
2157
+ catch (err) {
2158
+ const code = err.code;
2159
+ if (code !== "ENOENT") {
2160
+ console.error(`Setup failed: ${linkPath} lstat failed: ${err.message}`);
2161
+ console.error(`[create-maxy:error] ${linkPath} lstat failed: ${err.message}`);
2162
+ process.exit(1);
2163
+ }
2164
+ }
2165
+ if (lstat === null) {
2166
+ symlinkSync(targetAbs, linkPath);
2167
+ console.log(` [create-maxy] symlink ${linkPath} → ${targetAbs}`);
2168
+ logFile(` symlink created: ${linkPath} → ${targetAbs}`);
2169
+ return;
2170
+ }
2171
+ if (!lstat.isSymbolicLink()) {
2172
+ console.error(`Setup failed: ${linkPath} collision (regular file)`);
2173
+ console.error(`[create-maxy:collision] ${linkPath} already exists target=<regular-file>`);
2174
+ console.error(` Remove the file and re-run: npx -y @rubytech/create-maxy`);
2175
+ process.exit(1);
2176
+ }
2177
+ // Brand isolation (Task 659): a symlink at this path is either stale-same-brand,
2178
+ // dangling, unreadable, or pointing at another brand. All four cases are resolved
2179
+ // by last-writer-wins overwrite — setup-tunnel.sh takes <brand> as argv, so
2180
+ // whichever brand's installer ran last owns the shortcut.
2181
+ const resolvedTarget = (() => {
2182
+ try {
2183
+ return resolve(dirname(linkPath), readlinkSync(linkPath));
2184
+ }
2185
+ catch {
2186
+ return "<unreadable>";
2187
+ }
2188
+ })();
2189
+ if (resolvedTarget === targetAbs) {
2190
+ console.log(` [create-maxy] symlink ${linkPath} already points to ${targetAbs}`);
2191
+ logFile(` symlink unchanged: ${linkPath} → ${targetAbs}`);
2192
+ return;
2193
+ }
2194
+ unlinkSync(linkPath);
2195
+ symlinkSync(targetAbs, linkPath);
2196
+ console.log(` [create-maxy] symlink replaced: ${linkPath} → ${targetAbs}`);
2197
+ logFile(` symlink replaced: ${linkPath} was ${resolvedTarget}, now ${targetAbs}`);
2198
+ }
2199
+ function installTunnelScripts() {
2200
+ const setupSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/setup-tunnel.sh");
2201
+ const resetSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/reset-tunnel.sh");
2202
+ const listSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/list-cf-domains.sh");
2203
+ const setupLink = resolve(process.env.HOME ?? "/root", "setup-tunnel.sh");
2204
+ const resetLink = resolve(process.env.HOME ?? "/root", "reset-tunnel.sh");
2205
+ const listLink = resolve(process.env.HOME ?? "/root", "list-cf-domains.sh");
2206
+ for (const src of [setupSrc, resetSrc, listSrc]) {
2207
+ try {
2208
+ chmodSync(src, 0o755);
2209
+ }
2210
+ catch (err) {
2211
+ console.error(`Setup failed: tunnel script missing or chmod failed: ${src}`);
2212
+ console.error(`[create-maxy:error] tunnel script missing or chmod failed: ${src}`);
2213
+ console.error(` ${err.message}`);
2214
+ process.exit(1);
2215
+ }
2216
+ }
2217
+ createTunnelSymlink(setupLink, setupSrc);
2218
+ createTunnelSymlink(resetLink, resetSrc);
2219
+ createTunnelSymlink(listLink, listSrc);
2220
+ }
2221
+ // ---------------------------------------------------------------------------
2222
+ // Account discovery (shared between installService + installCrons)
2223
+ //
2224
+ // Task 955 — `installService` stamps `Environment=ACCOUNT_ID=` into the brand
2225
+ // systemd unit so the writeNodeWithEdges gate has a non-undefined identity to
2226
+ // compare against; `installCrons` needs the same value to scope cron stdout
2227
+ // to the per-account log dir + stamp ACCOUNT_ID into the cron entry env.
2228
+ // Both pull from `INSTALL_DIR/data/accounts/<uuid>/account.json` written by
2229
+ // seed-neo4j.sh during setupAccount(). One reader, one shape, one source of
2230
+ // truth — without sharing, the two callers would drift on classification of
2231
+ // `corrupt account.json` (one might count it, the other might not) and the
2232
+ // gate would reject writes the cron's "scoped" log was already happily
2233
+ // publishing under that uuid.
2234
+ // ---------------------------------------------------------------------------
2235
+ function resolveInstallAccountId() {
2236
+ const accountsDir = join(INSTALL_DIR, "data/accounts");
2237
+ if (!existsSync(accountsDir))
2238
+ return "";
2239
+ try {
2240
+ for (const d of readdirSync(accountsDir)) {
2241
+ if (existsSync(join(accountsDir, d, "account.json")))
2242
+ return d;
2243
+ }
2244
+ }
2245
+ catch { /* directory unreadable */ }
2246
+ return "";
2247
+ }
2248
+ // ---------------------------------------------------------------------------
2249
+ // Cron Registration
2250
+ //
2251
+ // Registers platform cron jobs (heartbeat, email-fetch, email-auto-respond).
2252
+ // Uses BEGIN/END markers for idempotent replacement on re-install.
2253
+ // Crons persist across reboots — registered once at install time.
2254
+ // Email crons run unconditionally; scripts exit early if unconfigured.
2255
+ // ---------------------------------------------------------------------------
2256
+ const CRON_BLOCK_BEGIN = `# BEGIN ${BRAND.productName.toUpperCase()} CRONS`;
2257
+ const CRON_BLOCK_END = `# END ${BRAND.productName.toUpperCase()} CRONS`;
2258
+ function installCrons() {
2259
+ if (!isLinux())
2260
+ return;
2261
+ const nodeBin = spawnSync("which", ["node"], { encoding: "utf-8" }).stdout.trim() || "/usr/bin/node";
2262
+ const platformRoot = join(INSTALL_DIR, "platform");
2263
+ // Account discovery shared with installService — see resolveInstallAccountId.
2264
+ const accountId = resolveInstallAccountId();
2265
+ const accountsDir = join(INSTALL_DIR, "data/accounts");
2266
+ if (!accountId) {
2267
+ console.error(" Cron jobs: skipped — no account found. Crons will register on the next install after account creation.");
2268
+ logFile(" cron registration skipped: no account directory with account.json found");
2269
+ return;
2270
+ }
2271
+ const accountLogDir = join(accountsDir, accountId, "logs");
2272
+ // NEO4J_URI is explicit per brand so a secondary-brand install (dedicated
2273
+ // Neo4j on a non-default port) doesn't silently fall back to
2274
+ // bolt://localhost:7687 in cron — cron inherits none of the user's shell
2275
+ // env or .env file. Without this, the scheduler writes and reads from the
2276
+ // shared default instance, producing cross-install event leaks (Task 571).
2277
+ const accountEnv = `ACCOUNT_ID=${accountId} NEO4J_URI=bolt://localhost:${NEO4J_PORT} `;
2278
+ const cronEntries = [
2279
+ `* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/scheduling/mcp/dist/scripts/check-due-events.js >> ${accountLogDir}/check-due-events.log 2>&1 # heartbeat`,
2280
+ `* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/email/mcp/dist/scripts/email-fetch.js >> ${accountLogDir}/email-fetch.log 2>&1 # email-fetch`,
2281
+ `* * * * * mkdir -p ${accountLogDir} && PLATFORM_ROOT=${platformRoot} ${accountEnv}${nodeBin} ${platformRoot}/plugins/email/mcp/dist/scripts/email-auto-respond.js >> ${accountLogDir}/email-auto-respond.log 2>&1 # email-auto-respond`,
2282
+ ];
2283
+ // Read existing crontab (empty string if none)
2284
+ const existing = spawnSync("crontab", ["-l"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
2285
+ const currentCrontab = existing.status === 0 ? existing.stdout : "";
2286
+ // Strip any existing Maxy cron block
2287
+ const blockPattern = new RegExp(`${CRON_BLOCK_BEGIN}[\\s\\S]*?${CRON_BLOCK_END}\\n?`, "g");
2288
+ const cleaned = currentCrontab.replace(blockPattern, "").trimEnd();
2289
+ // Build new crontab with Maxy block
2290
+ const newBlock = [
2291
+ CRON_BLOCK_BEGIN,
2292
+ ...cronEntries,
2293
+ CRON_BLOCK_END,
2294
+ ].join("\n");
2295
+ const newCrontab = cleaned ? `${cleaned}\n${newBlock}\n` : `${newBlock}\n`;
2296
+ // Write the new crontab
2297
+ const write = spawnSync("crontab", ["-"], {
2298
+ input: newCrontab,
2299
+ encoding: "utf-8",
2300
+ stdio: ["pipe", "pipe", "pipe"],
2301
+ });
2302
+ if (write.status === 0) {
2303
+ console.log(" Cron jobs: registered (heartbeat, email-fetch, email-auto-respond)");
2304
+ }
2305
+ else {
2306
+ console.error(` Cron jobs: failed to register — ${(write.stderr || "").trim()}`);
2307
+ logFile(` crontab write failed: ${write.stderr}`);
2308
+ }
2309
+ }
2310
+ // Task 664 retired the ttyd/tmux/xterm admin terminal stack. Upgrades run
2311
+ // via the action runner — `systemd-run --user` transient units spawned by
2312
+ // POST /api/admin/actions/upgrade — whose lifetime is independent of
2313
+ // maxy-ui, achieving the Task 647 invariant structurally rather than via
2314
+ // a peer edge service proxying a ttyd process. The installer no longer
2315
+ // provisions the ttyd binary, writes a tmux conf, or installs a ttyd
2316
+ // systemd unit. The corresponding admin UI (RemoteTerminal, TerminalOverlay,
2317
+ // xterm.js) was deleted in the same task.
2318
+ // Task 838 — reverse-DNS LaunchAgent label. Becomes both the plist's
2319
+ // <key>Label</key> value and the file basename
2320
+ // (`~/Library/LaunchAgents/com.rubytech.<hostname>.plist`). Per-brand so
2321
+ // installing brand B never displaces brand A's agent (mirrors the per-brand
2322
+ // systemd unit invariant from Task 662).
2323
+ function launchdLabel() {
2324
+ return `com.rubytech.${BRAND.hostname}`;
2325
+ }
2326
+ function launchAgentsDir() {
2327
+ return resolve(process.env.HOME ?? "/", "Library/LaunchAgents");
2328
+ }
2329
+ function plistPath() {
2330
+ return join(launchAgentsDir(), `${launchdLabel()}.plist`);
2331
+ }
2332
+ function gui() {
2333
+ // launchd's gui domain is keyed on the user's UID. process.getuid is only
2334
+ // defined on POSIX (always present on darwin); typed conservatively.
2335
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
2336
+ return `gui/${uid}`;
2337
+ }
2338
+ // Task 838 — darwin LaunchAgent supervisor. Mirrors the systemd-user body
2339
+ // below at the success-criteria level: process registered with the user's
2340
+ // session manager, KeepAlive respawns on crash, RunAtLoad starts the agent
2341
+ // on every login. Out-of-scope today (Task 839): brew-resolved node path,
2342
+ // dedicated Neo4j on a non-default port. Falls back to /usr/local/bin/node
2343
+ // (homebrew Intel + manual Apple-silicon installs) — Task 839 will replace
2344
+ // this with the resolver.
2345
+ function installServiceDarwin() {
2346
+ const persistDir = resolve(process.env.HOME ?? "/", BRAND.configDir);
2347
+ const logsDir = join(persistDir, "logs");
2348
+ mkdirSync(logsDir, { recursive: true });
2349
+ // Write install-time config to .env (the server reads it directly on
2350
+ // darwin; launchd does not have systemd's EnvironmentFile primitive).
2351
+ const envPath = join(persistDir, ".env");
2352
+ try {
2353
+ let envContent = "";
2354
+ try {
2355
+ envContent = readFileSync(envPath, "utf-8");
2356
+ }
2357
+ catch { /* first install */ }
2358
+ for (const [key, value] of [
2359
+ ["DISPLAY_MODE", DISPLAY_MODE],
2360
+ ["EMBED_MODEL", EMBED_MODEL],
2361
+ ["EMBED_DIMENSIONS", String(EMBED_DIMS)],
2362
+ ["NEO4J_URI", `bolt://localhost:${NEO4J_PORT}`],
2363
+ ["PORT", String(PORT)],
2364
+ ["MAXY_PLATFORM_ROOT", `${INSTALL_DIR}/platform`],
2365
+ ]) {
2366
+ const re = new RegExp(`^${key}=.*$`, "m");
2367
+ if (re.test(envContent)) {
2368
+ envContent = envContent.replace(re, `${key}=${value}`);
2369
+ }
2370
+ else {
2371
+ envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `${key}=${value}\n`;
2372
+ }
2373
+ }
2374
+ writeFileSync(envPath, envContent);
2375
+ logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=bolt://localhost:${NEO4J_PORT}, PORT=${PORT}`);
2376
+ }
2377
+ catch (err) {
2378
+ console.error(` WARNING: failed to write .env to ${envPath}: ${err instanceof Error ? err.message : String(err)}`);
2379
+ }
2380
+ // Render the plist. The wrapper shell script reads .env before exec'ing
2381
+ // node so the runtime config (PORT, MAXY_PLATFORM_ROOT, NEO4J_URI) lands
2382
+ // in the child env. Without this, ProgramArguments executes node directly
2383
+ // and the .env values are unread — server binds the wrong port.
2384
+ const wrapperPath = join(persistDir, "launchd-wrapper.sh");
2385
+ // Resolve node binary at install time so the wrapper picks the right
2386
+ // path on both Intel (/usr/local/bin/node) and Apple Silicon
2387
+ // (/opt/homebrew/bin/node) — Homebrew's prefix differs by arch.
2388
+ const nodeProbe = spawnSync("command", ["-v", "node"], { encoding: "utf-8", shell: true });
2389
+ const nodeBin = (nodeProbe.stdout ?? "").trim() || "/usr/local/bin/node";
2390
+ const wrapperBody = [
2391
+ "#!/bin/bash",
2392
+ "# Task 838 — generated by create-maxy installService(). Reads .env then",
2393
+ "# execs node so launchd's child inherits PORT, NEO4J_URI, etc. Replaces",
2394
+ "# the systemd EnvironmentFile= directive that has no launchd analogue.",
2395
+ `set -a; [ -f "${envPath}" ] && . "${envPath}"; set +a`,
2396
+ `cd "${INSTALL_DIR}/server"`,
2397
+ `exec ${nodeBin} --require ./server-init.cjs server.js`,
2398
+ "",
2399
+ ].join("\n");
2400
+ writeFileSync(wrapperPath, wrapperBody);
2401
+ chmodSync(wrapperPath, 0o755);
2402
+ const label = launchdLabel();
2403
+ const plist = renderPlist({
2404
+ label,
2405
+ programArguments: ["/bin/bash", wrapperPath],
2406
+ stdoutPath: join(logsDir, "server.log"),
2407
+ stderrPath: join(logsDir, "server.log"),
2408
+ keepAlive: true,
2409
+ runAtLoad: true,
2410
+ workingDirectory: `${INSTALL_DIR}/server`,
2411
+ });
2412
+ mkdirSync(launchAgentsDir(), { recursive: true });
2413
+ const path = plistPath();
2414
+ writeFileSync(path, plist);
2415
+ logFile(` ${path} written (${plist.length} bytes)`);
2416
+ // Idempotent re-install: bootout the previous instance (if any) first so
2417
+ // the second `bootstrap` does not exit 5 ("already loaded"). Best-effort —
2418
+ // a missing service is the expected case on fresh installs.
2419
+ spawnSync("launchctl", ["bootout", `${gui()}/${label}`], { stdio: "pipe" });
2420
+ const bootstrap = spawnSync("launchctl", ["bootstrap", gui(), path], {
2421
+ stdio: "pipe",
2422
+ encoding: "utf-8",
2423
+ timeout: 15_000,
2424
+ });
2425
+ if (bootstrap.status === 0) {
2426
+ console.log(` [launchd] bootstrap ${gui()}/${label} ok`);
2427
+ logFile(` [launchd] bootstrap ${gui()}/${label} ok`);
2428
+ }
2429
+ else {
2430
+ const stderr = (bootstrap.stderr ?? "").trim();
2431
+ console.error(` [launchd] bootstrap returned ${bootstrap.status}: ${stderr}`);
2432
+ logFile(` [launchd] bootstrap returned ${bootstrap.status}: ${stderr}`);
2433
+ throw new Error(`launchctl bootstrap ${gui()} ${path} failed (exit ${bootstrap.status}): ${stderr}`);
2434
+ }
2435
+ // Wait for the server to come up.
2436
+ console.log(" Waiting for web server...");
2437
+ let webServerUp = false;
2438
+ for (let i = 0; i < 20; i++) {
2439
+ try {
2440
+ execFileSync("curl", ["-sf", `http://localhost:${PORT}`, "-o", "/dev/null"], { timeout: 3000 });
2441
+ webServerUp = true;
2442
+ break;
2443
+ }
2444
+ catch {
2445
+ spawnSync("sleep", ["2"]);
2446
+ }
2447
+ }
2448
+ if (!webServerUp) {
2449
+ console.log(` Server may still be starting. Check http://localhost:${PORT} in a moment.`);
2450
+ }
2451
+ }
2452
+ function installService() {
2453
+ log("11", TOTAL, `Starting ${BRAND.productName}...`);
2454
+ // Task 838 — branch on the Task 836 ternary instead of the legacy
2455
+ // `isLinux()` boolean. Linux falls through to the systemd-user body
2456
+ // below; darwin renders + bootstraps a LaunchAgent and returns;
2457
+ // unsupported throws the literal refusal at requireSupportedPlatform.
2458
+ const platform = requireSupportedPlatform(process.platform);
2459
+ if (platform === "darwin") {
2460
+ installServiceDarwin();
2461
+ return;
2462
+ }
2463
+ // Persist UDP buffer sizes for cloudflared QUIC stability (applied on every boot via sysctl.d)
2464
+ const sysctlTmpPath = `/tmp/99-${BRAND.hostname}-quic.conf`;
2465
+ const sysctlDestPath = `/etc/sysctl.d/99-${BRAND.hostname}-quic.conf`;
2466
+ try {
2467
+ const sysctlConf = "net.core.rmem_max=7340032\nnet.core.wmem_max=7340032\n";
2468
+ writeFileSync(sysctlTmpPath, sysctlConf);
2469
+ console.log(" [privileged] cp");
2470
+ shell("cp", [sysctlTmpPath, sysctlDestPath], { sudo: true });
2471
+ spawnSync("rm", ["-f", sysctlTmpPath]);
2472
+ spawnSync("sudo", ["sysctl", "--system"], { stdio: "ignore", timeout: 10_000 });
2473
+ }
2474
+ catch { /* non-critical — values applied on next reboot */ }
2475
+ const serviceDir = resolve(process.env.HOME ?? "/root", ".config/systemd/user");
2476
+ mkdirSync(serviceDir, { recursive: true });
2477
+ // Create systemd user service
2478
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
2479
+ mkdirSync(join(persistDir, "logs"), { recursive: true });
2480
+ // Write install-time config to .env (systemd reads via EnvironmentFile).
2481
+ // Preserves existing .env content (e.g. PORT overrides) — only
2482
+ // replaces or appends each managed line.
2483
+ const envPath = join(persistDir, ".env");
2484
+ try {
2485
+ let envContent = "";
2486
+ try {
2487
+ envContent = readFileSync(envPath, "utf-8");
2488
+ }
2489
+ catch { /* first install */ }
2490
+ // DISPLAY_MODE
2491
+ if (/^DISPLAY_MODE=.*/m.test(envContent)) {
2492
+ envContent = envContent.replace(/^DISPLAY_MODE=.*/m, `DISPLAY_MODE=${DISPLAY_MODE}`);
2493
+ }
2494
+ else {
2495
+ envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `DISPLAY_MODE=${DISPLAY_MODE}\n`;
2496
+ }
2497
+ // EMBED_MODEL
2498
+ if (/^EMBED_MODEL=.*/m.test(envContent)) {
2499
+ envContent = envContent.replace(/^EMBED_MODEL=.*/m, `EMBED_MODEL=${EMBED_MODEL}`);
2500
+ }
2501
+ else {
2502
+ envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `EMBED_MODEL=${EMBED_MODEL}\n`;
2503
+ }
2504
+ // EMBED_DIMENSIONS
2505
+ if (/^EMBED_DIMENSIONS=.*/m.test(envContent)) {
2506
+ envContent = envContent.replace(/^EMBED_DIMENSIONS=.*/m, `EMBED_DIMENSIONS=${EMBED_DIMS}`);
2507
+ }
2508
+ else {
2509
+ envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `EMBED_DIMENSIONS=${EMBED_DIMS}\n`;
2510
+ }
2511
+ // NEO4J_URI — always written so the platform connects to the correct instance.
2512
+ // For shared instances this is bolt://localhost:7687 (the default seed-neo4j.sh
2513
+ // would use anyway), but writing it explicitly makes the .env self-documenting
2514
+ // and ensures upgrade detection works for any future port change.
2515
+ const neo4jUri = `bolt://localhost:${NEO4J_PORT}`;
2516
+ if (/^NEO4J_URI=.*/m.test(envContent)) {
2517
+ envContent = envContent.replace(/^NEO4J_URI=.*/m, `NEO4J_URI=${neo4jUri}`);
2518
+ }
2519
+ else {
2520
+ envContent = envContent.trimEnd() + (envContent.length > 0 ? "\n" : "") + `NEO4J_URI=${neo4jUri}\n`;
2521
+ }
2522
+ writeFileSync(envPath, envContent);
2523
+ logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=${neo4jUri}`);
2524
+ }
2525
+ catch (err) {
2526
+ console.error(` WARNING: failed to write .env to ${envPath}: ${err instanceof Error ? err.message : String(err)}`);
2527
+ }
2528
+ // Propagate to child processes — seed-neo4j.sh reads both variables.
2529
+ process.env.EMBED_DIMENSIONS = String(EMBED_DIMS);
2530
+ process.env.NEO4J_URI = `bolt://localhost:${NEO4J_PORT}`;
2531
+ // Task 647: maxy-ui runs on an internal-only port so a restart does not
2532
+ // drop the public TCP socket. maxy-edge.service owns the public port and
2533
+ // the VNC stack; maxy-ui sits behind it on 127.0.0.1:MAXY_UI_INTERNAL_PORT.
2534
+ // PORT + 1 (derived) avoids a fixed-port collision if the operator chose a
2535
+ // non-default --port.
2536
+ //
2537
+ // Task 666 — PORT in maxy.service's Environment block is the PUBLIC port,
2538
+ // not the internal one. Previously we wrote `Environment=PORT=<internal>`,
2539
+ // which collided with the install-time reader further down, which
2540
+ // correctly treats Environment=PORT= as public. The overload caused +1
2541
+ // drift per upgrade: each run read the internal value, treated it as
2542
+ // public, wrote internal = old_internal + 1. maxy-ui now binds
2543
+ // MAXY_UI_INTERNAL_PORT (with a fallback to PORT for mixed-state installs).
2544
+ const MAXY_UI_INTERNAL_PORT = PORT + 1;
2545
+ const edgeUnitShort = `${BRAND.hostname}-edge`;
2546
+ const edgeUnitName = `${edgeUnitShort}.service`;
2547
+ // Per-brand X display (Task 553). Same value used for the edge unit's
2548
+ // DISPLAY env (stamped via __VNC_DISPLAY__ a few lines down) so the main
2549
+ // brand service and the edge service agree on which display Chromium runs.
2550
+ // Task 924 + 959 — brand.json (BRAND) is the single source of truth for
2551
+ // these fields at install time. The vncDisplay-derived offset rule lives
2552
+ // in the brand-creation tooling; at this point in the installer BRAND
2553
+ // already represents a parsed, validated brand manifest, and any missing
2554
+ // field is a brand-publish defect. Loud-fail rather than silently
2555
+ // substituting an offset (silent-fallback-masks-root-cause recurrence).
2556
+ if (typeof BRAND.vncDisplay !== "number") {
2557
+ console.error(`[create-maxy] error reason=cdp-port-unresolved brand=${BRAND.configDir} field=vncDisplay`);
2558
+ throw new Error(`brand.json missing required field: vncDisplay`);
2559
+ }
2560
+ const VNC_DISPLAY = BRAND.vncDisplay;
2561
+ for (const field of ["rfbPort", "websockifyPort", "cdpPort"]) {
2562
+ if (typeof BRAND[field] !== "number") {
2563
+ console.error(`[create-maxy] error reason=cdp-port-unresolved brand=${BRAND.configDir} field=${field}`);
2564
+ throw new Error(`brand.json missing required field: ${field}`);
2565
+ }
2566
+ }
2567
+ const RFB_PORT = BRAND.rfbPort;
2568
+ const WEBSOCKIFY_PORT_BRAND = BRAND.websockifyPort;
2569
+ const CDP_PORT_BRAND = BRAND.cdpPort;
2570
+ const CLAUDE_SESSION_MANAGER_PORT_BRAND = BRAND.claudeSessionManagerPort;
2571
+ // Task 924/938 pre-flight — refuse to write service files if any of the
2572
+ // three brand-scoped ports is already held by a process that is NOT this
2573
+ // brand's own on-demand browser nor a peer brand's edge stack.
2574
+ //
2575
+ // Classification (Task 938 chromium, Task 939 Xtigervnc + websockify) reads
2576
+ // `/proc/<pid>/cmdline` and applies a holder-specific argv anchor. Task 938
2577
+ // covered chromium-only via `--user-data-dir=`; Task 939 closed the gap on
2578
+ // Xtigervnc (no such flag — anchor on the `:N` display literal) and
2579
+ // websockify (anchor on bind port). Brand identities (configDir,
2580
+ // vncDisplay, websockifyPort) come from brand-registry.json which the
2581
+ // bundler stamps at build time from every brands/<brand>/brand.json.
2582
+ //
2583
+ // Decisions per holder:
2584
+ // OWN_BRAND — SIGTERM, recheck, SIGKILL on stragglers, exit-1 only if
2585
+ // the port is still held after both signals.
2586
+ // PEER_BRAND — log OK and return (per-brand port sets are disjoint).
2587
+ // UNRELATED — refuse to write service files; emit operator override.
2588
+ // macOS dev hosts (no ss) fall through the catch and skip pre-flight
2589
+ // entirely — the runtime check in vnc.sh covers Linux production.
2590
+ const ownBrand = {
2591
+ configDir: BRAND.configDir,
2592
+ vncDisplay: VNC_DISPLAY,
2593
+ websockifyPort: WEBSOCKIFY_PORT_BRAND,
2594
+ };
2595
+ // Peer registry — load from payload/platform/config/brand-registry.json
2596
+ // when present (Task 939+ bundles). Older bundles ship without the
2597
+ // registry; in that case peerBrands stays empty. PEER_BRAND classification
2598
+ // for Xtigervnc/websockify is a defence-in-depth case anyway (port sets
2599
+ // are disjoint by Task 924), so the empty-list fallback is safe — peer
2600
+ // chromium will fall through to UNRELATED, matching pre-Task 938 behaviour
2601
+ // for the only realistic scenario (a stale peer browser on the wrong CDP
2602
+ // port).
2603
+ const peerBrands = (() => {
2604
+ const registryPath = join(PAYLOAD_DIR, "platform", "config", "brand-registry.json");
2605
+ if (!existsSync(registryPath)) {
2606
+ logFile(` [preflight] brand-registry.json not in payload — peer matching disabled`);
2607
+ return [];
2608
+ }
2609
+ try {
2610
+ const raw = JSON.parse(readFileSync(registryPath, "utf-8"));
2611
+ const entries = [];
2612
+ for (const b of raw.brands ?? []) {
2613
+ if (b.hostname === BRAND.hostname)
2614
+ continue;
2615
+ if (typeof b.configDir !== "string" || typeof b.vncDisplay !== "number" || typeof b.websockifyPort !== "number")
2616
+ continue;
2617
+ entries.push({ configDir: b.configDir, vncDisplay: b.vncDisplay, websockifyPort: b.websockifyPort });
2618
+ }
2619
+ return entries;
2620
+ }
2621
+ catch (err) {
2622
+ logFile(` [preflight] brand-registry.json parse failed: ${err instanceof Error ? err.message : String(err)} — peer matching disabled`);
2623
+ return [];
2624
+ }
2625
+ })();
2626
+ const ssReadHolder = (port) => {
2627
+ return execFileSync("ss", ["-tlnpH", `sport = :${port}`], {
2628
+ encoding: "utf-8", timeout: 3000, stdio: ["ignore", "pipe", "ignore"],
2629
+ });
2630
+ };
2631
+ // Pass raw NUL-separated cmdline to the classifier so it can argv-anchor
2632
+ // on `--user-data-dir=`. Replacing NUL with space here would defeat that.
2633
+ const readCmdline = (pid) => readFileSync(`/proc/${pid}/cmdline`, "utf-8");
2634
+ const sleepMs = (ms) => { spawnSync("sleep", [(ms / 1000).toString()]); };
2635
+ // Tightly scoped variant for retry-path ss reads. Failures here (timeout,
2636
+ // ENOMEM, signal) are structural — never the macOS-no-ss case (we already
2637
+ // succeeded once) — so they get a structured exit, not a stack trace.
2638
+ const ssReadOrAbort = (label, port) => {
2639
+ try {
2640
+ return ssReadHolder(port);
2641
+ }
2642
+ catch (err) {
2643
+ console.error(` ERROR: [preflight] ${label}=${port} ss recheck failed: ${err instanceof Error ? err.message : String(err)}`);
2644
+ console.error(` Resolve manually before retrying.`);
2645
+ process.exit(1);
2646
+ }
2647
+ };
2648
+ // Distinguish ESRCH (process already gone — expected) from EPERM/EINVAL
2649
+ // (alarming — signals we can't deliver, possibly a recycled pid). Returns
2650
+ // true for clean kill or ESRCH, false otherwise (caller logs a warning).
2651
+ const killNoThrow = (pid, signal) => {
2652
+ try {
2653
+ process.kill(pid, signal);
2654
+ return true;
2655
+ }
2656
+ catch (err) {
2657
+ const code = err.code;
2658
+ if (code === "ESRCH")
2659
+ return true;
2660
+ logFile(` [preflight] kill(${pid}, ${signal}) failed code=${code ?? "unknown"}`);
2661
+ return false;
2662
+ }
2663
+ };
2664
+ const classify = (ssOutput) => classifyPortHolder({
2665
+ ssOutput, ownBrand, peerBrands, getCmdline: readCmdline,
2666
+ });
2667
+ // Task 939 — log line varies by detected holder so the operator can see
2668
+ // which OWN_BRAND stack is being killed. The kill loop is identical for
2669
+ // all three holders (SIGTERM → 300ms → recheck → SIGKILL → recheck), so
2670
+ // only the announce line differs.
2671
+ const ownBrandAnnounceLine = (label, port, c) => {
2672
+ if (c.holderType === "xtigervnc") {
2673
+ return ` [preflight] ${label}=${port} held by OWN brand Xtigervnc display=:${c.vncDisplay} pid=${c.pid} — sending SIGTERM`;
2674
+ }
2675
+ if (c.holderType === "websockify") {
2676
+ return ` [preflight] ${label}=${port} held by OWN brand websockify pid=${c.pid} — sending SIGTERM`;
2677
+ }
2678
+ // Default — chromium / unknown OWN_BRAND
2679
+ return ` [preflight] ${label}=${port} held by OWN brand process pid=${c.pid} profile=${c.profilePath} — sending SIGTERM`;
2680
+ };
2681
+ const checkInstallPortFree = (label, port) => {
2682
+ let firstSsOutput;
2683
+ try {
2684
+ firstSsOutput = ssReadHolder(port);
2685
+ }
2686
+ catch (err) {
2687
+ // ss may not be present on macOS dev hosts — skip the pre-flight there
2688
+ // rather than abort the install. The runtime check in vnc.sh covers
2689
+ // production-like Linux installs where this matters. This catch is
2690
+ // narrow on purpose: only the first ss invocation may legitimately
2691
+ // fail (binary missing); retry-path failures use ssReadOrAbort.
2692
+ logFile(` [preflight] ${label}=${port} check skipped: ${err instanceof Error ? err.message : String(err)}`);
2693
+ return;
2694
+ }
2695
+ let r = classify(firstSsOutput);
2696
+ // ENOENT race — process exited between ss and cmdline read. Port is
2697
+ // probably free now; one re-check resolves it deterministically.
2698
+ if (r.cmdlineReadFailed)
2699
+ r = classify(ssReadOrAbort(label, port));
2700
+ if (r.kind === "EMPTY")
2701
+ return;
2702
+ if (r.kind === "PEER_BRAND") {
2703
+ logFile(` [preflight] ${label}=${port} held by a peer brand's stack — OK (per-brand ports are disjoint by construction)`);
2704
+ return;
2705
+ }
2706
+ if (r.kind === "OWN_BRAND" && r.pid !== undefined) {
2707
+ logFile(ownBrandAnnounceLine(label, port, r));
2708
+ killNoThrow(r.pid, "SIGTERM");
2709
+ sleepMs(300);
2710
+ const after = classify(ssReadOrAbort(label, port));
2711
+ if (after.kind === "EMPTY") {
2712
+ logFile(` [preflight] ${label}=${port} freed`);
2713
+ return;
2714
+ }
2715
+ if (after.kind === "OWN_BRAND" && after.pid === r.pid) {
2716
+ logFile(` [preflight] ${label}=${port} survived SIGTERM — sending SIGKILL`);
2717
+ killNoThrow(r.pid, "SIGKILL");
2718
+ sleepMs(300);
2719
+ const final = classify(ssReadOrAbort(label, port));
2720
+ if (final.kind === "EMPTY") {
2721
+ logFile(` [preflight] ${label}=${port} freed`);
2722
+ return;
2723
+ }
2724
+ console.error(` ERROR: [preflight] ${label}=${port} OWN_BRAND auto-kill failed pid=${r.pid} — resolve manually before retrying.`);
2725
+ process.exit(1);
2726
+ }
2727
+ // A different OWN_BRAND pid took the port. The brand's user services
2728
+ // are respawning Chromium — installer cannot win this race. Stop the
2729
+ // services, then retry.
2730
+ if (after.kind === "OWN_BRAND") {
2731
+ console.error(` ERROR: [preflight] ${label}=${port} brand respawned a new OWN_BRAND pid (was ${r.pid}, now ${after.pid}).`);
2732
+ console.error(` Stop the brand's user services first: \`systemctl --user stop ${BRAND.hostname}-edge ${BRAND.hostname}\`, then re-run the installer.`);
2733
+ process.exit(1);
2734
+ }
2735
+ // PEER_BRAND or UNRELATED took the port post-kill — fall through to
2736
+ // those branches by re-classifying the surviving holder.
2737
+ r = after;
2738
+ if (r.kind === "EMPTY")
2739
+ return;
2740
+ if (r.kind === "PEER_BRAND") {
2741
+ logFile(` [preflight] ${label}=${port} now held by a peer brand's stack — OK`);
2742
+ return;
2743
+ }
2744
+ // r.kind === "UNRELATED" — fall through to the operator-override block.
2745
+ }
2746
+ // UNRELATED — preserve the operator-override path verbatim.
2747
+ console.error(` ERROR: [preflight:collision] brand=${BRAND.hostname} ${label}=${port} held by an unrelated process:`);
2748
+ console.error(` ${firstSsOutput.trim()}`);
2749
+ if (r.cmdline)
2750
+ console.error(` cmdline: ${r.cmdline}`);
2751
+ console.error(` Refusing to write service files; resolve the collision before retrying.`);
2752
+ console.error(` Operator override: edit brands/${BRAND.hostname}/brand.json, set/add \`${label}\` to a free port, re-bundle, re-install.`);
2753
+ process.exit(1);
2754
+ };
2755
+ checkInstallPortFree("rfbPort", RFB_PORT);
2756
+ checkInstallPortFree("websockifyPort", WEBSOCKIFY_PORT_BRAND);
2757
+ checkInstallPortFree("cdpPort", CDP_PORT_BRAND);
2758
+ // Task 955 — ACCOUNT_ID stamped into the brand unit so the writeNodeWithEdges
2759
+ // gate at platform/lib/graph-write/src/index.ts:170 has a real identity to
2760
+ // compare against (instead of process.env.ACCOUNT_ID === undefined). Resolved
2761
+ // here AFTER setupAccount() ran upstream — seed-neo4j.sh wrote account.json,
2762
+ // so an empty resolution at this point is a corrupted install (e.g. the seed
2763
+ // failed silently, or accounts/ was wiped between setup and unit-write).
2764
+ const installAccountId = resolveInstallAccountId();
2765
+ if (!installAccountId) {
2766
+ throw new Error(`installService: no account discovered at ${INSTALL_DIR}/data/accounts/<uuid>/account.json — ` +
2767
+ `setupAccount() (seed-neo4j.sh) should have created one. Refusing to write a systemd unit ` +
2768
+ `without ACCOUNT_ID; the boot validator would FATAL on every restart.`);
2769
+ }
2770
+ const serviceFile = buildMaxyUnitFile({
2771
+ productName: BRAND.productName,
2772
+ brandHostname: BRAND.hostname,
2773
+ neo4jDedicated: NEO4J_DEDICATED,
2774
+ installDir: INSTALL_DIR,
2775
+ persistDir,
2776
+ port: PORT,
2777
+ maxyUiInternalPort: MAXY_UI_INTERNAL_PORT,
2778
+ vncDisplay: VNC_DISPLAY,
2779
+ rfbPort: RFB_PORT,
2780
+ websockifyPort: WEBSOCKIFY_PORT_BRAND,
2781
+ cdpPort: CDP_PORT_BRAND,
2782
+ claudeSessionManagerPort: CLAUDE_SESSION_MANAGER_PORT_BRAND, // Task 001 (maxy-code)
2783
+ chromiumBin: RESOLVED_CHROMIUM_BIN, // Task 929
2784
+ accountId: installAccountId, // Task 955
2785
+ });
2786
+ writeFileSync(join(serviceDir, BRAND.serviceName), serviceFile);
2787
+ // Task 001 (maxy-code) — write the claude-session-manager unit. The main
2788
+ // brand unit Requires= + After= this one, so systemd starts it first.
2789
+ const claudeSessionManagerUnitName = `${BRAND.hostname}-claude-session-manager.service`;
2790
+ const claudeSessionManagerUnit = buildClaudeSessionManagerUnitFile({
2791
+ productName: BRAND.productName,
2792
+ brandHostname: BRAND.hostname,
2793
+ installDir: INSTALL_DIR,
2794
+ persistDir,
2795
+ claudeSessionManagerPort: CLAUDE_SESSION_MANAGER_PORT_BRAND,
2796
+ accountId: installAccountId,
2797
+ });
2798
+ writeFileSync(join(serviceDir, claudeSessionManagerUnitName), claudeSessionManagerUnit);
2799
+ logFile(` ${claudeSessionManagerUnitName}: CLAUDE_SESSION_MANAGER_PORT=${CLAUDE_SESSION_MANAGER_PORT_BRAND}`);
2800
+ // Task 647 — the edge service: always-on front door that owns the public
2801
+ // port (PORT) and the VNC stack (Xtigervnc + websockify). Its lifecycle is
2802
+ // independent of the main brand service, so an in-place upgrade triggered
2803
+ // from the admin terminal can restart the main brand service without
2804
+ // disconnecting the browser's remote terminal WebSocket.
2805
+ //
2806
+ // Task 662: the unit is per-brand so two brands on the same device each
2807
+ // own their own edge listener on their own EDGE_PORT — installing brand B
2808
+ // never rewrites brand A's unit or steals brand A's public port. Upgrades
2809
+ // from pre-662 installs require the manual recovery paragraph in
2810
+ // .docs/deployment.md before re-running this installer; auto-migration is
2811
+ // intentionally scoped out.
2812
+ // VNC_DISPLAY (defined above for the main brand unit) is also stamped into
2813
+ // the edge unit so both services agree on the X display. Per-brand display
2814
+ // closes the cross-brand cookie leak documented in Task 553.
2815
+ const edgeTemplatePath = resolve(INSTALL_DIR, "platform/templates/systemd/edge.service.template");
2816
+ if (existsSync(edgeTemplatePath)) {
2817
+ const edgeServiceContent = readFileSync(edgeTemplatePath, "utf-8")
2818
+ .replace(/__INSTALL_DIR__/g, INSTALL_DIR)
2819
+ .replace(/__EDGE_PORT__/g, String(PORT))
2820
+ .replace(/__MAXY_UI_PORT__/g, String(MAXY_UI_INTERNAL_PORT))
2821
+ .replace(/__PERSIST_DIR__/g, persistDir)
2822
+ .replace(/__VNC_DISPLAY__/g, String(VNC_DISPLAY))
2823
+ .replace(/__WEBSOCKIFY_PORT__/g, String(WEBSOCKIFY_PORT_BRAND))
2824
+ .replace(/__CDP_PORT__/g, String(CDP_PORT_BRAND))
2825
+ .replace(/__RFB_PORT__/g, String(RFB_PORT));
2826
+ writeFileSync(join(serviceDir, edgeUnitName), edgeServiceContent);
2827
+ logFile(` ${edgeUnitName}: EDGE_PORT=${PORT} MAXY_UI_PORT=${MAXY_UI_INTERNAL_PORT} VNC_DISPLAY=:${VNC_DISPLAY} RFB_PORT=${RFB_PORT} WEBSOCKIFY_PORT=${WEBSOCKIFY_PORT_BRAND} CDP_PORT=${CDP_PORT_BRAND}`);
2828
+ }
2829
+ else {
2830
+ console.error(` WARNING: edge.service.template missing at ${edgeTemplatePath} — VNC transport unavailable`);
2831
+ }
2832
+ // Task 560: the unit declares Environment=PATH=%h/.local/bin:... so the graph
2833
+ // MCP shim's spawn("uvx", ...) resolves against uv's install location. Without
2834
+ // this line, the service inherits the default systemd-user PATH — which
2835
+ // excludes ~/.local/bin — and the shim exits ENOENT before any query lands.
2836
+ logFile(` ${BRAND.serviceName}: PATH env includes %h/.local/bin for uvx resolution`);
2837
+ // WiFi AP provisioning service — system-level (requires root for hostapd/dnsmasq).
2838
+ // Runs at boot to check connectivity. If no saved WiFi and no ethernet, activates
2839
+ // a temporary AP with a captive portal for first-time WiFi configuration.
2840
+ const currentUser = execFileSync("whoami", [], { encoding: "utf-8" }).trim();
2841
+ const wifiProvisionService = `[Unit]
2842
+ Description=${BRAND.productName} WiFi Provisioning
2843
+ After=NetworkManager.service
2844
+ Before=${BRAND.serviceName}
2845
+
2846
+ [Service]
2847
+ Type=simple
2848
+ ExecStart=/bin/bash ${INSTALL_DIR}/platform/scripts/wifi-provision.sh
2849
+ ExecStopPost=/bin/bash ${INSTALL_DIR}/platform/scripts/wifi-provision.sh cleanup
2850
+ Environment=MAXY_PLATFORM_ROOT=${INSTALL_DIR}/platform
2851
+ Environment=INSTALL_USER=${currentUser}
2852
+ Environment=INSTALL_HOME=${process.env.HOME ?? "/home/" + currentUser}
2853
+ TimeoutStartSec=300
2854
+ TimeoutStopSec=30
2855
+
2856
+ [Install]
2857
+ WantedBy=multi-user.target
2858
+ `;
2859
+ const systemServiceDir = "/etc/systemd/system";
2860
+ const wifiProvisionPath = join(systemServiceDir, "wifi-provision.service");
2861
+ try {
2862
+ const tmpPath = "/tmp/wifi-provision.service";
2863
+ writeFileSync(tmpPath, wifiProvisionService);
2864
+ console.log(" [privileged] cp");
2865
+ shell("cp", [tmpPath, wifiProvisionPath], { sudo: true });
2866
+ spawnSync("rm", ["-f", tmpPath]);
2867
+ spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
2868
+ spawnSync("sudo", ["systemctl", "enable", "wifi-provision"], { stdio: "inherit" });
2869
+ logFile(" WiFi provisioning service installed and enabled");
2870
+ }
2871
+ catch (err) {
2872
+ console.error(` WARNING: Failed to install wifi-provision service: ${err instanceof Error ? err.message : String(err)}`);
2873
+ }
2874
+ // Disable hostapd and dnsmasq system services (we manage them manually
2875
+ // from wifi-provision.sh — the system services would conflict).
2876
+ spawnSync("sudo", ["systemctl", "stop", "hostapd"], { stdio: "ignore" });
2877
+ spawnSync("sudo", ["systemctl", "disable", "hostapd"], { stdio: "ignore" });
2878
+ spawnSync("sudo", ["systemctl", "stop", "dnsmasq"], { stdio: "ignore" });
2879
+ spawnSync("sudo", ["systemctl", "disable", "dnsmasq"], { stdio: "ignore" });
2880
+ // Enable lingering so user services run without login
2881
+ try {
2882
+ spawnSync("sudo", ["loginctl", "enable-linger", currentUser], { stdio: "inherit" });
2883
+ }
2884
+ catch { /* not critical */ }
2885
+ // Reload and (re)start.
2886
+ //
2887
+ // Task 647 ordering: on upgrades, the old main brand service still holds
2888
+ // the public port (PORT). Stop it FIRST so the edge can bind that socket;
2889
+ // starting the edge first would race against the old main brand service
2890
+ // and fail with EADDRINUSE. Fresh installs: the stop is a no-op.
2891
+ const unitName = BRAND.serviceName.replace(".service", "");
2892
+ const claudeSessionManagerUnitShort = claudeSessionManagerUnitName.replace(".service", "");
2893
+ spawnSync("systemctl", ["--user", "stop", unitName], { stdio: "inherit" });
2894
+ spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
2895
+ spawnSync("systemctl", ["--user", "enable", edgeUnitShort], { stdio: "inherit" });
2896
+ spawnSync("systemctl", ["--user", "enable", claudeSessionManagerUnitShort], { stdio: "inherit" });
2897
+ spawnSync("systemctl", ["--user", "enable", unitName], { stdio: "inherit" });
2898
+ // edge first: binds public port + starts VNC stack. Then claude-session-manager
2899
+ // (so the main brand unit's Requires= is satisfied). Then main brand service.
2900
+ spawnSync("systemctl", ["--user", "restart", edgeUnitShort], { stdio: "inherit" });
2901
+ spawnSync("systemctl", ["--user", "restart", claudeSessionManagerUnitShort], { stdio: "inherit" });
2902
+ spawnSync("systemctl", ["--user", "restart", unitName], { stdio: "inherit" });
2903
+ // Wait for the server to come up
2904
+ console.log(" Waiting for web server...");
2905
+ let webServerUp = false;
2906
+ for (let i = 0; i < 20; i++) {
2907
+ try {
2908
+ execFileSync("curl", ["-sf", `http://localhost:${PORT}`, "-o", "/dev/null"], { timeout: 3000 });
2909
+ webServerUp = true;
2910
+ break;
2911
+ }
2912
+ catch {
2913
+ spawnSync("sleep", ["2"]);
2914
+ }
2915
+ }
2916
+ if (!webServerUp) {
2917
+ console.log(` Server may still be starting. Check http://${DEVICE_HOSTNAME}.local:${PORT} in a moment.`);
2918
+ }
2919
+ // Register cron jobs — depends on web server, independent of CDP
2920
+ installCrons();
2921
+ // Validate CDP: the programmatic Playwright MCP server connects to Chromium
2922
+ // via --cdp-endpoint http://127.0.0.1:9222. In virtual (VNC) mode, Chromium is
2923
+ // started by vnc.sh (ExecStartPre) before the web server — a failed probe here
2924
+ // indicates a genuine VNC boot failure and must fail the install loudly.
2925
+ // In native mode, Chromium is launched on-demand by the server and is not
2926
+ // expected to be bound at install time — the probe is skipped.
2927
+ if (DISPLAY_MODE === "native") {
2928
+ console.log(" [cdp-check] skipped reason=native-display (on-demand Chromium)");
2929
+ }
2930
+ else {
2931
+ console.log(` Verifying browser automation (CDP on port ${CDP_PORT_BRAND})...`);
2932
+ const cdpCheck = spawnSync("curl", ["-sf", `http://127.0.0.1:${CDP_PORT_BRAND}/json/version`, "-o", "/dev/null"], {
2933
+ timeout: 5000,
2934
+ stdio: "pipe",
2935
+ });
2936
+ if (cdpCheck.status === 0) {
2937
+ console.log(" Browser automation ready (CDP connected).");
2938
+ }
2939
+ else {
2940
+ const vncLogPath = resolve(process.env.HOME ?? "/root", BRAND.configDir, "logs/vnc-boot.log");
2941
+ let vncLog = "";
2942
+ try {
2943
+ vncLog = readFileSync(vncLogPath, "utf-8").slice(-2000);
2944
+ }
2945
+ catch {
2946
+ vncLog = `(no boot log found at ${vncLogPath})`;
2947
+ }
2948
+ console.error("");
2949
+ console.error(`Setup failed: Browser automation unavailable — CDP port ${CDP_PORT_BRAND} not responding`);
2950
+ console.error(` ERROR: Browser automation unavailable — CDP port ${CDP_PORT_BRAND} not responding.`);
2951
+ console.error(" Chromium should be started by vnc.sh (ExecStartPre). Check the boot log:");
2952
+ console.error("");
2953
+ console.error(vncLog);
2954
+ process.exit(1);
2955
+ }
2956
+ }
2957
+ // Tunnel script smoke check — the cloudflare SKILL contract invokes
2958
+ // ~/setup-tunnel.sh directly. If the install-time symlink creation in
2959
+ // installTunnelScripts() didn't land, fail the install loudly here rather
2960
+ // than shipping the exact Task 456 gap Task 555 exists to close.
2961
+ for (const linkPath of [
2962
+ resolve(process.env.HOME ?? "/root", "setup-tunnel.sh"),
2963
+ resolve(process.env.HOME ?? "/root", "reset-tunnel.sh"),
2964
+ resolve(process.env.HOME ?? "/root", "list-cf-domains.sh"),
2965
+ ]) {
2966
+ try {
2967
+ accessSync(linkPath, fsConstants.X_OK);
2968
+ console.log(` [create-maxy] verify: ${linkPath} executable ✓`);
2969
+ }
2970
+ catch (err) {
2971
+ console.error("");
2972
+ console.error(`Setup failed: tunnel script symlink missing or not executable: ${linkPath}`);
2973
+ console.error(` [create-maxy:error] tunnel script symlink missing or not executable: ${linkPath}`);
2974
+ console.error(` ${err.message}`);
2975
+ process.exit(1);
2976
+ }
2977
+ }
2978
+ }
2979
+ // ---------------------------------------------------------------------------
2980
+ // Main
2981
+ // ---------------------------------------------------------------------------
2982
+ // Route to uninstall if --uninstall flag is present
2983
+ const _args = process.argv.slice(2);
2984
+ if (_args.includes("--uninstall")) {
2985
+ const { runUninstall } = await import("./uninstall.js");
2986
+ const exportIdx = _args.indexOf("--export-data");
2987
+ const exportPath = exportIdx !== -1 ? _args[exportIdx + 1] : undefined;
2988
+ const skipConfirm = _args.includes("--yes");
2989
+ if (exportIdx !== -1 && !exportPath) {
2990
+ console.error("Setup failed: --export-data requires a path argument");
2991
+ console.error("--export-data requires a path argument.");
2992
+ process.exit(1);
2993
+ }
2994
+ await runUninstall({ exportPath, skipConfirm });
2995
+ process.exit(0);
2996
+ }
2997
+ // ---------------------------------------------------------------------------
2998
+ // Port — install-time flag, not a brand attribute.
2999
+ //
3000
+ // Priority: --port flag > .env override > existing service file > default 19200.
3001
+ // systemd applies EnvironmentFile=-~/.{brand}/.env AFTER Environment=PORT,
3002
+ // so .env is the final runtime truth and must be checked first on upgrade.
3003
+ //
3004
+ // Task 666 — this block also performs one-shot port-drift recovery against
3005
+ // maxy-edge.service's Environment=EDGE_PORT=. See port-resolution.ts for the
3006
+ // pure logic; unit tests live at __tests__/port-canonicalisation.test.mjs.
3007
+ // ---------------------------------------------------------------------------
3008
+ let portFlag;
3009
+ const portIdx = _args.indexOf("--port");
3010
+ if (portIdx !== -1) {
3011
+ const raw = _args[portIdx + 1];
3012
+ const parsed = raw ? parseInt(raw, 10) : NaN;
3013
+ if (isNaN(parsed) || parsed < 1024 || parsed > 65535) {
3014
+ console.error(`Setup failed: --port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"})`);
3015
+ console.error(`Error: --port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"}).`);
3016
+ process.exit(1);
3017
+ }
3018
+ portFlag = parsed;
3019
+ }
3020
+ const _portResolution = resolveInstallPortFromFs({
3021
+ portFlag,
3022
+ persistDir: resolve(process.env.HOME ?? "/root", BRAND.configDir),
3023
+ serviceDir: resolve(process.env.HOME ?? "/root", ".config/systemd/user"),
3024
+ brandServiceFileName: BRAND.serviceName,
3025
+ brandEdgeServiceFileName: `${BRAND.hostname}-edge.service`,
3026
+ });
3027
+ let PORT = _portResolution.port;
3028
+ let PORT_SOURCE = _portResolution.source;
3029
+ for (const line of _portResolution.driftLogs)
3030
+ console.log(line);
3031
+ // ---------------------------------------------------------------------------
3032
+ // Hostname — install-time flag, not solely a brand attribute.
3033
+ //
3034
+ // Priority: --hostname flag > OS detection (same-brand upgrade) > OS preservation
3035
+ // (another brand's service detected) > BRAND.hostname (fresh install).
3036
+ // When --hostname is provided, it is set unconditionally — no detection, no preservation.
3037
+ // ---------------------------------------------------------------------------
3038
+ let HOSTNAME_FLAG;
3039
+ const hostnameIdx = _args.indexOf("--hostname");
3040
+ if (hostnameIdx !== -1) {
3041
+ const raw = _args[hostnameIdx + 1];
3042
+ if (!raw || raw.startsWith("--")) {
3043
+ console.error("Setup failed: --hostname requires a value");
3044
+ console.error("Error: --hostname requires a value (e.g. --hostname muvin).");
3045
+ process.exit(1);
3046
+ }
3047
+ // RFC 1123: lowercase alphanumeric + hyphens, max 63 chars, no leading/trailing hyphen
3048
+ if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(raw)) {
3049
+ console.error(`Setup failed: --hostname value '${raw}' is invalid`);
3050
+ console.error(`Error: --hostname value '${raw}' is invalid. Must be lowercase letters, digits, and hyphens only (max 63 chars, no leading/trailing hyphen).`);
3051
+ process.exit(1);
3052
+ }
3053
+ HOSTNAME_FLAG = raw;
3054
+ }
3055
+ let DISPLAY_MODE = "virtual";
3056
+ let DISPLAY_MODE_SOURCE = "default";
3057
+ const displayIdx = _args.indexOf("--display");
3058
+ if (displayIdx !== -1) {
3059
+ const raw = _args[displayIdx + 1];
3060
+ if (!raw || raw.startsWith("--")) {
3061
+ console.error("Setup failed: --display requires a value");
3062
+ console.error("Error: --display requires a value: native or virtual.");
3063
+ process.exit(1);
3064
+ }
3065
+ if (raw !== "native" && raw !== "virtual") {
3066
+ console.error(`Setup failed: --display value '${raw}' is invalid`);
3067
+ console.error(`Error: --display value '${raw}' is invalid. Must be 'native' or 'virtual'.`);
3068
+ process.exit(1);
3069
+ }
3070
+ DISPLAY_MODE = raw;
3071
+ DISPLAY_MODE_SOURCE = "--display flag";
3072
+ }
3073
+ else {
3074
+ // Upgrade detection: preserve existing DISPLAY_MODE from .env
3075
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
3076
+ const envPath = join(persistDir, ".env");
3077
+ try {
3078
+ if (existsSync(envPath)) {
3079
+ const envContent = readFileSync(envPath, "utf-8");
3080
+ const envMatch = envContent.match(/^DISPLAY_MODE=(native|virtual)$/m);
3081
+ if (envMatch) {
3082
+ DISPLAY_MODE = envMatch[1];
3083
+ DISPLAY_MODE_SOURCE = ".env (preserved)";
3084
+ }
3085
+ }
3086
+ }
3087
+ catch { /* non-critical */ }
3088
+ }
3089
+ // ---------------------------------------------------------------------------
3090
+ // Embedding model — install-time flag, not a brand attribute.
3091
+ //
3092
+ // Controls which Ollama embedding model is pulled and the dimension count used
3093
+ // for Neo4j vector indexes. Dimensions are fixed at schema-creation time; changing
3094
+ // them later requires dropping and recreating all vector indexes.
3095
+ //
3096
+ // Priority: --embed-model flag > .env (upgrade) > default nomic-embed-text.
3097
+ // --embed-dimensions is required for models not in the curated lookup table.
3098
+ // --embed-dimensions without --embed-model is rejected (dimensions are model-specific).
3099
+ // ---------------------------------------------------------------------------
3100
+ const EMBED_MODEL_DIMS = {
3101
+ "nomic-embed-text": 768,
3102
+ "nomic-embed-text-v1.5": 768,
3103
+ "mxbai-embed-large": 1024,
3104
+ "snowflake-arctic-embed:335m": 768,
3105
+ };
3106
+ const DEFAULT_EMBED_MODEL = "nomic-embed-text";
3107
+ const DEFAULT_EMBED_DIMS = 768;
3108
+ let EMBED_MODEL = DEFAULT_EMBED_MODEL;
3109
+ let EMBED_DIMS = DEFAULT_EMBED_DIMS;
3110
+ let EMBED_SOURCE = "default";
3111
+ const embedModelIdx = _args.indexOf("--embed-model");
3112
+ const embedDimsIdx = _args.indexOf("--embed-dimensions");
3113
+ if (embedDimsIdx !== -1 && embedModelIdx === -1) {
3114
+ console.error("Setup failed: --embed-dimensions requires --embed-model");
3115
+ console.error("Error: --embed-dimensions requires --embed-model (dimensions are model-specific).");
3116
+ process.exit(1);
3117
+ }
3118
+ if (embedModelIdx !== -1) {
3119
+ const raw = _args[embedModelIdx + 1];
3120
+ if (!raw || raw.startsWith("--")) {
3121
+ console.error("Setup failed: --embed-model requires a value");
3122
+ console.error("Error: --embed-model requires a value (e.g. --embed-model mxbai-embed-large).");
3123
+ process.exit(1);
3124
+ }
3125
+ EMBED_MODEL = raw;
3126
+ EMBED_SOURCE = "--embed-model flag";
3127
+ if (embedDimsIdx !== -1) {
3128
+ // Explicit dimensions override the lookup table
3129
+ const rawDims = _args[embedDimsIdx + 1];
3130
+ const parsed = rawDims ? parseInt(rawDims, 10) : NaN;
3131
+ if (isNaN(parsed) || parsed <= 0) {
3132
+ console.error(`Setup failed: --embed-dimensions requires a positive integer (got: ${rawDims ?? "nothing"})`);
3133
+ console.error(`Error: --embed-dimensions requires a positive integer (got: ${rawDims ?? "nothing"}).`);
3134
+ process.exit(1);
3135
+ }
3136
+ EMBED_DIMS = parsed;
3137
+ }
3138
+ else if (EMBED_MODEL in EMBED_MODEL_DIMS) {
3139
+ // Known model — resolve dimensions from lookup table
3140
+ EMBED_DIMS = EMBED_MODEL_DIMS[EMBED_MODEL];
3141
+ }
3142
+ else {
3143
+ // Unknown model without explicit dimensions — cannot proceed
3144
+ console.error(`Setup failed: unknown embedding model '${EMBED_MODEL}'`);
3145
+ console.error(`Error: Unknown embedding model '${EMBED_MODEL}'.`);
3146
+ console.error("Known models and their dimensions:");
3147
+ for (const [model, dims] of Object.entries(EMBED_MODEL_DIMS)) {
3148
+ console.error(` ${model} — ${dims} dimensions`);
3149
+ }
3150
+ console.error("\nFor a custom model, pass --embed-dimensions N explicitly:");
3151
+ console.error(` npx -y @rubytech/create-maxy --embed-model ${EMBED_MODEL} --embed-dimensions 512`);
3152
+ process.exit(1);
3153
+ }
3154
+ }
3155
+ else {
3156
+ // No --embed-model flag: check .env for upgrade preservation
3157
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
3158
+ const envPath = join(persistDir, ".env");
3159
+ try {
3160
+ if (existsSync(envPath)) {
3161
+ const envContent = readFileSync(envPath, "utf-8");
3162
+ const modelMatch = envContent.match(/^EMBED_MODEL=(.+)$/m);
3163
+ const dimsMatch = envContent.match(/^EMBED_DIMENSIONS=(\d+)$/m);
3164
+ if (modelMatch) {
3165
+ EMBED_MODEL = modelMatch[1];
3166
+ EMBED_SOURCE = ".env (preserved)";
3167
+ if (dimsMatch) {
3168
+ EMBED_DIMS = parseInt(dimsMatch[1], 10);
3169
+ }
3170
+ else if (EMBED_MODEL in EMBED_MODEL_DIMS) {
3171
+ EMBED_DIMS = EMBED_MODEL_DIMS[EMBED_MODEL];
3172
+ }
3173
+ // Unknown model without dims in .env: keep DEFAULT_EMBED_DIMS — the schema
3174
+ // was created with whatever dims were configured at original install time.
3175
+ }
3176
+ }
3177
+ }
3178
+ catch { /* non-critical */ }
3179
+ }
3180
+ // ---------------------------------------------------------------------------
3181
+ // Neo4j port — multi-brand data isolation.
3182
+ //
3183
+ // Default Maxy brand: 7687 (shared system Neo4j instance). Branded builds ship
3184
+ // a dedicated port in brand.json (e.g. Real Agent = 7688) so installing a
3185
+ // second brand on the same device gets its own database by default — Task 659.
3186
+ //
3187
+ // Priority: --neo4j-port flag > .env NEO4J_URI (upgrade preserve) > BRAND.neo4jPort > 7687.
3188
+ // ---------------------------------------------------------------------------
3189
+ const DEFAULT_NEO4J_PORT = 7687;
3190
+ let NEO4J_PORT = BRAND.neo4jPort ?? DEFAULT_NEO4J_PORT;
3191
+ let NEO4J_PORT_SOURCE = BRAND.neo4jPort ? "brand.json" : "default";
3192
+ const neo4jPortIdx = _args.indexOf("--neo4j-port");
3193
+ if (neo4jPortIdx !== -1) {
3194
+ const raw = _args[neo4jPortIdx + 1];
3195
+ const parsed = raw ? parseInt(raw, 10) : NaN;
3196
+ if (isNaN(parsed) || parsed < 1024 || parsed > 65535) {
3197
+ console.error(`Setup failed: --neo4j-port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"})`);
3198
+ console.error(`Error: --neo4j-port requires a numeric value between 1024 and 65535 (got: ${raw ?? "nothing"}).`);
3199
+ process.exit(1);
3200
+ }
3201
+ NEO4J_PORT = parsed;
3202
+ NEO4J_PORT_SOURCE = "--neo4j-port flag";
3203
+ }
3204
+ else {
3205
+ // Upgrade detection: check .env for NEO4J_URI=bolt://localhost:{port}.
3206
+ // Preserves an existing install's port even if brand.json now says different.
3207
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
3208
+ const envPath = join(persistDir, ".env");
3209
+ try {
3210
+ if (existsSync(envPath)) {
3211
+ const envContent = readFileSync(envPath, "utf-8");
3212
+ const uriMatch = envContent.match(/^NEO4J_URI=bolt:\/\/localhost:(\d+)$/m);
3213
+ if (uriMatch) {
3214
+ const envPort = parseInt(uriMatch[1], 10);
3215
+ if (envPort >= 1024 && envPort <= 65535) {
3216
+ NEO4J_PORT = envPort;
3217
+ NEO4J_PORT_SOURCE = ".env (preserved)";
3218
+ }
3219
+ }
3220
+ }
3221
+ }
3222
+ catch { /* non-critical */ }
3223
+ }
3224
+ // Dedicated = port differs from the default shared instance
3225
+ const NEO4J_DEDICATED = NEO4J_PORT !== DEFAULT_NEO4J_PORT;
3226
+ // ---------------------------------------------------------------------------
3227
+ // Task 664 removed the per-brand ttyd port — the admin terminal stack
3228
+ // (ttyd, tmux, xterm.js) was retired in favour of the action runner that
3229
+ // spawns transient `systemd-run --user` units per upgrade or setup-tunnel
3230
+ // invocation. No TCP listener on the device needs to be reserved for an
3231
+ // interactive-shell surface any more.
3232
+ const PKG_VERSION = JSON.parse(readFileSync(resolve(import.meta.dirname, "../package.json"), "utf-8")).version;
3233
+ // ---------------------------------------------------------------------------
3234
+ // Task 840 — pre-flight platform refusal.
3235
+ //
3236
+ // Runs BEFORE initLogging() (LOG_DIR creation), port resolution, and any
3237
+ // brew/scutil/hostnamectl probe. Only Linux + darwin are supported (Task 836);
3238
+ // on darwin, macOS major must be ≥ 14 (the floor required by Tasks 838/839).
3239
+ // Older macOS partially succeeds, then breaks at the supervisor or brew-cellar
3240
+ // layer with cryptic errors — refusing loudly here is the contract.
3241
+ //
3242
+ // Platform-header line is emitted on every install start so operators can grep
3243
+ // `[create-maxy] platform=` to confirm pre-flight ran. On darwin the line also
3244
+ // carries `macos=<v>` (Task 840 token) so the macOS-14 floor check is visible.
3245
+ // ---------------------------------------------------------------------------
3246
+ const PLATFORM = requireSupportedPlatform(process.platform);
3247
+ let MACOS_VERSION = null;
3248
+ if (PLATFORM === "darwin") {
3249
+ const swVers = spawnSync("sw_vers", [], { encoding: "utf-8", stdio: "pipe", timeout: 5_000 });
3250
+ const parsed = parseSwVers(swVers.stdout ?? "");
3251
+ if (!parsed) {
3252
+ const head = (swVers.stdout ?? "").split("\n").slice(0, 2).join(" | ");
3253
+ console.error(`[create-maxy] sw_vers stdout malformed: ${head}`);
3254
+ console.error(`[create-maxy] platform=darwin macos=<unknown> — refusing: macOS 14+ required`);
3255
+ process.exit(1);
3256
+ }
3257
+ MACOS_VERSION = parsed.version;
3258
+ if (!isSupportedMacosVersion(MACOS_VERSION)) {
3259
+ console.error(`[create-maxy] platform=darwin macos=${MACOS_VERSION} — refusing: macOS 14+ required`);
3260
+ process.exit(1);
3261
+ }
3262
+ }
3263
+ // Platform-header — first log line. Operators grep `[create-maxy] platform=`
3264
+ // to confirm pre-flight ran. The /* macos=<v> */ token is the Task 840 marker;
3265
+ // keep it on this same line so 3-way merges with parallel installer edits stay
3266
+ // mechanical (no rewrap, no split into two log lines).
3267
+ const PLATFORM_HEADER = `[create-maxy] platform=${PLATFORM} arch=${process.arch}` +
3268
+ (MACOS_VERSION ? ` macos=${MACOS_VERSION}` : ``) +
3269
+ ` version=${PKG_VERSION}`;
3270
+ initLogging();
3271
+ console.log(PLATFORM_HEADER);
3272
+ console.log("================================================================");
3273
+ console.log(` ${BRAND.productName} — ${BRAND.tagline}. (${BRAND.hostname} v${PKG_VERSION})`);
3274
+ console.log("================================================================");
3275
+ console.log(` Install log: ${LOG_FILE}`);
3276
+ console.log(` Port: ${PORT} (${PORT_SOURCE})`);
3277
+ if (HOSTNAME_FLAG)
3278
+ console.log(` Hostname: ${HOSTNAME_FLAG} (from --hostname flag)`);
3279
+ console.log(` Display: ${DISPLAY_MODE} (${DISPLAY_MODE_SOURCE})`);
3280
+ console.log(` Embed model: ${EMBED_MODEL} (${EMBED_DIMS} dims, ${EMBED_SOURCE})`);
3281
+ console.log(` Neo4j: ${NEO4J_DEDICATED ? "dedicated" : "shared"} on bolt://localhost:${NEO4J_PORT} (${NEO4J_PORT_SOURCE})`);
3282
+ console.log("");
3283
+ logDiagnostics("pre-flight");
3284
+ logFile(` Neo4j instance: ${NEO4J_DEDICATED ? "dedicated" : "shared"} on bolt://localhost:${NEO4J_PORT}`);
3285
+ try {
3286
+ installSystemDeps();
3287
+ installNodejs();
3288
+ installClaudeCode();
3289
+ installNeo4j();
3290
+ setupDedicatedNeo4j();
3291
+ installOllama(EMBED_MODEL);
3292
+ installUv();
3293
+ installCloudflared();
3294
+ installWhisperCpp();
3295
+ deployPayload(); // Must happen before ensureNeo4jPassword — restores config backup
3296
+ // Task 929: write the resolved Chromium absolute path into the deployed
3297
+ // platform/config/ so vnc.sh, writeChromiumWrapper, and setup-tunnel.sh
3298
+ // all read the same value. Must run after deployPayload (config dir is
3299
+ // a payload subdirectory). Linux-only; no-op on darwin/non-linux.
3300
+ writeChromiumBinaryPathFile();
3301
+ // Task 744: scrub plaintext neo4j passwords from any pre-fix install-*.log.
3302
+ // Idempotent — re-running on already-redacted logs is a no-op. Runs after
3303
+ // payload deploy so the bundled redact-install-logs.sh is on disk.
3304
+ redactInstallLogs();
3305
+ ensureNeo4jPassword(); // Now config/.neo4j-password is available if it existed before
3306
+ provisionRemoteSessionSecret(); // Task 653: shared HMAC key readable by maxy-edge + maxy-ui
3307
+ buildPlatform();
3308
+ setupVncViewer();
3309
+ setupAccount();
3310
+ installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
3311
+ installService();
3312
+ console.log("");
3313
+ console.log("================================================================");
3314
+ console.log("");
3315
+ console.log(` Open in your browser: http://${DEVICE_HOSTNAME}.local:${PORT}`);
3316
+ console.log("");
3317
+ console.log("================================================================");
3318
+ }
3319
+ catch (err) {
3320
+ console.error("");
3321
+ console.error(`Setup failed: ${err instanceof Error ? err.message : String(err)}`);
3322
+ console.error(` Full log: ${LOG_FILE}`);
3323
+ logDiagnostics("post-failure");
3324
+ process.exit(1);
3325
+ }