heyquilt 0.3.121 → 0.3.138
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/RELEASES.md +292 -17
- package/bin/quilt.js +72 -5
- package/desktop/main.js +16 -2
- package/package.json +3 -1
- package/src/agent-task-workflow.js +2 -2
- package/src/agents/common.js +13 -0
- package/src/ai-vm.js +9 -1
- package/src/api/activity.js +5 -0
- package/src/api/memory-store.js +21 -10
- package/src/api/notify.js +134 -0
- package/src/api/routes/browsers.js +29 -0
- package/src/api/routes/github.js +0 -13
- package/src/api/routes/relay.js +26 -3
- package/src/api/routes/seam-connect.js +103 -0
- package/src/api/routes/session-migrate.js +39 -0
- package/src/api/routes/sessions.js +73 -1
- package/src/api/server.js +46 -12
- package/src/api/supabase-store.js +53 -4
- package/src/autowork.js +182 -16
- package/src/connection.js +39 -9
- package/src/control.js +139 -13
- package/src/force-date.js +27 -0
- package/src/fsutil.js +13 -3
- package/src/gitstate.js +38 -2
- package/src/history.js +25 -1
- package/src/mcp.js +93 -7
- package/src/merge3.js +11 -3
- package/src/now-summary.js +113 -0
- package/src/pathrules.js +12 -0
- package/src/protocol.js +10 -2
- package/src/quilt-default.js +84 -0
- package/src/quilt-history.js +106 -0
- package/src/quilt-remote.js +78 -0
- package/src/relay-commit.js +5 -0
- package/src/relay-lease.js +6 -2
- package/src/relay-mcp.js +172 -8
- package/src/relay-outbound.js +1 -1
- package/src/relay-prune.js +4 -1
- package/src/release-words.js +2 -2
- package/src/runner.js +20 -7
- package/src/seam-access.js +97 -0
- package/src/seam-claims.js +53 -0
- package/src/seam-url.js +15 -0
- package/src/server.js +1767 -33
- package/src/session-follow.js +515 -0
- package/src/session-layout3.js +466 -0
- package/src/session-migrate.js +123 -0
- package/src/session-quilt.js +145 -0
- package/src/session-seam.js +284 -0
- package/src/session.js +899 -46
- package/src/source-live/bring-in.js +283 -0
- package/src/source-live/live-block.js +507 -35
- package/src/source-live/live-square.js +17 -6
- package/src/status.js +5 -0
- package/src/ui/agent-guide.js +1 -1
- package/src/ui/app.css +85 -20
- package/src/ui/app.js +4 -1
- package/src/ui/chat.js +9 -3
- package/src/ui/cloud-ai-model.js +13 -0
- package/src/ui/cloud-ai.js +10 -2
- package/src/ui/common.js +6 -2
- package/src/ui/fileview.js +20 -7
- package/src/ui/force-words.js +19 -0
- package/src/ui/git-rule.js +9 -0
- package/src/ui/home.js +0 -147
- package/src/ui/loom-model.js +15 -1
- package/src/ui/quiltview.js +213 -0
- package/src/ui/seam-model.js +251 -0
- package/src/ui/session.js +413 -31
- package/src/ui/voice-call.js +83 -14
- package/src/ui/voice-model.js +39 -0
- package/src/ui/voice.js +167 -17
- package/src/ui-server.js +80 -28
- package/src/update-check.js +25 -1
- package/src/voice.js +13 -6
- package/src/git.js +0 -147
- package/src/quilts/AGENTS.md +0 -1
- package/src/quilts/CLAUDE.md +0 -1
- package/src/quilts/archiver.js +0 -175
- package/src/quilts/autorevert.js +0 -49
- package/src/quilts/bisect-run.js +0 -31
- package/src/quilts/blame.js +0 -103
- package/src/quilts/block.js +0 -347
- package/src/quilts/bucket.js +0 -25
- package/src/quilts/cbor.js +0 -92
- package/src/quilts/claims.js +0 -144
- package/src/quilts/cli.js +0 -449
- package/src/quilts/client.js +0 -128
- package/src/quilts/conflict-format.js +0 -190
- package/src/quilts/describe.js +0 -266
- package/src/quilts/dogfood.js +0 -82
- package/src/quilts/errors.js +0 -8
- package/src/quilts/events.js +0 -160
- package/src/quilts/export-git.js +0 -130
- package/src/quilts/export.js +0 -81
- package/src/quilts/fastmerge.js +0 -41
- package/src/quilts/hash.js +0 -44
- package/src/quilts/history.js +0 -266
- package/src/quilts/ids.js +0 -12
- package/src/quilts/import-git.js +0 -291
- package/src/quilts/import.js +0 -134
- package/src/quilts/index.js +0 -72
- package/src/quilts/limits.js +0 -67
- package/src/quilts/lines.js +0 -49
- package/src/quilts/live.js +0 -50
- package/src/quilts/merge-smart.js +0 -114
- package/src/quilts/merge.js +0 -486
- package/src/quilts/objects.js +0 -144
- package/src/quilts/pack.js +0 -118
- package/src/quilts/records.js +0 -211
- package/src/quilts/resolutions.js +0 -69
- package/src/quilts/reviews.js +0 -195
- package/src/quilts/routes.js +0 -126
- package/src/quilts/service.js +0 -923
- package/src/quilts/setup.js +0 -34
- package/src/quilts/signing.js +0 -149
- package/src/quilts/squares.js +0 -239
- package/src/quilts/store-memory.js +0 -683
- package/src/quilts/store-rpc.js +0 -79
- package/src/quilts/store-supabase.js +0 -13
- package/src/quilts/tags.js +0 -64
- package/src/quilts/textdiff.js +0 -96
- package/src/quilts/tokens.js +0 -66
- package/src/quilts/tools.js +0 -375
- package/src/quilts/tree.js +0 -351
- package/src/quilts/webhook.js +0 -116
- package/src/quilts/window.js +0 -119
- package/src/quilts/xdiff.js +0 -435
- package/src/ui/branches.js +0 -111
package/src/quilts/cli.js
DELETED
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
// `seam <verb>`, Quilt source control's own command (the @heyquilt/seam package, seam/): git's
|
|
2
|
-
// command names and flags (plans/quilt-source-control/12), answered by the server. Inside a block
|
|
3
|
-
// folder (.seam/block.json) the quilt and square come from the folder; elsewhere name them with
|
|
4
|
-
// --quilt and --square. It is not part of the Quilt app's `quilt` command.
|
|
5
|
-
import path from 'node:path'
|
|
6
|
-
import fs from 'node:fs'
|
|
7
|
-
import os from 'node:os'
|
|
8
|
-
import { quiltClient } from './client.js'
|
|
9
|
-
import { findBlock, checkout, status as blockStatus, commitFolder, amendFolder, syncTo, localDiff, saveBlock, restore, reset, clean, localChanges } from './block.js'
|
|
10
|
-
import { describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeResolutions, describeLiveSet, describeMergeBase, describeGrep, describeBisect, describeCheck, describeReviews, describeReviewed, describeSquareCheck, describePolicy, describeKeys, describeTokens, describeNewToken } from './describe.js'
|
|
11
|
-
import { signWithLocalKey, localKey, makeLocalKey, signingKeyFile } from './signing.js'
|
|
12
|
-
|
|
13
|
-
export const API_URL = 'https://api.heyquilt.com'
|
|
14
|
-
|
|
15
|
-
/** The verbs `seam` answers. */
|
|
16
|
-
export const SEAM_VERBS = new Set(['quilts', 'square', 'checkout', 'status', 'commit', 'log', 'show', 'diff', 'merge', 'rebase', 'revert', 'cherry-pick', 'blame', 'resolve', 'cat', 'ls', 'import', 'export', 'events', 'tag', 'release', 'line', 'restore', 'reset', 'clean', 'live', 'bisect', 'grep', 'describe', 'merge-base', 'claim', 'unclaim', 'check', 'resolutions', 'review', 'sign', 'key', 'token'])
|
|
17
|
-
|
|
18
|
-
const VALUE_FLAGS = new Set(['-m', '--quilts', '--message', '-n', '--max-count', '--into', '--onto', '--on-conflict', '--quilt', '--square', '--dir', '--line', '--task', '--level', '--title', '--slug', '--format', '--expect-tip', '--side', '--file', '--batch', '--to', '--note', '--after', '--kinds', '--webhook', '--secret', '--branch', '--from', '-S', '--run', '--marks', '--patch', '--on', '--path', '--checks', '--name', '--public-key'])
|
|
19
|
-
|
|
20
|
-
export function parse (argv) {
|
|
21
|
-
const pos = []; const flags = {}; const paths = []
|
|
22
|
-
let rest = false
|
|
23
|
-
for (let i = 0; i < argv.length; i++) {
|
|
24
|
-
const a = argv[i]
|
|
25
|
-
if (rest) { paths.push(a); continue }
|
|
26
|
-
if (a === '--') { rest = true; continue }
|
|
27
|
-
const eq = a.match(/^(--[\w-]+)=(.*)$/)
|
|
28
|
-
if (eq) { flags[eq[1]] = eq[2]; continue }
|
|
29
|
-
if (VALUE_FLAGS.has(a)) { flags[a] = argv[++i]; continue }
|
|
30
|
-
if (/^-n\d+$/.test(a)) { flags['-n'] = a.slice(2); continue }
|
|
31
|
-
if (a.startsWith('-') && a.length > 1) { flags[a] = true; continue }
|
|
32
|
-
pos.push(a)
|
|
33
|
-
}
|
|
34
|
-
return { pos, flags, paths }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** The client for a block (or none): SEAM_API_URL / SEAM_TOKEN first, else the block's server and `token` (seam's own sign-in). */
|
|
38
|
-
export function connect (block, token) {
|
|
39
|
-
const api = process.env.SEAM_API_URL || block?.api || API_URL
|
|
40
|
-
const key = process.env.SEAM_TOKEN || token
|
|
41
|
-
if (!key) throw new Error('Not signed in: run seam login, or set SEAM_TOKEN to an agent key.')
|
|
42
|
-
return { client: quiltClient({ api, token: key }), api }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Runs one verb; returns the text to print (or JSON with --json). `token` is the signed-in person's, when there is one. */
|
|
46
|
-
export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, out = (s) => process.stdout.write(s.endsWith('\n') ? s : s + '\n') } = {}) {
|
|
47
|
-
const { pos, flags, paths } = parse(argv)
|
|
48
|
-
const found = findBlock(cwd)
|
|
49
|
-
const block = found?.block
|
|
50
|
-
const { client, api } = connect(block, token)
|
|
51
|
-
const quilt = flags['--quilt'] || block?.quilt
|
|
52
|
-
const needQuilt = () => { if (!quilt) throw new Error('Name the quilt with --quilt <slug or id> (or run this inside a block folder).'); return quilt }
|
|
53
|
-
const needSquare = () => { const s = flags['--square'] || block?.square; if (!s) throw new Error('No square here: run this in a block folder, or pass --square sq:<id>.'); return s }
|
|
54
|
-
const json = !!flags['--json']
|
|
55
|
-
const print = (data, text) => out(json ? JSON.stringify(data, null, 2) : text)
|
|
56
|
-
const message = flags['-m'] || flags['--message']
|
|
57
|
-
|
|
58
|
-
switch (cmd) {
|
|
59
|
-
case 'quilts': {
|
|
60
|
-
if (pos[0] === 'new') { const q = await client.createQuilt({ slug: pos[1], name: flags['--title'] }); return print(q, `Made quilt ${q.slug} (${q.id}).`) }
|
|
61
|
-
if (pos[0] === 'add') { const r = await client.addMember(needQuilt(), { member_id: pos[1], level: flags['--level'] || 'write' }); return print(r, `${r.member_id}: ${r.level || 'removed'}`) }
|
|
62
|
-
if (pos[0] === 'show') { const q = await client.quilt(pos[1] || needQuilt()); return print(q, `${q.slug} (${q.id})\n${q.lines.map((l) => ` ${l.name}: ${l.patch_count} patches, tip ${l.tip ? l.tip.slice(0, 12) : '(empty)'}`).join('\n')}`) }
|
|
63
|
-
const list = await client.quilts()
|
|
64
|
-
return print(list, list.length ? list.map((q) => `${q.slug} ${q.id} (${q.level})`).join('\n') : 'No quilts yet.')
|
|
65
|
-
}
|
|
66
|
-
case 'line': {
|
|
67
|
-
// seam line [list] | new release/<name> [--from <rev>] | freeze <line> | unfreeze <line>
|
|
68
|
-
const sub = pos[0] || 'list'
|
|
69
|
-
const describeLine = (l) => `${l.name}: ${l.patch_count} patches, tip ${l.tip ? l.tip.slice(0, 12) : '(empty)'}${l.frozen ? ', frozen' : ''}`
|
|
70
|
-
if (sub === 'list') { const ls = await client.lines(needQuilt()); return print(ls, ls.map(describeLine).join('\n')) }
|
|
71
|
-
if (sub === 'new') {
|
|
72
|
-
if (!pos[1]) throw new Error('Name the line: seam line new release/<name> [--from <rev>]')
|
|
73
|
-
const l = await client.newLine(needQuilt(), { name: pos[1], from: flags['--from'] })
|
|
74
|
-
return print(l, `Made line ${describeLine(l)}.\nOpen a square on it with: seam square open "<title>" --line ${l.name}`)
|
|
75
|
-
}
|
|
76
|
-
if (sub === 'freeze' || sub === 'unfreeze') {
|
|
77
|
-
if (!pos[1]) throw new Error(`Which line? seam line ${sub} <line>`)
|
|
78
|
-
const l = await (sub === 'freeze' ? client.freezeLine : client.unfreezeLine)(needQuilt(), pos[1])
|
|
79
|
-
return print(l, sub === 'freeze' ? `${l.name} is frozen: merges into it are refused until seam line unfreeze ${l.name}.` : `${l.name} takes merges again.`)
|
|
80
|
-
}
|
|
81
|
-
if (sub === 'require') {
|
|
82
|
-
if (!pos[1]) throw new Error('Which line? seam line require <line> [<approvals>] [--checks ci,lint]')
|
|
83
|
-
const checks = flags['--checks'] === undefined ? undefined : String(flags['--checks']).split(',').map((c) => c.trim()).filter(Boolean)
|
|
84
|
-
if (pos[2] === undefined && checks === undefined) { const p = await client.linePolicy(needQuilt(), pos[1]); return print(p, describePolicy(p)) }
|
|
85
|
-
const p = await client.setLinePolicy(needQuilt(), pos[1], { ...(pos[2] !== undefined ? { approvals: Number(pos[2]) } : {}), ...(checks ? { checks } : {}) })
|
|
86
|
-
return print(p, describePolicy(p))
|
|
87
|
-
}
|
|
88
|
-
throw new Error('seam line [list] | new release/<name> [--from <rev>] | freeze <line> | unfreeze <line> | require <line> [<approvals>] [--checks ci,lint]')
|
|
89
|
-
}
|
|
90
|
-
case 'tag':
|
|
91
|
-
case 'release': {
|
|
92
|
-
// seam tag [<name> [rev]] | seam release [<name> [rev] -m <notes>]: no name lists them.
|
|
93
|
-
const where = (x) => `${x.short}${x.patch ? ` (${x.patch.line}:${x.patch.patch_no})` : ''}`
|
|
94
|
-
if (!pos[0]) {
|
|
95
|
-
if (cmd === 'tag') { const ts = await client.tags(needQuilt()); return print(ts, ts.length ? ts.map(describeTag).join('\n') : 'No tags yet.') }
|
|
96
|
-
const rs = await client.releases(needQuilt())
|
|
97
|
-
return print(rs, rs.length ? rs.map(describeRelease).join('\n') : 'No releases yet.')
|
|
98
|
-
}
|
|
99
|
-
if (cmd === 'tag') { const x = await client.createTag(needQuilt(), { name: pos[0], rev: pos[1] }); return print(x, `Tagged ${x.name} at ${where(x)}. Read it as tag:${x.name}.`) }
|
|
100
|
-
const x = await client.createRelease(needQuilt(), { name: pos[0], rev: pos[1], notes: flags['-m'] ?? flags['--message'] ?? '' })
|
|
101
|
-
return print(x, `Released ${x.name} at ${where(x)}. Read it as tag:${x.name}.`)
|
|
102
|
-
}
|
|
103
|
-
case 'events': {
|
|
104
|
-
// seam events [--after n] [--kinds a,b] | --webhook <url> [--secret s] [--kinds a,b] | --webhook off
|
|
105
|
-
const kinds = flags['--kinds'] ? String(flags['--kinds']).split(',') : undefined
|
|
106
|
-
if (flags['--webhook']) {
|
|
107
|
-
const off = flags['--webhook'] === 'off'
|
|
108
|
-
const r = await client.subscribe(needQuilt(), off ? { url: null } : { url: flags['--webhook'], secret: flags['--secret'], events: kinds })
|
|
109
|
-
return print(r, off ? 'Webhook removed.' : `Events (${r.events.join(', ')}) now POST to ${r.url}${r.secret ? `\nSigning secret (shown once): ${r.secret}` : ''}`)
|
|
110
|
-
}
|
|
111
|
-
const r = await client.events(needQuilt(), { after: flags['--after'], kinds })
|
|
112
|
-
return print(r, describeEvents(r))
|
|
113
|
-
}
|
|
114
|
-
case 'square': {
|
|
115
|
-
const sub = pos[0] || 'list'
|
|
116
|
-
if (sub === 'open') {
|
|
117
|
-
const sq = await client.openSquare(needQuilt(), { title: pos.slice(1).join(' ') || flags['--title'] || 'untitled', line: flags['--line'], task_id: flags['--task'], ...(flags['--on'] ? { on: flags['--on'] } : {}) })
|
|
118
|
-
let text = describeSquare(sq)
|
|
119
|
-
if (flags['--dir']) {
|
|
120
|
-
const dir = path.resolve(cwd, flags['--dir'])
|
|
121
|
-
await checkout(client, { quilt: needQuilt(), square: sq.id, paths, dir, api })
|
|
122
|
-
text += `\nChecked out into ${dir}.`
|
|
123
|
-
}
|
|
124
|
-
return print(sq, text)
|
|
125
|
-
}
|
|
126
|
-
// seam square handoff [sq] --to <member> [--note text] | abandon [sq] | reopen [sq]
|
|
127
|
-
if (sub === 'handoff') { const sq = await client.handoff(needQuilt(), pos[1] || needSquare(), { to: flags['--to'], note: flags['--note'] }); return print(sq, `Handed ${sq.short || sq.id} to ${sq.holder}.`) }
|
|
128
|
-
if (sub === 'abandon') { const sq = await client.abandon(needQuilt(), pos[1] || needSquare()); return print(sq, `Abandoned ${sq.short || sq.id}; seam square reopen brings it back.`) }
|
|
129
|
-
if (sub === 'reopen') { const sq = await client.reopen(needQuilt(), pos[1] || needSquare()); return print(sq, `Reopened ${sq.short || sq.id}; you hold it.`) }
|
|
130
|
-
// seam square fold [sq] --into sq:<id> | split [sq] <paths,...> <paths,...> [--title t]
|
|
131
|
-
if (sub === 'fold') {
|
|
132
|
-
if (!flags['--into']) throw new Error('Fold into which square? seam square fold [sq:from] --into sq:<id>')
|
|
133
|
-
const r = await client.fold(needQuilt(), pos[1] || needSquare(), { into: flags['--into'], message })
|
|
134
|
-
return print(r, describeWrite(r, `Folded ${r.folded.short} into ${r.square.short}:`))
|
|
135
|
-
}
|
|
136
|
-
if (sub === 'split') {
|
|
137
|
-
const own = pos[1] && pos[1].startsWith('sq:')
|
|
138
|
-
const groups = pos.slice(own ? 2 : 1).map((g) => g.split(',').filter(Boolean))
|
|
139
|
-
if (!groups.length) throw new Error('Split by which paths? seam square split [sq] docs,README.md src (one group per new square)')
|
|
140
|
-
const r = await client.split(needQuilt(), own ? pos[1] : needSquare(), { by: 'paths', groups, ...(flags['--title'] ? { titles: [flags['--title']] } : {}) })
|
|
141
|
-
const text = [`Kept in ${r.square.short}: ${r.kept.join(', ') || '(nothing)'}`, ...r.squares.map((q) => `New ${describeSquare(q)}: ${q.paths.join(', ')}`)].join('\n')
|
|
142
|
-
if (found && block.square === r.square.id) { const st = await syncTo(client, found.dir, block, r.square.head, { square: r.square }); return print(r, `${text}\n${st.written} file(s) updated in the folder.`) }
|
|
143
|
-
return print(r, text)
|
|
144
|
-
}
|
|
145
|
-
if (sub === 'show') { const sq = await client.square(needQuilt(), pos[1] || needSquare()); return print(sq, describeSquareStatus(sq)) }
|
|
146
|
-
const list = await client.squares(needQuilt(), { state: flags['--all'] ? undefined : (flags['--state'] || 'open'), mine: !!flags['--mine'] })
|
|
147
|
-
return print(list, list.length ? list.map(describeSquare).join('\n') : 'No open squares.')
|
|
148
|
-
}
|
|
149
|
-
case 'checkout': {
|
|
150
|
-
const target = pos[0]
|
|
151
|
-
if (!target) throw new Error('seam checkout <sq:id | revision> [--dir folder] [-- paths]')
|
|
152
|
-
const isSquare = target.startsWith('sq:') && !/:(base|live)$|~\d+$/.test(target)
|
|
153
|
-
if (found && !flags['--dir']) {
|
|
154
|
-
// Switch this block to another square or revision: only from a clean folder.
|
|
155
|
-
const st = await blockStatus(client, found.dir, block)
|
|
156
|
-
if (st.changes.length) throw new Error(`This folder has ${st.changes.length} uncommitted change(s); commit them first.`)
|
|
157
|
-
const sq = isSquare ? await client.square(block.quilt, target) : null
|
|
158
|
-
const to = sq ? (sq.head || sq.base) : (await client.show(block.quilt, target, { format: 'none' })).commit.id
|
|
159
|
-
const r = await syncTo(client, found.dir, block, to, { square: sq })
|
|
160
|
-
if (!sq) { block.square = null; block.square_short = null; block.head = null; saveBlock(found.dir, block) }
|
|
161
|
-
return print(r, `Switched to ${sq ? `${sq.short} "${sq.title}"` : target} (${r.written} file(s) written).`)
|
|
162
|
-
}
|
|
163
|
-
const dir = path.resolve(cwd, flags['--dir'] || (isSquare ? target.replace(':', '-') : 'seam-checkout'))
|
|
164
|
-
const r = await checkout(client, { quilt: needQuilt(), ...(isSquare ? { square: target } : { rev: target }), paths, dir, api })
|
|
165
|
-
return print({ dir, files: r.files, block: { ...r.block, files: undefined } }, `Checked out ${r.files} file(s) of ${isSquare ? target : target} under ${r.block.paths.map((p) => p || '/').join(', ')} into ${dir}.`)
|
|
166
|
-
}
|
|
167
|
-
case 'status': {
|
|
168
|
-
if (!found) throw new Error('Not in a block folder (no .seam/block.json here or above).')
|
|
169
|
-
const st = await blockStatus(client, found.dir, block)
|
|
170
|
-
return print(st, describeStatus(st))
|
|
171
|
-
}
|
|
172
|
-
case 'review': {
|
|
173
|
-
// seam review [sq] (the reviews and where the square stands)
|
|
174
|
-
// seam review approve|request-changes|comment|dismiss [sq] [-m <text>] [--path <file> [--line <n>]]
|
|
175
|
-
const VERDICT = { approve: 'approve', 'request-changes': 'request_changes', comment: 'comment', dismiss: 'dismiss' }
|
|
176
|
-
const kind = VERDICT[pos[0]]
|
|
177
|
-
const sq = (kind ? pos[1] : pos[0]) || needSquare()
|
|
178
|
-
if (!kind) { const r = await client.reviews(needQuilt(), sq); return print(r, describeReviews(r)) }
|
|
179
|
-
const r = await client.review(needQuilt(), sq, { kind, body: message || '', ...(flags['--path'] ? { path: flags['--path'] } : {}), ...(flags['--line'] ? { line: Number(flags['--line']) } : {}) })
|
|
180
|
-
return print(r, describeReviewed(r))
|
|
181
|
-
}
|
|
182
|
-
case 'claim': {
|
|
183
|
-
// seam claim (lists the quilt's claims) | seam claim <pattern>... [--note <why>] (claims them for your square)
|
|
184
|
-
if (!pos.length) { const cs = await client.claims(needQuilt(), flags['--square']); return print(cs, describeClaims(cs)) }
|
|
185
|
-
const sq = needSquare()
|
|
186
|
-
const made = []
|
|
187
|
-
for (const p of pos) made.push(await client.claim(needQuilt(), sq, { pattern: p, note: flags['--note'] || message }))
|
|
188
|
-
return print(made, made.map((c) => `Claimed ${c.pattern} for ${c.square}: writes to it from other squares on ${c.line} are refused until the square merges or you run seam unclaim ${c.pattern}.`).join('\n'))
|
|
189
|
-
}
|
|
190
|
-
case 'check': {
|
|
191
|
-
// seam check <name> red|green|flaky [--line main] [--patch n] | seam check <name> green|red --square <sq>
|
|
192
|
-
const [name, status] = pos
|
|
193
|
-
if (!name || !['red', 'green', 'flaky'].includes(status)) throw new Error('seam check <name> red|green|flaky [--line <line>] [--patch <n>]')
|
|
194
|
-
if (flags['--square']) {
|
|
195
|
-
// seam check <name> green|red --square <sq>: a result for a square's latest commit (a line may require it before a merge)
|
|
196
|
-
const r = await client.reportSquareCheck(needQuilt(), flags['--square'], { check: name, status })
|
|
197
|
-
return print(r, describeSquareCheck(r))
|
|
198
|
-
}
|
|
199
|
-
const r = await client.reportCheck(needQuilt(), flags['--line'] || 'main', { check: name, status, ...(flags['--patch'] != null ? { patch: Number(flags['--patch']) } : {}) })
|
|
200
|
-
return print(r, describeCheck(r))
|
|
201
|
-
}
|
|
202
|
-
case 'unclaim': {
|
|
203
|
-
// seam unclaim <pattern|claim id>...
|
|
204
|
-
if (!pos.length) throw new Error('Which claim? seam unclaim <pattern>')
|
|
205
|
-
const sq = needSquare()
|
|
206
|
-
const out = []
|
|
207
|
-
for (const p of pos) out.push(await client.unclaim(needQuilt(), sq, p))
|
|
208
|
-
return print(out, out.map((r) => `Let go of ${r.released.pattern}.`).join('\n'))
|
|
209
|
-
}
|
|
210
|
-
case 'live': {
|
|
211
|
-
// seam live [sq] | live share (a block folder's uncommitted edits) | live clear
|
|
212
|
-
const sub = pos[0]
|
|
213
|
-
if (sub === 'share' || sub === 'clear') {
|
|
214
|
-
const sqRef = flags['--square'] || block?.square
|
|
215
|
-
if (!sqRef) throw new Error(`seam live ${sub} works in a block folder on a square (or pass --square sq:<id>).`)
|
|
216
|
-
if (sub === 'share' && !found) throw new Error('seam live share sends a block folder\'s uncommitted edits: run it in a folder made by seam checkout.')
|
|
217
|
-
const changes = sub === 'clear' ? [] : localChanges(found.dir, block).map((c) => c.status === 'deleted' ? { path: c.path, op: 'delete' } : { path: c.path, op: 'put', content: c.bytes.toString('base64'), mode: c.mode })
|
|
218
|
-
const r = await client.setLive(needQuilt(), sqRef, { changes })
|
|
219
|
-
return print(r, describeLiveSet(r))
|
|
220
|
-
}
|
|
221
|
-
const r = await client.live(needQuilt(), sub || needSquare())
|
|
222
|
-
return print(r, describeLive(r))
|
|
223
|
-
}
|
|
224
|
-
case 'restore':
|
|
225
|
-
case 'reset':
|
|
226
|
-
case 'clean': {
|
|
227
|
-
if (!found) throw new Error(`seam ${cmd} works on a block folder; run it in a folder made by seam checkout.`)
|
|
228
|
-
const files = [...(cmd === 'reset' ? pos.slice(1) : pos), ...paths].map((p) => path.resolve(cwd, p))
|
|
229
|
-
if (cmd === 'restore') { const r = await restore(client, found.dir, block, { paths: files }); return print(r, describeRestore(r)) }
|
|
230
|
-
if (cmd === 'reset') { const r = await reset(client, found.dir, block, { rev: pos[0] }); return print(r, describeReset(r, block)) }
|
|
231
|
-
const r = clean(found.dir, block, { paths: files.length ? files : [cwd], dryRun: !flags['-f'] && !flags['--force'] })
|
|
232
|
-
return print(r, describeClean(r, 'Run it again as seam clean -f to remove them.'))
|
|
233
|
-
}
|
|
234
|
-
case 'commit': {
|
|
235
|
-
if (!found) {
|
|
236
|
-
throw new Error('seam commit seals a block folder\'s changes; run it in a folder made by seam checkout.')
|
|
237
|
-
}
|
|
238
|
-
// --sign: signs the new commit with this computer's key (seam key add).
|
|
239
|
-
const signed = async (r) => {
|
|
240
|
-
if (!flags['--sign'] || !r?.result?.id) return ''
|
|
241
|
-
const s = await signWithLocalKey(client, needQuilt(), r.result.id)
|
|
242
|
-
r.signature = s.signature
|
|
243
|
-
return `\nSigned with ${s.signature.key}.`
|
|
244
|
-
}
|
|
245
|
-
if (flags['--amend']) {
|
|
246
|
-
const r = await amendFolder(client, found.dir, block, { message, paths: [...pos, ...paths].map((p) => path.resolve(cwd, p)) })
|
|
247
|
-
const s = await signed(r)
|
|
248
|
-
return print(r, describeWrite(r, `Amended ${r.amended.slice(0, 12)} as`) + s)
|
|
249
|
-
}
|
|
250
|
-
if (!message) throw new Error('Say what changed: seam commit -m "message" [paths...]')
|
|
251
|
-
const r = await commitFolder(client, found.dir, block, { message, paths: [...pos, ...paths].map((p) => path.resolve(cwd, p)) })
|
|
252
|
-
if (r.nothing) return print(r, 'Nothing to commit.')
|
|
253
|
-
const s = await signed(r)
|
|
254
|
-
return print(r, describeWrite(r, 'Committed') + s)
|
|
255
|
-
}
|
|
256
|
-
case 'sign': {
|
|
257
|
-
// seam sign [rev]: signs a commit you made (default: your square's head, or main's tip).
|
|
258
|
-
const r = await signWithLocalKey(client, needQuilt(), pos[0] || (block?.square ? block.square_short : block?.line) || 'main')
|
|
259
|
-
return print(r, `Signed ${r.short} with ${r.signature.key}.`)
|
|
260
|
-
}
|
|
261
|
-
case 'token': {
|
|
262
|
-
// seam token [list] | new <name> [--level read|write] | revoke <id or name>
|
|
263
|
-
const sub = pos[0] || 'list'
|
|
264
|
-
if (sub === 'list') { const r = await client.tokens(needQuilt()); return print(r, describeTokens(r)) }
|
|
265
|
-
if (sub === 'new') {
|
|
266
|
-
if (!pos[1]) throw new Error('seam token new <name> [--level read|write]')
|
|
267
|
-
const r = await client.createToken(needQuilt(), { name: pos[1], level: flags['--level'] || 'read' })
|
|
268
|
-
return print(r, describeNewToken(r))
|
|
269
|
-
}
|
|
270
|
-
if (sub === 'revoke') {
|
|
271
|
-
if (!pos[1]) throw new Error('seam token revoke <id or name>')
|
|
272
|
-
const r = await client.revokeToken(needQuilt(), pos[1])
|
|
273
|
-
return print(r, `Revoked token ${r.name} (${r.id}): it no longer reaches the quilt.`)
|
|
274
|
-
}
|
|
275
|
-
throw new Error('seam token [list] | new <name> [--level read|write] | revoke <id or name>')
|
|
276
|
-
}
|
|
277
|
-
case 'key': {
|
|
278
|
-
// seam key [list [member]] | add [--name <n>] [--public-key <file>] | remove <key>
|
|
279
|
-
const sub = pos[0] || 'list'
|
|
280
|
-
if (sub === 'list') { const r = await client.signingKeys({ member: pos[1] }); return print(r, describeKeys(r)) }
|
|
281
|
-
if (sub === 'remove') {
|
|
282
|
-
if (!pos[1]) throw new Error('seam key remove <key>')
|
|
283
|
-
const r = await client.removeKey(pos[1])
|
|
284
|
-
return print(r, `Removed ${r.key}: commits it signed now show "Signature by a removed key".`)
|
|
285
|
-
}
|
|
286
|
-
if (sub !== 'add') throw new Error('seam key [list [member]] | add [--name <name>] [--public-key <file>] | remove <key>')
|
|
287
|
-
let pub = flags['--public-key'] ? fs.readFileSync(path.resolve(cwd, flags['--public-key']), 'utf8') : null
|
|
288
|
-
let made = false
|
|
289
|
-
if (!pub) { const k = localKey(); if (k) pub = k.public_key; else { pub = makeLocalKey(); made = true } }
|
|
290
|
-
const r = await client.addKey({ public_key: pub, name: flags['--name'] || os.hostname() })
|
|
291
|
-
return print(r, `${made ? `Made a signing key at ${signingKeyFile()} and added` : 'Added'} ${r.key}. Sign with seam commit --sign or seam sign <rev>.`)
|
|
292
|
-
}
|
|
293
|
-
case 'log': {
|
|
294
|
-
const rev = pos[0] || (block?.square ? block.square_short : block?.line) || 'main'
|
|
295
|
-
const r = await client.log(needQuilt(), { rev, paths, limit: flags['-n'] || flags['--max-count'] || 20, follow: flags['--follow'] ? true : undefined, show_signature: flags['--show-signature'] ? true : undefined, search: typeof flags['-S'] === 'string' ? flags['-S'] : undefined })
|
|
296
|
-
return print(r, describeLog(r, { oneline: !!flags['--oneline'] }))
|
|
297
|
-
}
|
|
298
|
-
case 'describe': {
|
|
299
|
-
const r = await client.describe(needQuilt(), pos[0] || (block?.square ? block.square_short : block?.line) || 'main')
|
|
300
|
-
return print(r, r.describe)
|
|
301
|
-
}
|
|
302
|
-
case 'merge-base': {
|
|
303
|
-
if (pos.length !== 2) throw new Error('seam merge-base <revision> <revision>')
|
|
304
|
-
const r = await client.mergeBase(needQuilt(), pos[0], pos[1])
|
|
305
|
-
return print(r, describeMergeBase(r))
|
|
306
|
-
}
|
|
307
|
-
case 'grep': {
|
|
308
|
-
// seam grep <pattern> [revision] [-- paths]: -i ignores case, -F takes the pattern as plain text, -l lists files.
|
|
309
|
-
if (!pos[0]) throw new Error('seam grep <pattern> [revision] [-i] [-F] [-l] [--all] [-- paths]')
|
|
310
|
-
if (flags['--all']) {
|
|
311
|
-
// --all: every quilt you can read (or --quilts a,b), at the revision (default main).
|
|
312
|
-
const r = await client.search({ pattern: pos[0], rev: pos[1] || 'main', quilts: flags['--quilts'] ? String(flags['--quilts']).split(',') : undefined, paths, ignore_case: flags['-i'] ? 1 : undefined, fixed: flags['-F'] ? 1 : undefined, limit: flags['--max-count'] })
|
|
313
|
-
return print(r, describeGrep(r, { filesOnly: !!flags['-l'] }))
|
|
314
|
-
}
|
|
315
|
-
const rev = pos[1] || (block?.square ? block.square_short : block?.line) || 'main'
|
|
316
|
-
const rel = (p) => found ? path.relative(found.dir, path.resolve(cwd, p)).split(path.sep).join('/') : p
|
|
317
|
-
const r = await client.grep(needQuilt(), { pattern: pos[0], rev, paths: paths.map(rel), ignore_case: flags['-i'] ? 1 : undefined, fixed: flags['-F'] ? 1 : undefined, limit: flags['--max-count'] })
|
|
318
|
-
return print(r, describeGrep(r, { filesOnly: !!flags['-l'] }))
|
|
319
|
-
}
|
|
320
|
-
case 'bisect': {
|
|
321
|
-
// seam bisect <bad> <good> --run "<check>": exit 0 good, 125 skip, else bad. Without --run, one step (--marks id:good,id:bad).
|
|
322
|
-
if (pos.length !== 2) throw new Error('seam bisect <bad> <good> --run "<check command>" (or --marks <commit>:good,<commit>:bad for one step)')
|
|
323
|
-
const marks = Object.fromEntries(String(flags['--marks'] || '').split(',').filter(Boolean).map((x) => x.split(':')))
|
|
324
|
-
if (typeof flags['--run'] === 'string') {
|
|
325
|
-
const { bisectRun } = await import('./bisect-run.js')
|
|
326
|
-
const r = await bisectRun(client, { quilt: needQuilt(), bad: pos[0], good: pos[1], run: flags['--run'], marks, api, log: (s) => { if (!json) out(s) } })
|
|
327
|
-
return print(r, describeBisect(r))
|
|
328
|
-
}
|
|
329
|
-
const r = await client.bisect(needQuilt(), { bad: pos[0], good: pos[1], marks })
|
|
330
|
-
return print(r, describeBisect(r) + (r.done ? '' : `\nThen: seam bisect ${pos[0]} ${pos[1]} --marks ${[...Object.entries(marks).map(([k, v]) => `${k}:${v}`), `${r.next.short}:<good|bad|skip>`].join(',')}`))
|
|
331
|
-
}
|
|
332
|
-
case 'show': {
|
|
333
|
-
const rev = pos[0] || (block?.square ? block.square_short : 'main')
|
|
334
|
-
const format = flags['--stat'] ? 'stat' : flags['--name-only'] || flags['--name-status'] ? 'names' : 'unified'
|
|
335
|
-
const r = await client.show(needQuilt(), rev, { format, paths })
|
|
336
|
-
return print(r, describeShow(r, { format }))
|
|
337
|
-
}
|
|
338
|
-
case 'diff': {
|
|
339
|
-
const format = flags['--stat'] ? 'stat' : flags['--name-only'] || flags['--name-status'] ? 'names' : 'unified'
|
|
340
|
-
if (!pos.length && found) {
|
|
341
|
-
const d = await localDiff(client, found.dir, block, { paths: paths.map((p) => path.resolve(cwd, p)) })
|
|
342
|
-
return print(d, format === 'unified' ? (d.text || '') : d.changes.map((c) => `${c.status[0].toUpperCase()}\t${c.path}`).join('\n'))
|
|
343
|
-
}
|
|
344
|
-
const [from, to] = pos.length === 1 && pos[0].includes('..') ? pos[0].split('..') : pos.length === 1 ? [pos[0], block?.square ? block.square_short : 'main'] : pos
|
|
345
|
-
const r = await client.diff(needQuilt(), { from, to, paths, format })
|
|
346
|
-
return print(r, describeDiff(r, { format }))
|
|
347
|
-
}
|
|
348
|
-
case 'merge': {
|
|
349
|
-
let into = flags['--into'] || (pos.length ? null : (block?.line || 'main'))
|
|
350
|
-
// git merge <other>: in a block on a square, merges that square (or revision) into this one.
|
|
351
|
-
if (!into && block?.square) into = block.square_short || block.square
|
|
352
|
-
if (!into) throw new Error('Merge into what? seam merge sq:<id> --into main (a line) or --into sq:<id> (a square)')
|
|
353
|
-
if (/^sq:/.test(into)) {
|
|
354
|
-
const target = into
|
|
355
|
-
const from = pos[0] || needSquare()
|
|
356
|
-
const mine = found && (target === block.square_short || target === block.square)
|
|
357
|
-
const r = await client.mergeSquare(needQuilt(), target, { from, ...(mine ? { expect_head: block.head } : {}), message })
|
|
358
|
-
let text = r.noop ? `Already up to date: ${from} is in ${target}.` : describeWrite(r, `Merged ${from} into ${target}:`)
|
|
359
|
-
if (mine && !r.noop) { const st = await syncTo(client, found.dir, block, r.square.head, { square: r.square }); text += `\n${st.written} file(s) updated in the folder.` }
|
|
360
|
-
return print(r, text)
|
|
361
|
-
}
|
|
362
|
-
const sqId = pos[0] && pos[0].startsWith('sq:') ? pos[0] : needSquare()
|
|
363
|
-
const r = await client.merge(needQuilt(), into, { square: sqId, ...(found && sqId === block.square ? { expect_head: block.head } : {}), message, dry_run: !!flags['--dry-run'], on_conflict: flags['--on-conflict'], expect_tip: flags['--expect-tip'] })
|
|
364
|
-
let text = describeMerge(r)
|
|
365
|
-
// The refused merge left the square rebased onto the tip with its conflicts: bring the folder there.
|
|
366
|
-
if (found && sqId === block.square && !r.ok && r.head && r.head !== block.head) {
|
|
367
|
-
const s = await syncTo(client, found.dir, block, r.head, { square: r.square })
|
|
368
|
-
text += `\nThe folder now holds the rebased files (${s.written} written)${s.kept.length ? `; kept your own version of ${s.kept.join(', ')}` : ''}.`
|
|
369
|
-
}
|
|
370
|
-
return print(r, text)
|
|
371
|
-
}
|
|
372
|
-
case 'rebase': {
|
|
373
|
-
const r = await client.rebase(needQuilt(), needSquare(), { ...(found ? { expect_head: block.head } : {}), onto: flags['--onto'] })
|
|
374
|
-
let text = r.noop ? 'Already on the tip.' : describeWrite(r, 'Rebased')
|
|
375
|
-
if (found && !r.noop) { const s = await syncTo(client, found.dir, block, r.square.head || r.square.base, { square: r.square }); text += `\n${s.written} file(s) updated in the folder.` }
|
|
376
|
-
return print(r, text)
|
|
377
|
-
}
|
|
378
|
-
case 'cherry-pick': {
|
|
379
|
-
if (!pos[0]) throw new Error('seam cherry-pick <revision>')
|
|
380
|
-
const r = await client.cherryPick(needQuilt(), needSquare(), { from: pos[0], ...(found ? { expect_head: block.head } : {}) })
|
|
381
|
-
let text = describeWrite(r, 'Picked')
|
|
382
|
-
if (found) { const s = await syncTo(client, found.dir, block, r.square.head, { square: r.square }); text += `\n${s.written} file(s) updated in the folder.` }
|
|
383
|
-
return print(r, text)
|
|
384
|
-
}
|
|
385
|
-
case 'revert': {
|
|
386
|
-
if (!pos[0]) throw new Error('seam revert <patch:n | revision>')
|
|
387
|
-
const r = await client.revert(needQuilt(), { rev: pos[0], line: flags['--line'] })
|
|
388
|
-
let text = describeWrite(r, 'Reverted in a new square') + `\nMerge it with: seam merge ${r.square.short} --into ${r.square.line}`
|
|
389
|
-
if (flags['--dir']) { const dir = path.resolve(cwd, flags['--dir']); await checkout(client, { quilt: needQuilt(), square: r.square.id, dir, api }); text += `\nChecked out into ${dir}.` }
|
|
390
|
-
return print(r, text)
|
|
391
|
-
}
|
|
392
|
-
case 'resolve': {
|
|
393
|
-
const choose = flags['--ours'] ? 'ours' : flags['--theirs'] ? 'theirs' : flags['--base'] ? 'base' : flags['--delete'] ? 'delete' : flags['--suggestion'] ? 'suggestion' : flags['--side'] != null ? Number(flags['--side']) : null
|
|
394
|
-
if (choose === null || !pos.length) throw new Error('seam resolve <path>... --ours | --theirs | --base | --suggestion | --side n | --delete [--no-remember] (or edit the file and commit it without the markers)')
|
|
395
|
-
const rel = (p) => found ? path.relative(found.dir, path.resolve(cwd, p)).split(path.sep).join('/') : p
|
|
396
|
-
const r = await client.resolve(needQuilt(), needSquare(), { ...(found ? { expect_head: block.head } : {}), entries: pos.map((p) => ({ path: rel(p), choose })), ...(flags['--no-remember'] ? { remember: false } : {}) })
|
|
397
|
-
let text = describeWrite(r, 'Resolved')
|
|
398
|
-
if (found) { const s = await syncTo(client, found.dir, block, r.square.head, { square: r.square }); text += `\n${s.written} file(s) updated in the folder.` }
|
|
399
|
-
return print(r, text)
|
|
400
|
-
}
|
|
401
|
-
case 'resolutions': {
|
|
402
|
-
// seam resolutions [path] (the remembered answers) | seam resolutions forget <key>
|
|
403
|
-
if (pos[0] === 'forget') {
|
|
404
|
-
if (!pos[1]) throw new Error('Which one? seam resolutions forget <key>')
|
|
405
|
-
const r = await client.forgetResolution(needQuilt(), pos[1])
|
|
406
|
-
return print(r, `Forgot ${r.forgotten.short} (${r.forgotten.path}): that conflict will be asked about again.`)
|
|
407
|
-
}
|
|
408
|
-
const rows = await client.resolutions(needQuilt(), pos[0])
|
|
409
|
-
return print(rows, describeResolutions(rows))
|
|
410
|
-
}
|
|
411
|
-
case 'blame': {
|
|
412
|
-
const arg = pos[0]
|
|
413
|
-
if (!arg) throw new Error('seam blame <path>[@revision]')
|
|
414
|
-
const at = arg.lastIndexOf('@')
|
|
415
|
-
const [p, rev] = at > 0 ? [arg.slice(0, at), arg.slice(at + 1)] : [arg, pos[1] || (block?.square ? block.square_short : 'main')]
|
|
416
|
-
const rel = found && at < 0 ? path.relative(found.dir, path.resolve(cwd, p)).split(path.sep).join('/') : p
|
|
417
|
-
const r = await client.blame(needQuilt(), rev, rel)
|
|
418
|
-
return print(r, describeBlame(r))
|
|
419
|
-
}
|
|
420
|
-
case 'cat': {
|
|
421
|
-
const arg = pos[0] || ''
|
|
422
|
-
const at = arg.lastIndexOf('@')
|
|
423
|
-
const [p, rev] = at > 0 ? [arg.slice(0, at), arg.slice(at + 1)] : [arg, 'main']
|
|
424
|
-
const r = await client.cat(needQuilt(), rev, p, { format: flags['--format'] || 'diff' })
|
|
425
|
-
if (json) return print(r, '')
|
|
426
|
-
process.stdout.write(Buffer.from(r.content, 'base64'))
|
|
427
|
-
return
|
|
428
|
-
}
|
|
429
|
-
case 'ls': {
|
|
430
|
-
const r = await client.tree(needQuilt(), pos[0] || 'main', pos.slice(1).concat(paths))
|
|
431
|
-
return print(r, r.entries.map((e) => `${e.mode}\t${e.blob ? e.blob.slice(0, 12) : e.term.slice(0, 12)}\t${e.path}`).join('\n'))
|
|
432
|
-
}
|
|
433
|
-
case 'import': {
|
|
434
|
-
if (pos[0] !== 'git' || !pos[1]) throw new Error('seam import git <path> [--into <quilt-slug>] [--validate]')
|
|
435
|
-
const { importGit } = await import('./import-git.js')
|
|
436
|
-
const repo = path.resolve(cwd, pos[1])
|
|
437
|
-
if (!fs.existsSync(path.join(repo, '.git')) && !fs.existsSync(path.join(repo, 'HEAD'))) throw new Error(`${repo} is not a git repository`)
|
|
438
|
-
const r = await importGit(client, { repo, slug: flags['--into'] || path.basename(repo).toLowerCase().replace(/[^a-z0-9-]/g, '-'), log: (s) => { if (!json) out(s) }, validate: !!flags['--validate'], batch: Number(flags['--batch']) || 100 })
|
|
439
|
-
return print(r, describeImport(r))
|
|
440
|
-
}
|
|
441
|
-
case 'export': {
|
|
442
|
-
if (pos[0] !== 'git' || !pos[1]) throw new Error('seam export git <dir> [--line main] [--branch <name>]')
|
|
443
|
-
const { exportGit } = await import('./export-git.js')
|
|
444
|
-
const r = await exportGit(client, { q: needQuilt(), dir: path.resolve(cwd, pos[1]), line: flags['--line'] || 'main', branch: flags['--branch'] || undefined, log: (s) => { if (!json) out(s) } })
|
|
445
|
-
return print(r, r.exported ? `Exported ${r.exported} patches to ${r.branch} in ${pos[1]}: ${r.branch} is ${r.sha} (patch ${r.patch_no}).` : `Nothing new: ${r.branch} is ${r.sha || 'empty'} (patch ${r.patch_no}).`)
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
throw new Error(`unknown seam command ${cmd}`)
|
|
449
|
-
}
|
package/src/quilts/client.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
// The quilt API from a client (the CLI, the local MCP, the importer): one function per route.
|
|
2
|
-
// A refusal throws an Error with the server's `code`, `status` and fields (head, terms, ...).
|
|
3
|
-
import { buildPack } from './pack.js'
|
|
4
|
-
|
|
5
|
-
const enc = encodeURIComponent
|
|
6
|
-
const qs = (o) => {
|
|
7
|
-
const p = Object.entries(o).filter(([, v]) => v !== undefined && v !== null && v !== '' && !(Array.isArray(v) && !v.length))
|
|
8
|
-
return p.length ? '?' + p.map(([k, v]) => `${k}=${enc(Array.isArray(v) ? v.join(',') : v)}`).join('&') : ''
|
|
9
|
-
}
|
|
10
|
-
const pathPart = (p) => String(p).split('/').map(enc).join('/')
|
|
11
|
-
|
|
12
|
-
export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch, timeoutMs = 120_000 }) {
|
|
13
|
-
const base = String(api || '').replace(/\/+$/, '')
|
|
14
|
-
if (!base) throw new Error('No quilt server: set QUILT_API (or sign in with `quilt login`).')
|
|
15
|
-
const auth = token ? (token.startsWith('QuiltPass ') ? token : `Bearer ${token}`) : null
|
|
16
|
-
|
|
17
|
-
async function call (method, route, body, { raw = false } = {}) {
|
|
18
|
-
let res
|
|
19
|
-
try {
|
|
20
|
-
res = await fetchImpl(base + route, {
|
|
21
|
-
method,
|
|
22
|
-
headers: { ...(auth ? { authorization: auth } : {}), ...(body !== undefined ? { 'content-type': raw ? 'application/octet-stream' : 'application/json' } : {}) },
|
|
23
|
-
body: body === undefined ? undefined : raw ? body : JSON.stringify(body),
|
|
24
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
25
|
-
})
|
|
26
|
-
} catch (err) {
|
|
27
|
-
throw new Error(`Couldn't reach the quilt server at ${base} (${err?.cause?.code || err?.name || 'no answer'}).`)
|
|
28
|
-
}
|
|
29
|
-
const data = await res.json().catch(() => null)
|
|
30
|
-
if (!res.ok && !(data && data.refused)) {
|
|
31
|
-
const { error, ...extra } = data || {}
|
|
32
|
-
throw Object.assign(new Error(error || `The quilt server answered ${res.status}.`), { status: res.status, ...extra })
|
|
33
|
-
}
|
|
34
|
-
return data
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const Q = (q) => `/v1/quilts/${enc(q)}`
|
|
38
|
-
return {
|
|
39
|
-
call,
|
|
40
|
-
quilts: async () => (await call('GET', '/v1/quilts')).quilts,
|
|
41
|
-
createQuilt: (b) => call('POST', '/v1/quilts', b),
|
|
42
|
-
quilt: (q) => call('GET', Q(q)),
|
|
43
|
-
addMember: (q, b) => call('POST', `${Q(q)}/members`, b),
|
|
44
|
-
squares: async (q, f = {}) => (await call('GET', `${Q(q)}/squares${qs({ state: f.state, mine: f.mine ? 1 : undefined, task: f.task })}`)).squares,
|
|
45
|
-
openSquare: (q, b) => call('POST', `${Q(q)}/squares`, b),
|
|
46
|
-
square: (q, s) => call('GET', `${Q(q)}/squares/${enc(s)}`),
|
|
47
|
-
commit: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/commit`, b),
|
|
48
|
-
amend: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/amend`, b),
|
|
49
|
-
handoff: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/handoff`, b),
|
|
50
|
-
abandon: (q, s, b = {}) => call('POST', `${Q(q)}/squares/${enc(s)}/abandon`, b),
|
|
51
|
-
reopen: (q, s) => call('POST', `${Q(q)}/squares/${enc(s)}/reopen`, {}),
|
|
52
|
-
mergeSquare: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/merge`, b),
|
|
53
|
-
fold: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/fold`, b),
|
|
54
|
-
split: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/split`, b),
|
|
55
|
-
rebase: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/rebase`, b),
|
|
56
|
-
cherryPick: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/cherry-pick`, b),
|
|
57
|
-
resolve: (q, s, b) => call('POST', `${Q(q)}/squares/${enc(s)}/resolve`, b),
|
|
58
|
-
lines: async (q) => (await call('GET', `${Q(q)}/lines`)).lines,
|
|
59
|
-
newLine: (q, b) => call('POST', `${Q(q)}/lines`, b),
|
|
60
|
-
reportCheck: (q, line, b) => call('POST', `${Q(q)}/lines/${enc(line)}/checks`, b),
|
|
61
|
-
freezeLine: (q, line) => call('POST', `${Q(q)}/lines/${enc(line)}/freeze`, {}),
|
|
62
|
-
unfreezeLine: (q, line) => call('POST', `${Q(q)}/lines/${enc(line)}/unfreeze`, {}),
|
|
63
|
-
tags: async (q) => (await call('GET', `${Q(q)}/tags`)).tags,
|
|
64
|
-
tag: (q, name) => call('GET', `${Q(q)}/tags/${name.split('/').map(enc).join('/')}`),
|
|
65
|
-
createTag: (q, b) => call('POST', `${Q(q)}/tags`, b),
|
|
66
|
-
releases: async (q) => (await call('GET', `${Q(q)}/releases`)).releases,
|
|
67
|
-
createRelease: (q, b) => call('POST', `${Q(q)}/releases`, b),
|
|
68
|
-
merge: (q, line, b) => call('POST', `${Q(q)}/lines/${enc(line)}/merge`, b),
|
|
69
|
-
revert: (q, b) => call('POST', `${Q(q)}/revert`, b),
|
|
70
|
-
events: (q, o = {}) => call('GET', `${Q(q)}/events${qs(o)}`),
|
|
71
|
-
subscribe: (q, b) => call('POST', `${Q(q)}/webhook`, b),
|
|
72
|
-
show: (q, rev, o = {}) => call('GET', `${Q(q)}/show/${enc(rev)}${qs(o)}`),
|
|
73
|
-
exportPatches: (q, o = {}) => call('GET', `${Q(q)}/export/git${qs(o)}`),
|
|
74
|
-
log: (q, o = {}) => call('GET', `${Q(q)}/log${qs(o)}`),
|
|
75
|
-
sign: (q, rev, b) => call('POST', `${Q(q)}/commits/${enc(rev)}/signatures`, b),
|
|
76
|
-
tokens: (q) => call('GET', `${Q(q)}/tokens`),
|
|
77
|
-
createToken: (q, b) => call('POST', `${Q(q)}/tokens`, b),
|
|
78
|
-
revokeToken: (q, id) => call('DELETE', `${Q(q)}/tokens/${enc(id)}`),
|
|
79
|
-
signingKeys: (o = {}) => call('GET', `/v1/signing-keys${qs(o)}`),
|
|
80
|
-
addKey: (b) => call('POST', '/v1/signing-keys', b),
|
|
81
|
-
removeKey: (key) => call('DELETE', `/v1/signing-keys/${enc(key)}`),
|
|
82
|
-
describe: (q, rev) => call('GET', `${Q(q)}/describe${qs({ rev })}`),
|
|
83
|
-
mergeBase: (q, a, b) => call('GET', `${Q(q)}/merge-base${qs({ a, b })}`),
|
|
84
|
-
grep: (q, o = {}) => call('GET', `${Q(q)}/grep${qs(o)}`),
|
|
85
|
-
search: (o = {}) => call('GET', `/v1/search${qs(o)}`),
|
|
86
|
-
bisect: (q, b) => call('POST', `${Q(q)}/bisect`, b),
|
|
87
|
-
linePatches: (q, line = 'main', o = {}) => call('GET', `${Q(q)}/lines/${encodeURIComponent(line)}/patches${qs(o)}`),
|
|
88
|
-
diff: (q, o = {}) => call('GET', `${Q(q)}/diff${qs(o)}`),
|
|
89
|
-
resolutions: async (q, p) => (await call('GET', `${Q(q)}/resolutions${p ? `?path=${enc(p)}` : ''}`)).resolutions,
|
|
90
|
-
forgetResolution: (q, key) => call('DELETE', `${Q(q)}/resolutions/${enc(key)}`),
|
|
91
|
-
reviews: (q, sq) => call('GET', `${Q(q)}/squares/${enc(sq)}/reviews`),
|
|
92
|
-
review: (q, sq, b) => call('POST', `${Q(q)}/squares/${enc(sq)}/reviews`, b),
|
|
93
|
-
reportSquareCheck: (q, sq, b) => call('POST', `${Q(q)}/squares/${enc(sq)}/checks`, b),
|
|
94
|
-
linePolicy: (q, line) => call('GET', `${Q(q)}/lines/${enc(line)}/policy`),
|
|
95
|
-
setLinePolicy: (q, line, b) => call('PUT', `${Q(q)}/lines/${enc(line)}/policy`, b),
|
|
96
|
-
claims: async (q, square) => (await call('GET', `${Q(q)}/claims${square ? `?square=${enc(square)}` : ''}`)).claims,
|
|
97
|
-
claim: (q, sq, b) => call('POST', `${Q(q)}/squares/${enc(sq)}/claims`, b),
|
|
98
|
-
unclaim: (q, sq, which) => call('DELETE', `${Q(q)}/squares/${enc(sq)}/claims/${enc(which)}`),
|
|
99
|
-
live: (q, sq) => call('GET', `${Q(q)}/squares/${enc(sq)}/live`),
|
|
100
|
-
setLive: (q, sq, body) => call('PUT', `${Q(q)}/squares/${enc(sq)}/live`, body),
|
|
101
|
-
tree: (q, rev, paths) => call('GET', `${Q(q)}/tree/${enc(rev || 'main')}${qs({ paths })}`),
|
|
102
|
-
cat: (q, rev, path, o = {}) => call('GET', `${Q(q)}/blob/${enc(rev || 'main')}/${pathPart(path)}${qs(o)}`),
|
|
103
|
-
blame: (q, rev, path) => call('GET', `${Q(q)}/blame/${enc(rev || 'main')}/${pathPart(path)}`),
|
|
104
|
-
have: (q, ids) => call('POST', `${Q(q)}/have`, { ids }),
|
|
105
|
-
blobs: (q, ids) => call('POST', `${Q(q)}/blobs`, { ids }),
|
|
106
|
-
uploadPack: (q, bytes) => call('POST', `${Q(q)}/packs`, bytes, { raw: true }),
|
|
107
|
-
importCommits: (q, items, lines = []) => call('POST', `${Q(q)}/import/commits`, { items, ...(lines.length ? { lines } : {}) }),
|
|
108
|
-
importKnown: (q, shas) => call('POST', `${Q(q)}/import/known`, { shas }),
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Uploads objects the server does not have yet ([{ kind, id, bytes }]), in packs of at most
|
|
112
|
-
* ~48 MiB, after asking `have` (04: want-only, one pack per commit for a typical commit).
|
|
113
|
-
*/
|
|
114
|
-
async upload (q, objs) {
|
|
115
|
-
const missing = new Set()
|
|
116
|
-
for (let i = 0; i < objs.length; i += 10000) for (const id of (await call('POST', `${Q(q)}/have`, { ids: objs.slice(i, i + 10000).map((o) => o.id) })).missing) missing.add(id)
|
|
117
|
-
const todo = objs.filter((o) => missing.has(o.id))
|
|
118
|
-
let batch = []; let size = 0; let packs = 0
|
|
119
|
-
const flush = async () => { if (!batch.length) return; await call('POST', `${Q(q)}/packs`, buildPack(batch).bytes, { raw: true }); packs++; batch = []; size = 0 }
|
|
120
|
-
for (const o of todo) {
|
|
121
|
-
if (size + o.bytes.length > 48 * 1024 * 1024) await flush()
|
|
122
|
-
batch.push(o); size += o.bytes.length
|
|
123
|
-
}
|
|
124
|
-
await flush()
|
|
125
|
-
return { uploaded: todo.length, packs }
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|