@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 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
- // The published package ships the client in lib/quilts (scripts/build.mjs); in the repo it is
12
- // read straight from the server's src/quilts.
13
- const QUILTS = fs.existsSync(path.join(here, '../lib/quilts/cli.js')) ? '../lib/quilts' : '../../src/quilts'
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}: Quilt Source, source control for people and their agents
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
- export const apiUrl = () => String(process.env.SEAM_API_URL || API_URL).replace(/\/+$/, '')
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'
@@ -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)}`),
@@ -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>).`
@@ -1,5 +1,8 @@
1
- // The error every quilt operation refuses with, shared by the server and seam's client.
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)}`
@@ -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.16.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",