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,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Quilt</title>
7
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
8
+ <link rel="stylesheet" href="/app.css">
9
+ <link rel="stylesheet" href="/loom.css">
10
+ </head>
11
+ <body>
12
+ <div id="app"></div>
13
+ <div id="toast" role="status" aria-live="polite"></div>
14
+ <script type="module" src="/app.js"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1,97 @@
1
+ // Invite link helpers: pure functions only, no Node built-ins, so this one file loads
2
+ // unmodified in the CLI/runner (Node) and in the app's browser UI. Both sides build and
3
+ // parse invite links here; nowhere else should match these shapes with its own regex.
4
+ import { agentGuide, CLI_FIRST, INSTALL_CLI, SANDBOX_CLI } from './agent-guide.js'
5
+
6
+ /** Where invites live on the website. The relay is implied: Quilt's own. */
7
+ export const JOIN_HOST = 'join.heyquilt.com'
8
+ export const JOIN_URL = `https://${JOIN_HOST}`
9
+
10
+ export const MISSING_SECRET = 'This link is missing part of it. Ask for a new invite.'
11
+ export const INVALID_INVITE = 'That invite link is not valid. Copy the whole link they sent.'
12
+
13
+ // A room name becomes a folder name when joining, so it may never carry a path.
14
+ const ROOM = /^[A-Za-z0-9_-]{1,64}$/
15
+
16
+ function base64urlToJson (raw) {
17
+ try {
18
+ return JSON.parse(atob(raw.replace(/-/g, '+').replace(/_/g, '/')))
19
+ } catch {
20
+ return null
21
+ }
22
+ }
23
+
24
+ /**
25
+ * An invite link: https://join.heyquilt.com/<room>#<secret> for sessions on Quilt's relay, or
26
+ * https://<relay>/join/<room>#<secret> for any other relay (development relays). The secret
27
+ * sits after `#`, so browsers never send it anywhere. `isHosted(server)` tells the forms apart.
28
+ */
29
+ export function buildInvite (c, isHosted) {
30
+ const room = encodeURIComponent(c.room)
31
+ const secret = encodeURIComponent(c.secret || '')
32
+ if (isHosted(c.server)) return `${JOIN_URL}/${room}#${secret}`
33
+ const base = String(c.server).replace(/\/+$/, '').replace(/^ws(s?):\/\//, 'http$1://')
34
+ return `${base}/join/${room}#${secret}`
35
+ }
36
+
37
+ /**
38
+ * Reads an invite link (or an older base64 code), with or without "quilt join" or "quilt:" in
39
+ * front. A join.heyquilt.com link has no relay of its own (`relay: null`, caller fills in
40
+ * Quilt's relay) and throws MISSING_SECRET if its secret is missing. An old-style relay link
41
+ * keeps its own relay, but only if `allowRelay(relay)` says yes: a link must never point the
42
+ * app at a relay it doesn't already use. Throws INVALID_INVITE when nothing matches, the room
43
+ * isn't a plain name, or the relay isn't allowed.
44
+ */
45
+ export function parseInvite (code, { allowRelay = () => false } = {}) {
46
+ const raw = String(code || '').trim().replace(/^quilt join\s+/, '').replace(/^quilt:/, '').split(/\s/)[0].replace(/^["']|["']$/g, '')
47
+ const j = raw.match(/^https:\/\/join\.heyquilt\.com\/([A-Za-z0-9_-]{1,64})\/?(?:#(.*))?$/)
48
+ if (j) {
49
+ let secret = ''
50
+ try { secret = decodeURIComponent(j[2] || '') } catch {}
51
+ if (!secret) throw new Error(MISSING_SECRET)
52
+ return { relay: null, room: j[1], secret }
53
+ }
54
+ let r = null
55
+ const m = raw.match(/^(https?):\/\/(.+)\/join\/([^/#?]+)\/?(?:#(.*))?$/)
56
+ if (m) {
57
+ try {
58
+ r = { relay: `${m[1] === 'https' ? 'wss' : 'ws'}://${m[2]}`, room: decodeURIComponent(m[3]), secret: decodeURIComponent(m[4] || '') }
59
+ } catch {}
60
+ }
61
+ if (!r) {
62
+ const j2 = base64urlToJson(raw)
63
+ if (j2 && j2.s && j2.r) r = { relay: String(j2.s), room: String(j2.r), secret: String(j2.k || '') }
64
+ }
65
+ if (!r || !ROOM.test(r.room) || !allowRelay(r.relay)) throw new Error(INVALID_INVITE)
66
+ return r
67
+ }
68
+
69
+ /**
70
+ * What to paste into an AI so it joins Quilt as your agent. `link` is a one-time agent
71
+ * invite from the accounts API; `session` is the session invite link when it is made from a
72
+ * session's Invite, so the agent joins that session straight after registering. This is the
73
+ * one invite prompt: the app (Settings and a session's Invite) and the website (Agents, an
74
+ * org's People) all show exactly this.
75
+ */
76
+ export function agentPaste ({ link, session, invitedToSessions = false } = {}) {
77
+ let apiUrl
78
+ try { apiUrl = new URL(link).origin } catch {}
79
+ return [
80
+ 'Join Quilt as my AI agent. Quilt is a live project folder that people and AI agents work in together.',
81
+ '',
82
+ CLI_FIRST,
83
+ '',
84
+ `1. Register once with this invite. It works one time, within an hour: ${link}`,
85
+ `- With the CLI (whenever you can run commands): ${INSTALL_CLI} Then run \`quilt agent join ${link} --name <your name>\` (pick a short name). ${SANDBOX_CLI}`,
86
+ `- Only if you have no computer and can do nothing but make HTTP requests: open ${link} and follow it to join over HTTP.`,
87
+ '- If you joined Quilt before, give the agent id Quilt gave you then (CLI: `--agent-id <your agent id>`; over HTTP: "agentId"), so you come back as the same agent instead of a new one. Not joined before, or lost it? Leave it out.',
88
+ '- Quilt answers with your agent id. Save it where you will still have it later (your memory, notes or config): it is public, not a secret, and it is how you come back as yourself.',
89
+ session
90
+ ? `2. Then join my session with this session invite link: ${session}\n- With the CLI, run \`quilt join ${session} --agent <your name>\` in the project folder (see below to keep it running); only over HTTP, call quilt_join_session with it. I may have to let you in first.`
91
+ : '2. Join a session: I will send you a session invite link (https://join.heyquilt.com/...). With the CLI, run `quilt join <session invite link> --agent <your name>` in the project folder; only over HTTP, call quilt_join_session with it.',
92
+ // Global and workspace agents are invited to new sessions by a webhook carrying each session's link.
93
+ ...(invitedToSessions ? ['3. You are invited to each new session in my workspaces. To hear about them, call quilt_workspace_webhook with a public https URL that you receive POSTs on: Quilt sends `session.started` there with the session\'s invite link. Join it as in step 2, and wait until I let you in.'] : []),
94
+ '',
95
+ agentGuide({ apiUrl })
96
+ ].join('\n')
97
+ }
@@ -0,0 +1,79 @@
1
+ // Invites waiting for you, on Home: workspaces to accept or decline, and sessions to join
2
+ // (with the link the owner sent) or decline. Loaded when Home opens and every minute after.
3
+ import { state, esc, toast, api, avatar } from './common.js'
4
+
5
+ const sessionAccess = (i) => (i.access ? ` · ${esc(i.access)}` : '')
6
+
7
+ function rowHtml (i) {
8
+ const from = i.from?.name || 'Someone'
9
+ if (i.kind === 'workspace') {
10
+ const where = `the <b>${esc(i.workspace.name)}</b> workspace${i.workspace.org ? ` in ${esc(i.workspace.org)}` : ''}`
11
+ return `
12
+ <div class="session-row invite-row">
13
+ ${avatar(from, null)}
14
+ <div class="meta"><div class="name"><span><b>${esc(from)}</b> invited you to ${where}</span></div>
15
+ <div class="sub">${i.access === 'view' ? 'View only' : 'Can edit'}</div></div>
16
+ <div class="facts"></div>
17
+ <div class="acts"><button class="btn sm ghost" data-invite-decline="${esc(i.id)}">Decline</button><button class="btn sm primary" data-invite-accept="${esc(i.id)}">Accept</button></div>
18
+ </div>`
19
+ }
20
+ return `
21
+ <div class="session-row invite-row">
22
+ ${avatar(from, null)}
23
+ <div class="meta"><div class="name"><span><b>${esc(from)}</b> invited you to the session <b>${esc(i.session.name)}</b></span></div>
24
+ <div class="sub">${i.link ? 'Session' : 'Open the link in the email they sent to join'}${sessionAccess(i)}</div></div>
25
+ <div class="facts"></div>
26
+ <div class="acts"><button class="btn sm ghost" data-invite-decline="${esc(i.id)}">Decline</button>${i.link ? `<button class="btn sm primary" data-invite-join="${esc(i.id)}">Join</button>` : ''}</div>
27
+ </div>`
28
+ }
29
+
30
+ export function invitesHtml () {
31
+ const list = state.invites || []
32
+ if (!list.length) return ''
33
+ return `
34
+ <div class="sec-head"><h2>Invites for you</h2><span class="count">${list.length}</span></div>
35
+ <div class="card session-list">${list.map(rowHtml).join('')}</div>`
36
+ }
37
+
38
+ /** Fetches your invites; true when the list changed. Quiet when signed out or offline. */
39
+ export async function loadInvites () {
40
+ try {
41
+ const { invites } = await api('GET', '/api/invites')
42
+ const before = JSON.stringify(state.invites || [])
43
+ state.invites = invites
44
+ return JSON.stringify(invites) !== before
45
+ } catch { return false }
46
+ }
47
+
48
+ /** Fills Home's invites slot and answers its buttons. `joinDialog(link)` opens Join; `after()` refreshes what an accept changes. */
49
+ export function bindInvites (root, { joinDialog, after }) {
50
+ const slot = root.querySelector('#invites-slot')
51
+ if (!slot) return
52
+ const paint = () => { slot.innerHTML = invitesHtml(); slot.hidden = !(state.invites || []).length }
53
+ paint()
54
+ loadInvites().then((changed) => { if (changed && root.isConnected) paint() })
55
+ slot.onclick = async (e) => {
56
+ const b = e.target.closest('[data-invite-accept],[data-invite-decline],[data-invite-join]')
57
+ if (!b) return
58
+ const id = b.dataset.inviteAccept || b.dataset.inviteDecline || b.dataset.inviteJoin
59
+ const inv = (state.invites || []).find((i) => i.id === id)
60
+ if (!inv) return
61
+ if (b.dataset.inviteJoin) return joinDialog(inv.link)
62
+ b.disabled = true
63
+ try {
64
+ if (b.dataset.inviteAccept) {
65
+ await api('POST', `/api/invites/${encodeURIComponent(id)}/accept`, {})
66
+ toast(`You're in ${inv.workspace.name}`)
67
+ } else {
68
+ await api('POST', `/api/invites/${encodeURIComponent(id)}/decline`, {})
69
+ toast('Invite declined')
70
+ }
71
+ state.invites = state.invites.filter((i) => i.id !== id)
72
+ paint()
73
+ if (b.dataset.inviteAccept) await after()
74
+ } catch (err) {
75
+ b.disabled = false
76
+ toast(err.message)
77
+ }
78
+ }
79
+ }