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,170 @@
|
|
|
1
|
+
// What someone may do in a session: the access types people define, the grants that
|
|
2
|
+
// give someone a type in one session (narrowed, never widened), and the access that
|
|
3
|
+
// works out to. Pure and shared: the accounts API works access out and signs it into
|
|
4
|
+
// passes, the relay enforces it, and the app shows it.
|
|
5
|
+
//
|
|
6
|
+
// Two shapes:
|
|
7
|
+
// - access, as the API and passes carry it: { files: 'edit'|'view', folders, foldersExcept, talk }
|
|
8
|
+
// (empty folders means every folder);
|
|
9
|
+
// - relay access, as the relay and the app have always kept members: { role: 'editor'|'viewer',
|
|
10
|
+
// scopes, scopesExcept, talk }.
|
|
11
|
+
import { globMatcher } from './pathrules.js'
|
|
12
|
+
|
|
13
|
+
export const BUILTIN_TYPES = Object.freeze([
|
|
14
|
+
Object.freeze({ id: 'builtin:edit', name: 'Can edit', files: 'edit', folders: Object.freeze([]), talk: true, builtin: true }),
|
|
15
|
+
Object.freeze({ id: 'builtin:view', name: 'View only', files: 'view', folders: Object.freeze([]), talk: true, builtin: true })
|
|
16
|
+
])
|
|
17
|
+
export const DEFAULT_TYPE = 'builtin:edit'
|
|
18
|
+
// Where grants go when their type is deleted: the safe default.
|
|
19
|
+
export const FALLBACK_TYPE = 'builtin:view'
|
|
20
|
+
export const MAX_TYPES = 50
|
|
21
|
+
export const MAX_FOLDERS = 20
|
|
22
|
+
export const MAX_FOLDER_LENGTH = 200
|
|
23
|
+
export const TYPE_NAME_MAX = 40
|
|
24
|
+
export const TALK_REFUSED = "You can't post in this session."
|
|
25
|
+
export const BAD_FOLDER = 'Folders must be paths inside the project, like src or docs.'
|
|
26
|
+
export const TOO_MANY_FOLDERS = 'An access type can list at most 20 folders.'
|
|
27
|
+
|
|
28
|
+
// Control and format characters (zero-width, bidi overrides) never belong in a name or a folder.
|
|
29
|
+
const INVISIBLE = /[\p{Cc}\p{Cf}]/gu
|
|
30
|
+
|
|
31
|
+
export const builtinType = (id) => BUILTIN_TYPES.find((t) => t.id === id) || null
|
|
32
|
+
|
|
33
|
+
/** Folders as the relay checks them: trimmed, no leading "./" or trailing "/", each once. Throws BAD_FOLDER or TOO_MANY_FOLDERS. */
|
|
34
|
+
export function cleanFolders (input) {
|
|
35
|
+
if (input == null) return []
|
|
36
|
+
if (!Array.isArray(input)) throw new Error(BAD_FOLDER)
|
|
37
|
+
const out = []
|
|
38
|
+
for (const raw of input) {
|
|
39
|
+
if (typeof raw !== 'string') throw new Error(BAD_FOLDER)
|
|
40
|
+
const s = raw.replace(INVISIBLE, '').trim().replace(/^(\.\/)+/, '').replace(/\/+$/, '')
|
|
41
|
+
if (!s) continue
|
|
42
|
+
if (s.startsWith('/') || s.includes('\\') || s.length > MAX_FOLDER_LENGTH || /^[A-Za-z]:/.test(s)) throw new Error(BAD_FOLDER)
|
|
43
|
+
if (s.split('/').some((seg) => seg === '' || seg === '.' || seg === '..')) throw new Error(BAD_FOLDER)
|
|
44
|
+
if (!out.includes(s)) out.push(s)
|
|
45
|
+
}
|
|
46
|
+
if (out.length > MAX_FOLDERS) throw new Error(TOO_MANY_FOLDERS)
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A type's name, trimmed: 1 to 40 characters with nothing invisible. Throws otherwise. */
|
|
51
|
+
export function cleanTypeName (value) {
|
|
52
|
+
const name = [...String(value ?? '').replace(INVISIBLE, '').trim()]
|
|
53
|
+
if (!name.length || name.length > TYPE_NAME_MAX) throw new Error('Give the access type a name of 1 to 40 characters.')
|
|
54
|
+
return name.join('')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A new or changed access type's fields, checked. `partial` keeps fields that weren't sent out of the result. */
|
|
58
|
+
export function cleanTypeFields (body, { partial = false } = {}) {
|
|
59
|
+
const b = body || {}
|
|
60
|
+
const out = {}
|
|
61
|
+
if (!partial || b.name !== undefined) out.name = cleanTypeName(b.name)
|
|
62
|
+
if (!partial || b.files !== undefined) {
|
|
63
|
+
if (b.files !== 'edit' && b.files !== 'view') throw new Error('files must be edit or view.')
|
|
64
|
+
out.files = b.files
|
|
65
|
+
}
|
|
66
|
+
if (!partial || b.folders !== undefined) out.folders = cleanFolders(b.folders ?? [])
|
|
67
|
+
if (!partial || b.talk !== undefined) {
|
|
68
|
+
if (b.talk !== undefined && typeof b.talk !== 'boolean') throw new Error('talk must be true or false.')
|
|
69
|
+
out.talk = b.talk !== false
|
|
70
|
+
}
|
|
71
|
+
return out
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** How a grant narrows its type: { files?: 'view', foldersRemove?: [...], talk?: false }. Anything else is dropped. */
|
|
75
|
+
export function cleanTighten (raw) {
|
|
76
|
+
const t = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {}
|
|
77
|
+
const out = {}
|
|
78
|
+
if (t.files === 'view') out.files = 'view'
|
|
79
|
+
const remove = cleanFolders(t.foldersRemove ?? [])
|
|
80
|
+
if (remove.length) out.foldersRemove = remove
|
|
81
|
+
if (t.talk === false) out.talk = false
|
|
82
|
+
return out
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* What a grant comes to: its type, narrowed. A type limited to folders loses the ones
|
|
87
|
+
* removed; removing from "all folders" (or a folder the type doesn't list) is kept as
|
|
88
|
+
* foldersExcept. Removing every folder a limited type has leaves nothing to change: view.
|
|
89
|
+
*/
|
|
90
|
+
export function effectiveAccess (type, tighten = {}) {
|
|
91
|
+
const t = tighten || {}
|
|
92
|
+
const typeFolders = type.folders || []
|
|
93
|
+
const removed = t.foldersRemove || []
|
|
94
|
+
let files = type.files === 'view' || t.files === 'view' ? 'view' : 'edit'
|
|
95
|
+
let folders = typeFolders.filter((f) => !removed.includes(f))
|
|
96
|
+
let foldersExcept = removed.filter((f) => !typeFolders.includes(f))
|
|
97
|
+
if (typeFolders.length && !folders.length) { files = 'view'; folders = []; foldersExcept = [] }
|
|
98
|
+
return { files, folders, foldersExcept, talk: type.talk !== false && t.talk !== false }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const inside = (rel, folder) => globMatcher(folder)(rel)
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* What both allow. The relay uses it so an owner's live change can only narrow what a
|
|
105
|
+
* person's pass allows. Folders: those of one that lie inside the other's; none left
|
|
106
|
+
* means nothing to change (view). Exceptions and "no posting" add up.
|
|
107
|
+
*/
|
|
108
|
+
export function narrowAccess (a, b) {
|
|
109
|
+
let files = a.files === 'view' || b.files === 'view' ? 'view' : 'edit'
|
|
110
|
+
const fa = a.folders || []
|
|
111
|
+
const fb = b.folders || []
|
|
112
|
+
let folders
|
|
113
|
+
if (!fa.length) folders = [...fb]
|
|
114
|
+
else if (!fb.length) folders = [...fa]
|
|
115
|
+
else {
|
|
116
|
+
folders = [...new Set([...fa.filter((x) => fb.some((y) => inside(x, y))), ...fb.filter((y) => fa.some((x) => inside(y, x)))])]
|
|
117
|
+
if (!folders.length) files = 'view'
|
|
118
|
+
}
|
|
119
|
+
const foldersExcept = [...new Set([...(a.foldersExcept || []), ...(b.foldersExcept || [])])]
|
|
120
|
+
return { files, folders, foldersExcept, talk: a.talk !== false && b.talk !== false }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Access from anywhere it isn't trusted to be well formed (a pass, an owner's request): checked, or null. */
|
|
124
|
+
export function cleanAccess (raw) {
|
|
125
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null
|
|
126
|
+
if (raw.files !== 'edit' && raw.files !== 'view') return null
|
|
127
|
+
try {
|
|
128
|
+
return { files: raw.files, folders: cleanFolders(raw.folders ?? []), foldersExcept: cleanFolders(raw.foldersExcept ?? []), talk: raw.talk !== false }
|
|
129
|
+
} catch { return null }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The relay's shape: { role, scopes, scopesExcept, talk }. */
|
|
133
|
+
export function relayAccess (access) {
|
|
134
|
+
return { role: access.files === 'view' ? 'viewer' : 'editor', scopes: [...(access.folders || [])], scopesExcept: [...(access.foldersExcept || [])], talk: access.talk !== false }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Back from the relay's shape (a member saved before access types has no exceptions and may talk). */
|
|
138
|
+
export function fromRelay (m) {
|
|
139
|
+
return { files: m.role === 'viewer' ? 'view' : 'edit', folders: [...(m.scopes || [])], foldersExcept: [...(m.scopesExcept || [])], talk: m.talk !== false }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** May someone with this relay access change `rel`? */
|
|
143
|
+
export function mayChange (a, rel) {
|
|
144
|
+
if (!a || a.role === 'viewer') return false
|
|
145
|
+
if (a.scopes && a.scopes.length && !a.scopes.some((s) => inside(rel, s))) return false
|
|
146
|
+
return !(a.scopesExcept || []).some((s) => inside(rel, s))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Why someone with this relay access may not change `rel`, in words, or null. */
|
|
150
|
+
export function changeRefusal (a, rel) {
|
|
151
|
+
if (!a || a.role === 'viewer') return 'you can only view this session'
|
|
152
|
+
if (a.scopes && a.scopes.length && !a.scopes.some((s) => inside(rel, s))) return `you may only change files in ${a.scopes.join(', ')}`
|
|
153
|
+
const except = (a.scopesExcept || []).find((s) => inside(rel, s))
|
|
154
|
+
return except ? `you may not change files in ${except}` : null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Same access? (Folder order doesn't matter.) */
|
|
158
|
+
export function sameAccess (a, b) {
|
|
159
|
+
const set = (x) => [...(x || [])].sort().join('\n')
|
|
160
|
+
return a.files === b.files && set(a.folders) === set(b.folders) && set(a.foldersExcept) === set(b.foldersExcept) && (a.talk !== false) === (b.talk !== false)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** "Can edit · src, docs · except src/secrets · no posting": one line for menus and lists. */
|
|
164
|
+
export function describeAccess (a) {
|
|
165
|
+
const parts = [a.files === 'view' ? 'View only' : 'Can edit']
|
|
166
|
+
if (a.files !== 'view') parts.push(a.folders && a.folders.length ? a.folders.join(', ') : 'all folders')
|
|
167
|
+
if (a.files !== 'view' && a.foldersExcept && a.foldersExcept.length) parts.push(`except ${a.foldersExcept.join(', ')}`)
|
|
168
|
+
if (a.talk === false) parts.push('no posting')
|
|
169
|
+
return parts.join(' · ')
|
|
170
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Session names: the owner's app names a session after its folder, and the owner can
|
|
2
|
+
// rename it. The relay and the accounts API both check names with this.
|
|
3
|
+
export const SESSION_NAME_MAX = 80
|
|
4
|
+
// C0 and C1 control characters, including newlines and tabs.
|
|
5
|
+
const CONTROL = /[\u0000-\u001f\u007f-\u009f]/
|
|
6
|
+
|
|
7
|
+
/** The name trimmed, or null unless it is 1 to 80 characters with no control characters. */
|
|
8
|
+
export function cleanSessionName (value) {
|
|
9
|
+
if (typeof value !== 'string') return null
|
|
10
|
+
const name = value.trim()
|
|
11
|
+
if (!name || [...name].length > SESSION_NAME_MAX || CONTROL.test(name)) return null
|
|
12
|
+
return name
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const BAD_SESSION_NAME = 'Give the session a name of 1 to 80 characters.'
|