@heyquilt/seam 0.130.0 → 0.134.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 +5 -0
- package/lib/quilts/cli.js +48 -4
- package/lib/quilts/client.js +14 -0
- package/lib/quilts/describe.js +50 -1
- package/lib/quilts/tools.js +29 -2
- package/package.json +1 -1
package/bin/seam.js
CHANGED
|
@@ -87,6 +87,11 @@ 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 manager add|remove <handle> <member or email> people who edit an app without being its org's owners
|
|
93
|
+
seam org domain add|check|remove <org> <domain> verify an org by a TXT record at _seam-verify.<domain>
|
|
94
|
+
seam org log <org> [-n <count>] [--before <id>] what happened in an org: members, roles, teams, quilts (owners and admins)
|
|
90
95
|
seam org team <org> <name> [<member>...] an org's team, named org_team:<name> in its quilts' protections
|
|
91
96
|
seam quilts new <slug> --org <org> | quilts move --quilt <q> --org <org> a quilt that belongs to an org
|
|
92
97
|
seam quilts agents-make-lines on|off whether agents may make lines, tags and releases
|
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, 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, 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'])
|
|
86
|
+
const VALUE_FLAGS = new Set(['--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,56 @@ 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 (sub === 'manager') {
|
|
639
|
+
// seam app manager add|remove <handle> <person or email>
|
|
640
|
+
const [, act, h, who] = pos
|
|
641
|
+
if (!['add', 'remove'].includes(act) || !h || !who) throw new Error('seam app manager add|remove <handle> <person:id or email>')
|
|
642
|
+
const r = act === 'add' ? await client.addAppManager(h, who) : await client.removeAppManager(h, who)
|
|
643
|
+
return print(r, describeApp(r))
|
|
644
|
+
}
|
|
645
|
+
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>; 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'
|
|
646
|
+
if (!['new', 'show', 'edit', 'remove'].includes(sub) || !pos[1]) throw new Error(usage)
|
|
647
|
+
if (sub === 'show') { const r = await client.app(pos[1]); return print(r, describeApp(r)) }
|
|
648
|
+
if (sub === 'remove') { const r = await client.deleteApp(pos[1]); return print(r, describeApp(r)) }
|
|
649
|
+
const b = {}
|
|
650
|
+
const str = (f) => typeof flags[f] === 'string' ? flags[f] : undefined
|
|
651
|
+
if (str('--name') !== undefined) b.name = str('--name')
|
|
652
|
+
if (str('--description') !== undefined) b.description = str('--description')
|
|
653
|
+
for (const [f, k] of [['--homepage', 'homepage'], ['--callback', 'callback_url'], ['--setup', 'setup_url'], ['--webhook', 'webhook_url']]) if (str(f) !== undefined) b[k] = str(f)
|
|
654
|
+
if (str('--events') !== undefined) b.events = str('--events').split(',').map((x) => x.trim()).filter(Boolean)
|
|
655
|
+
if (str('--perm') !== undefined) b.permissions = Object.fromEntries(str('--perm').split(',').map((x) => x.trim().split('=')).filter((x) => x[0]))
|
|
656
|
+
if (flags['--public']) b.public = true
|
|
657
|
+
if (flags['--private']) b.public = false
|
|
658
|
+
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)) }
|
|
659
|
+
if (flags['--new-secret']) b.webhook_secret = true
|
|
660
|
+
const r = await client.editApp(pos[1], b)
|
|
661
|
+
return print(r, describeApp(r))
|
|
662
|
+
}
|
|
627
663
|
case 'org': {
|
|
628
664
|
// seam org [list] | new <slug> [--title <name>] | show <org> | add <org> <member> [--role member|admin|owner] | remove <org> <member>
|
|
629
665
|
const sub = pos[0] || 'list'
|
|
630
666
|
if (sub === 'list') { const r = await client.orgs(); return print(r, describeOrgs(r)) }
|
|
631
667
|
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
668
|
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)) }
|
|
669
|
+
if (sub === 'domain') {
|
|
670
|
+
// seam org domain add|check|remove <org> <domain>
|
|
671
|
+
const [, act, o, d] = pos
|
|
672
|
+
if (!['add', 'check', 'remove'].includes(act) || !o || !d) throw new Error('seam org domain add|check|remove <org> <domain>')
|
|
673
|
+
const r = act === 'add' ? await client.addOrgDomain(o, d) : act === 'check' ? await client.checkOrgDomain(o, d) : await client.removeOrgDomain(o, d)
|
|
674
|
+
return print(r, describeOrgDomain(r))
|
|
675
|
+
}
|
|
676
|
+
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)) }
|
|
633
677
|
if (sub === 'add' || sub === 'remove') {
|
|
634
678
|
if (!pos[1] || !pos[2]) throw new Error(`seam org ${sub} <org> <member>${sub === 'add' ? ' [--role member|admin|owner]' : ''}`)
|
|
635
679
|
const r = await client.setOrgMember(pos[1], pos[2], sub === 'add' ? (flags['--role'] || 'member') : null)
|
|
@@ -641,7 +685,7 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
|
|
|
641
685
|
const r = await client.setOrgTeam(pos[1], pos[2], pos.slice(3))
|
|
642
686
|
return print(r, describeOrgTeams(r))
|
|
643
687
|
}
|
|
644
|
-
throw new Error('seam org [list] | new <slug> | show <org> | add <org> <member> [--role r] | remove <org> <member> | team <org> <name> [<member>...]')
|
|
688
|
+
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>...]')
|
|
645
689
|
}
|
|
646
690
|
case 'policy': {
|
|
647
691
|
// seam policy versions|approve|reject|confirm <line> [<version>]: a line's policy versions (11.4)
|
package/lib/quilts/client.js
CHANGED
|
@@ -311,8 +311,22 @@ 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
|
+
appKeys: (h) => call('GET', `/v1/apps/${enc(h)}/keys`),
|
|
321
|
+
addAppKey: (h) => call('POST', `/v1/apps/${enc(h)}/keys`, {}),
|
|
322
|
+
removeAppKey: (h, id) => call('DELETE', `/v1/apps/${enc(h)}/keys/${enc(id)}`),
|
|
323
|
+
deleteApp: (h) => call('DELETE', `/v1/apps/${enc(h)}`),
|
|
314
324
|
createOrg: (b) => call('POST', '/v1/orgs', b),
|
|
315
325
|
org: (o) => call('GET', `/v1/orgs/${enc(o)}`),
|
|
326
|
+
addOrgDomain: (o, domain) => call('POST', `/v1/orgs/${enc(o)}/domains`, { domain }),
|
|
327
|
+
checkOrgDomain: (o, d) => call('POST', `/v1/orgs/${enc(o)}/domains/${enc(d)}/check`, {}),
|
|
328
|
+
removeOrgDomain: (o, d) => call('DELETE', `/v1/orgs/${enc(o)}/domains/${enc(d)}`),
|
|
329
|
+
orgLog: (o, { limit, before } = {}) => call('GET', `/v1/orgs/${enc(o)}/log${qs({ limit, before })}`),
|
|
316
330
|
// A member id, or the email of a person who has signed in to Seam.
|
|
317
331
|
setOrgTeam: (o, name, members) => call('PUT', `/v1/orgs/${enc(o)}/teams/${enc(name)}`, { members }),
|
|
318
332
|
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 }),
|
package/lib/quilts/describe.js
CHANGED
|
@@ -494,7 +494,56 @@ 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
|
-
|
|
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')
|
|
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
|
+
|
|
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.description ? [` ${a.description}`] : []),
|
|
515
|
+
` May: ${perms.length ? perms.join(', ') : 'nothing yet (every permission is none)'}`,
|
|
516
|
+
` Hears: ${a.events?.length ? a.events.join(', ') : 'no events'}`,
|
|
517
|
+
...['homepage', 'callback_url', 'setup_url', 'webhook_url'].filter((k) => a[k]).map((k) => ` ${k}: ${a[k]}`),
|
|
518
|
+
...(a.managers?.length ? [` Managers: ${a.managers.join(', ')}`] : []),
|
|
519
|
+
...(a.keys ? [` Keys: ${a.keys.length ? a.keys.map((k) => k.id).join(', ') : 'none'}`] : []),
|
|
520
|
+
...(a.webhook_secret ? [` Webhook secret (shown once, keep it): ${a.webhook_secret}`] : [])
|
|
521
|
+
].join('\n')
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** An app's keys; a new one with its private half, shown once. */
|
|
525
|
+
export function describeAppKey (k) {
|
|
526
|
+
if (k.removed) return `Removed the key ${k.id}: it no longer works.`
|
|
527
|
+
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}`
|
|
528
|
+
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.'
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export const describeApps = (r) => r.apps.length ? r.apps.map((a) => describeApp(a)).join('\n\n') : 'No apps.'
|
|
532
|
+
|
|
533
|
+
export function describeOrgLog (r) {
|
|
534
|
+
const what = (x) => {
|
|
535
|
+
const d = x.data || {}
|
|
536
|
+
if (x.kind === 'created') return `made the org ${d.slug}`
|
|
537
|
+
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})`
|
|
538
|
+
if (x.kind === 'team') return d.members?.length ? `set the team ${d.name}: ${d.members.join(', ')}` : `removed the team ${d.name}`
|
|
539
|
+
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` }[x.kind] || x.kind}${x.kind.startsWith('app_key') || x.kind.startsWith('app_manager') ? '' : ' the app'} ${d.handle} ${d.handle}${d.fields?.length ? ` (${d.fields.join(', ')})` : ''}`
|
|
540
|
+
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}`
|
|
541
|
+
if (x.kind === 'quilt_in') return `moved the quilt ${d.slug} in (from ${d.from})`
|
|
542
|
+
if (x.kind === 'quilt_out') return `moved the quilt ${d.slug} out (to ${d.to})`
|
|
543
|
+
return `${x.kind} ${JSON.stringify(d)}`
|
|
544
|
+
}
|
|
545
|
+
if (!r.log.length) return `${r.org}'s log is empty.`
|
|
546
|
+
return [...r.log.map((x) => `${x.id.padStart(6)} ${new Date(x.at).toISOString().slice(0, 16).replace('T', ' ')} ${x.actor} ${what(x)}`), ...(r.next ? [`More: seam org log ${r.org} --before ${r.next}`] : [])].join('\n')
|
|
498
547
|
}
|
|
499
548
|
|
|
500
549
|
export const describeOrgTeams = (r) => r.teams.length ? r.teams.map((t) => `${t.name}: ${t.members.join(', ')}`).join('\n') : `${r.org} has no teams.`
|
package/lib/quilts/tools.js
CHANGED
|
@@ -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, 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, 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_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_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. ' +
|
|
@@ -100,6 +100,12 @@ export const SEAM_DESCRIPTIONS = {
|
|
|
100
100
|
seam_policy: 'A square\'s change classes (whitespace, comment-only, docs, test-only, dependency, ci-config, schema, logic, per file) and what its line\'s rules by path and class need before it merges: approvals from whom, checks, and which are met.',
|
|
101
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
|
+
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. 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_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.',
|
|
108
|
+
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.',
|
|
103
109
|
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.',
|
|
104
110
|
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.',
|
|
105
111
|
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.',
|
|
@@ -226,6 +232,12 @@ export const SEAM_INPUTS = {
|
|
|
226
232
|
seam_agent_token_list: { quilt: QUILT },
|
|
227
233
|
seam_agent_token_revoke: { quilt: QUILT, id: z.string().max(100) },
|
|
228
234
|
seam_orgs: { org: z.string().max(100).optional() },
|
|
235
|
+
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() },
|
|
236
|
+
seam_apps: { verified: z.boolean().optional() },
|
|
237
|
+
seam_app: { action: z.enum(['new', 'show', 'edit', 'remove']), 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() },
|
|
238
|
+
seam_app_key: { action: z.enum(['new', 'list', 'remove']), handle: z.string().max(39), id: z.string().max(100).optional() },
|
|
239
|
+
seam_app_manager: { action: z.enum(['add', 'remove']), handle: z.string().max(39), member: z.string().max(200) },
|
|
240
|
+
seam_org_domain: { org: z.string().max(100), action: z.enum(['add', 'check', 'remove']), domain: z.string().max(253) },
|
|
229
241
|
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') },
|
|
230
242
|
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() },
|
|
231
243
|
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() },
|
|
@@ -622,6 +634,21 @@ export async function runSeamTool (name, args, { client, found = null, hosted =
|
|
|
622
634
|
return describePolicyVersions(r)
|
|
623
635
|
}
|
|
624
636
|
case 'seam_orgs': return args.org ? describeOrg(await client.org(args.org)) : describeOrgs(await client.orgs())
|
|
637
|
+
case 'seam_org_log': return describeOrgLog(await client.orgLog(args.org, { limit: args.limit, before: args.before }))
|
|
638
|
+
case 'seam_apps': return describeApps(await client.apps({ verified: !!args.verified }))
|
|
639
|
+
case 'seam_app': {
|
|
640
|
+
if (args.action === 'show') return describeApp(await client.app(args.handle))
|
|
641
|
+
if (args.action === 'remove') return describeApp(await client.deleteApp(args.handle))
|
|
642
|
+
const { action, handle, new_secret: fresh, ...b } = args
|
|
643
|
+
if (action === 'new') return describeApp(await client.createApp({ handle, name: b.name || handle, ...b }))
|
|
644
|
+
return describeApp(await client.editApp(handle, { ...b, ...(fresh ? { webhook_secret: true } : {}) }))
|
|
645
|
+
}
|
|
646
|
+
case 'seam_app_key': {
|
|
647
|
+
if (args.action === 'remove' && !args.id) throw new Error('give the key id to remove')
|
|
648
|
+
return describeAppKey(await (args.action === 'new' ? client.addAppKey(args.handle) : args.action === 'list' ? client.appKeys(args.handle) : client.removeAppKey(args.handle, args.id)))
|
|
649
|
+
}
|
|
650
|
+
case 'seam_app_manager': return describeApp(await (args.action === 'add' ? client.addAppManager(args.handle, args.member) : client.removeAppManager(args.handle, args.member)))
|
|
651
|
+
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)))
|
|
625
652
|
case 'seam_org_team': return describeOrgTeams(await client.setOrgTeam(args.org, args.name, args.members))
|
|
626
653
|
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) }
|
|
627
654
|
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