heyquilt 0.0.1 → 0.3.106
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.
- package/LICENSE +85 -0
- package/README.md +633 -2
- package/RELEASES.md +862 -0
- package/assets/logo.svg +1 -0
- package/bin/quilt.js +849 -0
- package/desktop/main.js +315 -0
- package/package.json +163 -7
- package/src/account.js +591 -0
- package/src/admit-policy.js +32 -0
- package/src/agent-join.js +370 -0
- package/src/agent-task-workflow.js +176 -0
- package/src/agents/actions.js +85 -0
- package/src/agents/claude-code.js +187 -0
- package/src/agents/common.js +21 -0
- package/src/agents/cursor-transcripts.js +177 -0
- package/src/agents/cursor.js +404 -0
- package/src/agents/index.js +69 -0
- package/src/ai-vm-signin.js +110 -0
- package/src/ai-vm.js +522 -0
- package/src/api/access.js +75 -0
- package/src/api/activity.js +181 -0
- package/src/api/agent-auth.js +136 -0
- package/src/api/agent-placement.js +157 -0
- package/src/api/audit.js +83 -0
- package/src/api/auth.js +17 -0
- package/src/api/domains.js +30 -0
- package/src/api/file-paths.js +40 -0
- package/src/api/file-store.js +99 -0
- package/src/api/fly-machines.js +133 -0
- package/src/api/github-app.js +172 -0
- package/src/api/http.js +34 -0
- package/src/api/invite-email.js +53 -0
- package/src/api/issues.js +110 -0
- package/src/api/join-text.js +78 -0
- package/src/api/mailer.js +47 -0
- package/src/api/memory-store.js +1219 -0
- package/src/api/org-access.js +54 -0
- package/src/api/permissions.js +71 -0
- package/src/api/relay-cluster.js +545 -0
- package/src/api/relay-machines.js +92 -0
- package/src/api/releases.js +87 -0
- package/src/api/routes/access-types.js +54 -0
- package/src/api/routes/agent-invites.js +115 -0
- package/src/api/routes/agents.js +252 -0
- package/src/api/routes/ai-vm.js +495 -0
- package/src/api/routes/github.js +380 -0
- package/src/api/routes/grants.js +70 -0
- package/src/api/routes/invites.js +188 -0
- package/src/api/routes/issues.js +47 -0
- package/src/api/routes/join.js +167 -0
- package/src/api/routes/members.js +110 -0
- package/src/api/routes/orgs.js +179 -0
- package/src/api/routes/relay-cluster.js +90 -0
- package/src/api/routes/relay.js +76 -0
- package/src/api/routes/secrets.js +448 -0
- package/src/api/routes/session-invites.js +119 -0
- package/src/api/routes/sessions.js +92 -0
- package/src/api/routes/teams.js +132 -0
- package/src/api/routes/workspace-agents.js +332 -0
- package/src/api/routes/workspace-files.js +277 -0
- package/src/api/routes/workspace-invites.js +196 -0
- package/src/api/routes/workspaces.js +232 -0
- package/src/api/sandbox-keys.js +57 -0
- package/src/api/secrets-access.js +93 -0
- package/src/api/server.js +654 -0
- package/src/api/slugs.js +20 -0
- package/src/api/supabase-store.js +875 -0
- package/src/api/team-access.js +30 -0
- package/src/api/tokens.js +31 -0
- package/src/api/workspace-access.js +62 -0
- package/src/api/workspace-reach.js +60 -0
- package/src/autowork-tools.js +166 -0
- package/src/autowork.js +375 -0
- package/src/blobstore.js +99 -0
- package/src/boxes.js +37 -0
- package/src/branchdocs.js +235 -0
- package/src/branches.js +194 -0
- package/src/catchup.js +111 -0
- package/src/chat-archive.js +85 -0
- package/src/chat-links.js +923 -0
- package/src/clash.js +128 -0
- package/src/cloud-ai.js +50 -0
- package/src/commit-request.js +59 -0
- package/src/commit.js +213 -0
- package/src/connection.js +569 -0
- package/src/control.js +248 -0
- package/src/conversation.js +125 -0
- package/src/doctor.js +156 -0
- package/src/duties.js +303 -0
- package/src/editors.js +228 -0
- package/src/fsutil.js +97 -0
- package/src/git-hook.js +48 -0
- package/src/git.js +147 -0
- package/src/github-commit.js +320 -0
- package/src/gitignore.js +49 -0
- package/src/gitstate.js +924 -0
- package/src/history.js +307 -0
- package/src/hooks.js +371 -0
- package/src/hosted-agents.js +24 -0
- package/src/hosted-client.js +112 -0
- package/src/identity.js +99 -0
- package/src/inbox.js +237 -0
- package/src/integrations.js +221 -0
- package/src/key-cli.js +115 -0
- package/src/largefiles.js +91 -0
- package/src/legacy.js +25 -0
- package/src/mcp-proxy.js +45 -0
- package/src/mcp-room.js +32 -0
- package/src/mcp.js +1258 -0
- package/src/merge3.js +240 -0
- package/src/merges.js +148 -0
- package/src/pass-source.js +183 -0
- package/src/passes.js +64 -0
- package/src/pathrules.js +134 -0
- package/src/persona.js +101 -0
- package/src/presence.js +360 -0
- package/src/private-file.js +41 -0
- package/src/procs.js +70 -0
- package/src/project-rules.js +178 -0
- package/src/protocol.js +92 -0
- package/src/quiet-warnings.js +9 -0
- package/src/relay-ai-vm.js +82 -0
- package/src/relay-ask.js +141 -0
- package/src/relay-ci.js +111 -0
- package/src/relay-clash.js +214 -0
- package/src/relay-cluster.js +454 -0
- package/src/relay-commit.js +887 -0
- package/src/relay-filekey.js +46 -0
- package/src/relay-git.js +274 -0
- package/src/relay-githttp.js +166 -0
- package/src/relay-github.js +109 -0
- package/src/relay-lease.js +137 -0
- package/src/relay-lfs.js +37 -0
- package/src/relay-mcp.js +1691 -0
- package/src/relay-metrics.js +140 -0
- package/src/relay-outbound.js +1273 -0
- package/src/relay-sync.js +716 -0
- package/src/relay-turn-sync.js +506 -0
- package/src/relay-upstream.js +804 -0
- package/src/relay-webhooks.js +111 -0
- package/src/releases.js +138 -0
- package/src/report.js +129 -0
- package/src/runner.js +307 -0
- package/src/same-edit.js +18 -0
- package/src/secret-import.js +55 -0
- package/src/secret-names.js +26 -0
- package/src/secret-run.js +80 -0
- package/src/secrets-client.js +56 -0
- package/src/server.js +4214 -0
- package/src/session-access.js +170 -0
- package/src/session-name.js +15 -0
- package/src/session.js +7446 -0
- package/src/settings.js +46 -0
- package/src/setup.js +184 -0
- package/src/shell-path.js +46 -0
- package/src/status.js +197 -0
- package/src/summarize.js +109 -0
- package/src/sync-words.js +42 -0
- package/src/task-comments.js +92 -0
- package/src/tasks.js +670 -0
- package/src/textdiff.js +20 -0
- package/src/turn-sync-plan.js +145 -0
- package/src/turn-sync.js +212 -0
- package/src/ui/access-form.js +37 -0
- package/src/ui/agent-env-key.js +76 -0
- package/src/ui/agent-guide.js +127 -0
- package/src/ui/agent-kinds.js +175 -0
- package/src/ui/agent-place.js +265 -0
- package/src/ui/app.css +2494 -0
- package/src/ui/app.js +546 -0
- package/src/ui/board.js +465 -0
- package/src/ui/boxes.js +76 -0
- package/src/ui/branches.js +111 -0
- package/src/ui/catchup.js +77 -0
- package/src/ui/changes.js +159 -0
- package/src/ui/chat.js +266 -0
- package/src/ui/cloud-ai-model.js +108 -0
- package/src/ui/cloud-ai.js +221 -0
- package/src/ui/common.js +433 -0
- package/src/ui/csv.js +21 -0
- package/src/ui/diffview.js +74 -0
- package/src/ui/feed-convs.js +39 -0
- package/src/ui/feed.js +174 -0
- package/src/ui/files.js +579 -0
- package/src/ui/fileview.js +150 -0
- package/src/ui/git-rule.js +60 -0
- package/src/ui/home.js +1322 -0
- package/src/ui/index.html +16 -0
- package/src/ui/invite.js +97 -0
- package/src/ui/invites.js +79 -0
- package/src/ui/loom-model.js +516 -0
- package/src/ui/loom.css +365 -0
- package/src/ui/loom.js +786 -0
- package/src/ui/mark.js +104 -0
- package/src/ui/rail-model.js +75 -0
- package/src/ui/rail.js +173 -0
- package/src/ui/releases.js +182 -0
- package/src/ui/sandbox-keys-model.js +53 -0
- package/src/ui/sandbox-keys.js +132 -0
- package/src/ui/schedule.js +156 -0
- package/src/ui/secret-rules.js +255 -0
- package/src/ui/secrets-view.js +162 -0
- package/src/ui/secrets.js +283 -0
- package/src/ui/session.js +3292 -0
- package/src/ui/signin.js +75 -0
- package/src/ui/sounds.js +110 -0
- package/src/ui/tool-logo.js +86 -0
- package/src/ui/tree.js +281 -0
- package/src/ui/workspaces.js +547 -0
- package/src/ui-server.js +1381 -0
- package/src/update-check.js +67 -0
- package/src/upstream.js +192 -0
- package/src/vault/crypto.js +96 -0
- package/src/vault/kms.js +61 -0
- package/src/vault/main.js +33 -0
- package/src/vault/server.js +279 -0
- package/src/vault/sigv4.js +38 -0
- package/src/webhooks.js +235 -0
- package/src/workspace-cli.js +62 -0
- package/src/workspace-tools.js +340 -0
- package/index.js +0 -2
package/desktop/main.js
ADDED
|
@@ -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.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
3
|
+
"version": "0.3.106",
|
|
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-timeout=600000 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-timeout=600000 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
|
}
|