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.
- package/LICENSE +85 -0
- package/README.md +630 -2
- package/RELEASES.md +733 -0
- package/assets/logo.svg +1 -0
- package/bin/quilt.js +816 -0
- package/desktop/main.js +315 -0
- package/package.json +163 -7
- package/src/account.js +563 -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 +180 -0
- package/src/agents/common.js +21 -0
- package/src/agents/cursor-transcripts.js +170 -0
- package/src/agents/cursor.js +404 -0
- package/src/agents/index.js +69 -0
- package/src/ai-vm-signin.js +98 -0
- package/src/ai-vm.js +380 -0
- package/src/api/access.js +75 -0
- package/src/api/activity.js +181 -0
- package/src/api/agent-auth.js +130 -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 +76 -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 +1083 -0
- package/src/api/org-access.js +54 -0
- package/src/api/permissions.js +71 -0
- package/src/api/relay-cluster.js +251 -0
- package/src/api/relay-machines.js +60 -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 +157 -0
- package/src/api/routes/ai-vm.js +369 -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 +177 -0
- package/src/api/routes/relay-cluster.js +90 -0
- package/src/api/routes/relay.js +76 -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 +225 -0
- package/src/api/server.js +623 -0
- package/src/api/slugs.js +20 -0
- package/src/api/supabase-store.js +765 -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 +387 -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 +140 -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 +245 -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 +87 -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 +900 -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 +40 -0
- package/src/mcp-room.js +32 -0
- package/src/mcp.js +1261 -0
- package/src/merge3.js +158 -0
- package/src/merges.js +148 -0
- package/src/pass-source.js +176 -0
- package/src/passes.js +64 -0
- package/src/pathrules.js +127 -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 +597 -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 +137 -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 +1679 -0
- package/src/relay-metrics.js +75 -0
- package/src/relay-outbound.js +1144 -0
- package/src/relay-sync.js +719 -0
- package/src/relay-turn-sync.js +307 -0
- package/src/relay-upstream.js +934 -0
- package/src/relay-webhooks.js +111 -0
- package/src/releases.js +131 -0
- package/src/report.js +129 -0
- package/src/runner.js +306 -0
- package/src/same-edit.js +18 -0
- package/src/server.js +4187 -0
- package/src/session-access.js +170 -0
- package/src/session-name.js +15 -0
- package/src/session.js +7361 -0
- package/src/settings.js +46 -0
- package/src/setup.js +184 -0
- package/src/shell-path.js +46 -0
- package/src/status.js +199 -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 +142 -0
- package/src/turn-sync.js +191 -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 +2309 -0
- package/src/ui/app.js +542 -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 +107 -0
- package/src/ui/cloud-ai.js +221 -0
- package/src/ui/common.js +431 -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 +123 -0
- package/src/ui/git-rule.js +60 -0
- package/src/ui/home.js +1323 -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 +349 -0
- package/src/ui/loom.js +753 -0
- package/src/ui/mark.js +104 -0
- package/src/ui/merges.js +158 -0
- package/src/ui/rail-model.js +78 -0
- package/src/ui/rail.js +173 -0
- package/src/ui/releases.js +182 -0
- package/src/ui/schedule.js +156 -0
- package/src/ui/session.js +3394 -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 +276 -0
- package/src/ui/workspaces.js +542 -0
- package/src/ui-server.js +1272 -0
- package/src/update-check.js +67 -0
- package/src/upstream.js +155 -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
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// A read-only view of one shared file, with who's editing it, its claim, and
|
|
2
|
+
// freshly changed lines highlighted for a few seconds. Its Changes tab lists
|
|
3
|
+
// every change to the file with its diff (diffview.js).
|
|
4
|
+
import { esc, bytes, I, loadingNote } from './common.js'
|
|
5
|
+
import { claimFolder, claimHolder, claimTitle } from './tree.js'
|
|
6
|
+
import { historyMarkup, changeCount } from './diffview.js'
|
|
7
|
+
|
|
8
|
+
const MAX_LINES = 20000
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {HTMLElement} el
|
|
12
|
+
* @param {object} o
|
|
13
|
+
* @param {object|null} o.file response from /file, or { missing: true }
|
|
14
|
+
* @param {object|null} o.meta tree entry ({ edited, claim })
|
|
15
|
+
* @param {string} o.me
|
|
16
|
+
* @param {string|null} o.prevText previous text, to highlight what changed
|
|
17
|
+
* @param {'file'|'changes'} [o.tab] which tab is shown
|
|
18
|
+
* @param {object[]|null} [o.history] the file's changes, newest first (null: not loaded yet)
|
|
19
|
+
* @param {object} [o.people] { who, colorOf } for the Changes tab
|
|
20
|
+
* @param {object|null} [o.summary] the file's totals in this session (changes.js fileChanges)
|
|
21
|
+
*/
|
|
22
|
+
export function renderFileView (el, { path, file, meta, me, prevText, bannerOnly = false, tab = 'file', history = null, people, summary = null }) {
|
|
23
|
+
const scroller = el.querySelector('.fv-scroll')
|
|
24
|
+
const keep = scroller && scroller.dataset.path === path ? scroller.scrollTop : 0
|
|
25
|
+
|
|
26
|
+
const edited = meta && meta.edited
|
|
27
|
+
const claim = meta && meta.claim
|
|
28
|
+
const editedText = edited ? `Edited by ${edited.by === me ? 'you' : esc(edited.by)} ${agoLong(edited.ts)}` : 'Not edited recently'
|
|
29
|
+
const claimText = claim
|
|
30
|
+
? `<span class="fv-claim" title="${esc(claimTitle(claim, me))}">${I.lock}Claimed by ${esc(claimHolder(claim, me))}${claimFolder(claim.pattern) !== path ? ` (via <code>${esc(claim.pattern)}</code>)` : ''}${claim.note ? `: ${esc(claim.note)}` : ''}${claim.queue && claim.queue.length ? ` · ${claim.queue.length} waiting` : ''}</span>`
|
|
31
|
+
: '<span class="hint">Not claimed</span>'
|
|
32
|
+
let action = ''
|
|
33
|
+
if (!file || !file.missing) {
|
|
34
|
+
if (!claim) action = `<button class="btn sm" data-fv="claim">${I.lock}Claim</button>`
|
|
35
|
+
else if (claim.by === me && !(claim.queue || []).length) action = `<button class="btn sm ghost" data-fv="release" data-pattern="${esc(claim.pattern)}">Release</button>`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const banner = `<div class="fv-banner"><span class="fv-path mono">${esc(path)}</span><span class="fv-edited">${editedText}</span>${claimText}<span class="spacer"></span>${action}<span class="tag">Read-only</span></div>`
|
|
39
|
+
// Refresh who-edited/claim info without re-rendering (and un-highlighting) the code.
|
|
40
|
+
const existing = el.querySelector('.fv-banner')
|
|
41
|
+
if (bannerOnly && existing && scroller && scroller.dataset.path === path) { existing.outerHTML = banner; return }
|
|
42
|
+
|
|
43
|
+
let body
|
|
44
|
+
if (tab === 'changes') body = history ? `<div class="fv-history">${historyMarkup(history, { ...people, summary })}</div>` : '<div class="fv-note">Reading what changed…</div>'
|
|
45
|
+
else if (!file) body = `<div class="fv-note">${loadingNote()}</div>`
|
|
46
|
+
else if (file.missing) body = `<div class="fv-note">${file.deleted ? 'This file was deleted.' : 'This file isn’t in the session. It may be too large to sync, or ignored.'}</div>`
|
|
47
|
+
else if (file.binary) body = `<div class="fv-note">${I.file} Binary file · ${bytes(file.size)}. Only text files can be shown here.</div>`
|
|
48
|
+
else {
|
|
49
|
+
const lines = file.text.split('\n')
|
|
50
|
+
if (lines.length > 1 && lines[lines.length - 1] === '') lines.pop()
|
|
51
|
+
const changed = prevText != null && prevText !== file.text ? changedLines(prevText.split('\n'), lines) : new Set()
|
|
52
|
+
const shown = lines.slice(0, MAX_LINES)
|
|
53
|
+
body = `<table class="fv-code"><tbody>${shown.map((l, i) =>
|
|
54
|
+
`<tr${changed.has(i) ? ' class="chg"' : ''}><td class="ln">${i + 1}</td><td class="lt">${esc(l) || ' '}</td></tr>`).join('')}</tbody></table>
|
|
55
|
+
${lines.length > MAX_LINES ? `<div class="fv-note">Showing the first ${MAX_LINES.toLocaleString()} lines.</div>` : ''}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
el.innerHTML = `<div class="fv">${banner}${tabsMarkup(tab, history, summary)}<div class="fv-scroll" data-path="${esc(path)}" data-tab="${tab}">${body}</div></div>`
|
|
59
|
+
const s = el.querySelector('.fv-scroll')
|
|
60
|
+
if (scroller && scroller.dataset.path === path && scroller.dataset.tab !== s.dataset.tab) { s.scrollTop = 0; return } // the other tab starts at the top
|
|
61
|
+
const first = s.querySelector('tr.chg')
|
|
62
|
+
if (first && prevText != null) {
|
|
63
|
+
// Bring the change into view if it's off screen.
|
|
64
|
+
s.scrollTop = keep
|
|
65
|
+
const top = first.offsetTop
|
|
66
|
+
if (top < s.scrollTop || top > s.scrollTop + s.clientHeight - 40) s.scrollTop = Math.max(0, top - 80)
|
|
67
|
+
} else {
|
|
68
|
+
s.scrollTop = keep
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const tabsMarkup = (tab, history, summary) => `<div class="fv-tabs segmented" role="tablist" aria-label="File or its changes">
|
|
73
|
+
<button type="button" role="tab" data-fvtab="file" class="${tab === 'file' ? 'on' : ''}" aria-selected="${tab === 'file'}">File</button>
|
|
74
|
+
<button type="button" role="tab" data-fvtab="changes" class="${tab === 'changes' ? 'on' : ''}" aria-selected="${tab === 'changes'}" title="Every change to this file, with who made it and what changed">Changes${history ? ` <span class="n">${changeCount(history, summary)}</span>` : ''}</button>
|
|
75
|
+
</div>`
|
|
76
|
+
|
|
77
|
+
/** Updates the tabs (the Changes count) without re-rendering, so freshly changed lines stay lit. */
|
|
78
|
+
export function renderFileTabs (el, { tab, history, summary = null }) {
|
|
79
|
+
const tabs = el.querySelector('.fv-tabs')
|
|
80
|
+
if (tabs) tabs.outerHTML = tabsMarkup(tab, history, summary)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function agoLong (ts) {
|
|
84
|
+
const s = Math.max(0, Math.round((Date.now() - ts) / 1000))
|
|
85
|
+
if (s < 5) return 'just now'
|
|
86
|
+
if (s < 60) return `${s}s ago`
|
|
87
|
+
if (s < 3600) return `${Math.round(s / 60)}m ago`
|
|
88
|
+
if (s < 86400) return `${Math.round(s / 3600)}h ago`
|
|
89
|
+
return new Date(ts).toLocaleDateString()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Indexes of lines in `b` that are new or changed compared with `a`. */
|
|
93
|
+
export function changedLines (a, b) {
|
|
94
|
+
const out = new Set()
|
|
95
|
+
let start = 0
|
|
96
|
+
while (start < a.length && start < b.length && a[start] === b[start]) start++
|
|
97
|
+
let endA = a.length - 1
|
|
98
|
+
let endB = b.length - 1
|
|
99
|
+
while (endA >= start && endB >= start && a[endA] === b[endB]) { endA--; endB-- }
|
|
100
|
+
if (endB < start) return out // only deletions
|
|
101
|
+
const midA = a.slice(start, endA + 1)
|
|
102
|
+
const midB = b.slice(start, endB + 1)
|
|
103
|
+
if (midA.length * midB.length > 1_000_000) {
|
|
104
|
+
for (let i = start; i <= endB; i++) out.add(i)
|
|
105
|
+
return out
|
|
106
|
+
}
|
|
107
|
+
// Longest common subsequence over the changed middle section.
|
|
108
|
+
const n = midA.length
|
|
109
|
+
const m = midB.length
|
|
110
|
+
const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1))
|
|
111
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
112
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
113
|
+
dp[i][j] = midA[i] === midB[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1])
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let i = 0
|
|
117
|
+
let j = 0
|
|
118
|
+
while (j < m) {
|
|
119
|
+
if (i < n && midA[i] === midB[j]) { i++; j++ } else if (i < n && dp[i + 1][j] >= dp[i][j + 1]) i++
|
|
120
|
+
else { out.add(start + j); j++ }
|
|
121
|
+
}
|
|
122
|
+
return out
|
|
123
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Git for everyone's one control (docs/superpowers/specs/2026-10-17-git-for-everyone.md, mockup option A):
|
|
2
|
+
// off for everyone, or on with agents pushing to branches of their own or to any branch. The same
|
|
3
|
+
// segmented control in the session's settings, a workspace's and an org's GitHub settings (the app) and a
|
|
4
|
+
// team's and an org's settings (the website, which imports this file). A rule above (org, team, workspace)
|
|
5
|
+
// is a ceiling: the choices above it are greyed, with who set it and who changes it. Pure: no DOM.
|
|
6
|
+
|
|
7
|
+
export const GIT_SEGMENTS = [
|
|
8
|
+
{ value: 'off', head: 'Off', sub: 'for everyone' },
|
|
9
|
+
{ value: 'branches', head: 'On', sub: 'agents: own branches' },
|
|
10
|
+
{ value: 'any', head: 'On', sub: 'agents: any branch' }
|
|
11
|
+
]
|
|
12
|
+
const RANK = { off: 0, branches: 1, any: 2 }
|
|
13
|
+
const UP_TO = { off: 'git off', branches: 'up to own branches', any: 'up to any branch' }
|
|
14
|
+
const LEVEL = { org: 'Org', team: 'Team', workspace: 'Workspace' }
|
|
15
|
+
const FIXER = {
|
|
16
|
+
workspace: 'Its admins change that in its GitHub settings.',
|
|
17
|
+
org: 'Its admins change that in the org\'s settings.',
|
|
18
|
+
team: 'Whoever may update the org\'s teams changes that in the team\'s settings, on the website.'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** "Workspace Platform", "Org Acme", "Team Night shift" (or "The workspace" without a name). */
|
|
22
|
+
export function ruleWho (rule) {
|
|
23
|
+
const level = LEVEL[rule && rule.by] || 'Workspace'
|
|
24
|
+
return rule && rule.name ? `${level} ${rule.name}` : `The ${level.toLowerCase()}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The control's state. `value`: the level's own choice ('' when it sets nothing), or for a session what
|
|
29
|
+
* holds. `rule`: { by, name, mode } from the level above that holds, or null. `canEdit`: false greys it all.
|
|
30
|
+
*/
|
|
31
|
+
export function gitControl ({ value = '', rule = null, canEdit = true } = {}) {
|
|
32
|
+
const ceiling = rule && rule.mode in RANK ? rule.mode : null
|
|
33
|
+
const above = (v) => ceiling !== null && RANK[v] > RANK[ceiling]
|
|
34
|
+
const holds = value in RANK ? (above(value) ? ceiling : value) : ''
|
|
35
|
+
const segments = GIT_SEGMENTS.map((s) => ({ ...s, on: s.value === holds, disabled: !canEdit || above(s.value) }))
|
|
36
|
+
if (!ceiling) return { segments, rulePill: '', lockedHint: '' }
|
|
37
|
+
const who = ruleWho(rule)
|
|
38
|
+
const whoLower = who.charAt(0).toLowerCase() + who.slice(1)
|
|
39
|
+
const allows = whoLower.startsWith('the ') ? `what ${whoLower} allows` : `what the ${whoLower} allows`
|
|
40
|
+
const fixer = FIXER[rule.by] || FIXER.workspace
|
|
41
|
+
const lockedHint = ceiling === 'off'
|
|
42
|
+
? `Git on is above ${allows}: it turned git off. ${fixer}`
|
|
43
|
+
: ceiling === 'branches' ? `"Any branch" is above ${allows}. ${fixer}` : ''
|
|
44
|
+
return { segments, rulePill: `${who}: ${UP_TO[ceiling]}`, lockedHint }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The status line ("Git is on for everyone here, through …"), its first clause bold as in the mockup. */
|
|
48
|
+
export function gitStatusParts (words) {
|
|
49
|
+
const s = String(words || '')
|
|
50
|
+
const m = s.match(/^(.*?)(, | \(|: )/)
|
|
51
|
+
return m ? { strong: m[1], rest: s.slice(m[1].length) } : { strong: s, rest: '' }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]))
|
|
55
|
+
|
|
56
|
+
/** The control as the app draws it: `attr` is the data attribute each button carries its value in. */
|
|
57
|
+
export function gitSegHtml (model, { attr, label }) {
|
|
58
|
+
return `<div class="seg" role="group" aria-label="${esc(label)}">${model.segments.map((s) =>
|
|
59
|
+
`<button type="button"${s.on ? ' class="on"' : ''} ${attr}="${s.value}" aria-pressed="${s.on}"${s.disabled ? ' disabled' : ''}><b>${esc(s.head)}</b>${esc(s.sub)}</button>`).join('')}</div>`
|
|
60
|
+
}
|