heyquilt 0.0.1 → 0.3.90

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 (204) hide show
  1. package/LICENSE +85 -0
  2. package/README.md +630 -2
  3. package/RELEASES.md +733 -0
  4. package/assets/logo.svg +1 -0
  5. package/bin/quilt.js +816 -0
  6. package/desktop/main.js +315 -0
  7. package/package.json +163 -7
  8. package/src/account.js +563 -0
  9. package/src/admit-policy.js +32 -0
  10. package/src/agent-join.js +370 -0
  11. package/src/agent-task-workflow.js +176 -0
  12. package/src/agents/actions.js +85 -0
  13. package/src/agents/claude-code.js +180 -0
  14. package/src/agents/common.js +21 -0
  15. package/src/agents/cursor-transcripts.js +170 -0
  16. package/src/agents/cursor.js +404 -0
  17. package/src/agents/index.js +69 -0
  18. package/src/ai-vm-signin.js +98 -0
  19. package/src/ai-vm.js +380 -0
  20. package/src/api/access.js +75 -0
  21. package/src/api/activity.js +181 -0
  22. package/src/api/agent-auth.js +130 -0
  23. package/src/api/agent-placement.js +157 -0
  24. package/src/api/audit.js +83 -0
  25. package/src/api/auth.js +17 -0
  26. package/src/api/domains.js +30 -0
  27. package/src/api/file-paths.js +40 -0
  28. package/src/api/file-store.js +99 -0
  29. package/src/api/fly-machines.js +76 -0
  30. package/src/api/github-app.js +172 -0
  31. package/src/api/http.js +34 -0
  32. package/src/api/invite-email.js +53 -0
  33. package/src/api/issues.js +110 -0
  34. package/src/api/join-text.js +78 -0
  35. package/src/api/mailer.js +47 -0
  36. package/src/api/memory-store.js +1083 -0
  37. package/src/api/org-access.js +54 -0
  38. package/src/api/permissions.js +71 -0
  39. package/src/api/relay-cluster.js +251 -0
  40. package/src/api/relay-machines.js +60 -0
  41. package/src/api/routes/access-types.js +54 -0
  42. package/src/api/routes/agent-invites.js +115 -0
  43. package/src/api/routes/agents.js +157 -0
  44. package/src/api/routes/ai-vm.js +369 -0
  45. package/src/api/routes/github.js +380 -0
  46. package/src/api/routes/grants.js +70 -0
  47. package/src/api/routes/invites.js +188 -0
  48. package/src/api/routes/issues.js +47 -0
  49. package/src/api/routes/join.js +167 -0
  50. package/src/api/routes/members.js +110 -0
  51. package/src/api/routes/orgs.js +177 -0
  52. package/src/api/routes/relay-cluster.js +90 -0
  53. package/src/api/routes/relay.js +76 -0
  54. package/src/api/routes/session-invites.js +119 -0
  55. package/src/api/routes/sessions.js +92 -0
  56. package/src/api/routes/teams.js +132 -0
  57. package/src/api/routes/workspace-agents.js +332 -0
  58. package/src/api/routes/workspace-files.js +277 -0
  59. package/src/api/routes/workspace-invites.js +196 -0
  60. package/src/api/routes/workspaces.js +225 -0
  61. package/src/api/server.js +623 -0
  62. package/src/api/slugs.js +20 -0
  63. package/src/api/supabase-store.js +765 -0
  64. package/src/api/team-access.js +30 -0
  65. package/src/api/tokens.js +31 -0
  66. package/src/api/workspace-access.js +62 -0
  67. package/src/api/workspace-reach.js +60 -0
  68. package/src/autowork-tools.js +166 -0
  69. package/src/autowork.js +387 -0
  70. package/src/blobstore.js +99 -0
  71. package/src/boxes.js +37 -0
  72. package/src/branchdocs.js +235 -0
  73. package/src/branches.js +194 -0
  74. package/src/catchup.js +111 -0
  75. package/src/chat-archive.js +85 -0
  76. package/src/chat-links.js +923 -0
  77. package/src/clash.js +140 -0
  78. package/src/cloud-ai.js +50 -0
  79. package/src/commit-request.js +59 -0
  80. package/src/commit.js +213 -0
  81. package/src/connection.js +569 -0
  82. package/src/control.js +245 -0
  83. package/src/conversation.js +125 -0
  84. package/src/doctor.js +156 -0
  85. package/src/duties.js +303 -0
  86. package/src/editors.js +228 -0
  87. package/src/fsutil.js +87 -0
  88. package/src/git-hook.js +48 -0
  89. package/src/git.js +147 -0
  90. package/src/github-commit.js +320 -0
  91. package/src/gitignore.js +49 -0
  92. package/src/gitstate.js +900 -0
  93. package/src/history.js +307 -0
  94. package/src/hooks.js +371 -0
  95. package/src/hosted-agents.js +24 -0
  96. package/src/hosted-client.js +112 -0
  97. package/src/identity.js +99 -0
  98. package/src/inbox.js +237 -0
  99. package/src/integrations.js +221 -0
  100. package/src/key-cli.js +115 -0
  101. package/src/largefiles.js +91 -0
  102. package/src/legacy.js +25 -0
  103. package/src/mcp-proxy.js +40 -0
  104. package/src/mcp-room.js +32 -0
  105. package/src/mcp.js +1261 -0
  106. package/src/merge3.js +158 -0
  107. package/src/merges.js +148 -0
  108. package/src/pass-source.js +176 -0
  109. package/src/passes.js +64 -0
  110. package/src/pathrules.js +127 -0
  111. package/src/persona.js +101 -0
  112. package/src/presence.js +360 -0
  113. package/src/private-file.js +41 -0
  114. package/src/procs.js +70 -0
  115. package/src/project-rules.js +178 -0
  116. package/src/protocol.js +92 -0
  117. package/src/quiet-warnings.js +9 -0
  118. package/src/relay-ai-vm.js +82 -0
  119. package/src/relay-ask.js +141 -0
  120. package/src/relay-ci.js +111 -0
  121. package/src/relay-clash.js +597 -0
  122. package/src/relay-cluster.js +454 -0
  123. package/src/relay-commit.js +887 -0
  124. package/src/relay-filekey.js +46 -0
  125. package/src/relay-git.js +274 -0
  126. package/src/relay-githttp.js +137 -0
  127. package/src/relay-github.js +109 -0
  128. package/src/relay-lease.js +137 -0
  129. package/src/relay-lfs.js +37 -0
  130. package/src/relay-mcp.js +1679 -0
  131. package/src/relay-metrics.js +75 -0
  132. package/src/relay-outbound.js +1144 -0
  133. package/src/relay-sync.js +719 -0
  134. package/src/relay-turn-sync.js +307 -0
  135. package/src/relay-upstream.js +934 -0
  136. package/src/relay-webhooks.js +111 -0
  137. package/src/releases.js +131 -0
  138. package/src/report.js +129 -0
  139. package/src/runner.js +306 -0
  140. package/src/same-edit.js +18 -0
  141. package/src/server.js +4187 -0
  142. package/src/session-access.js +170 -0
  143. package/src/session-name.js +15 -0
  144. package/src/session.js +7361 -0
  145. package/src/settings.js +46 -0
  146. package/src/setup.js +184 -0
  147. package/src/shell-path.js +46 -0
  148. package/src/status.js +199 -0
  149. package/src/summarize.js +109 -0
  150. package/src/sync-words.js +42 -0
  151. package/src/task-comments.js +92 -0
  152. package/src/tasks.js +670 -0
  153. package/src/textdiff.js +20 -0
  154. package/src/turn-sync-plan.js +142 -0
  155. package/src/turn-sync.js +191 -0
  156. package/src/ui/access-form.js +37 -0
  157. package/src/ui/agent-env-key.js +76 -0
  158. package/src/ui/agent-guide.js +127 -0
  159. package/src/ui/agent-kinds.js +175 -0
  160. package/src/ui/agent-place.js +265 -0
  161. package/src/ui/app.css +2309 -0
  162. package/src/ui/app.js +542 -0
  163. package/src/ui/board.js +465 -0
  164. package/src/ui/boxes.js +76 -0
  165. package/src/ui/branches.js +111 -0
  166. package/src/ui/catchup.js +77 -0
  167. package/src/ui/changes.js +159 -0
  168. package/src/ui/chat.js +266 -0
  169. package/src/ui/cloud-ai-model.js +107 -0
  170. package/src/ui/cloud-ai.js +221 -0
  171. package/src/ui/common.js +431 -0
  172. package/src/ui/csv.js +21 -0
  173. package/src/ui/diffview.js +74 -0
  174. package/src/ui/feed-convs.js +39 -0
  175. package/src/ui/feed.js +174 -0
  176. package/src/ui/files.js +579 -0
  177. package/src/ui/fileview.js +123 -0
  178. package/src/ui/git-rule.js +60 -0
  179. package/src/ui/home.js +1323 -0
  180. package/src/ui/index.html +16 -0
  181. package/src/ui/invite.js +97 -0
  182. package/src/ui/invites.js +79 -0
  183. package/src/ui/loom-model.js +516 -0
  184. package/src/ui/loom.css +349 -0
  185. package/src/ui/loom.js +753 -0
  186. package/src/ui/mark.js +104 -0
  187. package/src/ui/merges.js +158 -0
  188. package/src/ui/rail-model.js +78 -0
  189. package/src/ui/rail.js +173 -0
  190. package/src/ui/releases.js +182 -0
  191. package/src/ui/schedule.js +156 -0
  192. package/src/ui/session.js +3394 -0
  193. package/src/ui/signin.js +75 -0
  194. package/src/ui/sounds.js +110 -0
  195. package/src/ui/tool-logo.js +86 -0
  196. package/src/ui/tree.js +276 -0
  197. package/src/ui/workspaces.js +542 -0
  198. package/src/ui-server.js +1272 -0
  199. package/src/update-check.js +67 -0
  200. package/src/upstream.js +155 -0
  201. package/src/webhooks.js +235 -0
  202. package/src/workspace-cli.js +62 -0
  203. package/src/workspace-tools.js +340 -0
  204. package/index.js +0 -2
@@ -0,0 +1,315 @@
1
+ // Quilt desktop app: the same app as `quilt ui`, in its own window. Sessions
2
+ // keep syncing from the menu bar when the window is closed, and invite links
3
+ // (quilt://join?invite=…) open straight into the join screen.
4
+ import '../src/quiet-warnings.js'
5
+ import { app, BrowserWindow, Tray, Menu, shell, dialog, ipcMain, nativeTheme } from 'electron'
6
+ import { execFile } from 'node:child_process'
7
+ import fs from 'node:fs'
8
+ import os from 'node:os'
9
+ import path from 'node:path'
10
+ import { fileURLToPath } from 'node:url'
11
+ import { startUi } from '../src/ui-server.js'
12
+ import { registerProcess } from '../src/procs.js'
13
+ import { decodeInvite } from '../src/runner.js'
14
+ import { quiltHome, adoptLegacyEnv } from '../src/legacy.js'
15
+ import { downloadUrl } from '../src/releases.js'
16
+ import { installUpdate } from './updater.js'
17
+ import { registerOnStart } from '../src/integrations.js'
18
+ import { adoptLoginShellPath } from '../src/shell-path.js'
19
+ import { getSettings } from '../src/settings.js'
20
+
21
+ adoptLegacyEnv()
22
+ // Opened from Finder or the Dock, the app has a minimal PATH: git (Homebrew's, Xcode's) is
23
+ // found through your login shell's PATH, read while the app gets ready. Never rejects.
24
+ const shellPath = adoptLoginShellPath()
25
+
26
+ const HERE = path.dirname(fileURLToPath(import.meta.url))
27
+ const CLI_SHIM = path.join(quiltHome(), 'bin', 'quilt')
28
+ const CLI_LINK = '/usr/local/bin/quilt'
29
+
30
+ let ui = null
31
+ let win = null
32
+ let tray = null
33
+ let quitting = false
34
+ let pendingInvite = null
35
+ let rendererReady = false
36
+
37
+ /** The invite link inside a quilt:// URL, or null. */
38
+ function inviteFrom (url) {
39
+ try {
40
+ const u = new URL(url)
41
+ if (u.protocol !== 'quilt:') return null
42
+ const link = u.searchParams.get('invite') || ''
43
+ decodeInvite(link)
44
+ return link
45
+ } catch { return null }
46
+ }
47
+
48
+ function openInvite (link) {
49
+ if (!link) return
50
+ if (!win || !rendererReady) { pendingInvite = link; if (win) showWindow(); return }
51
+ showWindow()
52
+ win.webContents.send('invite', link)
53
+ }
54
+
55
+ /** Tells Quilt about a crash in this process, when the app is far enough along to. Never throws. */
56
+ async function reportCrash (name, err) {
57
+ try {
58
+ if (!ui) return
59
+ ui.report({ kind: 'crash', name, outcome: 'error', message: err?.stack || err?.message || String(err) })
60
+ await ui.flushReports()
61
+ } catch {}
62
+ }
63
+
64
+ // Electron would show its own dialog and carry on; do the same, after telling Quilt.
65
+ process.on('uncaughtException', (err) => {
66
+ reportCrash('main', err).finally(() => dialog.showErrorBox('quilt hit a problem', err?.stack || err?.message || String(err)))
67
+ })
68
+
69
+ if (!app.requestSingleInstanceLock()) {
70
+ app.quit()
71
+ } else {
72
+ // A second launch (Windows/Linux invite links arrive this way) focuses this one.
73
+ app.on('second-instance', (e, argv) => {
74
+ showWindow()
75
+ openInvite(argv.map(inviteFrom).find(Boolean))
76
+ })
77
+ // macOS delivers quilt:// links here, possibly before the app is ready.
78
+ // Any other quilt:// link (e.g. quilt://open from the website) just brings the app forward.
79
+ app.on('open-url', (e, url) => {
80
+ e.preventDefault()
81
+ const link = inviteFrom(url)
82
+ if (link) openInvite(link)
83
+ else if (win) showWindow()
84
+ })
85
+
86
+ if (process.defaultApp) app.setAsDefaultProtocolClient('quilt', process.execPath, [path.resolve(process.argv[1])])
87
+ else app.setAsDefaultProtocolClient('quilt')
88
+
89
+ pendingInvite = process.argv.map(inviteFrom).find(Boolean) || null
90
+ app.whenReady().then(start).catch(async (err) => {
91
+ await reportCrash('start', err)
92
+ dialog.showErrorBox('quilt could not start', err.stack || err.message)
93
+ app.exit(1)
94
+ })
95
+ }
96
+
97
+ async function start () {
98
+ await shellPath // before anything can run git
99
+ ui = await startUi({ port: 0, onShutdown: () => app.quit() })
100
+ registerProcess('app', { port: ui.port, url: ui.url, desktop: true })
101
+ process.on('SIGTERM', () => app.quit()) // `quilt stop`
102
+ if (app.isPackaged) writeCliShim()
103
+ // Every AI tool on this computer gets Quilt's MCP server before anyone joins anything.
104
+ registerOnStart((line) => console.log(line))
105
+
106
+ ipcMain.handle('ready', () => { rendererReady = true; const l = pendingInvite; pendingInvite = null; return l })
107
+ ipcMain.handle('pick-folder', async (e, current) => {
108
+ const expanded = String(current || '').replace(/^~(?=$|\/)/, os.homedir())
109
+ const r = await dialog.showOpenDialog(win, {
110
+ title: 'Choose a folder',
111
+ buttonLabel: 'Use this folder',
112
+ defaultPath: expanded && fs.existsSync(expanded) ? expanded : os.homedir(),
113
+ properties: ['openDirectory', 'createDirectory']
114
+ })
115
+ return r.canceled ? null : r.filePaths[0]
116
+ })
117
+
118
+ ipcMain.handle('install-update', () => update())
119
+
120
+ // The title bar follows the app's theme (Settings → Appearance), from the saved one at launch, then as the
121
+ // page changes it (preload.cjs). Its icon follows too: the ink tile on dark, the linen one on light.
122
+ followTheme(getSettings().theme)
123
+ ipcMain.on('theme', (e, theme) => followTheme(theme))
124
+ nativeTheme.on('updated', () => { if (win && !win.isDestroyed() && process.platform !== 'darwin') win.setIcon(windowIcon()) })
125
+
126
+ Menu.setApplicationMenu(appMenu())
127
+ makeTray()
128
+ createWindow()
129
+ }
130
+
131
+ /** Light, Dark or System, as the app calls them; anything else is the app's default, dark. */
132
+ function followTheme (theme) {
133
+ const source = ['light', 'dark', 'system'].includes(theme) ? theme : 'dark'
134
+ if (nativeTheme.themeSource !== source) nativeTheme.themeSource = source
135
+ }
136
+
137
+ const windowIcon = () => path.join(HERE, 'icons', (nativeTheme.shouldUseDarkColors ? 'app' : 'app-light') + (process.platform === 'win32' ? '.ico' : '.png'))
138
+
139
+ // ---------------------------------------------------------------- update --
140
+ // "Update Quilt" in the app: download the newest build, install it, restart.
141
+ let updating = null
142
+ function update () {
143
+ if (updating) return updating
144
+ updating = (async () => {
145
+ if (!app.isPackaged) throw new Error('Updates install into the packaged app only; from source, pull and restart.')
146
+ const progress = (p) => { if (win && !win.isDestroyed()) win.webContents.send('update-progress', p) }
147
+ try {
148
+ const next = await installUpdate(downloadUrl(), { tempDir: app.getPath('temp'), onProgress: progress })
149
+ quitting = true
150
+ // An AppImage relaunches from its file, not from the mount it runs in now.
151
+ if (next === 'relaunch') app.relaunch(process.env.APPIMAGE ? { execPath: process.env.APPIMAGE, args: process.argv.slice(1) } : undefined)
152
+ setTimeout(() => app.quit(), 300) // let the renderer show "Restarting…"
153
+ return { ok: true }
154
+ } catch (err) {
155
+ updating = null
156
+ throw new Error(err.message || 'The update failed.')
157
+ }
158
+ })()
159
+ return updating
160
+ }
161
+
162
+ function createWindow () {
163
+ win = new BrowserWindow({
164
+ width: 1280,
165
+ height: 840,
166
+ minWidth: 880,
167
+ minHeight: 600,
168
+ title: 'quilt',
169
+ show: false,
170
+ backgroundColor: '#17161f', // the dark theme's ground, the default (src/ui/app.css)
171
+ // The title bar's and taskbar's icon. The installed app's .exe carries it too, but from source the
172
+ // window would show Electron's. macOS takes the app bundle's.
173
+ ...(process.platform === 'darwin' ? {} : { icon: windowIcon() }),
174
+ // plugins: Chromium's PDF viewer, which the workspace file preview uses to show PDFs in an iframe.
175
+ webPreferences: { preload: path.join(HERE, 'preload.cjs'), contextIsolation: true, sandbox: true, plugins: true }
176
+ })
177
+ rendererReady = false
178
+ win.loadURL(ui.url)
179
+ win.once('ready-to-show', () => win.show())
180
+ win.webContents.on('did-start-navigation', (e) => { if (e.isMainFrame && !e.isSameDocument) rendererReady = false })
181
+
182
+ // Links to anywhere else open in the browser, never inside the app.
183
+ const origin = new URL(ui.url).origin
184
+ win.webContents.setWindowOpenHandler(({ url }) => {
185
+ if (/^https?:/.test(url) && !url.startsWith(origin)) shell.openExternal(url)
186
+ return { action: 'deny' }
187
+ })
188
+ win.webContents.on('will-navigate', (e, url) => {
189
+ if (url.startsWith(origin)) return
190
+ e.preventDefault()
191
+ if (/^https?:/.test(url)) shell.openExternal(url)
192
+ })
193
+
194
+ // Closing the window keeps sessions syncing in the menu bar.
195
+ win.on('close', (e) => {
196
+ if (quitting) return
197
+ e.preventDefault()
198
+ win.hide()
199
+ })
200
+ }
201
+
202
+ function showReleaseNotes () {
203
+ showWindow()
204
+ win.webContents.send('release-notes')
205
+ }
206
+
207
+ function showWindow () {
208
+ if (!win || win.isDestroyed()) return createWindow()
209
+ if (win.isMinimized()) win.restore()
210
+ win.show()
211
+ win.focus()
212
+ }
213
+
214
+ // The tray icon is the mark with no tile, so its pieces follow the taskbar: ink on a light one, linen on a
215
+ // dark one (brand/SPEC.md). macOS tints its template image itself; Linux panels are nearly always dark.
216
+ const trayIcon = (light) => path.join(HERE, 'icons', process.platform === 'darwin' ? 'trayTemplate.png' : process.platform === 'win32' ? (light ? 'tray-light.ico' : 'tray-dark.ico') : 'tray-dark.png')
217
+
218
+ /** Whether Windows' taskbar is light. It follows the system theme, which can differ from the apps' one (nativeTheme). */
219
+ function lightTaskbar () {
220
+ return new Promise((resolve) => {
221
+ execFile('reg', ['query', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize', '/v', 'SystemUsesLightTheme'], { windowsHide: true }, (err, out) => {
222
+ resolve(!err && /SystemUsesLightTheme\s+REG_DWORD\s+0x0*1\b/.test(out))
223
+ })
224
+ })
225
+ }
226
+
227
+ function makeTray () {
228
+ tray = new Tray(trayIcon(false))
229
+ if (process.platform === 'win32') {
230
+ const follow = () => lightTaskbar().then((light) => { if (tray && !tray.isDestroyed()) tray.setImage(trayIcon(light)) })
231
+ follow()
232
+ nativeTheme.on('updated', follow) // Windows' theme changed (the system's or the apps')
233
+ }
234
+ tray.setToolTip('quilt')
235
+ tray.setContextMenu(Menu.buildFromTemplate([
236
+ { label: 'Open Quilt', click: showWindow },
237
+ { type: 'separator' },
238
+ { label: 'Quit Quilt', click: () => app.quit() }
239
+ ]))
240
+ if (process.platform !== 'darwin') tray.on('click', showWindow)
241
+ }
242
+
243
+ function appMenu () {
244
+ const isMac = process.platform === 'darwin'
245
+ return Menu.buildFromTemplate([
246
+ ...(isMac
247
+ ? [{
248
+ label: app.name,
249
+ submenu: [
250
+ { role: 'about' },
251
+ { label: 'What’s New in Quilt…', click: showReleaseNotes },
252
+ { type: 'separator' },
253
+ { label: 'Install the Quilt Command…', click: installCli },
254
+ { type: 'separator' },
255
+ { role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' },
256
+ { type: 'separator' },
257
+ { role: 'quit' }
258
+ ]
259
+ }]
260
+ : [{ label: 'File', submenu: [{ label: 'What’s New in Quilt…', click: showReleaseNotes }, { type: 'separator' }, { role: 'quit' }] }]),
261
+ { role: 'editMenu' },
262
+ { label: 'View', submenu: [{ role: 'reload' }, { role: 'toggleDevTools' }, { type: 'separator' }, { role: 'resetZoom' }, { role: 'zoomIn' }, { role: 'zoomOut' }, { type: 'separator' }, { role: 'togglefullscreen' }] },
263
+ { role: 'windowMenu' }
264
+ ])
265
+ }
266
+
267
+ // ------------------------------------------------------ command line --
268
+ // AI tools reach Quilt through the `quilt` command (its MCP server). The
269
+ // app ships that command; this keeps a small launcher for it up to date.
270
+ function writeCliShim () {
271
+ const main = path.join(app.getAppPath(), 'bin', 'quilt.js')
272
+ const script = `#!/bin/sh\n# The Quilt command, run by the Quilt desktop app's copy of Node.\nELECTRON_RUN_AS_NODE=1 exec "${process.execPath}" "${main}" "$@"\n`
273
+ try {
274
+ fs.mkdirSync(path.dirname(CLI_SHIM), { recursive: true })
275
+ if (!fs.existsSync(CLI_SHIM) || fs.readFileSync(CLI_SHIM, 'utf8') !== script) fs.writeFileSync(CLI_SHIM, script, { mode: 0o755 })
276
+ } catch {}
277
+ }
278
+
279
+ async function installCli () {
280
+ if (!app.isPackaged) {
281
+ return dialog.showMessageBox(win, { message: 'Install the command from the packaged app', detail: 'When running from source, use `npm link` in the Quilt folder instead.' })
282
+ }
283
+ writeCliShim()
284
+ let current = null
285
+ try { current = fs.readlinkSync(CLI_LINK) } catch {}
286
+ if (current === CLI_SHIM) {
287
+ return dialog.showMessageBox(win, { message: 'The Quilt command is installed', detail: `You can run \`quilt\` in any terminal, and AI tools can use it.` })
288
+ }
289
+ if (fs.existsSync(CLI_LINK) && current !== CLI_SHIM) {
290
+ const r = await dialog.showMessageBox(win, { type: 'question', buttons: ['Replace', 'Cancel'], defaultId: 0, cancelId: 1, message: 'A Quilt command is already installed', detail: `${CLI_LINK} already exists. Replace it with the one from this app?` })
291
+ if (r.response !== 0) return
292
+ }
293
+ try {
294
+ fs.mkdirSync(path.dirname(CLI_LINK), { recursive: true })
295
+ fs.rmSync(CLI_LINK, { force: true })
296
+ fs.symlinkSync(CLI_SHIM, CLI_LINK)
297
+ } catch {
298
+ // /usr/local/bin usually needs an administrator; macOS asks for the password.
299
+ const cmd = `mkdir -p /usr/local/bin && ln -sf '${CLI_SHIM}' '${CLI_LINK}'`
300
+ const ok = await new Promise((resolve) => execFile('osascript', ['-e', `do shell script "${cmd}" with administrator privileges`], (err) => resolve(!err)))
301
+ if (!ok) return
302
+ }
303
+ dialog.showMessageBox(win, { message: 'The Quilt command is installed', detail: 'You can now run `quilt` in any terminal. AI tools like Claude Code and Cursor use it to see your session.' })
304
+ }
305
+
306
+ // Quit cleanly: stop every session (so edits are flushed) before exiting.
307
+ let closed = false
308
+ app.on('before-quit', (e) => {
309
+ quitting = true
310
+ if (closed || !ui) return
311
+ e.preventDefault()
312
+ ui.close().catch(() => {}).finally(() => { closed = true; app.quit() })
313
+ })
314
+ app.on('activate', showWindow)
315
+ app.on('window-all-closed', () => {}) // stay in the menu bar
package/package.json CHANGED
@@ -1,10 +1,166 @@
1
1
  {
2
2
  "name": "heyquilt",
3
- "version": "0.0.1",
4
- "description": "Quilt: real-time pair coding that works with any AI tool. Coming soon at https://heyquilt.com",
5
- "homepage": "https://heyquilt.com",
6
- "license": "MIT",
7
- "author": "Daniel Carmichael",
8
- "files": ["README.md", "index.js"],
9
- "main": "index.js"
3
+ "version": "0.3.90",
4
+ "description": "Real-time, tool-agnostic pair vibe coding: sync a project folder live between collaborators using any AI coding tool.",
5
+ "author": {
6
+ "name": "Daniel Carmichael",
7
+ "url": "https://heyquilt.com"
8
+ },
9
+ "type": "module",
10
+ "bin": {
11
+ "quilt": "bin/quilt.js",
12
+ "heyquilt": "bin/quilt.js",
13
+ "cowove": "bin/quilt.js"
14
+ },
15
+ "scripts": {
16
+ "test": "node --disable-warning=ExperimentalWarning --import ./test/helpers/setup.js --test test/unit/*.test.js test/integration/*.test.js test/e2e/*.test.js",
17
+ "test:unit": "node --disable-warning=ExperimentalWarning --import ./test/helpers/setup.js --test test/unit/*.test.js",
18
+ "test:integration": "node --disable-warning=ExperimentalWarning --import ./test/helpers/setup.js --test test/integration/*.test.js",
19
+ "test:e2e": "node --disable-warning=ExperimentalWarning --import ./test/helpers/setup.js --test test/e2e/*.test.js",
20
+ "test:stress": "node --disable-warning=ExperimentalWarning --import ./test/helpers/setup.js --test --test-concurrency=1 test/stress/*.test.js",
21
+ "test:file": "node --disable-warning=ExperimentalWarning --import ./test/helpers/setup.js --test",
22
+ "test:coverage": "node scripts/coverage.mjs",
23
+ "test:fast": "node --disable-warning=ExperimentalWarning --import ./test/helpers/setup.js --test test/unit/*.test.js test/integration/*.test.js",
24
+ "serve": "node bin/quilt.js serve",
25
+ "app": "electron .",
26
+ "dist:mac": "electron-builder --mac",
27
+ "dist:win": "electron-builder --win",
28
+ "icons": "electron desktop/make-icons.cjs",
29
+ "release": "node scripts/release.mjs",
30
+ "dist:linux": "electron-builder --linux",
31
+ "dist:cli": "node scripts/cli-bundle.mjs"
32
+ },
33
+ "engines": {
34
+ "node": ">=22"
35
+ },
36
+ "license": "SEE LICENSE IN LICENSE",
37
+ "dependencies": {
38
+ "@fontsource/ibm-plex-mono": "^5.3.0",
39
+ "@fontsource/poppins": "^5.3.0",
40
+ "@modelcontextprotocol/sdk": "^1.30.1",
41
+ "@supabase/supabase-js": "^2.117.2",
42
+ "chokidar": "^5.0.0",
43
+ "fast-diff": "^1.3.0",
44
+ "ignore": "^7.0.10",
45
+ "jose": "^6.2.12",
46
+ "lib0": "^0.2.118",
47
+ "node-diff3": "^3.2.1",
48
+ "nodemailer": "^10.0.12",
49
+ "ws": "^8.22.0",
50
+ "y-protocols": "^1.0.7",
51
+ "yjs": "^13.6.33",
52
+ "zod": "^4.6.5"
53
+ },
54
+ "devDependencies": {
55
+ "electron": "^44.4.5",
56
+ "electron-builder": "^26.15.3"
57
+ },
58
+ "files": [
59
+ "bin",
60
+ "src",
61
+ "assets",
62
+ "RELEASES.md",
63
+ "LICENSE"
64
+ ],
65
+ "main": "desktop/main.js",
66
+ "build": {
67
+ "appId": "dev.quilt.app",
68
+ "productName": "Quilt",
69
+ "copyright": "Copyright © 2026 Daniel Carmichael. All rights reserved.",
70
+ "directories": {
71
+ "buildResources": "desktop/build",
72
+ "output": "dist"
73
+ },
74
+ "files": [
75
+ "bin/**",
76
+ "src/**",
77
+ "assets/**",
78
+ "desktop/main.js",
79
+ "desktop/preload.cjs",
80
+ "desktop/updater.js",
81
+ "desktop/icons/*.png",
82
+ "desktop/icons/*.ico",
83
+ "package.json",
84
+ "RELEASES.md",
85
+ "LICENSE"
86
+ ],
87
+ "asar": true,
88
+ "protocols": [
89
+ {
90
+ "name": "quilt invite",
91
+ "schemes": [
92
+ "quilt"
93
+ ]
94
+ }
95
+ ],
96
+ "mac": {
97
+ "category": "public.app-category.developer-tools",
98
+ "target": [
99
+ {
100
+ "target": "dmg",
101
+ "arch": [
102
+ "arm64",
103
+ "x64"
104
+ ]
105
+ }
106
+ ],
107
+ "artifactName": "quilt-mac-${arch}.${ext}",
108
+ "identity": "-",
109
+ "hardenedRuntime": false
110
+ },
111
+ "dmg": {
112
+ "title": "Quilt",
113
+ "artifactName": "quilt-mac-${arch}.${ext}",
114
+ "background": "desktop/build/background.png",
115
+ "iconSize": 96,
116
+ "iconTextSize": 13,
117
+ "window": {
118
+ "width": 660,
119
+ "height": 420
120
+ },
121
+ "contents": [
122
+ {
123
+ "x": 180,
124
+ "y": 215,
125
+ "type": "file"
126
+ },
127
+ {
128
+ "x": 480,
129
+ "y": 215,
130
+ "type": "link",
131
+ "path": "/Applications"
132
+ }
133
+ ]
134
+ },
135
+ "win": {
136
+ "target": [
137
+ {
138
+ "target": "nsis",
139
+ "arch": [
140
+ "x64"
141
+ ]
142
+ }
143
+ ],
144
+ "artifactName": "quilt-windows-${arch}.${ext}"
145
+ },
146
+ "nsis": {
147
+ "oneClick": true,
148
+ "perMachine": false
149
+ },
150
+ "linux": {
151
+ "target": [
152
+ {
153
+ "target": "AppImage",
154
+ "arch": [
155
+ "x64",
156
+ "arm64"
157
+ ]
158
+ }
159
+ ],
160
+ "artifactName": "quilt-linux-${arch}.${ext}",
161
+ "category": "Development",
162
+ "icon": "desktop/build/icon.png",
163
+ "synopsis": "Real-time pair coding, whatever AI tool you use"
164
+ }
165
+ }
10
166
  }