@heyquilt/seam 0.143.0 → 0.145.0
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/bin/seam.js +17 -3
- package/lib/quilts/block.js +15 -3
- package/lib/quilts/cli.js +42 -10
- package/lib/quilts/client.js +8 -0
- package/lib/quilts/describe.js +48 -5
- package/lib/quilts/export-git.js +5 -3
- package/lib/quilts/tools.js +24 -12
- package/package.json +1 -1
package/bin/seam.js
CHANGED
|
@@ -21,9 +21,11 @@ Docs: https://seam.heyquilt.com/docs
|
|
|
21
21
|
seam logout | seam whoami
|
|
22
22
|
seam computers [list | signout <id or name>] computers signed in to your account (seam login)
|
|
23
23
|
seam handle [<new>] your name in your repos' addresses (<handle>/<repo>); with <new>, change it
|
|
24
|
-
seam account delete --confirm <your email> delete your Seam account (your patches stay)
|
|
24
|
+
seam account delete --confirm <your email> delete your Seam account (your patches stay; repos you deleted are purged)
|
|
25
|
+
seam account log [-n <count>] [--before <id>] what happened to your repos: deleted, restored, purged
|
|
25
26
|
seam agent new <name> | list | key <name> | revoke <name> | relink [agent:<id>] your agents and their keys
|
|
26
27
|
seam oauth client new <name> --redirect <uri> | list | revoke <client_id> apps that ask people for access (OAuth 2, PKCE)
|
|
28
|
+
seam oauth allowed [list | revoke <grant id>] the apps you allowed into your account, and ending one
|
|
27
29
|
seam quilts [new <slug> | show <q>] Your quilts (repositories)
|
|
28
30
|
seam import git <path> [--into <slug>] [--authors <file>]
|
|
29
31
|
seam github [import <owner/name> --installation <id> [--into <slug>]] from GitHub, followed
|
|
@@ -98,6 +100,9 @@ Docs: https://seam.heyquilt.com/docs
|
|
|
98
100
|
seam org log <org> [-n <count>] [--before <id>] what happened in an org: members, roles, teams, quilts (owners and admins)
|
|
99
101
|
seam org team <org> <name> [<member>...] an org's team, named org_team:<name> in its quilts' protections
|
|
100
102
|
seam quilts new <slug> --org <org> | quilts move --quilt <q> --org <org> a quilt that belongs to an org
|
|
103
|
+
seam quilts rename <new-slug> --quilt <q> [--title <name>] a new address (<owner>/<new-slug>); the old one stays an alias
|
|
104
|
+
seam quilts delete <q> --confirm <owner/name> delete a repo (its owner, or an org's admin); restorable for 7 days, then purged
|
|
105
|
+
seam quilts deleted [--org <org>] | restore <slug> [--org <org>] Recently deleted, and bringing one back as it was
|
|
101
106
|
seam quilts agents-make-lines on|off whether agents may make lines, tags and releases
|
|
102
107
|
seam quilts public on|off anyone may read the quilt, signed in or not
|
|
103
108
|
seam quilts add <email|member> [--level read|write|admin] add a member; an email with no account yet is invited
|
|
@@ -139,7 +144,7 @@ async function main () {
|
|
|
139
144
|
const r = await go('PUT', '/v1/me/handle', { handle: argv[0] })
|
|
140
145
|
return console.log(`Your handle is ${r.handle}: your repos are ${r.handle}/<repo> now, and their old addresses stop working.`)
|
|
141
146
|
}
|
|
142
|
-
if (cmd === 'account') {
|
|
147
|
+
if (cmd === 'account' && argv[0] !== 'log') {
|
|
143
148
|
const i = argv.indexOf('--confirm')
|
|
144
149
|
if (argv[0] !== 'delete' || i < 0 || !argv[i + 1]) fail('seam account delete --confirm <your email>')
|
|
145
150
|
const r = await signedIn()('DELETE', `/v1/me?confirm=${encodeURIComponent(argv[i + 1])}`)
|
|
@@ -185,7 +190,16 @@ async function oauthCommand (argv) {
|
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
const [what, sub, ...rest] = argv
|
|
188
|
-
|
|
193
|
+
// The apps you allowed (GET /v1/me/grants): what each may do; revoking one ends its tokens.
|
|
194
|
+
if (what === 'allowed') {
|
|
195
|
+
if (sub === 'revoke') { if (!rest[0]) fail('seam oauth allowed revoke <grant id>'); await go('DELETE', `/v1/me/grants/${encodeURIComponent(rest[0])}`); return console.log(`Revoked: the app's tokens from that approval have ended. It asks you again to come back.`) }
|
|
196
|
+
if (sub && sub !== 'list') fail(`seam oauth allowed: '${sub}' is not a command (list, revoke).`)
|
|
197
|
+
const { grants } = await go('GET', '/v1/me/grants')
|
|
198
|
+
if (!grants.length) return console.log('You have allowed no apps.')
|
|
199
|
+
for (const g of grants) console.log(`${g.id} ${g.client.name} ${g.scopes.map((x) => { const q = g.quilts.find((y) => x === `quilt:${y.id}:${y.level}`); return q ? `${q.slug || q.id}:${q.level}` : x }).join(' ')}`)
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
if (what !== 'client') fail('seam oauth client new <name> --redirect <uri> | list | revoke <client_id> | seam oauth allowed [list | revoke <grant id>]')
|
|
189
203
|
if (sub === 'new') {
|
|
190
204
|
const redirects = []; const words = []
|
|
191
205
|
for (let i = 0; i < rest.length; i++) { if (rest[i] === '--redirect') redirects.push(rest[++i]); else words.push(rest[i]) }
|
package/lib/quilts/block.js
CHANGED
|
@@ -892,10 +892,22 @@ export async function pull (client, dir, block) {
|
|
|
892
892
|
const s = await toLineTip(client, dir, block, sq.line, tip)
|
|
893
893
|
return { left: { square: short, state: sq.state }, line: sq.line, at: tip, written: s.written, merged: s.merged, conflicted: s.conflicted }
|
|
894
894
|
}
|
|
895
|
+
// The square moved on the server since this folder last looked (the site's Merge brought its
|
|
896
|
+
// line in, conflicts kept in its new head; or a commit from another folder): the folder takes
|
|
897
|
+
// that head first, markers and all, as seam checkout would. Uncommitted edits stay (syncTo).
|
|
898
|
+
const caught = (sq.head || null) !== (block.head || null) ? { from: block.head || null, ...await syncTo(client, dir, block, sq.head || sq.base, { square: sq }) } : null
|
|
895
899
|
const r = await client.rebase(block.quilt, block.square, { expect_head: block.head })
|
|
896
|
-
if (r.noop) return { rebased: false, noop: true, square: r.square }
|
|
897
|
-
const s = await syncTo(client, dir, block, r.square.head || r.square.base, { square: r.square })
|
|
898
|
-
|
|
900
|
+
if (r.noop && !caught) return { rebased: false, noop: true, square: r.square }
|
|
901
|
+
const s = r.noop ? { written: 0, merged: [], conflicted: [] } : await syncTo(client, dir, block, r.square.head || r.square.base, { square: r.square })
|
|
902
|
+
const both = (k) => [...new Set([...(caught?.[k] || []), ...s[k]])].sort(cmpPath)
|
|
903
|
+
const conflicted = both('conflicted')
|
|
904
|
+
// The conflicts the square itself holds (terms in its head), apart from the folder's own clashes.
|
|
905
|
+
const held = Object.entries(block.files).filter(([p, f]) => f.term && !conflicted.includes(p)).map(([p]) => p).sort(cmpPath)
|
|
906
|
+
return {
|
|
907
|
+
rebased: !r.noop, square: r.square, ...(r.noop ? {} : { result: r }),
|
|
908
|
+
...(caught ? { caught_up: { from: caught.from, to: sq.head || sq.base } } : {}),
|
|
909
|
+
written: (caught?.written || 0) + s.written, merged: both('merged'), conflicted, held
|
|
910
|
+
}
|
|
899
911
|
}
|
|
900
912
|
if (!isLineCheckout(block)) throw new Error('This folder holds one revision, not a line: seam checkout <line or square> moves it.')
|
|
901
913
|
const to = (await client.show(block.quilt, block.line, { format: 'none' })).commit.id
|
package/lib/quilts/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { quiltClient } from './client.js'
|
|
|
9
9
|
import { runChecks, readRunnerConfig, listenForWork } from './runner.js'
|
|
10
10
|
import { startDaemon, askDaemon } from './daemon.js'
|
|
11
11
|
import { rebuild as rebuildBlock, conflictStyle } from './block.js'
|
|
12
|
-
import { describeRebuild, describeOpened, describeBoth } from './describe.js'
|
|
12
|
+
import { describeRebuild, describeOpened, describeBoth, describePull, describeDeletedQuilt, describeDeletedQuilts, describePersonLog } from './describe.js'
|
|
13
13
|
import { findBlock, checkout, status as blockStatus, commitFolder, amendFolder, syncTo, localDiff, saveBlock, pull, toLineTip, restore, reset, clean, localChanges, bytesOf, livePatch, resolvePulled } from './block.js'
|
|
14
14
|
import { describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeImportRecord, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeHandoff, describeFinished, describeResolutions, describeLiveSet, describeLiveJoin, describeMergeBase, describeStack, describeStackDiff, describeGrep, describeBisect, describeCheck, describeReviews, describeInterdiff, describeReviewed, describeSquareCheck, describePolicy, describeKeys, describeTokens, describeNewToken, describeBlock, describeBlocks, describeProtections, describeTeams, describeAccess, describeRedacted, describeRedactions, describeMetrics, describeSquarePolicy, describePolicyVersions, describeOrg, describeOrgs, describeOrgLog, describeOrgDomain, describeApp, describeApps, describeAppKey, describeInstall, describeInstalls, describeRequest, describeRequests, describeOrgTeams, describeCheckRuns, describeRerun, describeAncestor, describeContains, describePipeline, describeWatches, describeSearch, describePublicReads } from './describe.js'
|
|
15
15
|
import { verifyQuilt, describeVerify } from './attest.js'
|
|
@@ -21,7 +21,7 @@ import { signWithLocalKey, localKey, makeLocalKey, signingKeyFile } from './sign
|
|
|
21
21
|
export const API_URL = 'https://api.seam.heyquilt.com'
|
|
22
22
|
|
|
23
23
|
/** The verbs `seam` answers. */
|
|
24
|
-
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', 'stack', 'claim', 'unclaim', 'check', 'checks', 'resolutions', 'review', 'sign', 'key', 'token', 'block', 'runner', 'protect', 'team', 'verify', 'redact', 'metrics', 'seal', 'policy', 'org', 'app', 'watch', 'search', 'contains', 'daemon', 'clone', 'pull'])
|
|
24
|
+
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', 'stack', 'claim', 'unclaim', 'check', 'checks', 'resolutions', 'review', 'sign', 'key', 'token', 'block', 'runner', 'protect', 'team', 'verify', 'redact', 'metrics', 'seal', 'policy', 'org', 'app', 'watch', 'search', 'contains', 'daemon', 'clone', 'pull', 'account'])
|
|
25
25
|
|
|
26
26
|
/** `ci=agent:ci,lint=agent:a+agent:b` → { ci: ['agent:ci'], lint: ['agent:a', 'agent:b'] }; `none` clears them. */
|
|
27
27
|
export function parseRunners (text) {
|
|
@@ -83,7 +83,7 @@ export function agentTokenExtras (flags) {
|
|
|
83
83
|
return out
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const VALUE_FLAGS = new Set(['--repos', '--name', '--org', '--description', '--homepage', '--callback', '--setup', '--webhook', '--events', '--perm', '--authors', '--debounce', '-m', '--installation', '--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', '--interval', '--credential', '--as', '--runners', '--run-key', '--state', '--tier', '--tiers', '--tests', '--ci-config', '--commit', '--check', '--log-url', '--at', '--human-only', '--keys', '--reason', '--without', '--allow', '--rules', '--org', '--role', '--agent', '--paths', '--squares', '--hours', '--pre-merge-checks', '--by', '--mode', '--machine', '--add', '--remove', '--exclude', '--max-fetch-bytes', '--expires', '--out', '--auto-combine', '--needs-stack', '--monotonic-stack', '--migrations-in-order', '--rule', '--bisect-width', '--classes', '--min-lines', '-L', '--ignore-rev', '--reverse', '--grep', '-G', '--author', '--on-behalf-of', '--since', '--until', '--group-by', '--cursor', '-U', '--unified', '--renames', '--config', '--only', '--listen', '--idle-exit', '--wait', '--days', '--with', '--stack', '--scope', '--suggest', '--to-line', '--approval-survives', '--stack-defaults', '--stack-keys', '--lfs-url', '--lfs-threshold', '--offset', '--length', '--rate', '--before'])
|
|
86
|
+
const VALUE_FLAGS = new Set(['--repos', '--name', '--org', '--description', '--homepage', '--callback', '--setup', '--webhook', '--events', '--perm', '--authors', '--debounce', '-m', '--installation', '--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', '--interval', '--credential', '--as', '--runners', '--run-key', '--state', '--tier', '--tiers', '--tests', '--ci-config', '--commit', '--check', '--log-url', '--at', '--human-only', '--keys', '--reason', '--without', '--allow', '--rules', '--org', '--role', '--agent', '--paths', '--squares', '--hours', '--pre-merge-checks', '--by', '--mode', '--machine', '--add', '--remove', '--exclude', '--max-fetch-bytes', '--expires', '--out', '--auto-combine', '--needs-stack', '--monotonic-stack', '--migrations-in-order', '--rule', '--bisect-width', '--classes', '--min-lines', '-L', '--ignore-rev', '--reverse', '--grep', '-G', '--author', '--on-behalf-of', '--since', '--until', '--group-by', '--cursor', '-U', '--unified', '--renames', '--config', '--only', '--listen', '--idle-exit', '--wait', '--days', '--with', '--stack', '--scope', '--suggest', '--to-line', '--approval-survives', '--stack-defaults', '--stack-keys', '--lfs-url', '--lfs-threshold', '--offset', '--length', '--rate', '--before', '--confirm'])
|
|
87
87
|
|
|
88
88
|
export function parse (argv) {
|
|
89
89
|
const pos = []; const flags = {}; const paths = []
|
|
@@ -147,6 +147,30 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
|
|
|
147
147
|
const r = await client.setQuiltOwner(needQuilt(), flags['--org'])
|
|
148
148
|
return print(r, `${r.quilt} now belongs to ${r.org}.`)
|
|
149
149
|
}
|
|
150
|
+
if (pos[0] === 'rename') {
|
|
151
|
+
// seam quilts rename <new-slug> --quilt <q> [--title <name>]: its address is <owner>/<new-slug> after.
|
|
152
|
+
if (!pos[1] && !flags['--title']) throw new Error('seam quilts rename <new-slug> --quilt <q> [--title <name>]')
|
|
153
|
+
const r = await client.renameQuilt(needQuilt(), { ...(pos[1] ? { slug: pos[1] } : {}), ...(flags['--title'] ? { name: flags['--title'] } : {}) })
|
|
154
|
+
return print(r, r.slug === r.was ? `${r.slug} is now called ${r.name}.` : `${r.was} is now ${r.path || r.slug}${r.name !== r.slug ? ` (${r.name})` : ''}. Links by its id keep working; its old address doesn't. In a block folder of it, nothing changes: the block names it by id.`)
|
|
155
|
+
}
|
|
156
|
+
if (pos[0] === 'delete') {
|
|
157
|
+
// seam quilts delete <q> --confirm <owner/name> (11.13): hidden at once, restored for 7 days, purged after.
|
|
158
|
+
const q = pos[1] || quilt
|
|
159
|
+
if (!q || !flags['--confirm']) throw new Error('seam quilts delete <q> --confirm <owner/name>: type the repo\'s address to delete it')
|
|
160
|
+
const r = await client.deleteQuilt(q, flags['--confirm'])
|
|
161
|
+
return print(r, describeDeletedQuilt(r))
|
|
162
|
+
}
|
|
163
|
+
if (pos[0] === 'deleted') {
|
|
164
|
+
// seam quilts deleted [--org <org>]: Recently deleted, yours or an org's (its admins and owners).
|
|
165
|
+
const r = await client.deletedQuilts({ org: flags['--org'] })
|
|
166
|
+
return print(r, describeDeletedQuilts(r, { org: flags['--org'] }))
|
|
167
|
+
}
|
|
168
|
+
if (pos[0] === 'restore') {
|
|
169
|
+
// seam quilts restore <slug|id|owner/name> [--org <org>]: back as it was, within 7 days of the delete.
|
|
170
|
+
if (!pos[1]) throw new Error('seam quilts restore <slug, id or owner/name> [--org <org>]')
|
|
171
|
+
const r = await client.undeleteQuilt(pos[1], { org: flags['--org'] })
|
|
172
|
+
return print(r, `Restored ${r.path || r.quilt}: it is back as it was. The squares the delete closed stay closed; seam square reopen <sq> --quilt ${r.quilt} brings one back.`)
|
|
173
|
+
}
|
|
150
174
|
if (pos[0] === 'sealed') {
|
|
151
175
|
if (!['on', 'off'].includes(pos[1])) throw new Error('seam quilts sealed on|off --quilt <q>')
|
|
152
176
|
const r = await client.setSealed(needQuilt(), pos[1] === 'on')
|
|
@@ -345,12 +369,9 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
|
|
|
345
369
|
}
|
|
346
370
|
case 'pull': {
|
|
347
371
|
if (!found) throw new Error('seam pull works in a block folder: run it in a folder made by seam clone or seam checkout.')
|
|
372
|
+
const was = { ...block }
|
|
348
373
|
const r = await pull(client, found.dir, block)
|
|
349
|
-
|
|
350
|
-
if (r.noop) return print(r, block.square ? `Already up to date: ${block.square_short || block.square} sits on ${block.line}'s tip.` : `Already up to date with ${block.line}.`)
|
|
351
|
-
if (r.left) return print(r, `${r.left.square} was ${r.left.state}; the folder is now on ${r.line} (${r.at.slice(0, 12)}, ${r.written} file(s) updated).` + kept)
|
|
352
|
-
if (r.rebased) return print(r, describeWrite(r.result, 'Rebased') + `\n${r.written} file(s) updated in the folder.` + kept)
|
|
353
|
-
return print(r, `Pulled ${r.line} (now ${r.at.slice(0, 12)}): ${r.written} file(s) updated.` + kept)
|
|
374
|
+
return print(r, describePull(r, was))
|
|
354
375
|
}
|
|
355
376
|
case 'checkout': {
|
|
356
377
|
const target = pos[0]
|
|
@@ -683,6 +704,12 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
|
|
|
683
704
|
const r = await client.editApp(pos[1], b)
|
|
684
705
|
return print(r, describeApp(r))
|
|
685
706
|
}
|
|
707
|
+
case 'account': {
|
|
708
|
+
// seam account log [-n <count>] [--before <id>]: what happened to your repos (11.13). seam account delete is the seam package's.
|
|
709
|
+
if (pos[0] !== 'log') throw new Error('seam account log [-n <count>] [--before <id>] | seam account delete --confirm <your email>')
|
|
710
|
+
const r = await client.personLog({ limit: flags['-n'], before: flags['--before'] })
|
|
711
|
+
return print(r, describePersonLog(r))
|
|
712
|
+
}
|
|
686
713
|
case 'org': {
|
|
687
714
|
// seam org [list] | new <slug> [--title <name>] | show <org> | add <org> <member> [--role member|admin|owner] | remove <org> <member>
|
|
688
715
|
const sub = pos[0] || 'list'
|
|
@@ -1097,8 +1124,13 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
|
|
|
1097
1124
|
case 'export': {
|
|
1098
1125
|
// To a git remote, on the server, after each merge (14): seam export remote <url> | status.
|
|
1099
1126
|
if (pos[0] === 'remote' && pos[1]) {
|
|
1100
|
-
const r = await client.setExport(needQuilt(), { target: pos[1], lines: flags['--line'] ? String(flags['--line']).split(',') : undefined, releases: !!flags['--releases'], credential: flags['--credential'] || undefined, scratch: !!flags['--scratch'], enabled: !flags['--off'], lfs: flags['--lfs-url'] ? { url: String(flags['--lfs-url']), ...(flags['--lfs-threshold'] != null ? { threshold_bytes: Number(flags['--lfs-threshold']) } : {}) } : undefined })
|
|
1101
|
-
return print(r, r.enabled ? `Exporting ${r.lines.join(', ')} to ${r.target} after each merge${r.releases ? ', with release tags' : ''}${r.lfs ? `, files over ${r.lfs.threshold_bytes} bytes to LFS at ${r.lfs.url}` : ''}. seam export status` : `The export to ${r.target} is off.`)
|
|
1127
|
+
const r = await client.setExport(needQuilt(), { target: pos[1], lines: flags['--line'] ? String(flags['--line']).split(',') : undefined, releases: !!flags['--releases'], credential: flags['--credential'] || undefined, scratch: !!flags['--scratch'], enabled: !flags['--off'], lfs: flags['--lfs-url'] ? { url: String(flags['--lfs-url']), ...(flags['--lfs-threshold'] != null ? { threshold_bytes: Number(flags['--lfs-threshold']) } : {}) } : undefined, main_as: flags['--main-as'] || undefined, push: flags['--push'] || undefined, exclude: flags['--exclude'] ? String(flags['--exclude']).split(',').map((x) => x.trim()).filter(Boolean) : undefined })
|
|
1128
|
+
return print(r, r.enabled ? `Exporting ${r.lines.join(', ')} to ${r.target} ${r.push === 'tag' ? 'up to the newest tagged patch' : 'after each merge'}${r.main_as && r.main_as !== 'main' ? `, main as ${r.main_as}` : ''}${r.releases ? ', with release tags' : ''}${r.exclude?.length ? `, without ${r.exclude.join(', ')}` : ''}${r.lfs ? `, files over ${r.lfs.threshold_bytes} bytes to LFS at ${r.lfs.url}` : ''}. seam export status` : `The export to ${r.target} is off.`)
|
|
1129
|
+
}
|
|
1130
|
+
// Runs the export now (admins): after fixing a remote, or without waiting for the next retry.
|
|
1131
|
+
if (pos[0] === 'push') {
|
|
1132
|
+
const r = await client.exportPush(needQuilt())
|
|
1133
|
+
return print(r, `Pushing to ${r.target} now. seam export status`)
|
|
1102
1134
|
}
|
|
1103
1135
|
if (pos[0] === 'status') {
|
|
1104
1136
|
const r = await client.exportStatus(needQuilt())
|
package/lib/quilts/client.js
CHANGED
|
@@ -237,6 +237,7 @@ export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch,
|
|
|
237
237
|
search: (o = {}) => call('GET', `/v1/search${qs(o)}`),
|
|
238
238
|
searchCode: (q, o = {}) => call('GET', `${Q(q)}/search${qs(o)}`),
|
|
239
239
|
bisect: (q, b) => call('POST', `${Q(q)}/bisect`, b),
|
|
240
|
+
lineLasts: (q, line = 'main', paths = []) => call('GET', `${Q(q)}/lines/${encodeURIComponent(line)}/lasts${qs({ paths: paths.join(',') })}`),
|
|
240
241
|
linePatches: (q, line = 'main', o = {}) => call('GET', `${Q(q)}/lines/${encodeURIComponent(line)}/patches${qs(o)}`),
|
|
241
242
|
diff: (q, o = {}) => call('GET', `${Q(q)}/diff${qs(o)}`),
|
|
242
243
|
resolutions: async (q, p, { hunks = false } = {}) => (await call('GET', `${Q(q)}/resolutions${qs({ path: p || undefined, hunks: hunks ? 1 : undefined })}`)).resolutions,
|
|
@@ -345,6 +346,12 @@ export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch,
|
|
|
345
346
|
setOrgTeam: (o, name, members) => call('PUT', `/v1/orgs/${enc(o)}/teams/${enc(name)}`, { members }),
|
|
346
347
|
setOrgMember: (o, member, role) => String(member).includes('@') ? call('POST', `/v1/orgs/${enc(o)}/members`, { email: member, role }) : call('PUT', `/v1/orgs/${enc(o)}/members/${enc(member)}`, { role }),
|
|
347
348
|
setQuiltOwner: (q, org) => call('PUT', `${Q(q)}/owner`, { org }),
|
|
349
|
+
renameQuilt: (q, body) => call('PATCH', Q(q), body),
|
|
350
|
+
// Deleting a repo (11.13): confirm is its address, <owner>/<slug>; restored for 7 days by id, slug or address.
|
|
351
|
+
deleteQuilt: (q, confirm) => call('DELETE', `${Q(q)}${qs({ confirm })}`),
|
|
352
|
+
deletedQuilts: ({ org } = {}) => call('GET', org ? `/v1/orgs/${enc(org)}/deleted-quilts` : '/v1/me/deleted-quilts'),
|
|
353
|
+
undeleteQuilt: (q, { org } = {}) => call('POST', `${org ? `/v1/orgs/${enc(org)}` : '/v1/me'}/deleted-quilts/${enc(q)}/restore`, {}),
|
|
354
|
+
personLog: ({ limit, before } = {}) => call('GET', `/v1/me/log${qs({ limit, before })}`),
|
|
348
355
|
setAgentsMakeLines: (q, allow) => call('PUT', `${Q(q)}/agents-make-lines`, { allow }),
|
|
349
356
|
setPublic: (q, value) => call('PUT', `${Q(q)}/public`, { public: value }),
|
|
350
357
|
setSealed: (q, value) => call('PUT', `${Q(q)}/sealed`, { sealed: value }),
|
|
@@ -372,6 +379,7 @@ export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch,
|
|
|
372
379
|
importKnown: (q, shas) => call('POST', `${Q(q)}/import/known`, { shas }),
|
|
373
380
|
setExport: (q, b) => call('PUT', `${Q(q)}/export`, b),
|
|
374
381
|
exportStatus: (q) => call('GET', `${Q(q)}/export`),
|
|
382
|
+
exportPush: (q) => call('POST', `${Q(q)}/export/push`, {}),
|
|
375
383
|
startImport: (b) => call('POST', '/v1/imports', b),
|
|
376
384
|
github: () => call('GET', '/v1/github'),
|
|
377
385
|
importGithub: (b) => call('POST', '/v1/github/import', b),
|
package/lib/quilts/describe.js
CHANGED
|
@@ -19,20 +19,22 @@ export function describeSquare (s) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function describeLog (r, { oneline = false, graph = false } = {}) {
|
|
22
|
-
|
|
22
|
+
// A search that ran out of time (stopped: 'time') or of commits to look at ('scan') goes on with its cursor.
|
|
23
|
+
const early = r.partial ? (r.stopped === 'time' ? '(stopped early: the search ran out of time) ' : '(partial) ') : ''
|
|
24
|
+
const more = r.cursor ? (r.partial ? `${early}(search further: seam log … --cursor ${r.cursor})` : `(more: seam log … --cursor ${r.cursor})`) : r.more ? (oneline ? '…' : '(more: seam log -n <count>)') : null
|
|
23
25
|
if (r.groups) {
|
|
24
26
|
// group_by: git shortlog -s, with lines added and removed.
|
|
25
27
|
if (!r.groups.length) return 'No commits.'
|
|
26
28
|
const w = Math.max(...r.groups.map((g) => String(g.commits).length))
|
|
27
|
-
return [...r.groups.map((g) => `${String(g.commits).padStart(w + 2)} ${g.key == null ? '(none)' : g.name ? `${g.name} <${g.key}>` : g.key} +${g.added} -${g.removed}`), ...(more ? [
|
|
29
|
+
return [...r.groups.map((g) => `${String(g.commits).padStart(w + 2)} ${g.key == null ? '(none)' : g.name ? `${g.name} <${g.key}>` : g.key} +${g.added} -${g.removed}`), ...(more ? [more] : [])].join('\n')
|
|
28
30
|
}
|
|
29
|
-
if (!r.commits.length) return more ?
|
|
31
|
+
if (!r.commits.length) return more ? `${r.partial ? 'None found yet.' : 'No commits on this page.'}\n${more}` : r.existed === false ? 'No commits: this path was never here.' : 'No commits.'
|
|
30
32
|
const mark = (c) => graph ? (c.parents.length > 1 ? '* ' : c.hidden ? 'x ' : '* ') : ''
|
|
31
33
|
const merged = (c) => (c.parent_squares || []).map((s) => `${graph ? '|\\ ' : ''}merged ${s.square_id ? `square ${s.square_id.slice(0, 8)}${s.title ? ` "${s.title}"` : ''}` : s.commit.slice(0, 12)}`)
|
|
32
34
|
const lines = r.commits.map((c) => oneline
|
|
33
35
|
? [`${mark(c)}${c.short} ${c.patch ? `[${c.patch.line}:${c.patch.patch_no}] ` : ''}${first(c.message)}${r.follow && c.path && c.path !== r.follow ? ` (${c.path})` : ''}${c.hidden ? ` (hidden: replaced by ${c.predecessor_of.slice(0, 12)})` : ''}${c.signature ? ` (signature: ${c.signature.status})` : ''}`, ...merged(c)].join('\n')
|
|
34
36
|
: [`${mark(c)}commit ${c.id}${c.patch ? ` (${patchName(c.patch)})` : ''}${c.hidden ? ` (hidden: replaced by ${c.predecessor_of.slice(0, 12)})` : ''}`, ...(c.parents.length > 1 ? [`Merge: ${c.parents.map((p) => p.slice(0, 12)).join(' ')}`, ...merged(c)] : []), `Square: ${c.square}`, ...(r.follow && c.path && c.path !== r.follow ? [`Path: ${c.path}`] : []), `Author: ${c.author.name} <${c.author.member_id}>${c.author.tool ? ` via ${c.author.tool}` : ''}${c.on_behalf_of ? ` for ${c.on_behalf_of}` : ''}`, ...(c.task_id ? [`Task: ${c.task_id}`] : []), ...(c.signature ? [describeSignature(c.signature)] : []), `Date: ${when(c.authored_at)}`, '', ...String(c.message || '').split('\n').map((l) => ` ${l}`), ''].join('\n'))
|
|
35
|
-
if (more) lines.push(
|
|
37
|
+
if (more) lines.push(more)
|
|
36
38
|
if (r.last_present !== undefined) lines.push(`This path is gone: it was last here in patch ${r.last_present}.`)
|
|
37
39
|
return lines.join('\n')
|
|
38
40
|
}
|
|
@@ -111,9 +113,24 @@ export function describeBoth (r, { diff = 'seam diff', resolve = 'seam resolve <
|
|
|
111
113
|
const out = []
|
|
112
114
|
if (r.merged?.length) out.push(`Merged upstream's changes into your edits to ${r.merged.join(', ')}: compare with ${diff}.`)
|
|
113
115
|
if (r.conflicted?.length) out.push(`Conflict in ${r.conflicted.join(', ')} (changed on both sides): settle each by editing out the conflict markers, or with ${resolve}, then commit.`)
|
|
116
|
+
if (r.held?.length) out.push(`The square holds a conflict in ${r.held.join(', ')}: settle each by editing out the conflict markers, or with ${resolve}, then commit.`)
|
|
114
117
|
return out.map((l) => '\n' + l).join('')
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
/**
|
|
121
|
+
* seam pull's answer (block.js pull), the same for the CLI and seam mcp. `block` is the folder's
|
|
122
|
+
* record before the pull; `diff` and `resolve` as describeBoth.
|
|
123
|
+
*/
|
|
124
|
+
export function describePull (r, block, names) {
|
|
125
|
+
const kept = describeBoth(r, names)
|
|
126
|
+
if (r.noop) return block.square ? `Already up to date: ${block.square_short || block.square} sits on ${block.line}'s tip.` : `Already up to date with ${block.line}.`
|
|
127
|
+
if (r.left) return `${r.left.square} was ${r.left.state}; the folder is now on ${r.line} (${r.at.slice(0, 12)}, ${r.written} file(s) updated).` + kept
|
|
128
|
+
const caught = r.caught_up ? `Brought ${r.square.short}'s new head in (${String(r.caught_up.to).slice(0, 12)}): the square moved on the server since this folder last looked.` : ''
|
|
129
|
+
if (r.rebased) return (caught ? caught + '\n' : '') + describeWrite(r.result, 'Rebased') + `\n${r.written} file(s) updated in the folder.` + kept
|
|
130
|
+
if (caught) return `${caught} ${r.written} file(s) updated; it sits on ${r.square.line}'s tip.` + kept
|
|
131
|
+
return `Pulled ${r.line} (now ${r.at.slice(0, 12)}): ${r.written} file(s) updated.` + kept
|
|
132
|
+
}
|
|
133
|
+
|
|
117
134
|
export function describeStatus (st) {
|
|
118
135
|
const lines = []
|
|
119
136
|
if (st.square) lines.push(describeSquareStatus(st.square, { prefix: 'On square ' }))
|
|
@@ -121,7 +138,7 @@ export function describeStatus (st) {
|
|
|
121
138
|
else lines.push(`A read-only checkout of ${st.at ? st.at.slice(0, 12) : 'an empty line'}`)
|
|
122
139
|
lines.push(`Block: ${st.paths.map((p) => p || '/').join(', ')} at ${st.at ? st.at.slice(0, 12) : '(empty)'}`)
|
|
123
140
|
if (st.square_closed) lines.push(`This square is ${st.square_closed}: seam pull moves the folder to ${st.square.line}'s tip.`)
|
|
124
|
-
if (st.stale) lines.push('The square moved on since this folder last looked:
|
|
141
|
+
if (st.stale) lines.push('The square moved on since this folder last looked: seam pull brings its new head in (your uncommitted edits stay).')
|
|
125
142
|
if (st.conflicted.length) lines.push(`Conflicted (edit and commit without the markers, or seam resolve --ours|--theirs): ${st.conflicted.join(', ')}`)
|
|
126
143
|
if (!st.changes.length) lines.push('Nothing changed since the last commit.')
|
|
127
144
|
else lines.push('Changes not yet committed:', ...st.changes.map((c) => ` ${c.status.padEnd(9)} ${c.path}`))
|
|
@@ -572,6 +589,31 @@ export const describeInstalls = (r) => r.installations.length ? r.installations.
|
|
|
572
589
|
|
|
573
590
|
export const describeApps = (r) => r.apps.length ? r.apps.map((a) => describeApp(a)).join('\n\n') : 'No apps.'
|
|
574
591
|
|
|
592
|
+
// The rows deleting a repo writes in its owner's log (11.13), the org's and a person's alike.
|
|
593
|
+
const QUILT_LOG = {
|
|
594
|
+
quilt_deleted: (d) => `deleted the repo ${d.slug}${d.closed ? ` (closed ${d.closed} agent square${d.closed === 1 ? '' : 's'})` : ''}`,
|
|
595
|
+
quilt_restored: (d) => `restored the repo ${d.slug}`,
|
|
596
|
+
quilt_purged: (d) => `purged the repo ${d.slug}${d.reason === 'account_deleted' ? ' (the account was deleted)' : ', 7 days after it was deleted'}`
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** A deleted repo (11.13): when it is purged, and how to bring it back. */
|
|
600
|
+
export function describeDeletedQuilt (r) {
|
|
601
|
+
const org = String(r.owner || '').startsWith('org:') && r.path ? r.path.split('/')[0] : null
|
|
602
|
+
return `Deleted ${r.path || r.quilt}: nobody reaches it now, and its address stays ${org ? `${org}'s` : 'yours'}. Restore it until ${when(r.purge_at)} with seam quilts restore ${r.quilt}${org ? ` --org ${org}` : ''}; after that it is purged.${r.closed_squares ? ` ${r.closed_squares} square${r.closed_squares === 1 ? '' : 's'} agents held closed.` : ''}`
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Recently deleted: yours or an org's, each with when it is purged. */
|
|
606
|
+
export function describeDeletedQuilts (r, { org } = {}) {
|
|
607
|
+
if (!r.quilts.length) return `No deleted repos${org ? ` in ${org}` : ''}: a deleted repo stays here 7 days.`
|
|
608
|
+
return r.quilts.map((q) => `${(q.path || q.slug).padEnd(24)} deleted ${when(q.deleted_at)} by ${q.deleted_by}, purged ${when(q.purge_at)} (seam quilts restore ${q.slug}${org ? ` --org ${org}` : ''})`).join('\n')
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** A person's log (11.13): what happened to their repos, newest first. */
|
|
612
|
+
export function describePersonLog (r) {
|
|
613
|
+
if (!r.log.length) return 'Your log is empty.'
|
|
614
|
+
return [...r.log.map((x) => `${x.id.padStart(6)} ${new Date(x.at).toISOString().slice(0, 16).replace('T', ' ')} ${x.actor} ${(QUILT_LOG[x.kind] || ((d) => `${x.kind} ${JSON.stringify(d)}`))(x.data || {})}`), ...(r.next ? [`More: seam account log --before ${r.next}`] : [])].join('\n')
|
|
615
|
+
}
|
|
616
|
+
|
|
575
617
|
export function describeOrgLog (r) {
|
|
576
618
|
const what = (x) => {
|
|
577
619
|
const d = x.data || {}
|
|
@@ -581,6 +623,7 @@ export function describeOrgLog (r) {
|
|
|
581
623
|
if (x.kind.startsWith('app_')) return `${{ app_created: 'made', app_changed: 'changed', app_removed: 'removed', app_manager_added: `made ${d.member} a manager of`, app_manager_removed: `took ${d.member} off the managers of`, app_key_added: `made a key ${d.key_id} for`, app_key_removed: `removed the key ${d.key_id} of`, app_moved_in: `moved in from ${d.from}` }[x.kind] || x.kind}${x.kind.startsWith('app_key') || x.kind.startsWith('app_manager') || x.kind === 'app_moved_in' ? '' : ' the app'} ${d.handle} ${d.handle}${d.fields?.length ? ` (${d.fields.join(', ')})` : ''}`
|
|
582
624
|
if (x.kind === 'app_installed' || x.kind === 'app_uninstalled' || x.kind.startsWith('install_')) return `${{ app_installed: 'installed', app_uninstalled: 'removed', install_requested: 'asked for', install_request_approved: 'approved the request for', install_request_declined: 'declined the request for', install_repos_changed: 'changed the repos of', install_paused: 'paused', install_permissions_accepted: 'accepted new permissions for', install_resumed: 'resumed' }[x.kind] || x.kind} the app ${d.handle}${['app_installed', 'install_repos_changed', 'install_requested'].includes(x.kind) ? ` (${d.repos ? d.repos.join(', ') : 'all repos'})` : ''}`
|
|
583
625
|
if (x.kind.startsWith('domain_')) return `${{ domain_added: 'added', domain_verified: 'verified', domain_lost: 'lost (its record went away)', domain_removed: 'removed' }[x.kind] || x.kind} the domain ${d.domain}`
|
|
626
|
+
if (x.kind.startsWith('quilt_') && QUILT_LOG[x.kind]) return QUILT_LOG[x.kind](d)
|
|
584
627
|
if (x.kind === 'quilt_in') return `moved the quilt ${d.slug} in (from ${d.from})`
|
|
585
628
|
if (x.kind === 'quilt_out') return `moved the quilt ${d.slug} out (to ${d.to})`
|
|
586
629
|
return `${x.kind} ${JSON.stringify(d)}`
|
package/lib/quilts/export-git.js
CHANGED
|
@@ -61,9 +61,10 @@ async function fetchBlobs (client, q, ids) {
|
|
|
61
61
|
* missing). Returns { exported, patch_no, sha, branch }. With `sign` (export-sign.js exportKey), every
|
|
62
62
|
* commit carries an SSH signature with that key. With `lfs` ({ threshold, upload(bytes) → pointer
|
|
63
63
|
* text }, export-lfs.js), a file over the threshold or named by a `filter=lfs` line of the root
|
|
64
|
-
* `.gitattributes` is written as an LFS pointer and its bytes uploaded.
|
|
64
|
+
* `.gitattributes` is written as an LFS pointer and its bytes uploaded. With `exclude` (a function
|
|
65
|
+
* of a path), the paths it names are left out of every exported tree (14, `exclude`).
|
|
65
66
|
*/
|
|
66
|
-
export async function exportGit (client, { q, dir, line = 'main', branch, page = 200, sign = null, lfs = null, log = () => {} }) {
|
|
67
|
+
export async function exportGit (client, { q, dir, line = 'main', branch, page = 200, sign = null, lfs = null, exclude = null, log = () => {} }) {
|
|
67
68
|
branch = branch || line
|
|
68
69
|
const gitDir = openRepo(dir)
|
|
69
70
|
const stateFile = path.join(gitDir, 'quilt-export.json')
|
|
@@ -89,7 +90,7 @@ export async function exportGit (client, { q, dir, line = 'main', branch, page =
|
|
|
89
90
|
if (version !== null && version !== r.version) throw new Error(`export format ${r.version} differs from the ${version} this branch was exported with: export to a new branch`)
|
|
90
91
|
version = r.version; quiltId = r.quilt
|
|
91
92
|
if (!r.patches.length) break
|
|
92
|
-
const blobs = await fetchBlobs(client, q, r.patches.flatMap((p) => p.changes.filter((c) => c.op === 'put').map((c) => c.blob)))
|
|
93
|
+
const blobs = await fetchBlobs(client, q, r.patches.flatMap((p) => p.changes.filter((c) => c.op === 'put' && !(exclude && exclude(c.path))).map((c) => c.blob)))
|
|
93
94
|
const marksFile = path.join(gitDir, 'quilt-export.marks')
|
|
94
95
|
const fi = fastImport(dir, marksFile)
|
|
95
96
|
const send = async (s) => { const w = fi.write(s); if (w) await w }
|
|
@@ -116,6 +117,7 @@ export async function exportGit (client, { q, dir, line = 'main', branch, page =
|
|
|
116
117
|
if (attrs) lfsMatch = attrs.op === 'delete' ? null : lfsPatterns(blobs.get(attrs.blob)?.toString('utf8'))
|
|
117
118
|
}
|
|
118
119
|
for (const c of p.changes) {
|
|
120
|
+
if (exclude && exclude(c.path)) continue
|
|
119
121
|
const bytes = lfs && c.op === 'put' && (!c.mode || c.mode === 'file' || c.mode === 'exec') && c.path !== '.gitattributes' ? blobs.get(c.blob) : null
|
|
120
122
|
if (bytes && (bytes.length > lfs.threshold || lfsMatch?.ignores(c.path))) {
|
|
121
123
|
const pointer = await lfs.upload(bytes)
|
package/lib/quilts/tools.js
CHANGED
|
@@ -16,10 +16,10 @@ import { eventLine, describeLog, describeShow, describeDiff, describeBlame, desc
|
|
|
16
16
|
import { signWithLocalKey, localKey, makeLocalKey, signingKeyFile } from './signing.js'
|
|
17
17
|
import { startDaemon, askDaemon } from './daemon.js'
|
|
18
18
|
import { rebuild as rebuildBlock, conflictStyle } from './block.js'
|
|
19
|
-
import { describeRebuild, describeOpened, describeBoth } from './describe.js'
|
|
19
|
+
import { describeRebuild, describeOpened, describeBoth, describePull, describeDeletedQuilt, describeDeletedQuilts, describePersonLog } from './describe.js'
|
|
20
20
|
import { status as blockStatus, commitFolder, amendFolder, syncTo, pull, toLineTip, checkout, restore, reset, clean, localChanges, bytesOf, livePatch, resolvePulled } from './block.js'
|
|
21
21
|
|
|
22
|
-
export const SEAM_TOOLS = ['seam_quilts', 'seam_open_square', 'seam_squares', 'seam_log', 'seam_show', 'seam_diff', 'seam_blame', 'seam_cat', 'seam_ls', 'seam_commit', 'seam_merge', 'seam_rebase', 'seam_revert', 'seam_cherry_pick', 'seam_resolve', 'seam_square_status', 'seam_square_handoff', 'seam_abandon', 'seam_square_merged', 'seam_reopen', 'seam_fold', 'seam_split', 'seam_events', 'seam_events_webhook', 'seam_line_list', 'seam_line_new', 'seam_line_freeze', 'seam_line_unfreeze', 'seam_line_require', 'seam_reviews', 'seam_review', 'seam_tag_list', 'seam_tag', 'seam_release_list', 'seam_release', 'seam_import_git', 'seam_import_remote', 'seam_github', 'seam_github_import', 'seam_import_status', 'seam_import_authors', 'seam_import_poke', 'seam_import_cutover', 'seam_export_remote', 'seam_export_status', 'seam_restore', 'seam_reset', 'seam_clean', 'seam_live', 'seam_live_join', 'seam_live_set', 'seam_bisect', 'seam_grep', 'seam_search', 'seam_describe', 'seam_merge_base', 'seam_stack', 'seam_migrations_dir', 'seam_claims', 'seam_claim', 'seam_unclaim', 'seam_check', 'seam_resolutions', 'seam_forget_resolution', 'seam_sign', 'seam_keys', 'seam_key_add', 'seam_key_remove', 'seam_token_list', 'seam_token_new', 'seam_token_revoke', 'seam_protections', 'seam_protections_set', 'seam_team_set', 'seam_verify', 'seam_redact', 'seam_redactions', 'seam_seal', 'seam_secret_scanning', 'seam_public', 'seam_sealed', 'seam_policy', 'seam_line_policy_versions', 'seam_orgs', 'seam_org_log', 'seam_org_domain', 'seam_apps', 'seam_app', 'seam_app_key', 'seam_app_manager', 'seam_app_install', 'seam_app_request', 'seam_org_member', 'seam_org_team', 'seam_agent_token_new', 'seam_agent_token_list', 'seam_agent_token_revoke', 'seam_agents_make_lines', 'seam_checks', 'seam_check_rerun', 'seam_reparent', 'seam_archive', 'seam_unarchive', 'seam_hide', 'seam_unhide', 'seam_square_edit', 'seam_share', 'seam_unshare', 'seam_export_tarball', 'seam_claim_request', 'seam_claim_withdraw', 'seam_claim_handoff', 'seam_claim_finish', 'seam_blocks', 'seam_block_open', 'seam_block_touch', 'seam_block_close', 'seam_ancestor', 'seam_contains', 'seam_pipeline', 'seam_watch', 'seam_unwatch', 'seam_absorb', 'seam_interdiff', 'seam_apply_suggestion', 'seam_infra_paths', 'seam_owner_idle_hours', 'seam_public_reads', 'seam_export_archive', 'seam_metrics', 'seam_daemon', 'seam_block_rebuild', 'seam_clone', 'seam_pull', 'seam_member_add']
|
|
22
|
+
export const SEAM_TOOLS = ['seam_quilts', 'seam_open_square', 'seam_squares', 'seam_log', 'seam_show', 'seam_diff', 'seam_blame', 'seam_cat', 'seam_ls', 'seam_commit', 'seam_merge', 'seam_rebase', 'seam_revert', 'seam_cherry_pick', 'seam_resolve', 'seam_square_status', 'seam_square_handoff', 'seam_abandon', 'seam_square_merged', 'seam_reopen', 'seam_fold', 'seam_split', 'seam_events', 'seam_events_webhook', 'seam_line_list', 'seam_line_new', 'seam_line_freeze', 'seam_line_unfreeze', 'seam_line_require', 'seam_reviews', 'seam_review', 'seam_tag_list', 'seam_tag', 'seam_release_list', 'seam_release', 'seam_import_git', 'seam_import_remote', 'seam_github', 'seam_github_import', 'seam_import_status', 'seam_import_authors', 'seam_import_poke', 'seam_import_cutover', 'seam_export_remote', 'seam_export_status', 'seam_export_push', 'seam_restore', 'seam_reset', 'seam_clean', 'seam_live', 'seam_live_join', 'seam_live_set', 'seam_bisect', 'seam_grep', 'seam_search', 'seam_describe', 'seam_merge_base', 'seam_stack', 'seam_migrations_dir', 'seam_claims', 'seam_claim', 'seam_unclaim', 'seam_check', 'seam_resolutions', 'seam_forget_resolution', 'seam_sign', 'seam_keys', 'seam_key_add', 'seam_key_remove', 'seam_token_list', 'seam_token_new', 'seam_token_revoke', 'seam_protections', 'seam_protections_set', 'seam_team_set', 'seam_verify', 'seam_redact', 'seam_redactions', 'seam_seal', 'seam_secret_scanning', 'seam_public', 'seam_sealed', 'seam_policy', 'seam_line_policy_versions', 'seam_orgs', 'seam_org_log', 'seam_org_domain', 'seam_apps', 'seam_app', 'seam_app_key', 'seam_app_manager', 'seam_app_install', 'seam_app_request', 'seam_org_member', 'seam_org_team', 'seam_agent_token_new', 'seam_agent_token_list', 'seam_agent_token_revoke', 'seam_agents_make_lines', 'seam_checks', 'seam_check_rerun', 'seam_reparent', 'seam_archive', 'seam_unarchive', 'seam_hide', 'seam_unhide', 'seam_square_edit', 'seam_share', 'seam_unshare', 'seam_export_tarball', 'seam_claim_request', 'seam_claim_withdraw', 'seam_claim_handoff', 'seam_claim_finish', 'seam_blocks', 'seam_block_open', 'seam_block_touch', 'seam_block_close', 'seam_ancestor', 'seam_contains', 'seam_pipeline', 'seam_watch', 'seam_unwatch', 'seam_absorb', 'seam_interdiff', 'seam_apply_suggestion', 'seam_infra_paths', 'seam_owner_idle_hours', 'seam_public_reads', 'seam_export_archive', 'seam_metrics', 'seam_daemon', 'seam_block_rebuild', 'seam_clone', 'seam_pull', 'seam_member_add', 'seam_quilt_delete', 'seam_quilts_deleted', 'seam_quilt_restore', 'seam_account_log']
|
|
23
23
|
|
|
24
24
|
/** What every agent is told about source control, in each MCP's instructions. */
|
|
25
25
|
export const SEAM_GUIDE = 'Quilt source control (quilts): a quilt is a repository on the Quilt server; you work in a square (your own branch and working copy), commit save points in it, and merge it into main, where it becomes a patch. ' +
|
|
@@ -71,6 +71,7 @@ export const SEAM_DESCRIPTIONS = {
|
|
|
71
71
|
seam_export_archive: 'Writes the whole quilt as one .tar (admin) to a file on this computer: every log segment, every pack and the members, with a manifest of sizes and BLAKE3s; out (default <quilt>-<row>.seam.tar). seam export check <file> checks it offline. Over HTTP: POST /v1/quilts/:q/exit/archive.',
|
|
72
72
|
seam_export_tarball: 'Writes one commit as a .tar.gz (what git archive makes) to a file on this computer: rev (default main), paths to take only part, out (default <quilt>-<commit>.tar.gz in this folder). The same commit always gives the same bytes. Over HTTP: GET /v1/quilts/:q/tarball/:rev?paths=.',
|
|
73
73
|
seam_export_status: 'The export to a git remote: its target, lines, state (idle, ok, retrying, refused), each line\'s last exported patch and sha, and the last error.',
|
|
74
|
+
seam_export_push: 'Runs the export to a git remote now (admins), without waiting for the next merge or retry.',
|
|
74
75
|
seam_import_cutover: 'Ends a following import for good (admins): a last pass, a check that the remote\'s tips equal the imported ones, then main and release/* take merges in Seam and main exports to the source after each merge. Refuses with remote_moved if git moved.',
|
|
75
76
|
seam_import_status: 'An import from a git remote: its state (reading, awaiting_authors, following, done, failed), its lines with their git tips, its last error, and while it waits the authors with the proposed member of each.',
|
|
76
77
|
seam_import_authors: 'Answers an import waiting in awaiting_authors (admins): a map of git author email → member id of the quilt; the import then runs. An email left out becomes a stub member. Refuses an id that is not a member (unknown_author).',
|
|
@@ -84,7 +85,7 @@ export const SEAM_DESCRIPTIONS = {
|
|
|
84
85
|
seam_block_touch: 'The heartbeat of an open block: keeps it open (blocks untouched for seven days are closed) and moves a live block to the square\'s current head; add and remove change its folders.',
|
|
85
86
|
seam_clone: 'Checks out a line of a quilt (main by default) into a new folder on this computer, ready to work: the first commit there opens a square by itself. The folder is named after the quilt unless dir is given.',
|
|
86
87
|
seam_member_add: 'Adds a member to the quilt or changes their level (an admin): email (a person; one with no Seam account yet is invited, and is in the quilt from their first sign-in) or member_id (person:… or agent:…), level read, write or admin (default write; remove takes them out).',
|
|
87
|
-
seam_pull: 'In a block folder: brings the line\'s new patches in (uncommitted edits stay; a file changed on both sides is merged, or given conflict markers to settle before it is committed); on a square, rebases it onto its line\'s tip; on a square merged from elsewhere, moves the folder to the line\'s tip.',
|
|
88
|
+
seam_pull: 'In a block folder: brings the line\'s new patches in (uncommitted edits stay; a file changed on both sides is merged, or given conflict markers to settle before it is committed); on a square, brings in its new head if it moved on the server (a merge on the site, conflicts included), then rebases it onto its line\'s tip; on a square merged from elsewhere, moves the folder to the line\'s tip.',
|
|
88
89
|
seam_block_rebuild: 'In a block folder: finishes a move of the folder (rebase, checkout of its square) that stopped part way, from the journal in .seam; edits made meanwhile are kept as yours. On a whole folder it writes nothing.',
|
|
89
90
|
seam_block_close: 'Closes a block (its row on the server). The square, its work and any files on disk are untouched.',
|
|
90
91
|
seam_token_list: 'The quilt\'s tokens (keys for one quilt, for CI and scripts): id, name, read or write, and whether revoked. Admins only.',
|
|
@@ -96,6 +97,10 @@ export const SEAM_DESCRIPTIONS = {
|
|
|
96
97
|
seam_redact: 'Redacts content from the quilt for good (a person who is an admin): the blob\'s bytes leave every pack, every read gets a tombstone, and the same bytes are refused if added again. History and ids stay. Name blob, or path (and rev). Cannot be undone: confirm must be true.',
|
|
97
98
|
seam_seal: 'A sealed quilt\'s key (the server never sees its contents): status (key version, who holds it), or grant member: wrap the key this computer holds for that member\'s computers.',
|
|
98
99
|
seam_secret_scanning: 'Reads, or (a person who is an admin) sets, secret scanning on the quilt: refuse (a commit that looks like it holds a secret is refused with path, line and finding id), warn, or off.',
|
|
100
|
+
seam_quilt_delete: 'Deletes a repo (a person: its owner, or an owner or admin of the org that owns it; never an agent on its own). confirm is the repo\'s address as <owner>/<name>, typed by the person who asked. It is hidden at once (it answers as a missing repo; its tokens, webhooks and app installs stop; agents\' open squares in it close) and can be restored for 7 days with seam_quilt_restore; then it is purged. Export it first if anything in it is wanted (seam export archive, or the export to git).',
|
|
101
|
+
seam_quilts_deleted: 'Lists Recently deleted repos: yours, or an org\'s (org: its slug; its admins and owners), each with when it was deleted, by whom, and when it is purged.',
|
|
102
|
+
seam_quilt_restore: 'Restores a repo deleted in the last 7 days, as it was (its address, members, settings, tokens and people\'s squares; the squares the delete closed stay closed, seam_reopen brings one back). quilt: its slug, id or address; org: when an org owned it.',
|
|
103
|
+
seam_account_log: 'What happened to your repos, newest first: deleted, restored, purged; who did it and when (an org\'s are in seam_org_log). before: a row id, for the page after.',
|
|
99
104
|
seam_public: 'Makes the quilt readable by anyone, signed in or not (public: true), or private again (a person who is an admin). The quilt\'s protections can still keep paths out of public view.',
|
|
100
105
|
seam_policy: 'A square\'s change classes (whitespace, comment-only, docs, test-only, dependency, ci-config, schema, logic, per file) and what its line\'s rules by path and class need before it merges: approvals from whom, checks, and which are met.',
|
|
101
106
|
seam_line_policy_versions: 'A line\'s policy versions (every change to what it needs is one), or, for a person with admin: approve or reject a pending version someone else proposed, or confirm your own with this computer\'s signing key when you are the only admin. Agents read them only.',
|
|
@@ -140,7 +145,7 @@ export const SEAM_DESCRIPTIONS = {
|
|
|
140
145
|
seam_absorb: 'Puts your square\'s uncommitted live work into the commit of the square that last changed each file (amending it and rewriting the commits above it), so a fix to commit 2 of 5 needs no manual rebase. Files no commit of the square changed stay live.',
|
|
141
146
|
seam_fold: 'Folds a square (square) into another square you hold (into): its work becomes one commit there and the square closes. Live work it shared (seam_live_set) is committed on it first and goes along (commit_live: false refuses with live_work instead). Overlapping changes become conflict markers in the target.',
|
|
142
147
|
seam_split: 'Splits your square: by paths (groups: [[paths], ...]) or by claim (one group per claim on the square that covers a changed path; the claim and its holder go with it). Each group moves to a new square (same base) as one commit; the largest group, and paths no group names, stay in this square. stacked: true chains the new squares on this one in order instead of opening them side by side.',
|
|
143
|
-
seam_log: 'History, newest first: rev (default main, or your square, or a..b), paths to limit it, limit (default 20). follow: true with one path follows the file through its renames; search: "<text>" keeps only commits that changed how many times the text appears (git log -S); match: "<regex>" keeps commits whose added or removed lines match (git log -G). message (with message_regex, ignore_case) filters on the commit message (git log --grep); author (member id or part of a name), on_behalf_of, task, since and until (an ISO date, or 2h, 3d, 1w) filter on who, what for and when. group_by (author, on_behalf_of, task, tool) gives counts with lines added and removed instead of commits; graph: true names the square each merge brought in; include_hidden: true shows a square\'s commits replaced by an amend. When there is more, pass the answer\'s cursor for the next page. Each patch on main is one line\'s merge. With one path on a line, existed says whether it was ever there and, if it is gone now, last_present is the last patch it was there in.',
|
|
148
|
+
seam_log: 'History, newest first: rev (default main, or your square, or a..b), paths to limit it, limit (default 20). follow: true with one path follows the file through its renames; search: "<text>" keeps only commits that changed how many times the text appears (git log -S); match: "<regex>" keeps commits whose added or removed lines match (git log -G). message (with message_regex, ignore_case) filters on the commit message (git log --grep); author (member id or part of a name), on_behalf_of, task, since and until (an ISO date, or 2h, 3d, 1w) filter on who, what for and when. group_by (author, on_behalf_of, task, tool) gives counts with lines added and removed instead of commits; graph: true names the square each merge brought in; include_hidden: true shows a square\'s commits replaced by an amend. When there is more, pass the answer\'s cursor for the next page. A search (search, match, a regex message) looks for about a second: past that it answers what it found so far, says it stopped early, and gives a cursor to search further. Each patch on main is one line\'s merge. With one path on a line, existed says whether it was ever there and, if it is gone now, last_present is the last patch it was there in.',
|
|
144
149
|
seam_describe: 'Names a revision (rev, default main) by the nearest tag behind it: v1.0 on the tag itself, v1.0-3-<commit> three commits past it, the commit alone when no tag is behind it.',
|
|
145
150
|
seam_merge_base: 'Where two revisions (a, b: lines, squares, patches, commits) last shared history: the commit a merge of one into the other starts from.',
|
|
146
151
|
seam_stack: 'The full-stack pointers at a revision (a line, patch:n, sq:…, a commit): each key with its value, where it was set (a patch number or c:<commit>) and how (set by a commit, or derived: db_migration is the newest .sql file under supabase/migrations). Read it to know which schema and database the code at a patch runs against.',
|
|
@@ -200,10 +205,11 @@ export const SEAM_INPUTS = {
|
|
|
200
205
|
seam_github: {},
|
|
201
206
|
seam_github_import: { repo: z.string().max(201).describe('owner/name, as seam_github lists it'), installation: z.string().max(20).describe('The installation id seam_github shows for it'), into: z.string().max(63).optional().describe('The new quilt\'s slug (default the repo\'s name)'), follow: z.boolean().optional().describe('Keep following it (default true)') },
|
|
202
207
|
seam_import_remote: { url: z.string().max(2000).describe('The git remote: https://, ssh:// or user@host:path'), into: z.string().max(63).describe('The new quilt\'s slug'), follow: z.boolean().optional().describe('Keep following main and release/* until cut-over'), interval: z.number().int().min(60).optional().describe('Seconds between follow passes (default 600)'), credential: z.string().max(100).optional().describe('The name of the server secret that reads the remote (user:token or a token)'), authors: z.record(z.string(), z.string()).optional().describe('Git author email → member id of the quilt'), ask_authors: z.boolean().optional().describe('Wait in awaiting_authors with the authors and a proposed map until seam_import_authors answers') },
|
|
203
|
-
seam_export_remote: { quilt: z.string().max(100).optional().describe('The quilt'), target: z.string().max(2000).describe('The git remote'), lines: z.array(z.string().max(100)).max(20).optional().describe('Lines to export (default ["main"]); release/* for every release line'), releases: z.boolean().optional().describe('Push release tags too'), credential: z.string().max(100).optional().describe('The name of the server secret that may push'), scratch: z.boolean().optional().describe('A scratch remote nothing pins a sha from'), enabled: z.boolean().optional().describe('false turns the export off'), lfs: z.object({ url: z.string().max(2000).describe('The LFS endpoint (…/info/lfs)'), threshold_bytes: z.number().int().min(0).optional().describe('Files over this many bytes go to LFS (default 50 MiB)') }).optional().describe('Git LFS on the remote: big files and filter=lfs paths go as LFS pointers. Set before the first export') },
|
|
208
|
+
seam_export_remote: { quilt: z.string().max(100).optional().describe('The quilt'), target: z.string().max(2000).describe('The git remote'), lines: z.array(z.string().max(100)).max(20).optional().describe('Lines to export (default ["main"]); release/* for every release line'), releases: z.boolean().optional().describe('Push release tags too'), credential: z.string().max(100).optional().describe('The name of the server secret that may push'), scratch: z.boolean().optional().describe('A scratch remote nothing pins a sha from'), enabled: z.boolean().optional().describe('false turns the export off'), lfs: z.object({ url: z.string().max(2000).describe('The LFS endpoint (…/info/lfs)'), threshold_bytes: z.number().int().min(0).optional().describe('Files over this many bytes go to LFS (default 50 MiB)') }).optional().describe('Git LFS on the remote: big files and filter=lfs paths go as LFS pointers. Set before the first export'), main_as: z.string().max(100).optional().describe('The branch name main has on the remote (default main), such as master or trunk'), push: z.enum(['patch', 'tag']).optional().describe('patch (default): after every merged patch; tag: only up to the newest patch a tag or release points at'), exclude: z.array(z.string().max(500)).max(100).optional().describe('Paths left out of the exported tree: a folder ending in /, a file or a glob. Set before the first export') },
|
|
204
209
|
seam_export_archive: { quilt: z.string().max(100).optional().describe('The quilt'), out: z.string().max(1000).optional() },
|
|
205
210
|
seam_export_tarball: { quilt: z.string().max(100).optional().describe('The quilt'), rev: z.string().max(200).optional(), paths: z.array(z.string().max(1000)).max(100).optional(), out: z.string().max(1000).optional() },
|
|
206
211
|
seam_export_status: { quilt: z.string().max(100).optional().describe('The quilt') },
|
|
212
|
+
seam_export_push: { quilt: z.string().max(100).optional().describe('The quilt') },
|
|
207
213
|
seam_import_cutover: { id: z.string().max(100).describe('The import id'), credential: z.string().max(100).optional().describe('The server secret that may push to the source (default the import\'s)') },
|
|
208
214
|
seam_import_status: { id: z.string().max(100).describe('The import id') },
|
|
209
215
|
seam_import_authors: { id: z.string().max(100).describe('The import id'), authors: z.record(z.string(), z.string()).describe('Git author email → member id of the quilt') },
|
|
@@ -247,6 +253,10 @@ export const SEAM_INPUTS = {
|
|
|
247
253
|
seam_line_policy_versions: { quilt: QUILT, line: z.string().max(120), action: z.enum(['list', 'approve', 'reject', 'confirm']).optional(), version: z.number().int().min(1).optional() },
|
|
248
254
|
seam_policy: { quilt: QUILT, square: z.string().max(100).optional(), line: z.string().max(120).optional() },
|
|
249
255
|
seam_public: { quilt: QUILT, public: z.boolean() },
|
|
256
|
+
seam_quilt_delete: { quilt: QUILT, confirm: z.string().max(200).describe('The repo\'s address, <owner>/<name>, exactly as the person typed it') },
|
|
257
|
+
seam_quilts_deleted: { org: z.string().max(100).optional().describe('An org you run: its deleted repos') },
|
|
258
|
+
seam_quilt_restore: { quilt: z.string().max(200).describe('The deleted repo: its slug, id or <owner>/<name>'), org: z.string().max(100).optional().describe('The org that owned it') },
|
|
259
|
+
seam_account_log: { limit: z.number().int().min(1).max(500).optional(), before: z.string().regex(/^\d{1,19}$/).optional() },
|
|
250
260
|
seam_sealed: { quilt: QUILT, sealed: z.boolean() },
|
|
251
261
|
seam_secret_scanning: { quilt: QUILT, mode: z.enum(['refuse', 'warn', 'off']).optional() },
|
|
252
262
|
seam_seal: { quilt: QUILT, action: z.enum(['status', 'grant']).optional(), member: z.string().max(200).optional().describe('For grant: the member id to give the key to') },
|
|
@@ -530,7 +540,8 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
|
|
|
530
540
|
}
|
|
531
541
|
case 'seam_github_import': { const r = await client.importGithub({ installation_id: args.installation, repo: args.repo, slug: args.into, follow: args.follow !== false }); return `Importing ${args.repo} into ${r.id ? (args.into || args.repo.split('/')[1]) : '?'} (import ${r.id})${r.follow ? ', following it' : ''}.` }
|
|
532
542
|
case 'seam_import_remote': { const r = await client.startImport({ url: args.url, slug: args.into, follow: !!args.follow, interval: args.interval, credential: args.credential, authors: args.authors, ask_authors: !!args.ask_authors }); return `Importing ${args.url} into ${args.into} (import ${r.id})${r.ask_authors ? ', waiting for its authors first (seam_import_status, then seam_import_authors)' : ''}${r.follow ? `, following every ${r.interval_s} s; poke secret ${r.poke_secret} (shown once)` : ''}.` }
|
|
533
|
-
case 'seam_export_remote': { const r = await client.setExport(needQuilt(), { target: args.target, lines: args.lines, releases: args.releases, credential: args.credential, scratch: args.scratch, enabled: args.enabled !== false, lfs: args.lfs }); return r.enabled ? `Exporting ${r.lines.join(', ')} to ${r.target} after each merge.` : `The export to ${r.target} is off.` }
|
|
543
|
+
case 'seam_export_remote': { const r = await client.setExport(needQuilt(), { target: args.target, lines: args.lines, releases: args.releases, credential: args.credential, scratch: args.scratch, enabled: args.enabled !== false, lfs: args.lfs, main_as: args.main_as, push: args.push, exclude: args.exclude }); return r.enabled ? `Exporting ${r.lines.join(', ')} to ${r.target} ${r.push === 'tag' ? 'up to the newest tagged patch' : 'after each merge'}.` : `The export to ${r.target} is off.` }
|
|
544
|
+
case 'seam_export_push': { const r = await client.exportPush(needQuilt()); return `Pushing to ${r.target} now; seam_export_status shows how it went.` }
|
|
534
545
|
case 'seam_export_archive': {
|
|
535
546
|
if (hosted) return `This server cannot write files to your computer. Fetch the archive over HTTP with your token: POST /v1/quilts/${encodeURIComponent(needQuilt())}/exit/archive`
|
|
536
547
|
const r = await client.exitArchive(needQuilt())
|
|
@@ -548,7 +559,7 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
|
|
|
548
559
|
writeFileSync(file, r.bytes)
|
|
549
560
|
return `Wrote ${args.rev || 'main'} (${String(r.commit || 'empty').slice(0, 12)}) to ${file}: ${r.bytes.length} bytes.`
|
|
550
561
|
}
|
|
551
|
-
case 'seam_export_status': { const r = await client.exportStatus(needQuilt()); return JSON.stringify({ target: r.target, lines: r.lines, releases: r.releases, enabled: r.enabled, status: r.status }, null, 2) }
|
|
562
|
+
case 'seam_export_status': { const r = await client.exportStatus(needQuilt()); return JSON.stringify({ target: r.target, lines: r.lines, main_as: r.main_as, push: r.push, exclude: r.exclude, releases: r.releases, enabled: r.enabled, status: r.status }, null, 2) }
|
|
552
563
|
case 'seam_import_cutover': { const r = await client.cutover(args.id, { credential: args.credential }); return `Cut over: import ${r.import.id} is done; main exports to ${r.export.target} after each merge.` }
|
|
553
564
|
case 'seam_import_status': return describeImportRecord(await client.importStatus(args.id))
|
|
554
565
|
case 'seam_import_authors': { const r = await client.answerImportAuthors(args.id, { authors: args.authors }); return `Import ${r.id} has its authors and runs now.` }
|
|
@@ -591,12 +602,9 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
|
|
|
591
602
|
}
|
|
592
603
|
case 'seam_pull': {
|
|
593
604
|
if (!found) throw new Error('seam_pull works on a block folder: run seam mcp in a folder made by seam clone or seam checkout.')
|
|
605
|
+
const was = { ...block }
|
|
594
606
|
const r = await pull(client, found.dir, block)
|
|
595
|
-
|
|
596
|
-
if (r.noop) return block.square ? `Already up to date: ${block.square_short || block.square} sits on ${block.line}'s tip.` : `Already up to date with ${block.line}.`
|
|
597
|
-
if (r.left) return `${r.left.square} was ${r.left.state}; the folder is now on ${r.line} (${r.at.slice(0, 12)}, ${r.written} file(s) updated).` + kept
|
|
598
|
-
if (r.rebased) return describeWrite(r.result, 'Rebased') + `\n${r.written} file(s) updated in the folder.` + kept
|
|
599
|
-
return `Pulled ${r.line} (now ${r.at.slice(0, 12)}): ${r.written} file(s) updated.` + kept
|
|
607
|
+
return describePull(r, was, { diff: 'seam_diff', resolve: 'seam_resolve (choose ours or theirs)' })
|
|
600
608
|
}
|
|
601
609
|
case 'seam_block_rebuild': {
|
|
602
610
|
if (!found) throw new Error('seam_block_rebuild works on a block folder: run seam mcp in a folder made by seam checkout.')
|
|
@@ -638,6 +646,10 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
|
|
|
638
646
|
return describePolicyVersions(r)
|
|
639
647
|
}
|
|
640
648
|
case 'seam_orgs': return args.org ? describeOrg(await client.org(args.org)) : describeOrgs(await client.orgs())
|
|
649
|
+
case 'seam_quilt_delete': return describeDeletedQuilt(await client.deleteQuilt(needQuilt(), args.confirm))
|
|
650
|
+
case 'seam_quilts_deleted': return describeDeletedQuilts(await client.deletedQuilts({ org: args.org }), { org: args.org })
|
|
651
|
+
case 'seam_quilt_restore': { const r = await client.undeleteQuilt(args.quilt, { org: args.org }); return `Restored ${r.path || r.quilt}: it is back as it was. The squares the delete closed stay closed; seam_reopen brings one back.` }
|
|
652
|
+
case 'seam_account_log': return describePersonLog(await client.personLog({ limit: args.limit, before: args.before }))
|
|
641
653
|
case 'seam_org_log': return describeOrgLog(await client.orgLog(args.org, { limit: args.limit, before: args.before }))
|
|
642
654
|
case 'seam_apps': return describeApps(await client.apps({ verified: !!args.verified }))
|
|
643
655
|
case 'seam_app': {
|
package/package.json
CHANGED