jettypod 4.4.120 → 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 (208) hide show
  1. package/.env +2 -1
  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 +54 -49
  8. package/apps/dashboard/app/demo/gates/page.tsx +3 -5
  9. package/apps/dashboard/app/design-system/page.tsx +1 -1
  10. package/apps/dashboard/app/globals.css +74 -2
  11. package/apps/dashboard/app/install-claude/page.tsx +3 -5
  12. package/apps/dashboard/app/login/page.tsx +17 -20
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +60 -12
  15. package/apps/dashboard/app/signup/page.tsx +14 -17
  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 +12 -15
  19. package/apps/dashboard/app/work/[id]/page.tsx +90 -75
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +70 -61
  22. package/apps/dashboard/components/CardMenu.tsx +0 -1
  23. package/apps/dashboard/components/ClaudePanel.tsx +541 -283
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
  26. package/apps/dashboard/components/CopyableId.tsx +1 -2
  27. package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
  28. package/apps/dashboard/components/DragContext.tsx +132 -62
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +5 -6
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +0 -1
  34. package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
  35. package/apps/dashboard/components/EpicGroup.tsx +100 -70
  36. package/apps/dashboard/components/GateCard.tsx +0 -1
  37. package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
  39. package/apps/dashboard/components/JettyLoader.tsx +0 -1
  40. package/apps/dashboard/components/KanbanBoard.tsx +319 -173
  41. package/apps/dashboard/components/KanbanCard.tsx +341 -107
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
  44. package/apps/dashboard/components/MainNav.tsx +24 -25
  45. package/apps/dashboard/components/MessageBlock.tsx +93 -16
  46. package/apps/dashboard/components/ModeStartCard.tsx +0 -1
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
  48. package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
  53. package/apps/dashboard/components/ReviewFooter.tsx +12 -14
  54. package/apps/dashboard/components/SessionList.tsx +0 -1
  55. package/apps/dashboard/components/SubscribeContent.tsx +40 -11
  56. package/apps/dashboard/components/TestTree.tsx +1 -2
  57. package/apps/dashboard/components/TipCard.tsx +2 -4
  58. package/apps/dashboard/components/Toast.tsx +0 -1
  59. package/apps/dashboard/components/TypeIcon.tsx +7 -8
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
  62. package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
  63. package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
  64. package/apps/dashboard/components/WorkItemTree.tsx +2 -4
  65. package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
  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 +20 -73
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
  72. package/apps/dashboard/components/ui/Button.tsx +1 -1
  73. package/apps/dashboard/components/ui/Input.tsx +1 -1
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
  77. package/apps/dashboard/contexts/UsageContext.tsx +62 -31
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  81. package/apps/dashboard/index.html +73 -0
  82. package/apps/dashboard/lib/data-bridge.ts +722 -0
  83. package/apps/dashboard/lib/db.ts +69 -1302
  84. package/apps/dashboard/lib/environment-config.ts +173 -0
  85. package/apps/dashboard/lib/environment-verification.ts +119 -0
  86. package/apps/dashboard/lib/kanban-utils.ts +226 -26
  87. package/apps/dashboard/lib/proof-run.ts +495 -0
  88. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  89. package/apps/dashboard/lib/service-recovery.ts +326 -0
  90. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  91. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  92. package/apps/dashboard/lib/session-stream-manager.ts +253 -122
  93. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  94. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  95. package/apps/dashboard/lib/tauri.ts +106 -0
  96. package/apps/dashboard/lib/utils.ts +3 -3
  97. package/apps/dashboard/next-env.d.ts +1 -1
  98. package/apps/dashboard/package.json +21 -33
  99. package/apps/dashboard/public/bug-icon.png +0 -0
  100. package/apps/dashboard/public/buoy-icon.png +0 -0
  101. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  102. package/apps/dashboard/public/pier-icon.png +0 -0
  103. package/apps/dashboard/public/star-icon.png +0 -0
  104. package/apps/dashboard/public/wrench-icon.png +0 -0
  105. package/apps/dashboard/scripts/tauri-build.js +228 -0
  106. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  107. package/apps/dashboard/src/main.tsx +12 -0
  108. package/apps/dashboard/src/router.tsx +107 -0
  109. package/apps/dashboard/src/vite-env.d.ts +1 -0
  110. package/apps/dashboard/tsconfig.json +7 -12
  111. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  112. package/apps/dashboard/vite.config.ts +33 -0
  113. package/apps/update-server/src/index.ts +167 -30
  114. package/claude-hooks/global-guardrails.js +14 -13
  115. package/crates/jettypod-cli/Cargo.toml +19 -0
  116. package/crates/jettypod-cli/src/commands.rs +1249 -0
  117. package/crates/jettypod-cli/src/main.rs +595 -0
  118. package/crates/jettypod-core/Cargo.toml +26 -0
  119. package/crates/jettypod-core/build.rs +98 -0
  120. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  121. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  122. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  123. package/crates/jettypod-core/src/auth.rs +294 -0
  124. package/crates/jettypod-core/src/config.rs +397 -0
  125. package/crates/jettypod-core/src/db/mod.rs +507 -0
  126. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  127. package/crates/jettypod-core/src/db/startup.rs +101 -0
  128. package/crates/jettypod-core/src/db/validate.rs +149 -0
  129. package/crates/jettypod-core/src/error.rs +76 -0
  130. package/crates/jettypod-core/src/git.rs +458 -0
  131. package/crates/jettypod-core/src/lib.rs +20 -0
  132. package/crates/jettypod-core/src/sessions.rs +625 -0
  133. package/crates/jettypod-core/src/skills.rs +556 -0
  134. package/crates/jettypod-core/src/work.rs +1086 -0
  135. package/crates/jettypod-core/src/worktree.rs +628 -0
  136. package/crates/jettypod-core/src/ws.rs +767 -0
  137. package/cucumber-test.cjs +6 -0
  138. package/jettypod.js +96 -4
  139. package/lib/bdd-preflight.js +96 -0
  140. package/lib/merge-lock.js +111 -253
  141. package/lib/migrations/030-rejection-round-columns.js +54 -0
  142. package/lib/migrations/031-session-isolation-index.js +17 -0
  143. package/lib/work-commands/index.js +58 -16
  144. package/lib/work-tracking/index.js +108 -8
  145. package/package.json +1 -1
  146. package/skills-templates/bug-mode/SKILL.md +43 -1
  147. package/skills-templates/chore-mode/SKILL.md +40 -1
  148. package/skills-templates/design-system-selection/SKILL.md +273 -0
  149. package/skills-templates/epic-planning/SKILL.md +14 -0
  150. package/skills-templates/feature-planning/SKILL.md +90 -1
  151. package/skills-templates/production-mode/SKILL.md +20 -0
  152. package/skills-templates/simple-improvement/SKILL.md +39 -2
  153. package/skills-templates/speed-mode/SKILL.md +10 -15
  154. package/skills-templates/stable-mode/SKILL.md +47 -0
  155. package/apps/dashboard/README.md +0 -36
  156. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
  157. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  158. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
  159. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  160. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -525
  161. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  162. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  163. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  164. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  165. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  166. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  167. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  168. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  169. package/apps/dashboard/app/api/tests/route.ts +0 -9
  170. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  171. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  172. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  173. package/apps/dashboard/app/api/usage/route.ts +0 -17
  174. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  175. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  176. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  177. package/apps/dashboard/app/api/work/[id]/route.ts +0 -35
  178. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
  179. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  180. package/apps/dashboard/app/layout.tsx +0 -55
  181. package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
  182. package/apps/dashboard/electron/ipc-handlers.js +0 -1026
  183. package/apps/dashboard/electron/main.js +0 -2306
  184. package/apps/dashboard/electron/preload.js +0 -125
  185. package/apps/dashboard/electron/session-manager.js +0 -163
  186. package/apps/dashboard/electron-builder.config.js +0 -357
  187. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  188. package/apps/dashboard/lib/backlog-parser.ts +0 -50
  189. package/apps/dashboard/lib/claude-process-manager.ts +0 -529
  190. package/apps/dashboard/lib/db-bridge.ts +0 -283
  191. package/apps/dashboard/lib/prototypes.ts +0 -202
  192. package/apps/dashboard/lib/test-results-db.ts +0 -307
  193. package/apps/dashboard/lib/tests.ts +0 -282
  194. package/apps/dashboard/next.config.js +0 -66
  195. package/apps/dashboard/postcss.config.mjs +0 -7
  196. package/apps/dashboard/public/bug-icon.svg +0 -9
  197. package/apps/dashboard/public/buoy-icon.svg +0 -9
  198. package/apps/dashboard/public/file.svg +0 -1
  199. package/apps/dashboard/public/globe.svg +0 -1
  200. package/apps/dashboard/public/in-flight-seagull.svg +0 -9
  201. package/apps/dashboard/public/next.svg +0 -1
  202. package/apps/dashboard/public/pier-icon.svg +0 -14
  203. package/apps/dashboard/public/star-icon.svg +0 -9
  204. package/apps/dashboard/public/vercel.svg +0 -1
  205. package/apps/dashboard/public/window.svg +0 -1
  206. package/apps/dashboard/public/wrench-icon.svg +0 -9
  207. package/apps/dashboard/scripts/download-node.js +0 -104
  208. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
@@ -1,125 +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
- getPostLoginPath: () => ipcRenderer.invoke('auth:getPostLoginPath'),
115
- hasLoggedInBefore: () => ipcRenderer.invoke('auth:hasLoggedInBefore'),
116
- },
117
-
118
- // Shell operations
119
- shell: {
120
- openPath: (filePath) => ipcRenderer.invoke('shell:openPath', filePath),
121
- },
122
-
123
- // Check if running in Electron
124
- isElectron: true,
125
- });
@@ -1,163 +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 heartbeatWithRetry(maxRetries = 3) {
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
-
69
- let lastError;
70
- for (let attempt = 0; attempt < maxRetries; attempt++) {
71
- if (attempt > 0) {
72
- const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
73
- log(`[Session] Heartbeat retry ${attempt}/${maxRetries - 1} in ${delay}ms...`);
74
- await new Promise(resolve => setTimeout(resolve, delay));
75
- }
76
-
77
- try {
78
- const response = await fetch(`${UPDATE_SERVER_URL}/auth/me`, {
79
- headers: { 'Authorization': `Bearer ${auth.token}` },
80
- });
81
-
82
- if (!response.ok) {
83
- log(`[Session] Heartbeat failed: ${response.status}`);
84
- if (response.status >= 500) {
85
- lastError = new Error(`Server error: ${response.status}`);
86
- continue;
87
- }
88
- return;
89
- }
90
-
91
- const data = await response.json();
92
-
93
- // If server returned a new token (plan changed), save it
94
- if (data.token) {
95
- const payload = decodeJWT(data.token);
96
- const user = payload
97
- ? { id: payload.sub, email: payload.email, plan: payload.plan }
98
- : auth.user;
99
- saveAuth({ token: data.token, user, savedAt: new Date().toISOString() });
100
- log('[Session] Token refreshed from server');
101
- } else if (data.user && data.user.plan !== auth.user?.plan) {
102
- // Plan changed but no new token — update user info
103
- saveAuth({ ...auth, user: data.user, savedAt: new Date().toISOString() });
104
- log(`[Session] Plan updated: ${auth.user?.plan} → ${data.user.plan}`);
105
- }
106
-
107
- updateAutoUpdaterHeaders();
108
- return;
109
- } catch (error) {
110
- lastError = error;
111
- log(`[Session] Heartbeat error: ${error.message}`);
112
- }
113
- }
114
-
115
- log(`[Session] Heartbeat failed after ${maxRetries} attempts: ${lastError?.message}`);
116
- }
117
-
118
- async function heartbeat() {
119
- return heartbeatWithRetry();
120
- }
121
-
122
- function start() {
123
- log('[Session] Starting session manager...');
124
- updateAutoUpdaterHeaders();
125
-
126
- // Run first heartbeat after a short delay (don't block startup)
127
- setTimeout(heartbeat, 5000);
128
-
129
- // Schedule periodic heartbeats
130
- heartbeatInterval = setInterval(heartbeat, HEARTBEAT_INTERVAL_MS);
131
-
132
- log('[Session] Session manager started (heartbeat every 4 hours)');
133
- return true;
134
- }
135
-
136
- function stop() {
137
- if (heartbeatInterval) {
138
- clearInterval(heartbeatInterval);
139
- heartbeatInterval = null;
140
- }
141
- log('[Session] Session manager stopped');
142
- }
143
-
144
- function isRunning() {
145
- return heartbeatInterval !== null;
146
- }
147
-
148
- function getUpdaterHeaders() {
149
- return updaterHeaders;
150
- }
151
-
152
- module.exports = {
153
- start,
154
- stop,
155
- heartbeat,
156
- readAuth,
157
- saveAuth,
158
- updateAutoUpdaterHeaders,
159
- isRunning,
160
- getUpdaterHeaders,
161
- setLogger,
162
- getAuthPath,
163
- };
@@ -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
- };