wiki-viewer 1.1.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  12. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/api/wiki/upload/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/index.html +1 -1
  21. package/.next/standalone/.next/server/app/index.rsc +1 -1
  22. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  24. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  27. package/.next/standalone/.next/server/app/signin.html +1 -1
  28. package/.next/standalone/.next/server/app/signin.rsc +1 -1
  29. package/.next/standalone/.next/server/app/signin.segments/_full.segment.rsc +1 -1
  30. package/.next/standalone/.next/server/app/signin.segments/_head.segment.rsc +1 -1
  31. package/.next/standalone/.next/server/app/signin.segments/_index.segment.rsc +1 -1
  32. package/.next/standalone/.next/server/app/signin.segments/_tree.segment.rsc +1 -1
  33. package/.next/standalone/.next/server/app/signin.segments/signin/__PAGE__.segment.rsc +1 -1
  34. package/.next/standalone/.next/server/app/signin.segments/signin.segment.rsc +1 -1
  35. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  36. package/.next/standalone/.next/server/middleware-manifest.json +5 -5
  37. package/.next/standalone/.next/server/pages/404.html +1 -1
  38. package/.next/standalone/.next/server/pages/500.html +1 -1
  39. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  41. package/.next/standalone/README.md +43 -2
  42. package/.next/standalone/bin/wiki-viewer.js +358 -4
  43. package/.next/standalone/package.json +1 -1
  44. package/README.md +43 -2
  45. package/bin/wiki-viewer.js +358 -4
  46. package/package.json +1 -1
  47. /package/.next/standalone/.next/static/{8im3AoVCFwRIj5snIjj9L → R5pAJFCIncBd90BC7hUpr}/_buildManifest.js +0 -0
  48. /package/.next/standalone/.next/static/{8im3AoVCFwRIj5snIjj9L → R5pAJFCIncBd90BC7hUpr}/_clientMiddlewareManifest.js +0 -0
  49. /package/.next/standalone/.next/static/{8im3AoVCFwRIj5snIjj9L → R5pAJFCIncBd90BC7hUpr}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/0ocp.6r7w_saw.js"/><script src="/_next/static/chunks/0quh-cfcrp2ib.js" async=""></script><script src="/_next/static/chunks/0vp-ahhagxptc.js" async=""></script><script src="/_next/static/chunks/turbopack-12~z-mr7b9b50.js" async=""></script><script src="/_next/static/chunks/057umwogp53v_.js" async=""></script><meta name="next-size-adjust" content=""/><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/03~yq9q893hmn.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/0ocp.6r7w_saw.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[902063,[\"/_next/static/chunks/057umwogp53v_.js\"],\"default\"]\n3:I[259666,[\"/_next/static/chunks/057umwogp53v_.js\"],\"default\"]\n4:I[62711,[\"/_next/static/chunks/057umwogp53v_.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[62711,[\"/_next/static/chunks/057umwogp53v_.js\"],\"ViewportBoundary\"]\na:I[62711,[\"/_next/static/chunks/057umwogp53v_.js\"],\"MetadataBoundary\"]\nc:I[514743,[\"/_next/static/chunks/057umwogp53v_.js\"],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"8im3AoVCFwRIj5snIjj9L\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
1
+ <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/0ocp.6r7w_saw.js"/><script src="/_next/static/chunks/0quh-cfcrp2ib.js" async=""></script><script src="/_next/static/chunks/0vp-ahhagxptc.js" async=""></script><script src="/_next/static/chunks/turbopack-12~z-mr7b9b50.js" async=""></script><script src="/_next/static/chunks/057umwogp53v_.js" async=""></script><meta name="next-size-adjust" content=""/><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/03~yq9q893hmn.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/0ocp.6r7w_saw.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[902063,[\"/_next/static/chunks/057umwogp53v_.js\"],\"default\"]\n3:I[259666,[\"/_next/static/chunks/057umwogp53v_.js\"],\"default\"]\n4:I[62711,[\"/_next/static/chunks/057umwogp53v_.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[62711,[\"/_next/static/chunks/057umwogp53v_.js\"],\"ViewportBoundary\"]\na:I[62711,[\"/_next/static/chunks/057umwogp53v_.js\"],\"MetadataBoundary\"]\nc:I[514743,[\"/_next/static/chunks/057umwogp53v_.js\"],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"R5pAJFCIncBd90BC7hUpr\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
@@ -1 +1 @@
1
- self.__RSC_SERVER_MANIFEST="{\n \"node\": {},\n \"edge\": {},\n \"encryptionKey\": \"uDRPTKb35w410ko797iDHKj0G5zOWVt/Kpuub/Q7vYk=\"\n}"
1
+ self.__RSC_SERVER_MANIFEST="{\n \"node\": {},\n \"edge\": {},\n \"encryptionKey\": \"o6PnydZ24z6qATK/oQuAoqO6Ur7OwwgwnEjRf9k2IFw=\"\n}"
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "node": {},
3
3
  "edge": {},
4
- "encryptionKey": "uDRPTKb35w410ko797iDHKj0G5zOWVt/Kpuub/Q7vYk="
4
+ "encryptionKey": "o6PnydZ24z6qATK/oQuAoqO6Ur7OwwgwnEjRf9k2IFw="
5
5
  }
@@ -47,7 +47,11 @@ Single-user, no-auth mode still works. Auth turns on automatically once anyone s
47
47
  ## Quick start
48
48
 
49
49
  ```bash
50
- # Point it at a directory
50
+ # Guided setup: directory, host/port, HTTPS, app settings, and optional
51
+ # install as a reboot-persistent service. Recommended for first run.
52
+ npx wiki-viewer init
53
+
54
+ # Or run directly. Point it at a directory
51
55
  npx wiki-viewer ~/notes
52
56
 
53
57
  # No directory? Pick one in the browser
@@ -57,6 +61,8 @@ npx wiki-viewer
57
61
  npx wiki-viewer ~/notes --https
58
62
  ```
59
63
 
64
+ The wizard walks you through every option and writes `~/.wiki-viewer/config.json`, so you do not have to remember flags. You can re-run it any time, or edit the config with `wiki-viewer config set`.
65
+
60
66
  Open **http://localhost:3000** (or **https://localhost:3000** with `--https`).
61
67
 
62
68
  To switch the served directory later, click **Change** at the bottom of the sidebar and pick a new folder. No restart needed.
@@ -119,7 +125,42 @@ The run config is saved to `~/.wiki-viewer/config.json`. Edit that file and run
119
125
 
120
126
  On Linux, install enables lingering (`loginctl enable-linger`) so the service runs without an active login session and survives reboot. If that step needs privileges, the installer prints the command to run manually.
121
127
 
122
- > Ad-hoc runs like `wiki-viewer ~/docs` ignore the saved config. Only the service (and `wiki-viewer service run`) reads `config.json`.
128
+ > Ad-hoc runs like `wiki-viewer ~/docs` ignore the saved bind (dir/host/port), but still read app env from `config.json`. Only the service (and `wiki-viewer service run`) reads the full config.
129
+
130
+ ### App configuration (env)
131
+
132
+ App settings (OAuth keys, allowlists, rate limits) are env vars. You can keep them in the config file instead of exporting them, and the service will load them on every start.
133
+
134
+ ```bash
135
+ # Set at install time
136
+ wiki-viewer service install ~/notes --env GOOGLE_CLIENT_ID=... --env GOOGLE_CLIENT_SECRET=...
137
+
138
+ # Or manage them later
139
+ wiki-viewer config set AUTH_ALLOWED_DOMAIN=example.com
140
+ wiki-viewer config set AGENT_RATE_LIMIT=120
141
+ wiki-viewer config unset AGENT_RATE_LIMIT
142
+ wiki-viewer config show
143
+ wiki-viewer service restart # apply changes
144
+ ```
145
+
146
+ These land in the `env` block of `config.json`:
147
+
148
+ ```json
149
+ {
150
+ "rootDir": "/home/you/notes",
151
+ "host": "0.0.0.0",
152
+ "port": "3003",
153
+ "https": true,
154
+ "env": {
155
+ "GOOGLE_CLIENT_ID": "...",
156
+ "AUTH_ALLOWED_DOMAIN": "example.com"
157
+ }
158
+ }
159
+ ```
160
+
161
+ **Precedence:** a variable exported in your shell always wins, then the `env` block in `config.json`, then values the CLI derives for you. So you can still override anything per run with `KEY=VALUE wiki-viewer ...` or `--env KEY=VALUE`.
162
+
163
+ **`BETTER_AUTH_URL` is derived automatically** from the host, port, and scheme you run with, so the common case needs no config. On `localhost` over HTTP the CLI also sets `WIKI_ALLOW_INSECURE=1` for you (browsers treat localhost as a secure context). If you serve plain HTTP on a non-local host the CLI prints a warning, because login cookies and service workers will not work there. Use `--https`, or terminate TLS in a proxy and set `BETTER_AUTH_URL` to its public `https://` URL via `config set` or your shell.
123
164
 
124
165
  ### Update
125
166
 
@@ -7,6 +7,8 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from "node
7
7
  import path from "node:path";
8
8
  import os from "node:os";
9
9
  import { fileURLToPath } from "node:url";
10
+ import { createInterface } from "node:readline";
11
+ import { stdin, stdout } from "node:process";
10
12
 
11
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
14
  const appRoot = path.resolve(__dirname, "..");
@@ -31,19 +33,29 @@ function printUsage() {
31
33
  console.error(" -H, --host <host> Host to bind to (default: localhost)");
32
34
  console.error(" --https Enable HTTPS (self-signed cert, enables service workers)");
33
35
  console.error("");
36
+ console.error(" -e, --env <KEY=VALUE> Set an app env var (repeatable; persisted with service install)");
37
+ console.error("");
34
38
  console.error("Commands:");
39
+ console.error(" init Interactive setup wizard (dir, host/port, https, env, service)");
35
40
  console.error(" service install [dir] [options] Install as a user service (persists across reboot)");
36
41
  console.error(" service uninstall Remove the user service");
37
42
  console.error(" service status Show service status");
38
43
  console.error(" service logs Tail service logs");
44
+ console.error(" service restart Restart the service");
39
45
  console.error(" service run Run from saved config (used internally by the service)");
46
+ console.error(" config show Print the saved config");
47
+ console.error(" config set KEY=VALUE Set an app env var in the config");
48
+ console.error(" config unset KEY Remove an app env var from the config");
40
49
  console.error(" update Update wiki-viewer to the latest version and restart");
41
50
  console.error("");
42
51
  console.error("Examples:");
52
+ console.error(" wiki-viewer init");
43
53
  console.error(" wiki-viewer ~/notes");
44
54
  console.error(" wiki-viewer ~/notes --https");
45
55
  console.error(" wiki-viewer ~/notes -p 8080 -H 0.0.0.0");
46
56
  console.error(" wiki-viewer service install ~/notes -H 0.0.0.0 -p 3003 --https");
57
+ console.error(" wiki-viewer service install ~/notes --env GOOGLE_CLIENT_ID=... --env GOOGLE_CLIENT_SECRET=...");
58
+ console.error(" wiki-viewer config set AUTH_ALLOWED_DOMAIN=example.com");
47
59
  console.error(" wiki-viewer update");
48
60
  }
49
61
 
@@ -60,6 +72,7 @@ function parseServeArgs(args) {
60
72
  const a = args[i];
61
73
  if (a === "-p" || a === "--port") { port = args[++i] ?? port; userSpecifiedPort = true; }
62
74
  else if (a === "-H" || a === "--host") host = args[++i] ?? host;
75
+ else if (a === "-e" || a === "--env") { i++; } // consumed by parseEnvFlags
63
76
  else if (a === "--https") useHttps = true;
64
77
  else if (!a.startsWith("-") && rootDir === undefined) rootDir = a;
65
78
  }
@@ -84,16 +97,21 @@ function saveConfig(cfg) {
84
97
  writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n");
85
98
  }
86
99
 
87
- // Ad-hoc serve: pure CLI flags + built-in defaults. Does NOT read the config
88
- // file, so an installed service never silently alters a one-off invocation.
100
+ // Ad-hoc serve: CLI flags + built-in defaults. The run *shape* (dir/host/port)
101
+ // does NOT read the config file, so an installed service never silently alters
102
+ // a one-off invocation. App-level env (config.env) is still read, because those
103
+ // are settings about the app itself (allowlists, OAuth) rather than the bind.
89
104
  function resolveServeOptions(args) {
90
105
  const cli = parseServeArgs(args);
106
+ const cfg = loadConfig();
91
107
  return {
92
108
  rootDir: cli.rootDir ? path.resolve(cli.rootDir) : null,
93
109
  port: String(cli.port ?? "3000"),
94
110
  host: cli.host ?? "localhost",
95
111
  useHttps: Boolean(cli.useHttps),
96
112
  userSpecifiedPort: cli.userSpecifiedPort,
113
+ // App env: config.env as base, ad-hoc --env flags override per run.
114
+ configEnv: { ...(cfg.env ?? {}), ...parseEnvFlags(args) },
97
115
  };
98
116
  }
99
117
 
@@ -117,9 +135,77 @@ function resolveRunOptions(args) {
117
135
  host,
118
136
  useHttps: Boolean(useHttps),
119
137
  userSpecifiedPort,
138
+ configEnv: { ...(cfg.env ?? {}), ...parseEnvFlags(args) },
120
139
  };
121
140
  }
122
141
 
142
+ // ── environment resolution ───────────────────────────────────────────────────
143
+
144
+ // Env vars the app reads that the bin can derive or manage on the user's behalf.
145
+ // Everything else in config.env is passed through verbatim.
146
+ const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1", ""]);
147
+
148
+ function isLocalHost(h) {
149
+ return LOCAL_HOSTS.has(h);
150
+ }
151
+
152
+ // Build the env for the spawned server.
153
+ // Precedence (highest first):
154
+ // 1. the shell environment the user launched us with (explicit override)
155
+ // 2. config.env from ~/.wiki-viewer/config.json
156
+ // 3. values derived by the bin from the run options (e.g. BETTER_AUTH_URL)
157
+ //
158
+ // Returns { env, warnings } where warnings are human-readable strings to print
159
+ // before the server starts.
160
+ function computeServerEnv({ host, port, useHttps, configEnv }) {
161
+ const warnings = [];
162
+ const derived = {};
163
+
164
+ const scheme = useHttps ? "https" : "http";
165
+ const urlHost = isLocalHost(host) || host === "0.0.0.0" ? "localhost" : host;
166
+ const isSecureContext = useHttps || isLocalHost(host);
167
+
168
+ // BETTER_AUTH_URL: the app requires this in production so cookies and OAuth
169
+ // callbacks resolve. Derive it from the bind so the common case needs no
170
+ // config at all.
171
+ derived.BETTER_AUTH_URL = `${scheme}://${urlHost}:${port}`;
172
+
173
+ // The app refuses to boot in production over an insecure context unless
174
+ // WIKI_ALLOW_INSECURE=1. localhost http is fine (browsers treat it as secure).
175
+ // Remote http is not: auth cookies and service workers will silently break.
176
+ if (!isSecureContext) {
177
+ derived.WIKI_ALLOW_INSECURE = "1";
178
+ warnings.push(
179
+ `Serving plain HTTP on a non-local host (${host}). Browsers treat this as\n` +
180
+ ` an insecure context: login cookies, service workers and PDF.js will not\n` +
181
+ ` work reliably, and OAuth callbacks will fail.\n` +
182
+ ` Fix: re-run with --https, or put a TLS-terminating proxy in front and set\n` +
183
+ ` BETTER_AUTH_URL to its public https:// URL (see "env" in ${configPath}).`,
184
+ );
185
+ } else if (isLocalHost(host) && !useHttps) {
186
+ // localhost http is a secure context for the browser, but the app's prod
187
+ // guard still wants the bypass flag set explicitly. Do it for the user.
188
+ derived.WIKI_ALLOW_INSECURE = "1";
189
+ }
190
+
191
+ // Layer: derived < config.env < shell env.
192
+ const env = { ...derived, ...configEnv };
193
+
194
+ // Note when the shell or config overrode a derived value so the user isn't
195
+ // surprised that --host/--port didn't change the auth URL.
196
+ if (configEnv.BETTER_AUTH_URL && configEnv.BETTER_AUTH_URL !== derived.BETTER_AUTH_URL) {
197
+ // config wins over derived; that's intentional, no warning needed.
198
+ }
199
+ if (process.env.BETTER_AUTH_URL && process.env.BETTER_AUTH_URL !== env.BETTER_AUTH_URL) {
200
+ warnings.push(
201
+ `BETTER_AUTH_URL is set in your shell (${process.env.BETTER_AUTH_URL}) and\n` +
202
+ ` overrides the derived/config value.`,
203
+ );
204
+ }
205
+
206
+ return { env, warnings };
207
+ }
208
+
123
209
  // ── HTTPS cert generation ──────────────────────────────────────────────────
124
210
 
125
211
  function ensureCerts(host) {
@@ -189,7 +275,7 @@ function getNetworkAddress() {
189
275
  // ── start ──────────────────────────────────────────────────────────────────
190
276
 
191
277
  async function start(opts) {
192
- const { rootDir: resolvedRoot, useHttps } = opts;
278
+ const { rootDir: resolvedRoot, useHttps, configEnv = {} } = opts;
193
279
  let { port, host, userSpecifiedPort } = opts;
194
280
 
195
281
  if (!existsSync(serverJs)) {
@@ -221,11 +307,18 @@ async function start(opts) {
221
307
  // Otherwise it must bind to the user-requested host directly.
222
308
  const internalHost = useHttps ? "127.0.0.1" : host;
223
309
 
310
+ // Resolve the app environment (config.env + shell overrides + derived
311
+ // BETTER_AUTH_URL etc.) against the user-facing host/port, then print any
312
+ // guidance before the server boots.
313
+ const { env: appEnv, warnings } = computeServerEnv({ host, port, useHttps, configEnv });
314
+ for (const w of warnings) console.log(`\n⚠️ ${w}`);
315
+
224
316
  const child = spawn(process.execPath, [serverJs], {
225
317
  cwd: path.join(appRoot, ".next", "standalone"),
226
318
  stdio: "inherit",
227
319
  env: {
228
- ...process.env,
320
+ ...appEnv, // derived defaults + config.env
321
+ ...process.env, // shell env always wins over derived/config
229
322
  ...(resolvedRoot ? { ROOT_DIR: resolvedRoot } : {}),
230
323
  PORT: internalPort,
231
324
  HOSTNAME: internalHost,
@@ -310,17 +403,38 @@ function runQuiet(cmd, args) {
310
403
 
311
404
  // ── service: install ──────────────────────────────────────────────────────────
312
405
 
406
+ // Pull repeatable `--env KEY=VALUE` (or `-e KEY=VALUE`) pairs out of an argv
407
+ // list. Returns the collected map; the caller has already parsed the rest.
408
+ function parseEnvFlags(args) {
409
+ const env = {};
410
+ for (let i = 0; i < args.length; i++) {
411
+ if (args[i] === "--env" || args[i] === "-e") {
412
+ const pair = args[++i];
413
+ if (!pair || !pair.includes("=")) {
414
+ console.error(`Error: --env expects KEY=VALUE (got: ${pair ?? "(nothing)"})`);
415
+ process.exit(1);
416
+ }
417
+ const idx = pair.indexOf("=");
418
+ env[pair.slice(0, idx)] = pair.slice(idx + 1);
419
+ }
420
+ }
421
+ return env;
422
+ }
423
+
313
424
  function serviceInstall(args) {
314
425
  const p = requireSupportedPlatform();
315
426
 
316
427
  // Capture the run config from flags (falling back to existing config), persist it.
317
428
  const cli = parseServeArgs(args);
429
+ const envFlags = parseEnvFlags(args);
318
430
  const existing = loadConfig();
431
+ const mergedEnv = { ...(existing.env ?? {}), ...envFlags };
319
432
  const cfg = {
320
433
  rootDir: cli.rootDir != null ? path.resolve(cli.rootDir) : existing.rootDir ?? null,
321
434
  host: cli.host ?? existing.host ?? "localhost",
322
435
  port: cli.port ?? existing.port ?? "3000",
323
436
  https: cli.useHttps ?? existing.https ?? false,
437
+ ...(Object.keys(mergedEnv).length ? { env: mergedEnv } : {}),
324
438
  };
325
439
  saveConfig(cfg);
326
440
  console.log(`Saved config to ${configPath}`);
@@ -328,12 +442,72 @@ function serviceInstall(args) {
328
442
  console.log(` host: ${cfg.host}`);
329
443
  console.log(` port: ${cfg.port}`);
330
444
  console.log(` https: ${cfg.https}`);
445
+ if (cfg.env) {
446
+ console.log(` env: ${Object.keys(cfg.env).join(", ")}`);
447
+ }
331
448
  console.log("");
332
449
 
450
+ // Surface the same secure-context guidance the server would, at install time.
451
+ const { warnings } = computeServerEnv({
452
+ host: cfg.host, port: String(cfg.port), useHttps: Boolean(cfg.https), configEnv: cfg.env ?? {},
453
+ });
454
+ for (const w of warnings) console.log(`⚠️ ${w}\n`);
455
+
333
456
  if (p === "linux") installSystemd();
334
457
  else installLaunchd();
335
458
  }
336
459
 
460
+ // `wiki-viewer config` — show or edit the saved config (incl. env).
461
+ function configCommand(args) {
462
+ const cfg = loadConfig();
463
+
464
+ if (args.length === 0 || args[0] === "show" || args[0] === "list") {
465
+ if (!existsSync(configPath)) {
466
+ console.log(`No config yet at ${configPath}`);
467
+ console.log(`Create one with: wiki-viewer service install [dir] [options] [--env KEY=VALUE]`);
468
+ return;
469
+ }
470
+ console.log(configPath);
471
+ console.log(JSON.stringify(cfg, null, 2));
472
+ return;
473
+ }
474
+
475
+ if (args[0] === "set") {
476
+ const pairs = args.slice(1);
477
+ if (pairs.length === 0) {
478
+ console.error("Usage: wiki-viewer config set KEY=VALUE [KEY=VALUE ...]");
479
+ process.exit(1);
480
+ }
481
+ cfg.env = cfg.env ?? {};
482
+ for (const pair of pairs) {
483
+ const idx = pair.indexOf("=");
484
+ if (idx < 0) { console.error(`Error: expected KEY=VALUE (got: ${pair})`); process.exit(1); }
485
+ cfg.env[pair.slice(0, idx)] = pair.slice(idx + 1);
486
+ }
487
+ saveConfig(cfg);
488
+ console.log(`Updated env in ${configPath}: ${pairs.map((p) => p.split("=")[0]).join(", ")}`);
489
+ if (serviceIsInstalled()) console.log("Run `wiki-viewer service restart` to apply.");
490
+ return;
491
+ }
492
+
493
+ if (args[0] === "unset") {
494
+ const keys = args.slice(1);
495
+ if (keys.length === 0) { console.error("Usage: wiki-viewer config unset KEY [KEY ...]"); process.exit(1); }
496
+ cfg.env = cfg.env ?? {};
497
+ for (const k of keys) delete cfg.env[k];
498
+ saveConfig(cfg);
499
+ console.log(`Removed from env: ${keys.join(", ")}`);
500
+ if (serviceIsInstalled()) console.log("Run `wiki-viewer service restart` to apply.");
501
+ return;
502
+ }
503
+
504
+ if (args[0] === "path") { console.log(configPath); return; }
505
+
506
+ console.error(`Unknown config command: ${args[0]}`);
507
+ console.error("Try: show | set KEY=VALUE | unset KEY | path");
508
+ process.exit(1);
509
+ }
510
+
337
511
  function installSystemd() {
338
512
  const unitDir = path.join(os.homedir(), ".config", "systemd", "user");
339
513
  mkdirSync(unitDir, { recursive: true });
@@ -519,6 +693,170 @@ function update() {
519
693
  console.log("Update complete.");
520
694
  }
521
695
 
696
+ // ── interactive setup wizard ──────────────────────────────────────────────
697
+
698
+ // Common app env vars worth offering during setup. Anything not listed can
699
+ // still be added later with `wiki-viewer config set KEY=VALUE`.
700
+ const WIZARD_ENV_VARS = [
701
+ { key: "AUTH_ALLOWED_DOMAIN", hint: "Restrict signup to an email domain, e.g. example.com" },
702
+ { key: "AUTH_ALLOWED_EMAILS", hint: "Restrict signup to specific emails (comma-separated)" },
703
+ { key: "GOOGLE_CLIENT_ID", hint: "Google OAuth client ID (enables Google sign-in)" },
704
+ { key: "GOOGLE_CLIENT_SECRET", hint: "Google OAuth client secret" },
705
+ { key: "WIKI_OWNER_HOSTS", hint: "Extra hostnames trusted for the AI panel owner cookie" },
706
+ { key: "AGENT_RATE_LIMIT", hint: "Per-minute agent API rate limit (default 60)" },
707
+ ];
708
+
709
+ // A prompt helper that survives piped (non-TTY) stdin. node:readline/promises
710
+ // closes the stream between awaited questions when input is piped, so we buffer
711
+ // every line via the 'line' event and hand them out one at a time instead.
712
+ function makePrompter() {
713
+ const rl = createInterface({ input: stdin, output: stdout });
714
+ const queue = [];
715
+ const waiters = [];
716
+ let closed = false;
717
+
718
+ rl.on("line", (line) => {
719
+ if (waiters.length) waiters.shift()(line);
720
+ else queue.push(line);
721
+ });
722
+ rl.on("close", () => {
723
+ closed = true;
724
+ while (waiters.length) waiters.shift()(null);
725
+ });
726
+
727
+ const nextLine = () =>
728
+ new Promise((resolve) => {
729
+ if (queue.length) resolve(queue.shift());
730
+ else if (closed) resolve(null);
731
+ else waiters.push(resolve);
732
+ });
733
+
734
+ const prompt = async (text) => {
735
+ stdout.write(text);
736
+ const line = await nextLine();
737
+ return line == null ? "" : line;
738
+ };
739
+
740
+ return { prompt, close: () => rl.close() };
741
+ }
742
+
743
+ async function runWizard() {
744
+ const io = makePrompter();
745
+ const existing = loadConfig();
746
+
747
+ const ask = async (label, def) => {
748
+ const suffix = def ? ` [${def}]` : "";
749
+ const answer = (await io.prompt(`${label}${suffix}: `)).trim();
750
+ return answer || def || "";
751
+ };
752
+ const askYesNo = async (label, defYes) => {
753
+ const def = defYes ? "Y/n" : "y/N";
754
+ const answer = (await io.prompt(`${label} [${def}]: `)).trim().toLowerCase();
755
+ if (!answer) return defYes;
756
+ return answer === "y" || answer === "yes";
757
+ };
758
+
759
+ try {
760
+ console.log("\nwiki-viewer setup\n");
761
+ console.log("Answer a few questions. Press Enter to accept the [default].\n");
762
+
763
+ // 1. directory
764
+ const dirInput = await ask(
765
+ "Directory to serve (blank = choose later in the browser)",
766
+ existing.rootDir ?? process.cwd(),
767
+ );
768
+ const rootDir = dirInput ? path.resolve(dirInput) : null;
769
+ if (rootDir && !existsSync(rootDir)) {
770
+ console.log(` Note: ${rootDir} does not exist yet; it will be served once created.`);
771
+ }
772
+
773
+ // 2. host
774
+ const host = await ask(
775
+ "Host to bind (localhost for this machine only, 0.0.0.0 for the network)",
776
+ existing.host ?? "localhost",
777
+ );
778
+
779
+ // 3. port
780
+ const port = await ask("Port", String(existing.port ?? "3000"));
781
+
782
+ // 4. https
783
+ const httpsDefault = existing.https ?? (!isLocalHost(host) && host !== "0.0.0.0");
784
+ const useHttps = await askYesNo(
785
+ "Enable HTTPS? (recommended for any non-localhost access)",
786
+ Boolean(httpsDefault),
787
+ );
788
+
789
+ // 5. app env vars
790
+ const env = { ...(existing.env ?? {}) };
791
+ const wantEnv = await askYesNo(
792
+ "\nConfigure app settings now? (OAuth, signup allowlist, rate limit)",
793
+ false,
794
+ );
795
+ if (wantEnv) {
796
+ console.log("Leave blank to skip a setting.\n");
797
+ for (const { key, hint } of WIZARD_ENV_VARS) {
798
+ const cur = env[key];
799
+ const val = await ask(` ${key} — ${hint}`, cur);
800
+ if (val) env[key] = val;
801
+ else delete env[key];
802
+ }
803
+ }
804
+
805
+ // Build and preview the config.
806
+ const cfg = {
807
+ rootDir,
808
+ host,
809
+ port,
810
+ https: useHttps,
811
+ ...(Object.keys(env).length ? { env } : {}),
812
+ };
813
+
814
+ console.log("\nConfiguration:");
815
+ console.log(` directory : ${rootDir ?? "(choose in browser)"}`);
816
+ console.log(` host : ${host}`);
817
+ console.log(` port : ${port}`);
818
+ console.log(` https : ${useHttps}`);
819
+ if (cfg.env) console.log(` app env : ${Object.keys(cfg.env).join(", ")}`);
820
+
821
+ // Secure-context guidance before committing.
822
+ const { warnings } = computeServerEnv({ host, port: String(port), useHttps, configEnv: cfg.env ?? {} });
823
+ for (const w of warnings) console.log(`\n⚠️ ${w}`);
824
+
825
+ // 6. what to do now
826
+ console.log("");
827
+ console.log("What next?");
828
+ console.log(" 1) Install as a service (starts now and on every reboot)");
829
+ console.log(" 2) Run once now (foreground)");
830
+ console.log(" 3) Save config only");
831
+ const choice = (await io.prompt("Choose [1/2/3]: ")).trim() || "1";
832
+
833
+ saveConfig(cfg);
834
+ console.log(`\nSaved ${configPath}`);
835
+
836
+ io.close();
837
+
838
+ if (choice === "1") {
839
+ const p = platform();
840
+ if (!p) {
841
+ console.log("Service install is only supported on Linux and macOS.");
842
+ console.log("Run it yourself with: wiki-viewer service run");
843
+ return;
844
+ }
845
+ if (p === "linux") installSystemd();
846
+ else installLaunchd();
847
+ } else if (choice === "2") {
848
+ start(resolveRunOptions([]));
849
+ } else {
850
+ console.log("\nStart it any time with:");
851
+ console.log(" wiki-viewer service install # persistent service");
852
+ console.log(" wiki-viewer service run # run from this config");
853
+ }
854
+ } finally {
855
+ // io may already be closed; closing twice is a no-op.
856
+ io.close();
857
+ }
858
+ }
859
+
522
860
  // ── dispatch ──────────────────────────────────────────────────────────────
523
861
 
524
862
  const argv = process.argv.slice(2);
@@ -547,10 +885,26 @@ switch (cmd) {
547
885
  }
548
886
  break;
549
887
  }
888
+ case "config":
889
+ configCommand(rest);
890
+ break;
891
+ case "init":
892
+ case "setup":
893
+ runWizard();
894
+ break;
550
895
  case "update":
551
896
  update();
552
897
  break;
553
898
  default:
899
+ if (argv.includes("--setup") || argv.includes("--init")) {
900
+ runWizard();
901
+ break;
902
+ }
554
903
  // No recognized command → ad-hoc serve (directory + flags only).
904
+ // On a bare interactive run, point users at the guided setup first.
905
+ if (argv.length === 0 && stdin.isTTY) {
906
+ console.log("Tip: run `wiki-viewer init` for guided setup (directory, host/port, HTTPS,");
907
+ console.log(" app settings, and optional install as a reboot-persistent service).\n");
908
+ }
555
909
  start(resolveServeOptions(argv));
556
910
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-viewer",
3
- "version": "1.1.2",
3
+ "version": "1.3.0",
4
4
  "description": "Local file viewer and wiki — view markdown, HTML, PDFs, notebooks, office docs, and more from any directory",
5
5
  "license": "MIT",
6
6
  "type": "module",