@heyquilt/seam 0.27.0 → 0.131.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 +118 -17
- package/lib/quilts/agent-tokens.js +148 -0
- package/lib/quilts/archiver.js +241 -0
- package/lib/quilts/attest.js +212 -0
- package/lib/quilts/bisect-run.js +3 -2
- package/lib/quilts/block.js +497 -55
- package/lib/quilts/cas.js +78 -0
- package/lib/quilts/cbor.js +2 -0
- package/lib/quilts/claims.js +409 -0
- package/lib/quilts/cli.js +612 -69
- package/lib/quilts/client.js +306 -19
- package/lib/quilts/conflict-format.js +266 -0
- package/lib/quilts/daemon.js +174 -0
- package/lib/quilts/describe.js +296 -38
- package/lib/quilts/dict.js +34 -0
- package/lib/quilts/exit-archive.js +184 -0
- package/lib/quilts/export-format.js +52 -0
- package/lib/quilts/export-git.js +27 -3
- package/lib/quilts/export-lfs.js +52 -0
- package/lib/quilts/export-sign.js +49 -0
- package/lib/quilts/fastcdc.js +186 -0
- package/lib/quilts/hash.js +17 -2
- package/lib/quilts/ignored.js +46 -0
- package/lib/quilts/import-git.js +54 -2
- package/lib/quilts/journal.js +105 -0
- package/lib/quilts/limits.js +197 -0
- package/lib/quilts/linepolicy.js +109 -0
- package/lib/quilts/pack.js +146 -18
- package/lib/quilts/persona.js +70 -0
- package/lib/quilts/records.js +7 -3
- package/lib/quilts/recover.js +37 -0
- package/lib/quilts/runner.js +79 -12
- package/lib/quilts/sealed.js +231 -0
- package/lib/quilts/secrets.js +86 -0
- package/lib/quilts/signing.js +7 -3
- package/lib/quilts/tarball.js +107 -0
- package/lib/quilts/textdiff.js +7 -5
- package/lib/quilts/tools.js +404 -83
- package/lib/quilts/tree.js +351 -0
- package/lib/quilts/want.js +51 -0
- package/lib/quilts/webhook.js +116 -0
- package/package.json +3 -2
package/lib/quilts/tools.js
CHANGED
|
@@ -8,15 +8,22 @@
|
|
|
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 {
|
|
11
|
+
import { verifyQuilt, describeVerify } from './attest.js'
|
|
12
|
+
import { codecFor, catBytes, grantSealed, describeSeal, startSealedIfNew } from './sealed.js'
|
|
13
|
+
import { confirmWithLocalKey } from './linepolicy.js'
|
|
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'
|
|
12
16
|
import { signWithLocalKey, localKey, makeLocalKey, signingKeyFile } from './signing.js'
|
|
13
|
-
import {
|
|
17
|
+
import { startDaemon, askDaemon } from './daemon.js'
|
|
18
|
+
import { rebuild as rebuildBlock, conflictStyle } from './block.js'
|
|
19
|
+
import { describeRebuild, describeOpened } from './describe.js'
|
|
20
|
+
import { status as blockStatus, commitFolder, amendFolder, syncTo, pull, toLineTip, checkout, restore, reset, clean, localChanges, bytesOf, livePatch } from './block.js'
|
|
14
21
|
|
|
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_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_poke', 'seam_import_cutover', 'seam_export_remote', 'seam_export_status', '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', 'seam_checks', 'seam_check_rerun']
|
|
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']
|
|
16
23
|
|
|
17
24
|
/** What every agent is told about source control, in each MCP's instructions. */
|
|
18
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. ' +
|
|
19
|
-
'Open a square with seam_open_square, commit with seam_commit, merge with seam_merge (into: "main"). A merge never stops half way: a real conflict is refused, kept in your square as conflict markers, and you settle it (edit the file and commit it without the markers, or seam_resolve) and merge again. ' +
|
|
26
|
+
'Open a square with seam_open_square, commit with seam_commit, merge with seam_merge (into: "main"). A merge never stops half way: a real conflict is refused, kept in your square as conflict markers, and you settle it (edit the file and commit it without the markers, or seam_resolve) and merge again. An AI gets two attempts at a conflicted file: when conflict.added says escalated (attempts_left 0), stop and leave that file to the person it names as resolver. ' +
|
|
20
27
|
'Read history with seam_log, seam_show, seam_diff, seam_blame, seam_cat and seam_grep (seam_bisect finds the commit that broke something); revisions are main, main~3, patch:12, sq:<id>, a commit id or git:<sha>. ' +
|
|
21
28
|
'In a folder checked out from a square (it holds .seam/block.json), edit the files with your own tools and check them with seam_square_status.'
|
|
22
29
|
|
|
@@ -27,16 +34,27 @@ const PATHS = z.array(z.string().max(1000)).max(200).optional().describe('Only t
|
|
|
27
34
|
|
|
28
35
|
export const SEAM_DESCRIPTIONS = {
|
|
29
36
|
seam_live: 'A square\'s uncommitted work its holder shared (sq:<id>:live): each changed file, added, modified or deleted against the square\'s head. Read the files with seam_cat or seam_diff (rev sq:<id>:live).',
|
|
30
|
-
seam_line_require: 'Reads, or (a person who is an admin) sets, what a line needs before a square merges into it: approvals of the square\'s latest commit (0: none) and checks that must be green on it (check names, reported with seam_check and square). Leave
|
|
37
|
+
seam_line_require: 'Reads, or (a person who is an admin) sets, what a line needs before a square merges into it: approvals of the square\'s latest commit (0: none) and checks that must be green on it (check names, reported with seam_check and square), human_only (paths agents read but never change), auto_combine, how far merges into it combine clashing changes by themselves, needs_stack, the stack keys every merge must carry (seam_stack), monotonic_stack, keys a merge may never lower, and migrations_in_order, refusing a migration that sorts before the tip\'s db_migration. Leave them out to read it.',
|
|
38
|
+
seam_apply_suggestion: 'Applies a reviewer\'s suggestion (a comment with suggestion) as a commit on the square: only its holder or a writer they shared it with. Refused (suggestion_outdated) when the file changed since the suggestion was made. The commit message names who suggested it.',
|
|
39
|
+
seam_interdiff: 'What changed in a square since you last reviewed it (or since a commit you name): each file that differs with its change class, the strictest first, and whether only the line under it moved. Read this before approving again.',
|
|
31
40
|
seam_reviews: 'A square\'s reviews (comments, approvals, requests for changes) and where it stands: approvals it has against what its line needs, and whether it is ready to merge.',
|
|
32
|
-
seam_review: 'Reviews a square: kind approve, request_changes (say what in body), comment (body; path and line put it on a line of a file at the square\'s head)
|
|
41
|
+
seam_review: 'Reviews a square: kind approve, request_changes (say what in body), comment (body; path and line put it on a line of a file at the square\'s head; suggestion { text, to_line? } proposes text in place of line..to_line, which the holder applies with seam_apply_suggestion) dismiss (takes back your verdict) or request (asked: members to ask for a review; the members the line\'s unmet rules name are asked too, each with a reason; each gets review.requested). Nobody approves a square they or the person they work for opened or hold. An approval counts for the square\'s latest commit only.',
|
|
33
42
|
seam_claims: 'The claims that hold now: which square is working on which paths, who, and why (square: only that square\'s).',
|
|
34
43
|
seam_claim: 'Claims paths (patterns: files, folders or globs like src/api/**) for your square, so writes to them from other squares on its line are refused until your square merges or you let go. note says what for. Claim before a larger change across several files.',
|
|
44
|
+
seam_watch: 'Watches paths (files, folders or globs like src/**): each patch that changes them sends you watch.triggered (seam_events), with its change class and size. classes: only those change classes (whitespace, comment-only, docs, test-only, dependency, ci-config, schema, logic); min_lines: only patches that big; lines: only those lines. Leave paths out to list your watches. Watches never block a merge.',
|
|
45
|
+
seam_unwatch: 'Stops one of your watches (id, from seam_watch).',
|
|
46
|
+
seam_pipeline: 'Where the post-merge pipeline stands for a check on a line: the open bisect of a red verdict (the patch range still to search, the round) and the last one\'s outcome (the culprit it reverted, or flaky). Runners answer the bisect\'s runs like any check run (seam_checks, then seam_check with run_key). With stop (admin), ends the open bisect: its waiting runs are cancelled and nothing is reverted.',
|
|
35
47
|
seam_check_rerun: 'Asks a check\'s runners to run it again: with square, on that square\'s latest commit (pre_merge); with line, on the line\'s tip (post_merge); or on commit. A run of it still open there is cancelled (check.cancelled to its runners). Returns the new run_key.',
|
|
36
48
|
seam_checks: 'The quilt\'s check runs, newest first (filter by state, name, tier, line, square). A runner polls state "queued" for the checks its line\'s policy names it for (or gets check.requested events), runs each on its commit and reports with seam_check and the run\'s run_key.',
|
|
37
49
|
seam_check: 'Reports a post-merge check on a line (status red, green or flaky; a red check halves the line\'s merge window, green restores it). With patch (the patch number it failed at, found for example with seam_bisect) a red check reverts that patch on the line by itself and opens a square holding the change again for its holder to fix. With square instead, the result (green or red) is for that square\'s latest commit, which its line may require green before a merge.',
|
|
38
|
-
seam_unclaim: 'Lets go of claims on your square (patterns: the patterns or claim ids).',
|
|
39
|
-
|
|
50
|
+
seam_unclaim: 'Lets go of claims on your square (patterns: the patterns or claim ids). Refused while someone waits for one: hand it off instead.',
|
|
51
|
+
seam_claim_request: 'Waits in the queue for another square\'s claim (claim: its id from claim_refused, or its pattern) for your square, saying what you need it for. The holder hands it over when done.',
|
|
52
|
+
seam_claim_withdraw: 'Takes your request (its id) out of a claim\'s queue.',
|
|
53
|
+
seam_claim_handoff: 'Hands a claim on your square to the first in its queue (or request id `to`), with a note on what you changed and what is left.',
|
|
54
|
+
seam_claim_finish: 'Says your square is finished with the files it wrote: lets go of its auto-claims now (instead of after 10 quiet minutes) and hands any someone waits for to the first in line, with your note. Claims made on purpose stay.',
|
|
55
|
+
seam_daemon: 'The daemon for this block folder (as seam daemon): start watches the folder and shares each changed path as the square\'s live work, for as long as this MCP server runs; status, flush and stop ask the daemon running for the folder (yours or a seam daemon in a terminal).',
|
|
56
|
+
seam_live_join: 'Whether a member (default you) may load a square\'s shared live work whole: the holder, its writers and the holder\'s own agents always; anyone else only when it may read every shared path.',
|
|
57
|
+
seam_live_set: 'Shares your square\'s uncommitted work so others can read it as sq:<id>:live before you commit: in a block folder the folder\'s changed files; elsewhere files: [{ path, content }] and delete: [paths]. Each call replaces what was shared; none clears it. paths: only those paths of the block folder, the rest stays shared. It is not a commit: seam_commit keeps work.',
|
|
40
58
|
seam_restore: 'In a block folder: puts the named files (paths; "." for all) back as they were at the folder\'s last commit, throwing away their uncommitted edits and deletions. Files nobody committed stay (seam_clean removes them).',
|
|
41
59
|
seam_reset: 'In a block folder: makes the folder\'s files those of a revision (rev). With no rev, throws away every uncommitted edit. The folder stays on its square: what differs from it shows as uncommitted changes, so seam_commit makes the square match rev (to undo its last commit: rev sq:<id>~1).',
|
|
42
60
|
seam_clean: 'In a block folder: removes the files nobody committed and no .gitignore names (under paths, default all). dry_run: true lists them without removing.',
|
|
@@ -50,67 +68,123 @@ export const SEAM_DESCRIPTIONS = {
|
|
|
50
68
|
seam_github_import: 'Imports a GitHub repository the Seam app reaches into a new quilt and follows it (GitHub tells Seam of every push), with the installation as its credential: no token to paste.',
|
|
51
69
|
seam_import_remote: 'Imports a git remote (https:// or ssh) into a new quilt on the server. With follow, it keeps taking new commits on main and release/* every interval (and on seam_import_poke) and freezes those lines until cut-over, because git stays the authority. Returns the import id and its poke secret (shown once).',
|
|
52
70
|
seam_export_remote: 'Exports lines of a quilt to a git remote (admins) after each merge on them, as fast-forwards that are never forced; releases: true pushes release tags too. While an import follows git, only to a scratch remote (scratch: true).',
|
|
71
|
+
seam_export_archive: 'Writes the whole quilt as one .tar (admin) to a file on this computer: every log segment, every pack and the members, with a manifest of sizes and BLAKE3s; out (default <quilt>-<row>.seam.tar). seam export check <file> checks it offline. Over HTTP: POST /v1/quilts/:q/exit/archive.',
|
|
72
|
+
seam_export_tarball: 'Writes one commit as a .tar.gz (what git archive makes) to a file on this computer: rev (default main), paths to take only part, out (default <quilt>-<commit>.tar.gz in this folder). The same commit always gives the same bytes. Over HTTP: GET /v1/quilts/:q/tarball/:rev?paths=.',
|
|
53
73
|
seam_export_status: 'The export to a git remote: its target, lines, state (idle, ok, retrying, refused), each line\'s last exported patch and sha, and the last error.',
|
|
54
74
|
seam_import_cutover: 'Ends a following import for good (admins): a last pass, a check that the remote\'s tips equal the imported ones, then main and release/* take merges in Seam and main exports to the source after each merge. Refuses with remote_moved if git moved.',
|
|
55
|
-
seam_import_status: 'An import from a git remote: its state (reading, following, done, failed), its lines with their git tips,
|
|
75
|
+
seam_import_status: 'An import from a git remote: its state (reading, awaiting_authors, following, done, failed), its lines with their git tips, its last error, and while it waits the authors with the proposed member of each.',
|
|
76
|
+
seam_import_authors: 'Answers an import waiting in awaiting_authors (admins): a map of git author email → member id of the quilt; the import then runs. An email left out becomes a stub member. Refuses an id that is not a member (unknown_author).',
|
|
56
77
|
seam_import_poke: 'Makes a following import fetch its git remote now instead of at its next interval.',
|
|
57
78
|
seam_import_git: 'Imports a git repository on this computer into a quilt: HEAD\'s branch as main, every other branch as a line of its name, every tag, submodules as gitlink entries. Run it again into the same quilt to add only the new commits.',
|
|
58
79
|
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).',
|
|
59
80
|
seam_keys: 'Your signing keys (or member\'s): their ids, names, and when each was added or removed.',
|
|
60
81
|
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.',
|
|
82
|
+
seam_blocks: 'The quilt\'s open blocks (a block is one machine\'s view of part of a square, as a row on the server): id, square, holder, machine, mode, live or read-only, folders, and the commit it is at. square narrows to one square; mine: true to yours.',
|
|
83
|
+
seam_block_open: 'Opens a block on a square: mode sparse (files on disk, the default), virtual or api-only (nothing on disk; read and commit by calls). A live block follows the square\'s head and is for whoever may write in it; read_only: true opens a frozen block at any commit (at). paths limits it to folders. Up to 20 open blocks per member on a quilt (blocks_limit).',
|
|
84
|
+
seam_block_touch: 'The heartbeat of an open block: keeps it open (blocks untouched for seven days are closed) and moves a live block to the square\'s current head; add and remove change its folders.',
|
|
85
|
+
seam_clone: 'Checks out a line of a quilt (main by default) into a new folder on this computer, ready to work: the first commit there opens a square by itself. The folder is named after the quilt unless dir is given.',
|
|
86
|
+
seam_member_add: 'Adds a member to the quilt or changes their level (an admin): email (a person; one with no Seam account yet is invited, and is in the quilt from their first sign-in) or member_id (person:… or agent:…), level read, write or admin (default write; remove takes them out).',
|
|
87
|
+
seam_pull: 'In a block folder: brings the line\'s new patches in (uncommitted edits stay); on a square, rebases it onto its line\'s tip; on a square merged from elsewhere, moves the folder to the line\'s tip.',
|
|
88
|
+
seam_block_rebuild: 'In a block folder: finishes a move of the folder (rebase, checkout of its square) that stopped part way, from the journal in .seam; edits made meanwhile are kept as yours. On a whole folder it writes nothing.',
|
|
89
|
+
seam_block_close: 'Closes a block (its row on the server). The square, its work and any files on disk are untouched.',
|
|
61
90
|
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.',
|
|
62
91
|
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.',
|
|
63
92
|
seam_token_revoke: 'Revokes a quilt token (by id, or by name): it stops working at once. Admins only.',
|
|
93
|
+
seam_protections: 'The quilt\'s protections: who may list, read, write, merge or administer which paths (an ordered table; the last row that matches decides) and its teams. With path: the level you hold there. Refusals from it answer 403 protections with the path.',
|
|
94
|
+
seam_protections_set: 'Replaces the quilt\'s protections table (a person who is an admin; never an agent). rows: [{ path (\'\' for the whole quilt, a folder ending in /, a file or a glob), principal (everyone, people, agents, role:read|write|admin, team:<name>, person:<id>, agent:<id>, token:<id>, org_role:member|admin|owner in an org\'s quilt), level (list, read, write, merge, admin), exclude? }]; null goes back to the default rows.',
|
|
95
|
+
seam_verify: 'Checks the quilt\'s log against the server\'s signatures (attestations) and its published keys: every row signed, none changed, none missing. Needs a server that signs its log.',
|
|
96
|
+
seam_redact: 'Redacts content from the quilt for good (a person who is an admin): the blob\'s bytes leave every pack, every read gets a tombstone, and the same bytes are refused if added again. History and ids stay. Name blob, or path (and rev). Cannot be undone: confirm must be true.',
|
|
97
|
+
seam_seal: 'A sealed quilt\'s key (the server never sees its contents): status (key version, who holds it), or grant member: wrap the key this computer holds for that member\'s computers.',
|
|
98
|
+
seam_secret_scanning: 'Reads, or (a person who is an admin) sets, secret scanning on the quilt: refuse (a commit that looks like it holds a secret is refused with path, line and finding id), warn, or off.',
|
|
99
|
+
seam_public: 'Makes the quilt readable by anyone, signed in or not (public: true), or private again (a person who is an admin). The quilt\'s protections can still keep paths out of public view.',
|
|
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
|
+
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
|
+
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_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
|
+
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
|
+
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.',
|
|
107
|
+
seam_agent_token_list: 'The quilt\'s agent tokens you minted or hold (an admin sees all), never their secrets.',
|
|
108
|
+
seam_agent_token_revoke: 'Revokes an agent token: it stops working at once.',
|
|
109
|
+
seam_agents_make_lines: 'Whether agents may make lines, tags and releases in the quilt (a person who is an admin decides; where protections are on, only people may unless allowed).',
|
|
110
|
+
seam_metrics: 'The quilt\'s numbers the monitors watch (archiver lag, open scrub findings, overdue purges, derive queue) and which are past their threshold in ops/alerts.yml. Admin.',
|
|
111
|
+
seam_redactions: 'The quilt\'s redactions (its deny list): blob, seq, who and why.',
|
|
112
|
+
seam_team_set: 'Sets a team\'s members (a person who is an admin); members [] removes the team. Protections name teams as team:<name>.',
|
|
64
113
|
seam_key_remove: 'Removes one of your signing keys (key: ed25519:…). Commits it signed then show "removed_key" instead of "good".',
|
|
65
114
|
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>.',
|
|
66
115
|
seam_release_list: 'The quilt\'s releases, newest first, each with its notes.',
|
|
67
116
|
seam_release: 'Makes a release: a tag (name) on a revision (rev, default main) with release notes (notes).',
|
|
68
|
-
seam_open_square: 'Opens a square: your own unit of work, branched from the tip of main (or line). Returns its id (sq:…). Agents open squares, never lines. on: another open square (sq:…) to stack on: yours starts from its head, merges after it, and is rebased onto the line by itself when it merges.',
|
|
69
|
-
seam_squares: 'The open squares of a quilt, with holder, head, how far behind main each is and its conflicted paths. all: true for merged and abandoned ones too.',
|
|
70
|
-
seam_square_status: 'One square as the server sees it (head, behind_by, conflicted paths), the patches merged under it that changed its files,
|
|
117
|
+
seam_open_square: 'Opens a square: your own unit of work, branched from the tip of main (or line). Returns its id (sq:…). Agents open squares, never lines. on: another open square (sq:…) to stack on: yours starts from its head, merges after it, and is rebased onto the line by itself when it merges. With task_id, you get the task\'s open square back if it is yours; if someone else holds it you are told who (task_held), and another: true opens a second square on the task. stack: full-stack keys for the work, e.g. { db_branch: "supabase:<preview ref>" }: written into the square\'s first commit, so every later commit inherits them.',
|
|
118
|
+
seam_squares: 'The open squares of a quilt, with holder, head, how far behind main each is and its conflicted paths. all: true for merged and abandoned ones too; hidden: true for hidden ones; archived: true for archived ones.',
|
|
119
|
+
seam_square_status: 'One square as the server sees it (head, behind_by, conflicted paths), the patches merged under it that changed its files, which files would conflict if merged now, and other squares\' claims over its files (claims_overlap); in a block folder also the files changed since the last commit.',
|
|
71
120
|
seam_square_handoff: 'Hands your square to another member (to), with a note on what is done and what is left. You stop being able to commit to it.',
|
|
72
121
|
seam_square_merged: 'Closes an open square whose work a person put into git by hand while an import follows git, when the follower could not match it (the diff was edited on the way). Admins; git_sha is the imported git commit that holds the work.',
|
|
73
|
-
seam_abandon: 'Abandons a square whose work is not wanted. Its commits stay readable and seam_reopen brings it back.',
|
|
122
|
+
seam_abandon: 'Abandons a square whose work is not wanted. Its commits stay readable and seam_reopen brings it back. Squares stacked on it refuse the abandon (stacked_children, naming each holder); cascade: true abandons your own with it.',
|
|
74
123
|
seam_reopen: 'Reopens an abandoned square; you become its holder.',
|
|
75
|
-
|
|
76
|
-
|
|
124
|
+
seam_archive: 'Archives a square you hold or write to that has gone quiet: its claims are released and lists leave it out (seam_squares archived: true lists it). Its work is kept; a commit to it, or seam_unarchive, wakes it.',
|
|
125
|
+
seam_unarchive: 'Wakes an archived square. Its claims are not restored.',
|
|
126
|
+
seam_hide: 'Hides a square from lists (a scratch square, say). It stays open with its work and claims; seam_squares hidden: true lists it, seam_unhide shows it again.',
|
|
127
|
+
seam_unhide: 'Shows a hidden square in lists again.',
|
|
128
|
+
seam_square_edit: 'Changes a square\'s title, task (task_id null clears it) or line. The line changes only before the square\'s first commit.',
|
|
129
|
+
seam_share: 'Adds a writer to a square you hold, for pairing: up to three people besides you, each able to write to the quilt. An agent with its own account gets its own square instead.',
|
|
130
|
+
seam_unshare: 'Takes a writer off a square you hold, or takes you off one you were shared on.',
|
|
131
|
+
seam_events: 'What happened to your work since you last looked: patch.merged (a patch changed files your open square changed, and whether your merge would now conflict), conflict.added (a conflict was kept in your square), square.handoff, square.restacked (your stacked square was moved onto its parent\'s new head or the line, with how many conflicts it now holds), check.requested (you run a check a line names you for: run it on the commit and report with seam_check and its run_key). Pass after (the cursor from the last answer) to get only new ones. Pass wait (seconds, up to 30) to wait for the next one instead of answering at once.',
|
|
132
|
+
seam_events_webhook: 'Has the quilt server POST your events to a URL (signed with HMAC-SHA256 over <timestamp>.<body>, retried 3 times), so you are woken instead of polling: url, events (default all), secret (made for you if left out). url: "off" removes them all. add: true keeps your other webhooks (up to 5); remove: true removes the one at url.',
|
|
133
|
+
seam_absorb: 'Puts your square\'s uncommitted live work into the commit of the square that last changed each file (amending it and rewriting the commits above it), so a fix to commit 2 of 5 needs no manual rebase. Files no commit of the square changed stay live.',
|
|
77
134
|
seam_fold: 'Folds a square (square) into another square you hold (into): its work becomes one commit there and the square closes. Overlapping changes become conflict markers in the target.',
|
|
78
|
-
seam_split: 'Splits your square by paths
|
|
79
|
-
seam_log: 'History, newest first: rev (default main, or your square, or a..b), paths to limit it, limit (default 20). follow: true with one path follows the file through its renames; search: "<text>" keeps only commits that changed how many times the text appears (git log -S). Each patch on main is one line\'s merge.',
|
|
135
|
+
seam_split: 'Splits your square: by paths (groups: [[paths], ...]) or by claim (one group per claim on the square that covers a changed path; the claim and its holder go with it). Each group moves to a new square (same base) as one commit; the largest group, and paths no group names, stay in this square. stacked: true chains the new squares on this one in order instead of opening them side by side.',
|
|
136
|
+
seam_log: 'History, newest first: rev (default main, or your square, or a..b), paths to limit it, limit (default 20). follow: true with one path follows the file through its renames; search: "<text>" keeps only commits that changed how many times the text appears (git log -S); match: "<regex>" keeps commits whose added or removed lines match (git log -G). message (with message_regex, ignore_case) filters on the commit message (git log --grep); author (member id or part of a name), on_behalf_of, task, since and until (an ISO date, or 2h, 3d, 1w) filter on who, what for and when. group_by (author, on_behalf_of, task, tool) gives counts with lines added and removed instead of commits; graph: true names the square each merge brought in; include_hidden: true shows a square\'s commits replaced by an amend. When there is more, pass the answer\'s cursor for the next page. Each patch on main is one line\'s merge. With one path on a line, existed says whether it was ever there and, if it is gone now, last_present is the last patch it was there in.',
|
|
80
137
|
seam_describe: 'Names a revision (rev, default main) by the nearest tag behind it: v1.0 on the tag itself, v1.0-3-<commit> three commits past it, the commit alone when no tag is behind it.',
|
|
81
138
|
seam_merge_base: 'Where two revisions (a, b: lines, squares, patches, commits) last shared history: the commit a merge of one into the other starts from.',
|
|
82
|
-
|
|
139
|
+
seam_stack: 'The full-stack pointers at a revision (a line, patch:n, sq:…, a commit): each key with its value, where it was set (a patch number or c:<commit>) and how (set by a commit, or derived: db_migration is the newest .sql file under supabase/migrations). Read it to know which schema and database the code at a patch runs against.',
|
|
140
|
+
seam_infra_paths: 'Sets (an admin) the globs seam_stack derives infra from (tree:<digest> of the matching files): a list, "default" (fly.toml, infra/**, .github/workflows/**, Dockerfile), or null for none.',
|
|
141
|
+
seam_owner_idle_hours: 'Sets (an admin) how long an owner claim (a lockfile or migration) holds after its square last wrote there: whole hours 1..24, or null for the 2-hour default.',
|
|
142
|
+
seam_sealed: 'Switches a quilt that holds no files yet to sealed (sealed: true: the server never reads its files, its key is made on this computer) or back (a person who is an admin). A quilt with files is refused: every file\'s id would change, so its files move to a new quilt instead.',
|
|
143
|
+
seam_public_reads: 'Lists (an admin) what readers with no sign-in fetched from a public quilt over the last 90 days: per hashed client address and 10 minutes, the answers and their bytes.',
|
|
144
|
+
seam_migrations_dir: 'Sets (an admin) the folder seam_stack derives db_migration from: a folder, "" for none, or null for supabase/migrations.',
|
|
145
|
+
seam_ancestor: 'Whether revision a is in revision b\'s history (git merge-base --is-ancestor): ancestor, first_parent (on b\'s own line of commits) and distance.',
|
|
146
|
+
seam_contains: 'Where a commit (rev) went: the lines whose history has it (a square\'s commit counts through the patch its square merged as), and picked_to, the squares that cherry-picked or reverted it, merged or not.',
|
|
147
|
+
seam_search: 'Code search. query: words and "phrases" (content, case-insensitive; case:yes), /regex/, path:src/**, lang:ts, symbol:name (definitions first, then calls), class:logic (files whose last change had that class), -term, OR and ( ); ranked: a path match, then a definition, then content. In the quilt given, or with quilts (or all: true) across them; rev: any revision (default main). Or pattern instead of query: the lines matching a regular expression in every quilt you can read, under paths (fixed, ignore_case, files_only, limit).',
|
|
83
148
|
seam_grep: 'The lines matching a pattern (a JavaScript regular expression; fixed: true for plain text) in the files at a revision (rev, default main or your square; sq:<id>:live too), under paths. ignore_case, files_only, limit (default 200).',
|
|
84
|
-
seam_bisect: 'Finds the commit that broke something between good (a revision without the problem) and bad (default main), along first parents (on a line: its patches). Without run, one step: pass marks { <commit>: "good" | "bad" | "skip" } for those tested so far and it names the next to test, or the first bad one. With run (a shell command) it checks out each commit into a fresh folder on this computer and runs it there: exit 0 good, 125 skip, other bad.',
|
|
149
|
+
seam_bisect: 'Finds the commit that broke something between good (a revision without the problem) and bad (default main), along first parents (on a line: its patches). Without run, one step: pass marks { <commit>: "good" | "bad" | "skip" } for those tested so far and it names the next to test, or the first bad one. With run (a shell command) it checks out each commit into a fresh folder on this computer and runs it there: exit 0 good, 125 skip, other bad. paths: only commits that changed them are tested.',
|
|
85
150
|
seam_show: 'One commit or patch: who, when, why, which square, and its diff. rev: patch:12, main~1, sq:…, a commit id, git:<sha>. format: unified (default), stat or names.',
|
|
86
|
-
seam_diff: 'The difference between two revisions (from, to); with only to, against its parent (for a square: against where it branched). format: unified, stat or names.',
|
|
87
|
-
seam_blame: 'Each line of a file at a revision, with the commit and patch that last changed it and who made it. Computed by the server.',
|
|
151
|
+
seam_diff: 'The difference between two revisions (from, to); with only to, against its parent (for a square: against where it branched). format: unified, stat or names. A file moved with mostly the same lines shows once, as renamed from its old path (renames: off, exact or similar, the default). ignore_whitespace: trailing or all looks through whitespace changes; context: lines around each change (default 3). A large diff comes in pages of limit files (default 500): pass the answer\'s cursor for the next. stack: true lists the full-stack pointers (db_migration, db_branch, …) that changed instead of the files.',
|
|
152
|
+
seam_blame: 'Each line of a file at a revision, with the commit and patch that last changed it and who made it. Computed by the server and kept. range: [first, last] lines only; ignore: commits whose lines keep the attribution they had before (the file .seam-blame-ignore at the revision lists more); moves: true keeps the origin of a block moved within the file; reverse: a later revision on the same line, to get for each line the last patch that still had it.',
|
|
88
153
|
seam_cat: 'A file\'s content at any revision (path, rev). To change a file, read it from your own square (rev sq:<id>), not from main: main moves while you work, and a commit built from a different version of main than your square started from can undo work that merged in between. A conflicted file comes back with its conflict markers: format diff (default: one side in full after +++++++, each other side as a diff from its base after %%%%%%%), snapshot (every side in full) or git (<<<<<<< ======= >>>>>>>, two sides only).',
|
|
89
154
|
seam_ls: 'The files at a revision, under paths (directories).',
|
|
90
155
|
seam_commit: 'Seals your square\'s changes as a commit (a save point). In a block folder: the folder\'s changed files (paths: only these). Elsewhere: files: [{ path, content }] (text; base64: true for bytes) and delete: [paths]. message says why. Committing a conflicted file without its markers settles the conflict. amend: true changes the square\'s last commit instead (its changes plus these, a new message if given), as git commit --amend does. sign: true signs the new commit with this computer\'s key.',
|
|
91
|
-
seam_merge: 'Merges your square into a line (into: "main") or, with from, merges another square or revision into your square (a two-parent commit; conflicts stay in your square as markers). Into a line: the server rebases it onto the tip as one commit and moves main, retrying lost races itself. The result is a patch. A real conflict is refused: the square is rebased with the conflict kept in it (your folder gets the markers); settle it and merge again. dry_run: true says what would happen.',
|
|
156
|
+
seam_merge: 'Merges your square into a line (into: "main") or, with from, merges another square or revision into your square (a two-parent commit; conflicts stay in your square as markers). Into a line: the server rebases it onto the tip as one commit and moves main, retrying lost races itself. The result is a patch. A real conflict is refused: the square is rebased with the conflict kept in it (your folder gets the markers); settle it and merge again. Live work you shared (seam_live_set) is committed first (commit_live: false to leave it out); if someone else is still saving it the merge is refused with writers_active. dry_run: true says what would happen. Merged from its own block folder, the folder moves to the line\'s new tip and the next commit opens a new square.',
|
|
157
|
+
seam_reparent: 'Moves your square onto another open square on its line (it then merges after that one), or off its stack onto its line\'s tip (to: "line"). The work is rebased there; conflicts become markers in the square.',
|
|
92
158
|
seam_rebase: 'Moves your square onto the tip of its line (or onto a revision). Conflicts become conflict markers in the square, never a stop.',
|
|
93
159
|
seam_revert: 'Undoes a patch (or commit): a new square from the tip whose one commit inverts it. Merge that square with seam_merge.',
|
|
94
160
|
seam_cherry_pick: 'Applies one commit\'s change (from) to your square as a new commit.',
|
|
95
|
-
seam_resolve: 'Settles conflicted files in your square by choosing a side: entries [{ path, choose: ours | theirs | base | suggestion | delete }]; suggestion takes the word-level merge a conflict offers (its suggestion, shown when the merge is refused). (Or edit the file and commit it without the markers.) The answer is remembered: the same conflict met again in a rebase or merge is settled the same way, unless remember: false.',
|
|
96
|
-
seam_resolutions: 'The remembered resolutions: conflicts settled once that seam settles the same way when they come back (path: only that file\'s).',
|
|
161
|
+
seam_resolve: 'Settles conflicted files in your square by choosing a side: entries [{ path, choose: ours | theirs | base | suggestion | delete | keep_both }]; suggestion takes the word-level merge a conflict offers (its suggestion, shown when the merge is refused); when both sides renamed one file, ours or theirs keeps that side\'s path and keep_both keeps both. Or rule: additive (both inserts, in order), whitespace (the side that changed more than spacing), words (word by word) or union (both sides, ours first). (Or edit the file and commit it without the markers.) The answer is remembered: the same conflict met again in a rebase or merge is settled the same way, unless remember: false.',
|
|
162
|
+
seam_resolutions: 'The remembered resolutions: conflicts settled once that seam settles the same way when they come back, per file, and per clashing region within the same squares (path: only that file\'s; hunks: true lists the regions too). One unused for 90 days is dropped.',
|
|
97
163
|
seam_forget_resolution: 'Forgets a remembered resolution (key: its key or first 8+ characters), so that conflict is asked about again next time.'
|
|
98
164
|
}
|
|
99
165
|
|
|
100
166
|
export const SEAM_INPUTS = {
|
|
101
167
|
seam_live: { quilt: QUILT, square: SQUARE },
|
|
102
|
-
|
|
168
|
+
seam_daemon: { action: z.enum(['start', 'status', 'flush', 'stop']).optional().describe('Default status') },
|
|
169
|
+
seam_live_join: { quilt: QUILT, square: SQUARE, member: z.string().max(200).optional().describe('Another member to ask about (the holder or an admin asks); default you') },
|
|
170
|
+
seam_line_require: { quilt: QUILT, line: z.string().max(120), approvals: z.number().int().min(0).max(20).optional(), checks: z.array(z.string().max(100)).max(20).optional().describe('Check names that must be green on a square\'s latest commit, e.g. ["ci"]; [] for none'), runners: z.record(z.string().max(100), z.array(z.string().max(200)).max(20)).optional().describe('Who runs each check, e.g. {"ci": ["agent:ci"]}: they are asked to run it (check.requested) and only their verdicts count'), tiers: z.record(z.string().max(100), z.array(z.enum(['pre_merge', 'post_merge'])).max(2)).optional().describe('Where each check runs, e.g. {"e2e": ["post_merge"]}; default: a required check runs pre_merge on the square, a check with runners runs post_merge on the line'), tests: z.array(z.string().max(300)).max(50).optional().describe('Globs of the line\'s test files, e.g. ["test/**"]: a pre-merge check.requested names the tests a change reaches (affected)'), ci_config: z.array(z.string().max(300)).max(50).optional().describe('Globs of ci config: a change there makes affected confidence "all"'), only: z.record(z.string().max(100), z.array(z.string().max(300)).max(50)).optional().describe('The paths each check is about, e.g. {"deploy-api": ["src/**", "!src/vault/**"]}: it is asked for, and required, only when a change touches them'), human_only: z.array(z.string().max(300)).max(50).optional().describe('Globs agents may read and never change on this line, e.g. ["supabase/migrations/**"]; a person sets them'), rules: z.array(z.object({ id: z.string().max(64), match: z.object({ paths: z.array(z.string().max(300)).optional(), classes: z.array(z.string()).optional(), flags: z.array(z.string()).optional() }).optional(), needs: z.object({ approvals: z.number().int().min(0).max(20), from: z.union([z.enum(['anyone', 'person', 'owner', 'agent']), z.array(z.string().max(200))]).optional(), not_requester: z.boolean().optional(), checks: z.array(z.string().max(100)).optional() }) })).max(100).optional().describe('Rules by path and change class: what each kind of change needs, e.g. [{ id: "schema", match: { classes: ["schema"] }, needs: { approvals: 1, from: "owner" } }]'), mode: z.enum(['enforce', 'evaluate']).optional(), pre_merge_checks: z.boolean().optional().describe('true: the required checks must be green on the tree that merges; a square the line moved past on its own paths is refused (checks_stale) until it rebases and its checks run again'), approval_survives: z.enum(['none', 'whitespace', 'comment-only', 'docs']).nullable().optional().describe('The strictest change class a new commit may have and keep earlier approvals (default comment-only; none: only a restack that leaves the change the same); null: the default'), auto_combine: z.enum(['lines', 'additive', 'whitespace']).optional().describe('How far a merge into the line combines clashing changes by itself: lines (only different lines), additive (also two inserts at one place), whitespace (default: also a side that only respaced)'), batch: z.object({ patches: z.number().int().min(1).max(1000), seconds: z.number().int().min(1).max(86400) }).nullable().optional().describe('Post-merge checks run once a batch has this many patches or has waited this long, at the tip; null: after every merge (the default)'), bisect_width: z.number().int().min(1).max(64).optional().describe('Runs a round when a red post-merge check is bisected (default 8)'), needs_stack: z.array(z.string().max(32)).max(16).nullable().optional().describe('Stack keys every merge into the line must carry, e.g. ["db_branch"] (refused 428 policy without one); null clears it'), monotonic_stack: z.array(z.string().max(32)).max(16).nullable().optional().describe('Stack keys a merge may never lower, e.g. ["db_migration"]; null clears it'), migrations_in_order: z.boolean().nullable().optional().describe('true: a merge that adds a migration sorting before the tip\'s db_migration is refused (out_of_order)'), stack_defaults: z.record(z.string().max(32), z.string().max(500)).nullable().optional().describe('Values a merge into the line gives its commit where the square\'s differ, e.g. {"db_branch": "supabase:<production ref>"} so a preview database is never pointed at after it is gone; null clears it') },
|
|
103
171
|
seam_reviews: { quilt: QUILT, square: SQUARE },
|
|
104
|
-
|
|
172
|
+
seam_interdiff: { quilt: QUILT, square: SQUARE, since: z.string().max(200).optional().describe('A revision to compare with; default: the head of your latest verdict on the square') },
|
|
173
|
+
seam_review: { quilt: QUILT, square: SQUARE, kind: z.enum(['approve', 'request_changes', 'comment', 'dismiss', 'request']), asked: z.array(z.string().max(200)).max(20).optional().describe('For kind request: member ids to ask for a review (optional when the line\'s rules name reviewers)'), body: z.string().max(20000).optional(), path: z.string().max(1000).optional().describe('A file, for a comment on it'), line: z.number().int().min(1).optional().describe('A line of that file at the square\'s head'), scope: z.array(z.string().max(300)).min(1).max(50).optional().describe('For kind approve: approve only these paths or globs, e.g. ["db/**"]; it counts for the rules on those paths, and for the line only when it covers the whole change'), suggestion: z.object({ text: z.string().max(20000).describe('The text to put in place of line..to_line ("" removes them)'), to_line: z.number().int().min(1).optional().describe('The last line it replaces (default: line)') }).optional().describe('For a comment on a line: a change the holder can apply with seam_apply_suggestion') },
|
|
174
|
+
seam_apply_suggestion: { quilt: QUILT, square: SQUARE, review: z.string().max(64).describe('The review id of the suggestion (from seam_reviews)') },
|
|
105
175
|
seam_claims: { quilt: QUILT, square: SQUARE },
|
|
106
|
-
seam_claim: { quilt: QUILT, square: SQUARE, patterns: z.array(z.string().max(500)).min(1).max(50).describe('Files, folders or globs, e.g. src/api/** or README.md'), note: z.string().max(300).optional().describe('What you are doing with them') },
|
|
176
|
+
seam_claim: { quilt: QUILT, square: SQUARE, patterns: z.array(z.string().max(500)).min(1).max(50).describe('Files, folders or globs, e.g. src/api/** or README.md'), note: z.string().max(300).optional().describe('What you are doing with them'), expires_in: z.number().int().min(60).max(604800).optional().describe('Seconds until the claim lets go by itself (60 to a week); without it the claim holds until the square merges or you let go') },
|
|
107
177
|
seam_check: { quilt: QUILT, check: z.string().max(200).describe('The check\'s name, e.g. ci'), status: z.enum(['red', 'green', 'flaky']), run_key: z.string().max(600).optional().describe('The run_key of the check.requested event or run being answered'), url: z.string().max(2000).optional().describe('A link to the run\'s log (http(s) or file://), kept on the run'), square: z.string().max(100).optional().describe('A square (sq:…): the result is for its latest commit, which a line may require green before a merge'), line: z.string().max(110).optional().describe('Default main'), patch: z.number().int().min(1).optional().describe('The patch number the check failed at: reverts it') },
|
|
108
178
|
seam_unclaim: { quilt: QUILT, square: SQUARE, patterns: z.array(z.string().max(500)).min(1).max(50).describe('The claimed patterns (or claim ids)') },
|
|
109
|
-
|
|
179
|
+
seam_claim_request: { quilt: QUILT, square: SQUARE, claim: z.string().max(500).describe('The claim\'s id or pattern'), title: z.string().min(1).max(120).describe('What you need it for'), description: z.string().max(300).optional().describe('Your plan, briefly') },
|
|
180
|
+
seam_claim_withdraw: { quilt: QUILT, request: z.string().max(100).describe('The request id') },
|
|
181
|
+
seam_claim_handoff: { quilt: QUILT, square: SQUARE, claim: z.string().max(500).describe('The claim\'s id or pattern'), to: z.string().max(100).optional().describe('A request id; default the first in line'), note: z.string().max(2000).optional().describe('What you changed, what is left, gotchas') },
|
|
182
|
+
seam_claim_finish: { quilt: QUILT, square: SQUARE, note: z.string().max(2000).optional().describe('For anyone a claim is handed to: what you changed, what is left') },
|
|
183
|
+
seam_live_set: { quilt: QUILT, square: SQUARE, files: z.array(z.object({ path: z.string().max(1000), content: z.string(), base64: z.boolean().optional(), mode: z.enum(['file', 'exec']).optional() })).max(2000).optional(), delete: z.array(z.string().max(1000)).max(2000).optional(), paths: z.array(z.string().max(1000)).max(200).optional().describe('In a block folder: share only these files or folders, leaving the rest of what is shared as it is') },
|
|
110
184
|
seam_restore: { paths: z.array(z.string().max(1000)).min(1).max(200).describe('Files or folders, relative to the block folder ("." for all)') },
|
|
111
185
|
seam_reset: { rev: z.string().max(200).optional().describe('The revision: sq:<id>~1, patch:12, tag:v1.0, a commit; default the folder\'s last commit') },
|
|
112
186
|
seam_clean: { paths: PATHS, dry_run: z.boolean().optional() },
|
|
113
|
-
seam_quilts: { new: z.string().max(63).optional().describe('Make a quilt with this slug') },
|
|
187
|
+
seam_quilts: { new: z.string().max(63).optional().describe('Make a quilt with this slug'), org: z.string().max(100).optional().describe('With new: the org that owns it (you are its admin or owner)') },
|
|
114
188
|
seam_line_list: { quilt: QUILT },
|
|
115
189
|
seam_line_new: { quilt: QUILT, name: z.string().max(110).describe('release/<name> (or just <name>)'), from: z.string().max(200).optional().describe('The revision it starts at: main, patch:12, a commit, sq:…; default main') },
|
|
116
190
|
seam_line_freeze: { quilt: QUILT, line: z.string().max(110) },
|
|
@@ -118,36 +192,75 @@ export const SEAM_INPUTS = {
|
|
|
118
192
|
seam_tag_list: { quilt: QUILT },
|
|
119
193
|
seam_github: {},
|
|
120
194
|
seam_github_import: { repo: z.string().max(201).describe('owner/name, as seam_github lists it'), installation: z.string().max(20).describe('The installation id seam_github shows for it'), into: z.string().max(63).optional().describe('The new quilt\'s slug (default the repo\'s name)'), follow: z.boolean().optional().describe('Keep following it (default true)') },
|
|
121
|
-
seam_import_remote: { url: z.string().max(2000).describe('The git remote: https://, ssh:// or user@host:path'), into: z.string().max(63).describe('The new quilt\'s slug'), follow: z.boolean().optional().describe('Keep following main and release/* until cut-over'), interval: z.number().int().min(60).optional().describe('Seconds between follow passes (default 600)'), credential: z.string().max(100).optional().describe('The name of the server secret that reads the remote (user:token or a token)') },
|
|
122
|
-
seam_export_remote: { quilt: z.string().max(100).optional().describe('The quilt'), target: z.string().max(2000).describe('The git remote'), lines: z.array(z.string().max(100)).max(20).optional().describe('Lines to export (default ["main"]); release/* for every release line'), releases: z.boolean().optional().describe('Push release tags too'), credential: z.string().max(100).optional().describe('The name of the server secret that may push'), scratch: z.boolean().optional().describe('A scratch remote nothing pins a sha from'), enabled: z.boolean().optional().describe('false turns the export off') },
|
|
195
|
+
seam_import_remote: { url: z.string().max(2000).describe('The git remote: https://, ssh:// or user@host:path'), into: z.string().max(63).describe('The new quilt\'s slug'), follow: z.boolean().optional().describe('Keep following main and release/* until cut-over'), interval: z.number().int().min(60).optional().describe('Seconds between follow passes (default 600)'), credential: z.string().max(100).optional().describe('The name of the server secret that reads the remote (user:token or a token)'), authors: z.record(z.string(), z.string()).optional().describe('Git author email → member id of the quilt'), ask_authors: z.boolean().optional().describe('Wait in awaiting_authors with the authors and a proposed map until seam_import_authors answers') },
|
|
196
|
+
seam_export_remote: { quilt: z.string().max(100).optional().describe('The quilt'), target: z.string().max(2000).describe('The git remote'), lines: z.array(z.string().max(100)).max(20).optional().describe('Lines to export (default ["main"]); release/* for every release line'), releases: z.boolean().optional().describe('Push release tags too'), credential: z.string().max(100).optional().describe('The name of the server secret that may push'), scratch: z.boolean().optional().describe('A scratch remote nothing pins a sha from'), enabled: z.boolean().optional().describe('false turns the export off'), lfs: z.object({ url: z.string().max(2000).describe('The LFS endpoint (…/info/lfs)'), threshold_bytes: z.number().int().min(0).optional().describe('Files over this many bytes go to LFS (default 50 MiB)') }).optional().describe('Git LFS on the remote: big files and filter=lfs paths go as LFS pointers. Set before the first export') },
|
|
197
|
+
seam_export_archive: { quilt: z.string().max(100).optional().describe('The quilt'), out: z.string().max(1000).optional() },
|
|
198
|
+
seam_export_tarball: { quilt: z.string().max(100).optional().describe('The quilt'), rev: z.string().max(200).optional(), paths: z.array(z.string().max(1000)).max(100).optional(), out: z.string().max(1000).optional() },
|
|
123
199
|
seam_export_status: { quilt: z.string().max(100).optional().describe('The quilt') },
|
|
124
200
|
seam_import_cutover: { id: z.string().max(100).describe('The import id'), credential: z.string().max(100).optional().describe('The server secret that may push to the source (default the import\'s)') },
|
|
125
201
|
seam_import_status: { id: z.string().max(100).describe('The import id') },
|
|
202
|
+
seam_import_authors: { id: z.string().max(100).describe('The import id'), authors: z.record(z.string(), z.string()).describe('Git author email → member id of the quilt') },
|
|
126
203
|
seam_import_poke: { id: z.string().max(100).describe('The import id') },
|
|
127
|
-
seam_import_git: { path: z.string().max(1000).describe('The git repository\'s folder'), into: z.string().max(63).optional().describe('The quilt\'s slug (default the folder\'s name); a quilt an import made takes only the new commits'), validate: z.boolean().optional().describe('Compare every patch written with git\'s tree') },
|
|
204
|
+
seam_import_git: { path: z.string().max(1000).describe('The git repository\'s folder'), into: z.string().max(63).optional().describe('The quilt\'s slug (default the folder\'s name); a quilt an import made takes only the new commits'), validate: z.boolean().optional().describe('Compare every patch written with git\'s tree'), authors: z.record(z.string().max(320), z.string().max(200)).optional().describe('Git author emails mapped to member ids of the quilt (like .mailmap); other authors become stub members') },
|
|
128
205
|
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"') },
|
|
129
206
|
seam_keys: { member: z.string().max(200).optional().describe('Whose keys (default yours)') },
|
|
130
207
|
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() },
|
|
208
|
+
seam_blocks: { quilt: QUILT, square: z.string().max(100).optional().describe('Only blocks on this square'), mine: z.boolean().optional().describe('Only your blocks') },
|
|
209
|
+
seam_block_open: { quilt: QUILT, square: z.string().max(100).describe('The square the block is a view of'), paths: z.array(z.string().max(1024)).max(200).optional().describe('Folders it covers (default: the whole quilt)'), mode: z.enum(['sparse', 'virtual', 'api-only']).optional(), machine: z.string().max(200).optional().describe('Which machine holds it (default api for api-only)'), at: z.string().max(200).optional().describe('The commit of a read_only block'), read_only: z.boolean().optional().describe('A frozen block: no writes, stays at its commit'), exclude: z.array(z.string().max(1024)).max(200).optional().describe('Patterns never fetched'), max_fetch_bytes: z.number().int().min(0).optional().describe('Refuse to fetch more than this (default 2 GiB)') },
|
|
210
|
+
seam_block_touch: { quilt: QUILT, id: z.string().max(100).describe('The block'), add: z.array(z.string().max(1024)).max(200).optional().describe('Folders to add'), remove: z.array(z.string().max(1024)).max(200).optional().describe('Folders to remove') },
|
|
211
|
+
seam_block_rebuild: {},
|
|
212
|
+
seam_clone: { quilt: z.string().max(100).describe('The quilt to clone (its slug or id)'), dir: z.string().max(1000).optional().describe('The new folder (default: the quilt\'s name, under the folder seam mcp runs in)'), line: z.string().max(200).optional().describe('The line (default main)') },
|
|
213
|
+
seam_pull: {},
|
|
214
|
+
seam_member_add: { quilt: QUILT, email: z.string().optional(), member_id: z.string().optional(), level: z.enum(['read', 'write', 'admin', 'remove']).optional() },
|
|
215
|
+
seam_block_close: { quilt: QUILT, id: z.string().max(100).describe('The block') },
|
|
131
216
|
seam_token_list: { quilt: QUILT },
|
|
132
217
|
seam_token_new: { quilt: QUILT, name: z.string().max(100).describe('What it is for, e.g. ci'), level: z.enum(['read', 'write']).optional() },
|
|
133
218
|
seam_token_revoke: { quilt: QUILT, token: z.string().max(100).describe('The token\'s id or name') },
|
|
219
|
+
seam_protections: { quilt: QUILT, path: z.string().max(500).optional().describe('A path to ask about: the level you hold there') },
|
|
220
|
+
seam_protections_set: { quilt: QUILT, rows: z.array(z.object({ path: z.string().max(500), principal: z.string().max(200), level: z.enum(['list', 'read', 'write', 'merge', 'admin']), exclude: z.boolean().optional(), note: z.string().max(300).optional() })).max(2000).nullable() },
|
|
221
|
+
seam_verify: { quilt: QUILT, after: z.number().int().min(0).optional().describe('Check rows after this seq only') },
|
|
222
|
+
seam_redact: { quilt: QUILT, blob: z.string().regex(/^[0-9a-f]{64}$/).optional(), path: z.string().max(1000).optional(), rev: z.string().max(200).optional(), reason: z.string().min(1).max(500), confirm: z.literal(true).describe('Redaction cannot be undone') },
|
|
223
|
+
seam_redactions: { quilt: QUILT },
|
|
224
|
+
seam_metrics: { quilt: QUILT },
|
|
225
|
+
seam_agents_make_lines: { quilt: QUILT, allow: z.boolean() },
|
|
226
|
+
seam_agent_token_new: { quilt: QUILT, agent: z.string().max(200).optional().describe('agent:<id>, when a person mints for their agent'), max_level: z.enum(['read', 'write', 'merge']).optional(), squares: z.union([z.literal('own'), z.array(z.string().max(100))]).optional(), paths: z.array(z.string().max(500)).max(50).optional(), hours: z.number().min(0.02).max(24).optional(), checks: z.boolean().optional().describe('whether the token reports check verdicts (a check runner)'), rate: z.record(z.string(), z.number().int().min(1)).optional().describe('lower per-token limits, e.g. { commit_rate: 10 }: token_rate, commit_rate, merge_rate, fetch_rate; never above the defaults') },
|
|
227
|
+
seam_agent_token_list: { quilt: QUILT },
|
|
228
|
+
seam_agent_token_revoke: { quilt: QUILT, id: z.string().max(100) },
|
|
229
|
+
seam_orgs: { org: z.string().max(100).optional() },
|
|
230
|
+
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() },
|
|
231
|
+
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
|
+
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
|
+
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() },
|
|
234
|
+
seam_policy: { quilt: QUILT, square: z.string().max(100).optional(), line: z.string().max(120).optional() },
|
|
235
|
+
seam_public: { quilt: QUILT, public: z.boolean() },
|
|
236
|
+
seam_sealed: { quilt: QUILT, sealed: z.boolean() },
|
|
237
|
+
seam_secret_scanning: { quilt: QUILT, mode: z.enum(['refuse', 'warn', 'off']).optional() },
|
|
238
|
+
seam_seal: { quilt: QUILT, action: z.enum(['status', 'grant']).optional(), member: z.string().max(200).optional().describe('For grant: the member id to give the key to') },
|
|
239
|
+
seam_team_set: { quilt: QUILT, name: z.string().max(63), members: z.array(z.string().max(200)).max(1000).describe('Member ids, e.g. ["person:ab12", "agent:ci"]') },
|
|
134
240
|
seam_key_remove: { key: z.string().max(40).describe('The key id, ed25519:…') },
|
|
135
241
|
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') },
|
|
136
242
|
seam_release_list: { quilt: QUILT },
|
|
137
243
|
seam_release: { quilt: QUILT, name: z.string().max(100).describe('The release\'s tag, e.g. v1.0'), rev: z.string().max(200).optional().describe('default main'), notes: z.string().max(20000).describe('Release notes') },
|
|
138
|
-
seam_open_square: { quilt: QUILT, title: z.string().max(200).describe('What the work is'), line: z.string().max(100).optional(), task_id: z.string().max(64).optional(), on: z.string().max(64).optional().describe('A square to stack on (sq:…)') },
|
|
139
|
-
seam_squares: { quilt: QUILT, all: z.boolean().optional(), mine: z.boolean().optional() },
|
|
244
|
+
seam_open_square: { quilt: QUILT, title: z.string().max(200).describe('What the work is'), line: z.string().max(100).optional(), task_id: z.string().max(64).optional(), on: z.string().max(64).optional().describe('A square to stack on (sq:…)'), another: z.boolean().optional().describe('With task_id: open a second square on a task someone else already holds'), stack: z.record(z.string().max(32), z.string().max(500)).optional().describe('Stack keys for the square\'s first commit, e.g. {"db_branch": "supabase:abcd"}') },
|
|
245
|
+
seam_squares: { quilt: QUILT, all: z.boolean().optional(), mine: z.boolean().optional(), hidden: z.boolean().optional(), archived: z.boolean().optional() },
|
|
140
246
|
seam_square_status: { quilt: QUILT, square: SQUARE },
|
|
141
|
-
seam_log: { quilt: QUILT, rev: REV, paths: PATHS, limit: z.number().int().min(1).max(500).optional(), follow: z.boolean().optional().describe('Follow one file (paths: [it]) through its renames'), search: z.string().max(500).optional().describe('Only commits that changed how many times this text appears'), show_signature: z.boolean().optional().describe('Check and show each commit\'s signature') },
|
|
247
|
+
seam_log: { quilt: QUILT, rev: REV, paths: PATHS, limit: z.number().int().min(1).max(500).optional(), follow: z.boolean().optional().describe('Follow one file (paths: [it]) through its renames'), search: z.string().max(500).optional().describe('Only commits that changed how many times this text appears'), show_signature: z.boolean().optional().describe('Check and show each commit\'s signature'), match: z.string().max(500).optional().describe('Only commits whose added or removed lines match this regular expression (git log -G)'), message: z.string().max(500).optional().describe('Only commits whose message holds this text (git log --grep)'), message_regex: z.boolean().optional().describe('message is a regular expression'), ignore_case: z.boolean().optional().describe('message matches in any case'), author: z.string().max(200).optional().describe('Only commits by this member id, or whose author\'s name holds this text'), on_behalf_of: z.string().max(200).optional().describe('Only commits made on behalf of this member id'), task: z.string().max(200).optional().describe('Only commits for this task id'), since: z.string().max(100).optional().describe('Only commits from this time on: an ISO date, or 30m, 2h, 3d, 1w, yesterday'), until: z.string().max(100).optional().describe('Only commits up to this time'), group_by: z.enum(['author', 'on_behalf_of', 'task', 'tool']).optional().describe('Counts per author, on_behalf_of, task or tool (with lines added and removed) instead of commits'), graph: z.boolean().optional().describe('Name the square each merge brought in'), include_hidden: z.boolean().optional().describe('Show a square\'s commits replaced by an amend'), cursor: z.string().max(2000).optional().describe('The cursor from the last answer: its next page') },
|
|
142
248
|
seam_describe: { quilt: QUILT, rev: REV },
|
|
143
249
|
seam_merge_base: { quilt: QUILT, a: z.string().max(200), b: z.string().max(200) },
|
|
144
|
-
|
|
250
|
+
seam_infra_paths: { quilt: QUILT, paths: z.union([z.array(z.string().max(300)).max(50), z.literal('default')]).nullable() },
|
|
251
|
+
seam_owner_idle_hours: { quilt: QUILT, hours: z.number().int().min(1).max(24).nullable() },
|
|
252
|
+
seam_public_reads: { quilt: QUILT },
|
|
253
|
+
seam_migrations_dir: { quilt: QUILT, dir: z.string().max(200).nullable() },
|
|
254
|
+
seam_stack: { quilt: QUILT, rev: z.string().max(200).optional().describe('A revision; main by default') },
|
|
255
|
+
seam_ancestor: { quilt: QUILT, a: z.string().max(200), b: z.string().max(200) },
|
|
256
|
+
seam_contains: { quilt: QUILT, rev: z.string().max(200) },
|
|
257
|
+
seam_search: { query: z.string().max(1000).optional(), quilt: QUILT, all: z.boolean().optional(), pattern: z.string().max(500).optional(), quilts: z.array(z.string().max(200)).max(100).optional(), rev: REV, paths: PATHS, ignore_case: z.boolean().optional(), fixed: z.boolean().optional(), files_only: z.boolean().optional(), limit: z.number().int().min(1).max(2000).optional() },
|
|
145
258
|
seam_grep: { quilt: QUILT, pattern: z.string().max(500), rev: REV, paths: PATHS, ignore_case: z.boolean().optional(), fixed: z.boolean().optional(), files_only: z.boolean().optional(), limit: z.number().int().min(1).max(2000).optional() },
|
|
146
|
-
seam_bisect: { quilt: QUILT, good: z.string().max(200).describe('A revision without the problem'), bad: z.string().max(200).optional().describe('A revision with it; default main'), marks: z.record(z.string().max(100), z.enum(['good', 'bad', 'skip'])).optional().describe('Commits tested so far'), run: z.string().max(4000).optional().describe('A check command, run in each checked-out commit: exit 0 good, 125 skip, other bad') },
|
|
259
|
+
seam_bisect: { quilt: QUILT, paths: z.array(z.string().max(1000)).max(200).optional().describe('Only test commits that changed these paths (files or folders)'), good: z.string().max(200).describe('A revision without the problem'), bad: z.string().max(200).optional().describe('A revision with it; default main'), marks: z.record(z.string().max(100), z.enum(['good', 'bad', 'skip'])).optional().describe('Commits tested so far'), run: z.string().max(4000).optional().describe('A check command, run in each checked-out commit: exit 0 good, 125 skip, other bad') },
|
|
147
260
|
seam_show: { quilt: QUILT, rev: REV, format: z.enum(['unified', 'stat', 'names']).optional(), paths: PATHS },
|
|
148
|
-
seam_diff: { quilt: QUILT, from: REV, to: REV, format: z.enum(['unified', 'stat', 'names']).optional(), paths: PATHS },
|
|
149
|
-
seam_blame: { quilt: QUILT, path: z.string().max(1000), rev: REV },
|
|
150
|
-
seam_cat: { quilt: QUILT, path: z.string().max(1000), rev: REV, format: z.enum(['diff', 'snapshot', 'git']).optional().describe('How a conflicted file is written (default diff)') },
|
|
261
|
+
seam_diff: { quilt: QUILT, from: REV, to: REV, stack: z.boolean().optional().describe('List the stack keys that changed, not the files'), format: z.enum(['unified', 'stat', 'names']).optional(), paths: PATHS, renames: z.enum(['off', 'exact', 'similar']).optional().describe('How renamed files are found: off, exact (same content), similar (at least half the lines, the default)'), ignore_whitespace: z.enum(['none', 'trailing', 'all']).optional().describe('Look through trailing whitespace, or all whitespace, when comparing lines'), context: z.number().int().min(0).max(1000).optional().describe('Lines of context around each change (default 3)'), limit: z.number().int().min(1).max(5000).optional().describe('Files per page (default 500)'), cursor: z.string().max(2000).optional().describe('The cursor from the last answer: its next page') },
|
|
262
|
+
seam_blame: { quilt: QUILT, path: z.string().max(1000), rev: REV, range: z.array(z.number().int().min(1)).length(2).optional(), ignore: z.array(z.string().max(80)).max(100).optional(), moves: z.boolean().optional(), reverse: z.string().max(200).optional() },
|
|
263
|
+
seam_cat: { quilt: QUILT, path: z.string().max(1000), rev: REV, format: z.enum(['diff', 'snapshot', 'git']).optional().describe('How a conflicted file is written (default diff)'), offset: z.number().int().min(0).optional().describe('Read from this byte (a part of a large file)'), length: z.number().int().min(0).optional().describe('Read at most this many bytes (up to 64 MiB)') },
|
|
151
264
|
seam_ls: { quilt: QUILT, rev: REV, paths: PATHS },
|
|
152
265
|
seam_commit: {
|
|
153
266
|
quilt: QUILT, square: SQUARE, message: z.string().max(65536).optional().describe('Why (with amend, a new message; left out, the old one stays)'),
|
|
@@ -155,24 +268,37 @@ export const SEAM_INPUTS = {
|
|
|
155
268
|
files: z.array(z.object({ path: z.string().max(1000), content: z.string(), base64: z.boolean().optional(), mode: z.enum(['file', 'exec']).optional() })).max(2000).optional(),
|
|
156
269
|
delete: z.array(z.string().max(1000)).max(2000).optional(),
|
|
157
270
|
paths: PATHS,
|
|
158
|
-
sign: z.boolean().optional().describe('Sign the new commit with this computer\'s key (seam_key_add)')
|
|
271
|
+
sign: z.boolean().optional().describe('Sign the new commit with this computer\'s key (seam_key_add)'),
|
|
272
|
+
allow_findings: z.array(z.string().max(20)).max(50).optional().describe('Secret findings (sf_…) a person checked and lets through; agents may not')
|
|
159
273
|
},
|
|
160
274
|
seam_square_handoff: { quilt: QUILT, square: SQUARE, to: z.string().max(200).describe('The member to hand it to'), note: z.string().max(2000).optional() },
|
|
161
|
-
seam_abandon: { quilt: QUILT, square: SQUARE },
|
|
275
|
+
seam_abandon: { quilt: QUILT, square: SQUARE, cascade: z.boolean().optional().describe('Also abandon your own squares stacked on it') },
|
|
162
276
|
seam_square_merged: { quilt: QUILT, square: SQUARE, git_sha: z.string().max(64).describe('The git commit (full sha) that holds the square\'s work') },
|
|
163
277
|
seam_reopen: { quilt: QUILT, square: SQUARE },
|
|
278
|
+
seam_archive: { quilt: QUILT, square: SQUARE },
|
|
279
|
+
seam_unarchive: { quilt: QUILT, square: SQUARE },
|
|
280
|
+
seam_hide: { quilt: QUILT, square: SQUARE },
|
|
281
|
+
seam_unhide: { quilt: QUILT, square: SQUARE },
|
|
282
|
+
seam_square_edit: { quilt: QUILT, square: SQUARE, title: z.string().optional(), task_id: z.string().nullable().optional(), line: z.string().optional() },
|
|
283
|
+
seam_share: { quilt: QUILT, square: SQUARE, member: z.string().describe('the member id to add, e.g. person:…') },
|
|
284
|
+
seam_unshare: { quilt: QUILT, square: SQUARE, member: z.string().describe('the member id to take off') },
|
|
285
|
+
seam_watch: { quilt: QUILT, paths: z.array(z.string().max(300)).max(50).optional(), classes: z.array(z.enum(['whitespace', 'comment-only', 'docs', 'test-only', 'dependency', 'ci-config', 'schema', 'logic'])).max(8).optional(), min_lines: z.number().int().min(0).optional(), lines: z.array(z.string().max(110)).max(20).optional(), note: z.string().max(300).optional() },
|
|
286
|
+
seam_unwatch: { quilt: QUILT, id: z.string().max(80) },
|
|
287
|
+
seam_pipeline: { quilt: QUILT, check: z.string().max(200).describe('The check\'s name, e.g. ci'), line: z.string().max(110).optional().describe('Default main'), stop: z.boolean().optional().describe('Stop the open bisect (admin)') },
|
|
164
288
|
seam_check_rerun: { quilt: QUILT, check: z.string().max(200).describe('The check\'s name, e.g. ci'), square: z.string().max(100).optional().describe('A square (sq:…): re-run on its latest commit'), line: z.string().max(110).optional().describe('A line: re-run on its tip (default main when no square)'), commit: z.string().max(80).optional().describe('A commit instead of the head or tip') },
|
|
165
289
|
seam_checks: { quilt: QUILT, state: z.enum(['queued', 'running', 'done', 'cancelled']).optional(), name: z.string().max(100).optional(), tier: z.enum(['pre_merge', 'post_merge']).optional(), line: z.string().max(120).optional(), square: z.string().max(100).optional() },
|
|
166
|
-
seam_events: { quilt: QUILT, after: z.number().int().min(0).optional(), kinds: z.array(z.enum(['patch.merged', 'conflict.added', 'square.handoff', 'check.requested'])).optional() },
|
|
167
|
-
seam_events_webhook: { quilt: QUILT, url: z.string().max(2000), events: z.array(z.enum(['patch.merged', 'conflict.added', 'square.handoff', 'check.requested'])).optional(), secret: z.string().min(16).max(200).optional() },
|
|
290
|
+
seam_events: { quilt: QUILT, after: z.number().int().min(0).optional(), wait: z.number().int().min(0).max(30).optional(), kinds: z.array(z.enum(['patch.merged', 'patch.reverted', 'conflict.added', 'square.handoff', 'square.restacked', 'check.requested', 'check.cancelled', 'check.culprit', 'check.verdict', 'watch.triggered', 'policy.needed', 'review.comment', 'review.decision', 'review.requested', 'review.stale', 'claim.granted', 'claim.refused', 'claim.expired', 'claim.handoff', 'export.state'])).optional() },
|
|
291
|
+
seam_events_webhook: { quilt: QUILT, url: z.string().max(2000), events: z.array(z.enum(['patch.merged', 'patch.reverted', 'conflict.added', 'square.handoff', 'square.restacked', 'check.requested', 'check.cancelled', 'check.culprit', 'check.verdict', 'watch.triggered', 'policy.needed', 'review.comment', 'review.decision', 'review.requested', 'review.stale', 'claim.granted', 'claim.refused', 'claim.expired', 'claim.handoff', 'export.state'])).optional(), secret: z.string().min(16).max(200).optional(), add: z.boolean().optional(), remove: z.boolean().optional() },
|
|
292
|
+
seam_absorb: { quilt: QUILT, square: SQUARE },
|
|
168
293
|
seam_fold: { quilt: QUILT, square: SQUARE, into: z.string().max(100).describe('The square (sq:…) that takes the work'), message: z.string().max(65536).optional() },
|
|
169
|
-
seam_split: { quilt: QUILT, square: SQUARE, groups: z.array(z.array(z.string().max(1000)).min(1).max(2000)).min(1).max(20).describe('Paths (files or directories) per new square'), titles: z.array(z.string().max(200)).max(20).optional() },
|
|
170
|
-
seam_merge: { quilt: QUILT, square: SQUARE, from: z.string().max(200).optional().describe('Merge this square (sq:…) or revision into your square instead of merging your square into a line'), into: z.string().max(100).optional().describe('The line, default main'), message: z.string().max(65536).optional(), dry_run: z.boolean().optional(), on_conflict: z.enum(['rebase', 'leave']).optional() },
|
|
294
|
+
seam_split: { quilt: QUILT, square: SQUARE, by: z.enum(['paths', 'claim']).optional().describe('paths (the default) or claim'), groups: z.array(z.array(z.string().max(1000)).min(1).max(2000)).min(1).max(20).optional().describe('Paths (files or directories) per new square, for by: paths'), titles: z.array(z.string().max(200)).max(20).optional(), stacked: z.boolean().optional().describe('Chain the new squares on this one in order') },
|
|
295
|
+
seam_merge: { quilt: QUILT, square: SQUARE, from: z.string().max(200).optional().describe('Merge this square (sq:…) or revision into your square instead of merging your square into a line'), into: z.string().max(100).optional().describe('The line, default main'), message: z.string().max(65536).optional(), dry_run: z.boolean().optional(), on_conflict: z.enum(['rebase', 'leave']).optional(), commit_live: z.boolean().optional().describe('Default true: your square\'s shared live work (seam_live_set) is committed first and merged with it; false merges the committed head as it stands') },
|
|
171
296
|
seam_rebase: { quilt: QUILT, square: SQUARE, onto: REV },
|
|
297
|
+
seam_reparent: { quilt: QUILT, square: SQUARE, to: z.string().describe('the open square on the same line to stack on, or "line" to take the square off its stack onto its line\'s tip') },
|
|
172
298
|
seam_revert: { quilt: QUILT, rev: z.string().max(200).describe('patch:12, a commit id, …'), line: z.string().max(100).optional() },
|
|
173
299
|
seam_cherry_pick: { quilt: QUILT, square: SQUARE, from: z.string().max(200) },
|
|
174
|
-
seam_resolve: { quilt: QUILT, square: SQUARE, entries: z.array(z.object({ path: z.string().max(1000), choose: z.union([z.enum(['ours', 'theirs', 'base', 'suggestion', 'delete', 'keep_edit', 'keep_delete']), z.number().int().min(0).max(8)]) })).min(1).max(500), remember: z.boolean().optional().describe('Remember the answer for the same conflict next time (default true)') },
|
|
175
|
-
seam_resolutions: { quilt: QUILT, path: z.string().max(1000).optional() },
|
|
300
|
+
seam_resolve: { quilt: QUILT, square: SQUARE, entries: z.array(z.object({ path: z.string().max(1000), choose: z.union([z.enum(['ours', 'theirs', 'base', 'suggestion', 'delete', 'keep_edit', 'keep_delete', 'keep_both']), z.number().int().min(0).max(8)]).optional(), rule: z.enum(['additive', 'whitespace', 'words', 'union']).optional().describe('Instead of choose: settle it with a merge step the line does not apply by itself') })).min(1).max(500), remember: z.boolean().optional().describe('Remember the answer for the same conflict next time (default true)') },
|
|
301
|
+
seam_resolutions: { quilt: QUILT, path: z.string().max(1000).optional(), hunks: z.boolean().optional().describe('Also each clashing region\'s remembered answer') },
|
|
176
302
|
seam_forget_resolution: { quilt: QUILT, key: z.string().min(8).max(64) }
|
|
177
303
|
}
|
|
178
304
|
|
|
@@ -180,7 +306,7 @@ export const SEAM_INPUTS = {
|
|
|
180
306
|
* Runs one tool. `client` is a quilt client (client.js) acting as the agent; `found` is the
|
|
181
307
|
* block folder the local MCP runs in ({ dir, block }) or null. Returns the text to answer.
|
|
182
308
|
*/
|
|
183
|
-
export async function runSeamTool (name, args, { client, found = null }) {
|
|
309
|
+
export async function runSeamTool (name, args, { client, found = null, hosted = false }) {
|
|
184
310
|
const block = found?.block || null
|
|
185
311
|
const quilt = args.quilt || block?.quilt
|
|
186
312
|
const square = args.square || block?.square
|
|
@@ -191,31 +317,66 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
191
317
|
try {
|
|
192
318
|
switch (name) {
|
|
193
319
|
case 'seam_line_require': {
|
|
194
|
-
const extra = Object.fromEntries(['tiers', 'tests', 'ci_config'].filter((k) => args[k] !== undefined).map((k) => [k, args[k]]))
|
|
320
|
+
const extra = Object.fromEntries(['tiers', 'tests', 'ci_config', 'only', 'human_only', 'rules', 'mode', 'pre_merge_checks', 'approval_survives', 'auto_combine', 'batch', 'bisect_width', 'needs_stack', 'monotonic_stack', 'migrations_in_order'].filter((k) => args[k] !== undefined).map((k) => [k, args[k]]))
|
|
195
321
|
if (args.approvals === undefined && args.checks === undefined && args.runners === undefined && !Object.keys(extra).length) return describePolicy(await client.linePolicy(needQuilt(), args.line))
|
|
196
322
|
return describePolicy(await client.setLinePolicy(needQuilt(), args.line, { ...(args.approvals !== undefined ? { approvals: args.approvals } : {}), ...(args.checks ? { checks: args.checks } : {}), ...(args.runners ? { runners: args.runners } : {}), ...extra }))
|
|
197
323
|
}
|
|
324
|
+
case 'seam_interdiff': return describeInterdiff(await client.interdiff(needQuilt(), needSquare(), args.since ? { since: args.since } : {}))
|
|
198
325
|
case 'seam_reviews': return describeReviews(await client.reviews(needQuilt(), needSquare()))
|
|
199
|
-
case 'seam_review': return describeReviewed(await client.review(needQuilt(), needSquare(), { kind: args.kind, body: args.body || '', ...(args.path ? { path: args.path } : {}), ...(args.line ? { line: args.line } : {}) }))
|
|
326
|
+
case 'seam_review': return describeReviewed(await client.review(needQuilt(), needSquare(), { kind: args.kind, body: args.body || '', ...(args.asked ? { asked: args.asked } : {}), ...(args.path ? { path: args.path } : {}), ...(args.line ? { line: args.line } : {}), ...(args.suggestion ? { suggestion: args.suggestion } : {}), ...(args.scope ? { scope: args.scope } : {}) }))
|
|
327
|
+
case 'seam_apply_suggestion': { const r = await client.applySuggestion(needQuilt(), needSquare(), args.review); return `Applied the suggestion as ${r.result.id.slice(0, 12)}: the square's head moved.` }
|
|
200
328
|
case 'seam_claims': return describeClaims(await client.claims(needQuilt(), square))
|
|
201
329
|
case 'seam_claim': {
|
|
202
330
|
const made = []
|
|
203
|
-
for (const p of args.patterns) made.push(await client.claim(needQuilt(), needSquare(), { pattern: p, note: args.note }))
|
|
204
|
-
return made.map((c) => `Claimed ${c.pattern} for ${c.square}: writes to it from other squares on ${c.line} are refused until the square merges or seam_unclaim.`).join('\n')
|
|
331
|
+
for (const p of args.patterns) made.push(await client.claim(needQuilt(), needSquare(), { pattern: p, note: args.note, ...(args.expires_in ? { expires_in: args.expires_in } : {}) }))
|
|
332
|
+
return made.map((c) => `Claimed ${c.pattern} for ${c.square}: writes to it from other squares on ${c.line} are refused until ${c.expires_at ? `${new Date(c.expires_at).toISOString()}, ` : ''}the square merges or seam_unclaim.`).join('\n')
|
|
205
333
|
}
|
|
206
334
|
case 'seam_check': if (args.square) return describeSquareCheck(await client.reportSquareCheck(needQuilt(), args.square, { check: args.check, status: args.status, ...(args.run_key ? { run_key: args.run_key } : {}), ...(args.url ? { url: args.url } : {}) }))
|
|
207
335
|
return describeCheck(await client.reportCheck(needQuilt(), args.line || 'main', { check: args.check, status: args.status, ...(args.patch ? { patch: args.patch } : {}), ...(args.run_key ? { run_key: args.run_key } : {}), ...(args.url ? { url: args.url } : {}) }))
|
|
336
|
+
case 'seam_watch': {
|
|
337
|
+
if (!args.paths || !args.paths.length) return describeWatches(await client.watches(needQuilt()))
|
|
338
|
+
const w = await client.watch(needQuilt(), { paths: args.paths, ...(args.classes ? { classes: args.classes } : {}), ...(args.min_lines != null ? { min_lines: args.min_lines } : {}), ...(args.lines ? { lines: args.lines } : {}), ...(args.note ? { note: args.note } : {}) })
|
|
339
|
+
return `Watching ${w.paths.join(', ')}: each matching patch sends you watch.triggered. ${w.id}`
|
|
340
|
+
}
|
|
341
|
+
case 'seam_unwatch': { const r = await client.unwatch(needQuilt(), args.id); return `Stopped watching (${r.removed}).` }
|
|
342
|
+
case 'seam_pipeline': {
|
|
343
|
+
if (args.stop) { const r = await client.stopPipeline(needQuilt(), args.line || 'main', args.check); return r.stopped ? `Stopped the bisect of ${args.check} (patches ${r.bisect.range[0] + 1} to ${r.bisect.range[1]}): its waiting runs are cancelled and nothing is reverted.` : `No bisect of ${args.check} is open.` }
|
|
344
|
+
return describePipeline(await client.pipeline(needQuilt(), args.line || 'main', args.check))
|
|
345
|
+
}
|
|
208
346
|
case 'seam_check_rerun': return describeRerun(await client.rerunCheck(needQuilt(), { check: args.check, ...(args.square ? { square: args.square } : { line: args.line || 'main' }), ...(args.commit ? { commit: args.commit } : {}) }))
|
|
209
347
|
case 'seam_checks': return describeCheckRuns(await client.checkRuns(needQuilt(), { state: args.state, name: args.name, tier: args.tier, line: args.line, square: args.square }))
|
|
348
|
+
case 'seam_claim_request': { const r = await client.requestClaim(needQuilt(), { claim: args.claim, square: needSquare(), title: args.title, ...(args.description ? { description: args.description } : {}) }); return `Waiting for ${args.claim} as request ${r.id}: the holder hands it over when done.` }
|
|
349
|
+
case 'seam_claim_withdraw': { await client.withdrawRequest(needQuilt(), args.request); return `Withdrew request ${args.request}.` }
|
|
350
|
+
case 'seam_claim_handoff': return describeHandoff(await client.handoffClaim(needQuilt(), needSquare(), { claim: args.claim, ...(args.to ? { to: args.to } : {}), ...(args.note ? { note: args.note } : {}) }))
|
|
351
|
+
case 'seam_claim_finish': return describeFinished(await client.finishClaims(needQuilt(), needSquare(), args.note ? { note: args.note } : {}))
|
|
210
352
|
case 'seam_unclaim': {
|
|
211
353
|
const out = []
|
|
212
354
|
for (const p of args.patterns) out.push(await client.unclaim(needQuilt(), needSquare(), p))
|
|
213
355
|
return out.map((r) => `Let go of ${r.released.pattern}.`).join('\n')
|
|
214
356
|
}
|
|
215
357
|
case 'seam_live': return describeLive(await client.live(needQuilt(), needSquare()))
|
|
358
|
+
case 'seam_daemon': {
|
|
359
|
+
if (!inFolder) throw new Error('seam_daemon watches a block folder: run seam mcp in a folder made by seam checkout.')
|
|
360
|
+
const action = args.action || 'status'
|
|
361
|
+
if (action === 'start') {
|
|
362
|
+
const running = await askDaemon(found.dir)
|
|
363
|
+
if (running) return `A daemon already watches ${found.dir} (${running.state}).`
|
|
364
|
+
const d = await startDaemon({ client, dir: found.dir, codec: block._codec || null })
|
|
365
|
+
return `Watching ${found.dir}: each change is shared as live work on ${block.square} while this MCP server runs (control API on 127.0.0.1:${d.port}).`
|
|
366
|
+
}
|
|
367
|
+
const r = await askDaemon(found.dir, action, action === 'status' ? 'GET' : 'POST')
|
|
368
|
+
if (!r) return `No daemon is running for ${found.dir}: start one with seam_daemon action start, or seam daemon in a terminal.`
|
|
369
|
+
if (action === 'stop') return 'The daemon is stopping.'
|
|
370
|
+
return `Watching ${r.dir} (${r.state}): sent ${r.sent} time(s)${r.pending.length ? `, waiting: ${r.pending.join(', ')}` : ''}${r.last_error ? `. ${r.last_error}` : ''}.`
|
|
371
|
+
}
|
|
372
|
+
case 'seam_live_join': return describeLiveJoin(await client.liveJoin(needQuilt(), needSquare(), args.member))
|
|
216
373
|
case 'seam_live_set': {
|
|
217
374
|
const given = [...(args.files || []).map((f) => ({ path: f.path, op: 'put', content: f.base64 ? f.content : Buffer.from(f.content, 'utf8').toString('base64'), mode: f.mode || 'file' })), ...(args.delete || []).map((p) => ({ path: p, op: 'delete' }))]
|
|
218
|
-
|
|
375
|
+
if (args.paths?.length) {
|
|
376
|
+
if (!inFolder) throw new Error('seam_live_set paths shares files of a block folder: run seam mcp in a folder made by seam checkout.')
|
|
377
|
+
return describeLiveSet(await client.patchLive(needQuilt(), needSquare(), livePatch(found.dir, block, args.paths.map((p) => path.resolve(found.dir, p)))))
|
|
378
|
+
}
|
|
379
|
+
const changes = given.length || !inFolder ? given : localChanges(found.dir, block).map((c) => c.status === 'deleted' ? { path: c.path, op: 'delete' } : { path: c.path, op: 'put', content: bytesOf(c).toString('base64'), mode: c.mode })
|
|
219
380
|
return describeLiveSet(await client.setLive(needQuilt(), needSquare(), { changes }))
|
|
220
381
|
}
|
|
221
382
|
case 'seam_restore':
|
|
@@ -228,37 +389,53 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
228
389
|
return describeClean(clean(found.dir, block, { paths: files, dryRun: !!args.dry_run }), 'Call seam_clean again without dry_run to remove them.')
|
|
229
390
|
}
|
|
230
391
|
case 'seam_quilts': {
|
|
231
|
-
if (args.new) { const q = await client.createQuilt({ slug: args.new }); return `Made quilt ${q.slug} (${q.id}).` }
|
|
392
|
+
if (args.new) { const q = await client.createQuilt({ slug: args.new, ...(args.org ? { org: args.org } : {}) }); return `Made quilt ${q.slug} (${q.id})${args.org ? ` for ${args.org}` : ''}.` }
|
|
232
393
|
const list = await client.quilts()
|
|
233
394
|
return list.length ? list.map((q) => `${q.slug} (${q.id}), ${q.level}`).join('\n') : 'No quilts yet.'
|
|
234
395
|
}
|
|
235
396
|
case 'seam_open_square': {
|
|
236
|
-
const sq = await client.openSquare(needQuilt(), { title: args.title, line: args.line, task_id: args.task_id, ...(args.on ? { on: args.on } : {}) })
|
|
397
|
+
const sq = await client.openSquare(needQuilt(), { title: args.title, line: args.line, task_id: args.task_id, ...(args.on ? { on: args.on } : {}), ...(args.another ? { another: true } : {}), ...(args.stack ? { stack: args.stack } : {}) })
|
|
237
398
|
return `Opened ${describeSquare(sq)}.\nCommit in it with seam_commit (square: "${sq.short}") and merge it with seam_merge.`
|
|
238
399
|
}
|
|
239
400
|
case 'seam_squares': {
|
|
240
|
-
const list = await client.squares(needQuilt(), { state: args.all ? undefined : 'open', mine: !!args.mine })
|
|
401
|
+
const list = await client.squares(needQuilt(), { state: args.all ? undefined : 'open', mine: !!args.mine, hidden: !!args.hidden, archived: !!args.archived })
|
|
241
402
|
return list.length ? list.map(describeSquare).join('\n') : 'No open squares.'
|
|
242
403
|
}
|
|
243
404
|
case 'seam_square_status': {
|
|
244
405
|
if (inFolder) return describeStatus(await blockStatus(client, found.dir, block))
|
|
245
406
|
return describeSquareStatus(await client.square(needQuilt(), needSquare()))
|
|
246
407
|
}
|
|
247
|
-
case 'seam_log': return describeLog(await client.log(needQuilt(), {
|
|
408
|
+
case 'seam_log': return describeLog(await client.log(needQuilt(), {
|
|
409
|
+
rev: args.rev || (block?.square ? block.square_short : 'main'), paths: args.paths, limit: args.limit, follow: args.follow || undefined, search: args.search, show_signature: args.show_signature || undefined,
|
|
410
|
+
pickaxe: args.match, pickaxe_regex: args.match ? 1 : undefined, pickaxe_mode: args.match ? 'match' : undefined,
|
|
411
|
+
message: args.message, message_regex: args.message && args.message_regex ? 1 : undefined, message_ignore_case: args.message && args.ignore_case ? 1 : undefined,
|
|
412
|
+
author: args.author, on_behalf_of: args.on_behalf_of, task: args.task, since: args.since, until: args.until,
|
|
413
|
+
group_by: args.group_by, graph: args.graph ? 1 : undefined, include_hidden: args.include_hidden ? 1 : undefined, cursor: args.cursor
|
|
414
|
+
}), { oneline: !args.show_signature, graph: !!args.graph })
|
|
248
415
|
case 'seam_describe': return (await client.describe(needQuilt(), args.rev || (block?.square ? block.square_short : 'main'))).describe
|
|
249
416
|
case 'seam_merge_base': return describeMergeBase(await client.mergeBase(needQuilt(), args.a, args.b))
|
|
250
|
-
case '
|
|
417
|
+
case 'seam_infra_paths': { const r = await client.setInfraPaths(needQuilt(), args.paths); return r.infra_paths ? `${r.quilt} derives infra from ${r.infra_paths.join(', ')}.` : `${r.quilt} derives no infra.` }
|
|
418
|
+
case 'seam_public_reads': return describePublicReads(await client.publicReads(needQuilt()))
|
|
419
|
+
case 'seam_owner_idle_hours': { const r = await client.setOwnerIdleHours(needQuilt(), args.hours); return `${r.quilt}'s owner claims let go after ${r.owner_idle_hours ?? 2} quiet hours.` }
|
|
420
|
+
case 'seam_migrations_dir': { const r = await client.setMigrationsDir(needQuilt(), args.dir); return r.migrations_dir === null ? `${r.quilt} derives db_migration from supabase/migrations.` : r.migrations_dir ? `${r.quilt} derives db_migration from ${r.migrations_dir}.` : `${r.quilt} derives no db_migration.` }
|
|
421
|
+
case 'seam_stack': return describeStack(await client.stack(needQuilt(), args.rev || 'main'))
|
|
422
|
+
case 'seam_ancestor': return describeAncestor(await client.ancestor(needQuilt(), args.a, args.b), args.a, args.b)
|
|
423
|
+
case 'seam_contains': return describeContains(await client.contains(needQuilt(), args.rev))
|
|
424
|
+
case 'seam_search': if (args.query) return describeSearch(args.all || (args.quilts && args.quilts.length) ? await client.search({ q: args.query, rev: args.rev, quilts: args.quilts, limit: args.limit }) : await client.searchCode(needQuilt(), { q: args.query, rev: args.rev, limit: args.limit }))
|
|
425
|
+
if (!args.pattern) throw new Error('seam_search needs a query (or a pattern)')
|
|
426
|
+
return describeGrep(await client.search({ pattern: args.pattern, quilts: args.quilts, rev: args.rev, paths: args.paths, ignore_case: args.ignore_case || undefined, fixed: args.fixed || undefined, limit: args.limit }), { filesOnly: !!args.files_only })
|
|
251
427
|
case 'seam_grep': return describeGrep(await client.grep(needQuilt(), { pattern: args.pattern, rev: args.rev || (block?.square ? block.square_short : 'main'), paths: args.paths, ignore_case: args.ignore_case || undefined, fixed: args.fixed || undefined, limit: args.limit }), { filesOnly: !!args.files_only })
|
|
252
428
|
case 'seam_bisect': {
|
|
253
429
|
const o = { bad: args.bad || 'main', good: args.good, marks: args.marks || {} }
|
|
254
|
-
if (!args.run) return describeBisect(await client.bisect(needQuilt(), o))
|
|
430
|
+
if (!args.run) return describeBisect(await client.bisect(needQuilt(), { ...o, paths: args.paths }))
|
|
255
431
|
const { bisectRun } = await import('./bisect-run.js')
|
|
256
|
-
return describeBisect(await bisectRun(client, { quilt: needQuilt(), ...o, run: args.run }))
|
|
432
|
+
return describeBisect(await bisectRun(client, { quilt: needQuilt(), ...o, only: args.paths, run: args.run }))
|
|
257
433
|
}
|
|
258
434
|
case 'seam_show': return describeShow(await client.show(needQuilt(), args.rev || 'main', { format: args.format || 'unified', paths: args.paths }), { format: args.format || 'unified' })
|
|
259
|
-
case 'seam_diff': return
|
|
260
|
-
|
|
261
|
-
case '
|
|
435
|
+
case 'seam_diff': if (args.stack) return describeStackDiff(await client.stackDiff(needQuilt(), args.from || 'main', args.to || 'main'))
|
|
436
|
+
return describeDiff(await client.diff(needQuilt(), { from: args.from, to: args.to, paths: args.paths, format: args.format || 'unified', renames: args.renames || 'similar', ignore_whitespace: args.ignore_whitespace, context: args.context, limit: args.limit || 500, cursor: args.cursor }), { format: args.format || 'unified' })
|
|
437
|
+
case 'seam_blame': return describeBlame(await client.blame(needQuilt(), args.rev || 'main', args.path, { range: args.range, ignore: args.ignore, moves: args.moves, reverse: args.reverse }))
|
|
438
|
+
case 'seam_cat': { const r = await client.cat(needQuilt(), args.rev || 'main', args.path, { format: args.format || conflictStyle(), ...(args.offset != null ? { offset: args.offset } : {}), ...(args.length != null ? { length: args.length } : {}) }); return (await catBytes(client, needQuilt(), r, r.sealed ? await codecFor(client, needQuilt()) : null)).toString('utf8') }
|
|
262
439
|
case 'seam_ls': { const r = await client.tree(needQuilt(), args.rev || 'main', args.paths); return r.entries.map((e) => `${e.mode}\t${e.path}`).join('\n') || 'No files.' }
|
|
263
440
|
case 'seam_commit': {
|
|
264
441
|
if (!args.amend && !args.message) return 'Say why: message.'
|
|
@@ -269,37 +446,54 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
269
446
|
return `${text}\nSigned with ${s.signature.key}.`
|
|
270
447
|
}
|
|
271
448
|
if (args.amend && inFolder && !args.files && !args.delete) {
|
|
272
|
-
return signed(await amendFolder(client, found.dir, block, { message: args.message, paths: (args.paths || []).map((p) => `${found.dir}/${p}`) }), 'Amended the last commit as')
|
|
449
|
+
return signed(await amendFolder(client, found.dir, block, { message: args.message, paths: (args.paths || []).map((p) => `${found.dir}/${p}`), allow: args.allow_findings || [] }), 'Amended the last commit as')
|
|
273
450
|
}
|
|
274
451
|
if (inFolder && !args.files && !args.delete) {
|
|
275
|
-
const r = await commitFolder(client, found.dir, block, { message: args.message, paths: (args.paths || []).map((p) => `${found.dir}/${p}`) })
|
|
276
|
-
return r.nothing ? 'Nothing to commit: no file in this folder changed since the last commit.' : signed(r, 'Committed')
|
|
452
|
+
const r = await commitFolder(client, found.dir, block, { message: args.message, paths: (args.paths || []).map((p) => `${found.dir}/${p}`), allow: args.allow_findings || [] })
|
|
453
|
+
return r.nothing ? 'Nothing to commit: no file in this folder changed since the last commit.' : describeOpened(r) + await signed(r, 'Committed')
|
|
277
454
|
}
|
|
278
455
|
const changes = [
|
|
279
456
|
...(args.files || []).map((f) => ({ path: f.path, op: 'put', content: f.base64 ? f.content : Buffer.from(f.content, 'utf8').toString('base64'), ...(f.mode ? { mode: f.mode } : {}) })),
|
|
280
457
|
...(args.delete || []).map((p) => ({ path: p, op: 'delete' }))
|
|
281
458
|
]
|
|
282
459
|
const sq = await client.square(needQuilt(), needSquare())
|
|
283
|
-
|
|
460
|
+
const allow = args.allow_findings?.length ? { allow_findings: args.allow_findings } : {}
|
|
461
|
+
if (args.amend) return signed(await client.amend(quilt, sq.id, { expect_head: sq.head, message: args.message, changes, ...allow }), 'Amended the last commit as')
|
|
284
462
|
if (!changes.length) return 'Nothing to commit: give files (and delete), or run in a block folder.'
|
|
285
|
-
return signed(await client.commit(quilt, sq.id, { expect_head: sq.head, message: args.message, changes }), 'Committed')
|
|
463
|
+
return signed(await client.commit(quilt, sq.id, { expect_head: sq.head, message: args.message, changes, ...allow }), 'Committed')
|
|
286
464
|
}
|
|
287
465
|
case 'seam_square_handoff': { const sq = await client.handoff(needQuilt(), needSquare(), { to: args.to, note: args.note }); return `Handed ${sq.short} to ${sq.holder}.` }
|
|
288
466
|
case 'seam_square_merged': { const sq = await client.squareMerged(needQuilt(), needSquare(), { git_sha: args.git_sha }); return `${sq.short} is merged by hand: its work is patch ${sq.merged.patch_no} on ${sq.merged.line} (git ${sq.merged.git_sha.slice(0, 12)}).` }
|
|
289
|
-
case 'seam_abandon': { const sq = await client.abandon(needQuilt(), needSquare(), inFolder ? { expect_head: block.head } : {}); return `Abandoned ${sq.short}; seam_reopen brings it back.` }
|
|
467
|
+
case 'seam_abandon': { const sq = await client.abandon(needQuilt(), needSquare(), { ...(inFolder ? { expect_head: block.head } : {}), ...(args.cascade ? { children: 'cascade' } : {}) }); return `Abandoned ${sq.short}${sq.cascaded ? ` and ${sq.cascaded.length} square(s) of yours stacked on it` : ''}; seam_reopen brings it back.` }
|
|
468
|
+
case 'seam_archive': { const sq = await client.archive(needQuilt(), needSquare()); return `Archived ${sq.short}: its claims are released; seam_squares archived: true lists it, and a commit or seam_unarchive wakes it.` }
|
|
469
|
+
case 'seam_unarchive': { const sq = await client.unarchive(needQuilt(), needSquare()); return `${sq.short} is awake again; its claims were not restored.` }
|
|
470
|
+
case 'seam_hide': { const sq = await client.hide(needQuilt(), needSquare()); return `Hid ${sq.short}; it stays open, and seam_squares hidden: true lists it.` }
|
|
471
|
+
case 'seam_unhide': { const sq = await client.unhide(needQuilt(), needSquare()); return `${sq.short} is listed again.` }
|
|
472
|
+
case 'seam_square_edit': {
|
|
473
|
+
const body = Object.fromEntries(['title', 'task_id', 'line'].filter((k) => args[k] !== undefined).map((k) => [k, args[k]]))
|
|
474
|
+
return `Edited ${describeSquare(await client.editSquare(needQuilt(), needSquare(), body))}.`
|
|
475
|
+
}
|
|
476
|
+
case 'seam_share':
|
|
477
|
+
case 'seam_unshare': { const sq = await client[name === 'seam_share' ? 'share' : 'unshare'](needQuilt(), needSquare(), { member: args.member }); return `${sq.short}'s writers: ${(sq.writers || []).join(', ') || 'only its holder'}.` }
|
|
290
478
|
case 'seam_reopen': { const sq = await client.reopen(needQuilt(), needSquare()); return `Reopened ${describeSquare(sq)}.` }
|
|
291
|
-
case 'seam_events': return describeEvents(await client.events(needQuilt(), { after: args.after, kinds: args.kinds }))
|
|
479
|
+
case 'seam_events': return describeEvents(await client.events(needQuilt(), { after: args.after, kinds: args.kinds, ...(args.wait ? { wait: args.wait } : {}) }))
|
|
292
480
|
case 'seam_events_webhook': {
|
|
293
|
-
if (args.url === 'off') { await client.subscribe(needQuilt(), { url: null }); return '
|
|
294
|
-
const r = await client.subscribe(needQuilt(), { url: args.url,
|
|
295
|
-
|
|
481
|
+
if (args.url === 'off') { await client.subscribe(needQuilt(), { url: null }); return 'Webhooks removed.' }
|
|
482
|
+
if (args.remove) { const r = await client.subscribe(needQuilt(), { url: args.url, remove: true }); return `Webhook to ${r.url} removed.${r.hooks.length ? ` Still posting to ${r.hooks.map((h) => h.url).join(', ')}.` : ''}` }
|
|
483
|
+
const r = await client.subscribe(needQuilt(), { url: args.url, events: args.events, secret: args.secret, ...(args.add ? { add: true } : {}) })
|
|
484
|
+
return `Events (${r.events.join(', ')}) now POST to ${r.url}${r.hooks?.length > 1 ? ` (and ${r.hooks.length - 1} more webhook(s) of yours)` : ''}.${r.secret ? ` Signing secret (shown once): ${r.secret}` : ''}`
|
|
485
|
+
}
|
|
486
|
+
case 'seam_absorb': {
|
|
487
|
+
const r = await client.absorb(needQuilt(), needSquare(), inFolder ? { expect_head: block.head } : {})
|
|
488
|
+
const moved = r.amended.filter((a) => a.paths.length).map((a) => `${a.paths.join(', ')} into ${a.commit_before.slice(0, 12)}`)
|
|
489
|
+
return `${moved.length ? `Absorbed ${moved.join('; ')} (${r.amended.length} commit(s) rewritten).` : 'Nothing to absorb.'}${r.left_live ? ` Still live: ${r.left_paths.join(', ')}.` : ''}` + (r.amended.length ? await sync(r.square.head, r.square) : '')
|
|
296
490
|
}
|
|
297
491
|
case 'seam_fold': {
|
|
298
492
|
const r = await client.fold(needQuilt(), needSquare(), { into: args.into, message: args.message, ...(inFolder ? { expect_head: block.head } : {}) })
|
|
299
493
|
return describeWrite(r, `Folded ${r.folded.short} into ${r.square.short}:`)
|
|
300
494
|
}
|
|
301
495
|
case 'seam_split': {
|
|
302
|
-
const r = await client.split(needQuilt(), needSquare(), { by: 'paths', groups: args.groups, titles: args.titles, ...(inFolder ? { expect_head: block.head } : {}) })
|
|
496
|
+
const r = await client.split(needQuilt(), needSquare(), { by: args.by || 'paths', groups: args.groups, titles: args.titles, ...(args.stacked ? { stacked: true } : {}), ...(inFolder ? { expect_head: block.head } : {}) })
|
|
303
497
|
return [`Kept in ${r.square.short}: ${r.kept.join(', ') || '(nothing)'}`, ...r.squares.map((q) => `New ${describeSquare(q)}: ${q.paths.join(', ')}`)].join('\n') + await sync(r.square.head, r.square)
|
|
304
498
|
}
|
|
305
499
|
case 'seam_line_list': {
|
|
@@ -321,17 +515,35 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
321
515
|
return g.installations.map((i) => `${i.login} (installation ${i.id}): ${i.repos.map((r) => r.full_name).join(', ') || 'no repositories'}`).join('\n')
|
|
322
516
|
}
|
|
323
517
|
case 'seam_github_import': { const r = await client.importGithub({ installation_id: args.installation, repo: args.repo, slug: args.into, follow: args.follow !== false }); return `Importing ${args.repo} into ${r.id ? (args.into || args.repo.split('/')[1]) : '?'} (import ${r.id})${r.follow ? ', following it' : ''}.` }
|
|
324
|
-
case 'seam_import_remote': { const r = await client.startImport({ url: args.url, slug: args.into, follow: !!args.follow, interval: args.interval, credential: args.credential }); return `Importing ${args.url} into ${args.into} (import ${r.id})${r.follow ? `, following every ${r.interval_s} s; poke secret ${r.poke_secret} (shown once)` : ''}.` }
|
|
325
|
-
case 'seam_export_remote': { const r = await client.setExport(needQuilt(), { target: args.target, lines: args.lines, releases: args.releases, credential: args.credential, scratch: args.scratch, enabled: args.enabled !== false }); return r.enabled ? `Exporting ${r.lines.join(', ')} to ${r.target} after each merge.` : `The export to ${r.target} is off.` }
|
|
518
|
+
case 'seam_import_remote': { const r = await client.startImport({ url: args.url, slug: args.into, follow: !!args.follow, interval: args.interval, credential: args.credential, authors: args.authors, ask_authors: !!args.ask_authors }); return `Importing ${args.url} into ${args.into} (import ${r.id})${r.ask_authors ? ', waiting for its authors first (seam_import_status, then seam_import_authors)' : ''}${r.follow ? `, following every ${r.interval_s} s; poke secret ${r.poke_secret} (shown once)` : ''}.` }
|
|
519
|
+
case 'seam_export_remote': { const r = await client.setExport(needQuilt(), { target: args.target, lines: args.lines, releases: args.releases, credential: args.credential, scratch: args.scratch, enabled: args.enabled !== false, lfs: args.lfs }); return r.enabled ? `Exporting ${r.lines.join(', ')} to ${r.target} after each merge.` : `The export to ${r.target} is off.` }
|
|
520
|
+
case 'seam_export_archive': {
|
|
521
|
+
if (hosted) return `This server cannot write files to your computer. Fetch the archive over HTTP with your token: POST /v1/quilts/${encodeURIComponent(needQuilt())}/exit/archive`
|
|
522
|
+
const r = await client.exitArchive(needQuilt())
|
|
523
|
+
const file = path.resolve(found?.dir || process.cwd(), args.out || r.name)
|
|
524
|
+
const { writeFileSync } = await import('node:fs')
|
|
525
|
+
writeFileSync(file, r.bytes)
|
|
526
|
+
return `Wrote the whole quilt to ${file}: ${r.bytes.length} bytes.`
|
|
527
|
+
}
|
|
528
|
+
case 'seam_export_tarball': {
|
|
529
|
+
// The hosted door has no computer of the caller's to write to: it names the route instead.
|
|
530
|
+
if (hosted) return `This server cannot write files to your computer. Fetch the tarball over HTTP with your token: GET /v1/quilts/${encodeURIComponent(needQuilt())}/tarball/${encodeURIComponent(args.rev || 'main')}${args.paths?.length ? `?paths=${args.paths.map(encodeURIComponent).join(',')}` : ''}`
|
|
531
|
+
const r = await client.tarball(needQuilt(), args.rev || 'main', { paths: args.paths })
|
|
532
|
+
const file = path.resolve(found?.dir || process.cwd(), args.out || r.name)
|
|
533
|
+
const { writeFileSync } = await import('node:fs')
|
|
534
|
+
writeFileSync(file, r.bytes)
|
|
535
|
+
return `Wrote ${args.rev || 'main'} (${String(r.commit || 'empty').slice(0, 12)}) to ${file}: ${r.bytes.length} bytes.`
|
|
536
|
+
}
|
|
326
537
|
case 'seam_export_status': { const r = await client.exportStatus(needQuilt()); return JSON.stringify({ target: r.target, lines: r.lines, releases: r.releases, enabled: r.enabled, status: r.status }, null, 2) }
|
|
327
538
|
case 'seam_import_cutover': { const r = await client.cutover(args.id, { credential: args.credential }); return `Cut over: import ${r.import.id} is done; main exports to ${r.export.target} after each merge.` }
|
|
328
539
|
case 'seam_import_status': return describeImportRecord(await client.importStatus(args.id))
|
|
540
|
+
case 'seam_import_authors': { const r = await client.answerImportAuthors(args.id, { authors: args.authors }); return `Import ${r.id} has its authors and runs now.` }
|
|
329
541
|
case 'seam_import_poke': { const r = await client.pokeImport(args.id); return `Poked: import ${r.id} fetches the remote now.` }
|
|
330
542
|
case 'seam_import_git': {
|
|
331
543
|
const { importGit } = await import('./import-git.js')
|
|
332
544
|
const { default: nodePath } = await import('node:path')
|
|
333
545
|
const repo = nodePath.resolve(found?.dir || process.cwd(), args.path)
|
|
334
|
-
const r = await importGit(client, { repo, slug: args.into || nodePath.basename(repo).toLowerCase().replace(/[^a-z0-9-]/g, '-'), validate: !!args.validate })
|
|
546
|
+
const r = await importGit(client, { repo, slug: args.into || nodePath.basename(repo).toLowerCase().replace(/[^a-z0-9-]/g, '-'), validate: !!args.validate, authors: args.authors })
|
|
335
547
|
return describeImport(r)
|
|
336
548
|
}
|
|
337
549
|
case 'seam_sign': {
|
|
@@ -348,8 +560,80 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
348
560
|
const r = await client.addKey({ public_key: pub, name: args.name || '' })
|
|
349
561
|
return `${made ? `Made a signing key at ${signingKeyFile()} and added` : 'Added'} ${r.key}. Sign with seam_commit { sign: true } or seam_sign.`
|
|
350
562
|
}
|
|
563
|
+
case 'seam_blocks': return JSON.stringify(await client.blocks(needQuilt(), { square: args.square, mine: args.mine ? 1 : undefined }), null, 2)
|
|
564
|
+
case 'seam_block_open': return JSON.stringify(await client.openBlock(needQuilt(), { square: args.square, paths: args.paths, mode: args.mode, machine: args.machine, at: args.at, read_only: args.read_only, exclude: args.exclude, max_fetch_bytes: args.max_fetch_bytes }), null, 2)
|
|
565
|
+
case 'seam_block_touch': return JSON.stringify(await client.touchBlock(needQuilt(), args.id, { add: args.add, remove: args.remove }), null, 2)
|
|
566
|
+
case 'seam_clone': {
|
|
567
|
+
if (hosted) throw new Error('seam_clone writes a folder on this computer: run seam mcp locally.')
|
|
568
|
+
const line = args.line || 'main'
|
|
569
|
+
const dir = path.resolve(found?.dir ? path.dirname(found.dir) : process.cwd(), args.dir || String(args.quilt).split('/').pop())
|
|
570
|
+
const r = await checkout(client, { quilt: args.quilt, rev: line, dir, api: client.api, codec: await codecFor(client, args.quilt) })
|
|
571
|
+
return `Cloned ${args.quilt} (${line}, ${r.files} file(s)) into ${dir}. The first commit there opens a square; seam_merge lands it, seam_pull brings others' patches in.`
|
|
572
|
+
}
|
|
573
|
+
case 'seam_member_add': {
|
|
574
|
+
if (!args.email === !args.member_id) throw new Error('seam_member_add takes email or member_id')
|
|
575
|
+
const r = await client.addMember(needQuilt(), { ...(args.email ? { email: args.email } : { member_id: args.member_id }), ...(args.level === 'remove' ? {} : { level: args.level || 'write' }) })
|
|
576
|
+
return r.invited ? `Invited ${r.email} (${r.level}): ${r.emailed ? 'Seam emailed them a link' : `send them ${r.sign_up}`}. They are in the quilt from their first sign-in.` : `${r.member_id}: ${r.level || 'removed'}.`
|
|
577
|
+
}
|
|
578
|
+
case 'seam_pull': {
|
|
579
|
+
if (!found) throw new Error('seam_pull works on a block folder: run seam mcp in a folder made by seam clone or seam checkout.')
|
|
580
|
+
const r = await pull(client, found.dir, block)
|
|
581
|
+
const kept = r.kept?.length ? `\nKept your own version of ${r.kept.join(', ')} (changed on both sides): compare with seam_diff.` : ''
|
|
582
|
+
if (r.noop) return block.square ? `Already up to date: ${block.square_short || block.square} sits on ${block.line}'s tip.` : `Already up to date with ${block.line}.`
|
|
583
|
+
if (r.left) return `${r.left.square} was ${r.left.state}; the folder is now on ${r.line} (${r.at.slice(0, 12)}, ${r.written} file(s) updated).` + kept
|
|
584
|
+
if (r.rebased) return describeWrite(r.result, 'Rebased') + `\n${r.written} file(s) updated in the folder.` + kept
|
|
585
|
+
return `Pulled ${r.line} (now ${r.at.slice(0, 12)}): ${r.written} file(s) updated.` + kept
|
|
586
|
+
}
|
|
587
|
+
case 'seam_block_rebuild': {
|
|
588
|
+
if (!found) throw new Error('seam_block_rebuild works on a block folder: run seam mcp in a folder made by seam checkout.')
|
|
589
|
+
return describeRebuild(await rebuildBlock(client, found.dir, block))
|
|
590
|
+
}
|
|
591
|
+
case 'seam_block_close': { const r = await client.closeBlock(needQuilt(), args.id); return `Closed block ${r.id}: the square and any files on disk are untouched.` }
|
|
351
592
|
case 'seam_token_list': return describeTokens(await client.tokens(needQuilt()))
|
|
352
593
|
case 'seam_token_new': return describeNewToken(await client.createToken(needQuilt(), { name: args.name, level: args.level || 'read' }))
|
|
594
|
+
case 'seam_protections': return args.path != null ? describeAccess(await client.access(needQuilt(), args.path)) : describeProtections(await client.protections(needQuilt()))
|
|
595
|
+
case 'seam_protections_set': return describeProtections(await client.setProtections(needQuilt(), args.rows))
|
|
596
|
+
case 'seam_verify': return describeVerify(await verifyQuilt(client, needQuilt(), { after: args.after || 0 }))
|
|
597
|
+
case 'seam_redact': {
|
|
598
|
+
const blob = args.blob || (args.path ? (await client.cat(needQuilt(), args.rev || 'main', args.path)).blob : null)
|
|
599
|
+
if (!blob) throw new Error('Name the blob, or a path (and rev) that has content.')
|
|
600
|
+
return describeRedacted(await client.redact(needQuilt(), { blob, reason: args.reason }))
|
|
601
|
+
}
|
|
602
|
+
case 'seam_seal': {
|
|
603
|
+
if ((args.action || 'status') === 'status') return describeSeal(await client.seal(needQuilt()))
|
|
604
|
+
if (!args.member) return 'Name the member to grant the key to.'
|
|
605
|
+
return describeSeal(await grantSealed(client, needQuilt(), args.member))
|
|
606
|
+
}
|
|
607
|
+
case 'seam_secret_scanning': {
|
|
608
|
+
if (!args.mode) return `Secret scanning on ${needQuilt()}: ${(await client.quilt(needQuilt())).secret_scanning}.`
|
|
609
|
+
const r = await client.setSecretScanning(needQuilt(), args.mode)
|
|
610
|
+
return `Secret scanning on ${r.quilt}: ${r.secret_scanning}.`
|
|
611
|
+
}
|
|
612
|
+
case 'seam_sealed': {
|
|
613
|
+
const r = await client.setSealed(needQuilt(), args.sealed)
|
|
614
|
+
if (!r.sealed) return `${r.quilt} is not sealed: the server reads its files.`
|
|
615
|
+
await startSealedIfNew(client, r.quilt_id)
|
|
616
|
+
return `${r.quilt} is sealed. Its key is on this computer only: give it to other members with seam_seal (grant).`
|
|
617
|
+
}
|
|
618
|
+
case 'seam_public': { const r = await client.setPublic(needQuilt(), args.public); return r.public ? `${r.quilt} is public: anyone can read it, by its id ${r.quilt_id}.${r.page ? ` Its page: ${r.page}` : ''}` : `${r.quilt} is private again.` }
|
|
619
|
+
case 'seam_policy': return describeSquarePolicy(await client.squarePolicy(needQuilt(), needSquare(), args.line ? { line: args.line } : {}))
|
|
620
|
+
case 'seam_line_policy_versions': {
|
|
621
|
+
const act = args.action || 'list'
|
|
622
|
+
if (act !== 'list' && !args.version) return 'Name the version.'
|
|
623
|
+
const r = act === 'list' ? await client.linePolicyVersions(needQuilt(), args.line) : act === 'confirm' ? await confirmWithLocalKey(client, needQuilt(), args.line, args.version) : await client.decideLinePolicy(needQuilt(), args.line, args.version, act)
|
|
624
|
+
return describePolicyVersions(r)
|
|
625
|
+
}
|
|
626
|
+
case 'seam_orgs': return args.org ? describeOrg(await client.org(args.org)) : describeOrgs(await client.orgs())
|
|
627
|
+
case 'seam_org_log': return describeOrgLog(await client.orgLog(args.org, { limit: args.limit, before: args.before }))
|
|
628
|
+
case 'seam_org_team': return describeOrgTeams(await client.setOrgTeam(args.org, args.name, args.members))
|
|
629
|
+
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
|
+
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 } : {}) }))
|
|
631
|
+
case 'seam_agent_token_list': return describeAgentTokens(await client.agentTokens(needQuilt()))
|
|
632
|
+
case 'seam_agent_token_revoke': { const r = await client.revokeAgentToken(needQuilt(), args.id); return `Revoked agent token ${r.id}: it no longer reaches the quilt.` }
|
|
633
|
+
case 'seam_agents_make_lines': { const r = await client.setAgentsMakeLines(needQuilt(), args.allow); return r.agents_make_lines ? `Agents may make lines, tags and releases in ${r.quilt}.` : `Only people make lines, tags and releases in ${r.quilt}.` }
|
|
634
|
+
case 'seam_metrics': return describeMetrics(await client.metrics(needQuilt()))
|
|
635
|
+
case 'seam_redactions': return describeRedactions(await client.redactions(needQuilt()))
|
|
636
|
+
case 'seam_team_set': return describeTeams(await client.setTeam(needQuilt(), args.name, args.members))
|
|
353
637
|
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.` }
|
|
354
638
|
case 'seam_key_remove': { const r = await client.removeKey(args.key); return `Removed ${r.key}: commits it signed now show removed_key.` }
|
|
355
639
|
case 'seam_tag':
|
|
@@ -362,13 +646,21 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
362
646
|
const r = await client.mergeSquare(needQuilt(), needSquare(), { from: args.from, message: args.message, ...(inFolder ? { expect_head: block.head } : {}) })
|
|
363
647
|
return r.noop ? `Already up to date: ${args.from} is in this square.` : describeWrite(r, `Merged ${args.from} in:`) + await sync(r.square.head, r.square)
|
|
364
648
|
}
|
|
365
|
-
const r = await client.merge(needQuilt(), args.into || block?.line || 'main', { square: needSquare(), ...(inFolder ? { expect_head: block.head } : {}), message: args.message, dry_run: !!args.dry_run, on_conflict: args.on_conflict })
|
|
649
|
+
const r = await client.merge(needQuilt(), args.into || block?.line || 'main', { square: needSquare(), ...(inFolder ? { expect_head: block.head } : {}), message: args.message, dry_run: !!args.dry_run, on_conflict: args.on_conflict, ...(args.commit_live === false ? { commit_live: false } : {}) })
|
|
650
|
+
if (r.ok && inFolder && !args.dry_run && r.merged?.commit) {
|
|
651
|
+
const st = await toLineTip(client, found.dir, block, r.merged.line, r.merged.commit)
|
|
652
|
+
return describeMerge(r) + `\nThe folder is now on ${r.merged.line} (${st.written} file(s) updated); the next commit opens a new square.`
|
|
653
|
+
}
|
|
366
654
|
return describeMerge(r) + (!r.ok && r.head ? await sync(r.head, r.square) : '')
|
|
367
655
|
}
|
|
368
656
|
case 'seam_rebase': {
|
|
369
657
|
const r = await client.rebase(needQuilt(), needSquare(), { ...(inFolder ? { expect_head: block.head } : {}), onto: args.onto })
|
|
370
658
|
return (r.noop ? 'Already on the tip.' : describeWrite(r, 'Rebased')) + (r.square ? await sync(r.square.head || r.square.base, r.square) : '')
|
|
371
659
|
}
|
|
660
|
+
case 'seam_reparent': {
|
|
661
|
+
const r = await client.reparent(needQuilt(), needSquare(), { ...(inFolder ? { expect_head: block.head } : {}), to: args.to })
|
|
662
|
+
return (r.noop ? 'Already there.' : `${r.square.short} now ${r.reparented.to ? `stacks on ${r.reparented.to}` : `sits on ${r.square.line}'s tip`}${r.conflicted ? `, with ${r.conflicted} conflict(s) to settle` : ''}.`) + (r.square ? await sync(r.square.head || r.square.base, r.square) : '')
|
|
663
|
+
}
|
|
372
664
|
case 'seam_revert': {
|
|
373
665
|
const r = await client.revert(needQuilt(), { rev: args.rev, line: args.line })
|
|
374
666
|
return describeWrite(r, 'Reverted in a new square') + `\nMerge it with seam_merge (square: "${r.square.short}").`
|
|
@@ -381,7 +673,7 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
381
673
|
const r = await client.resolve(needQuilt(), needSquare(), { ...(inFolder ? { expect_head: block.head } : {}), entries: args.entries, ...(args.remember === false ? { remember: false } : {}) })
|
|
382
674
|
return describeWrite(r, 'Resolved') + await sync(r.square.head, r.square)
|
|
383
675
|
}
|
|
384
|
-
case 'seam_resolutions': return describeResolutions(await client.resolutions(needQuilt(), args.path))
|
|
676
|
+
case 'seam_resolutions': return describeResolutions(await client.resolutions(needQuilt(), args.path, { hunks: !!args.hunks }))
|
|
385
677
|
case 'seam_forget_resolution': {
|
|
386
678
|
const r = await client.forgetResolution(needQuilt(), args.key)
|
|
387
679
|
return `Forgot ${r.forgotten.short} (${r.forgotten.path}): that conflict will be asked about again.`
|
|
@@ -395,17 +687,46 @@ export async function runSeamTool (name, args, { client, found = null }) {
|
|
|
395
687
|
}
|
|
396
688
|
|
|
397
689
|
|
|
690
|
+
// Tools whose answer already is the caller's events.
|
|
691
|
+
const NO_EVENT_LINES = new Set(['seam_events', 'seam_events_webhook'])
|
|
692
|
+
/**
|
|
693
|
+
* The event lines every tool answer ends with (12 WriteResult.events): what happened to the
|
|
694
|
+
* caller's work in that quilt since their last answer (up to 20, from the server's per-member
|
|
695
|
+
* cursor, so the local and the hosted MCP say the same). Never fails the answer.
|
|
696
|
+
*/
|
|
697
|
+
async function answerEvents (client, name, quilt) {
|
|
698
|
+
if (!quilt || NO_EVENT_LINES.has(name) || !client.events) return ''
|
|
699
|
+
try {
|
|
700
|
+
const r = await client.events(quilt, { since: 'answer' })
|
|
701
|
+
const parts = []
|
|
702
|
+
if (r.events && r.events.length) parts.push(`New events (seam_events for more, after=${r.after}):\n${r.events.map(eventLine).join('\n')}`)
|
|
703
|
+
if (r.duties && r.duties.length) parts.push(`Waiting on you:\n${r.duties.map((d) => `- ${d}`).join('\n')}`)
|
|
704
|
+
return parts.length ? `\n\n${parts.join('\n\n')}` : ''
|
|
705
|
+
} catch { return '' }
|
|
706
|
+
}
|
|
707
|
+
|
|
398
708
|
/**
|
|
399
709
|
* Registers every tool on an MCP server. `client()` makes the quilt client for a call (it may
|
|
400
710
|
* throw when the caller is not signed in); `found()` the block folder, or null.
|
|
401
711
|
*/
|
|
402
|
-
export function registerSeamTools (server, { client, found = () => null, extraInput = {} }) {
|
|
712
|
+
export function registerSeamTools (server, { client, found = () => null, extraInput = {}, hosted = false }) {
|
|
403
713
|
for (const name of SEAM_TOOLS) {
|
|
404
|
-
|
|
714
|
+
const shape = { ...SEAM_INPUTS[name], ...extraInput }
|
|
715
|
+
// Loose, so an argument the tool does not take reaches the handler and is refused by name
|
|
716
|
+
// (a plain shape would drop it without a word: seam_log with n: 3 read as no limit).
|
|
717
|
+
server.registerTool(name, { description: SEAM_DESCRIPTIONS[name], inputSchema: z.looseObject(shape) }, async (args = {}) => {
|
|
405
718
|
try {
|
|
719
|
+
const unknown = Object.keys(args).filter((k) => !Object.hasOwn(shape, k) && k !== 'session')
|
|
720
|
+
if (unknown.length) {
|
|
721
|
+
const takes = Object.keys(shape).filter((k) => k !== 'session')
|
|
722
|
+
return { content: [{ type: 'text', text: `${name} does not take ${unknown.join(', ')}. It takes ${takes.length ? takes.join(', ') : 'no arguments'}.` }], isError: true }
|
|
723
|
+
}
|
|
406
724
|
const { session, ...rest } = args
|
|
407
725
|
void session
|
|
408
|
-
|
|
726
|
+
const c = await client(rest)
|
|
727
|
+
const f = found()
|
|
728
|
+
const text = await runSeamTool(name, rest, { client: c, found: f, hosted })
|
|
729
|
+
return { content: [{ type: 'text', text: text + await answerEvents(c, name, rest.quilt || f?.block?.quilt) }] }
|
|
409
730
|
} catch (e) {
|
|
410
731
|
return { content: [{ type: 'text', text: e.message }], isError: true }
|
|
411
732
|
}
|