@heyquilt/seam 0.144.0 → 0.146.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 CHANGED
@@ -21,7 +21,8 @@ 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)
27
28
  seam oauth allowed [list | revoke <grant id>] the apps you allowed into your account, and ending one
@@ -100,13 +101,15 @@ Docs: https://seam.heyquilt.com/docs
100
101
  seam org team <org> <name> [<member>...] an org's team, named org_team:<name> in its quilts' protections
101
102
  seam quilts new <slug> --org <org> | quilts move --quilt <q> --org <org> a quilt that belongs to an org
102
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
103
106
  seam quilts agents-make-lines on|off whether agents may make lines, tags and releases
104
107
  seam quilts public on|off anyone may read the quilt, signed in or not
105
108
  seam quilts add <email|member> [--level read|write|admin] add a member; an email with no account yet is invited
106
109
  seam quilts new <slug> --sealed a quilt the server never reads: its key lives on members' computers
107
110
  seam quilts sealed on|off --quilt <q> switch a quilt that holds no files yet to sealed or back
108
111
  seam seal [status] | key | grant <member> | rotate [--without <member>] a sealed quilt's key
109
- seam events [--follow|--webhook <url>] What happened to your squares
112
+ seam events [--follow|--webhook <url>|list] What happened to your squares
110
113
  seam export git <dir> Back to git
111
114
  seam mcp The same commands as MCP tools, for your AI tool
112
115
  seam mcp --as-agent [--merge] The same, with the AI tool as an agent of yours, on a token of its own
@@ -141,7 +144,7 @@ async function main () {
141
144
  const r = await go('PUT', '/v1/me/handle', { handle: argv[0] })
142
145
  return console.log(`Your handle is ${r.handle}: your repos are ${r.handle}/<repo> now, and their old addresses stop working.`)
143
146
  }
144
- if (cmd === 'account') {
147
+ if (cmd === 'account' && argv[0] !== 'log') {
145
148
  const i = argv.indexOf('--confirm')
146
149
  if (argv[0] !== 'delete' || i < 0 || !argv[i + 1]) fail('seam account delete --confirm <your email>')
147
150
  const r = await signedIn()('DELETE', `/v1/me?confirm=${encodeURIComponent(argv[i + 1])}`)
@@ -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
- return { rebased: true, square: r.square, result: r, written: s.written, merged: s.merged, conflicted: s.conflicted }
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, describeWebhooks } 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 = []
@@ -153,6 +153,24 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
153
153
  const r = await client.renameQuilt(needQuilt(), { ...(pos[1] ? { slug: pos[1] } : {}), ...(flags['--title'] ? { name: flags['--title'] } : {}) })
154
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
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
+ }
156
174
  if (pos[0] === 'sealed') {
157
175
  if (!['on', 'off'].includes(pos[1])) throw new Error('seam quilts sealed on|off --quilt <q>')
158
176
  const r = await client.setSealed(needQuilt(), pos[1] === 'on')
@@ -250,9 +268,10 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
250
268
  return print(x, `Released ${x.name} at ${where(x)}. Read it as tag:${x.name}.`)
251
269
  }
252
270
  case 'events': {
253
- // seam events [--after n] [--kinds a,b] [--wait seconds | --follow] | --webhook <url> [--add | --remove] [--secret s] [--kinds a,b] | --webhook off
271
+ // seam events [--after n] [--kinds a,b] [--wait seconds | --follow] | --webhook <url> [--add | --remove] [--secret s] [--kinds a,b] | --webhook off | --webhook list
254
272
  const kinds = flags['--kinds'] ? String(flags['--kinds']).split(',') : undefined
255
273
  if (flags['--webhook']) {
274
+ if (flags['--webhook'] === 'list') { const r = await client.webhooks(needQuilt()); return print(r, describeWebhooks(r)) }
256
275
  const off = flags['--webhook'] === 'off'
257
276
  if (!off && flags['--remove']) { const r = await client.subscribe(needQuilt(), { url: flags['--webhook'], remove: true }); return print(r, `Webhook to ${r.url} removed.${r.hooks.length ? ` Still posting to ${r.hooks.map((h) => h.url).join(', ')}.` : ''}`) }
258
277
  const r = await client.subscribe(needQuilt(), off ? { url: null } : { url: flags['--webhook'], secret: flags['--secret'], events: kinds, ...(flags['--add'] ? { add: true } : {}) })
@@ -351,12 +370,9 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
351
370
  }
352
371
  case 'pull': {
353
372
  if (!found) throw new Error('seam pull works in a block folder: run it in a folder made by seam clone or seam checkout.')
373
+ const was = { ...block }
354
374
  const r = await pull(client, found.dir, block)
355
- const kept = describeBoth(r)
356
- 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}.`)
357
- 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)
358
- if (r.rebased) return print(r, describeWrite(r.result, 'Rebased') + `\n${r.written} file(s) updated in the folder.` + kept)
359
- return print(r, `Pulled ${r.line} (now ${r.at.slice(0, 12)}): ${r.written} file(s) updated.` + kept)
375
+ return print(r, describePull(r, was))
360
376
  }
361
377
  case 'checkout': {
362
378
  const target = pos[0]
@@ -689,6 +705,12 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
689
705
  const r = await client.editApp(pos[1], b)
690
706
  return print(r, describeApp(r))
691
707
  }
708
+ case 'account': {
709
+ // seam account log [-n <count>] [--before <id>]: what happened to your repos (11.13). seam account delete is the seam package's.
710
+ if (pos[0] !== 'log') throw new Error('seam account log [-n <count>] [--before <id>] | seam account delete --confirm <your email>')
711
+ const r = await client.personLog({ limit: flags['-n'], before: flags['--before'] })
712
+ return print(r, describePersonLog(r))
713
+ }
692
714
  case 'org': {
693
715
  // seam org [list] | new <slug> [--title <name>] | show <org> | add <org> <member> [--role member|admin|owner] | remove <org> <member>
694
716
  const sub = pos[0] || 'list'
@@ -207,6 +207,7 @@ export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch,
207
207
  revert: (q, b) => call('POST', `${Q(q)}/revert`, b),
208
208
  events: (q, o = {}) => call('GET', `${Q(q)}/events${qs(o)}`),
209
209
  subscribe: (q, b) => call('POST', `${Q(q)}/webhook`, b),
210
+ webhooks: (q) => call('GET', `${Q(q)}/webhook`),
210
211
  show: (q, rev, o = {}) => call('GET', `${Q(q)}/show/${enc(rev)}${qs(o)}`),
211
212
  exportPatches: (q, o = {}) => call('GET', `${Q(q)}/export/git${qs(o)}`),
212
213
  log: (q, o = {}) => call('GET', `${Q(q)}/log${qs(o)}`),
@@ -237,6 +238,7 @@ export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch,
237
238
  search: (o = {}) => call('GET', `/v1/search${qs(o)}`),
238
239
  searchCode: (q, o = {}) => call('GET', `${Q(q)}/search${qs(o)}`),
239
240
  bisect: (q, b) => call('POST', `${Q(q)}/bisect`, b),
241
+ lineLasts: (q, line = 'main', paths = []) => call('GET', `${Q(q)}/lines/${encodeURIComponent(line)}/lasts${qs({ paths: paths.join(',') })}`),
240
242
  linePatches: (q, line = 'main', o = {}) => call('GET', `${Q(q)}/lines/${encodeURIComponent(line)}/patches${qs(o)}`),
241
243
  diff: (q, o = {}) => call('GET', `${Q(q)}/diff${qs(o)}`),
242
244
  resolutions: async (q, p, { hunks = false } = {}) => (await call('GET', `${Q(q)}/resolutions${qs({ path: p || undefined, hunks: hunks ? 1 : undefined })}`)).resolutions,
@@ -346,6 +348,11 @@ export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch,
346
348
  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
349
  setQuiltOwner: (q, org) => call('PUT', `${Q(q)}/owner`, { org }),
348
350
  renameQuilt: (q, body) => call('PATCH', Q(q), body),
351
+ // Deleting a repo (11.13): confirm is its address, <owner>/<slug>; restored for 7 days by id, slug or address.
352
+ deleteQuilt: (q, confirm) => call('DELETE', `${Q(q)}${qs({ confirm })}`),
353
+ deletedQuilts: ({ org } = {}) => call('GET', org ? `/v1/orgs/${enc(org)}/deleted-quilts` : '/v1/me/deleted-quilts'),
354
+ undeleteQuilt: (q, { org } = {}) => call('POST', `${org ? `/v1/orgs/${enc(org)}` : '/v1/me'}/deleted-quilts/${enc(q)}/restore`, {}),
355
+ personLog: ({ limit, before } = {}) => call('GET', `/v1/me/log${qs({ limit, before })}`),
349
356
  setAgentsMakeLines: (q, allow) => call('PUT', `${Q(q)}/agents-make-lines`, { allow }),
350
357
  setPublic: (q, value) => call('PUT', `${Q(q)}/public`, { public: value }),
351
358
  setSealed: (q, value) => call('PUT', `${Q(q)}/sealed`, { sealed: value }),
@@ -19,20 +19,22 @@ export function describeSquare (s) {
19
19
  }
20
20
 
21
21
  export function describeLog (r, { oneline = false, graph = false } = {}) {
22
- const more = r.cursor ? `(more: seam log … --cursor ${r.cursor})` : r.more ? (oneline ? '…' : '(more: seam log -n <count>)') : null
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 ? [`${r.partial ? '(partial) ' : ''}${more}`] : [])].join('\n')
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 ? `No commits on this page.\n${more}` : r.existed === false ? 'No commits: this path was never here.' : 'No commits.'
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(`${r.partial ? '(partial) ' : ''}${more}`)
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: run seam checkout ' + st.square.short + ' after committing.')
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}`))
@@ -176,6 +193,13 @@ export function describeMerge (r) {
176
193
  }
177
194
 
178
195
  /** Events as one line each (12's inbox line: kind, who, square, paths, patch, would_conflict). */
196
+ /** Your webhooks in a quilt (GET /webhook): each url with the events it hears; never a secret. */
197
+ export function describeWebhooks (r) {
198
+ const hooks = r.hooks || []
199
+ if (!hooks.length) return 'No webhooks of yours in this quilt.'
200
+ return hooks.map((h) => `${h.url} (${h.events.length > 4 ? `${h.events.length} kinds of event` : h.events.join(', ')})`).join('\n')
201
+ }
202
+
179
203
  export function describeEvents (r) {
180
204
  if (!r.events.length) return `No new events (cursor ${r.after}).`
181
205
  return [...r.events.map(eventLine), `(next: after=${r.after})`].join('\n')
@@ -572,6 +596,31 @@ export const describeInstalls = (r) => r.installations.length ? r.installations.
572
596
 
573
597
  export const describeApps = (r) => r.apps.length ? r.apps.map((a) => describeApp(a)).join('\n\n') : 'No apps.'
574
598
 
599
+ // The rows deleting a repo writes in its owner's log (11.13), the org's and a person's alike.
600
+ const QUILT_LOG = {
601
+ quilt_deleted: (d) => `deleted the repo ${d.slug}${d.closed ? ` (closed ${d.closed} agent square${d.closed === 1 ? '' : 's'})` : ''}`,
602
+ quilt_restored: (d) => `restored the repo ${d.slug}`,
603
+ quilt_purged: (d) => `purged the repo ${d.slug}${d.reason === 'account_deleted' ? ' (the account was deleted)' : ', 7 days after it was deleted'}`
604
+ }
605
+
606
+ /** A deleted repo (11.13): when it is purged, and how to bring it back. */
607
+ export function describeDeletedQuilt (r) {
608
+ const org = String(r.owner || '').startsWith('org:') && r.path ? r.path.split('/')[0] : null
609
+ 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.` : ''}`
610
+ }
611
+
612
+ /** Recently deleted: yours or an org's, each with when it is purged. */
613
+ export function describeDeletedQuilts (r, { org } = {}) {
614
+ if (!r.quilts.length) return `No deleted repos${org ? ` in ${org}` : ''}: a deleted repo stays here 7 days.`
615
+ 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')
616
+ }
617
+
618
+ /** A person's log (11.13): what happened to their repos, newest first. */
619
+ export function describePersonLog (r) {
620
+ if (!r.log.length) return 'Your log is empty.'
621
+ 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')
622
+ }
623
+
575
624
  export function describeOrgLog (r) {
576
625
  const what = (x) => {
577
626
  const d = x.data || {}
@@ -581,6 +630,7 @@ export function describeOrgLog (r) {
581
630
  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
631
  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
632
  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}`
633
+ if (x.kind.startsWith('quilt_') && QUILT_LOG[x.kind]) return QUILT_LOG[x.kind](d)
584
634
  if (x.kind === 'quilt_in') return `moved the quilt ${d.slug} in (from ${d.from})`
585
635
  if (x.kind === 'quilt_out') return `moved the quilt ${d.slug} out (to ${d.to})`
586
636
  return `${x.kind} ${JSON.stringify(d)}`
@@ -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, describeWebhooks } 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_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']
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. ' +
@@ -85,7 +85,7 @@ export const SEAM_DESCRIPTIONS = {
85
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.',
86
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.',
87
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).',
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, 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.',
89
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.',
90
90
  seam_block_close: 'Closes a block (its row on the server). The square, its work and any files on disk are untouched.',
91
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.',
@@ -97,6 +97,10 @@ export const SEAM_DESCRIPTIONS = {
97
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.',
98
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.',
99
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.',
100
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.',
101
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.',
102
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.',
@@ -137,11 +141,11 @@ export const SEAM_DESCRIPTIONS = {
137
141
  seam_share: 'Adds a writer to a square you hold, for pairing: up to three people besides you, each able to write to the quilt. An agent with its own account gets its own square instead.',
138
142
  seam_unshare: 'Takes a writer off a square you hold, or takes you off one you were shared on.',
139
143
  seam_events: 'What happened to your work since you last looked: patch.merged (a patch changed files your open square changed, and whether your merge would now conflict), conflict.added (a conflict was kept in your square), square.handoff, square.restacked (your stacked square was moved onto its parent\'s new head or the line, with how many conflicts it now holds), check.requested (you run a check a line names you for: run it on the commit and report with seam_check and its run_key). Pass after (the cursor from the last answer) to get only new ones. Pass wait (seconds, up to 30) to wait for the next one instead of answering at once.',
140
- seam_events_webhook: 'Has the quilt server POST your events to a URL (signed with HMAC-SHA256 over <timestamp>.<body>, retried 3 times), so you are woken instead of polling: url, events (default all), secret (made for you if left out). url: "off" removes them all. add: true keeps your other webhooks (up to 5); remove: true removes the one at url.',
144
+ seam_events_webhook: 'Has the quilt server POST your events to a URL (signed with HMAC-SHA256 over <timestamp>.<body>, retried 3 times), so you are woken instead of polling: url, events (default all), secret (made for you if left out). url: "off" removes them all; url: "list" lists yours (url and events, never the secret). add: true keeps your other webhooks (up to 5); remove: true removes the one at url.',
141
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.',
142
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.',
143
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.',
144
- 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.',
145
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.',
146
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.',
147
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.',
@@ -249,6 +253,10 @@ export const SEAM_INPUTS = {
249
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() },
250
254
  seam_policy: { quilt: QUILT, square: z.string().max(100).optional(), line: z.string().max(120).optional() },
251
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() },
252
260
  seam_sealed: { quilt: QUILT, sealed: z.boolean() },
253
261
  seam_secret_scanning: { quilt: QUILT, mode: z.enum(['refuse', 'warn', 'off']).optional() },
254
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') },
@@ -494,6 +502,7 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
494
502
  case 'seam_reopen': { const sq = await client.reopen(needQuilt(), needSquare()); return `Reopened ${describeSquare(sq)}.` }
495
503
  case 'seam_events': return describeEvents(await client.events(needQuilt(), { after: args.after, kinds: args.kinds, ...(args.wait ? { wait: args.wait } : {}) }))
496
504
  case 'seam_events_webhook': {
505
+ if (args.url === 'list') return describeWebhooks(await client.webhooks(needQuilt()))
497
506
  if (args.url === 'off') { await client.subscribe(needQuilt(), { url: null }); return 'Webhooks removed.' }
498
507
  if (args.remove) { const r = await client.subscribe(needQuilt(), { url: args.url, remove: true }); return `Webhook to ${r.url} removed.${r.hooks.length ? ` Still posting to ${r.hooks.map((h) => h.url).join(', ')}.` : ''}` }
499
508
  const r = await client.subscribe(needQuilt(), { url: args.url, events: args.events, secret: args.secret, ...(args.add ? { add: true } : {}) })
@@ -594,12 +603,9 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
594
603
  }
595
604
  case 'seam_pull': {
596
605
  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.')
606
+ const was = { ...block }
597
607
  const r = await pull(client, found.dir, block)
598
- const kept = describeBoth(r, { diff: 'seam_diff', resolve: 'seam_resolve (choose ours or theirs)' })
599
- 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}.`
600
- 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
601
- if (r.rebased) return describeWrite(r.result, 'Rebased') + `\n${r.written} file(s) updated in the folder.` + kept
602
- return `Pulled ${r.line} (now ${r.at.slice(0, 12)}): ${r.written} file(s) updated.` + kept
608
+ return describePull(r, was, { diff: 'seam_diff', resolve: 'seam_resolve (choose ours or theirs)' })
603
609
  }
604
610
  case 'seam_block_rebuild': {
605
611
  if (!found) throw new Error('seam_block_rebuild works on a block folder: run seam mcp in a folder made by seam checkout.')
@@ -641,6 +647,10 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
641
647
  return describePolicyVersions(r)
642
648
  }
643
649
  case 'seam_orgs': return args.org ? describeOrg(await client.org(args.org)) : describeOrgs(await client.orgs())
650
+ case 'seam_quilt_delete': return describeDeletedQuilt(await client.deleteQuilt(needQuilt(), args.confirm))
651
+ case 'seam_quilts_deleted': return describeDeletedQuilts(await client.deletedQuilts({ org: args.org }), { org: args.org })
652
+ 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.` }
653
+ case 'seam_account_log': return describePersonLog(await client.personLog({ limit: args.limit, before: args.before }))
644
654
  case 'seam_org_log': return describeOrgLog(await client.orgLog(args.org, { limit: args.limit, before: args.before }))
645
655
  case 'seam_apps': return describeApps(await client.apps({ verified: !!args.verified }))
646
656
  case 'seam_app': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyquilt/seam",
3
- "version": "0.144.0",
3
+ "version": "0.146.0",
4
4
  "description": "seam: Seam's command. Git's verbs for source control that runs on the server, built for many agents and people at once.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",