@knotpad/app 0.1.5 → 0.1.7

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 (337) hide show
  1. package/app/(app)/calendar/page.tsx +57 -0
  2. package/app/(app)/error.tsx +35 -0
  3. package/app/(app)/graph/page.tsx +32 -0
  4. package/app/(app)/guide/page.tsx +21 -0
  5. package/app/(app)/kanban/loading.tsx +24 -0
  6. package/app/(app)/kanban/page.tsx +59 -0
  7. package/app/(app)/layout.tsx +122 -0
  8. package/app/(app)/list/loading.tsx +21 -0
  9. package/app/(app)/list/page.tsx +137 -0
  10. package/app/(app)/loading.tsx +18 -0
  11. package/app/(app)/notes/[noteId]/page.tsx +84 -0
  12. package/app/(app)/notes/layout.tsx +30 -0
  13. package/app/(app)/notes/page.tsx +39 -0
  14. package/app/(app)/page.tsx +5 -0
  15. package/app/(app)/settings/agent-token/page.tsx +59 -0
  16. package/app/(app)/settings/backup/page.tsx +49 -0
  17. package/app/(app)/settings/billing/page.tsx +53 -0
  18. package/app/(app)/settings/calendar/page.tsx +41 -0
  19. package/app/(app)/settings/layout.test.tsx +39 -0
  20. package/app/(app)/settings/layout.tsx +71 -0
  21. package/app/(app)/settings/page.tsx +4 -0
  22. package/app/(app)/settings/security/page.tsx +43 -0
  23. package/app/(app)/settings/team/page.tsx +74 -0
  24. package/app/(app)/settings/workspace/page.tsx +27 -0
  25. package/app/(app)/tasks/[taskId]/page.tsx +79 -0
  26. package/app/(auth)/forgot-password/page.tsx +106 -0
  27. package/app/(auth)/guest/page.tsx +56 -0
  28. package/app/(auth)/layout.tsx +13 -0
  29. package/app/(auth)/login/page.tsx +14 -0
  30. package/app/(auth)/register/page.tsx +193 -0
  31. package/app/(auth)/reset-password/page.tsx +138 -0
  32. package/app/api/account/claim/route.tsx +135 -0
  33. package/app/api/admin/backfill-encryption/route.tsx +43 -0
  34. package/app/api/admin/license/route.tsx +42 -0
  35. package/app/api/auth/2fa/route.tsx +148 -0
  36. package/app/api/auth/[...nextauth]/route.tsx +3 -0
  37. package/app/api/auth/change-password/route.tsx +61 -0
  38. package/app/api/auth/check-2fa/route.tsx +19 -0
  39. package/app/api/auth/forgot-password/route.tsx +65 -0
  40. package/app/api/auth/reset-password/route.tsx +52 -0
  41. package/app/api/auth/verify-2fa/route.tsx +88 -0
  42. package/app/api/backup/download/db/route.ts +29 -0
  43. package/app/api/backup/download/notes/route.ts +25 -0
  44. package/app/api/backup/settings/route.ts +92 -0
  45. package/app/api/billing/checkout/route.tsx +81 -0
  46. package/app/api/billing/migrate/route.tsx +163 -0
  47. package/app/api/billing/portal/route.tsx +24 -0
  48. package/app/api/billing/setup-intent/route.tsx +55 -0
  49. package/app/api/billing/status/route.tsx +36 -0
  50. package/app/api/billing/subscribe/route.tsx +85 -0
  51. package/app/api/billing/webhook/route.tsx +199 -0
  52. package/app/api/calendar-feeds/[feedId]/route.tsx +67 -0
  53. package/app/api/calendar-feeds/[feedId]/sync/route.tsx +37 -0
  54. package/app/api/calendar-feeds/events/route.tsx +82 -0
  55. package/app/api/calendar-feeds/route.tsx +52 -0
  56. package/app/api/calendar-feeds/sync-all/route.tsx +34 -0
  57. package/app/api/cron/calendar-feeds/route.tsx +31 -0
  58. package/app/api/cron/stale-tasks/route.tsx +51 -0
  59. package/app/api/cron/sync/route.tsx +34 -0
  60. package/app/api/devices/[deviceId]/route.tsx +25 -0
  61. package/app/api/devices/route.tsx +41 -0
  62. package/app/api/export/route.tsx +40 -0
  63. package/app/api/feedback/route.tsx +54 -0
  64. package/app/api/folders/[folderId]/route.tsx +51 -0
  65. package/app/api/folders/route.tsx +37 -0
  66. package/app/api/graph/route.tsx +242 -0
  67. package/app/api/guest/route.tsx +58 -0
  68. package/app/api/health/route.tsx +10 -0
  69. package/app/api/holidays/countries/route.tsx +14 -0
  70. package/app/api/holidays/route.tsx +49 -0
  71. package/app/api/holidays/states/route.tsx +21 -0
  72. package/app/api/invites/[token]/route.tsx +131 -0
  73. package/app/api/invites/route.tsx +74 -0
  74. package/app/api/mcp/generate-token/route.tsx +55 -0
  75. package/app/api/mcp/revoke-token/[tokenId]/route.tsx +30 -0
  76. package/app/api/mcp/update-alias/[tokenId]/route.tsx +22 -0
  77. package/app/api/notes/[noteId]/export/route.tsx +45 -0
  78. package/app/api/notes/[noteId]/route.tsx +360 -0
  79. package/app/api/notes/route.tsx +112 -0
  80. package/app/api/notifications/route.tsx +44 -0
  81. package/app/api/register/route.tsx +67 -0
  82. package/app/api/restore/route.tsx +148 -0
  83. package/app/api/sync/conflicts/[conflictId]/route.tsx +134 -0
  84. package/app/api/sync/conflicts/route.tsx +48 -0
  85. package/app/api/sync/status/route.tsx +49 -0
  86. package/app/api/sync/trigger/route.tsx +15 -0
  87. package/app/api/tasks/[taskId]/detail/route.tsx +68 -0
  88. package/app/api/tasks/[taskId]/route.tsx +259 -0
  89. package/app/api/tasks/bulk/route.tsx +133 -0
  90. package/app/api/tasks/route.tsx +36 -0
  91. package/app/api/workspace/active/route.tsx +39 -0
  92. package/app/api/workspace/create-team/route.tsx +42 -0
  93. package/app/api/workspace/kanban-statuses/route.tsx +71 -0
  94. package/app/api/workspace/members/[memberId]/route.tsx +69 -0
  95. package/app/api/workspace/route.tsx +24 -0
  96. package/app/download/page.tsx +170 -0
  97. package/app/favicon.ico +0 -0
  98. package/app/generated/prisma/client.d.ts +1 -0
  99. package/app/generated/prisma/client.js +5 -0
  100. package/app/generated/prisma/default.d.ts +1 -0
  101. package/app/generated/prisma/default.js +5 -0
  102. package/app/generated/prisma/edge.d.ts +1 -0
  103. package/app/generated/prisma/edge.js +497 -0
  104. package/app/generated/prisma/index-browser.js +523 -0
  105. package/app/generated/prisma/index.d.ts +46376 -0
  106. package/app/generated/prisma/index.js +497 -0
  107. package/app/generated/prisma/package.json +144 -0
  108. package/app/generated/prisma/query_compiler_fast_bg.js +2 -0
  109. package/app/generated/prisma/query_compiler_fast_bg.wasm +0 -0
  110. package/app/generated/prisma/query_compiler_fast_bg.wasm-base64.js +2 -0
  111. package/app/generated/prisma/runtime/client.d.ts +3386 -0
  112. package/app/generated/prisma/runtime/client.js +86 -0
  113. package/app/generated/prisma/runtime/index-browser.d.ts +90 -0
  114. package/app/generated/prisma/runtime/index-browser.js +6 -0
  115. package/app/generated/prisma/runtime/wasm-compiler-edge.js +76 -0
  116. package/app/generated/prisma/schema.prisma +456 -0
  117. package/app/generated/prisma/wasm-edge-light-loader.mjs +5 -0
  118. package/app/generated/prisma/wasm-worker-loader.mjs +5 -0
  119. package/app/globals.css +54 -0
  120. package/app/invite/[token]/page.tsx +52 -0
  121. package/app/layout.tsx +90 -0
  122. package/app/mcp/route.tsx +430 -0
  123. package/app/opengraph-image.tsx +120 -0
  124. package/app/page.tsx +398 -0
  125. package/app/privacy/page.tsx +69 -0
  126. package/app/robots.tsx +25 -0
  127. package/app/sitemap.tsx +36 -0
  128. package/app/terms/page.tsx +69 -0
  129. package/app/upgrade/page.tsx +75 -0
  130. package/auth.config.ts +33 -0
  131. package/auth.ts +79 -0
  132. package/bin/brief.js +229 -0
  133. package/components/auth/login-form.tsx +302 -0
  134. package/components/auth/password-checklist.tsx +31 -0
  135. package/components/auth/password-input.tsx +36 -0
  136. package/components/auth/switch-account-button.test.tsx +22 -0
  137. package/components/auth/switch-account-button.tsx +19 -0
  138. package/components/auth/two-factor-input.tsx +116 -0
  139. package/components/billing/billing-dashboard.tsx +265 -0
  140. package/components/billing/card-form.tsx +210 -0
  141. package/components/billing/claim-account-form.tsx +99 -0
  142. package/components/branding/app-logo.test.tsx +20 -0
  143. package/components/branding/app-logo.tsx +25 -0
  144. package/components/calendar/calendar-agenda.tsx +150 -0
  145. package/components/calendar/calendar-drag.test.tsx +177 -0
  146. package/components/calendar/calendar-grid.tsx +357 -0
  147. package/components/calendar/calendar-hooks.test.tsx +27 -0
  148. package/components/calendar/calendar-hooks.ts +351 -0
  149. package/components/calendar/calendar-toolbar.test.tsx +68 -0
  150. package/components/calendar/calendar-toolbar.tsx +291 -0
  151. package/components/calendar/calendar-types.ts +148 -0
  152. package/components/calendar/calendar-view.test.tsx +295 -0
  153. package/components/calendar/calendar-view.tsx +307 -0
  154. package/components/calendar/day-detail-popover.tsx +174 -0
  155. package/components/calendar/task-chip.tsx +86 -0
  156. package/components/command/command-palette.test.tsx +33 -0
  157. package/components/command/command-palette.tsx +310 -0
  158. package/components/download-cta.tsx +87 -0
  159. package/components/feedback/feedback-popup.tsx +207 -0
  160. package/components/graph/graph-draw.ts +337 -0
  161. package/components/graph/graph-overlays.tsx +160 -0
  162. package/components/graph/graph-page.test.tsx +131 -0
  163. package/components/graph/graph-page.tsx +263 -0
  164. package/components/graph/graph-types.ts +47 -0
  165. package/components/graph/graph-view.tsx +322 -0
  166. package/components/guide/guide-view.tsx +522 -0
  167. package/components/kanban/kanban-board.test.tsx +128 -0
  168. package/components/kanban/kanban-board.tsx +361 -0
  169. package/components/kanban/kanban-card-menu.tsx +102 -0
  170. package/components/kanban/kanban-card.tsx +227 -0
  171. package/components/kanban/kanban-column.tsx +49 -0
  172. package/components/kanban/kanban-status-context.tsx +28 -0
  173. package/components/landing/calendar-sandbox.test.tsx +15 -0
  174. package/components/landing/calendar-sandbox.tsx +107 -0
  175. package/components/landing/graph-sandbox.test.tsx +27 -0
  176. package/components/landing/graph-sandbox.tsx +80 -0
  177. package/components/landing/kanban-sandbox.test.tsx +24 -0
  178. package/components/landing/kanban-sandbox.tsx +101 -0
  179. package/components/landing/landing-showcase.test.tsx +21 -0
  180. package/components/landing/landing-showcase.tsx +54 -0
  181. package/components/landing/list-sandbox.tsx +86 -0
  182. package/components/landing/mock-workspace.ts +168 -0
  183. package/components/landing/notes-sandbox.test.tsx +14 -0
  184. package/components/landing/notes-sandbox.tsx +88 -0
  185. package/components/layout/app-shell.tsx +83 -0
  186. package/components/layout/backup-scheduler.tsx +122 -0
  187. package/components/layout/bottom-nav.tsx +43 -0
  188. package/components/layout/icon-bar.test.tsx +29 -0
  189. package/components/layout/icon-bar.tsx +118 -0
  190. package/components/layout/mobile-top-bar.tsx +68 -0
  191. package/components/layout/notes-panel-folder.tsx +127 -0
  192. package/components/layout/notes-panel-note-item.tsx +140 -0
  193. package/components/layout/notes-panel-task-tab.tsx +63 -0
  194. package/components/layout/notes-panel-types.ts +44 -0
  195. package/components/layout/notes-panel.tsx +476 -0
  196. package/components/layout/notification-bell.tsx +251 -0
  197. package/components/layout/paywall-screen.tsx +41 -0
  198. package/components/layout/pro-banner.tsx +76 -0
  199. package/components/layout/sw-register.tsx +27 -0
  200. package/components/layout/workspace-switcher.tsx +90 -0
  201. package/components/notes/mobile-bottom-sheet.tsx +99 -0
  202. package/components/notes/note-editor-context-menu.tsx +47 -0
  203. package/components/notes/note-editor-dom.ts +33 -0
  204. package/components/notes/note-editor-dropdowns.tsx +484 -0
  205. package/components/notes/note-editor-hooks.ts +692 -0
  206. package/components/notes/note-editor-keyboard.ts +305 -0
  207. package/components/notes/note-editor-overlay.tsx +90 -0
  208. package/components/notes/note-editor.test.tsx +372 -0
  209. package/components/notes/note-editor.tsx +662 -0
  210. package/components/notes/note-preview-pane.tsx +156 -0
  211. package/components/notes/note-tabs.tsx +120 -0
  212. package/components/notes/note-types.tsx +157 -0
  213. package/components/settings/accept-invite.tsx +108 -0
  214. package/components/settings/agent-token-settings.tsx +369 -0
  215. package/components/settings/backup-restore-settings.test.tsx +25 -0
  216. package/components/settings/backup-restore-settings.tsx +327 -0
  217. package/components/settings/calendar-feeds-settings.tsx +489 -0
  218. package/components/settings/calendar-general-settings.tsx +174 -0
  219. package/components/settings/confirm-danger-action.test.tsx +215 -0
  220. package/components/settings/confirm-danger-action.tsx +65 -0
  221. package/components/settings/security-settings.tsx +252 -0
  222. package/components/settings/settings-guidance.test.tsx +98 -0
  223. package/components/settings/team-settings.tsx +319 -0
  224. package/components/settings/two-factor-auth.tsx +296 -0
  225. package/components/settings/workspace-settings-client.tsx +363 -0
  226. package/components/settings/workspace-settings-form.tsx +73 -0
  227. package/components/sync/conflict-viewer.tsx +247 -0
  228. package/components/sync/sync-indicator.tsx +171 -0
  229. package/components/tasks/snippet-thread.tsx +119 -0
  230. package/components/tasks/status-dot.tsx +47 -0
  231. package/components/tasks/task-badge.tsx +43 -0
  232. package/components/tasks/task-detail.test.tsx +187 -0
  233. package/components/tasks/task-detail.tsx +458 -0
  234. package/components/tasks/task-list-filters.test.tsx +75 -0
  235. package/components/tasks/task-list-filters.tsx +163 -0
  236. package/components/tasks/task-list-types.ts +20 -0
  237. package/components/tasks/task-list.test.tsx +175 -0
  238. package/components/tasks/task-list.tsx +481 -0
  239. package/components/tasks/task-row.tsx +85 -0
  240. package/components/tasks/task-table-row.tsx +259 -0
  241. package/components/ui/skeleton.tsx +3 -0
  242. package/components/ui/toast.test.tsx +42 -0
  243. package/components/ui/toast.tsx +70 -0
  244. package/instrumentation.tsx +23 -0
  245. package/lib/api-error.ts +50 -0
  246. package/lib/backup/backup-runner.test.ts +32 -0
  247. package/lib/backup/backup-runner.ts +19 -0
  248. package/lib/backup/backup-schedule.test.ts +23 -0
  249. package/lib/backup/backup-schedule.ts +55 -0
  250. package/lib/backup/backup-settings.test.ts +30 -0
  251. package/lib/backup/backup-settings.ts +27 -0
  252. package/lib/backup/export-notes-zip.test.ts +26 -0
  253. package/lib/backup/export-notes-zip.ts +82 -0
  254. package/lib/backup/export-workspace-backup.test.ts +17 -0
  255. package/lib/backup/export-workspace-backup.ts +77 -0
  256. package/lib/backup/restore-workspace-from-export.test.ts +18 -0
  257. package/lib/backup/restore-workspace-from-export.ts +183 -0
  258. package/lib/backup/types.ts +14 -0
  259. package/lib/brand-icons.ts +1 -0
  260. package/lib/calendar-feed-crypto.ts +38 -0
  261. package/lib/calendar-feed.ts +239 -0
  262. package/lib/client/online-status.ts +47 -0
  263. package/lib/conflict-resolver.test.ts +57 -0
  264. package/lib/conflict-resolver.ts +240 -0
  265. package/lib/db-init.ts +79 -0
  266. package/lib/email.ts +159 -0
  267. package/lib/encryption.test.ts +41 -0
  268. package/lib/encryption.ts +98 -0
  269. package/lib/extract-snippet.test.ts +123 -0
  270. package/lib/extract-snippet.ts +69 -0
  271. package/lib/kanban-status.ts +55 -0
  272. package/lib/license.ts +21 -0
  273. package/lib/limits.ts +31 -0
  274. package/lib/mcp-auth.test.ts +58 -0
  275. package/lib/mcp-auth.ts +65 -0
  276. package/lib/mcp-contract.test.ts +25 -0
  277. package/lib/mcp-contract.ts +210 -0
  278. package/lib/mcp-handler.ts +31 -0
  279. package/lib/mcp-url.test.ts +12 -0
  280. package/lib/mcp-url.ts +7 -0
  281. package/lib/mentions.test.ts +45 -0
  282. package/lib/mentions.ts +73 -0
  283. package/lib/note-crypto.ts +108 -0
  284. package/lib/note-sync.ts +201 -0
  285. package/lib/note-title.ts +93 -0
  286. package/lib/prisma.ts +193 -0
  287. package/lib/pro-flush.ts +292 -0
  288. package/lib/rate-limit.ts +57 -0
  289. package/lib/stripe.ts +38 -0
  290. package/lib/sync-worker.ts +388 -0
  291. package/lib/task-parser.test.ts +91 -0
  292. package/lib/task-parser.ts +81 -0
  293. package/lib/task-utils.ts +52 -0
  294. package/lib/use-is-electron.ts +19 -0
  295. package/lib/use-is-mobile.ts +22 -0
  296. package/lib/validation/calendar-feed.ts +31 -0
  297. package/lib/validation/note.ts +27 -0
  298. package/lib/validation/task.ts +26 -0
  299. package/lib/view-preferences.test.ts +54 -0
  300. package/lib/view-preferences.ts +28 -0
  301. package/lib/workspace.ts +66 -0
  302. package/next.config.ts +21 -0
  303. package/package.json +54 -3
  304. package/postcss.config.mjs +7 -0
  305. package/prisma/migrations/20260519021916_init/migration.sql +388 -0
  306. package/prisma/migrations/20260519061113_drop_sync_password/migration.sql +8 -0
  307. package/prisma/migrations/20260520065016_add_task_start_date/migration.sql +2 -0
  308. package/prisma/migrations/20260529010600_remove_encryption_fields/migration.sql +12 -0
  309. package/prisma/migrations/20260529020000_restore_encryption_salt/migration.sql +3 -0
  310. package/prisma/migrations/20260529030000_add_folders/migration.sql +17 -0
  311. package/prisma/migrations/20260605000000_deferred_fixes/migration.sql +31 -0
  312. package/prisma/migrations/20260605020806_add_pending_sync_to_note_and_task/migration.sql +5 -0
  313. package/prisma/migrations/20260605063634_add_stripe_webhook_event_sync_lock/migration.sql +14 -0
  314. package/prisma/migrations/20260605100000_add_prod_indexes/migration.sql +26 -0
  315. package/prisma/migrations/20260608081404_add_kanban_statuses/migration.sql +23 -0
  316. package/prisma/migrations/20260611032723_add_calendar_feeds/migration.sql +43 -0
  317. package/prisma/migrations/20260611040000_add_calendar_feed_color/migration.sql +2 -0
  318. package/prisma/migrations/20260611050000_add_task_priority/migration.sql +14 -0
  319. package/prisma/migrations/20260612060000_add_critical_priority/migration.sql +2 -0
  320. package/prisma/migrations/20260613090000_add_backup_settings/migration.sql +25 -0
  321. package/prisma/migrations/20260614160000_add_feedback/migration.sql +20 -0
  322. package/prisma/migrations/20260614210000_add_2fa/migration.sql +4 -0
  323. package/prisma/migrations/migration_lock.toml +3 -0
  324. package/prisma/schema.prisma +457 -0
  325. package/public/Logo_icon.svg +1 -0
  326. package/public/file.svg +1 -0
  327. package/public/globe.svg +1 -0
  328. package/public/icon_dark.svg +1 -0
  329. package/public/knotpad_icon.svg +1 -0
  330. package/public/knotpad_logo_full.svg +1 -0
  331. package/public/manifest.json +14 -0
  332. package/public/next.svg +1 -0
  333. package/public/sw.js +137 -0
  334. package/public/vercel.svg +1 -0
  335. package/public/window.svg +1 -0
  336. package/tsconfig.json +35 -0
  337. package/brief.js +0 -311
package/public/sw.js ADDED
@@ -0,0 +1,137 @@
1
+ // Brief service worker — offline read layer for the cloud-deployed PWA.
2
+ //
3
+ // Strategy (online behavior is unchanged — network is always tried first for
4
+ // app content, so this only adds offline fallbacks):
5
+ // • Navigations (HTML documents): network-first → cache the result → on
6
+ // failure serve the exact cached page, else a cached shell route, else the
7
+ // offline fallback. Fixes dynamic routes (e.g. /notes/<id>) 404-ing offline.
8
+ // • RSC payloads (client-side navigations): network-first → cache → fallback.
9
+ // • Immutable build assets (/_next/static): cache-first (safe, content-hashed).
10
+ // • API GET reads: stale-while-revalidate so previously-loaded data shows
11
+ // offline. Non-GET (writes) are passed straight through — they will fail
12
+ // offline until the IndexedDB outbox lands (offline-writes phase).
13
+ //
14
+ // NOTE: cached pages/API responses are user-specific. This is fine for a
15
+ // single-user installed PWA; on signout the app posts {type:"brief-purge"} to
16
+ // wipe caches (handled below).
17
+
18
+ const CACHE = "brief-shell-v3";
19
+ const SHELL_URLS = ["/", "/notes", "/list", "/kanban", "/graph", "/calendar"];
20
+ const OFFLINE_FALLBACK = "/notes";
21
+
22
+ self.addEventListener("install", (e) => {
23
+ e.waitUntil(
24
+ caches.open(CACHE).then((cache) => cache.addAll(SHELL_URLS).catch(() => {}))
25
+ );
26
+ self.skipWaiting();
27
+ });
28
+
29
+ self.addEventListener("activate", (e) => {
30
+ e.waitUntil(
31
+ caches
32
+ .keys()
33
+ .then((keys) =>
34
+ Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
35
+ )
36
+ .then(() => self.clients.claim())
37
+ );
38
+ });
39
+
40
+ // Allow the page to wipe caches on signout.
41
+ self.addEventListener("message", (e) => {
42
+ if (e.data && e.data.type === "brief-purge") {
43
+ e.waitUntil(caches.keys().then((keys) => Promise.all(keys.map((k) => caches.delete(k)))));
44
+ }
45
+ });
46
+
47
+ function isRscRequest(request, url) {
48
+ return request.headers.get("RSC") === "1" || url.searchParams.has("_rsc");
49
+ }
50
+
51
+ async function networkFirst(request, { fallbackToShell } = {}) {
52
+ const cache = await caches.open(CACHE);
53
+ try {
54
+ const res = await fetch(request);
55
+ if (res && res.ok) cache.put(request, res.clone());
56
+ return res;
57
+ } catch {
58
+ const cached = await cache.match(request);
59
+ if (cached) return cached;
60
+ if (fallbackToShell) {
61
+ const shell = (await cache.match(OFFLINE_FALLBACK)) || (await cache.match("/"));
62
+ if (shell) return shell;
63
+ }
64
+ return new Response("Offline", { status: 503, statusText: "Offline" });
65
+ }
66
+ }
67
+
68
+ async function staleWhileRevalidate(request) {
69
+ const cache = await caches.open(CACHE);
70
+ const cached = await cache.match(request);
71
+ const network = fetch(request)
72
+ .then((res) => {
73
+ if (res && res.ok) cache.put(request, res.clone());
74
+ return res;
75
+ })
76
+ .catch(() => null);
77
+ if (cached) {
78
+ void network; // fire-and-forget revalidation
79
+ return cached;
80
+ }
81
+ const res = await network;
82
+ if (res) return res;
83
+ return new Response(JSON.stringify({ error: "offline" }), {
84
+ status: 503,
85
+ headers: { "Content-Type": "application/json" },
86
+ });
87
+ }
88
+
89
+ async function cacheFirst(request) {
90
+ const cache = await caches.open(CACHE);
91
+ const cached = await cache.match(request);
92
+ if (cached) return cached;
93
+ try {
94
+ const res = await fetch(request);
95
+ if (res && res.ok) cache.put(request, res.clone());
96
+ return res;
97
+ } catch {
98
+ return new Response("Offline", { status: 503 });
99
+ }
100
+ }
101
+
102
+ self.addEventListener("fetch", (e) => {
103
+ const { request } = e;
104
+ if (request.method !== "GET") return; // writes pass through (outbox handles offline later)
105
+
106
+ const url = new URL(request.url);
107
+ if (url.origin !== self.location.origin) return; // only same-origin
108
+
109
+ // Immutable, content-hashed build output → cache-first.
110
+ if (url.pathname.startsWith("/_next/static/")) {
111
+ e.respondWith(cacheFirst(request));
112
+ return;
113
+ }
114
+
115
+ // API reads → stale-while-revalidate so data is available offline.
116
+ // Write-like API routes (POST/PATCH/DELETE) already pass through above.
117
+ // Skip SWR for routes that trigger side-effects on read (e.g. sync endpoints).
118
+ if (url.pathname.startsWith("/api/") && !url.pathname.includes("/sync")) {
119
+ e.respondWith(staleWhileRevalidate(request));
120
+ return;
121
+ }
122
+
123
+ // Client-navigation RSC payloads → network-first with cache fallback.
124
+ if (isRscRequest(request, url)) {
125
+ e.respondWith(networkFirst(request));
126
+ return;
127
+ }
128
+
129
+ // Full-page navigations → network-first, fall back to cached page/shell.
130
+ if (request.mode === "navigate") {
131
+ e.respondWith(networkFirst(request, { fallbackToShell: true }));
132
+ return;
133
+ }
134
+
135
+ // Everything else same-origin → network-first with cache fallback.
136
+ e.respondWith(networkFirst(request));
137
+ });
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1155 1000"><path fill="#fff" d="m577.3 0 577.4 1000H0z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16"><path fill="#666" fill-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" clip-rule="evenodd"/></svg>
package/tsconfig.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts",
32
+ "electron/**/*"
33
+ ],
34
+ "exclude": ["node_modules"]
35
+ }
package/brief.js DELETED
@@ -1,311 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Knotpad CLI — npx @knotpad/app
4
- *
5
- * Sets up and starts a local Knotpad instance in under 60 seconds.
6
- * Requires: Node 18+ only. No external database needed.
7
- *
8
- * What it does:
9
- * 1. Checks Node version
10
- * 2. Downloads/clones the Knotpad source code if not present
11
- * 3. Finds or creates a .env file in the current directory
12
- * 4. Generates NEXTAUTH_SECRET / MCP_SECRET if not present
13
- * 5. Starts the Next.js server on port 3099
14
- * → PGlite (embedded local DB) bootstraps automatically at startup
15
- * via instrumentation.tsx → lib/prisma.ts + lib/db-init.ts
16
- * 6. Opens an app-mode window (Chrome/Edge — no address bar) or falls back to browser
17
- */
18
-
19
- "use strict";
20
-
21
- const { execSync, spawn } = require("child_process");
22
- const fs = require("fs");
23
- const path = require("path");
24
- const { randomBytes } = require("crypto");
25
- const https = require("https");
26
-
27
- // ── Helpers ────────────────────────────────────────────────────────────────────
28
-
29
- function log(msg) { process.stdout.write(`\x1b[36m[knotpad]\x1b[0m ${msg}\n`); }
30
- function ok(msg) { process.stdout.write(`\x1b[32m[knotpad]\x1b[0m ${msg}\n`); }
31
- function die(msg) { process.stderr.write(`\x1b[31m[knotpad]\x1b[0m ${msg}\n`); process.exit(1); }
32
-
33
- function genSecret(bytes = 32) {
34
- return randomBytes(bytes).toString("base64url");
35
- }
36
-
37
- function openBrowser(url) {
38
- const cmd =
39
- process.platform === "win32" ? `start "" "${url}"` :
40
- process.platform === "darwin" ? `open "${url}"` :
41
- `xdg-open "${url}"`;
42
- try { execSync(cmd, { stdio: "ignore" }); } catch {}
43
- }
44
-
45
- // ── Download source code ──────────────────────────────────────────────────────────
46
-
47
- async function downloadSource() {
48
- const INSTALL_DIR = path.join(WORK_DIR, ".brief", "app");
49
-
50
- if (fs.existsSync(INSTALL_DIR)) {
51
- log("Knotpad source already installed");
52
- return INSTALL_DIR;
53
- }
54
-
55
- log("Downloading Knotpad source (this may take a minute)…");
56
- fs.mkdirSync(path.dirname(INSTALL_DIR), { recursive: true });
57
-
58
- try {
59
- // Try git clone first (faster and more reliable)
60
- execSync(`git clone --depth 1 https://github.com/thillagen/brief.git "${INSTALL_DIR}"`, {
61
- stdio: "inherit",
62
- cwd: path.dirname(INSTALL_DIR)
63
- });
64
- ok("Source downloaded via git");
65
- return INSTALL_DIR;
66
- } catch (e) {
67
- // Fallback: download zip from GitHub
68
- log("Git not available, downloading zip…");
69
- const zipPath = path.join(path.dirname(INSTALL_DIR), "brief.zip");
70
-
71
- await new Promise((resolve, reject) => {
72
- const file = fs.createWriteStream(zipPath);
73
- https.get("https://github.com/thillagen/brief/archive/refs/heads/main.zip", (response) => {
74
- response.pipe(file);
75
- file.on("finish", () => {
76
- file.close();
77
- log("Extracting…");
78
- try {
79
- if (process.platform === "win32") {
80
- execSync(`powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${path.dirname(INSTALL_DIR)}' -Force"`, { stdio: "inherit" });
81
- } else {
82
- execSync(`unzip -q "${zipPath}" -d "${path.dirname(INSTALL_DIR)}"`, { stdio: "inherit" });
83
- }
84
- fs.unlinkSync(zipPath);
85
- // Move the extracted folder to the expected location
86
- const extractedDir = path.join(path.dirname(INSTALL_DIR), "brief-main");
87
- if (fs.existsSync(extractedDir)) {
88
- fs.renameSync(extractedDir, INSTALL_DIR);
89
- }
90
- ok("Source downloaded and extracted");
91
- resolve();
92
- } catch (err) {
93
- reject(err);
94
- }
95
- });
96
- }).on("error", (err) => {
97
- if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
98
- reject(err);
99
- });
100
- });
101
-
102
- return INSTALL_DIR;
103
- }
104
- }
105
-
106
- // ── Chrome / Edge app-mode detection ──────────────────────────────────────────
107
-
108
- /**
109
- * Find a Chrome or Edge binary on the system. Returns { bin, name } or null.
110
- */
111
- function findChromeOrEdge() {
112
- const platform = process.platform;
113
-
114
- if (platform === "win32") {
115
- const pf = process.env.ProgramFiles || "";
116
- const pfx = process.env["ProgramFiles(x86)"] || "";
117
- const local = process.env.LOCALAPPDATA || "";
118
- const candidates = [
119
- { bin: path.join(pf, "Google", "Chrome", "Application", "chrome.exe"), name: "chrome" },
120
- { bin: path.join(pfx, "Google", "Chrome", "Application", "chrome.exe"), name: "chrome" },
121
- { bin: path.join(local, "Google", "Chrome", "Application", "chrome.exe"), name: "chrome" },
122
- { bin: path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"), name: "edge" },
123
- { bin: path.join(pfx, "Microsoft", "Edge", "Application", "msedge.exe"), name: "edge" },
124
- ];
125
- for (const c of candidates) {
126
- if (c.bin && fs.existsSync(c.bin)) return c;
127
- }
128
- return null;
129
- }
130
-
131
- if (platform === "darwin") {
132
- const candidates = [
133
- { bin: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", name: "chrome" },
134
- { bin: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", name: "edge" },
135
- ];
136
- for (const c of candidates) {
137
- if (fs.existsSync(c.bin)) return c;
138
- }
139
- return null;
140
- }
141
-
142
- // Linux / other unix-like
143
- const bins = [
144
- { bin: "google-chrome", name: "chrome" },
145
- { bin: "chromium", name: "chrome" },
146
- { bin: "microsoft-edge", name: "edge" },
147
- ];
148
- for (const c of bins) {
149
- try {
150
- execSync(`which ${c.bin}`, { stdio: "ignore" });
151
- return { bin: c.bin, name: c.name };
152
- } catch {}
153
- }
154
- return null;
155
- }
156
-
157
- /**
158
- * Open the app URL in a Chrome/Edge app-mode window (no address bar, no tabs).
159
- * Falls back to the regular browser if no Chrome/Edge is found.
160
- */
161
- function openAppWindow(url) {
162
- const browser = findChromeOrEdge();
163
- if (!browser) {
164
- log("No Chrome/Edge found — falling back to default browser");
165
- return openBrowser(url);
166
- }
167
-
168
- try {
169
- const profileDir = path.join(WORK_DIR, ".brief", "chrome-profile");
170
- fs.mkdirSync(profileDir, { recursive: true });
171
-
172
- const args = [
173
- `--app=${url}`,
174
- `--user-data-dir=${profileDir}`,
175
- "--window-size=1400,900",
176
- "--no-first-run",
177
- "--no-default-browser-check",
178
- ];
179
-
180
- log(`Launching ${browser.name} app-mode window…`);
181
- const child = spawn(browser.bin, args, {
182
- detached: true,
183
- stdio: "ignore",
184
- });
185
- child.unref();
186
- } catch {
187
- log("Failed to launch app-mode window — falling back to default browser");
188
- openBrowser(url);
189
- }
190
- }
191
-
192
- // ── 1. Node version ────────────────────────────────────────────────────────────
193
-
194
- const [major] = process.versions.node.split(".").map(Number);
195
- if (major < 18) die(`Node 18+ required (you have ${process.versions.node}). Install from https://nodejs.org`);
196
-
197
- // ── 2. Download source ───────────────────────────────────────────────────────────
198
-
199
- const WORK_DIR = process.cwd();
200
- let PKG_DIR;
201
-
202
- async function main() {
203
- PKG_DIR = await downloadSource();
204
-
205
- // ── 3. Install dependencies ─────────────────────────────────────────────────────
206
-
207
- const NODE_MODULES = path.join(PKG_DIR, "node_modules");
208
- if (!fs.existsSync(NODE_MODULES)) {
209
- log("Installing dependencies (this may take a minute)…");
210
- try {
211
- execSync("npm install", { cwd: PKG_DIR, stdio: "inherit" });
212
- ok("Dependencies installed");
213
- } catch (e) {
214
- die("Failed to install dependencies. Please run manually: cd .brief/app && npm install");
215
- }
216
- }
217
-
218
- // ── 4. .env setup ──────────────────────────────────────────────────────────────
219
-
220
- const ENV_PATH = path.join(WORK_DIR, ".env");
221
-
222
- let env = {};
223
- if (fs.existsSync(ENV_PATH)) {
224
- const raw = fs.readFileSync(ENV_PATH, "utf8");
225
- for (const line of raw.split(/\r?\n/)) {
226
- const m = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line.trim());
227
- if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
228
- }
229
- log(".env found, loading existing config");
230
- } else {
231
- log("Creating .env with defaults…");
232
- }
233
-
234
- // Fill defaults — no DATABASE_URL needed; PGlite handles local storage automatically
235
- // Port 3099 avoids clashing with dev servers (3000) commonly running alongside.
236
- const defaults = {
237
- NEXTAUTH_SECRET: genSecret(),
238
- NEXTAUTH_URL: "http://localhost:3099",
239
- MCP_SECRET: genSecret(),
240
- ENCRYPTION_PEPPER: genSecret(),
241
- NEXT_PUBLIC_APP_URL: "http://localhost:3099",
242
- NEXT_PUBLIC_MCP_URL: "http://localhost:3099/mcp",
243
- IS_CLOUD: "false",
244
- CRON_SECRET: genSecret(16),
245
- };
246
-
247
- let changed = false;
248
- for (const [k, v] of Object.entries(defaults)) {
249
- if (!env[k]) { env[k] = v; changed = true; }
250
- }
251
-
252
- if (changed) {
253
- const lines = Object.entries(env).map(([k, v]) => `${k}="${v}"`).join("\n");
254
- fs.writeFileSync(ENV_PATH, lines + "\n", "utf8");
255
- ok(".env written");
256
- }
257
-
258
- // Apply env for this process
259
- for (const [k, v] of Object.entries(env)) {
260
- process.env[k] = v;
261
- }
262
-
263
- // ── 5. Build (if not already built) ───────────────────────────────────────────
264
-
265
- // BUILD_ID is only written after a successful build — a leftover .next/ from a
266
- // failed build won't have it, so we correctly re-run in that case.
267
- const BUILD_ID = path.join(PKG_DIR, ".next", "BUILD_ID");
268
- if (!fs.existsSync(BUILD_ID)) {
269
- log("Building Knotpad (first run — takes ~30 seconds)…");
270
- try {
271
- execSync("npx next build", { cwd: PKG_DIR, stdio: "inherit", env: process.env });
272
- ok("Build complete");
273
- } catch {
274
- die("Build failed. Run `npx @knotpad/app` again after fixing any errors.");
275
- }
276
- }
277
-
278
- // ── 6. Start server ────────────────────────────────────────────────────────────
279
-
280
- // Pre-create PGlite data directory so the app can open it immediately at startup
281
- fs.mkdirSync(path.join(WORK_DIR, ".brief", "db"), { recursive: true });
282
-
283
- const PORT = parseInt(process.env.PORT ?? "3099", 10);
284
- const URL = `http://localhost:${PORT}`;
285
-
286
- ok(`\nStarting Knotpad on ${URL}\n`);
287
- process.stdout.write(` ${"\x1b[2m"}Ctrl+C to stop${"\x1b[0m"}\n\n`);
288
-
289
- const NEXT_BIN = path.join(PKG_DIR, "node_modules", "next", "dist", "bin", "next");
290
- const server = spawn(process.execPath, [NEXT_BIN, "start", "--port", String(PORT)], {
291
- cwd: PKG_DIR,
292
- stdio: "inherit",
293
- env: process.env,
294
- });
295
-
296
- server.on("error", (e) => die(`Failed to start server: ${e.message}`));
297
- server.on("exit", (code) => {
298
- if (code !== 0 && code !== null) die(`Server exited with code ${code}`);
299
- });
300
-
301
- // Open in app-mode Chrome/Edge window (falls back to browser if not found)
302
- setTimeout(() => openAppWindow(URL), 1500);
303
-
304
- // Graceful exit
305
- process.on("SIGINT", () => { server.kill("SIGINT"); process.exit(0); });
306
- process.on("SIGTERM", () => { server.kill("SIGTERM"); process.exit(0); });
307
- }
308
-
309
- main().catch((err) => {
310
- die(`Error: ${err.message}`);
311
- });