wiki-viewer 2.9.2 → 2.9.3

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 (396) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +0 -4
  3. package/.next/standalone/.next/build-manifest.json +5 -5
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +4 -4
  6. package/.next/standalone/.next/routes-manifest.json +0 -24
  7. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/api/agent/activity/route.js +7 -7
  19. package/.next/standalone/.next/server/app/api/agent/activity/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js +5 -5
  21. package/.next/standalone/.next/server/app/api/agent/admin/agents/[agentId]/revoke/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js +5 -5
  23. package/.next/standalone/.next/server/app/api/agent/admin/agents/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js +5 -5
  25. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/approve/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js +5 -5
  27. package/.next/standalone/.next/server/app/api/agent/admin/registrations/[regId]/deny/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js +5 -5
  29. package/.next/standalone/.next/server/app/api/agent/admin/registrations/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js +7 -7
  31. package/.next/standalone/.next/server/app/api/agent/events/[...path]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js +9 -9
  33. package/.next/standalone/.next/server/app/api/agent/files/[...path]/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js +7 -7
  35. package/.next/standalone/.next/server/app/api/agent/fs/file/[...path]/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js +8 -8
  37. package/.next/standalone/.next/server/app/api/agent/fs/ls/[[...path]]/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js +8 -8
  39. package/.next/standalone/.next/server/app/api/agent/fs/move/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js +8 -8
  41. package/.next/standalone/.next/server/app/api/agent/fs/search/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js +8 -8
  43. package/.next/standalone/.next/server/app/api/agent/internal/span/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/agent/settings/route.js +8 -8
  45. package/.next/standalone/.next/server/app/api/agent/settings/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js +8 -8
  47. package/.next/standalone/.next/server/app/api/agent/sidecar/[...path]/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js +10 -2
  49. package/.next/standalone/.next/server/app/api/app-proxy/[...path]/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/assets/[...path]/route.js +5 -5
  51. package/.next/standalone/.next/server/app/api/assets/[...path]/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/auth/[...all]/route.js +2 -2
  53. package/.next/standalone/.next/server/app/api/pdf/save/route.js +8 -8
  54. package/.next/standalone/.next/server/app/api/pdf/save/route.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/api/share/[token]/asset/route.js +3 -2
  56. package/.next/standalone/.next/server/app/api/share/[token]/asset/route.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/api/share/[token]/route.js +8 -7
  58. package/.next/standalone/.next/server/app/api/share/[token]/route.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/api/share/route.js +8 -8
  60. package/.next/standalone/.next/server/app/api/share/route.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/api/system/admins/route.js +5 -5
  62. package/.next/standalone/.next/server/app/api/system/admins/route.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/api/system/api-key/route.js +2 -2
  64. package/.next/standalone/.next/server/app/api/system/api-key/route.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/api/system/auth-config/route.js +3 -3
  66. package/.next/standalone/.next/server/app/api/system/auth-config/route.js.nft.json +1 -1
  67. package/.next/standalone/.next/server/app/api/system/auth-settings/route.js +5 -5
  68. package/.next/standalone/.next/server/app/api/system/auth-settings/route.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/api/system/browse/route.js +4 -4
  70. package/.next/standalone/.next/server/app/api/system/browse/route.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/api/system/config/route.js +3 -3
  72. package/.next/standalone/.next/server/app/api/system/config/route.js.nft.json +1 -1
  73. package/.next/standalone/.next/server/app/api/system/pins/route.js +5 -5
  74. package/.next/standalone/.next/server/app/api/system/pins/route.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/api/system/reveal/route.js +4 -4
  76. package/.next/standalone/.next/server/app/api/system/reveal/route.js.nft.json +1 -1
  77. package/.next/standalone/.next/server/app/api/system/users/route.js +4 -4
  78. package/.next/standalone/.next/server/app/api/system/users/route.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/branch/route.js +5 -5
  80. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/branch/route.js.nft.json +1 -1
  81. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js +5 -5
  82. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/open/route.js.nft.json +1 -1
  83. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/refresh/route.js +5 -5
  84. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/refresh/route.js.nft.json +1 -1
  85. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js +5 -5
  86. package/.next/standalone/.next/server/app/api/system/workspaces/[id]/route.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/api/system/workspaces/route.js +4 -4
  88. package/.next/standalone/.next/server/app/api/system/workspaces/route.js.nft.json +1 -1
  89. package/.next/standalone/.next/server/app/api/upload/[...path]/route.js +7 -7
  90. package/.next/standalone/.next/server/app/api/upload/[...path]/route.js.nft.json +1 -1
  91. package/.next/standalone/.next/server/app/api/wiki/app/route.js +9 -9
  92. package/.next/standalone/.next/server/app/api/wiki/app/route.js.nft.json +1 -1
  93. package/.next/standalone/.next/server/app/api/wiki/backlinks/route.js +7 -7
  94. package/.next/standalone/.next/server/app/api/wiki/backlinks/route.js.nft.json +1 -1
  95. package/.next/standalone/.next/server/app/api/wiki/content/route.js +10 -10
  96. package/.next/standalone/.next/server/app/api/wiki/content/route.js.nft.json +1 -1
  97. package/.next/standalone/.next/server/app/api/wiki/download/route.js +9 -9
  98. package/.next/standalone/.next/server/app/api/wiki/download/route.js.nft.json +1 -1
  99. package/.next/standalone/.next/server/app/api/wiki/folder/route.js +7 -7
  100. package/.next/standalone/.next/server/app/api/wiki/folder/route.js.nft.json +1 -1
  101. package/.next/standalone/.next/server/app/api/wiki/git-branches/route.js +7 -7
  102. package/.next/standalone/.next/server/app/api/wiki/git-branches/route.js.nft.json +1 -1
  103. package/.next/standalone/.next/server/app/api/wiki/git-checkout/route.js +6 -6
  104. package/.next/standalone/.next/server/app/api/wiki/git-checkout/route.js.nft.json +1 -1
  105. package/.next/standalone/.next/server/app/api/wiki/git-diff/route.js +7 -7
  106. package/.next/standalone/.next/server/app/api/wiki/git-diff/route.js.nft.json +1 -1
  107. package/.next/standalone/.next/server/app/api/wiki/git-file-info/route.js +7 -7
  108. package/.next/standalone/.next/server/app/api/wiki/git-file-info/route.js.nft.json +1 -1
  109. package/.next/standalone/.next/server/app/api/wiki/git-history/route.js +7 -7
  110. package/.next/standalone/.next/server/app/api/wiki/git-history/route.js.nft.json +1 -1
  111. package/.next/standalone/.next/server/app/api/wiki/git-pull/route.js +8 -8
  112. package/.next/standalone/.next/server/app/api/wiki/git-pull/route.js.nft.json +1 -1
  113. package/.next/standalone/.next/server/app/api/wiki/move/route.js +8 -8
  114. package/.next/standalone/.next/server/app/api/wiki/move/route.js.nft.json +1 -1
  115. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js +8 -8
  116. package/.next/standalone/.next/server/app/api/wiki/new-file/route.js.nft.json +1 -1
  117. package/.next/standalone/.next/server/app/api/wiki/outlinks/route.js +7 -7
  118. package/.next/standalone/.next/server/app/api/wiki/outlinks/route.js.nft.json +1 -1
  119. package/.next/standalone/.next/server/app/api/wiki/page/route.js +8 -8
  120. package/.next/standalone/.next/server/app/api/wiki/page/route.js.nft.json +1 -1
  121. package/.next/standalone/.next/server/app/api/wiki/presence/route.js +7 -7
  122. package/.next/standalone/.next/server/app/api/wiki/presence/route.js.nft.json +1 -1
  123. package/.next/standalone/.next/server/app/api/wiki/route.js +8 -8
  124. package/.next/standalone/.next/server/app/api/wiki/route.js.nft.json +1 -1
  125. package/.next/standalone/.next/server/app/api/wiki/search/route.js +8 -8
  126. package/.next/standalone/.next/server/app/api/wiki/search/route.js.nft.json +1 -1
  127. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js +8 -8
  128. package/.next/standalone/.next/server/app/api/wiki/slugs/route.js.nft.json +1 -1
  129. package/.next/standalone/.next/server/app/api/wiki/upload/route.js +8 -8
  130. package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
  131. package/.next/standalone/.next/server/app/api/wiki/watch/route.js +4 -4
  132. package/.next/standalone/.next/server/app/api/wiki/watch/route.js.nft.json +1 -1
  133. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  134. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  135. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  136. package/.next/standalone/.next/server/app/s/[token]/page/build-manifest.json +2 -2
  137. package/.next/standalone/.next/server/app/s/[token]/page.js.nft.json +1 -1
  138. package/.next/standalone/.next/server/app/s/[token]/page_client-reference-manifest.js +1 -1
  139. package/.next/standalone/.next/server/app/signin/page/build-manifest.json +2 -2
  140. package/.next/standalone/.next/server/app/signin/page.js +4 -4
  141. package/.next/standalone/.next/server/app/signin/page.js.nft.json +1 -1
  142. package/.next/standalone/.next/server/app/signin/page_client-reference-manifest.js +1 -1
  143. package/.next/standalone/.next/server/app-paths-manifest.json +0 -4
  144. package/.next/standalone/.next/server/chunks/0p91_next_dist_esm_build_templates_app-route_0ytral2.js +1 -1
  145. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.xk8gi._.js +3 -0
  146. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00awqt2._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00sn-o5._.js +16 -0
  148. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0mw1brk._.js → [root-of-the-server]__01y-312._.js} +2 -2
  149. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__12jdw86._.js → [root-of-the-server]__02373p-._.js} +1 -1
  150. package/.next/standalone/.next/server/chunks/[root-of-the-server]__03_q-~2._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/[root-of-the-server]__03c09g7._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/[root-of-the-server]__03prziv._.js +3 -0
  153. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04vo55d._.js +3 -0
  154. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04~wbjj._.js +3 -0
  155. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05tbv~.._.js +16 -0
  156. package/.next/standalone/.next/server/chunks/[root-of-the-server]__068r9h7._.js +3 -0
  157. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06jm~o8._.js +3 -0
  158. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07ox9i8._.js +12 -0
  159. package/.next/standalone/.next/server/chunks/[root-of-the-server]__088zh92._.js +3 -0
  160. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_5~w8k._.js +20 -0
  161. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_g-.kr._.js +20 -0
  162. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0bdxjy5._.js +1 -1
  163. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10c4_fo._.js → [root-of-the-server]__0e1kbl7._.js} +1 -1
  164. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__12v.eva._.js → [root-of-the-server]__0er9oof._.js} +2 -2
  165. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e~2gkf._.js +3 -0
  166. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f2frhc._.js +4 -0
  167. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0hgccef._.js +3 -0
  168. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0egcl8n._.js → [root-of-the-server]__0j40f60._.js} +2 -2
  169. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__04mie79._.js → [root-of-the-server]__0k0t_.e._.js} +1 -1
  170. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0k46u9j._.js +3 -0
  171. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kau~ca._.js +3 -0
  172. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kuwuwk._.js +3 -0
  173. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kvxxt~._.js +3 -0
  174. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l5io1-._.js +3 -0
  175. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0md2vr3._.js +3 -0
  176. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0muj-bp._.js +3 -0
  177. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nnxcs7._.js +3 -0
  178. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0omny3u._.js +3 -0
  179. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ou7hai._.js +3 -0
  180. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0r--676._.js +3 -0
  181. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0r55bc0._.js +3 -0
  182. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rdt~cr._.js +3 -0
  183. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0tizmgc._.js +3 -0
  184. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vr.qhy._.js +3 -0
  185. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0915i_~._.js → [root-of-the-server]__0x.030x._.js} +2 -2
  186. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__11mhei8._.js → [root-of-the-server]__0xlo2r_._.js} +2 -2
  187. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yfb5md._.js +3 -0
  188. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0a7c~hj._.js → [root-of-the-server]__0z5xk6f._.js} +1 -1
  189. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~gd460._.js +3 -0
  190. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~i3blt._.js +3 -0
  191. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10p-nga._.js +3 -0
  192. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10zrueo._.js +12 -0
  193. package/.next/standalone/.next/server/chunks/[root-of-the-server]__11ltgmp._.js +20 -0
  194. package/.next/standalone/.next/server/chunks/[root-of-the-server]__11zy4x1._.js +6 -6
  195. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12bl1v6._.js +3 -0
  196. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12g8s_j._.js +3 -0
  197. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h_w~h._.js +4 -0
  198. package/.next/standalone/.next/server/chunks/_079xuln._.js +1 -1
  199. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_workspaces_[id]_open_route_actions_1087xu7.js +1 -1
  200. package/.next/standalone/.next/server/chunks/node_modules__pnpm_0aj5g-w._.js +1 -1
  201. package/.next/standalone/.next/server/chunks/src_0ks~u~e._.js +1 -1
  202. package/.next/standalone/.next/server/chunks/src_lib_fs_mounts_ts_0ksy80l._.js +1 -1
  203. package/.next/standalone/.next/server/chunks/src_lib_workspaces_ts_0qon40u._.js +2 -2
  204. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0bt7d39._.js → 0e.4_sonner_dist_index_mjs_0u2em8w._.js} +2 -2
  205. package/.next/standalone/.next/server/chunks/ssr/{0~w0_katex_dist_katex_mjs_0p7ph25._.js → 0~w0_katex_dist_katex_mjs_018q_uc._.js} +1 -1
  206. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_chunk-5ZQYHXKU_mjs_0z1.978._.js +1 -1
  207. package/.next/standalone/.next/server/chunks/ssr/12y~_mermaid_dist_chunks_mermaid_core_chunk-KSCS5N6A_mjs_0v4oeem._.js +1 -1
  208. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ybtj28._.js → [root-of-the-server]__02ov~kn._.js} +2 -2
  209. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__13k6~ax._.js → [root-of-the-server]__0n5kphk._.js} +2 -2
  210. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vglc36._.js +3 -0
  211. package/.next/standalone/.next/server/chunks/ssr/_00pku9f._.js +3 -0
  212. package/.next/standalone/.next/server/chunks/ssr/_0_.s7b8._.js +3 -0
  213. package/.next/standalone/.next/server/chunks/ssr/_0oq8kwv._.js +174 -0
  214. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_04qqrjr._.js → node_modules__pnpm_0.wgpvp._.js} +2 -2
  215. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_09k0q18._.js → node_modules__pnpm_02j0p3a._.js} +1 -1
  216. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0wg48.8._.js → node_modules__pnpm_02yqus6._.js} +1 -1
  217. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_11g2aee._.js → node_modules__pnpm_07bf4mp._.js} +1 -1
  218. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0a4su30._.js +1 -1
  219. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0a-f4gi._.js → node_modules__pnpm_0a~3f2a._.js} +1 -1
  220. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0819uyi._.js → node_modules__pnpm_0bnc0lk._.js} +2 -2
  221. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0b3l3b4._.js → node_modules__pnpm_0dm.meu._.js} +1 -1
  222. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0rc4s48._.js → node_modules__pnpm_0ebvpwi._.js} +1 -1
  223. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0jp7ggg._.js → node_modules__pnpm_0f.5boy._.js} +1 -1
  224. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ha2xsd._.js → node_modules__pnpm_0f9vj0~._.js} +2 -2
  225. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ypcps.._.js → node_modules__pnpm_0f9vuiw._.js} +2 -2
  226. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_06usij4._.js → node_modules__pnpm_0fa23-q._.js} +1 -1
  227. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0cuckmp._.js → node_modules__pnpm_0g21lw4._.js} +1 -1
  228. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_09ybwhl._.js → node_modules__pnpm_0kdk2ot._.js} +1 -1
  229. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0wjizot._.js → node_modules__pnpm_0kkbph0._.js} +2 -2
  230. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0xp3s_y._.js → node_modules__pnpm_0mv0.4i._.js} +1 -1
  231. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0z~bh__._.js → node_modules__pnpm_0nt22aq._.js} +2 -2
  232. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0o~d81.._.js +1 -1
  233. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0m2hb6k._.js → node_modules__pnpm_0p4w0~q._.js} +1 -1
  234. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_03ciu.l._.js → node_modules__pnpm_0pv3k.9._.js} +1 -1
  235. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_09c3lg3._.js → node_modules__pnpm_0qmqt0m._.js} +1 -1
  236. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0iuzd3o._.js → node_modules__pnpm_0q~maar._.js} +1 -1
  237. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0ldft78._.js → node_modules__pnpm_0s1th-1._.js} +1 -1
  238. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0qhs4r2._.js → node_modules__pnpm_0tdnho_._.js} +1 -1
  239. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0dsnv76._.js → node_modules__pnpm_0w6oo9l._.js} +1 -1
  240. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0eiz-8_._.js → node_modules__pnpm_0x9-_1d._.js} +1 -1
  241. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0gnc95r._.js → node_modules__pnpm_0xq~zku._.js} +2 -2
  242. package/.next/standalone/.next/server/chunks/ssr/{node_modules__pnpm_0~88b7s._.js → node_modules__pnpm_12nfd13._.js} +1 -1
  243. package/.next/standalone/.next/server/chunks/ssr/src_app_s_[token]_page_tsx_0_fzrmd._.js +1 -1
  244. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  245. package/.next/standalone/.next/server/pages/500.html +1 -1
  246. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  247. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  248. package/.next/standalone/.next/static/chunks/00_12ekhbvhhp.js +1 -0
  249. package/.next/standalone/.next/static/chunks/01u~45r8icusu.js +1 -0
  250. package/.next/standalone/.next/static/chunks/0aeii_m.9~9_t.js +1 -0
  251. package/.next/standalone/.next/static/chunks/0fm2eul-wqh2f.js +1 -0
  252. package/.next/standalone/.next/static/chunks/{08usk_~k6d0yf.js → 0k31ncl7m_j9q.js} +2 -2
  253. package/.next/standalone/.next/static/chunks/0n7px0u5.eizf.css +9 -0
  254. package/.next/standalone/.next/static/chunks/0qs6cl.~q-0tv.js +1 -0
  255. package/.next/standalone/.next/static/chunks/0wu_n6m9jg8rn.js +1 -0
  256. package/.next/standalone/.next/static/chunks/0yixq3fhhpk~9.js +172 -0
  257. package/.next/standalone/.next/static/chunks/{turbopack-0~8fsjmdvb_1i.js → turbopack-1105gjd__15ti.js} +1 -1
  258. package/.next/standalone/.test-floor +1 -1
  259. package/.next/standalone/biome.json +70 -0
  260. package/.next/standalone/node_modules/@vscode/ripgrep-linux-x64/bin/rg +0 -0
  261. package/.next/standalone/package.json +8 -9
  262. package/.next/standalone/scripts/format-check.mjs +25 -0
  263. package/.next/standalone/scripts/test-floor.mjs +86 -37
  264. package/.next/standalone/server.js +1 -1
  265. package/README.md +12 -5
  266. package/bin/cli/config.js +105 -0
  267. package/bin/cli/prompt.js +36 -0
  268. package/bin/cli/serve.js +413 -0
  269. package/bin/cli/service.js +304 -0
  270. package/bin/cli/update.js +58 -0
  271. package/bin/cli/wizard.js +134 -0
  272. package/bin/shared/ssh-target.js +154 -0
  273. package/bin/wiki-viewer.js +118 -1224
  274. package/package.json +19 -13
  275. package/.next/standalone/.next/server/app/api/system/clear-root/route/app-paths-manifest.json +0 -3
  276. package/.next/standalone/.next/server/app/api/system/clear-root/route/build-manifest.json +0 -9
  277. package/.next/standalone/.next/server/app/api/system/clear-root/route/server-reference-manifest.json +0 -4
  278. package/.next/standalone/.next/server/app/api/system/clear-root/route.js +0 -14
  279. package/.next/standalone/.next/server/app/api/system/clear-root/route.js.map +0 -5
  280. package/.next/standalone/.next/server/app/api/system/clear-root/route.js.nft.json +0 -1
  281. package/.next/standalone/.next/server/app/api/system/clear-root/route_client-reference-manifest.js +0 -3
  282. package/.next/standalone/.next/server/app/api/system/root-status/route/app-paths-manifest.json +0 -3
  283. package/.next/standalone/.next/server/app/api/system/root-status/route/build-manifest.json +0 -9
  284. package/.next/standalone/.next/server/app/api/system/root-status/route/server-reference-manifest.json +0 -4
  285. package/.next/standalone/.next/server/app/api/system/root-status/route.js +0 -14
  286. package/.next/standalone/.next/server/app/api/system/root-status/route.js.map +0 -5
  287. package/.next/standalone/.next/server/app/api/system/root-status/route.js.nft.json +0 -1
  288. package/.next/standalone/.next/server/app/api/system/root-status/route_client-reference-manifest.js +0 -3
  289. package/.next/standalone/.next/server/app/api/system/set-root/route/app-paths-manifest.json +0 -3
  290. package/.next/standalone/.next/server/app/api/system/set-root/route/build-manifest.json +0 -9
  291. package/.next/standalone/.next/server/app/api/system/set-root/route/server-reference-manifest.json +0 -4
  292. package/.next/standalone/.next/server/app/api/system/set-root/route.js +0 -15
  293. package/.next/standalone/.next/server/app/api/system/set-root/route.js.map +0 -5
  294. package/.next/standalone/.next/server/app/api/system/set-root/route.js.nft.json +0 -1
  295. package/.next/standalone/.next/server/app/api/system/set-root/route_client-reference-manifest.js +0 -3
  296. package/.next/standalone/.next/server/app/api/wiki/file/route/app-paths-manifest.json +0 -3
  297. package/.next/standalone/.next/server/app/api/wiki/file/route/build-manifest.json +0 -9
  298. package/.next/standalone/.next/server/app/api/wiki/file/route/server-reference-manifest.json +0 -4
  299. package/.next/standalone/.next/server/app/api/wiki/file/route.js +0 -16
  300. package/.next/standalone/.next/server/app/api/wiki/file/route.js.map +0 -5
  301. package/.next/standalone/.next/server/app/api/wiki/file/route.js.nft.json +0 -1
  302. package/.next/standalone/.next/server/app/api/wiki/file/route_client-reference-manifest.js +0 -3
  303. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-3eby~._.js +0 -3
  304. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-bd36s._.js +0 -3
  305. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0._o62m._.js +0 -3
  306. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.f~mr1._.js +0 -3
  307. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.xsx4e._.js +0 -3
  308. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01w5s-4._.js +0 -3
  309. package/.next/standalone/.next/server/chunks/[root-of-the-server]__02hiw0n._.js +0 -3
  310. package/.next/standalone/.next/server/chunks/[root-of-the-server]__037g~yp._.js +0 -20
  311. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04yzrn9._.js +0 -3
  312. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05dk_5q._.js +0 -3
  313. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05nk8xg._.js +0 -3
  314. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05wicn_._.js +0 -3
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__07i72.r._.js +0 -3
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__086~~78._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08h0txl._.js +0 -3
  318. package/.next/standalone/.next/server/chunks/[root-of-the-server]__09ezcba._.js +0 -3
  319. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0au~ncj._.js +0 -3
  320. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0c-49tx._.js +0 -3
  321. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0di13ue._.js +0 -3
  322. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dkxp4y._.js +0 -3
  323. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jhwupj._.js +0 -3
  324. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ker6~l._.js +0 -3
  325. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0mkrdf0._.js +0 -3
  326. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nzjsw9._.js +0 -3
  327. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o0lsmx._.js +0 -3
  328. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0omc1-s._.js +0 -20
  329. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0os3ej1._.js +0 -16
  330. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0px8._m._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qe7kxb._.js +0 -3
  332. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qe8lje._.js +0 -3
  333. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qox302._.js +0 -3
  334. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s5ns33._.js +0 -3
  335. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0s6kt0y._.js +0 -3
  336. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t_s2dg._.js +0 -20
  337. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0tchvup._.js +0 -3
  338. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0tw6h_5._.js +0 -3
  339. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmp13_._.js +0 -3
  340. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z8qb~s._.js +0 -3
  341. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zy2f8y._.js +0 -12
  342. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0~s~szv._.js +0 -16
  343. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1024gkd._.js +0 -3
  344. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12abg.4._.js +0 -3
  345. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_clear-root_route_actions_065znew.js +0 -3
  346. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_root-status_route_actions_0stp57y.js +0 -3
  347. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_system_set-root_route_actions_0vxn8ey.js +0 -3
  348. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_wiki_file_route_actions_0ytnc0i.js +0 -3
  349. package/.next/standalone/.next/server/chunks/ssr/0~w0_katex_dist_katex_mjs_12lt4cp._.js +0 -3
  350. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0a3bjyp._.js +0 -3
  351. package/.next/standalone/.next/server/chunks/ssr/_03xt0rl._.js +0 -158
  352. package/.next/standalone/.next/server/chunks/ssr/_06rf50_._.js +0 -3
  353. package/.next/standalone/.next/server/chunks/ssr/_0i3r51-._.js +0 -158
  354. package/.next/standalone/.next/server/chunks/ssr/_0jdop6l._.js +0 -3
  355. package/.next/standalone/.next/server/chunks/ssr/_0v4uz-4._.js +0 -19
  356. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0.6uh54._.js +0 -3
  357. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_00o9din._.js +0 -3
  358. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_02jnz2o._.js +0 -17
  359. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_03jchtd._.js +0 -3
  360. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_04xh4re._.js +0 -26
  361. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_07p8tmd._.js +0 -17
  362. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_08jqh.4._.js +0 -3
  363. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0_up15t._.js +0 -17
  364. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0c.b4rt._.js +0 -3
  365. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0dr38_k._.js +0 -31
  366. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0e-nvo5._.js +0 -45
  367. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0e9l2~p._.js +0 -3
  368. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0k_8g5d._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0kli5~f._.js +0 -3
  370. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0n-ec__._.js +0 -3
  371. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0odzirk._.js +0 -3
  372. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0ov9ih8._.js +0 -17
  373. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0qb~hug._.js +0 -17
  374. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0t24-57._.js +0 -3
  375. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0umkew~._.js +0 -17
  376. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0w_fkrb._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0yfe2s1._.js +0 -3
  378. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_0~ovwm1._.js +0 -3
  379. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_10-i1v8._.js +0 -3
  380. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_12dmj0l._.js +0 -3
  381. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_133w.gx._.js +0 -11
  382. package/.next/standalone/.next/server/chunks/ssr/node_modules__pnpm_13y5ncy._.js +0 -3
  383. package/.next/standalone/.next/static/chunks/0-lsw0io13aha.js +0 -1
  384. package/.next/standalone/.next/static/chunks/01-v2wyaj~5gn.js +0 -1
  385. package/.next/standalone/.next/static/chunks/03zmegz1kvvod.js +0 -17
  386. package/.next/standalone/.next/static/chunks/04_l8s7dy454_.js +0 -1
  387. package/.next/standalone/.next/static/chunks/0a~z4.g.dtxjm.js +0 -156
  388. package/.next/standalone/.next/static/chunks/0ky8fha87kp0_.js +0 -1
  389. package/.next/standalone/.next/static/chunks/0nzvnpn.j1~sx.js +0 -1
  390. package/.next/standalone/.next/static/chunks/0t5bc7buy7sx9.js +0 -156
  391. package/.next/standalone/.next/static/chunks/0umlolnxs~j3r.js +0 -1
  392. package/.next/standalone/.next/static/chunks/1012fl5fxholx.js +0 -1
  393. package/.next/standalone/.next/static/chunks/17isnstdq92js.css +0 -5
  394. /package/.next/standalone/.next/static/{R2lG97g-a6HpbZZtS4qep → I6ZpQF8Ua7ytNZMW5ZKWH}/_buildManifest.js +0 -0
  395. /package/.next/standalone/.next/static/{R2lG97g-a6HpbZZtS4qep → I6ZpQF8Ua7ytNZMW5ZKWH}/_clientMiddlewareManifest.js +0 -0
  396. /package/.next/standalone/.next/static/{R2lG97g-a6HpbZZtS4qep → I6ZpQF8Ua7ytNZMW5ZKWH}/_ssgManifest.js +0 -0
@@ -1,1232 +1,126 @@
1
1
  #!/usr/bin/env node
2
- import { spawn, execSync, execFileSync } from "node:child_process";
3
- import { createServer as createHttpsServer } from "node:https";
4
- import { createServer as createHttpServer, request as httpRequest } from "node:http";
5
- import { createServer as createNetServer } from "node:net";
6
- import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
7
3
  import path from "node:path";
8
- import os from "node:os";
9
4
  import { fileURLToPath } from "node:url";
10
- import { createInterface } from "node:readline";
11
- import { stdin, stdout } from "node:process";
5
+ import { start, resolveServeOptions, resolveRunOptions } from "./cli/serve.js";
6
+ import {
7
+ serviceInstall,
8
+ serviceUninstall,
9
+ serviceStatus,
10
+ serviceLogs,
11
+ serviceRestart,
12
+ } from "./cli/service.js";
13
+ import { configCommand } from "./cli/config.js";
14
+ import { update } from "./cli/update.js";
15
+ import { runWizard } from "./cli/wizard.js";
12
16
 
13
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
18
  const appRoot = path.resolve(__dirname, "..");
15
- const serverJs = path.join(appRoot, ".next", "standalone", "server.js");
16
- const selfScript = fileURLToPath(import.meta.url);
17
-
18
- const configDir = path.join(os.homedir(), ".wiki-viewer");
19
- const configPath = path.join(configDir, "config.json");
20
- const logDir = path.join(configDir, "logs");
21
-
22
- const SERVICE_NAME = "wiki-viewer";
23
- const LAUNCHD_LABEL = "com.wiki-viewer";
24
19
 
25
20
  function printUsage() {
26
- console.error("Usage: wiki-viewer [directory] [options]");
27
- console.error(" wiki-viewer <command> [args]");
28
- console.error("");
29
- console.error(" directory Directory to serve (optional — pick in browser if omitted).");
30
- console.error(" May also be an SSH target (user@host:/path) — mounted via");
31
- console.error(" sshfs and served live, no local clone.");
32
- console.error("");
33
- console.error("Options:");
34
- console.error(" -p, --port <port> Port to listen on (default: 3000)");
35
- console.error(" -H, --host <host> Host to bind to (default: localhost)");
36
- console.error(" --https Enable HTTPS (self-signed cert, enables service workers)");
37
- console.error(" --no-auth Run without authentication — no sign-in, no session check");
38
- console.error(" --ssh-key <path> Private key for the SSH target (default: ssh-agent / host keys)");
39
- console.error(" --ssh-port <port> SSH port for the target (default: 22)");
40
- console.error(" --ssh-password Prompt for an SSH password (or set WIKI_SSH_PASSWORD)");
41
- console.error(" --ssh-readonly Mount the SSH target read-only");
42
- console.error(" -v, --version Print version");
43
- console.error("");
44
- console.error(" -e, --env <KEY=VALUE> Set an app env var (repeatable; persisted with service install)");
45
- console.error("");
46
- console.error("Commands:");
47
- console.error(" init Interactive setup wizard (dir, host/port, https, env, service)");
48
- console.error(" service install [dir] [options] Install as a user service (persists across reboot)");
49
- console.error(" service uninstall Remove the user service");
50
- console.error(" service status Show service status");
51
- console.error(" service logs Tail service logs");
52
- console.error(" service restart Restart the service");
53
- console.error(" service run Run from saved config (used internally by the service)");
54
- console.error(" config show Print the saved config");
55
- console.error(" config set KEY=VALUE Set an app env var in the config");
56
- console.error(" config unset KEY Remove an app env var from the config");
57
- console.error(" update Update wiki-viewer to the latest version and restart");
58
- console.error("");
59
- console.error("Examples:");
60
- console.error(" wiki-viewer init");
61
- console.error(" wiki-viewer ~/notes");
62
- console.error(" wiki-viewer ~/notes --https");
63
- console.error(" wiki-viewer ~/notes -p 8080 -H 0.0.0.0");
64
- console.error(" wiki-viewer me@server:/srv/docs");
65
- console.error(" wiki-viewer me@server:/srv/docs --ssh-key ~/.ssh/id_ed25519 --ssh-readonly");
66
- console.error(" wiki-viewer service install ~/notes -H 0.0.0.0 -p 3003 --https");
67
- console.error(" wiki-viewer service install ~/notes --env GOOGLE_CLIENT_ID=... --env GOOGLE_CLIENT_SECRET=...");
68
- console.error(" wiki-viewer config set AUTH_ALLOWED_DOMAIN=example.com");
69
- console.error(" wiki-viewer update");
70
- }
71
-
72
- // ── arg parsing ──────────────────────────────────────────────────────────────
73
-
74
- function parseServeArgs(args) {
75
- let port = process.env.PORT;
76
- let host = process.env.HOSTNAME;
77
- let useHttps;
78
- let userSpecifiedPort = false;
79
- let userSpecifiedHost = false;
80
- let rootDir;
81
- let noAuth;
82
- let sshKey;
83
- let sshPort;
84
- let sshPassword;
85
- let sshReadOnly;
86
-
87
- for (let i = 0; i < args.length; i++) {
88
- const a = args[i];
89
- if (a === "-p" || a === "--port") { port = args[++i] ?? port; userSpecifiedPort = true; }
90
- else if (a === "-H" || a === "--host") { host = args[++i] ?? host; userSpecifiedHost = true; }
91
- else if (a === "-e" || a === "--env") { i++; } // consumed by parseEnvFlags
92
- else if (a === "--https") useHttps = true;
93
- else if (a === "--no-auth") noAuth = true;
94
- else if (a === "--ssh-key") sshKey = args[++i];
95
- else if (a === "--ssh-port") sshPort = args[++i];
96
- else if (a === "--ssh-password") sshPassword = true;
97
- else if (a === "--ssh-readonly") sshReadOnly = true;
98
- else if (!a.startsWith("-") && rootDir === undefined) rootDir = a;
99
- }
100
-
101
- return { rootDir, port, host, useHttps, userSpecifiedPort, userSpecifiedHost, noAuth: Boolean(noAuth), sshKey, sshPort, sshPassword: Boolean(sshPassword), sshReadOnly: Boolean(sshReadOnly) };
102
- }
103
-
104
- // ── ssh (sshfs) targets ──────────────────────────────────────────────────────
105
- // Symmetry with the local-directory arg: `wiki-viewer user@host:/path` mounts a
106
- // remote directory over sshfs and serves it as ROOT_DIR — no local clone. The
107
- // mount is ephemeral (under ~/.wiki-viewer/mounts) and unmounted on exit. Auth:
108
- // ssh-agent/host keys by default, --ssh-key <path>, or --ssh-password.
109
-
110
- const mountsDir = path.join(configDir, "mounts");
111
- const FORBIDDEN_SSH = /[;|&$`<>(){}\n\r\0]/;
112
- let activeMount = null;
113
-
114
- function looksLikeSshTarget(s) {
115
- if (!s || FORBIDDEN_SSH.test(s)) return false;
116
- // [user@]host:/abs/path host has no slash, remote path is absolute. An
117
- // existing local path (e.g. a real dir literally named like this) wins.
118
- return /^([\w.-]+@)?[\w.-]+:\/.+/.test(s) && !existsSync(s);
119
- }
120
-
121
- function parseSshTarget(s) {
122
- if (!s || FORBIDDEN_SSH.test(s)) return null;
123
- const colon = s.indexOf(":");
124
- if (colon <= 0) return null;
125
- const hostPart = s.slice(0, colon);
126
- const remotePath = s.slice(colon + 1);
127
- if (!remotePath.startsWith("/") || remotePath.includes("..")) return null;
128
- let user;
129
- let host = hostPart;
130
- const at = hostPart.indexOf("@");
131
- if (at >= 0) { user = hostPart.slice(0, at); host = hostPart.slice(at + 1); if (!user) return null; }
132
- if (!host || !/^[a-zA-Z0-9.-]+$/.test(host)) return null;
133
- if (user && !/^[a-zA-Z0-9._-]+$/.test(user)) return null;
134
- return { user, host, remotePath };
135
- }
136
-
137
- // Mirror of src/lib/sshfs.ts buildSshfsArgs — kept in sync intentionally so the
138
- // bin stays dependency-light (no TS import at runtime).
139
- function buildSshfsArgs({ target, mountpoint, port, keyPath, password, readOnly }) {
140
- const userPart = target.user ? `${target.user}@` : "";
141
- const args = [`${userPart}${target.host}:${target.remotePath}`, mountpoint];
142
- if (port && Number.isInteger(Number(port)) && Number(port) > 0 && Number(port) < 65536) {
143
- args.push("-p", String(port));
144
- }
145
- const o = [
146
- "reconnect", "ServerAliveInterval=15", "ServerAliveCountMax=3",
147
- "compression=yes", "cache=yes", "kernel_cache",
148
- "StrictHostKeyChecking=accept-new", "BatchMode=yes",
149
- ];
150
- if (readOnly) o.push("ro");
151
- if (keyPath) { o.push(`IdentityFile=${keyPath}`, "IdentitiesOnly=yes"); }
152
- if (password != null) {
153
- const i = o.indexOf("BatchMode=yes"); if (i >= 0) o.splice(i, 1);
154
- o.push("password_stdin", "PreferredAuthentications=password,keyboard-interactive", "PubkeyAuthentication=no", "NumberOfPasswordPrompts=1");
155
- }
156
- args.push("-o", o.join(","));
157
- return args;
158
- }
159
-
160
- function isMountedSync(mp) {
161
- try {
162
- if (process.platform === "linux") {
163
- const m = readFileSync("/proc/mounts", "utf8");
164
- return m.split("\n").some((l) => { const p = l.split(" "); return p[1] === mp && /fuse/.test(p[2] || ""); });
165
- }
166
- const out = execFileSync("mount", [], { encoding: "utf8" });
167
- return out.split("\n").some((l) => l.includes(` on ${mp} `));
168
- } catch { return false; }
169
- }
170
-
171
- function unmountSync(mp) {
172
- const tries = process.platform === "linux"
173
- ? [["fusermount", ["-u", "-z", mp]], ["umount", [mp]]]
174
- : [["umount", [mp]], ["diskutil", ["unmount", "force", mp]]];
175
- for (const [bin, a] of tries) { try { execFileSync(bin, a, { stdio: "ignore" }); break; } catch { /* try next */ } }
176
- try { rmSync(mp, { recursive: false, force: true }); } catch { /* best-effort */ }
177
- }
178
-
179
- function registerMountCleanup() {
180
- const cleanup = () => { if (activeMount) { const mp = activeMount; activeMount = null; unmountSync(mp); } };
181
- process.on("exit", cleanup);
182
- for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
183
- process.on(sig, () => { cleanup(); process.exit(0); });
184
- }
185
- }
186
-
187
- async function mountSshTarget({ targetStr, port, keyPath, password, readOnly }) {
188
- const target = parseSshTarget(targetStr);
189
- if (!target) {
190
- console.error(`Error: invalid SSH target '${targetStr}'. Use the form user@host:/abs/path.`);
191
- process.exit(1);
192
- }
193
- try { execFileSync("sshfs", ["--version"], { stdio: "ignore" }); }
194
- catch {
195
- console.error("Error: sshfs not found. Install sshfs + FUSE (e.g. `apt install sshfs`, `brew install macfuse sshfs`).");
196
- process.exit(1);
197
- }
198
-
199
- mkdirSync(mountsDir, { recursive: true });
200
- const mp = path.join(mountsDir, `cli-${process.pid}`);
201
- // Reuse-or-clear a stale mountpoint from a previous crashed run.
202
- if (isMountedSync(mp)) unmountSync(mp);
203
- mkdirSync(mp, { recursive: true });
204
-
205
- const args = buildSshfsArgs({ target, mountpoint: mp, port, keyPath, password, readOnly });
206
- registerMountCleanup();
207
- await new Promise((resolve, reject) => {
208
- const child = spawn("sshfs", args, { stdio: ["pipe", "ignore", "pipe"] });
209
- let err = "";
210
- const t = setTimeout(() => { child.kill("SIGKILL"); reject(new Error("sshfs mount timed out after 25s")); }, 25_000);
211
- child.stderr.on("data", (d) => { err += d.toString(); });
212
- child.on("error", (e) => { clearTimeout(t); reject(e); });
213
- child.on("close", (code) => { clearTimeout(t); code === 0 ? resolve() : reject(new Error(err.trim() || `sshfs exited ${code}`)); });
214
- if (password != null) child.stdin.write(password + "\n");
215
- child.stdin.end();
216
- }).catch((e) => {
217
- console.error(`Error: sshfs mount failed: ${e.message}`);
218
- try { unmountSync(mp); } catch { /* ignore */ }
219
- process.exit(1);
220
- });
221
-
222
- if (!isMountedSync(mp)) {
223
- console.error("Error: sshfs reported success but the mount is not live.");
224
- unmountSync(mp);
225
- process.exit(1);
226
- }
227
- activeMount = mp;
228
- return mp;
229
- }
230
-
231
- // One-shot line prompt (echoes input). Used for --ssh-password when the
232
- // WIKI_SSH_PASSWORD env var is not set.
233
- async function promptLine(text) {
234
- const io = makePrompter();
235
- const ans = await io.prompt(text);
236
- io.close();
237
- return ans;
238
- }
239
- // ── config file ────────────────────────────────────────────────────────────
240
-
241
- function loadConfig() {
242
- if (!existsSync(configPath)) return {};
243
- try {
244
- return JSON.parse(readFileSync(configPath, "utf8"));
245
- } catch {
246
- console.error(`Warning: could not parse ${configPath}, ignoring it`);
247
- return {};
248
- }
249
- }
250
-
251
- function saveConfig(cfg) {
252
- mkdirSync(configDir, { recursive: true });
253
- writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
254
- }
255
-
256
- // Ad-hoc serve: CLI flags + built-in defaults. The run *shape* (dir/host/port)
257
- // does NOT read the config file, so an installed service never silently alters
258
- // a one-off invocation. App-level env (config.env) is still read, because those
259
- // are settings about the app itself (allowlists, OAuth) rather than the bind.
260
- function resolveServeOptions(args) {
261
- const cli = parseServeArgs(args);
262
- const cfg = loadConfig();
263
- const isSsh = cli.rootDir != null && looksLikeSshTarget(cli.rootDir);
264
- return {
265
- rootDir: cli.rootDir && !isSsh ? path.resolve(cli.rootDir) : null,
266
- sshTarget: isSsh ? cli.rootDir : null,
267
- sshKey: cli.sshKey, sshPort: cli.sshPort, sshPassword: cli.sshPassword, sshReadOnly: cli.sshReadOnly,
268
- port: String(cli.port ?? "3000"),
269
- host: cli.host ?? "localhost",
270
- useHttps: Boolean(cli.useHttps),
271
- userSpecifiedPort: cli.userSpecifiedPort,
272
- // App env: config.env as base, ad-hoc --env flags override per run.
273
- configEnv: { ...(cfg.env ?? {}), ...(cli.noAuth ? { WIKI_NO_AUTH: "1" } : {}), ...parseEnvFlags(args) },
274
- };
275
- }
276
-
277
- // Service run: config file is the source of truth. CLI flags (if any) still win
278
- // so the unit/plist could pass overrides, but normally there are none.
279
- // Precedence: explicit CLI flags > config file > built-in defaults.
280
- function resolveRunOptions(args) {
281
- const cli = parseServeArgs(args);
282
- const cfg = loadConfig();
283
-
284
- const rootDir = cli.rootDir ?? cfg.rootDir ?? null;
285
- const port = cli.port ?? cfg.port ?? "3000";
286
- const host = cli.host ?? cfg.host ?? "localhost";
287
- const useHttps = cli.useHttps ?? cfg.https ?? false;
288
- // A config-pinned port is explicit too (don't auto-bump to next free port).
289
- const userSpecifiedPort = cli.userSpecifiedPort || cfg.port != null;
290
-
291
- const isSsh = rootDir != null && looksLikeSshTarget(rootDir);
292
- return {
293
- rootDir: rootDir && !isSsh ? path.resolve(rootDir) : null,
294
- sshTarget: isSsh ? rootDir : null,
295
- // CLI flags win; otherwise fall back to persisted ssh settings (cfg.ssh).
296
- sshKey: cli.sshKey ?? cfg.ssh?.key,
297
- sshPort: cli.sshPort ?? cfg.ssh?.port,
298
- sshPassword: cli.sshPassword,
299
- sshReadOnly: cli.sshReadOnly || Boolean(cfg.ssh?.readOnly),
300
- port: String(port),
301
- host,
302
- useHttps: Boolean(useHttps),
303
- userSpecifiedPort,
304
- configEnv: { ...(cfg.env ?? {}), ...(cli.noAuth ? { WIKI_NO_AUTH: "1" } : {}), ...parseEnvFlags(args) },
305
- };
306
- }
307
-
308
- // ── environment resolution ───────────────────────────────────────────────────
309
-
310
- // Env vars the app reads that the bin can derive or manage on the user's behalf.
311
- // Everything else in config.env is passed through verbatim.
312
- const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1", ""]);
313
-
314
- function isLocalHost(h) {
315
- return LOCAL_HOSTS.has(h);
316
- }
317
-
318
- // Build the env for the spawned server.
319
- // Precedence (highest first):
320
- // 1. the shell environment the user launched us with (explicit override)
321
- // 2. config.env from ~/.wiki-viewer/config.json
322
- // 3. values derived by the bin from the run options (e.g. BETTER_AUTH_URL)
323
- //
324
- // Returns { env, warnings } where warnings are human-readable strings to print
325
- // before the server starts.
326
- function computeServerEnv({ host, port, useHttps, configEnv }) {
327
- const warnings = [];
328
- const derived = {};
329
-
330
- const scheme = useHttps ? "https" : "http";
331
- const urlHost = isLocalHost(host) || host === "0.0.0.0" ? "localhost" : host;
332
- const isSecureContext = useHttps || isLocalHost(host);
333
-
334
- // BETTER_AUTH_URL: the app requires this in production so cookies and OAuth
335
- // callbacks resolve. Derive it from the bind so the common case needs no
336
- // config at all.
337
- derived.BETTER_AUTH_URL = `${scheme}://${urlHost}:${port}`;
338
-
339
- // The app refuses to boot in production over an insecure context unless
340
- // WIKI_ALLOW_INSECURE=1. localhost http is fine (browsers treat it as secure).
341
- // Remote http is not: auth cookies and service workers will silently break.
342
- if (!isSecureContext) {
343
- derived.WIKI_ALLOW_INSECURE = "1";
344
- warnings.push(
345
- `Serving plain HTTP on a non-local host (${host}). Browsers treat this as\n` +
346
- ` an insecure context: login cookies, service workers and PDF.js will not\n` +
347
- ` work reliably, and OAuth callbacks will fail.\n` +
348
- ` Fix: re-run with --https, or put a TLS-terminating proxy in front and set\n` +
349
- ` BETTER_AUTH_URL to its public https:// URL (see "env" in ${configPath}).`,
350
- );
351
- } else if (isLocalHost(host) && !useHttps) {
352
- // localhost http is a secure context for the browser, but the app's prod
353
- // guard still wants the bypass flag set explicitly. Do it for the user.
354
- derived.WIKI_ALLOW_INSECURE = "1";
355
- }
356
-
357
- // Layer: derived < config.env < shell env.
358
- const env = { ...derived, ...configEnv };
359
-
360
- // Note when the shell or config overrode a derived value so the user isn't
361
- // surprised that --host/--port didn't change the auth URL.
362
- if (configEnv.BETTER_AUTH_URL && configEnv.BETTER_AUTH_URL !== derived.BETTER_AUTH_URL) {
363
- // config wins over derived; that's intentional, no warning needed.
364
- }
365
- if (process.env.BETTER_AUTH_URL && process.env.BETTER_AUTH_URL !== env.BETTER_AUTH_URL) {
366
- warnings.push(
367
- `BETTER_AUTH_URL is set in your shell (${process.env.BETTER_AUTH_URL}) and\n` +
368
- ` overrides the derived/config value.`,
369
- );
370
- }
371
-
372
- return { env, warnings };
373
- }
374
-
375
- // ── HTTPS cert generation ──────────────────────────────────────────────────
376
-
377
- function ensureCerts(host) {
378
- const dir = path.join(configDir, "certs");
379
- mkdirSync(dir, { recursive: true });
380
- const keyPath = path.join(dir, "key.pem");
381
- const certPath = path.join(dir, "cert.pem");
382
- if (existsSync(keyPath) && existsSync(certPath)) {
383
- return { key: readFileSync(keyPath), cert: readFileSync(certPath) };
384
- }
385
- // Try mkcert (trusted), fall back to openssl (self-signed)
386
- try {
387
- execSync("mkcert -version", { stdio: "ignore" });
388
- execSync(`mkcert -install 2>/dev/null; mkcert -key-file "${keyPath}" -cert-file "${certPath}" localhost 127.0.0.1 "${host}"`, { stdio: "pipe" });
389
- console.log("�� Trusted cert via mkcert");
390
- } catch {
391
- try {
392
- execSync(
393
- `openssl req -x509 -newkey rsa:2048 -keyout "${keyPath}" -out "${certPath}" -days 825 -nodes -subj "/CN=localhost"`,
394
- { stdio: "ignore" },
395
- );
396
- console.log("�� Self-signed cert (browser will warn once — click through)");
397
- } catch {
398
- console.error("Error: --https requires mkcert or openssl");
399
- process.exit(1);
400
- }
401
- }
402
- return { key: readFileSync(keyPath), cert: readFileSync(certPath) };
403
- }
404
-
405
- // ── port availability helpers ──────────────────────────────────────────────
406
-
407
- function isPortAvailable(p, h) {
408
- return new Promise((resolve) => {
409
- const s = createNetServer();
410
- s.once("error", () => resolve(false));
411
- s.once("listening", () => s.close(() => resolve(true)));
412
- s.listen(Number(p), h);
413
- });
414
- }
415
-
416
- async function findNextAvailablePort(startPort, h) {
417
- let p = Number(startPort);
418
- while (!(await isPortAvailable(p, h))) p++;
419
- return String(p);
420
- }
421
-
422
- function freePort() {
423
- return new Promise((resolve) => {
424
- const s = createNetServer();
425
- s.listen(0, "127.0.0.1", () => {
426
- const { port: p } = s.address();
427
- s.close(() => resolve(p));
428
- });
429
- });
430
- }
431
-
432
- function getNetworkAddress() {
433
- for (const ifaces of Object.values(os.networkInterfaces())) {
434
- for (const iface of ifaces ?? []) {
435
- if (iface.family === "IPv4" && !iface.internal) return iface.address;
436
- }
437
- }
438
- return null;
439
- }
440
-
441
- // ── start ──────────────────────────────────────────────────────────────────
442
-
443
- async function start(opts) {
444
- const { useHttps, configEnv = {} } = opts;
445
- let resolvedRoot = opts.rootDir;
446
- let { port, host, userSpecifiedPort } = opts;
447
-
448
- if (!existsSync(serverJs)) {
449
- console.error("Error: pre-built server not found at", serverJs);
450
- console.error("This is a bug – please report it at https://github.com/anh-chu/wiki-viewer/issues");
451
- process.exit(1);
452
- }
453
-
454
- // SSH target: mount it over sshfs and serve the mount point as ROOT_DIR.
455
- if (opts.sshTarget) {
456
- let password = null;
457
- if (opts.sshPassword) {
458
- password = process.env.WIKI_SSH_PASSWORD
459
- ?? await promptLine(`SSH password for ${opts.sshTarget}: `);
460
- }
461
- console.log(`🔗 Mounting ${opts.sshTarget} over SSH…`);
462
- resolvedRoot = await mountSshTarget({
463
- targetStr: opts.sshTarget,
464
- port: opts.sshPort,
465
- keyPath: opts.sshKey,
466
- password,
467
- readOnly: opts.sshReadOnly,
468
- });
469
- console.log(` mounted at ${resolvedRoot}${opts.sshReadOnly ? " (read-only)" : ""}`);
470
- }
471
-
472
- if (resolvedRoot) {
473
- console.log(`�� ${resolvedRoot}`);
474
- } else {
475
- console.log("�� No directory specified — open the browser to choose one");
476
- }
477
-
478
- // Auto-select next free port when user didn't specify one
479
- if (!userSpecifiedPort) {
480
- const available = await isPortAvailable(Number(port), host);
481
- if (!available) {
482
- const original = port;
483
- port = await findNextAvailablePort(Number(port) + 1, host);
484
- console.log(`⚠️ Port ${original} in use → using ${port} (pass -p <port> to override)`);
485
- }
486
- }
487
-
488
- // When HTTPS is requested, run the standalone server on a random internal
489
- // HTTP port and stand up an HTTPS reverse-proxy on the user-facing port.
490
- const internalPort = useHttps ? String(await freePort()) : port;
491
- // In HTTPS mode the standalone server sits behind the proxy on loopback.
492
- // Otherwise it must bind to the user-requested host directly.
493
- const internalHost = useHttps ? "127.0.0.1" : host;
494
-
495
- // Resolve the app environment (config.env + shell overrides + derived
496
- // BETTER_AUTH_URL etc.) against the user-facing host/port, then print any
497
- // guidance before the server boots.
498
- const { env: appEnv, warnings } = computeServerEnv({ host, port, useHttps, configEnv });
499
- for (const w of warnings) console.log(`\n⚠️ ${w}`);
500
-
501
- const child = spawn(process.execPath, [serverJs], {
502
- cwd: path.join(appRoot, ".next", "standalone"),
503
- stdio: "inherit",
504
- env: {
505
- ...appEnv, // derived defaults + config.env
506
- ...process.env, // shell env always wins over derived/config
507
- ...(resolvedRoot ? { ROOT_DIR: resolvedRoot } : {}),
508
- PORT: internalPort,
509
- HOSTNAME: internalHost,
510
- },
511
- });
512
-
513
- child.on("exit", (code) => process.exit(code ?? 0));
514
-
515
- if (useHttps) {
516
- const { key, cert } = ensureCerts(host);
517
-
518
- // Simple HTTPS → HTTP proxy
519
- const proxy = createHttpsServer({ key, cert }, (req, res) => {
520
- const options = {
521
- hostname: internalHost,
522
- port: Number(internalPort),
523
- path: req.url,
524
- method: req.method,
525
- headers: req.headers,
526
- };
527
- const upstream = httpRequest(options, (upRes) => {
528
- res.writeHead(upRes.statusCode, upRes.headers);
529
- upRes.pipe(res);
530
- });
531
- upstream.on("error", () => res.destroy());
532
- req.pipe(upstream);
533
- });
534
-
535
- // Wait a moment for the standalone server to bind before starting proxy
536
- setTimeout(() => {
537
- proxy.listen(Number(port), host, () => {
538
- const scheme = "https";
539
- const displayHost = host === "0.0.0.0" ? "localhost" : host;
540
- console.log(`\n ➜ Local: ${scheme}://${displayHost}:${port}`);
541
- const netAddr = getNetworkAddress();
542
- if (netAddr && host !== "localhost" && host !== "127.0.0.1") {
543
- console.log(` ➜ Network: ${scheme}://${netAddr}:${port}`);
544
- }
545
- console.log(`\n Listening on ${host}:${port} (--host / -H, --port / -p to rebind)\n`);
546
- });
547
- }, 1_000);
548
- } else {
549
- const scheme = "http";
550
- const displayHost = host === "0.0.0.0" ? "localhost" : host;
551
- console.log(`\n ➜ Local: ${scheme}://${displayHost}:${port}`);
552
- const netAddr = getNetworkAddress();
553
- if (netAddr && host !== "localhost" && host !== "127.0.0.1") {
554
- console.log(` ➜ Network: ${scheme}://${netAddr}:${port}`);
555
- }
556
- console.log(`\n Listening on ${host}:${port} (--host / -H, --port / -p to rebind)\n`);
557
- }
558
- }
559
-
560
- // ── service: shared ──────────────────────────────────────────────────────────
561
-
562
- function platform() {
563
- if (process.platform === "linux") return "linux";
564
- if (process.platform === "darwin") return "macos";
565
- return null;
566
- }
567
-
568
- function requireSupportedPlatform() {
569
- const p = platform();
570
- if (!p) {
571
- console.error(`Error: service management is only supported on Linux (systemd) and macOS (launchd), not ${process.platform}.`);
572
- process.exit(1);
573
- }
574
- return p;
575
- }
576
-
577
- function run(cmd, args, opts = {}) {
578
- return execFileSync(cmd, args, { stdio: "inherit", ...opts });
579
- }
580
-
581
- function runQuiet(cmd, args) {
582
- try {
583
- return execFileSync(cmd, args, { stdio: ["ignore", "pipe", "pipe"] }).toString();
584
- } catch (e) {
585
- return null;
586
- }
587
- }
588
-
589
- // ── service: install ──────────────────────────────────────────────────────────
590
-
591
- // A persistent service bakes an absolute path to this script into the unit /
592
- // plist (ExecStart). If we're running from a package-manager scratch cache
593
- // (`npx wiki-viewer …`, `pnpm dlx …`, or an OS temp dir), that path is deleted
594
- // the moment the cache is pruned, leaving a service that silently fails on the
595
- // next reboot. Detect it and refuse, pointing the user at a global install.
596
- function ephemeralInstallReason() {
597
- const p = selfScript;
598
- const sep = path.sep;
599
- if (p.includes(`${sep}_npx${sep}`)) return "an npx cache";
600
- if (p.includes(`${sep}dlx-`)) return "a pnpm/yarn dlx cache";
601
- const tmp = os.tmpdir();
602
- if (p === tmp || p.startsWith(tmp + sep)) return "a temporary directory";
603
- return null;
604
- }
605
-
606
- function requirePersistentInstall() {
607
- const reason = ephemeralInstallReason();
608
- if (!reason) return;
609
- console.error(`Error: cannot install a service from ${reason}.`);
610
- console.error(` wiki-viewer is running from ${selfScript},`);
611
- console.error(` which your package manager will delete. A service pointed there would`);
612
- console.error(` break after the next cache cleanup or reboot.`);
613
- console.error(``);
614
- console.error(` Install it globally first, then install the service:`);
615
- console.error(` npm install -g ${SERVICE_NAME}`);
616
- console.error(` ${SERVICE_NAME} service install ...`);
617
- process.exit(1);
618
- }
619
-
620
- // The unit/plist runs node by absolute path. process.execPath is often a
621
- // version-manager runtime pinned to one Node version (fnm/nvm) — a version bump
622
- // deletes that path and silently breaks the service. fnm keeps a stable
623
- // `aliases/default/bin/node` symlink that follows the user's default across
624
- // upgrades; prefer it. Never use `which node`: under fnm that's an ephemeral
625
- // per-shell path in /run that vanishes on reboot.
626
- function fnmRoot() {
627
- return process.env.FNM_DIR || path.join(os.homedir(), ".local", "share", "fnm");
628
- }
629
-
630
- function resolveServiceNode() {
631
- const exec = process.execPath;
632
- const looksFnm = exec.includes("/fnm/") || exec.includes("/.fnm/") || exec.includes("fnm_multishells") || exec.includes("/fnm/node-versions/") || exec.includes("/.local/share/fnm/");
633
- if (looksFnm) {
634
- const fnmDefault = path.join(fnmRoot(), "aliases", "default", "bin", "node");
635
- if (existsSync(fnmDefault)) return fnmDefault;
636
- }
637
- return exec;
638
- }
639
-
640
- // Symmetric to resolveServiceNode: when this script lives inside an fnm
641
- // version-pinned global tree (`.../node-versions/<ver>/installation/...`, where
642
- // `npm install -g` lands under fnm), a Node upgrade or prune deletes that whole
643
- // tree — including this file. Redirect ExecStart through the stable
644
- // `aliases/default` symlink, which follows the user's default version.
645
- function resolveServiceScript() {
646
- const script = selfScript;
647
- const pinned = path.join(fnmRoot(), "node-versions") + path.sep;
648
- if (!script.startsWith(pinned)) return script; // dev checkout / system install: already stable
649
- const parts = path.relative(path.join(fnmRoot(), "node-versions"), script).split(path.sep);
650
- // parts = [<ver>, "installation", ...rest]; aliases/default -> the installation dir.
651
- if (parts[1] !== "installation") return script;
652
- const durable = path.join(fnmRoot(), "aliases", "default", ...parts.slice(2));
653
- return existsSync(durable) ? durable : script;
654
- }
655
-
656
- // After resolving the most durable node we can, warn if it's *still* pinned to a
657
- // version-manager runtime version we couldn't redirect to a stable alias.
658
- function versionManagedNodeWarning(nodePath) {
659
- const markers = ["/.nvm/", "/.asdf/", "/.volta/", "/n/versions/", "/fnm_multishells/", "/node-versions/"];
660
- if (!markers.some((m) => nodePath.includes(m))) return null;
661
- return (
662
- `The service will run Node from a version-manager path:\n` +
663
- ` ${nodePath}\n` +
664
- ` If you upgrade or remove that Node version, the service will stop working.\n` +
665
- ` Set a stable default (e.g. \`fnm default <version>\`) or install wiki-viewer\n` +
666
- ` under a system Node, then re-run the install.`
667
- );
668
- }
669
-
670
- // Pull repeatable `--env KEY=VALUE` (or `-e KEY=VALUE`) pairs out of an argv
671
- // list. Returns the collected map; the caller has already parsed the rest.
672
- function parseEnvFlags(args) {
673
- const env = {};
674
- for (let i = 0; i < args.length; i++) {
675
- if (args[i] === "--env" || args[i] === "-e") {
676
- const pair = args[++i];
677
- if (!pair || !pair.includes("=")) {
678
- console.error(`Error: --env expects KEY=VALUE (got: ${pair ?? "(nothing)"})`);
679
- process.exit(1);
680
- }
681
- const idx = pair.indexOf("=");
682
- env[pair.slice(0, idx)] = pair.slice(idx + 1);
683
- }
684
- }
685
- return env;
686
- }
687
-
688
- function serviceInstall(args) {
689
- const p = requireSupportedPlatform();
690
- // Refuse before writing anything if we can't produce a durable ExecStart path.
691
- requirePersistentInstall();
692
-
693
- // Capture the run config from flags (falling back to existing config), persist it.
694
- const cli = parseServeArgs(args);
695
- const envFlags = parseEnvFlags(args);
696
- const existing = loadConfig();
697
- const mergedEnv = { ...(existing.env ?? {}), ...envFlags };
698
- const cliIsSsh = cli.rootDir != null && looksLikeSshTarget(cli.rootDir);
699
- // Spread the existing config first so keys we don't manage here (and
700
- // existing.ssh when this run isn't an SSH target) survive instead of being
701
- // clobbered back to defaults.
702
- const cfg = {
703
- ...existing,
704
- rootDir: cli.rootDir != null
705
- ? (cliIsSsh ? cli.rootDir : path.resolve(cli.rootDir))
706
- : existing.rootDir ?? null,
707
- // Only let the CLI override the persisted host/port when the flag was
708
- // explicitly passed. cli.host/cli.port are seeded from HOSTNAME/PORT env, so
709
- // gating on `??` would let ambient env silently clobber the saved config on
710
- // every reinstall.
711
- host: cli.userSpecifiedHost ? cli.host : existing.host ?? "localhost",
712
- port: cli.userSpecifiedPort ? cli.port : existing.port ?? "3000",
713
- https: cli.useHttps ?? existing.https ?? false,
714
- };
715
- // Persist ssh options so `service run` can remount on boot. Password auth is
716
- // intentionally NOT persisted — services run non-interactively, so use
717
- // ssh-agent or --ssh-key for a reboot-persistent SSH workspace. Only overwrite
718
- // ssh when this run explicitly targets one; otherwise existing.ssh is kept.
719
- if (cliIsSsh) {
720
- cfg.ssh = { ...(cli.sshKey ? { key: cli.sshKey } : {}), ...(cli.sshPort ? { port: cli.sshPort } : {}), ...(cli.sshReadOnly ? { readOnly: true } : {}) };
721
- }
722
- if (Object.keys(mergedEnv).length) cfg.env = mergedEnv;
723
- else delete cfg.env;
724
- if (cliIsSsh && cli.sshPassword) {
725
- console.log("⚠️ --ssh-password is ignored for services (non-interactive). Use ssh-agent or --ssh-key.\n");
726
- }
727
- saveConfig(cfg);
728
- console.log(`Saved config to ${configPath}`);
729
- console.log(` dir: ${cfg.rootDir ?? "(choose in browser)"}`);
730
- console.log(` host: ${cfg.host}`);
731
- console.log(` port: ${cfg.port}`);
732
- console.log(` https: ${cfg.https}`);
733
- if (cfg.env) {
734
- console.log(` env: ${Object.keys(cfg.env).join(", ")}`);
735
- }
736
- console.log("");
737
-
738
- // Surface the same secure-context guidance the server would, at install time.
739
- const { warnings } = computeServerEnv({
740
- host: cfg.host, port: String(cfg.port), useHttps: Boolean(cfg.https), configEnv: cfg.env ?? {},
741
- });
742
- for (const w of warnings) console.log(`⚠️ ${w}\n`);
743
-
744
- const nodeWarning = versionManagedNodeWarning(resolveServiceNode());
745
- if (nodeWarning) console.log(`⚠️ ${nodeWarning}\n`);
746
-
747
- if (p === "linux") installSystemd();
748
- else installLaunchd();
749
- }
750
-
751
- // `wiki-viewer config` — show or edit the saved config (incl. env).
752
- function configCommand(args) {
753
- const cfg = loadConfig();
754
-
755
- if (args.length === 0 || args[0] === "show" || args[0] === "list") {
756
- if (!existsSync(configPath)) {
757
- console.log(`No config yet at ${configPath}`);
758
- console.log(`Create one with: wiki-viewer service install [dir] [options] [--env KEY=VALUE]`);
759
- return;
760
- }
761
- console.log(configPath);
762
- console.log(JSON.stringify(cfg, null, 2));
763
- return;
764
- }
765
-
766
- if (args[0] === "set") {
767
- const pairs = args.slice(1);
768
- if (pairs.length === 0) {
769
- console.error("Usage: wiki-viewer config set KEY=VALUE [KEY=VALUE ...]");
770
- process.exit(1);
771
- }
772
- cfg.env = cfg.env ?? {};
773
- for (const pair of pairs) {
774
- const idx = pair.indexOf("=");
775
- if (idx < 0) { console.error(`Error: expected KEY=VALUE (got: ${pair})`); process.exit(1); }
776
- cfg.env[pair.slice(0, idx)] = pair.slice(idx + 1);
777
- }
778
- saveConfig(cfg);
779
- console.log(`Updated env in ${configPath}: ${pairs.map((p) => p.split("=")[0]).join(", ")}`);
780
- if (serviceIsInstalled()) console.log("Run `wiki-viewer service restart` to apply.");
781
- return;
782
- }
783
-
784
- if (args[0] === "unset") {
785
- const keys = args.slice(1);
786
- if (keys.length === 0) { console.error("Usage: wiki-viewer config unset KEY [KEY ...]"); process.exit(1); }
787
- cfg.env = cfg.env ?? {};
788
- for (const k of keys) delete cfg.env[k];
789
- saveConfig(cfg);
790
- console.log(`Removed from env: ${keys.join(", ")}`);
791
- if (serviceIsInstalled()) console.log("Run `wiki-viewer service restart` to apply.");
792
- return;
793
- }
794
-
795
- if (args[0] === "path") { console.log(configPath); return; }
796
-
797
- console.error(`Unknown config command: ${args[0]}`);
798
- console.error("Try: show | set KEY=VALUE | unset KEY | path");
799
- process.exit(1);
800
- }
801
-
802
- function installSystemd() {
803
- const unitDir = path.join(os.homedir(), ".config", "systemd", "user");
804
- mkdirSync(unitDir, { recursive: true });
805
- const unitPath = path.join(unitDir, `${SERVICE_NAME}.service`);
806
-
807
- const nodeBin = resolveServiceNode();
808
- const scriptPath = resolveServiceScript();
809
- const nodeDir = path.dirname(nodeBin);
810
- const sysPath = `${nodeDir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`;
811
- const rgEnv = process.env.WIKI_VIEWER_RG
812
- ? `\nEnvironment=WIKI_VIEWER_RG=${process.env.WIKI_VIEWER_RG}`
813
- : "";
814
- const unit = `[Unit]
815
- Description=wiki-viewer local file viewer
816
- After=network.target
817
-
818
- [Service]
819
- Type=simple
820
- ExecStart=${nodeBin} ${scriptPath} service run
821
- Restart=on-failure
822
- RestartSec=3
823
- Environment=NODE_ENV=production
824
- Environment=PATH=${sysPath}${rgEnv}
825
-
826
- [Install]
827
- WantedBy=default.target
828
- `;
829
- writeFileSync(unitPath, unit);
830
- console.log(`Wrote unit ${unitPath}`);
831
-
832
- // Enable lingering so the service survives logout and starts at boot.
833
- const user = os.userInfo().username;
834
- try {
835
- execFileSync("loginctl", ["enable-linger", user], { stdio: "ignore" });
836
- console.log(`Enabled linger for ${user} (starts at boot)`);
837
- } catch {
838
- console.log(`Note: could not enable linger automatically. For boot persistence run:`);
839
- console.log(` sudo loginctl enable-linger ${user}`);
840
- }
841
-
842
- run("systemctl", ["--user", "daemon-reload"]);
843
- run("systemctl", ["--user", "enable", "--now", `${SERVICE_NAME}.service`]);
844
- console.log("\nService installed and started.");
845
- console.log(" Status: wiki-viewer service status");
846
- console.log(" Logs: wiki-viewer service logs");
847
- }
848
-
849
- function installLaunchd() {
850
- const agentsDir = path.join(os.homedir(), "Library", "LaunchAgents");
851
- mkdirSync(agentsDir, { recursive: true });
852
- mkdirSync(logDir, { recursive: true });
853
- const plistPath = path.join(agentsDir, `${LAUNCHD_LABEL}.plist`);
854
- const outLog = path.join(logDir, "out.log");
855
- const errLog = path.join(logDir, "err.log");
856
-
857
- const nodeBin = resolveServiceNode();
858
- const scriptPath = resolveServiceScript();
859
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
860
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
861
- <plist version="1.0">
862
- <dict>
863
- <key>Label</key>
864
- <string>${LAUNCHD_LABEL}</string>
865
- <key>ProgramArguments</key>
866
- <array>
867
- <string>${nodeBin}</string>
868
- <string>${scriptPath}</string>
869
- <string>service</string>
870
- <string>run</string>
871
- </array>
872
- <key>RunAtLoad</key>
873
- <true/>
874
- <key>KeepAlive</key>
875
- <true/>
876
- <key>StandardOutPath</key>
877
- <string>${outLog}</string>
878
- <key>StandardErrorPath</key>
879
- <string>${errLog}</string>
880
- </dict>
881
- </plist>
882
- `;
883
- writeFileSync(plistPath, plist);
884
- console.log(`Wrote plist ${plistPath}`);
885
-
886
- // Reload if already loaded, then load with -w to persist across reboot.
887
- runQuiet("launchctl", ["unload", plistPath]);
888
- run("launchctl", ["load", "-w", plistPath]);
889
- console.log("\nService installed and started.");
890
- console.log(" Status: wiki-viewer service status");
891
- console.log(" Logs: wiki-viewer service logs");
892
- }
893
-
894
- // ── service: uninstall ──────────────────────────────────────────────────────
895
-
896
- function serviceUninstall() {
897
- const p = requireSupportedPlatform();
898
- if (p === "linux") {
899
- runQuiet("systemctl", ["--user", "disable", "--now", `${SERVICE_NAME}.service`]);
900
- const unitPath = path.join(os.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
901
- if (existsSync(unitPath)) { rmSync(unitPath); console.log(`Removed ${unitPath}`); }
902
- runQuiet("systemctl", ["--user", "daemon-reload"]);
903
- } else {
904
- const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
905
- runQuiet("launchctl", ["unload", "-w", plistPath]);
906
- if (existsSync(plistPath)) { rmSync(plistPath); console.log(`Removed ${plistPath}`); }
907
- }
908
- console.log("Service uninstalled.");
909
- }
910
-
911
- // ── service: status / logs ──────────────────────────────────────────────────
912
-
913
- function serviceStatus() {
914
- const p = requireSupportedPlatform();
915
- if (p === "linux") {
916
- try { run("systemctl", ["--user", "status", `${SERVICE_NAME}.service`, "--no-pager"]); }
917
- catch { /* systemctl exits non-zero when inactive; output already shown */ }
918
- } else {
919
- const out = runQuiet("launchctl", ["list"]);
920
- if (out) {
921
- const line = out.split("\n").find((l) => l.includes(LAUNCHD_LABEL));
922
- console.log(line ? line.trim() : `${LAUNCHD_LABEL}: not loaded`);
923
- }
924
- }
925
- }
926
-
927
- function serviceLogs() {
928
- const p = requireSupportedPlatform();
929
- if (p === "linux") {
930
- run("journalctl", ["--user", "-u", `${SERVICE_NAME}.service`, "-n", "100", "-f"]);
931
- } else {
932
- const outLog = path.join(logDir, "out.log");
933
- const errLog = path.join(logDir, "err.log");
934
- run("tail", ["-n", "100", "-f", outLog, errLog]);
935
- }
936
- }
937
-
938
- function serviceIsInstalled() {
939
- const p = platform();
940
- if (p === "linux") {
941
- return existsSync(path.join(os.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`));
942
- }
943
- if (p === "macos") {
944
- return existsSync(path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`));
945
- }
946
- return false;
947
- }
948
-
949
- function serviceRestart() {
950
- const p = platform();
951
- if (p === "linux") {
952
- run("systemctl", ["--user", "restart", `${SERVICE_NAME}.service`]);
953
- } else if (p === "macos") {
954
- const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
955
- runQuiet("launchctl", ["unload", plistPath]);
956
- run("launchctl", ["load", "-w", plistPath]);
957
- }
958
- }
959
-
960
- // ── update ────────────────────────────────────────────────────────────────
961
-
962
- function detectPackageManager() {
963
- // Prefer the manager whose global root contains this install.
964
- const ua = process.env.npm_config_user_agent ?? "";
965
- if (ua.startsWith("pnpm")) return "pnpm";
966
- if (ua.startsWith("yarn")) return "yarn";
967
- if (selfScript.includes(`${path.sep}pnpm${path.sep}`)) return "pnpm";
968
- return "npm";
969
- }
970
-
971
- function update() {
972
- const pkg = JSON.parse(readFileSync(path.join(appRoot, "package.json"), "utf8"));
973
- const name = pkg.name;
974
- console.log(`Current ${name}: v${pkg.version}`);
975
-
976
- // Cheap registry metadata check first. The package tarball is ~20 MB, so a
977
- // blind reinstall on every `update` re-downloads + re-extracts 2000+ files
978
- // even when nothing changed (the common case). `npm view` fetches only JSON.
979
- const latest = (runQuiet("npm", ["view", `${name}@latest`, "version"]) || "").trim();
980
- if (latest && latest === pkg.version) {
981
- console.log(`Already on the latest version (v${latest}). Nothing to do.`);
982
- return;
983
- }
984
- if (latest) console.log(`Latest: v${latest}`);
985
-
986
- const pm = detectPackageManager();
987
- // --prefer-offline: when invoked via `npx`, the latest tarball is often
988
- // already in the local cache from the npx fetch, so the global install can
989
- // skip a second network download of the ~20 MB package.
990
- const cmd = pm === "pnpm" ? ["pnpm", ["add", "-g", `${name}@latest`, "--prefer-offline"]]
991
- : pm === "yarn" ? ["yarn", ["global", "add", `${name}@latest`]]
992
- : ["npm", ["install", "-g", `${name}@latest`, "--prefer-offline"]];
993
-
994
- console.log(`Updating via ${pm}…`);
995
- try {
996
- run(cmd[0], cmd[1]);
997
- } catch {
998
- console.error(`Error: update failed. Try manually: ${cmd[0]} ${cmd[1].join(" ")}`);
999
- process.exit(1);
1000
- }
1001
-
1002
- if (serviceIsInstalled()) {
1003
- console.log("Restarting service…");
1004
- try { serviceRestart(); console.log("Service restarted."); }
1005
- catch { console.log("Note: could not restart service automatically. Run: wiki-viewer service restart"); }
1006
- }
1007
- console.log("Update complete.");
1008
- }
1009
-
1010
- // ── interactive setup wizard ──────────────────────────────────────────────
1011
-
1012
- // Common app env vars worth offering during setup. Anything not listed can
1013
- // still be added later with `wiki-viewer config set KEY=VALUE`.
1014
- const WIZARD_ENV_VARS = [
1015
- { key: "AUTH_ALLOWED_DOMAIN", hint: "Restrict signup to an email domain, e.g. example.com" },
1016
- { key: "AUTH_ALLOWED_EMAILS", hint: "Restrict signup to specific emails (comma-separated)" },
1017
- { key: "GOOGLE_CLIENT_ID", hint: "Google OAuth client ID (enables Google sign-in)" },
1018
- { key: "GOOGLE_CLIENT_SECRET", hint: "Google OAuth client secret" },
1019
- { key: "AUTH_DISABLE_PASSWORD", hint: "Set to 1 to allow Google sign-in only (requires Google keys)" },
1020
- { key: "AUTH_TRUSTED_PROVIDERS", hint: "Auto-link these providers to existing accounts, e.g. google" },
1021
- { key: "WIKI_OWNER_HOSTS", hint: "Extra hostnames trusted for the AI panel owner cookie" },
1022
- { key: "AGENT_RATE_LIMIT", hint: "Per-minute agent API rate limit (default 60)" },
1023
- ];
1024
-
1025
- // A prompt helper that survives piped (non-TTY) stdin. node:readline/promises
1026
- // closes the stream between awaited questions when input is piped, so we buffer
1027
- // every line via the 'line' event and hand them out one at a time instead.
1028
- function makePrompter() {
1029
- const rl = createInterface({ input: stdin, output: stdout });
1030
- const queue = [];
1031
- const waiters = [];
1032
- let closed = false;
1033
-
1034
- rl.on("line", (line) => {
1035
- if (waiters.length) waiters.shift()(line);
1036
- else queue.push(line);
1037
- });
1038
- rl.on("close", () => {
1039
- closed = true;
1040
- while (waiters.length) waiters.shift()(null);
1041
- });
1042
-
1043
- const nextLine = () =>
1044
- new Promise((resolve) => {
1045
- if (queue.length) resolve(queue.shift());
1046
- else if (closed) resolve(null);
1047
- else waiters.push(resolve);
1048
- });
1049
-
1050
- const prompt = async (text) => {
1051
- stdout.write(text);
1052
- const line = await nextLine();
1053
- return line == null ? "" : line;
1054
- };
1055
-
1056
- return { prompt, close: () => rl.close() };
1057
- }
1058
-
1059
- async function runWizard() {
1060
- const io = makePrompter();
1061
- const existing = loadConfig();
1062
-
1063
- const ask = async (label, def) => {
1064
- const suffix = def ? ` [${def}]` : "";
1065
- const answer = (await io.prompt(`${label}${suffix}: `)).trim();
1066
- return answer || def || "";
1067
- };
1068
- const askYesNo = async (label, defYes) => {
1069
- const def = defYes ? "Y/n" : "y/N";
1070
- const answer = (await io.prompt(`${label} [${def}]: `)).trim().toLowerCase();
1071
- if (!answer) return defYes;
1072
- return answer === "y" || answer === "yes";
1073
- };
1074
-
1075
- try {
1076
- console.log("\nwiki-viewer setup\n");
1077
- console.log("Answer a few questions. Press Enter to accept the [default].\n");
1078
-
1079
- // 1. directory
1080
- const dirInput = await ask(
1081
- "Directory to serve (blank = choose later in the browser)",
1082
- existing.rootDir ?? process.cwd(),
1083
- );
1084
- const rootDir = dirInput ? path.resolve(dirInput) : null;
1085
- if (rootDir && !existsSync(rootDir)) {
1086
- console.log(` Note: ${rootDir} does not exist yet; it will be served once created.`);
1087
- }
1088
-
1089
- // 2. host
1090
- const host = await ask(
1091
- "Host to bind (localhost for this machine only, 0.0.0.0 for the network)",
1092
- existing.host ?? "localhost",
1093
- );
1094
-
1095
- // 3. port
1096
- const port = await ask("Port", String(existing.port ?? "3000"));
1097
-
1098
- // 4. https
1099
- const httpsDefault = existing.https ?? (!isLocalHost(host) && host !== "0.0.0.0");
1100
- const useHttps = await askYesNo(
1101
- "Enable HTTPS? (recommended for any non-localhost access)",
1102
- Boolean(httpsDefault),
1103
- );
1104
-
1105
- // 5. app env vars
1106
- const env = { ...(existing.env ?? {}) };
1107
- const wantEnv = await askYesNo(
1108
- "\nConfigure app settings now? (OAuth, signup allowlist, rate limit)",
1109
- false,
1110
- );
1111
- if (wantEnv) {
1112
- console.log("Leave blank to skip a setting.\n");
1113
- for (const { key, hint } of WIZARD_ENV_VARS) {
1114
- const cur = env[key];
1115
- const val = await ask(` ${key} — ${hint}`, cur);
1116
- if (val) env[key] = val;
1117
- else delete env[key];
1118
- }
1119
- }
1120
-
1121
- // Build and preview the config. Spread the existing config first so keys the
1122
- // wizard doesn't touch (e.g. ssh) survive; then explicitly set/clear env.
1123
- const cfg = { ...existing, rootDir, host, port, https: useHttps };
1124
- if (Object.keys(env).length) cfg.env = env;
1125
- else delete cfg.env;
1126
-
1127
- console.log("\nConfiguration:");
1128
- console.log(` directory : ${rootDir ?? "(choose in browser)"}`);
1129
- console.log(` host : ${host}`);
1130
- console.log(` port : ${port}`);
1131
- console.log(` https : ${useHttps}`);
1132
- if (cfg.env) console.log(` app env : ${Object.keys(cfg.env).join(", ")}`);
1133
-
1134
- // Secure-context guidance before committing.
1135
- const { warnings } = computeServerEnv({ host, port: String(port), useHttps, configEnv: cfg.env ?? {} });
1136
- for (const w of warnings) console.log(`\n⚠️ ${w}`);
1137
-
1138
- // 6. what to do now
1139
- console.log("");
1140
- console.log("What next?");
1141
- console.log(" 1) Install as a service (starts now and on every reboot)");
1142
- console.log(" 2) Run once now (foreground)");
1143
- console.log(" 3) Save config only");
1144
- const choice = (await io.prompt("Choose [1/2/3]: ")).trim() || "1";
1145
-
1146
- saveConfig(cfg);
1147
- console.log(`\nSaved ${configPath}`);
1148
-
1149
- io.close();
1150
-
1151
- if (choice === "1") {
1152
- const p = platform();
1153
- if (!p) {
1154
- console.log("Service install is only supported on Linux and macOS.");
1155
- console.log("Run it yourself with: wiki-viewer service run");
1156
- return;
1157
- }
1158
- requirePersistentInstall();
1159
- const nodeWarning = versionManagedNodeWarning(resolveServiceNode());
1160
- if (nodeWarning) console.log(`\n⚠️ ${nodeWarning}\n`);
1161
- if (p === "linux") installSystemd();
1162
- else installLaunchd();
1163
- } else if (choice === "2") {
1164
- start(resolveRunOptions([]));
1165
- } else {
1166
- console.log("\nStart it any time with:");
1167
- console.log(" wiki-viewer service install # persistent service");
1168
- console.log(" wiki-viewer service run # run from this config");
1169
- }
1170
- } finally {
1171
- // io may already be closed; closing twice is a no-op.
1172
- io.close();
1173
- }
1174
- }
1175
-
1176
- // ── dispatch ──────────────────────────────────────────────────────────────
1177
-
1178
- const argv = process.argv.slice(2);
1179
-
1180
- if (argv.includes("--help") || argv.includes("-h")) {
1181
- printUsage();
1182
- process.exit(0);
1183
- }
1184
-
1185
- if (argv.includes("--version") || argv.includes("-v")) {
1186
- const pkg = JSON.parse(readFileSync(path.join(appRoot, "package.json"), "utf8"));
1187
- console.log(pkg.version);
1188
- process.exit(0);
1189
- }
1190
-
1191
- const [cmd, ...rest] = argv;
1192
-
1193
- switch (cmd) {
1194
- case "service": {
1195
- const [sub, ...subArgs] = rest;
1196
- switch (sub) {
1197
- case "install": serviceInstall(subArgs); break;
1198
- case "uninstall": serviceUninstall(); break;
1199
- case "status": serviceStatus(); break;
1200
- case "logs": serviceLogs(); break;
1201
- case "restart": serviceRestart(); break;
1202
- case "run": start(resolveRunOptions(subArgs)); break;
1203
- default:
1204
- console.error(`Unknown service command: ${sub ?? "(none)"}`);
1205
- console.error("Try: install | uninstall | status | logs | restart | run");
1206
- process.exit(1);
1207
- }
1208
- break;
1209
- }
1210
- case "config":
1211
- configCommand(rest);
1212
- break;
1213
- case "init":
1214
- case "setup":
1215
- runWizard();
1216
- break;
1217
- case "update":
1218
- update();
1219
- break;
1220
- default:
1221
- if (argv.includes("--setup") || argv.includes("--init")) {
1222
- runWizard();
1223
- break;
1224
- }
1225
- // No recognized command → ad-hoc serve (directory + flags only).
1226
- // On a bare interactive run, point users at the guided setup first.
1227
- if (argv.length === 0 && stdin.isTTY) {
1228
- console.log("Tip: run `wiki-viewer init` for guided setup (directory, host/port, HTTPS,");
1229
- console.log(" app settings, and optional install as a reboot-persistent service).\n");
1230
- }
1231
- start(resolveServeOptions(argv));
1232
- }
21
+ console.error("Usage: wiki-viewer [directory] [options]");
22
+ console.error(" wiki-viewer <command> [args]");
23
+ console.error("");
24
+ console.error(" directory Directory to serve (optional — pick in browser if omitted).");
25
+ console.error(" May also be an SSH target (user@host:/path) — mounted via");
26
+ console.error(" sshfs and served live, no local clone.");
27
+ console.error("");
28
+ console.error("Options:");
29
+ console.error(" -p, --port <port> Port to listen on (default: 3000)");
30
+ console.error(" -H, --host <host> Host to bind to (default: localhost)");
31
+ console.error(" --https Enable HTTPS (self-signed cert, enables service workers)");
32
+ console.error(" --no-auth Run without authentication — no sign-in, no session check");
33
+ console.error(" --ssh-key <path> Private key for the SSH target (default: ssh-agent / host keys)");
34
+ console.error(" --ssh-port <port> SSH port for the target (default: 22)");
35
+ console.error(" --ssh-password Prompt for an SSH password (or set WIKI_SSH_PASSWORD)");
36
+ console.error(" --ssh-readonly Mount the SSH target read-only");
37
+ console.error(" -v, --version Print version");
38
+ console.error("");
39
+ console.error(" -e, --env <KEY=VALUE> Set an app env var (repeatable; persisted with service install)");
40
+ console.error("");
41
+ console.error("Commands:");
42
+ console.error(" init Interactive setup wizard (dir, host/port, https, env, service)");
43
+ console.error(" service install [dir] [options] Install as a user service (persists across reboot)");
44
+ console.error(" service uninstall Remove the user service");
45
+ console.error(" service status Show service status");
46
+ console.error(" service logs Tail service logs");
47
+ console.error(" service restart Restart the service");
48
+ console.error(" service run Run from saved config (used internally by the service)");
49
+ console.error(" config show Print the saved config");
50
+ console.error(" config set KEY=VALUE Set an app env var in the config");
51
+ console.error(" config unset KEY Remove an app env var from the config");
52
+ console.error(" update Update wiki-viewer to the latest version and restart");
53
+ console.error("");
54
+ console.error("Examples:");
55
+ console.error(" wiki-viewer init");
56
+ console.error(" wiki-viewer ~/notes");
57
+ console.error(" wiki-viewer ~/notes --https");
58
+ console.error(" wiki-viewer ~/notes -p 8080 -H 0.0.0.0");
59
+ console.error(" wiki-viewer me@server:/srv/docs");
60
+ console.error(" wiki-viewer me@server:/srv/docs --ssh-key ~/.ssh/id_ed25519 --ssh-readonly");
61
+ console.error(" wiki-viewer service install ~/notes -H 0.0.0.0 -p 3003 --https");
62
+ console.error(" wiki-viewer service install ~/notes --env GOOGLE_CLIENT_ID=... --env GOOGLE_CLIENT_SECRET=...");
63
+ console.error(" wiki-viewer config set AUTH_ALLOWED_DOMAIN=example.com");
64
+ console.error(" wiki-viewer update");
65
+ }
66
+
67
+ async function main() {
68
+ const argv = process.argv.slice(2);
69
+
70
+ if (argv.includes("--help") || argv.includes("-h")) {
71
+ printUsage();
72
+ process.exit(0);
73
+ }
74
+
75
+ if (argv.includes("--version") || argv.includes("-v")) {
76
+ const pkg = JSON.parse(readFileSync(path.join(appRoot, "package.json"), "utf8"));
77
+ console.log(pkg.version);
78
+ process.exit(0);
79
+ }
80
+
81
+ const [cmd, ...rest] = argv;
82
+
83
+ switch (cmd) {
84
+ case "service": {
85
+ const [sub, ...subArgs] = rest;
86
+ switch (sub) {
87
+ case "install": serviceInstall(subArgs); break;
88
+ case "uninstall": serviceUninstall(); break;
89
+ case "status": serviceStatus(); break;
90
+ case "logs": serviceLogs(); break;
91
+ case "restart": serviceRestart(); break;
92
+ case "run": await start(resolveRunOptions(subArgs)); break;
93
+ default:
94
+ console.error(`Unknown service command: ${sub ?? "(none)"}`);
95
+ console.error("Try: install | uninstall | status | logs | restart | run");
96
+ process.exit(1);
97
+ }
98
+ break;
99
+ }
100
+ case "config":
101
+ configCommand(rest);
102
+ break;
103
+ case "init":
104
+ case "setup":
105
+ await runWizard();
106
+ break;
107
+ case "update":
108
+ update();
109
+ break;
110
+ default:
111
+ if (argv.includes("--setup") || argv.includes("--init")) {
112
+ await runWizard();
113
+ break;
114
+ }
115
+ if (argv.length === 0 && process.stdin.isTTY) {
116
+ console.log("Tip: run `wiki-viewer init` for guided setup (directory, host/port, HTTPS,");
117
+ console.log(" app settings, and optional install as a reboot-persistent service).\n");
118
+ }
119
+ await start(resolveServeOptions(argv));
120
+ }
121
+ }
122
+
123
+ main().catch((err) => {
124
+ console.error(err);
125
+ process.exit(1);
126
+ });