@heyquilt/seam 0.131.0 → 0.140.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
@@ -87,6 +87,13 @@ Docs: https://seam.heyquilt.com/docs
87
87
  seam policy [sq] seam line require <line> --rules rules.json change classes and rules by path and class
88
88
  seam policy versions <line> | approve|reject|confirm <line> <version> a line's policy versions and their approval
89
89
  seam org [new <slug> | show <org> | add <org> <member> [--role r] | remove <org> <member>] orgs that own quilts
90
+ seam app [list [--verified]] | new <handle> --name <n> [--org <org>] | show|edit|remove <handle> apps: any tool that connects to Seam, with the permissions and events it asks for
91
+ seam app key new|list <handle> | key remove <handle> <id> an app's private keys (the private half shown once; up to ten live)
92
+ seam app install <handle> [--org <org>] [--repos all|a,b] put an app on your account or an org you run, on all repos or chosen ones
93
+ seam app installs [--org <org>] | repos <installation> all|a,b | accept|pause|resume|uninstall <installation> the apps installed there
94
+ seam app request <handle> --org <org> [--repos a,b] [-m <why>] | requests --org <org> [--all] | approve|decline <request> --org <org> ask an org's admins for an app; they decide
95
+ seam app manager add|remove <handle> <member or email> people who edit an app without being its org's owners
96
+ seam org domain add|check|remove <org> <domain> verify an org by a TXT record at _seam-verify.<domain>
90
97
  seam org log <org> [-n <count>] [--before <id>] what happened in an org: members, roles, teams, quilts (owners and admins)
91
98
  seam org team <org> <name> [<member>...] an org's team, named org_team:<name> in its quilts' protections
92
99
  seam quilts new <slug> --org <org> | quilts move --quilt <q> --org <org> a quilt that belongs to an org
package/lib/quilts/cli.js CHANGED
@@ -11,7 +11,7 @@ import { startDaemon, askDaemon } from './daemon.js'
11
11
  import { rebuild as rebuildBlock, conflictStyle } from './block.js'
12
12
  import { describeRebuild, describeOpened } from './describe.js'
13
13
  import { findBlock, checkout, status as blockStatus, commitFolder, amendFolder, syncTo, localDiff, saveBlock, pull, toLineTip, restore, reset, clean, localChanges, bytesOf, livePatch } from './block.js'
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, describeOrgTeams, describeCheckRuns, describeRerun, describeAncestor, describeContains, describePipeline, describeWatches, describeSearch, describePublicReads } from './describe.js'
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'
16
16
  import { confirmWithLocalKey } from './linepolicy.js'
17
17
  import { describeAgentToken, describeAgentTokens } from './agent-tokens.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', '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'])
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(['--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'])
87
87
 
88
88
  export function parse (argv) {
89
89
  const pos = []; const flags = {}; const paths = []
@@ -624,12 +624,78 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
624
624
  }
625
625
  throw new Error('seam token [list] | new <name> [--level read|write] [--days 1-365] | revoke <id or name>')
626
626
  }
627
+ case 'app': {
628
+ // seam app [list [--verified]] | new <handle> | show <handle> | edit <handle> | remove <handle> (19.4)
629
+ const sub = pos[0] || 'list'
630
+ if (sub === 'list') { const r = await client.apps({ verified: !!flags['--verified'] }); return print(r, describeApps(r)) }
631
+ if (sub === 'key') {
632
+ // seam app key new|list <handle> | remove <handle> <id>
633
+ const [, act, h, id] = pos
634
+ if (!['new', 'list', 'remove'].includes(act) || !h || (act === 'remove' && !id)) throw new Error('seam app key new|list <handle> | seam app key remove <handle> <id>')
635
+ const r = act === 'new' ? await client.addAppKey(h) : act === 'list' ? await client.appKeys(h) : await client.removeAppKey(h, id)
636
+ return print(r, describeAppKey(r))
637
+ }
638
+ if (['request', 'requests', 'approve', 'decline'].includes(sub)) {
639
+ // seam app request <handle> --org <org> [--repos a,b] [-m <why>] | requests --org <org> [--all] | approve|decline <request id> --org <org>
640
+ const org = typeof flags['--org'] === 'string' ? flags['--org'] : null
641
+ if (!org || (sub !== 'requests' && !pos[1])) throw new Error('seam app request <handle> --org <org> [--repos a,b] [-m <why>] | requests --org <org> [--all] | approve|decline <request> --org <org>')
642
+ if (sub === 'requests') { const r = await client.orgRequests(org, { all: !!flags['--all'] }); return print(r, describeRequests(r)) }
643
+ const r = sub === 'request' ? await client.requestApp(pos[1], { org, repos: typeof flags['--repos'] === 'string' && flags['--repos'] !== 'all' ? flags['--repos'].split(',').map((x) => x.trim()).filter(Boolean) : 'all', ...(typeof flags['-m'] === 'string' ? { note: flags['-m'] } : {}) })
644
+ : await client.decideRequest(org, pos[1], sub)
645
+ return print(r, describeRequest(r))
646
+ }
647
+ if (['install', 'installs', 'repos', 'accept', 'pause', 'resume', 'uninstall'].includes(sub)) {
648
+ // seam app install <handle> [--org <org>] [--repos all|a,b] | installs [--org <org>] | repos <install id> all|a,b | pause|resume|uninstall <install id>
649
+ const repos = (v) => v == null || v === true || v === 'all' ? 'all' : String(v).split(',').map((x) => x.trim()).filter(Boolean)
650
+ const str = (f) => typeof flags[f] === 'string' ? flags[f] : undefined
651
+ if (sub === 'installs') { const r = str('--org') ? await client.orgInstallations(str('--org')) : await client.myInstallations(); return print(r, describeInstalls(r)) }
652
+ if (!pos[1] || (sub === 'repos' && !pos[2])) throw new Error('seam app install <handle> [--org <org>] [--repos all|<quilt>,<quilt>] | installs [--org <org>] | repos <installation> all|<quilt>,… | pause|resume|uninstall <installation>')
653
+ const r = sub === 'install' ? await client.installApp(pos[1], { ...(str('--org') ? { org: str('--org') } : {}), repos: repos(flags['--repos']) })
654
+ : sub === 'repos' ? await client.editInstallation(pos[1], { repos: repos(pos[2]) })
655
+ : sub === 'accept' ? await client.acceptInstallation(pos[1]) : sub === 'pause' ? await client.pauseInstallation(pos[1]) : sub === 'resume' ? await client.resumeInstallation(pos[1]) : await client.deleteInstallation(pos[1])
656
+ return print(r, describeInstall(r))
657
+ }
658
+ if (sub === 'manager') {
659
+ // seam app manager add|remove <handle> <person or email>
660
+ const [, act, h, who] = pos
661
+ if (!['add', 'remove'].includes(act) || !h || !who) throw new Error('seam app manager add|remove <handle> <person:id or email>')
662
+ const r = act === 'add' ? await client.addAppManager(h, who) : await client.removeAppManager(h, who)
663
+ return print(r, describeApp(r))
664
+ }
665
+ const usage = 'seam app [list [--verified]] | new <handle> --name <name> [--org <org>] | show <handle> | edit <handle> | remove <handle> | key new|list|remove <handle> [id] | manager add|remove <handle> <member> | install <handle> [--org <org>] [--repos a,b] | installs [--org <org>] | repos|accept|pause|resume|uninstall <installation> | request <handle> --org <org> | requests --org <org> | approve|decline <request> --org <org> | disable <handle> [--reason <why>] | enable <handle> (Seam staff); new and edit take --name --description --homepage --callback --setup --webhook <url>, --events a,b, --perm contents=read,reviews=write, --public|--private, and edit --new-secret'
666
+ if (!['new', 'show', 'edit', 'remove', 'disable', 'enable'].includes(sub) || !pos[1]) throw new Error(usage)
667
+ if (sub === 'disable') { const r = await client.disableApp(pos[1], typeof flags['--reason'] === 'string' ? flags['--reason'] : undefined); return print(r, describeApp(r)) }
668
+ if (sub === 'enable') { const r = await client.enableApp(pos[1]); return print(r, describeApp(r)) }
669
+ if (sub === 'show') { const r = await client.app(pos[1]); return print(r, describeApp(r)) }
670
+ if (sub === 'remove') { const r = await client.deleteApp(pos[1]); return print(r, describeApp(r)) }
671
+ const b = {}
672
+ const str = (f) => typeof flags[f] === 'string' ? flags[f] : undefined
673
+ if (str('--name') !== undefined) b.name = str('--name')
674
+ if (str('--description') !== undefined) b.description = str('--description')
675
+ for (const [f, k] of [['--homepage', 'homepage'], ['--callback', 'callback_url'], ['--setup', 'setup_url'], ['--webhook', 'webhook_url']]) if (str(f) !== undefined) b[k] = str(f)
676
+ if (str('--events') !== undefined) b.events = str('--events').split(',').map((x) => x.trim()).filter(Boolean)
677
+ if (str('--perm') !== undefined) b.permissions = Object.fromEntries(str('--perm').split(',').map((x) => x.trim().split('=')).filter((x) => x[0]))
678
+ if (flags['--public']) b.public = true
679
+ if (flags['--private']) b.public = false
680
+ if (sub === 'new') { const r = await client.createApp({ handle: pos[1], name: b.name || pos[1], ...(str('--org') ? { org: str('--org') } : {}), ...b }); return print(r, describeApp(r)) }
681
+ if (flags['--new-secret']) b.webhook_secret = true
682
+ if (str('--org') !== undefined) b.org = str('--org')
683
+ const r = await client.editApp(pos[1], b)
684
+ return print(r, describeApp(r))
685
+ }
627
686
  case 'org': {
628
687
  // seam org [list] | new <slug> [--title <name>] | show <org> | add <org> <member> [--role member|admin|owner] | remove <org> <member>
629
688
  const sub = pos[0] || 'list'
630
689
  if (sub === 'list') { const r = await client.orgs(); return print(r, describeOrgs(r)) }
631
690
  if (sub === 'new') { if (!pos[1]) throw new Error('seam org new <slug>'); const r = await client.createOrg({ slug: pos[1], name: flags['--title'] }); return print(r, `Made org ${r.slug}; you are its owner. Make its quilts with seam quilts new <slug> --org ${r.slug}.`) }
632
691
  if (sub === 'show') { if (!pos[1]) throw new Error('seam org show <org>'); const r = await client.org(pos[1]); return print(r, describeOrg(r)) }
692
+ if (sub === 'domain') {
693
+ // seam org domain add|check|remove <org> <domain>
694
+ const [, act, o, d] = pos
695
+ if (!['add', 'check', 'remove'].includes(act) || !o || !d) throw new Error('seam org domain add|check|remove <org> <domain>')
696
+ const r = act === 'add' ? await client.addOrgDomain(o, d) : act === 'check' ? await client.checkOrgDomain(o, d) : await client.removeOrgDomain(o, d)
697
+ return print(r, describeOrgDomain(r))
698
+ }
633
699
  if (sub === 'log') { if (!pos[1]) throw new Error('seam org log <org> [-n <count>] [--before <id>]'); const r = await client.orgLog(pos[1], { limit: flags['-n'], before: flags['--before'] }); return print(r, describeOrgLog(r)) }
634
700
  if (sub === 'add' || sub === 'remove') {
635
701
  if (!pos[1] || !pos[2]) throw new Error(`seam org ${sub} <org> <member>${sub === 'add' ? ' [--role member|admin|owner]' : ''}`)
@@ -642,7 +708,7 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
642
708
  const r = await client.setOrgTeam(pos[1], pos[2], pos.slice(3))
643
709
  return print(r, describeOrgTeams(r))
644
710
  }
645
- throw new Error('seam org [list] | new <slug> | show <org> | log <org> | add <org> <member> [--role r] | remove <org> <member> | team <org> <name> [<member>...]')
711
+ throw new Error('seam org [list] | new <slug> | show <org> | log <org> | domain add|check|remove <org> <domain> | add <org> <member> [--role r] | remove <org> <member> | team <org> <name> [<member>...]')
646
712
  }
647
713
  case 'policy': {
648
714
  // seam policy versions|approve|reject|confirm <line> [<version>]: a line's policy versions (11.4)
@@ -311,8 +311,35 @@ export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch,
311
311
  createAgentToken: (q, b) => call('POST', `${Q(q)}/agent-tokens`, b),
312
312
  revokeAgentToken: (q, id) => call('DELETE', `${Q(q)}/agent-tokens/${enc(id)}`),
313
313
  orgs: () => call('GET', '/v1/orgs'),
314
+ apps: ({ verified } = {}) => call('GET', `/v1/apps${verified ? '?verified=1' : ''}`),
315
+ createApp: (b) => call('POST', '/v1/apps', b),
316
+ app: (h) => call('GET', `/v1/apps/${enc(h)}`),
317
+ editApp: (h, b) => call('PATCH', `/v1/apps/${enc(h)}`, b),
318
+ addAppManager: (h, member) => call('POST', `/v1/apps/${enc(h)}/managers`, { member }),
319
+ removeAppManager: (h, member) => call('DELETE', `/v1/apps/${enc(h)}/managers/${enc(member)}`),
320
+ installApp: (h, b = {}) => call('POST', `/v1/apps/${enc(h)}/installations`, b),
321
+ requestApp: (h, b) => call('POST', `/v1/apps/${enc(h)}/requests`, b),
322
+ orgRequests: (o, { all } = {}) => call('GET', `/v1/orgs/${enc(o)}/requests${all ? '?all=1' : ''}`),
323
+ decideRequest: (o, id, verdict) => call('POST', `/v1/orgs/${enc(o)}/requests/${enc(id)}/${verdict === 'decline' ? 'decline' : 'approve'}`, {}),
324
+ myInstallations: () => call('GET', '/v1/me/installations'),
325
+ orgInstallations: (o) => call('GET', `/v1/orgs/${enc(o)}/installations`),
326
+ installation: (id) => call('GET', `/v1/installations/${enc(id)}`),
327
+ editInstallation: (id, b) => call('PATCH', `/v1/installations/${enc(id)}`, b),
328
+ acceptInstallation: (id) => call('POST', `/v1/installations/${enc(id)}/accept`, {}),
329
+ pauseInstallation: (id) => call('POST', `/v1/installations/${enc(id)}/pause`, {}),
330
+ resumeInstallation: (id) => call('POST', `/v1/installations/${enc(id)}/resume`, {}),
331
+ deleteInstallation: (id) => call('DELETE', `/v1/installations/${enc(id)}`),
332
+ appKeys: (h) => call('GET', `/v1/apps/${enc(h)}/keys`),
333
+ addAppKey: (h) => call('POST', `/v1/apps/${enc(h)}/keys`, {}),
334
+ removeAppKey: (h, id) => call('DELETE', `/v1/apps/${enc(h)}/keys/${enc(id)}`),
335
+ deleteApp: (h) => call('DELETE', `/v1/apps/${enc(h)}`),
336
+ disableApp: (h, reason) => call('POST', `/v1/apps/${enc(h)}/disable`, reason ? { reason } : {}),
337
+ enableApp: (h) => call('POST', `/v1/apps/${enc(h)}/enable`, {}),
314
338
  createOrg: (b) => call('POST', '/v1/orgs', b),
315
339
  org: (o) => call('GET', `/v1/orgs/${enc(o)}`),
340
+ addOrgDomain: (o, domain) => call('POST', `/v1/orgs/${enc(o)}/domains`, { domain }),
341
+ checkOrgDomain: (o, d) => call('POST', `/v1/orgs/${enc(o)}/domains/${enc(d)}/check`, {}),
342
+ removeOrgDomain: (o, d) => call('DELETE', `/v1/orgs/${enc(o)}/domains/${enc(d)}`),
316
343
  orgLog: (o, { limit, before } = {}) => call('GET', `/v1/orgs/${enc(o)}/log${qs({ limit, before })}`),
317
344
  // A member id, or the email of a person who has signed in to Seam.
318
345
  setOrgTeam: (o, name, members) => call('PUT', `/v1/orgs/${enc(o)}/teams/${enc(name)}`, { members }),
@@ -494,16 +494,79 @@ export function describeOrg (o) {
494
494
  const members = (o.members || []).map((m) => ` ${m.role.padEnd(6)} ${m.name ? `${m.name} ` : ''}${m.member_id}`)
495
495
  const quilts = (o.quilts || []).map((q) => ` ${q.slug} ${q.id}`)
496
496
  const teams = (o.teams || []).map((t) => ` ${t.name}: ${t.members.join(', ')}`)
497
- return [`${o.slug}${o.name && o.name !== o.slug ? ` (${o.name})` : ''}${o.role ? `: you are ${o.role === 'admin' ? 'an' : 'a'} ${o.role}` : ''}`, ...(members.length ? ['Members:', ...members] : []), ...(teams.length ? ['Teams (org_team:<name> in its quilts\' protections):', ...teams] : []), ...(quilts.length ? ['Quilts:', ...quilts] : o.members ? ['No quilts yet.'] : [])].join('\n')
497
+ const domains = (o.domains || []).map((d) => ` ${d.domain} ${d.verified ? 'verified' : 'not verified'}`)
498
+ return [`${o.slug}${o.name && o.name !== o.slug ? ` (${o.name})` : ''}${o.role ? `: you are ${o.role === 'admin' ? 'an' : 'a'} ${o.role}` : ''}${o.verified ? ' (verified)' : ''}`, ...(domains.length ? ['Domains:', ...domains] : []), ...(members.length ? ['Members:', ...members] : []), ...(teams.length ? ['Teams (org_team:<name> in its quilts\' protections):', ...teams] : []), ...(quilts.length ? ['Quilts:', ...quilts] : o.members ? ['No quilts yet.'] : [])].join('\n')
498
499
  }
499
500
 
501
+ /** An org's domain (19.4): verified, or the TXT record that verifies it. */
502
+ export const describeOrgDomain = (d) => d.removed ? `Removed ${d.domain}.` : d.verified
503
+ ? `${d.domain} is verified: the org is verified while the record ${d.record} stays.`
504
+ : `${d.domain} is not verified yet. Add a TXT record at ${d.record} holding\n\n ${d.value}\n\nthen run seam org domain check <org> ${d.domain} (records can take a while to appear).`
505
+
500
506
  /** An org's log (19.4), newest first: when, who, what. */
507
+ /** An app (19.4): who owns it, what it may do and hear; a secret made for it, shown once. */
508
+ export function describeApp (a) {
509
+ if (a.removed) return `Removed the app ${a.handle}.`
510
+ const owner = a.owner?.kind === 'org' ? `the org ${a.owner.slug || a.owner.id}${a.owner.verified ? ' (verified)' : ' (unverified)'}` : (a.owner?.name || a.owner?.id)
511
+ const perms = Object.entries(a.permissions || {}).filter(([, l]) => l !== 'none').map(([r, l]) => `${r} ${l}`)
512
+ return [
513
+ `${a.handle}: ${a.name}, ${a.public ? 'public' : 'private'}, owned by ${owner}`,
514
+ ...(a.disabled ? [` Switched off by Seam${a.disabled.reason ? `: ${a.disabled.reason}` : ''}. It installs nowhere and its installs do nothing until Seam switches it on.`] : []),
515
+ ...(a.description ? [` ${a.description}`] : []),
516
+ ` May: ${perms.length ? perms.join(', ') : 'nothing yet (every permission is none)'}`,
517
+ ` Hears: ${a.events?.length ? a.events.join(', ') : 'no events'}`,
518
+ ...(a.installs != null ? [` Installs: ${a.installs}`] : []),
519
+ ...['homepage', 'callback_url', 'setup_url', 'webhook_url'].filter((k) => a[k]).map((k) => ` ${k}: ${a[k]}`),
520
+ ...(a.managers?.length ? [` Managers: ${a.managers.join(', ')}`] : []),
521
+ ...(a.keys ? [` Keys: ${a.keys.length ? a.keys.map((k) => k.id).join(', ') : 'none'}`] : []),
522
+ ...(a.webhook_secret ? [` Webhook secret (shown once, keep it): ${a.webhook_secret}`] : [])
523
+ ].join('\n')
524
+ }
525
+
526
+ /** An app's keys; a new one with its private half, shown once. */
527
+ export function describeAppKey (k) {
528
+ if (k.removed) return `Removed the key ${k.id}: it no longer works.`
529
+ if (k.private_key) return `Made a key ${k.id} for ${k.app}. Its private half, shown only now (keep it secret, e.g. in a file your app reads):\n\n${k.private_key}`
530
+ return k.keys.length ? k.keys.map((x) => `${x.id} made ${new Date(x.created_at).toISOString().slice(0, 10)} by ${x.created_by}`).join('\n') : 'No keys.'
531
+ }
532
+
533
+ /** An app installed on an account or an org (19.4): where, on which repos, what it may do. */
534
+ export function describeInstall (i) {
535
+ if (i.removed) return `Removed the installation ${i.id}: the app is off that account.`
536
+ const where = i.target?.kind === 'org' ? `the org ${i.target.slug || i.target.id}` : (i.target?.name || i.target?.id)
537
+ const perms = Object.entries(i.permissions || {}).filter(([, l]) => l !== 'none').map(([r, l]) => `${r} ${l}`)
538
+ return [
539
+ `${i.app?.handle || i.app?.id} on ${where}${i.paused ? ' (paused)' : ''}: installation ${i.id}`,
540
+ ` Repos: ${i.repos === 'all' ? 'all, and later ones' : i.repos.join(', ')}`,
541
+ ` May: ${perms.length ? perms.join(', ') : 'nothing'}`,
542
+ ...(i.pending_permissions ? [` Asks for more, waiting for you to accept (seam app accept ${i.id}): ${Object.entries(i.pending_permissions).map(([r, l]) => `${r} ${l}`).join(', ')}`] : []),
543
+ ...(i.unverified ? [' Unverified: the org that makes this app has no verified domain'] : []),
544
+ ...(i.disabled ? [' Switched off by Seam: this install does nothing until Seam switches the app on'] : [])
545
+ ].join('\n')
546
+ }
547
+ /** A member's request for an app on an org (19.4), and what an admin decided. */
548
+ export function describeRequest (r) {
549
+ const repos = r.repos === 'all' ? 'all repos' : r.repos.join(', ')
550
+ return [
551
+ `Request ${r.id}: ${r.app?.handle || r.app?.id} on ${repos}, asked by ${r.requested_by}: ${r.state}${r.decided_by ? ` by ${r.decided_by}` : r.state === 'pending' ? ' (the org\'s admins approve or decline it)' : ''}`,
552
+ ...(r.note ? [` "${r.note}"`] : []),
553
+ ...(r.installation ? [` Installed: installation ${r.installation.id}`] : [])
554
+ ].join('\n')
555
+ }
556
+ export const describeRequests = (r) => r.requests.length ? r.requests.map(describeRequest).join('\n') : 'No app requests.'
557
+ export const describeInstalls = (r) => r.installations.length ? r.installations.map(describeInstall).join('\n\n') : 'No apps installed.'
558
+
559
+ export const describeApps = (r) => r.apps.length ? r.apps.map((a) => describeApp(a)).join('\n\n') : 'No apps.'
560
+
501
561
  export function describeOrgLog (r) {
502
562
  const what = (x) => {
503
563
  const d = x.data || {}
504
564
  if (x.kind === 'created') return `made the org ${d.slug}`
505
565
  if (x.kind === 'member') return d.role ? `${d.was ? `made ${d.member} ${d.role === 'admin' || d.role === 'owner' ? 'an' : 'a'} ${d.role} (was ${d.was})` : `added ${d.member} as ${d.role === 'member' ? 'a' : 'an'} ${d.role}`}` : `removed ${d.member} (was ${d.was})`
506
566
  if (x.kind === 'team') return d.members?.length ? `set the team ${d.name}: ${d.members.join(', ')}` : `removed the team ${d.name}`
567
+ 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(', ')})` : ''}`
568
+ 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'})` : ''}`
569
+ 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}`
507
570
  if (x.kind === 'quilt_in') return `moved the quilt ${d.slug} in (from ${d.from})`
508
571
  if (x.kind === 'quilt_out') return `moved the quilt ${d.slug} out (to ${d.to})`
509
572
  return `${x.kind} ${JSON.stringify(d)}`
@@ -12,14 +12,14 @@ import { verifyQuilt, describeVerify } from './attest.js'
12
12
  import { codecFor, catBytes, grantSealed, describeSeal, startSealedIfNew } from './sealed.js'
13
13
  import { confirmWithLocalKey } from './linepolicy.js'
14
14
  import { describeAgentToken, describeAgentTokens } from './agent-tokens.js'
15
- import { eventLine, describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeImportRecord, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeResolutions, describeLiveSet, describeLiveJoin, describeMergeBase, describeStack, describeStackDiff, describeGrep, describeBisect, describeCheck, describeReviews, describeInterdiff, describeReviewed, describeSquareCheck, describePolicy, describeKeys, describeTokens, describeNewToken, describeProtections, describeTeams, describeAccess, describeRedacted, describeRedactions, describeMetrics, describeSquarePolicy, describePolicyVersions, describeOrg, describeOrgs, describeOrgLog, describeOrgTeams, describeCheckRuns, describeRerun, describeHandoff, describeFinished, describeAncestor, describeContains, describePipeline, describeWatches, describeSearch, describePublicReads } from './describe.js'
15
+ import { eventLine, describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeImportRecord, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeResolutions, describeLiveSet, describeLiveJoin, describeMergeBase, describeStack, describeStackDiff, describeGrep, describeBisect, describeCheck, describeReviews, describeInterdiff, describeReviewed, describeSquareCheck, describePolicy, describeKeys, describeTokens, describeNewToken, describeProtections, describeTeams, describeAccess, describeRedacted, describeRedactions, describeMetrics, describeSquarePolicy, describePolicyVersions, describeOrg, describeOrgs, describeOrgLog, describeOrgDomain, describeApp, describeApps, describeAppKey, describeInstall, describeInstalls, describeRequest, describeRequests, describeOrgTeams, describeCheckRuns, describeRerun, describeHandoff, describeFinished, describeAncestor, describeContains, describePipeline, describeWatches, describeSearch, describePublicReads } from './describe.js'
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
19
  import { describeRebuild, describeOpened } from './describe.js'
20
20
  import { status as blockStatus, commitFolder, amendFolder, syncTo, pull, toLineTip, checkout, restore, reset, clean, localChanges, bytesOf, livePatch } 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_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_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']
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. ' +
@@ -101,6 +101,13 @@ export const SEAM_DESCRIPTIONS = {
101
101
  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.',
102
102
  seam_orgs: 'Your orgs (who may own quilts besides people), or one org with org: its members by role (owner, admin, member) and its quilts.',
103
103
  seam_org_log: 'What happened in an org, newest first: made, members added, removed or given a role, teams set, quilts moved in or out; who did it and when. Its admins and owners. before: a row id, for the page after.',
104
+ seam_apps: 'Lists apps: the ones you change (yours, and your orgs\' where you are admin or owner), or with verified the public apps of verified orgs.',
105
+ seam_app: 'An app (any tool that connects to Seam): new makes one (owned by you, or by org when you are its admin or owner; a webhook secret Seam makes is shown once), show reads it (its public view when it is public and not yours), edit changes the fields given (permissions named change, the rest keep theirs; new_secret makes a new webhook secret), remove deletes it; disable (with a reason) and enable switch it off or on for everyone, for Seam staff only. Permissions are none, read or write for contents, squares, merges, lines, checks, reviews, claims, members and settings.',
106
+ seam_app_key: 'An app\'s private keys (Ed25519): new makes one and answers its private half (PEM) this once, list gives the public halves, remove ends one. Up to ten live, for rotation. Its owner, managers, or its org\'s admins and owners.',
107
+ seam_app_install: 'Installs an app and manages its installations. install puts the app (handle) on your account, or on org when you are its admin or owner, on repos (quilt ids or slugs of that account) or all of them and later ones, with the permissions the app asks; an app from an org without a verified domain says unverified. list gives the apps on your account, or on org. show, repos (change which repos), accept (take the permissions the app added since: until then the install keeps its old set, listed as pending_permissions), pause, resume and remove take the installation id. Only the person, or the org\'s admins and owners.',
108
+ seam_app_request: 'Install requests on an org: ask (an org member who is not its admin asks for the app handle, on repos or all, with a note), list (the org\'s open requests, or all with all; its admins and owners), approve (installs it with the repos asked) or decline (id). An admin installs with seam_app_install instead.',
109
+ seam_app_manager: 'Adds or removes a manager of an app (a person, by member id or email): managers edit the app and make its keys without being its org\'s owners. Its owner or its org\'s admins and owners.',
110
+ seam_org_domain: 'Verifies an org by a domain it names: add gives the TXT record to put at _seam-verify.<domain>, check looks it up (the org is verified while one of its domains is; a verified domain is looked up again daily), remove drops it. An org\'s admin or owner who is a person.',
104
111
  seam_org_team: 'Sets an org\'s team (its members, replaced whole; [] removes it), which the protections of every quilt the org owns name as org_team:<name>. An org\'s admin or owner who is a person.',
105
112
  seam_org_member: 'Adds a member to an org with a role (member writes its quilts; admin and owner administer them), or removes them with role null. An org\'s admin or owner who is a person.',
106
113
  seam_agent_token_new: 'Makes an agent token for this quilt: the key an agent writes with (on a server that asks for them, its own key only reads). Scoped to squares (own: the ones it holds or writes), paths and a level (read, write or merge), for hours (8 by default, 7 days at most), and never more than its person. A person names the agent; an agent asks for itself. The secret (sat_…) is in this answer only.',
@@ -228,6 +235,13 @@ export const SEAM_INPUTS = {
228
235
  seam_agent_token_revoke: { quilt: QUILT, id: z.string().max(100) },
229
236
  seam_orgs: { org: z.string().max(100).optional() },
230
237
  seam_org_log: { org: z.string().max(100), limit: z.number().int().min(1).max(500).optional(), before: z.string().regex(/^\d{1,19}$/).optional() },
238
+ seam_apps: { verified: z.boolean().optional() },
239
+ seam_app: { action: z.enum(['new', 'show', 'edit', 'remove', 'disable', 'enable']), reason: z.string().max(500).optional().describe('Why, for disable'), handle: z.string().max(39), name: z.string().max(100).optional(), org: z.string().max(100).optional(), description: z.string().max(2000).optional(), homepage: z.string().max(2000).optional(), callback_url: z.string().max(2000).optional(), setup_url: z.string().max(2000).optional(), webhook_url: z.string().max(2000).optional(), webhook_secret: z.string().min(16).max(200).optional(), new_secret: z.boolean().optional(), events: z.array(z.string().max(50)).max(50).optional(), permissions: z.record(z.string().max(20), z.enum(['none', 'read', 'write'])).optional(), public: z.boolean().optional() },
240
+ seam_app_key: { action: z.enum(['new', 'list', 'remove']), handle: z.string().max(39), id: z.string().max(100).optional() },
241
+ seam_app_install: { action: z.enum(['install', 'list', 'show', 'repos', 'accept', 'pause', 'resume', 'remove']), handle: z.string().max(39).optional().describe('The app, for install'), org: z.string().max(100).optional().describe('An org you administer, for install or list'), repos: z.union([z.literal('all'), z.array(z.string().max(200)).min(1).max(500)]).optional().describe('"all" (default) or quilts of the account'), id: z.string().max(100).optional().describe('The installation, for show, repos, pause, resume and remove') },
242
+ seam_app_request: { action: z.enum(['ask', 'list', 'approve', 'decline']), org: z.string().max(100), handle: z.string().max(39).optional().describe('The app, for ask'), repos: z.union([z.literal('all'), z.array(z.string().max(200)).min(1).max(500)]).optional(), note: z.string().max(1000).optional().describe('Why, for ask'), all: z.boolean().optional().describe('For list: decided ones too'), id: z.string().max(100).optional().describe('The request, for approve and decline') },
243
+ seam_app_manager: { action: z.enum(['add', 'remove']), handle: z.string().max(39), member: z.string().max(200) },
244
+ seam_org_domain: { org: z.string().max(100), action: z.enum(['add', 'check', 'remove']), domain: z.string().max(253) },
231
245
  seam_org_team: { org: z.string().max(100), name: z.string().max(63), members: z.array(z.string().max(200)).max(1000).describe('member ids (person:… or agent:…), every one in the org; [] removes the team') },
232
246
  seam_org_member: { org: z.string().max(100), member: z.string().max(320).describe('a member id (person:… or agent:…) or the email of a person who has signed in to Seam'), role: z.enum(['member', 'admin', 'owner']).nullable() },
233
247
  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() },
@@ -625,6 +639,38 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
625
639
  }
626
640
  case 'seam_orgs': return args.org ? describeOrg(await client.org(args.org)) : describeOrgs(await client.orgs())
627
641
  case 'seam_org_log': return describeOrgLog(await client.orgLog(args.org, { limit: args.limit, before: args.before }))
642
+ case 'seam_apps': return describeApps(await client.apps({ verified: !!args.verified }))
643
+ case 'seam_app': {
644
+ if (args.action === 'show') return describeApp(await client.app(args.handle))
645
+ if (args.action === 'remove') return describeApp(await client.deleteApp(args.handle))
646
+ if (args.action === 'disable') return describeApp(await client.disableApp(args.handle, args.reason))
647
+ if (args.action === 'enable') return describeApp(await client.enableApp(args.handle))
648
+ delete args.reason
649
+ const { action, handle, new_secret: fresh, ...b } = args
650
+ if (action === 'new') return describeApp(await client.createApp({ handle, name: b.name || handle, ...b }))
651
+ return describeApp(await client.editApp(handle, { ...b, ...(fresh ? { webhook_secret: true } : {}) }))
652
+ }
653
+ case 'seam_app_key': {
654
+ if (args.action === 'remove' && !args.id) throw new Error('give the key id to remove')
655
+ return describeAppKey(await (args.action === 'new' ? client.addAppKey(args.handle) : args.action === 'list' ? client.appKeys(args.handle) : client.removeAppKey(args.handle, args.id)))
656
+ }
657
+ case 'seam_app_install': {
658
+ const { action, handle, org, repos, id } = args
659
+ if (action === 'list') return describeInstalls(await (org ? client.orgInstallations(org) : client.myInstallations()))
660
+ if (action === 'install') { if (!handle) throw new Error('give the app handle to install'); return describeInstall(await client.installApp(handle, { ...(org ? { org } : {}), repos: repos || 'all' })) }
661
+ if (!id) throw new Error('give the installation id')
662
+ if (action === 'repos') { if (!repos) throw new Error('give repos: "all" or quilts'); return describeInstall(await client.editInstallation(id, { repos })) }
663
+ return describeInstall(await (action === 'show' ? client.installation(id) : action === 'accept' ? client.acceptInstallation(id) : action === 'pause' ? client.pauseInstallation(id) : action === 'resume' ? client.resumeInstallation(id) : client.deleteInstallation(id)))
664
+ }
665
+ case 'seam_app_request': {
666
+ const { action, org, handle, repos, note, all, id } = args
667
+ if (action === 'list') return describeRequests(await client.orgRequests(org, { all: !!all }))
668
+ if (action === 'ask') { if (!handle) throw new Error('give the app handle to ask for'); return describeRequest(await client.requestApp(handle, { org, repos: repos || 'all', ...(note ? { note } : {}) })) }
669
+ if (!id) throw new Error('give the request id')
670
+ return describeRequest(await client.decideRequest(org, id, action))
671
+ }
672
+ case 'seam_app_manager': return describeApp(await (args.action === 'add' ? client.addAppManager(args.handle, args.member) : client.removeAppManager(args.handle, args.member)))
673
+ case 'seam_org_domain': return describeOrgDomain(await (args.action === 'add' ? client.addOrgDomain(args.org, args.domain) : args.action === 'check' ? client.checkOrgDomain(args.org, args.domain) : client.removeOrgDomain(args.org, args.domain)))
628
674
  case 'seam_org_team': return describeOrgTeams(await client.setOrgTeam(args.org, args.name, args.members))
629
675
  case 'seam_org_member': { const r = await client.setOrgMember(args.org, args.member, args.role); return (r.invited ? `Invited ${r.email}: ${r.emailed ? 'Seam emailed them a link' : `send them ${r.sign_up}`}. They are in the org from their first sign-in.\n` : '') + describeOrg(r) }
630
676
  case 'seam_agent_token_new': return describeAgentToken(await client.createAgentToken(needQuilt(), { ...(args.agent ? { agent: args.agent } : {}), max_level: args.max_level || 'write', squares: args.squares || 'own', paths: args.paths || [], ...(args.hours ? { ttl_s: Math.round(args.hours * 3600) } : {}), ...(args.checks != null ? { checks: args.checks } : {}), ...(args.rate ? { rate: args.rate } : {}) }))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyquilt/seam",
3
- "version": "0.131.0",
3
+ "version": "0.140.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",