@knotpad/app 0.1.0 → 0.1.2

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 (341) hide show
  1. package/bin/brief.js +165 -78
  2. package/package.json +6 -86
  3. package/app/(app)/calendar/page.tsx +0 -57
  4. package/app/(app)/error.tsx +0 -35
  5. package/app/(app)/graph/page.tsx +0 -32
  6. package/app/(app)/guide/page.tsx +0 -21
  7. package/app/(app)/kanban/loading.tsx +0 -24
  8. package/app/(app)/kanban/page.tsx +0 -59
  9. package/app/(app)/layout.tsx +0 -122
  10. package/app/(app)/list/loading.tsx +0 -21
  11. package/app/(app)/list/page.tsx +0 -137
  12. package/app/(app)/loading.tsx +0 -18
  13. package/app/(app)/notes/[noteId]/page.tsx +0 -84
  14. package/app/(app)/notes/layout.tsx +0 -30
  15. package/app/(app)/notes/page.tsx +0 -39
  16. package/app/(app)/page.tsx +0 -5
  17. package/app/(app)/settings/agent-token/page.tsx +0 -59
  18. package/app/(app)/settings/backup/page.tsx +0 -49
  19. package/app/(app)/settings/billing/page.tsx +0 -53
  20. package/app/(app)/settings/calendar/page.tsx +0 -41
  21. package/app/(app)/settings/layout.test.tsx +0 -39
  22. package/app/(app)/settings/layout.tsx +0 -71
  23. package/app/(app)/settings/page.tsx +0 -4
  24. package/app/(app)/settings/security/page.tsx +0 -43
  25. package/app/(app)/settings/team/page.tsx +0 -74
  26. package/app/(app)/settings/workspace/page.tsx +0 -27
  27. package/app/(app)/tasks/[taskId]/page.tsx +0 -79
  28. package/app/(auth)/forgot-password/page.tsx +0 -106
  29. package/app/(auth)/guest/page.tsx +0 -56
  30. package/app/(auth)/layout.tsx +0 -13
  31. package/app/(auth)/login/page.tsx +0 -14
  32. package/app/(auth)/register/page.tsx +0 -193
  33. package/app/(auth)/reset-password/page.tsx +0 -138
  34. package/app/api/account/claim/route.tsx +0 -135
  35. package/app/api/admin/backfill-encryption/route.tsx +0 -43
  36. package/app/api/admin/license/route.tsx +0 -42
  37. package/app/api/auth/2fa/route.tsx +0 -148
  38. package/app/api/auth/[...nextauth]/route.tsx +0 -3
  39. package/app/api/auth/change-password/route.tsx +0 -61
  40. package/app/api/auth/check-2fa/route.tsx +0 -19
  41. package/app/api/auth/forgot-password/route.tsx +0 -65
  42. package/app/api/auth/reset-password/route.tsx +0 -52
  43. package/app/api/auth/verify-2fa/route.tsx +0 -88
  44. package/app/api/backup/download/db/route.ts +0 -29
  45. package/app/api/backup/download/notes/route.ts +0 -25
  46. package/app/api/backup/settings/route.ts +0 -92
  47. package/app/api/billing/checkout/route.tsx +0 -81
  48. package/app/api/billing/migrate/route.tsx +0 -163
  49. package/app/api/billing/portal/route.tsx +0 -24
  50. package/app/api/billing/setup-intent/route.tsx +0 -55
  51. package/app/api/billing/status/route.tsx +0 -36
  52. package/app/api/billing/subscribe/route.tsx +0 -85
  53. package/app/api/billing/webhook/route.tsx +0 -199
  54. package/app/api/calendar-feeds/[feedId]/route.tsx +0 -67
  55. package/app/api/calendar-feeds/[feedId]/sync/route.tsx +0 -37
  56. package/app/api/calendar-feeds/events/route.tsx +0 -82
  57. package/app/api/calendar-feeds/route.tsx +0 -52
  58. package/app/api/calendar-feeds/sync-all/route.tsx +0 -34
  59. package/app/api/cron/calendar-feeds/route.tsx +0 -31
  60. package/app/api/cron/stale-tasks/route.tsx +0 -51
  61. package/app/api/cron/sync/route.tsx +0 -34
  62. package/app/api/devices/[deviceId]/route.tsx +0 -25
  63. package/app/api/devices/route.tsx +0 -41
  64. package/app/api/export/route.tsx +0 -40
  65. package/app/api/feedback/route.tsx +0 -54
  66. package/app/api/folders/[folderId]/route.tsx +0 -51
  67. package/app/api/folders/route.tsx +0 -37
  68. package/app/api/graph/route.tsx +0 -242
  69. package/app/api/guest/route.tsx +0 -58
  70. package/app/api/health/route.tsx +0 -10
  71. package/app/api/holidays/countries/route.tsx +0 -14
  72. package/app/api/holidays/route.tsx +0 -49
  73. package/app/api/holidays/states/route.tsx +0 -21
  74. package/app/api/invites/[token]/route.tsx +0 -131
  75. package/app/api/invites/route.tsx +0 -74
  76. package/app/api/mcp/generate-token/route.tsx +0 -55
  77. package/app/api/mcp/revoke-token/[tokenId]/route.tsx +0 -30
  78. package/app/api/mcp/update-alias/[tokenId]/route.tsx +0 -22
  79. package/app/api/notes/[noteId]/export/route.tsx +0 -45
  80. package/app/api/notes/[noteId]/route.tsx +0 -360
  81. package/app/api/notes/route.tsx +0 -112
  82. package/app/api/notifications/route.tsx +0 -44
  83. package/app/api/register/route.tsx +0 -67
  84. package/app/api/restore/route.tsx +0 -148
  85. package/app/api/sync/conflicts/[conflictId]/route.tsx +0 -134
  86. package/app/api/sync/conflicts/route.tsx +0 -48
  87. package/app/api/sync/status/route.tsx +0 -49
  88. package/app/api/sync/trigger/route.tsx +0 -15
  89. package/app/api/tasks/[taskId]/detail/route.tsx +0 -68
  90. package/app/api/tasks/[taskId]/route.tsx +0 -259
  91. package/app/api/tasks/bulk/route.tsx +0 -133
  92. package/app/api/tasks/route.tsx +0 -36
  93. package/app/api/workspace/active/route.tsx +0 -39
  94. package/app/api/workspace/create-team/route.tsx +0 -42
  95. package/app/api/workspace/kanban-statuses/route.tsx +0 -71
  96. package/app/api/workspace/members/[memberId]/route.tsx +0 -69
  97. package/app/api/workspace/route.tsx +0 -24
  98. package/app/download/page.tsx +0 -170
  99. package/app/favicon.ico +0 -0
  100. package/app/generated/prisma/client.d.ts +0 -1
  101. package/app/generated/prisma/client.js +0 -5
  102. package/app/generated/prisma/default.d.ts +0 -1
  103. package/app/generated/prisma/default.js +0 -5
  104. package/app/generated/prisma/edge.d.ts +0 -1
  105. package/app/generated/prisma/edge.js +0 -497
  106. package/app/generated/prisma/index-browser.js +0 -523
  107. package/app/generated/prisma/index.d.ts +0 -46376
  108. package/app/generated/prisma/index.js +0 -497
  109. package/app/generated/prisma/package.json +0 -144
  110. package/app/generated/prisma/query_compiler_fast_bg.js +0 -2
  111. package/app/generated/prisma/query_compiler_fast_bg.wasm +0 -0
  112. package/app/generated/prisma/query_compiler_fast_bg.wasm-base64.js +0 -2
  113. package/app/generated/prisma/runtime/client.d.ts +0 -3386
  114. package/app/generated/prisma/runtime/client.js +0 -86
  115. package/app/generated/prisma/runtime/index-browser.d.ts +0 -90
  116. package/app/generated/prisma/runtime/index-browser.js +0 -6
  117. package/app/generated/prisma/runtime/wasm-compiler-edge.js +0 -76
  118. package/app/generated/prisma/schema.prisma +0 -456
  119. package/app/generated/prisma/wasm-edge-light-loader.mjs +0 -5
  120. package/app/generated/prisma/wasm-worker-loader.mjs +0 -5
  121. package/app/globals.css +0 -54
  122. package/app/invite/[token]/page.tsx +0 -52
  123. package/app/layout.tsx +0 -90
  124. package/app/mcp/route.tsx +0 -430
  125. package/app/opengraph-image.tsx +0 -120
  126. package/app/page.tsx +0 -398
  127. package/app/privacy/page.tsx +0 -69
  128. package/app/robots.tsx +0 -25
  129. package/app/sitemap.tsx +0 -36
  130. package/app/terms/page.tsx +0 -69
  131. package/app/upgrade/page.tsx +0 -75
  132. package/auth.config.ts +0 -33
  133. package/auth.ts +0 -79
  134. package/components/auth/login-form.tsx +0 -302
  135. package/components/auth/password-checklist.tsx +0 -31
  136. package/components/auth/password-input.tsx +0 -36
  137. package/components/auth/switch-account-button.test.tsx +0 -22
  138. package/components/auth/switch-account-button.tsx +0 -19
  139. package/components/auth/two-factor-input.tsx +0 -116
  140. package/components/billing/billing-dashboard.tsx +0 -265
  141. package/components/billing/card-form.tsx +0 -210
  142. package/components/billing/claim-account-form.tsx +0 -99
  143. package/components/branding/app-logo.test.tsx +0 -20
  144. package/components/branding/app-logo.tsx +0 -25
  145. package/components/calendar/calendar-agenda.tsx +0 -150
  146. package/components/calendar/calendar-drag.test.tsx +0 -177
  147. package/components/calendar/calendar-grid.tsx +0 -357
  148. package/components/calendar/calendar-hooks.test.tsx +0 -27
  149. package/components/calendar/calendar-hooks.ts +0 -351
  150. package/components/calendar/calendar-toolbar.test.tsx +0 -68
  151. package/components/calendar/calendar-toolbar.tsx +0 -291
  152. package/components/calendar/calendar-types.ts +0 -148
  153. package/components/calendar/calendar-view.test.tsx +0 -295
  154. package/components/calendar/calendar-view.tsx +0 -307
  155. package/components/calendar/day-detail-popover.tsx +0 -174
  156. package/components/calendar/task-chip.tsx +0 -86
  157. package/components/command/command-palette.test.tsx +0 -33
  158. package/components/command/command-palette.tsx +0 -310
  159. package/components/download-cta.tsx +0 -87
  160. package/components/feedback/feedback-popup.tsx +0 -207
  161. package/components/graph/graph-draw.ts +0 -337
  162. package/components/graph/graph-overlays.tsx +0 -160
  163. package/components/graph/graph-page.test.tsx +0 -131
  164. package/components/graph/graph-page.tsx +0 -263
  165. package/components/graph/graph-types.ts +0 -47
  166. package/components/graph/graph-view.tsx +0 -322
  167. package/components/guide/guide-view.tsx +0 -522
  168. package/components/kanban/kanban-board.test.tsx +0 -128
  169. package/components/kanban/kanban-board.tsx +0 -361
  170. package/components/kanban/kanban-card-menu.tsx +0 -102
  171. package/components/kanban/kanban-card.tsx +0 -227
  172. package/components/kanban/kanban-column.tsx +0 -49
  173. package/components/kanban/kanban-status-context.tsx +0 -28
  174. package/components/landing/calendar-sandbox.test.tsx +0 -15
  175. package/components/landing/calendar-sandbox.tsx +0 -107
  176. package/components/landing/graph-sandbox.test.tsx +0 -27
  177. package/components/landing/graph-sandbox.tsx +0 -80
  178. package/components/landing/kanban-sandbox.test.tsx +0 -24
  179. package/components/landing/kanban-sandbox.tsx +0 -101
  180. package/components/landing/landing-showcase.test.tsx +0 -21
  181. package/components/landing/landing-showcase.tsx +0 -54
  182. package/components/landing/list-sandbox.tsx +0 -86
  183. package/components/landing/mock-workspace.ts +0 -168
  184. package/components/landing/notes-sandbox.test.tsx +0 -14
  185. package/components/landing/notes-sandbox.tsx +0 -88
  186. package/components/layout/app-shell.tsx +0 -83
  187. package/components/layout/backup-scheduler.tsx +0 -122
  188. package/components/layout/bottom-nav.tsx +0 -43
  189. package/components/layout/icon-bar.test.tsx +0 -29
  190. package/components/layout/icon-bar.tsx +0 -118
  191. package/components/layout/mobile-top-bar.tsx +0 -68
  192. package/components/layout/notes-panel-folder.tsx +0 -127
  193. package/components/layout/notes-panel-note-item.tsx +0 -140
  194. package/components/layout/notes-panel-task-tab.tsx +0 -63
  195. package/components/layout/notes-panel-types.ts +0 -44
  196. package/components/layout/notes-panel.tsx +0 -476
  197. package/components/layout/notification-bell.tsx +0 -251
  198. package/components/layout/paywall-screen.tsx +0 -41
  199. package/components/layout/pro-banner.tsx +0 -76
  200. package/components/layout/sw-register.tsx +0 -27
  201. package/components/layout/workspace-switcher.tsx +0 -90
  202. package/components/notes/mobile-bottom-sheet.tsx +0 -99
  203. package/components/notes/note-editor-context-menu.tsx +0 -47
  204. package/components/notes/note-editor-dom.ts +0 -33
  205. package/components/notes/note-editor-dropdowns.tsx +0 -484
  206. package/components/notes/note-editor-hooks.ts +0 -692
  207. package/components/notes/note-editor-keyboard.ts +0 -305
  208. package/components/notes/note-editor-overlay.tsx +0 -90
  209. package/components/notes/note-editor.test.tsx +0 -372
  210. package/components/notes/note-editor.tsx +0 -662
  211. package/components/notes/note-preview-pane.tsx +0 -156
  212. package/components/notes/note-tabs.tsx +0 -120
  213. package/components/notes/note-types.tsx +0 -157
  214. package/components/settings/accept-invite.tsx +0 -108
  215. package/components/settings/agent-token-settings.tsx +0 -369
  216. package/components/settings/backup-restore-settings.test.tsx +0 -25
  217. package/components/settings/backup-restore-settings.tsx +0 -327
  218. package/components/settings/calendar-feeds-settings.tsx +0 -489
  219. package/components/settings/calendar-general-settings.tsx +0 -174
  220. package/components/settings/confirm-danger-action.test.tsx +0 -215
  221. package/components/settings/confirm-danger-action.tsx +0 -65
  222. package/components/settings/security-settings.tsx +0 -252
  223. package/components/settings/settings-guidance.test.tsx +0 -98
  224. package/components/settings/team-settings.tsx +0 -319
  225. package/components/settings/two-factor-auth.tsx +0 -296
  226. package/components/settings/workspace-settings-client.tsx +0 -363
  227. package/components/settings/workspace-settings-form.tsx +0 -73
  228. package/components/sync/conflict-viewer.tsx +0 -247
  229. package/components/sync/sync-indicator.tsx +0 -171
  230. package/components/tasks/snippet-thread.tsx +0 -119
  231. package/components/tasks/status-dot.tsx +0 -47
  232. package/components/tasks/task-badge.tsx +0 -43
  233. package/components/tasks/task-detail.test.tsx +0 -187
  234. package/components/tasks/task-detail.tsx +0 -458
  235. package/components/tasks/task-list-filters.test.tsx +0 -75
  236. package/components/tasks/task-list-filters.tsx +0 -163
  237. package/components/tasks/task-list-types.ts +0 -20
  238. package/components/tasks/task-list.test.tsx +0 -175
  239. package/components/tasks/task-list.tsx +0 -481
  240. package/components/tasks/task-row.tsx +0 -85
  241. package/components/tasks/task-table-row.tsx +0 -259
  242. package/components/ui/skeleton.tsx +0 -3
  243. package/components/ui/toast.test.tsx +0 -42
  244. package/components/ui/toast.tsx +0 -70
  245. package/electron/main.ts +0 -251
  246. package/electron/preload.ts +0 -56
  247. package/instrumentation.tsx +0 -23
  248. package/lib/api-error.ts +0 -50
  249. package/lib/backup/backup-runner.test.ts +0 -32
  250. package/lib/backup/backup-runner.ts +0 -19
  251. package/lib/backup/backup-schedule.test.ts +0 -23
  252. package/lib/backup/backup-schedule.ts +0 -55
  253. package/lib/backup/backup-settings.test.ts +0 -30
  254. package/lib/backup/backup-settings.ts +0 -27
  255. package/lib/backup/export-notes-zip.test.ts +0 -26
  256. package/lib/backup/export-notes-zip.ts +0 -82
  257. package/lib/backup/export-workspace-backup.test.ts +0 -17
  258. package/lib/backup/export-workspace-backup.ts +0 -77
  259. package/lib/backup/restore-workspace-from-export.test.ts +0 -18
  260. package/lib/backup/restore-workspace-from-export.ts +0 -183
  261. package/lib/backup/types.ts +0 -14
  262. package/lib/brand-icons.ts +0 -1
  263. package/lib/calendar-feed-crypto.ts +0 -38
  264. package/lib/calendar-feed.ts +0 -239
  265. package/lib/client/online-status.ts +0 -47
  266. package/lib/conflict-resolver.test.ts +0 -57
  267. package/lib/conflict-resolver.ts +0 -240
  268. package/lib/db-init.ts +0 -79
  269. package/lib/email.ts +0 -159
  270. package/lib/encryption.test.ts +0 -41
  271. package/lib/encryption.ts +0 -98
  272. package/lib/extract-snippet.test.ts +0 -123
  273. package/lib/extract-snippet.ts +0 -69
  274. package/lib/kanban-status.ts +0 -55
  275. package/lib/license.ts +0 -21
  276. package/lib/limits.ts +0 -31
  277. package/lib/mcp-auth.test.ts +0 -58
  278. package/lib/mcp-auth.ts +0 -65
  279. package/lib/mcp-contract.test.ts +0 -25
  280. package/lib/mcp-contract.ts +0 -210
  281. package/lib/mcp-handler.ts +0 -31
  282. package/lib/mcp-url.test.ts +0 -12
  283. package/lib/mcp-url.ts +0 -7
  284. package/lib/mentions.test.ts +0 -45
  285. package/lib/mentions.ts +0 -73
  286. package/lib/note-crypto.ts +0 -108
  287. package/lib/note-sync.ts +0 -201
  288. package/lib/note-title.ts +0 -93
  289. package/lib/prisma.ts +0 -193
  290. package/lib/pro-flush.ts +0 -292
  291. package/lib/rate-limit.ts +0 -57
  292. package/lib/stripe.ts +0 -38
  293. package/lib/sync-worker.ts +0 -388
  294. package/lib/task-parser.test.ts +0 -91
  295. package/lib/task-parser.ts +0 -81
  296. package/lib/task-utils.ts +0 -52
  297. package/lib/use-is-electron.ts +0 -19
  298. package/lib/use-is-mobile.ts +0 -22
  299. package/lib/validation/calendar-feed.ts +0 -31
  300. package/lib/validation/note.ts +0 -27
  301. package/lib/validation/task.ts +0 -26
  302. package/lib/view-preferences.test.ts +0 -54
  303. package/lib/view-preferences.ts +0 -28
  304. package/lib/workspace.ts +0 -66
  305. package/next.config.ts +0 -21
  306. package/postcss.config.mjs +0 -7
  307. package/prisma/migrations/20260519021916_init/migration.sql +0 -388
  308. package/prisma/migrations/20260519061113_drop_sync_password/migration.sql +0 -8
  309. package/prisma/migrations/20260520065016_add_task_start_date/migration.sql +0 -2
  310. package/prisma/migrations/20260529010600_remove_encryption_fields/migration.sql +0 -12
  311. package/prisma/migrations/20260529020000_restore_encryption_salt/migration.sql +0 -3
  312. package/prisma/migrations/20260529030000_add_folders/migration.sql +0 -17
  313. package/prisma/migrations/20260605000000_deferred_fixes/migration.sql +0 -31
  314. package/prisma/migrations/20260605020806_add_pending_sync_to_note_and_task/migration.sql +0 -5
  315. package/prisma/migrations/20260605063634_add_stripe_webhook_event_sync_lock/migration.sql +0 -14
  316. package/prisma/migrations/20260605100000_add_prod_indexes/migration.sql +0 -26
  317. package/prisma/migrations/20260608081404_add_kanban_statuses/migration.sql +0 -23
  318. package/prisma/migrations/20260611032723_add_calendar_feeds/migration.sql +0 -43
  319. package/prisma/migrations/20260611040000_add_calendar_feed_color/migration.sql +0 -2
  320. package/prisma/migrations/20260611050000_add_task_priority/migration.sql +0 -14
  321. package/prisma/migrations/20260612060000_add_critical_priority/migration.sql +0 -2
  322. package/prisma/migrations/20260613090000_add_backup_settings/migration.sql +0 -25
  323. package/prisma/migrations/20260614160000_add_feedback/migration.sql +0 -20
  324. package/prisma/migrations/20260614210000_add_2fa/migration.sql +0 -4
  325. package/prisma/migrations/migration_lock.toml +0 -3
  326. package/prisma/schema.prisma +0 -457
  327. package/public/Logo_icon.svg +0 -1
  328. package/public/file.svg +0 -1
  329. package/public/globe.svg +0 -1
  330. package/public/icon-192.png +0 -0
  331. package/public/icon-512.png +0 -0
  332. package/public/icon.svg +0 -4
  333. package/public/icon_dark.svg +0 -1
  334. package/public/knotpad_icon.svg +0 -1
  335. package/public/knotpad_logo_full.svg +0 -1
  336. package/public/manifest.json +0 -14
  337. package/public/next.svg +0 -1
  338. package/public/sw.js +0 -137
  339. package/public/vercel.svg +0 -1
  340. package/public/window.svg +0 -1
  341. package/tsconfig.json +0 -35
package/bin/brief.js CHANGED
@@ -7,12 +7,13 @@
7
7
  *
8
8
  * What it does:
9
9
  * 1. Checks Node version
10
- * 2. Finds or creates a .env file in the current directory
11
- * 3. Generates NEXTAUTH_SECRET / MCP_SECRET if not present
12
- * 4. Starts the Next.js server on port 3099
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
13
14
  * → PGlite (embedded local DB) bootstraps automatically at startup
14
15
  * via instrumentation.tsx → lib/prisma.ts + lib/db-init.ts
15
- * 5. Opens an app-mode window (Chrome/Edge — no address bar) or falls back to browser
16
+ * 6. Opens an app-mode window (Chrome/Edge — no address bar) or falls back to browser
16
17
  */
17
18
 
18
19
  "use strict";
@@ -21,6 +22,7 @@ const { execSync, spawn } = require("child_process");
21
22
  const fs = require("fs");
22
23
  const path = require("path");
23
24
  const { randomBytes } = require("crypto");
25
+ const https = require("https");
24
26
 
25
27
  // ── Helpers ────────────────────────────────────────────────────────────────────
26
28
 
@@ -40,6 +42,67 @@ function openBrowser(url) {
40
42
  try { execSync(cmd, { stdio: "ignore" }); } catch {}
41
43
  }
42
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/knotpad/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/knotpad/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(`tar -xf "${zipPath}" -C "${path.dirname(INSTALL_DIR)}"`, { 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
+
43
106
  // ── Chrome / Edge app-mode detection ──────────────────────────────────────────
44
107
 
45
108
  /**
@@ -131,94 +194,118 @@ function openAppWindow(url) {
131
194
  const [major] = process.versions.node.split(".").map(Number);
132
195
  if (major < 18) die(`Node 18+ required (you have ${process.versions.node}). Install from https://nodejs.org`);
133
196
 
134
- // ── 2. .env setup ──────────────────────────────────────────────────────────────
197
+ // ── 2. Download source ───────────────────────────────────────────────────────────
135
198
 
136
- const PKG_DIR = path.resolve(__dirname, "..");
137
199
  const WORK_DIR = process.cwd();
138
- const ENV_PATH = path.join(WORK_DIR, ".env");
139
-
140
- let env = {};
141
- if (fs.existsSync(ENV_PATH)) {
142
- const raw = fs.readFileSync(ENV_PATH, "utf8");
143
- for (const line of raw.split(/\r?\n/)) {
144
- const m = /^([A-Z_][A-Z0-9_]*)=(.*)$/.exec(line.trim());
145
- if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
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
+ }
146
216
  }
147
- log(".env found, loading existing config");
148
- } else {
149
- log("Creating .env with defaults…");
150
- }
151
217
 
152
- // Fill defaults no DATABASE_URL needed; PGlite handles local storage automatically
153
- // Port 3099 avoids clashing with dev servers (3000) commonly running alongside.
154
- const defaults = {
155
- NEXTAUTH_SECRET: genSecret(),
156
- NEXTAUTH_URL: "http://localhost:3099",
157
- MCP_SECRET: genSecret(),
158
- ENCRYPTION_PEPPER: genSecret(),
159
- NEXT_PUBLIC_APP_URL: "http://localhost:3099",
160
- NEXT_PUBLIC_MCP_URL: "http://localhost:3099/mcp",
161
- IS_CLOUD: "false",
162
- CRON_SECRET: genSecret(16),
163
- };
164
-
165
- let changed = false;
166
- for (const [k, v] of Object.entries(defaults)) {
167
- if (!env[k]) { env[k] = v; changed = true; }
168
- }
218
+ // ── 4. .env setup ──────────────────────────────────────────────────────────────
169
219
 
170
- if (changed) {
171
- const lines = Object.entries(env).map(([k, v]) => `${k}="${v}"`).join("\n");
172
- fs.writeFileSync(ENV_PATH, lines + "\n", "utf8");
173
- ok(".env written");
174
- }
220
+ const ENV_PATH = path.join(WORK_DIR, ".env");
175
221
 
176
- // Apply env for this process
177
- for (const [k, v] of Object.entries(env)) {
178
- process.env[k] = v;
179
- }
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
+ }
180
233
 
181
- // ── 3. Build (if not already built) ───────────────────────────────────────────
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
+ }
182
251
 
183
- // BUILD_ID is only written after a successful build — a leftover .next/ from a
184
- // failed build won't have it, so we correctly re-run in that case.
185
- const BUILD_ID = path.join(PKG_DIR, ".next", "BUILD_ID");
186
- if (!fs.existsSync(BUILD_ID)) {
187
- log("Building Knotpad (first run — takes ~30 seconds)…");
188
- try {
189
- execSync("npx next build", { cwd: PKG_DIR, stdio: "inherit", env: process.env });
190
- ok("Build complete");
191
- } catch {
192
- die("Build failed. Run `npx @knotpad/app` again after fixing any errors.");
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");
193
256
  }
194
- }
195
257
 
196
- // ── 4. Start server ────────────────────────────────────────────────────────────
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
+ }
197
277
 
198
- // Pre-create PGlite data directory so the app can open it immediately at startup
199
- fs.mkdirSync(path.join(WORK_DIR, ".brief", "db"), { recursive: true });
278
+ // ── 6. Start server ────────────────────────────────────────────────────────────
200
279
 
201
- const PORT = parseInt(process.env.PORT ?? "3099", 10);
202
- const URL = `http://localhost:${PORT}`;
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 });
203
282
 
204
- ok(`\nStarting Knotpad on ${URL}\n`);
205
- process.stdout.write(` ${"\x1b[2m"}Ctrl+C to stop${"\x1b[0m"}\n\n`);
283
+ const PORT = parseInt(process.env.PORT ?? "3099", 10);
284
+ const URL = `http://localhost:${PORT}`;
206
285
 
207
- const NEXT_BIN = path.join(PKG_DIR, "node_modules", "next", "dist", "bin", "next");
208
- const server = spawn(process.execPath, [NEXT_BIN, "start", "--port", String(PORT)], {
209
- cwd: PKG_DIR,
210
- stdio: "inherit",
211
- env: process.env,
212
- });
286
+ ok(`\nStarting Knotpad on ${URL}\n`);
287
+ process.stdout.write(` ${"\x1b[2m"}Ctrl+C to stop${"\x1b[0m"}\n\n`);
213
288
 
214
- server.on("error", (e) => die(`Failed to start server: ${e.message}`));
215
- server.on("exit", (code) => {
216
- if (code !== 0 && code !== null) die(`Server exited with code ${code}`);
217
- });
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
+ });
218
295
 
219
- // Open in app-mode Chrome/Edge window (falls back to browser if not found)
220
- setTimeout(() => openAppWindow(URL), 1500);
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
+ });
221
300
 
222
- // Graceful exit
223
- process.on("SIGINT", () => { server.kill("SIGINT"); process.exit(0); });
224
- process.on("SIGTERM", () => { server.kill("SIGTERM"); process.exit(0); });
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
+ });
package/package.json CHANGED
@@ -1,99 +1,19 @@
1
1
  {
2
2
  "name": "@knotpad/app",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Note-first project management with AI agent task routing",
5
5
  "bin": {
6
- "knotpad": "./bin/brief.js"
6
+ "knotpad": "bin/brief.js"
7
7
  },
8
8
  "main": "dist-electron/main.js",
9
9
  "files": [
10
10
  "package.json",
11
- "next.config.ts",
12
- "app",
13
- "components",
14
- "lib",
15
- "prisma",
16
- "public",
17
- "bin",
18
- "electron",
19
- "auth.ts",
20
- "auth.config.ts",
21
- "middleware.ts",
22
- "tailwind.config.ts",
23
- "tsconfig.json",
24
- "postcss.config.mjs",
25
- "instrumentation.tsx"
11
+ "bin"
26
12
  ],
27
13
  "publishConfig": {
28
14
  "access": "public"
29
15
  },
30
- "scripts": {
31
- "dev": "next dev",
32
- "build": "prisma generate && next build",
33
- "start": "next start",
34
- "lint": "eslint",
35
- "test": "vitest run",
36
- "postinstall": "prisma generate",
37
- "electron:dev": "rimraf dist-electron && tsc --project tsconfig.electron.json && electron . --dev",
38
- "electron:build": "rimraf dist && rimraf dist-electron && tsc --project tsconfig.electron.json && electron-builder",
39
- "electron:build:win": "rimraf dist && rimraf dist-electron && tsc --project tsconfig.electron.json && electron-builder --win",
40
- "electron:build:linux": "rimraf dist && rimraf dist-electron && tsc --project tsconfig.electron.json && electron-builder --linux",
41
- "electron:build:mac": "rimraf dist && rimraf dist-electron && tsc --project tsconfig.electron.json && electron-builder --mac"
42
- },
43
- "dependencies": {
44
- "@auth/prisma-adapter": "^2.11.2",
45
- "@dnd-kit/core": "^6.3.1",
46
- "@dnd-kit/sortable": "^10.0.0",
47
- "@dnd-kit/utilities": "^3.2.2",
48
- "@electric-sql/pglite": "^0.4.1",
49
- "@neondatabase/serverless": "^1.1.0",
50
- "@prisma/adapter-neon": "^7.8.0",
51
- "@prisma/adapter-pg": "^7.8.0",
52
- "@stripe/react-stripe-js": "^6.3.0",
53
- "@stripe/stripe-js": "^9.5.0",
54
- "bcryptjs": "^3.0.3",
55
- "d3": "^7.9.0",
56
- "date-holidays": "^3.30.2",
57
- "dotenv": "^17.4.2",
58
- "fflate": "^0.8.3",
59
- "jose": "^6.2.3",
60
- "lucide-react": "^1.16.0",
61
- "next": "16.2.6",
62
- "next-auth": "^5.0.0-beta.31",
63
- "node-ical": "^0.26.1",
64
- "pg": "^8.20.0",
65
- "pglite-prisma-adapter": "^0.7.2",
66
- "react": "19.2.4",
67
- "react-dom": "19.2.4",
68
- "react-markdown": "^10.1.0",
69
- "remark-gfm": "^4.0.1",
70
- "stripe": "^22.1.1",
71
- "zod": "^4.4.3"
72
- },
73
- "devDependencies": {
74
- "@prisma/client": "^7.8.0",
75
- "@tailwindcss/postcss": "^4",
76
- "@testing-library/jest-dom": "^6.9.1",
77
- "@testing-library/react": "^16.3.2",
78
- "@testing-library/user-event": "^14.6.1",
79
- "@types/bcryptjs": "^2.4.6",
80
- "@types/d3": "^7.4.3",
81
- "@types/node": "^20",
82
- "@types/pg": "^8.20.0",
83
- "@types/react": "^19",
84
- "@types/react-dom": "^19",
85
- "concurrently": "^10.0.3",
86
- "electron": "^42.3.3",
87
- "electron-builder": "^26.15.2",
88
- "electron-updater": "^6.8.9",
89
- "eslint": "^9",
90
- "eslint-config-next": "16.2.6",
91
- "jsdom": "^29.1.1",
92
- "prisma": "^7.8.0",
93
- "rimraf": "^6.1.3",
94
- "tailwindcss": "^4",
95
- "typescript": "^5",
96
- "vitest": "^4.1.7",
97
- "wait-on": "^9.0.10"
98
- }
16
+ "scripts": {},
17
+ "dependencies": {},
18
+ "devDependencies": {}
99
19
  }
@@ -1,57 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { getActiveWorkspaceId } from "@/lib/workspace";
5
- import { CalendarView } from "@/components/calendar/calendar-view";
6
-
7
- export default async function CalendarPage({
8
- searchParams,
9
- }: {
10
- searchParams: Promise<{ note?: string; month?: string; folder?: string }>;
11
- }) {
12
- const session = await auth();
13
- if (!session) redirect("/login");
14
-
15
- const { note: filterNoteId, month, folder: filterFolderId } = await searchParams;
16
-
17
- const workspaceId = await getActiveWorkspaceId(session.user.id);
18
- if (!workspaceId) redirect("/login");
19
-
20
- const tasks = await prisma.task.findMany({
21
- where: {
22
- workspaceId,
23
- ...(filterNoteId ? { noteId: filterNoteId } : {}),
24
- ...(filterFolderId && { note: { folderId: filterFolderId } }),
25
- status: { not: "DONE" },
26
- },
27
- orderBy: { dueDate: "asc" },
28
- include: {
29
- note: { select: { id: true, title: true } },
30
- assignee: { select: { id: true, name: true } },
31
- },
32
- });
33
-
34
- const serialized = tasks.map((t) => ({
35
- id: t.id,
36
- title: t.title,
37
- status: t.status,
38
- startDate: t.startDate?.toISOString() ?? null,
39
- dueDate: t.dueDate?.toISOString() ?? null,
40
- noteId: t.noteId,
41
- noteTitle: t.note.title,
42
- assigneeName: t.assignee?.name ?? null,
43
- assigneeType: t.assigneeType,
44
- claimedByAlias: t.claimedByAlias,
45
- }));
46
-
47
- return (
48
- <div className="flex flex-1 flex-col overflow-hidden">
49
- <div className="border-b border-zinc-800 px-6 py-3">
50
- <h1 className="text-sm font-semibold text-zinc-300">
51
- {filterFolderId ? "Filtered tasks" : "Calendar"}
52
- </h1>
53
- </div>
54
- <CalendarView tasks={serialized} initialMonth={month} filterNoteId={filterNoteId} filterFolderId={filterFolderId} />
55
- </div>
56
- );
57
- }
@@ -1,35 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect } from "react";
4
- import { AlertTriangle } from "lucide-react";
5
-
6
- export default function Error({
7
- error,
8
- reset,
9
- }: {
10
- error: Error & { digest?: string };
11
- reset: () => void;
12
- }) {
13
- useEffect(() => {
14
- // Surfaces in the server/console; hook a real error tracker here later.
15
- console.error(error);
16
- }, [error]);
17
-
18
- return (
19
- <div className="flex flex-1 flex-col items-center justify-center gap-4 p-8 text-center">
20
- <AlertTriangle size={28} className="text-amber-500" />
21
- <div className="space-y-1">
22
- <p className="text-sm font-medium text-zinc-300">Something went wrong</p>
23
- <p className="max-w-sm text-xs text-zinc-600">
24
- This view failed to load. You can retry, or head back to your notes.
25
- </p>
26
- </div>
27
- <button
28
- onClick={reset}
29
- className="rounded-md border border-zinc-700 px-3 py-1.5 text-sm text-zinc-300 transition-colors ring-focus hover:bg-zinc-800 hover:text-zinc-100"
30
- >
31
- Try again
32
- </button>
33
- </div>
34
- );
35
- }
@@ -1,32 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { GraphPage } from "@/components/graph/graph-page";
5
-
6
- export default async function GraphRoute({
7
- searchParams,
8
- }: {
9
- searchParams: Promise<{ notes?: string }>;
10
- }) {
11
- const session = await auth();
12
- if (!session) redirect("/login");
13
-
14
- const { notes: notesParam } = await searchParams;
15
- const selectedNoteIds = notesParam ? notesParam.split(",").filter(Boolean) : [];
16
-
17
- return (
18
- <div className="flex flex-1 flex-col overflow-hidden">
19
- <div className="border-b border-zinc-800 px-6 py-3">
20
- <h1 className="text-sm font-semibold text-zinc-300">
21
- Graph
22
- {selectedNoteIds.length > 0 && (
23
- <span className="ml-2 text-xs font-normal text-zinc-500">
24
- — {selectedNoteIds.length} note{selectedNoteIds.length > 1 ? "s" : ""} selected
25
- </span>
26
- )}
27
- </h1>
28
- </div>
29
- <GraphPage selectedNoteIds={selectedNoteIds} />
30
- </div>
31
- );
32
- }
@@ -1,21 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { GuideView } from "@/components/guide/guide-view";
4
-
5
- export const metadata = {
6
- title: "User Guide - Knotpad",
7
- };
8
-
9
- export default async function GuidePage() {
10
- const session = await auth();
11
- if (!session) redirect("/login");
12
-
13
- return (
14
- <div className="flex flex-1 flex-col overflow-hidden">
15
- <div className="border-b border-zinc-800 px-6 py-3">
16
- <h1 className="text-sm font-semibold text-zinc-300">User Guide</h1>
17
- </div>
18
- <GuideView />
19
- </div>
20
- );
21
- }
@@ -1,24 +0,0 @@
1
- import { Skeleton } from "@/components/ui/skeleton";
2
-
3
- export default function KanbanLoading() {
4
- return (
5
- <div className="flex flex-1 flex-col overflow-hidden">
6
- <div className="border-b border-zinc-800 px-6 py-3">
7
- <Skeleton className="h-4 w-20" />
8
- </div>
9
- <div className="flex flex-1 flex-col gap-3 overflow-hidden p-3 md:flex-row md:p-4">
10
- {[0, 1, 2].map((c) => (
11
- <div
12
- key={c}
13
- className="flex w-full shrink-0 flex-col gap-2 rounded-lg border border-zinc-800 bg-zinc-900/40 p-2 md:w-64"
14
- >
15
- <Skeleton className="mb-1 h-4 w-24" />
16
- {[0, 1, 2].map((card) => (
17
- <Skeleton key={card} className="h-14 w-full" />
18
- ))}
19
- </div>
20
- ))}
21
- </div>
22
- </div>
23
- );
24
- }
@@ -1,59 +0,0 @@
1
- import { auth } from "@/auth";
2
- import { redirect } from "next/navigation";
3
- import { prisma } from "@/lib/prisma";
4
- import { getActiveWorkspaceId } from "@/lib/workspace";
5
- import { KanbanBoard } from "@/components/kanban/kanban-board";
6
-
7
- export default async function KanbanPage({
8
- searchParams,
9
- }: {
10
- searchParams: Promise<{ note?: string; folder?: string }>;
11
- }) {
12
- const session = await auth();
13
- if (!session) redirect("/login");
14
-
15
- const { note: filterNoteId, folder: filterFolderId } = await searchParams;
16
-
17
- const workspaceId = await getActiveWorkspaceId(session.user.id);
18
- if (!workspaceId) redirect("/login");
19
-
20
- const tasks = await prisma.task.findMany({
21
- where: {
22
- workspaceId,
23
- ...(filterNoteId && { noteId: filterNoteId }),
24
- ...(filterFolderId && { note: { folderId: filterFolderId } }),
25
- },
26
- orderBy: { createdAt: "asc" },
27
- include: {
28
- note: { select: { id: true, title: true, folder: { select: { id: true, name: true } } } },
29
- assignee: { select: { id: true, name: true, email: true } },
30
- },
31
- });
32
-
33
- // Serialize dates for the client component
34
- const serialized = tasks.map((t) => ({
35
- id: t.id,
36
- title: t.title,
37
- status: t.status,
38
- claimedBy: t.claimedBy,
39
- claimedByAlias: t.claimedByAlias,
40
- lastHeartbeat: t.lastHeartbeat?.toISOString() ?? null,
41
- assigneeType: t.assigneeType,
42
- assignee: t.assignee,
43
- note: t.note,
44
- dueDate: t.dueDate?.toISOString() ?? null,
45
- }));
46
-
47
- return (
48
- <div className="flex flex-1 flex-col overflow-hidden">
49
- <div className="border-b border-zinc-800 px-6 py-3">
50
- <h1 className="text-sm font-semibold text-zinc-300">
51
- {filterNoteId || filterFolderId ? "Filtered tasks" : "Kanban"}
52
- </h1>
53
- </div>
54
- <div className="flex-1 overflow-hidden">
55
- <KanbanBoard tasks={serialized} filterNoteId={filterNoteId} filterFolderId={filterFolderId} />
56
- </div>
57
- </div>
58
- );
59
- }