jettypod 4.4.118 → 4.4.121

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 (240) hide show
  1. package/.env +4 -3
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -1,123 +0,0 @@
1
- const { contextBridge, ipcRenderer } = require('electron');
2
-
3
- // Expose IPC methods to renderer process via window.electronAPI
4
- contextBridge.exposeInMainWorld('electronAPI', {
5
- // Database operations
6
- db: {
7
- // Kanban
8
- getKanbanData: (doneLimit) => ipcRenderer.invoke('db:getKanbanData', doneLimit),
9
-
10
- // Work items
11
- getAllWorkItems: () => ipcRenderer.invoke('db:getAllWorkItems'),
12
- getWorkItem: (id) => ipcRenderer.invoke('db:getWorkItem', id),
13
- getChildWorkItems: (parentId) => ipcRenderer.invoke('db:getChildWorkItems', parentId),
14
- updateWorkItemTitle: (id, title) => ipcRenderer.invoke('db:updateWorkItemTitle', id, title),
15
- updateWorkItemStatus: (id, status) => ipcRenderer.invoke('db:updateWorkItemStatus', id, status),
16
- updateWorkItemOrder: (id, displayOrder) => ipcRenderer.invoke('db:updateWorkItemOrder', id, displayOrder),
17
- updateWorkItemEpic: (id, epicId) => ipcRenderer.invoke('db:updateWorkItemEpic', id, epicId),
18
-
19
- // Decisions
20
- getDecision: (id) => ipcRenderer.invoke('db:getDecision', id),
21
- getDecisionsForWorkItem: (workItemId) => ipcRenderer.invoke('db:getDecisionsForWorkItem', workItemId),
22
-
23
- // Sessions
24
- listSessions: () => ipcRenderer.invoke('db:listSessions'),
25
- createSession: (title) => ipcRenderer.invoke('db:createSession', title),
26
- getSession: (sessionId) => ipcRenderer.invoke('db:getSession', sessionId),
27
- closeSession: (sessionId) => ipcRenderer.invoke('db:closeSession', sessionId),
28
- closeSessionByWorkItem: (workItemId) => ipcRenderer.invoke('db:closeSessionByWorkItem', workItemId),
29
- linkSession: (sessionId, workItemId) => ipcRenderer.invoke('db:linkSession', sessionId, workItemId),
30
- isLinkableWorkItem: (workItemId) => ipcRenderer.invoke('db:isLinkableWorkItem', workItemId),
31
- getActiveSessionByWorkItem: (workItemId) => ipcRenderer.invoke('db:getActiveSessionByWorkItem', workItemId),
32
- getOrCreateSessionForWorkItem: (workItemId) => ipcRenderer.invoke('db:getOrCreateSessionForWorkItem', workItemId),
33
- countActiveSessions: () => ipcRenderer.invoke('db:countActiveSessions'),
34
- cleanupStaleSessions: (retentionDays) => ipcRenderer.invoke('db:cleanupStaleSessions', retentionDays),
35
-
36
- // Session content
37
- getSessionContent: (sessionId) => ipcRenderer.invoke('db:getSessionContent', sessionId),
38
- getSessionContentByWorkItem: (workItemId) => ipcRenderer.invoke('db:getSessionContentByWorkItem', workItemId),
39
- appendSessionContent: (sessionId, turn) => ipcRenderer.invoke('db:appendSessionContent', sessionId, turn),
40
- appendSessionContentByWorkItem: (workItemId, turn) => ipcRenderer.invoke('db:appendSessionContentByWorkItem', workItemId, turn),
41
-
42
- // Environment variables
43
- getEnvVars: () => ipcRenderer.invoke('db:getEnvVars'),
44
- setEnvVar: (name, value) => ipcRenderer.invoke('db:setEnvVar', name, value),
45
- deleteEnvVar: (name) => ipcRenderer.invoke('db:deleteEnvVar', name),
46
-
47
- // Project info
48
- getProjectName: () => ipcRenderer.invoke('db:getProjectName'),
49
-
50
- },
51
-
52
- // Claude subprocess management
53
- claude: {
54
- spawn: (sessionId, cwd) => ipcRenderer.invoke('claude:spawn', sessionId, cwd),
55
- write: (sessionId, text) => ipcRenderer.invoke('claude:write', sessionId, text),
56
- kill: (sessionId) => ipcRenderer.invoke('claude:kill', sessionId),
57
- onOutput: (callback) => {
58
- ipcRenderer.on('claude:output', (event, data) => callback(data));
59
- // Return cleanup function
60
- return () => ipcRenderer.removeAllListeners('claude:output');
61
- },
62
- },
63
-
64
- // Dev server management for app preview
65
- devServer: {
66
- spawn: (projectPath, command, port) => ipcRenderer.invoke('devServer:spawn', projectPath, command, port),
67
- kill: (projectPath) => ipcRenderer.invoke('devServer:kill', projectPath),
68
- status: (projectPath) => ipcRenderer.invoke('devServer:status', projectPath),
69
- list: () => ipcRenderer.invoke('devServer:list'),
70
- onOutput: (callback) => {
71
- ipcRenderer.on('devServer:output', (event, data) => callback(data));
72
- // Return cleanup function
73
- return () => ipcRenderer.removeAllListeners('devServer:output');
74
- },
75
- },
76
-
77
- // Project operations
78
- project: {
79
- openDialog: () => ipcRenderer.invoke('dialog:openProject'),
80
- newProject: () => ipcRenderer.invoke('dialog:newProject'),
81
- getRecent: () => ipcRenderer.invoke('projects:getRecent'),
82
- addRecent: (path) => ipcRenderer.invoke('projects:addRecent', path),
83
- openRecent: (path) => ipcRenderer.invoke('projects:openRecent', path),
84
- },
85
-
86
- // Claude Code operations
87
- claudeCode: {
88
- install: () => ipcRenderer.invoke('claudeCode:install'),
89
- isInstalled: () => ipcRenderer.invoke('claudeCode:isInstalled'),
90
- isAuthenticated: () => ipcRenderer.invoke('claudeCode:isAuthenticated'),
91
- login: () => ipcRenderer.invoke('claudeCode:login'),
92
- update: () => ipcRenderer.invoke('claudeCode:update'),
93
- },
94
-
95
- // Subscription gating (legacy)
96
- subscription: {
97
- createCheckout: (plan) => ipcRenderer.invoke('subscription:createCheckout', plan),
98
- activate: (customerId) => ipcRenderer.invoke('subscription:activate', customerId),
99
- getStatus: () => ipcRenderer.invoke('subscription:getStatus'),
100
- },
101
-
102
- // Billing
103
- billing: {
104
- openCustomerPortal: () => ipcRenderer.invoke('billing:openCustomerPortal'),
105
- },
106
-
107
- // Auth (JWT-based)
108
- auth: {
109
- loginWithGoogle: () => ipcRenderer.invoke('auth:loginWithGoogle'),
110
- saveToken: (token, user) => ipcRenderer.invoke('auth:saveToken', token, user),
111
- getStatus: () => ipcRenderer.invoke('auth:getStatus'),
112
- getToken: () => ipcRenderer.invoke('auth:getToken'),
113
- logout: () => ipcRenderer.invoke('auth:logout'),
114
- },
115
-
116
- // Shell operations
117
- shell: {
118
- openPath: (filePath) => ipcRenderer.invoke('shell:openPath', filePath),
119
- },
120
-
121
- // Check if running in Electron
122
- isElectron: true,
123
- });
@@ -1,141 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { app } = require('electron');
4
-
5
- const UPDATE_SERVER_URL = 'https://jettypod-update-server.spangbaryn2.workers.dev';
6
- const HEARTBEAT_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
7
-
8
- let heartbeatInterval = null;
9
- let updaterHeaders = {};
10
- let log = console.log;
11
-
12
- function setLogger(logFn) {
13
- log = logFn;
14
- }
15
-
16
- function getAuthPath() {
17
- return path.join(app.getPath('userData'), 'auth.json');
18
- }
19
-
20
- function readAuth() {
21
- const authPath = getAuthPath();
22
- if (!fs.existsSync(authPath)) return null;
23
- try {
24
- return JSON.parse(fs.readFileSync(authPath, 'utf-8'));
25
- } catch {
26
- return null;
27
- }
28
- }
29
-
30
- function saveAuth(data) {
31
- const authPath = getAuthPath();
32
- const dir = path.dirname(authPath);
33
- if (!fs.existsSync(dir)) {
34
- fs.mkdirSync(dir, { recursive: true });
35
- }
36
- fs.writeFileSync(authPath, JSON.stringify(data, null, 2));
37
- }
38
-
39
- function decodeJWT(token) {
40
- const parts = token.split('.');
41
- if (parts.length !== 3) return null;
42
- try {
43
- const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
44
- return JSON.parse(Buffer.from(payload, 'base64').toString('utf-8'));
45
- } catch {
46
- return null;
47
- }
48
- }
49
-
50
- function updateAutoUpdaterHeaders() {
51
- const auth = readAuth();
52
- if (auth && auth.token) {
53
- updaterHeaders = { 'Authorization': `Bearer ${auth.token}` };
54
- } else {
55
- updaterHeaders = {};
56
- }
57
- return updaterHeaders;
58
- }
59
-
60
- async function heartbeat() {
61
- const auth = readAuth();
62
- if (!auth || !auth.token) {
63
- log('[Session] No auth token for heartbeat');
64
- return;
65
- }
66
-
67
- log('[Session] Heartbeat: checking /auth/me...');
68
- try {
69
- const response = await fetch(`${UPDATE_SERVER_URL}/auth/me`, {
70
- headers: { 'Authorization': `Bearer ${auth.token}` },
71
- });
72
-
73
- if (!response.ok) {
74
- log(`[Session] Heartbeat failed: ${response.status}`);
75
- return;
76
- }
77
-
78
- const data = await response.json();
79
-
80
- // If server returned a new token (plan changed), save it
81
- if (data.token) {
82
- const payload = decodeJWT(data.token);
83
- const user = payload
84
- ? { id: payload.sub, email: payload.email, plan: payload.plan }
85
- : auth.user;
86
- saveAuth({ token: data.token, user, savedAt: new Date().toISOString() });
87
- log('[Session] Token refreshed from server');
88
- } else if (data.user && data.user.plan !== auth.user?.plan) {
89
- // Plan changed but no new token — update user info
90
- saveAuth({ ...auth, user: data.user, savedAt: new Date().toISOString() });
91
- log(`[Session] Plan updated: ${auth.user?.plan} → ${data.user.plan}`);
92
- }
93
-
94
- updateAutoUpdaterHeaders();
95
- } catch (error) {
96
- log(`[Session] Heartbeat error: ${error.message}`);
97
- }
98
- }
99
-
100
- function start() {
101
- log('[Session] Starting session manager...');
102
- updateAutoUpdaterHeaders();
103
-
104
- // Run first heartbeat after a short delay (don't block startup)
105
- setTimeout(heartbeat, 5000);
106
-
107
- // Schedule periodic heartbeats
108
- heartbeatInterval = setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);
109
-
110
- log('[Session] Session manager started (heartbeat every 4 hours)');
111
- return true;
112
- }
113
-
114
- function stop() {
115
- if (heartbeatInterval) {
116
- clearInterval(heartbeatInterval);
117
- heartbeatInterval = null;
118
- }
119
- log('[Session] Session manager stopped');
120
- }
121
-
122
- function isRunning() {
123
- return heartbeatInterval !== null;
124
- }
125
-
126
- function getUpdaterHeaders() {
127
- return updaterHeaders;
128
- }
129
-
130
- module.exports = {
131
- start,
132
- stop,
133
- heartbeat,
134
- readAuth,
135
- saveAuth,
136
- updateAutoUpdaterHeaders,
137
- isRunning,
138
- getUpdaterHeaders,
139
- setLogger,
140
- getAuthPath,
141
- };
@@ -1,357 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
- const { execSync } = require('child_process');
5
-
6
- // Minimum disk space required for build (2GB in bytes)
7
- const MIN_DISK_SPACE_GB = 2;
8
- const MIN_DISK_SPACE_BYTES = MIN_DISK_SPACE_GB * 1024 * 1024 * 1024;
9
-
10
- /**
11
- * Get available disk space in bytes for the given path
12
- * @param {string} dirPath - Directory to check
13
- * @returns {number} Available space in bytes
14
- */
15
- function getAvailableDiskSpace(dirPath) {
16
- try {
17
- // Use df command to get disk space (works on macOS and Linux)
18
- const output = execSync(`df -k "${dirPath}"`, { encoding: 'utf-8' });
19
- const lines = output.trim().split('\n');
20
- if (lines.length < 2) return Infinity; // Can't parse, assume OK
21
-
22
- // Parse the output (second line, fourth column is available space in KB)
23
- const parts = lines[1].split(/\s+/);
24
- const availableKB = parseInt(parts[3], 10);
25
- if (isNaN(availableKB)) return Infinity; // Can't parse, assume OK
26
-
27
- return availableKB * 1024; // Convert to bytes
28
- } catch (error) {
29
- // If we can't check, don't block the build
30
- console.warn('⚠️ Could not check disk space:', error.message);
31
- return Infinity;
32
- }
33
- }
34
-
35
- /**
36
- * Clean up partial build artifacts
37
- * @param {string} distDir - Distribution directory to clean
38
- */
39
- function cleanupPartialArtifacts(distDir) {
40
- try {
41
- if (fs.existsSync(distDir)) {
42
- const files = fs.readdirSync(distDir);
43
- for (const file of files) {
44
- if (file.includes('.tmp') || file.includes('.partial') || file.endsWith('.blockmap')) {
45
- const filePath = path.join(distDir, file);
46
- fs.unlinkSync(filePath);
47
- console.log(` Cleaned up: ${file}`);
48
- }
49
- }
50
- }
51
- } catch (error) {
52
- console.warn('⚠️ Could not clean up artifacts:', error.message);
53
- }
54
- }
55
-
56
- /**
57
- * electron-builder configuration
58
- * @type {import('electron-builder').Configuration}
59
- */
60
- module.exports = {
61
- // Pre-build validation hook
62
- beforeBuild: async (context) => {
63
- // Check disk space for all platforms
64
- const projectDir = __dirname;
65
- const distDir = path.join(projectDir, 'dist');
66
- const availableSpace = getAvailableDiskSpace(projectDir);
67
- const availableGB = (availableSpace / (1024 * 1024 * 1024)).toFixed(2);
68
-
69
- if (availableSpace < MIN_DISK_SPACE_BYTES) {
70
- console.error('\n❌ ERROR: Insufficient disk space');
71
- console.error('');
72
- console.error(`Available: ${availableGB} GB`);
73
- console.error(`Required: ${MIN_DISK_SPACE_GB} GB minimum`);
74
- console.error('');
75
- console.error('Electron builds require significant disk space for:');
76
- console.error(' - Node modules packaging');
77
- console.error(' - Native module compilation');
78
- console.error(' - DMG/installer creation');
79
- console.error('');
80
- console.error('Free up disk space and try again.');
81
-
82
- // Clean up any partial artifacts
83
- console.error('\nCleaning up partial build artifacts...');
84
- cleanupPartialArtifacts(distDir);
85
-
86
- process.exit(1);
87
- }
88
-
89
- // Only check macOS-specific requirements for mac builds
90
- if (context.platform.name === 'mac') {
91
- // Check for signing certificate (skip if CSC_IDENTITY_AUTO_DISCOVERY=false for local dev)
92
- const skipSigning = process.env.CSC_IDENTITY_AUTO_DISCOVERY === 'false';
93
-
94
- if (skipSigning) {
95
- console.warn('\n⚠️ WARNING: Code signing disabled (CSC_IDENTITY_AUTO_DISCOVERY=false)');
96
- console.warn('Build will be unsigned - for local development only.\n');
97
- } else if (!process.env.CSC_LINK && !process.env.CSC_NAME) {
98
- console.error('\n❌ ERROR: Code signing certificate not configured');
99
- console.error('');
100
- console.error('To sign macOS builds, you need to set one of:');
101
- console.error(' CSC_LINK - Path to .p12 certificate file (or base64 encoded)');
102
- console.error(' CSC_NAME - Name of certificate in Keychain');
103
- console.error('');
104
- console.error('And if using CSC_LINK:');
105
- console.error(' CSC_KEY_PASSWORD - Password for the .p12 certificate');
106
- console.error('');
107
- console.error('Unsigned builds are not allowed. Aborting.');
108
- process.exit(1);
109
- }
110
-
111
- // If CSC_LINK is set, also need CSC_KEY_PASSWORD (skip if signing disabled)
112
- if (!skipSigning && process.env.CSC_LINK && !process.env.CSC_KEY_PASSWORD) {
113
- console.error('\n❌ ERROR: CSC_KEY_PASSWORD not set');
114
- console.error('');
115
- console.error('CSC_LINK is configured but CSC_KEY_PASSWORD is missing.');
116
- console.error('Set CSC_KEY_PASSWORD to the password for your .p12 certificate.');
117
- console.error('');
118
- console.error('Aborting build.');
119
- process.exit(1);
120
- }
121
-
122
- // Warn if notarization credentials are missing (skip if signing disabled)
123
- if (!skipSigning && !process.env.APPLE_TEAM_ID) {
124
- console.warn('\n⚠️ WARNING: APPLE_TEAM_ID not set');
125
- console.warn('');
126
- console.warn('Notarization will be SKIPPED. The DMG will be signed but not notarized.');
127
- console.warn('Users may see "unidentified developer" warnings on first launch.');
128
- console.warn('');
129
- console.warn('To enable notarization, set these environment variables:');
130
- console.warn(' APPLE_TEAM_ID - Your Apple Developer Team ID');
131
- console.warn(' APPLE_ID - Your Apple ID email (for notarytool)');
132
- console.warn(' APPLE_APP_SPECIFIC_PASSWORD - App-specific password');
133
- console.warn('');
134
- console.warn('Continuing with build (without notarization)...\n');
135
- }
136
-
137
- // Check for entitlements file
138
- const entitlementsPath = path.join(__dirname, 'build-resources/entitlements.mac.plist');
139
- if (!fs.existsSync(entitlementsPath)) {
140
- console.error('\n❌ ERROR: Entitlements file not found');
141
- console.error('');
142
- console.error(`Expected file at: ${entitlementsPath}`);
143
- console.error('');
144
- console.error('The entitlements file is required for hardened runtime signing.');
145
- console.error('Create build-resources/entitlements.mac.plist with the required permissions.');
146
- console.error('');
147
- console.error('Aborting build.');
148
- process.exit(1);
149
- }
150
- }
151
- },
152
-
153
- appId: 'com.jettypod.dashboard',
154
- productName: 'JettyPod',
155
-
156
- // Directories
157
- directories: {
158
- output: 'dist',
159
- buildResources: 'build-resources',
160
- },
161
-
162
- // Files to include in the app
163
- // Note: node_modules is handled via extraFiles (with filter to exclude better-sqlite3)
164
- // and extraResources (for better-sqlite3 with proper Electron rebuild)
165
- files: [
166
- 'electron/**/*',
167
- 'lib/run-migrations.js',
168
- 'public/**/*',
169
- 'package.json',
170
- ],
171
-
172
- // Extra files to copy to app directory (for dotfiles like .next)
173
- extraFiles: [
174
- {
175
- from: '.next',
176
- to: 'Resources/app/.next',
177
- filter: [
178
- '**/*',
179
- // Exclude dev-only caches (not needed for production, breaks codesigning)
180
- '!dev/**',
181
- '!cache/**',
182
- ],
183
- },
184
- {
185
- from: 'node_modules',
186
- to: 'Resources/app/node_modules',
187
- filter: [
188
- '**/*',
189
- // Exclude electron/build tooling (better-sqlite3 stays - rebuilt by npmRebuild)
190
- '!electron/**',
191
- '!electron-builder/**',
192
- '!.bin/**',
193
- // Exclude cross-platform binaries that break codesigning
194
- '!7zip-bin/**',
195
- '!app-builder-bin/**',
196
- '!electron-winstaller/**',
197
- '!dmg-builder/**',
198
- // Exclude wrong-architecture native modules (Linux)
199
- '!@next/swc-linux-*/**',
200
- '!@img/sharp-linux-*/**',
201
- '!@tailwindcss/oxide-linux-*/**',
202
- '!@unrs/resolver-binding-linux-*/**',
203
- '!lightningcss-linux-*/**',
204
- // Exclude wrong-architecture native modules (Windows)
205
- '!@next/swc-win32-*/**',
206
- '!@img/sharp-win32-*/**',
207
- '!@tailwindcss/oxide-win32-*/**',
208
- '!@unrs/resolver-binding-win32-*/**',
209
- '!lightningcss-win32-*/**',
210
- ],
211
- },
212
- ],
213
-
214
- // Extra resources (not bundled into asar)
215
- extraResources: [
216
- // Bundled Node.js for Claude Code installation (architecture-specific)
217
- {
218
- from: 'build-resources/node/${arch}',
219
- to: 'node',
220
- filter: ['**/*'],
221
- },
222
- // JettyPod CLI - bundled for PATH setup
223
- {
224
- from: '../../bin/jettypod',
225
- to: 'bin/jettypod',
226
- },
227
- {
228
- from: '../../jettypod.js',
229
- to: 'bin/jettypod.js',
230
- },
231
- {
232
- from: '../../lib',
233
- to: 'bin/lib',
234
- filter: ['**/*'],
235
- },
236
- {
237
- from: '../../package.json',
238
- to: 'bin/package.json',
239
- },
240
- // JettyPod skills - bundled for auto-sync on launch
241
- {
242
- from: path.join(os.homedir(), '.claude', 'skills'),
243
- to: 'skills',
244
- filter: ['**/*'],
245
- },
246
- ],
247
-
248
- // Don't pack into asar (native modules need filesystem access)
249
- asar: false,
250
-
251
- // App icon (electron-builder auto-converts PNG to platform formats)
252
- icon: 'build-resources/icon.png',
253
-
254
- // macOS configuration
255
- mac: {
256
- target: [
257
- {
258
- target: 'dmg',
259
- // Allow BUILD_ARCH env var to override (for local dev builds)
260
- arch: process.env.BUILD_ARCH ? [process.env.BUILD_ARCH] : ['arm64', 'x64'],
261
- },
262
- ],
263
- category: 'public.app-category.developer-tools',
264
- hardenedRuntime: true,
265
- gatekeeperAssess: false,
266
- icon: 'build-resources/icon.png',
267
-
268
- // Entitlements for hardened runtime
269
- entitlements: 'build-resources/entitlements.mac.plist',
270
- entitlementsInherit: 'build-resources/entitlements.mac.plist',
271
-
272
- // Notarization - enabled when APPLE_TEAM_ID, APPLE_ID, and APPLE_APP_SPECIFIC_PASSWORD are set
273
- // electron-builder reads these env vars automatically
274
- notarize: !!process.env.APPLE_TEAM_ID,
275
- },
276
-
277
- // DMG configuration
278
- dmg: {
279
- contents: [
280
- {
281
- x: 130,
282
- y: 220,
283
- },
284
- {
285
- x: 410,
286
- y: 220,
287
- type: 'link',
288
- path: '/Applications',
289
- },
290
- ],
291
- },
292
-
293
- // Windows configuration
294
- win: {
295
- target: [
296
- {
297
- target: 'nsis',
298
- arch: ['x64'],
299
- },
300
- ],
301
- },
302
-
303
- // Linux configuration
304
- linux: {
305
- target: [
306
- {
307
- target: 'AppImage',
308
- arch: ['x64'],
309
- },
310
- ],
311
- category: 'Development',
312
- },
313
-
314
- // Rebuild native modules
315
- npmRebuild: true,
316
-
317
- // Auto-update configuration - generic provider pointing to update server
318
- publish: {
319
- provider: 'generic',
320
- url: 'https://jettypod-update-server.spangbaryn2.workers.dev/updates',
321
- },
322
-
323
- // Hooks for native module handling
324
- afterPack: async (context) => {
325
- const { rebuild } = require('@electron/rebuild');
326
- const { Arch } = require('builder-util');
327
-
328
- // Convert numeric arch enum to string (Arch.arm64=3, Arch.x64=1)
329
- const archString = Arch[context.arch]; // "arm64", "x64", etc.
330
-
331
- // macOS: appOutDir is dist/mac-arm64, app is at JettyPod.app/Contents/Resources/app
332
- // context.packager.appInfo.productFilename gives us "JettyPod"
333
- const appName = context.packager.appInfo.productFilename;
334
- const resourcesPath = context.electronPlatformName === 'darwin'
335
- ? path.join(context.appOutDir, `${appName}.app`, 'Contents', 'Resources', 'app')
336
- : path.join(context.appOutDir, 'resources', 'app');
337
-
338
- const betterSqlitePath = path.join(resourcesPath, 'node_modules', 'better-sqlite3');
339
-
340
- // Only rebuild if better-sqlite3 exists in the output
341
- if (fs.existsSync(betterSqlitePath)) {
342
- console.log(`Rebuilding better-sqlite3 for ${archString} at ${resourcesPath}...`);
343
-
344
- await rebuild({
345
- buildPath: resourcesPath,
346
- electronVersion: '32.3.3',
347
- arch: archString,
348
- onlyModules: ['better-sqlite3'],
349
- force: true,
350
- });
351
-
352
- console.log('better-sqlite3 rebuilt successfully');
353
- } else {
354
- console.log(`better-sqlite3 not found at ${betterSqlitePath}, skipping rebuild`);
355
- }
356
- },
357
- };