@heyquilt/seam 0.16.0 → 0.22.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 +19 -4
- package/lib/account.js +2 -1
- package/lib/quilts/cli.js +19 -3
- package/lib/quilts/client.js +3 -0
- package/lib/quilts/describe.js +7 -0
- package/lib/quilts/errors.js +4 -1
- package/lib/quilts/tools.js +11 -2
- package/package.json +1 -1
package/bin/seam.js
CHANGED
|
@@ -8,13 +8,13 @@ import { fileURLToPath } from 'node:url'
|
|
|
8
8
|
import { readAccount, login, logout, resume } from '../lib/account.js'
|
|
9
9
|
|
|
10
10
|
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
const QUILTS = fs.existsSync(path.join(here, '
|
|
11
|
+
// In the repo the client is read straight from the server's src/quilts (never a stale build);
|
|
12
|
+
// the published package has no src/, and ships it in lib/quilts (scripts/build.mjs).
|
|
13
|
+
const QUILTS = fs.existsSync(path.join(here, '../../src/quilts/cli.js')) ? '../../src/quilts' : '../lib/quilts'
|
|
14
14
|
const quilts = (file) => import(`${QUILTS}/${file}`)
|
|
15
15
|
const { version } = JSON.parse(fs.readFileSync(path.join(here, '../package.json'), 'utf8'))
|
|
16
16
|
|
|
17
|
-
const HELP = `seam ${version}:
|
|
17
|
+
const HELP = `seam ${version}: Seam, source control for people and their agents
|
|
18
18
|
Docs: https://seam.heyquilt.com/docs
|
|
19
19
|
|
|
20
20
|
seam login [--no-browser] Sign in with your heyquilt.com account
|
|
@@ -32,9 +32,19 @@ Docs: https://seam.heyquilt.com/docs
|
|
|
32
32
|
seam key [add [--name n] | remove <key>] seam commit --sign | sign [rev] seam log --show-signature
|
|
33
33
|
seam line [new <name> [--from rev] | freeze | unfreeze] Lines: main, release/<name>
|
|
34
34
|
seam tag [<name> [rev]] seam release [<name> [rev] -m <notes>] Read as tag:<name>
|
|
35
|
+
seam commit --amend [-m "<message>"] change your square's last commit
|
|
36
|
+
seam square open "<title>" --on sq:<id> a square stacked on another
|
|
37
|
+
seam claim <path|glob> [--note why] | unclaim <id> files your square is working on
|
|
38
|
+
seam review approve|request-changes|comment|dismiss <sq> seam line require <line> [--approvals n] [--checks a,b]
|
|
39
|
+
seam check <name> green|red [--square sq | --patch n] report a check (a red one on a patch reverts it)
|
|
40
|
+
seam resolutions [forget <key>] conflicts settled before, settled the same way again
|
|
41
|
+
seam bisect <bad> <good> --run "<check>" seam grep <pattern> [rev] [--all] seam describe [rev] seam merge-base <a> <b>
|
|
42
|
+
seam log --follow <path> | log -S <text>
|
|
43
|
+
seam token [new <name> [--level read|write] | revoke <name>] a key for one quilt, for CI
|
|
35
44
|
seam events [--webhook <url>] What happened to your squares
|
|
36
45
|
seam export git <dir> Back to git
|
|
37
46
|
seam mcp The same commands as MCP tools, for your AI tool
|
|
47
|
+
seam serve Run a Seam server (SEAM_IDENTITY_URL says who is calling)
|
|
38
48
|
|
|
39
49
|
Agents: set SEAM_TOKEN to the agent's key instead of signing in.`
|
|
40
50
|
|
|
@@ -55,6 +65,11 @@ async function main () {
|
|
|
55
65
|
if (cmd === 'logout') { const a = await logout(); return console.log(a ? `Signed out of ${a.account.email}.` : 'Not signed in.') }
|
|
56
66
|
if (cmd === 'whoami') { const a = readAccount(); if (!a) { process.exitCode = 1; return console.log('Not signed in.') } return console.log(`${a.account.name} (${a.account.email})`) }
|
|
57
67
|
if (cmd === 'mcp') return mcp()
|
|
68
|
+
// The server is not in the package (it ships the client): it runs from Seam's source or its image.
|
|
69
|
+
if (cmd === 'serve') {
|
|
70
|
+
if (QUILTS === '../lib/quilts') fail('seam serve runs from Seam\'s source: node src/quilts/server.js in the repository, or the Dockerfile.seam image (docs/seam-service.md).')
|
|
71
|
+
return (await quilts('server.js')).serveFromEnv(process.env)
|
|
72
|
+
}
|
|
58
73
|
const { SEAM_VERBS, runSeamCommand } = await quilts('cli.js')
|
|
59
74
|
if (!SEAM_VERBS.has(cmd)) fail(`seam: '${cmd}' is not a seam command. See seam help.`)
|
|
60
75
|
try {
|
package/lib/account.js
CHANGED
|
@@ -8,7 +8,8 @@ import path from 'node:path'
|
|
|
8
8
|
import crypto from 'node:crypto'
|
|
9
9
|
|
|
10
10
|
export const API_URL = 'https://api.heyquilt.com'
|
|
11
|
-
|
|
11
|
+
// Sign-in lives on the accounts API (Seam's identity service), not on Seam's own server.
|
|
12
|
+
export const apiUrl = () => String(process.env.SEAM_ACCOUNTS_URL || API_URL).replace(/\/+$/, '')
|
|
12
13
|
export const seamHome = () => process.env.SEAM_HOME || path.join(os.homedir(), '.seam')
|
|
13
14
|
const accountFile = () => path.join(seamHome(), 'account.json')
|
|
14
15
|
const identityFile = () => path.join(seamHome(), 'identity.json')
|
package/lib/quilts/cli.js
CHANGED
|
@@ -7,13 +7,13 @@ import fs from 'node:fs'
|
|
|
7
7
|
import os from 'node:os'
|
|
8
8
|
import { quiltClient } from './client.js'
|
|
9
9
|
import { findBlock, checkout, status as blockStatus, commitFolder, amendFolder, syncTo, localDiff, saveBlock, restore, reset, clean, localChanges } from './block.js'
|
|
10
|
-
import { describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeResolutions, describeLiveSet, describeMergeBase, describeGrep, describeBisect, describeCheck, describeReviews, describeReviewed, describeSquareCheck, describePolicy, describeKeys } from './describe.js'
|
|
10
|
+
import { describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeResolutions, describeLiveSet, describeMergeBase, describeGrep, describeBisect, describeCheck, describeReviews, describeReviewed, describeSquareCheck, describePolicy, describeKeys, describeTokens, describeNewToken } from './describe.js'
|
|
11
11
|
import { signWithLocalKey, localKey, makeLocalKey, signingKeyFile } from './signing.js'
|
|
12
12
|
|
|
13
|
-
export const API_URL = 'https://api.heyquilt.com'
|
|
13
|
+
export const API_URL = 'https://api.seam.heyquilt.com'
|
|
14
14
|
|
|
15
15
|
/** The verbs `seam` answers. */
|
|
16
|
-
export const SEAM_VERBS = new Set(['quilts', 'square', 'checkout', 'status', 'commit', 'log', 'show', 'diff', 'merge', 'rebase', 'revert', 'cherry-pick', 'blame', 'resolve', 'cat', 'ls', 'import', 'export', 'events', 'tag', 'release', 'line', 'restore', 'reset', 'clean', 'live', 'bisect', 'grep', 'describe', 'merge-base', 'claim', 'unclaim', 'check', 'resolutions', 'review', 'sign', 'key'])
|
|
16
|
+
export const SEAM_VERBS = new Set(['quilts', 'square', 'checkout', 'status', 'commit', 'log', 'show', 'diff', 'merge', 'rebase', 'revert', 'cherry-pick', 'blame', 'resolve', 'cat', 'ls', 'import', 'export', 'events', 'tag', 'release', 'line', 'restore', 'reset', 'clean', 'live', 'bisect', 'grep', 'describe', 'merge-base', 'claim', 'unclaim', 'check', 'resolutions', 'review', 'sign', 'key', 'token'])
|
|
17
17
|
|
|
18
18
|
const VALUE_FLAGS = new Set(['-m', '--quilts', '--message', '-n', '--max-count', '--into', '--onto', '--on-conflict', '--quilt', '--square', '--dir', '--line', '--task', '--level', '--title', '--slug', '--format', '--expect-tip', '--side', '--file', '--batch', '--to', '--note', '--after', '--kinds', '--webhook', '--secret', '--branch', '--from', '-S', '--run', '--marks', '--patch', '--on', '--path', '--checks', '--name', '--public-key'])
|
|
19
19
|
|
|
@@ -258,6 +258,22 @@ export async function runSeamCommand (cmd, argv, { cwd = process.cwd(), token, o
|
|
|
258
258
|
const r = await signWithLocalKey(client, needQuilt(), pos[0] || (block?.square ? block.square_short : block?.line) || 'main')
|
|
259
259
|
return print(r, `Signed ${r.short} with ${r.signature.key}.`)
|
|
260
260
|
}
|
|
261
|
+
case 'token': {
|
|
262
|
+
// seam token [list] | new <name> [--level read|write] | revoke <id or name>
|
|
263
|
+
const sub = pos[0] || 'list'
|
|
264
|
+
if (sub === 'list') { const r = await client.tokens(needQuilt()); return print(r, describeTokens(r)) }
|
|
265
|
+
if (sub === 'new') {
|
|
266
|
+
if (!pos[1]) throw new Error('seam token new <name> [--level read|write]')
|
|
267
|
+
const r = await client.createToken(needQuilt(), { name: pos[1], level: flags['--level'] || 'read' })
|
|
268
|
+
return print(r, describeNewToken(r))
|
|
269
|
+
}
|
|
270
|
+
if (sub === 'revoke') {
|
|
271
|
+
if (!pos[1]) throw new Error('seam token revoke <id or name>')
|
|
272
|
+
const r = await client.revokeToken(needQuilt(), pos[1])
|
|
273
|
+
return print(r, `Revoked token ${r.name} (${r.id}): it no longer reaches the quilt.`)
|
|
274
|
+
}
|
|
275
|
+
throw new Error('seam token [list] | new <name> [--level read|write] | revoke <id or name>')
|
|
276
|
+
}
|
|
261
277
|
case 'key': {
|
|
262
278
|
// seam key [list [member]] | add [--name <n>] [--public-key <file>] | remove <key>
|
|
263
279
|
const sub = pos[0] || 'list'
|
package/lib/quilts/client.js
CHANGED
|
@@ -73,6 +73,9 @@ export function quiltClient ({ api, token, fetch: fetchImpl = globalThis.fetch,
|
|
|
73
73
|
exportPatches: (q, o = {}) => call('GET', `${Q(q)}/export/git${qs(o)}`),
|
|
74
74
|
log: (q, o = {}) => call('GET', `${Q(q)}/log${qs(o)}`),
|
|
75
75
|
sign: (q, rev, b) => call('POST', `${Q(q)}/commits/${enc(rev)}/signatures`, b),
|
|
76
|
+
tokens: (q) => call('GET', `${Q(q)}/tokens`),
|
|
77
|
+
createToken: (q, b) => call('POST', `${Q(q)}/tokens`, b),
|
|
78
|
+
revokeToken: (q, id) => call('DELETE', `${Q(q)}/tokens/${enc(id)}`),
|
|
76
79
|
signingKeys: (o = {}) => call('GET', `/v1/signing-keys${qs(o)}`),
|
|
77
80
|
addKey: (b) => call('POST', '/v1/signing-keys', b),
|
|
78
81
|
removeKey: (key) => call('DELETE', `/v1/signing-keys/${enc(key)}`),
|
package/lib/quilts/describe.js
CHANGED
|
@@ -257,3 +257,10 @@ export function describeKeys (r) {
|
|
|
257
257
|
if (!r.keys.length) return `${r.member} has no signing keys.`
|
|
258
258
|
return r.keys.map((k) => `${k.key}${k.name ? ` ${k.name}` : ''}${k.removed_at ? ` (removed ${when(k.removed_at)})` : ` added ${when(k.added_at)}`}`).join('\n')
|
|
259
259
|
}
|
|
260
|
+
|
|
261
|
+
export function describeTokens (r) {
|
|
262
|
+
if (!r.tokens.length) return `${r.quilt} has no tokens. Make one with seam token new <name> [--level read|write].`
|
|
263
|
+
return r.tokens.map((t) => `${t.id} ${t.name} ${t.level}${t.revoked_at ? ` (revoked ${when(t.revoked_at)})` : ` made ${when(t.created_at)} by ${t.created_by}`}`).join('\n')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export const describeNewToken = (t) => `Made token ${t.name} (${t.level} on ${t.quilt}, id ${t.id}). Its secret, shown only this once:\n\n ${t.token}\n\nSet it as SEAM_TOKEN (or send it as Authorization: Bearer <token>).`
|
package/lib/quilts/errors.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
//
|
|
1
|
+
// What both the server and the seam client need without loading the server (service.js).
|
|
2
2
|
export class QuiltError extends Error {
|
|
3
3
|
/** `code` is the wire name (04/07/12): head_moved, tip_moved, conflict, not_found, ... */
|
|
4
4
|
constructor (status, code, message, extra = {}) { super(message); this.status = status; this.code = code; this.extra = extra }
|
|
5
5
|
}
|
|
6
|
+
|
|
7
|
+
/** A square's short handle: sq: + the last 8 hex of its uuid (02). */
|
|
8
|
+
export const squareShort = (id) => `sq:${String(id).replace(/-/g, '').slice(-8)}`
|
package/lib/quilts/tools.js
CHANGED
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
// quilt and square, and seam_commit takes the files' new contents.
|
|
9
9
|
import path from 'node:path'
|
|
10
10
|
import { z } from 'zod'
|
|
11
|
-
import { describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeResolutions, describeLiveSet, describeMergeBase, describeGrep, describeBisect, describeCheck, describeReviews, describeReviewed, describeSquareCheck, describePolicy, describeKeys } from './describe.js'
|
|
11
|
+
import { describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeResolutions, describeLiveSet, describeMergeBase, describeGrep, describeBisect, describeCheck, describeReviews, describeReviewed, describeSquareCheck, describePolicy, describeKeys, describeTokens, describeNewToken } from './describe.js'
|
|
12
12
|
import { signWithLocalKey, localKey, makeLocalKey, signingKeyFile } from './signing.js'
|
|
13
13
|
import { status as blockStatus, commitFolder, amendFolder, syncTo, restore, reset, clean, localChanges } from './block.js'
|
|
14
14
|
|
|
15
|
-
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_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_restore', 'seam_reset', 'seam_clean', 'seam_live', 'seam_live_set', 'seam_bisect', 'seam_grep', 'seam_search', 'seam_describe', 'seam_merge_base', 'seam_claims', 'seam_claim', 'seam_unclaim', 'seam_check', 'seam_resolutions', 'seam_forget_resolution', 'seam_sign', 'seam_keys', 'seam_key_add', 'seam_key_remove']
|
|
15
|
+
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_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_restore', 'seam_reset', 'seam_clean', 'seam_live', 'seam_live_set', 'seam_bisect', 'seam_grep', 'seam_search', 'seam_describe', 'seam_merge_base', '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']
|
|
16
16
|
|
|
17
17
|
/** What every agent is told about source control, in each MCP's instructions. */
|
|
18
18
|
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. ' +
|
|
@@ -48,6 +48,9 @@ export const SEAM_DESCRIPTIONS = {
|
|
|
48
48
|
seam_sign: 'Signs a commit you made (rev, default your square\'s head) with your Ed25519 key, so seam_log with show_signature shows it as yours. With no signature, signs with this computer\'s key (seam_key_add makes one); a hosted agent signs the text "seam-commit <commit id>\\n" itself and passes key and signature (base64).',
|
|
49
49
|
seam_keys: 'Your signing keys (or member\'s): their ids, names, and when each was added or removed.',
|
|
50
50
|
seam_key_add: 'Adds an Ed25519 signing key to your account. With no public_key, makes one on this computer (kept in ~/.seam/signing-key.pem) or adds the one already there.',
|
|
51
|
+
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.',
|
|
52
|
+
seam_token_new: 'Makes a quilt token: a key that reaches only this quilt, at level read (default) or write, for a CI job or a script. The secret (sqt_…) is in this answer only; it is used as SEAM_TOKEN or Authorization: Bearer. Admins only.',
|
|
53
|
+
seam_token_revoke: 'Revokes a quilt token (by id, or by name): it stops working at once. Admins only.',
|
|
51
54
|
seam_key_remove: 'Removes one of your signing keys (key: ed25519:…). Commits it signed then show "removed_key" instead of "good".',
|
|
52
55
|
seam_tag: 'Tags a revision (rev: patch:12, a commit, a line, sq:…; default main) with a name that never moves. Read it back as the revision tag:<name>.',
|
|
53
56
|
seam_release_list: 'The quilt\'s releases, newest first, each with its notes.',
|
|
@@ -106,6 +109,9 @@ export const SEAM_INPUTS = {
|
|
|
106
109
|
seam_sign: { quilt: QUILT, rev: REV, key: z.string().max(40).optional().describe('Your key id (ed25519:…), with signature'), signature: z.string().max(200).optional().describe('Base64 Ed25519 signature of "seam-commit <commit id>\\n"') },
|
|
107
110
|
seam_keys: { member: z.string().max(200).optional().describe('Whose keys (default yours)') },
|
|
108
111
|
seam_key_add: { public_key: z.string().max(2000).optional().describe('Ed25519 public key (PEM, or base64 of its 32 bytes); none: this computer\'s'), name: z.string().max(100).optional() },
|
|
112
|
+
seam_token_list: { quilt: QUILT },
|
|
113
|
+
seam_token_new: { quilt: QUILT, name: z.string().max(100).describe('What it is for, e.g. ci'), level: z.enum(['read', 'write']).optional() },
|
|
114
|
+
seam_token_revoke: { quilt: QUILT, token: z.string().max(100).describe('The token\'s id or name') },
|
|
109
115
|
seam_key_remove: { key: z.string().max(40).describe('The key id, ed25519:…') },
|
|
110
116
|
seam_tag: { quilt: QUILT, name: z.string().max(100).describe('The tag, e.g. v1.0'), rev: z.string().max(200).optional().describe('What it names: patch:12, a commit, a line, sq:…; default main') },
|
|
111
117
|
seam_release_list: { quilt: QUILT },
|
|
@@ -303,6 +309,9 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
303
309
|
const r = await client.addKey({ public_key: pub, name: args.name || '' })
|
|
304
310
|
return `${made ? `Made a signing key at ${signingKeyFile()} and added` : 'Added'} ${r.key}. Sign with seam_commit { sign: true } or seam_sign.`
|
|
305
311
|
}
|
|
312
|
+
case 'seam_token_list': return describeTokens(await client.tokens(needQuilt()))
|
|
313
|
+
case 'seam_token_new': return describeNewToken(await client.createToken(needQuilt(), { name: args.name, level: args.level || 'read' }))
|
|
314
|
+
case 'seam_token_revoke': { const r = await client.revokeToken(needQuilt(), args.token); return `Revoked token ${r.name} (${r.id}): it no longer reaches the quilt.` }
|
|
306
315
|
case 'seam_key_remove': { const r = await client.removeKey(args.key); return `Removed ${r.key}: commits it signed now show removed_key.` }
|
|
307
316
|
case 'seam_tag':
|
|
308
317
|
case 'seam_release': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyquilt/seam",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "seam: Quilt Source'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",
|