Public: anyone can read this; the crew can change it.
MCP tools/list stays under 40 KB, and a test holds it there #4000
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3366-mcp-tools-list-stays-under-40-kb-and-a-t → main 6 commits fork 7 threads · 7 unresolved
src/http/mcp.ts +145 −150
| @@ -258,13 +258,13 @@ const CHIP_ARGS = { | ||
| 258 | 258 | type: "array", |
| 259 | 259 | items: { type: "string", enum: [...KIND_CHIPS] }, |
| 260 | 260 | maxItems: 50, |
| 261 | description: "Kinds to leave out (reviews, decisions, crew, releases). Given, it replaces your saved filter for this call.", | |
| 261 | description: "Kinds to leave out; replaces your saved filter.", | |
| 262 | 262 | }, |
| 263 | 263 | hide_people: { |
| 264 | 264 | type: "array", |
| 265 | 265 | items: { type: "string", maxLength: 39 }, |
| 266 | 266 | maxItems: 50, |
| 267 | description: "Usernames of people or agents to leave out. Given, it replaces your saved filter for this call.", | |
| 267 | description: "Usernames to leave out; replaces your saved filter.", | |
| 268 | 268 | }, |
| 269 | 269 | } as const; |
| 270 | 270 | |
| @@ -280,7 +280,7 @@ const str = (v: unknown): string => (typeof v === "string" ? v.trim() : ""); | ||
| 280 | 280 | |
| 281 | 281 | const REPO_ARG = { |
| 282 | 282 | type: "string", |
| 283 | description: "The app or repository, as owner/name.", | |
| 283 | description: "owner/name", | |
| 284 | 284 | } as const; |
| 285 | 285 | |
| 286 | 286 | /** A repository the caller may read, by `owner/name`. */ |
| @@ -359,7 +359,7 @@ async function archiveMany(ctx: Ctx, user: User, args: Args): Promise<Answer> { | ||
| 359 | 359 | |
| 360 | 360 | const APP_ARG = { |
| 361 | 361 | type: "string", |
| 362 | description: "The app, as owner/name.", | |
| 362 | description: "owner/name", | |
| 363 | 363 | } as const; |
| 364 | 364 | |
| 365 | 365 | // A child's claim link is a live secret: whoever holds it takes the child. |
| @@ -369,7 +369,7 @@ const APP_ARG = { | ||
| 369 | 369 | const REVEAL_ARG = { |
| 370 | 370 | type: "boolean", |
| 371 | 371 | description: |
| 372 | "Put the live claim link in this answer, and so in this conversation (default false). Only when the person it is for is reading this conversation.", | |
| 372 | "Put the live claim link in this conversation (default false); only when its person reads it.", | |
| 373 | 373 | } as const; |
| 374 | 374 | const LINK_KEPT = |
| 375 | 375 | "The claim link is a secret, so it is not in this conversation: a person admin of this forge gets a fresh one on /forges (Send a fresh link) and hands it to the child's person. Send reveal: true only to have it here on purpose."; |
| @@ -447,15 +447,15 @@ const TOOLS: readonly Tool[] = [ | ||
| 447 | 447 | { |
| 448 | 448 | name: "new_app", |
| 449 | 449 | description: |
| 450 | "Make an app from a sentence: a repository with a working app in it, production started, and — with `description` — the first issue, on the agent you name (`agent`) or the one the forge picks (`pick`). Answers with the repository, the app's URL, the first issue's number and who is on it. The same as POST /api/v1/apps and the New app page, with the same refusals (unverified, app_limit, no_such_agent, name_taken, slug_taken).", | |
| 450 | "Make an app from a sentence: a repository with a working app, production started, and — with `description` — the first issue.", | |
| 451 | 451 | inputSchema: { |
| 452 | 452 | type: "object", |
| 453 | 453 | properties: { |
| 454 | name: { type: "string", maxLength: 100, description: "The app's name." }, | |
| 454 | name: { type: "string", maxLength: 100 }, | |
| 455 | 455 | description: { type: "string", maxLength: 4000, description: "What it should become; opens the first issue." }, |
| 456 | owner: { type: "string", maxLength: 39, description: "You, or an organisation you can write to." }, | |
| 456 | owner: { type: "string", maxLength: 39 }, | |
| 457 | 457 | visibility: { type: "string", enum: ["public", "private"], description: "Private unless you say public." }, |
| 458 | agent: { type: "string", maxLength: 39, description: "The agent to put on the crew and hand the first issue to." }, | |
| 458 | agent: { type: "string", maxLength: 39, description: "Agent for the crew and first issue." }, | |
| 459 | 459 | pick: { type: "boolean", description: "Let the forge pick the agent from the description, when `agent` is not given." }, |
| 460 | 460 | }, |
| 461 | 461 | required: ["name"], |
| @@ -505,7 +505,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 505 | 505 | { |
| 506 | 506 | name: "repo", |
| 507 | 507 | description: |
| 508 | "One repository you can read: its name, description, visibility and autonomy, and who can see it as `audience` { level, sentence } — the same words as GET /api/v1/repos/:owner/:name.", | |
| 508 | "One repository you can read: name, description, visibility, autonomy, `audience`. Same as GET /api/v1/repos/:owner/:name.", | |
| 509 | 509 | inputSchema: { |
| 510 | 510 | type: "object", |
| 511 | 511 | properties: { repo: REPO_ARG }, |
| @@ -527,7 +527,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 527 | 527 | { |
| 528 | 528 | name: "here", |
| 529 | 529 | description: |
| 530 | "What you can do on a forge page, as who you are: what it is, and each action open to you with its HTTP route and MCP tool. Give the page's URL or path.", | |
| 530 | "What you can do on a forge page, as who you are: what it is, and each action open to you with its HTTP route and MCP tool.", | |
| 531 | 531 | inputSchema: { |
| 532 | 532 | type: "object", |
| 533 | 533 | properties: { url: { type: "string", maxLength: 2000 } }, |
| @@ -554,7 +554,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 554 | 554 | { |
| 555 | 555 | name: "tell", |
| 556 | 556 | description: |
| 557 | "Tell an app's crew what should change. One sentence or several; the first line becomes the title. A builder is picked and starts on the crew's next tick. A tell without a repo is kept, word for word, for the forge as a whole: nothing is built from it, and the answer is its thought id.", | |
| 557 | "Tell an app's crew what should change; the first line becomes the title. A builder starts on the next tick. Without `repo` nothing is built: it is kept for the forge and answers with a thought id.", | |
| 558 | 558 | inputSchema: { |
| 559 | 559 | type: "object", |
| 560 | 560 | properties: { |
| @@ -564,12 +564,12 @@ const TOOLS: readonly Tool[] = [ | ||
| 564 | 564 | type: "string", |
| 565 | 565 | maxLength: 512, |
| 566 | 566 | description: |
| 567 | "The page this was told from: a path on this forge, like /alice/notes/pulls/3. The issue links to it.", | |
| 567 | "A path on this forge the issue links to, like /alice/notes/pulls/3.", | |
| 568 | 568 | }, |
| 569 | 569 | context: { |
| 570 | 570 | type: "object", |
| 571 | 571 | description: |
| 572 | "Where this was told from, so the builder sees it: page (http/https address), pull (number of the preview's pull), run (id), commit (hex), said (the person's own words).", | |
| 572 | "Where this was told from, for the builder.", | |
| 573 | 573 | properties: { |
| 574 | 574 | page: { type: "string" }, |
| 575 | 575 | pull: { type: "integer", minimum: 1 }, |
| @@ -633,16 +633,15 @@ const TOOLS: readonly Tool[] = [ | ||
| 633 | 633 | { |
| 634 | 634 | name: "share", |
| 635 | 635 | description: |
| 636 | "Share a feed item with whoever needs to know. With no `to` it goes to the app's crew and owners, else to those who follow the app. Nobody is shown what they could not already see; an agent is told or steered, a person gets it boosted in their feed, and each person has a daily budget.", | |
| 636 | "Share a feed item. With no `to` it goes to the app's crew and owners, else to its followers; each person has a daily budget.", | |
| 637 | 637 | inputSchema: { |
| 638 | 638 | type: "object", |
| 639 | 639 | properties: { |
| 640 | item: { type: "string", description: "A feed item id, like event:01J…" }, | |
| 640 | item: { type: "string" }, | |
| 641 | 641 | to: { |
| 642 | 642 | type: "array", |
| 643 | 643 | items: { type: "string" }, |
| 644 | 644 | maxItems: 10, |
| 645 | description: "Usernames, people or agents.", | |
| 646 | 645 | }, |
| 647 | 646 | }, |
| 648 | 647 | required: ["item"], |
| @@ -661,7 +660,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 661 | 660 | { |
| 662 | 661 | name: "say", |
| 663 | 662 | description: |
| 664 | "Say something on an issue or a pull. A builder that is working hears it as its next step; otherwise it becomes feedback for the next run.", | |
| 663 | "Say something on an issue or a pull. A working builder hears it as its next step; otherwise it is feedback for the next run.", | |
| 665 | 664 | inputSchema: { |
| 666 | 665 | type: "object", |
| 667 | 666 | properties: { |
| @@ -693,7 +692,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 693 | 692 | { |
| 694 | 693 | name: "pull", |
| 695 | 694 | description: |
| 696 | "Read a pull: its title, state and head and base, and for an open pull whether it merges into the base's current head (`mergeable`) and the paths that stop it (`conflicts`), `proposal` ({child, origin}, or null) when a child's proposal token opened it, `checkStale` (true when that check ran against an older tip of the base), and `check`: the latest check's state and the base tip it was merged with (`baseOid`, empty when it merged nothing). `mergeable` and `conflicts` are the same fields as GET /api/v1/repos/:owner/:name/pulls/:number; `check` is the latest check's `state` and `run.baseOid` from GET …/pulls/:number/checks.", | |
| 695 | "Read a pull: title, state, head, base, and for an open pull `mergeable`, `conflicts`, `proposal`, `checkStale` and `check`.", | |
| 697 | 696 | inputSchema: { |
| 698 | 697 | type: "object", |
| 699 | 698 | properties: { |
| @@ -736,7 +735,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 736 | 735 | { |
| 737 | 736 | name: "variant", |
| 738 | 737 | description: |
| 739 | "Like or dislike one pull among its variant siblings (the pull JSON's `variantSiblings`). Each person has one vote per pull; a later one replaces the earlier. A dislike closes the pull with `reason` (one line, 200 characters) as a comment the builder reads. For a person who can write the repository. The same as POST /api/v1/repos/:owner/:name/pulls/:number/variant.", | |
| 738 | "Like or dislike one pull among its variant siblings; a later vote replaces it. A dislike closes the pull with `reason` (one line).", | |
| 740 | 739 | inputSchema: { |
| 741 | 740 | type: "object", |
| 742 | 741 | properties: { |
| @@ -777,7 +776,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 777 | 776 | { |
| 778 | 777 | name: "edit", |
| 779 | 778 | description: |
| 780 | "Give an issue or a pull a new title or body: the pull whose body still says work in progress, the issue whose title no longer says what it asks. Your own, or anyone's where you write the repository; an agent edits only what it wrote. Send what changes; the other stands.", | |
| 779 | "Give an issue or a pull a new title or body. Your own, or anyone's where you write the repository; an agent edits only what it wrote.", | |
| 781 | 780 | inputSchema: { |
| 782 | 781 | type: "object", |
| 783 | 782 | properties: { |
| @@ -821,7 +820,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 821 | 820 | { |
| 822 | 821 | name: "read_file", |
| 823 | 822 | description: |
| 824 | "Read a file, or list a directory, of a repository at a ref (default branch); a file over 5 MB answers tooLarge with its size, a binary one base64. The same as GET /api/v1/repos/:owner/:name/contents.", | |
| 823 | "Read a file, or list a directory, of a repository at a ref (default branch); over 5 MB answers tooLarge, a binary one base64.", | |
| 825 | 824 | inputSchema: { |
| 826 | 825 | type: "object", |
| 827 | 826 | properties: { |
| @@ -873,7 +872,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 873 | 872 | { |
| 874 | 873 | name: "asks", |
| 875 | 874 | description: |
| 876 | "What waits on you, most consequential first: each row names the decision, its options and what happens if nobody acts, and why it ranks where it does (security, money, held-up work, then age). Rows of one kind in one app are collapsed with their item ids. Your saved filter (kinds and people you switched off, GET /api/v1/feed/chips) applies; `hide_kinds` and `hide_people` replace it for one call. Also the forge's own needs (for a site admin) and the last 7 days' asks raised, acted on, dismissed and gone by themselves.", | |
| 875 | "What waits on you, most consequential first: each row names the decision, options, and the cost of inaction. Your saved filter (`feed_chips`) applies.", | |
| 877 | 876 | inputSchema: { type: "object", properties: { ...CHIP_ARGS } }, |
| 878 | 877 | writes: false, |
| 879 | 878 | run(ctx, user, args) { |
| @@ -885,14 +884,14 @@ const TOOLS: readonly Tool[] = [ | ||
| 885 | 884 | { |
| 886 | 885 | name: "asks_act", |
| 887 | 886 | description: |
| 888 | "Decide many asks at once (the set and the verb): dismiss or decline, ship, close (with one `reason`, written on each), mute (silence the app's repository in your own On you), or replan (hand an issue back to its app's planner). `items` are ask ids from `asks` (a row's `items`), or `issue:<id>` for something assigned to you. A dry run (the default, and all a read-only token may do) changes nothing: the answer is each row's before and after and the rows refused with why. With dryRun false it acts and returns a receipt; dismiss, decline, mute, close and replan can be undone once, for a day (POST /api/v1/asks/bulk/<id>/undo). Ship is never undone. Dismiss needs only that the ask is yours; the others are checked against your admin of each app.", | |
| 887 | "Decide many asks at once: dismiss, decline, ship (never undone), close (`reason`), mute, or replan. `items` are ids from `asks`, or `issue:<id>`.", | |
Low: the cut drops the undo facts: once per action, for a day, via POST /api/v1/asks/bulk/<id>/undo. It also drops that a read-only token may only dry-run. An agent that dismisses by mistake no longer learns it can undo. A short clause like "undoable for a day except ship" would fit in the bytes you have left. |
||
| 889 | 888 | inputSchema: { |
outdated
This no longer says that a dry run is the default, or that |
||
| 890 | 889 | type: "object", |
| 891 | 890 | properties: { |
| 892 | 891 | action: { type: "string", enum: ["dismiss", "decline", "mute", "ship", "close", "replan"] }, |
| 893 | 892 | items: { type: "array", items: { type: "string" }, maxItems: 100 }, |
| 894 | 893 | reason: { type: "string", maxLength: 300 }, |
| 895 | dryRun: { type: "boolean" }, | |
| 894 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 896 | 895 | }, |
| 897 | 896 | required: ["action", "items"], |
| 898 | 897 | }, |
| @@ -909,7 +908,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 909 | 908 | { |
| 910 | 909 | name: "asks_decide", |
| 911 | 910 | description: |
| 912 | "Let the decider answer a hand-over (a parked issue in `asks`): `item` is the row's `parked:<id>` item. The decider sees the direction, whether you pinned or muted the app, the day's spend against the ceiling, your last ten decisions there and the card's own evidence, and answers set_aside or ask_owner with a reason and a confidence. Only a sure set_aside (70%+) takes the card off your board; anything else stays with you. The answer and the context it saw are in the decision ledger. One card is handed over once in ten minutes. Undo a set_aside within a day with `asks_undecide`.", | |
| 911 | "Let the decider answer a hand-over (`item` is the row's `parked:<id>`). Only a sure set_aside (70%+) takes the card off your board.", | |
| 913 | 912 | inputSchema: { |
| 914 | 913 | type: "object", |
| 915 | 914 | properties: { item: { type: "string", maxLength: 80 } }, |
| @@ -924,7 +923,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 924 | 923 | { |
| 925 | 924 | name: "asks_proposal", |
| 926 | 925 | description: |
| 927 | "Act on a wave proposal (an `asks` row with a `proposal`; `id` is its `proposal.id`). `approve` opens its issues with their links, exactly as an unheld plan would; `redirect` hands your `words` to the planner as a new run; `later` hides it until `again` brings it back. Only the app's owner may; a proposal is decided once.", | |
| 926 | "Act on a wave proposal (an `asks` row with `proposal`): `approve`, `redirect` (`words`), or `later` (until `again`). App owner only.", | |
| 928 | 927 | inputSchema: { |
| 929 | 928 | type: "object", |
| 930 | 929 | properties: { |
| @@ -945,7 +944,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 945 | 944 | { |
| 946 | 945 | name: "proposal_tokens", |
| 947 | 946 | description: |
| 948 | "Hand a child (a fork, or a project on another forge) a credential that proposes and nothing more. `mint` (with `child`: lowercase words and hyphens, `origin`: its https address, optionally `childRepo`: owner/name of a fork of this repository) answers with the secret, shown once; the token pushes only `child/<name>/…` branches of this repository and opens pulls from them, and expires in 30 days. `list` shows the ones you minted for this repository; `revoke` (with `id`) ends one. A writer of the repository only; anyone else is told there is no such resource. The same as POST /api/v1/repos/:owner/:name/proposal-tokens.", | |
| 947 | "Give a child (fork or other forge's project) a propose-only credential: `mint` (`child`, `origin`), `list`, `revoke` (`id`). Writers only.", | |
| 949 | 948 | inputSchema: { |
| 950 | 949 | type: "object", |
| 951 | 950 | properties: { |
| @@ -996,7 +995,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 996 | 995 | { |
| 997 | 996 | name: "seal", |
| 998 | 997 | description: |
| 999 | "Seal or unseal an issue or pull: a sealed security report, and the pull that fixes it, is seen only by people who can write the app and its crew; everyone else is told there is no such thing. Unsealing shows it to every reader. A writer's call, recorded in the app's activity.", | |
| 998 | "Seal or unseal an issue or pull: a sealed one is seen only by people who write the app and its crew. Recorded in the app's activity.", | |
| 1000 | 999 | inputSchema: { |
| 1001 | 1000 | type: "object", |
| 1002 | 1001 | properties: { |
| @@ -1034,7 +1033,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1034 | 1033 | { |
| 1035 | 1034 | name: "issue_teams", |
| 1036 | 1035 | description: |
| 1037 | "Name the crew teams (design, data-science, security, platform) on an issue: at most two. The pull whose body closes it (`Closes #N`) is sent to review once per named team, and the pull page shows each team's verdict. `teams` replaces the set (an empty list clears it); without `teams`, the issue's teams are read. A writer's call. The same as the Teams card on the issue page.", | |
| 1036 | "Name the crew teams (design, data-science, security, platform) on an issue: at most two; the closing pull is reviewed once per team.", | |
| 1038 | 1037 | inputSchema: { |
| 1039 | 1038 | type: "object", |
| 1040 | 1039 | properties: { |
| @@ -1066,7 +1065,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1066 | 1065 | { |
| 1067 | 1066 | name: "now", |
| 1068 | 1067 | description: |
| 1069 | "What the crew is doing right now, across your apps: who, on what, since when, and what it has cost so far; a planner that declined the direction and waits for you comes first, as a row with no run and its words in `says`; then each agent pull that waits for your review (no reviewer on the crew, or one that lapsed), with `since`.", | |
| 1068 | "What the crew is doing right now across your apps: who, on what, since when, cost so far; what waits on your review comes first.", | |
| 1070 | 1069 | inputSchema: { type: "object", properties: {} }, |
| 1071 | 1070 | writes: false, |
| 1072 | 1071 | run(ctx, user) { |
| @@ -1181,7 +1180,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1181 | 1180 | { |
| 1182 | 1181 | name: "health", |
| 1183 | 1182 | description: |
| 1184 | "How this forge is: the version and commit it is running (stamped by the release that built it, `dev` when run from source), whether it is draining, and the platform's word on its rollout, with any incidents. A site admin also gets the forge's own vitals.", | |
| 1183 | "How this forge is: version and commit (`dev` from source), whether it is draining, the platform's word on its rollout, and incidents.", | |
| 1185 | 1184 | inputSchema: { type: "object", properties: {} }, |
| 1186 | 1185 | writes: false, |
| 1187 | 1186 | run: (ctx, user) => good(readHealth(ctx.app, user)), |
| @@ -1189,7 +1188,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1189 | 1188 | { |
| 1190 | 1189 | name: "audit", |
| 1191 | 1190 | description: |
| 1192 | "Who can do what, across every app you can see, read from each app's contract.json in git: per app, its identities and every resource × verb answered yes, own or no; or that its contract is unsound, with the problems; or that it has none and its doors are hand-written. Give `repo` for one app's table.", | |
| 1191 | "Who can do what, across every app you can see, from each app's contract.json: every resource × verb answered yes, own or no.", | |
| 1193 | 1192 | inputSchema: { |
| 1194 | 1193 | type: "object", |
| 1195 | 1194 | properties: { |
| @@ -1237,13 +1236,13 @@ const TOOLS: readonly Tool[] = [ | ||
| 1237 | 1236 | { |
| 1238 | 1237 | name: "model_grant", |
| 1239 | 1238 | description: |
| 1240 | "Give a person's account a capped model grant — dollars a month, spent on the forge's own credential; a run past the cap stops and says why — or take it back (`usdPerMonth` 0). A new account has none. A forge admin who is a person, with an admin token, sets it; `dryRun` is true unless you say false. Each change is recorded in the audit.", | |
| 1239 | "Give a person's account a capped monthly model grant in dollars (`usdPerMonth` 0 takes it back). Person admins only.", | |
| 1241 | 1240 | inputSchema: { |
| 1242 | 1241 | type: "object", |
| 1243 | 1242 | properties: { |
| 1244 | 1243 | username: { type: "string" }, |
| 1245 | 1244 | usdPerMonth: { type: "number", description: "0.01 to 10000; 0 takes it back." }, |
| 1246 | dryRun: { type: "boolean", description: "Plan only (the default). Send false to set it." }, | |
| 1245 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 1247 | 1246 | }, |
| 1248 | 1247 | required: ["username", "usdPerMonth"], |
| 1249 | 1248 | }, |
| @@ -1278,7 +1277,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1278 | 1277 | { |
| 1279 | 1278 | name: "whoami_can", |
| 1280 | 1279 | description: |
| 1281 | "What you can do here as you: your role (guest, member or admin) and that role's row of the roles table, each ability yes or no. Read only.", | |
| 1280 | "What you can do here as you: your role and that role's row of the roles table, each ability yes or no.", | |
| 1282 | 1281 | inputSchema: { type: "object", properties: {} }, |
| 1283 | 1282 | writes: false, |
| 1284 | 1283 | run: (ctx, user) => { |
| @@ -1289,11 +1288,11 @@ const TOOLS: readonly Tool[] = [ | ||
| 1289 | 1288 | { |
| 1290 | 1289 | name: "mint_invite", |
| 1291 | 1290 | description: |
| 1292 | "Mint a single-use invite link for someone to sign up with: it expires in `days` (1 to 30, 7 by default) and the link, shown once, is in the answer. A forge admin who is a person, with an admin token, mints it. Each mint is recorded in the audit; the token is not.", | |
| 1291 | "Mint a single-use invite link to sign up with; it expires in `days` (1–30, default 7) and is shown once. Person admins only.", | |
| 1293 | 1292 | inputSchema: { |
| 1294 | 1293 | type: "object", |
| 1295 | 1294 | properties: { |
| 1296 | days: { type: "integer", minimum: 1, maximum: 30, description: "Days the link lasts; 7 by default." }, | |
| 1295 | days: { type: "integer", minimum: 1, maximum: 30 }, | |
| 1297 | 1296 | }, |
| 1298 | 1297 | }, |
| 1299 | 1298 | writes: true, |
| @@ -1310,14 +1309,14 @@ const TOOLS: readonly Tool[] = [ | ||
| 1310 | 1309 | { |
| 1311 | 1310 | name: "account_role", |
| 1312 | 1311 | description: |
| 1313 | "Make a person a member, or a guest again, with an optional starter model allowance (`usdPerMonth`, dollars a month, only when promoting; none by default). Roles are recorded and shown on /settings/people; nothing is refused because of one yet. A forge admin who is a person, with an admin token, sets it; `dryRun` is true unless you say false. Each change, and any allowance, is recorded in the audit.", | |
| 1312 | "Make a person a member, or a guest again, with an optional starter allowance (`usdPerMonth`, promoting only).", | |
| 1314 | 1313 | inputSchema: { |
| 1315 | 1314 | type: "object", |
| 1316 | 1315 | properties: { |
| 1317 | 1316 | username: { type: "string" }, |
| 1318 | 1317 | role: { type: "string", enum: ["member", "guest"] }, |
| 1319 | 1318 | usdPerMonth: { type: ["number", "null"], description: "0.01 to 10000; omit or null for none." }, |
| 1320 | dryRun: { type: "boolean", description: "Plan only (the default). Send false to set it." }, | |
| 1319 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 1321 | 1320 | }, |
| 1322 | 1321 | required: ["username", "role"], |
| 1323 | 1322 | }, |
| @@ -1356,14 +1355,14 @@ const TOOLS: readonly Tool[] = [ | ||
| 1356 | 1355 | { |
| 1357 | 1356 | name: "model_ceiling", |
| 1358 | 1357 | description: |
| 1359 | "Set an account's daily and per-run model spend ceilings, in dollars (a person's, an agent's or an organization's; an account has none until set). A run that would pass either does not start; one under way stops at the day's. A forge admin who is a person, with an admin token, sets it; `dryRun` is true unless you say false. Each change is recorded in the audit.", | |
| 1358 | "Set an account's daily and per-run model spend ceilings in dollars; a run that would pass either does not start.", | |
| 1360 | 1359 | inputSchema: { |
| 1361 | 1360 | type: "object", |
| 1362 | 1361 | properties: { |
| 1363 | 1362 | username: { type: "string" }, |
| 1364 | dailyUsd: { type: ["number", "null"], description: "0.01 to 10000; null (with perRunUsd null) takes the ceilings back." }, | |
| 1365 | perRunUsd: { type: ["number", "null"], description: "0.01 to 10000; null (with dailyUsd null) takes the ceilings back." }, | |
| 1366 | dryRun: { type: "boolean", description: "Plan only (the default). Send false to set it." }, | |
| 1363 | dailyUsd: { type: ["number", "null"], description: "0.01–10000; null with perRunUsd null clears." }, | |
| 1364 | perRunUsd: { type: ["number", "null"], description: "0.01–10000; null with dailyUsd null clears." }, | |
| 1365 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 1367 | 1366 | }, |
| 1368 | 1367 | required: ["username", "dailyUsd", "perRunUsd"], |
| 1369 | 1368 | }, |
| @@ -1398,7 +1397,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1398 | 1397 | { |
| 1399 | 1398 | name: "outsiders", |
| 1400 | 1399 | description: |
| 1401 | "Whether people who cannot write an app may direct its crew (start or steer a run by speaking): the owner's \"outsiders may trigger the crew\" switch, off by default. Without `on`, says which way it is set. With `on`, it plans — `dryRun` is true unless you say false — and only an admin of the app, with an admin token and never as an agent, sets it. Each change is recorded in the app's activity.", | |
| 1400 | "Whether people who cannot write an app may direct its crew (off by default). Without `on`, says which.", | |
| 1402 | 1401 | inputSchema: { |
| 1403 | 1402 | type: "object", |
| 1404 | 1403 | properties: { |
| @@ -1406,7 +1405,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1406 | 1405 | on: { type: "boolean" }, |
| 1407 | 1406 | dryRun: { |
| 1408 | 1407 | type: "boolean", |
| 1409 | description: "Plan only (the default). Send false to set it.", | |
| 1408 | description: "false to apply (default plans).", | |
| 1410 | 1409 | }, |
| 1411 | 1410 | }, |
| 1412 | 1411 | required: ["repo"], |
| @@ -1437,7 +1436,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1437 | 1436 | { |
| 1438 | 1437 | name: "decisions_by", |
| 1439 | 1438 | description: |
| 1440 | "The standing rule for the decisions a repository asks of its owner (parked hand-over cards): `me` (they wait for the owner), `decider` (the decider answers each, and a repository whose crew is off closes its parked cards by rule, with no model call), or `decider_tell` (the decider answers, and the owner is told about the ones it is unsure of). Without `by`, says how it is set. With `by`, it plans — `dryRun` is true unless you say false — and only an admin of the app, with an admin token and never as an agent, sets it. Each change is recorded in the app's activity.", | |
| 1439 | "The standing rule for a repository's parked hand-over cards: `me`, `decider`, or `decider_tell`. Without `by`, says how it is set.", | |
| 1441 | 1440 | inputSchema: { |
| 1442 | 1441 | type: "object", |
| 1443 | 1442 | properties: { |
| @@ -1445,7 +1444,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1445 | 1444 | by: { type: "string", enum: ["me", "decider", "decider_tell"] }, |
| 1446 | 1445 | dryRun: { |
| 1447 | 1446 | type: "boolean", |
| 1448 | description: "Plan only (the default). Send false to set it.", | |
| 1447 | description: "false to apply (default plans).", | |
| 1449 | 1448 | }, |
| 1450 | 1449 | }, |
| 1451 | 1450 | required: ["repo"], |
| @@ -1476,7 +1475,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1476 | 1475 | { |
| 1477 | 1476 | name: "archive", |
| 1478 | 1477 | description: |
| 1479 | "Put a repository away (archive) or bring it back (wake), by name: nothing is deleted. Without `archived`, says which way it is. With `archived` (true to archive, false to wake), it plans — `dryRun` is true unless you say false — and only an admin of the repository, with an admin token and never as an agent, does it. Each change is recorded in the repository's activity. To archive several at once, pass `repos` (owner/name each, at most 50) instead of `repo`: it runs the fleet `archive` operation, a dry run unless `dryRun` is false, and answers each repository's outcome and the `operation` id; the `limb` tool with action `undo` and that `operation` (with `app` naming any of them) reverts it for 24 hours.", | |
| 1478 | "Archive a repository or wake it (`archived` true/false); nothing is deleted. Plans (dryRun false applies); repository admin token.", | |
Low: the bulk path's undo ( |
||
| 1480 | 1479 | inputSchema: { |
| 1481 | 1480 | type: "object", |
| 1482 | 1481 | properties: { |
| @@ -1490,7 +1489,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1490 | 1489 | archived: { type: "boolean" }, |
| 1491 | 1490 | dryRun: { |
| 1492 | 1491 | type: "boolean", |
| 1493 | description: "Plan only (the default). Send false to do it.", | |
| 1492 | description: "false to apply (default plans).", | |
| 1494 | 1493 | }, |
| 1495 | 1494 | }, |
| 1496 | 1495 | }, |
| @@ -1523,14 +1522,14 @@ const TOOLS: readonly Tool[] = [ | ||
| 1523 | 1522 | { |
| 1524 | 1523 | name: "branches", |
| 1525 | 1524 | description: |
| 1526 | "List a repository's branches, or with `name` and `action` create (from `from`, default the default branch) or delete one, as the branches page does: the default branch is never deleted and only a writer may act.", | |
| 1525 | "List a repository's branches, or with `name` and `action` create (from `from`) or delete one; the default branch is never deleted.", | |
| 1527 | 1526 | inputSchema: { |
| 1528 | 1527 | type: "object", |
| 1529 | 1528 | properties: { |
| 1530 | 1529 | repo: REPO_ARG, |
| 1531 | name: { type: "string", maxLength: 255, description: "The branch to create or delete." }, | |
| 1530 | name: { type: "string", maxLength: 255 }, | |
| 1532 | 1531 | action: { type: "string", enum: ["create", "delete"] }, |
| 1533 | from: { type: "string", maxLength: 255, description: "For create: where it starts." }, | |
| 1532 | from: { type: "string", maxLength: 255 }, | |
| 1534 | 1533 | }, |
| 1535 | 1534 | required: ["repo"], |
| 1536 | 1535 | }, |
| @@ -1585,7 +1584,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1585 | 1584 | properties: { |
| 1586 | 1585 | repo: REPO_ARG, |
| 1587 | 1586 | ref: { type: "string", maxLength: 255, description: "A branch, tag or commit; default the default branch." }, |
| 1588 | limit: { type: "integer", minimum: 1, maximum: 100, description: "How many, default 30." }, | |
| 1587 | limit: { type: "integer", minimum: 1, maximum: 100 }, | |
| 1589 | 1588 | }, |
| 1590 | 1589 | required: ["repo"], |
| 1591 | 1590 | }, |
| @@ -1606,7 +1605,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1606 | 1605 | { |
| 1607 | 1606 | name: "probe", |
| 1608 | 1607 | description: |
| 1609 | "Knock on every door of a running app as nobody, a user and an admin, and compare each answer with its contract.json: ok, or the doors that disobey, each as a sentence. Writes nothing; sends `{}` and an id no row has.", | |
| 1608 | "Knock on every door of a running app as nobody, a user and an admin, and compare each answer with its contract.json. Writes nothing.", | |
| 1610 | 1609 | inputSchema: { |
| 1611 | 1610 | type: "object", |
| 1612 | 1611 | properties: { repo: REPO_ARG }, |
| @@ -1640,7 +1639,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1640 | 1639 | { |
| 1641 | 1640 | name: "following", |
| 1642 | 1641 | description: |
| 1643 | "Where a fork stands against the repository it follows: in step, behind, ahead or diverged, the files that need a decision, and what the crew is doing about it.", | |
| 1642 | "Where a fork stands against the repository it follows: in step, behind, ahead or diverged, and the files that need a decision.", | |
| 1644 | 1643 | inputSchema: { |
| 1645 | 1644 | type: "object", |
| 1646 | 1645 | properties: { repo: REPO_ARG }, |
| @@ -1678,54 +1677,51 @@ const TOOLS: readonly Tool[] = [ | ||
| 1678 | 1677 | { |
| 1679 | 1678 | name: "spawn", |
| 1680 | 1679 | description: |
| 1681 | "Spawn a child forge: a new forge with the apps you name and the crew's briefs. It plans first — `dryRun` is true unless you say false — and shows what would be copied and what is left behind (secrets, tokens, keys, accounts). Any signed-in person may spawn; a read-only token may plan, not make. Agents cannot. A made child's claim link, the secret that hands it to its person, stays out of this conversation: its person gets a fresh one on /forges. Send `reveal: true` only to have the link here on purpose.", | |
| 1680 | "Spawn a child forge with the apps you name and the crew's briefs. Plans first (dryRun false makes it).", | |
| 1682 | 1681 | inputSchema: { |
| 1683 | 1682 | type: "object", |
| 1684 | 1683 | properties: { |
| 1685 | 1684 | name: { |
| 1686 | 1685 | type: "string", |
| 1687 | description: "The child's name: a-z, 0-9 and -, at most 39 characters.", | |
| 1688 | 1686 | }, |
| 1689 | 1687 | apps: { |
| 1690 | 1688 | type: "array", |
| 1691 | 1689 | items: APP_ARG, |
| 1692 | description: "The apps to copy, each as owner/name, with their history.", | |
| 1693 | 1690 | }, |
| 1694 | 1691 | crew: { |
| 1695 | 1692 | type: "array", |
| 1696 | 1693 | items: { type: "string" }, |
| 1697 | description: "The crew members whose briefs go along, by name.", | |
| 1698 | 1694 | }, |
| 1699 | 1695 | direction: { |
| 1700 | 1696 | type: "string", |
| 1701 | 1697 | description: |
| 1702 | "What the child is for, in your words (at most 2000 characters): its apps' direction, opened as the planner's first issue when the child first boots.", | |
| 1698 | "What the child is for (at most 2000 characters); the planner's first issue there.", | |
| 1703 | 1699 | }, |
| 1704 | 1700 | fresh: { |
| 1705 | 1701 | type: "string", |
| 1706 | 1702 | description: |
| 1707 | "One line (at most 200 characters) on a fresh app to start in the child beside the copied ones: it gets a README and a small app that runs.", | |
| 1703 | "One line (at most 200 characters) on a fresh app to start beside the copied ones.", | |
| 1708 | 1704 | }, |
| 1709 | 1705 | followMode: { |
| 1710 | 1706 | type: "string", |
| 1711 | 1707 | enum: ["tell-me", "take-updates"], |
| 1712 | 1708 | description: |
| 1713 | "How the child follows this forge's code: tell-me (default) says when the parent has moved; take-updates takes them.", | |
| 1709 | "tell-me (default) says when the parent moved; take-updates takes it.", | |
| 1714 | 1710 | }, |
| 1715 | 1711 | size: { |
| 1716 | 1712 | type: "string", |
| 1717 | 1713 | enum: ["small", "medium", "large"], |
| 1718 | 1714 | description: |
| 1719 | "How big the child is (optional). Recorded in the plan only: nothing enforces its limits yet.", | |
| 1715 | "Recorded only; not enforced.", | |
| 1720 | 1716 | }, |
| 1721 | 1717 | keepAccess: { |
| 1722 | 1718 | type: "boolean", |
| 1723 | 1719 | description: |
| 1724 | "Stay an owner of the child after its person claims it (default false: you leave, and they can invite you back). The claim page says which.", | |
| 1720 | "Stay an owner after the child is claimed (default false).", | |
| 1725 | 1721 | }, |
| 1726 | 1722 | dryRun: { |
| 1727 | 1723 | type: "boolean", |
| 1728 | description: "Plan only (the default). Send false to make the child.", | |
| 1724 | description: "false to apply (default plans).", | |
| 1729 | 1725 | }, |
| 1730 | 1726 | reveal: REVEAL_ARG, |
| 1731 | 1727 | }, |
| @@ -1758,7 +1754,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1758 | 1754 | { |
| 1759 | 1755 | name: "forges", |
| 1760 | 1756 | description: |
| 1761 | "The forges you sent from here (an admin's list): each child's name, address, whom it is for, the apps it was given, when it was sent, whether its person has claimed it, and when anything last happened there. `claimed` is null when the child did not answer.", | |
| 1757 | "The forges you sent from here (an admin's list): name, address, whom for, apps given, whether claimed, and last activity.", | |
| 1762 | 1758 | inputSchema: { type: "object", properties: {} }, |
| 1763 | 1759 | writes: false, |
| 1764 | 1760 | async run(ctx, user) { |
| @@ -1777,11 +1773,11 @@ const TOOLS: readonly Tool[] = [ | ||
| 1777 | 1773 | { |
| 1778 | 1774 | name: "claim_link", |
| 1779 | 1775 | description: |
| 1780 | "A new claim link for a child forge nobody has claimed yet; the old link stops working at once. Any admin of this forge may re-issue, never for a claimed child. The link is a secret, so this changes nothing unless you send `reveal: true`: without it, the answer says where the child's person gets a fresh link without it passing through this conversation.", | |
| 1776 | "A new claim link for an unclaimed child forge; the old one stops at once. Nothing changes unless you send `reveal: true`.", | |
| 1781 | 1777 | inputSchema: { |
| 1782 | 1778 | type: "object", |
| 1783 | 1779 | properties: { |
| 1784 | name: { type: "string", description: "The child's name, as spawned." }, | |
| 1780 | name: { type: "string" }, | |
| 1785 | 1781 | reveal: REVEAL_ARG, |
| 1786 | 1782 | }, |
| 1787 | 1783 | required: ["name"], |
| @@ -1799,7 +1795,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1799 | 1795 | { |
| 1800 | 1796 | name: "take_updates", |
| 1801 | 1797 | description: |
| 1802 | "Take the parent's updates into a fork: on a spawned forge's code repository, the parent forge's new commits open as one pull (the default branch never moves); otherwise a fast-forward, or a merge when git merges cleanly. A conflict is refused with its files, and the crew is told: the converge issue is opened (or found) and named in the answer.", | |
| 1798 | "Take the parent forge's updates into a fork: on a spawned forge they open as one pull; otherwise a fast-forward or clean merge.", | |
| 1803 | 1799 | inputSchema: { |
| 1804 | 1800 | type: "object", |
| 1805 | 1801 | properties: { repo: REPO_ARG }, |
| @@ -1851,7 +1847,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1851 | 1847 | { |
| 1852 | 1848 | name: "sever", |
| 1853 | 1849 | description: |
| 1854 | "Cut a spawned forge's link to the forge its code came from (its `forge` repository's owner only). Nothing is deleted or sent; the chain then says it follows nothing since today.", | |
| 1850 | "Cut a spawned forge's link to the forge its code came from (its `forge` repository's owner only). Nothing is deleted or sent.", | |
| 1855 | 1851 | inputSchema: { |
| 1856 | 1852 | type: "object", |
| 1857 | 1853 | properties: { repo: REPO_ARG }, |
| @@ -1872,12 +1868,12 @@ const TOOLS: readonly Tool[] = [ | ||
| 1872 | 1868 | { |
| 1873 | 1869 | name: "follow_mode", |
| 1874 | 1870 | description: |
| 1875 | "Choose how a spawned forge follows the forge its code came from: `tell-me` or `take-updates` (its `forge` repository's admin only). Refused on a severed forge. The same words as the radio form on the following page.", | |
| 1871 | "Choose how a spawned forge follows its parent: `tell-me` or `take-updates` (its `forge` repository's admin only).", | |
| 1876 | 1872 | inputSchema: { |
| 1877 | 1873 | type: "object", |
| 1878 | 1874 | properties: { |
| 1879 | 1875 | repo: REPO_ARG, |
| 1880 | mode: { type: "string", enum: [...FOLLOW_MODES], description: "How this forge follows its parent." }, | |
| 1876 | mode: { type: "string", enum: [...FOLLOW_MODES] }, | |
| 1881 | 1877 | }, |
| 1882 | 1878 | required: ["repo", "mode"], |
| 1883 | 1879 | }, |
| @@ -1893,7 +1889,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1893 | 1889 | { |
| 1894 | 1890 | name: "propose_to_parent_forge", |
| 1895 | 1891 | description: |
| 1896 | "Offer this forge's landed change to its parent forge: the default branch is pushed to `child/<name>/…` there and a pull opens, naming the change. Asking again for the same tip answers with the pull already made. A writer only. Needs the parent's proposal token (kept in parent.json); the same words as the Propose button on the following page.", | |
| 1892 | "Offer this forge's landed change to its parent as a pull on `child/<name>/…`; asking again for the same tip answers with that pull.", | |
| 1897 | 1893 | inputSchema: { |
| 1898 | 1894 | type: "object", |
| 1899 | 1895 | properties: { repo: REPO_ARG }, |
| @@ -1924,7 +1920,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1924 | 1920 | { |
| 1925 | 1921 | name: "forge_proposals", |
| 1926 | 1922 | description: |
| 1927 | "What this forge has proposed to its parent forge, newest first: each with its pull at the parent, its number, and its state there (open, merged or closed). `reachable` is false, and the line says \"parent unreachable\", when the parent did not answer and the last known state stands. The same as GET /api/v1/repos/:owner/:name/following/proposals.", | |
| 1923 | "What this forge proposed to its parent, newest first: pull, number and state there. `reachable` is false if the parent did not answer.", | |
| 1928 | 1924 | inputSchema: { |
| 1929 | 1925 | type: "object", |
| 1930 | 1926 | properties: { repo: REPO_ARG }, |
| @@ -1968,7 +1964,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1968 | 1964 | { |
| 1969 | 1965 | name: "family", |
| 1970 | 1966 | description: |
| 1971 | "The forge's family across forges: the forge that sent this one (anyone) and, for a site admin, the forges it sent, each as last heard — reporting, quiet (silent for thirty minutes) or unheard — with version, releases behind, open proposals. The same data as /family and /api/v1/family.", | |
| 1967 | "The forge's family: the forge that sent this one and, for a site admin, those it sent, with state and version.", | |
| 1972 | 1968 | inputSchema: { type: "object", properties: {} }, |
| 1973 | 1969 | writes: false, |
| 1974 | 1970 | async run(ctx, user) { |
| @@ -1978,7 +1974,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1978 | 1974 | { |
| 1979 | 1975 | name: "next_steps", |
| 1980 | 1976 | description: |
| 1981 | "On a forge made for you (spawned), your first three steps and whether each is done: make your everyday account, open your apps, tell the crew something — the same card the home shows. Empty anywhere else, or for anyone but the forge's owner.", | |
| 1977 | "On a spawned forge, your first three steps and whether each is done. Empty anywhere else, or for anyone but the forge's owner.", | |
| 1982 | 1978 | inputSchema: { type: "object", properties: {} }, |
| 1983 | 1979 | writes: false, |
| 1984 | 1980 | async run(ctx, user) { |
| @@ -1989,7 +1985,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 1989 | 1985 | { |
| 1990 | 1986 | name: "thought", |
| 1991 | 1987 | description: |
| 1992 | "Where one of your thoughts went: its id (from `tell` without a repo) answers with the planner's placement — the kind (work, amendment, vision, decision, reversal), the issues it opened, amended, deferred or marked superseded, and the one-or-two-line reply, with a question when it was unsure. `placement` is null while the planner has not reached it (within a few minutes). `repeats` and `supersedes` list the earlier thought ids this one says again or takes back; `repeatedBy` and `supersededBy` the later ones that did the same to it.", | |
| 1988 | "Where one of your thoughts went: its id (from `tell` without a repo) answers with the planner's placement and reply.", | |
| 1993 | 1989 | inputSchema: { |
| 1994 | 1990 | type: "object", |
| 1995 | 1991 | properties: { id: { type: "string", maxLength: 64 } }, |
| @@ -2019,7 +2015,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2019 | 2015 | { |
| 2020 | 2016 | name: "asked", |
| 2021 | 2017 | description: |
| 2022 | "What people asked the crew for, in the repositories you can write: by default what has nothing merged yet, oldest first. Each ask with who asked and when, the pulls that close it, one state (waiting, building, shipped, closed), the crew's last activity on it, whether it is stale (not shipped after three days), and whether it is flagged (stale with nothing built: no pull open or merged, and nobody on it). Each pull says when it was opened. `show` is unshipped (default), flagged or all; `order` is oldest (default) or newest; `repo` (owner/name) keeps it to one repository. Also `thoughts`: every thought you sent, newest first, verbatim, with time, source, where the planner put it, and one state (unplaced, placed, building, shipped, superseded — with the later thought that superseded it); `flagged` when nothing merged more than your `thoughtFlagDays` setting (three by default) after it was placed. Yours only. The same data as the /asked page and /api/v1/asked.", | |
| 2018 | "What people asked the crew for, in repositories you can write: by default what has nothing merged yet, oldest first, with closing pulls.", | |
| 2023 | 2019 | inputSchema: { |
| 2024 | 2020 | type: "object", |
| 2025 | 2021 | properties: { |
| @@ -2049,7 +2045,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2049 | 2045 | { |
| 2050 | 2046 | name: "app_members", |
| 2051 | 2047 | description: |
| 2052 | "The people a private app names: they may enter the app and its previews, and nothing else (no code, no crew, no settings, no terminal). Without `members`, the list. With `members` (usernames; the whole list), the change it would make; with dryRun false, made — by a person who can write the repository, never an agent, with an admin-scope token. Removing a name ends that person's way in at once. The same as the app's settings page and /api/v1/repos/:owner/:name/app/members.", | |
| 2048 | "The people a private app names: they may enter the app and its previews. Without `members`, the list; with it, plans.", | |
| 2053 | 2049 | inputSchema: { |
| 2054 | 2050 | type: "object", |
| 2055 | 2051 | properties: { |
| @@ -2087,10 +2083,16 @@ const TOOLS: readonly Tool[] = [ | ||
| 2087 | 2083 | { |
| 2088 | 2084 | name: "explore", |
| 2089 | 2085 | description: |
| 2090 | "The apps, people (and, for a site admin, spawned forges) you can see, ranked by forks by people, pulls merged this month, hours in use and how lately they shipped, each with its numbers. `query` is one line: `kind:app|person|forge`, `sort:score|forks|ships|use|fresh|name`, and words to match. The same rows as /explore and /api/v1/explore.", | |
| 2086 | "The apps, people (and for a site admin, spawned forges) you can see, ranked.", | |
| 2091 | 2087 | inputSchema: { |
| 2092 | 2088 | type: "object", |
| 2093 | properties: { query: { type: "string", maxLength: 200 } }, | |
| 2089 | properties: { | |
| 2090 | query: { | |
outdated
The |
||
| 2091 | type: "string", | |
| 2092 | maxLength: 200, | |
| 2093 | description: "Words, plus kind:app|person|forge and sort:score|forks|ships|use|fresh|name.", | |
| 2094 | }, | |
| 2095 | }, | |
| 2094 | 2096 | }, |
| 2095 | 2097 | writes: false, |
| 2096 | 2098 | async run(ctx, user, args) { |
| @@ -2104,7 +2106,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2104 | 2106 | { |
| 2105 | 2107 | name: "search", |
| 2106 | 2108 | description: |
| 2107 | "Find issues, pulls, repositories and code you can read, the same results as /search; `q` is the words, `kind` narrows it, `repo` (owner/name) keeps to one repository.", | |
| 2109 | "Find issues, pulls, repositories and code you can read, as /search; `kind` narrows, `repo` keeps to one repository.", | |
| 2108 | 2110 | inputSchema: { |
| 2109 | 2111 | type: "object", |
| 2110 | 2112 | properties: { |
| @@ -2141,7 +2143,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2141 | 2143 | { |
| 2142 | 2144 | name: "feed", |
| 2143 | 2145 | description: |
| 2144 | "Your home timeline, as the forge's home shows it: tells, replies, runs, changes and looks across the apps you can see, ranked, each with who did it, what it says, why it is there and a link to its thing. Pass `cursor` (next_cursor from the last answer) for the next page. The same items as GET /api/v1/feed. Your saved filter (kinds and people you switched off, GET /api/v1/feed/chips) applies (see `feed_chips` to switch one); `hide_kinds` and `hide_people` replace it for one call.", | |
| 2146 | "Your home timeline across the apps you can see, ranked. `cursor` pages. Your saved filter (`feed_chips`) applies.", | |
| 2145 | 2147 | inputSchema: { |
| 2146 | 2148 | type: "object", |
| 2147 | 2149 | properties: { |
| @@ -2174,7 +2176,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2174 | 2176 | { |
| 2175 | 2177 | name: "feed_chips", |
| 2176 | 2178 | description: |
| 2177 | "The feed chips you switched off, which `feed` and `asks` leave out: `{kinds, people}` as GET /api/v1/feed/chips. With `kind` (`kind` or `person`), `value` (a chip: reviews, decisions, crew or releases; or a username) and `hidden` it switches one off (true) or back on (false), and answers the new set. Yours alone.", | |
| 2179 | "The feed chips you switched off, which `feed` and `asks` leave out. With `kind`, `value` and `hidden` it switches one off or on.", | |
| 2178 | 2180 | inputSchema: { |
| 2179 | 2181 | type: "object", |
| 2180 | 2182 | properties: { |
| @@ -2211,12 +2213,12 @@ const TOOLS: readonly Tool[] = [ | ||
| 2211 | 2213 | { |
| 2212 | 2214 | name: "make_it_yours", |
| 2213 | 2215 | description: |
| 2214 | "Make an app you can see yours: a fork of its code and history into your account, registered as your own app (its first deploy starts at once), following the original. Its data, variables, secrets, crew and domains are not copied. Plans unless dryRun is false; the answer names the copy, its address, and what is and is not copied. Someone who already has a copy is landed on it. The copy is private unless `visibility` is public (only for a public original); at most 5 copies an account a day.", | |
| 2216 | "Make an app you can see yours: a fork of its code and history, following the original; data, secrets, crew and domains are not copied.", | |
| 2215 | 2217 | inputSchema: { |
| 2216 | 2218 | type: "object", |
| 2217 | 2219 | properties: { |
| 2218 | 2220 | repo: REPO_ARG, |
| 2219 | name: { type: "string", description: "A name for your copy; the original's by default." }, | |
| 2221 | name: { type: "string" }, | |
| 2220 | 2222 | visibility: { type: "string", enum: ["private", "public"], description: "Who sees the copy: private by default; public only when the original is." }, |
| 2221 | 2223 | dryRun: { type: "boolean" }, |
| 2222 | 2224 | }, |
| @@ -2261,7 +2263,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2261 | 2263 | { |
| 2262 | 2264 | name: "front_page", |
| 2263 | 2265 | description: |
| 2264 | "The forge's front page as a stranger is shown it: the featured repository with the crew at work on it, then the public apps ranked by forks, ships, use and freshness, each with its numbers. A site admin also gets the picks; with `pinned` (in order; the first public one is featured) and/or `hidden`, each a list of owner/name, and dryRun false, sets them. The same data as /front and /api/v1/front.", | |
| 2266 | "The forge's front page as a stranger sees it. A site admin also gets the picks, and with `pinned`, `hidden`, `weights` sets them.", | |
| 2265 | 2267 | inputSchema: { |
| 2266 | 2268 | type: "object", |
| 2267 | 2269 | properties: { |
| @@ -2269,7 +2271,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2269 | 2271 | hidden: { type: "array", items: { type: "string" } }, |
| 2270 | 2272 | weights: { |
| 2271 | 2273 | type: "object", |
| 2272 | description: "What counts toward an app's place, each 0 to 10: forks, ships, use, fresh. One left out keeps what is set.", | |
| 2274 | description: "Each 0 to 10: forks, ships, use, fresh.", | |
| 2273 | 2275 | properties: { |
| 2274 | 2276 | forks: { type: "number", minimum: 0, maximum: 10 }, |
| 2275 | 2277 | ships: { type: "number", minimum: 0, maximum: 10 }, |
| @@ -2307,7 +2309,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2307 | 2309 | { |
| 2308 | 2310 | name: "map", |
| 2309 | 2311 | description: |
| 2310 | "An app's plan: its open issues as ready now, or waiting on others (with what each waits for), the critical path, every edge, where the crew is, and under `parkedTwice` each issue the crew parked twice and handed to the planner, with every reason. The same data as the map page and /api/v1/map.", | |
| 2312 | "An app's plan: open issues as ready or waiting, the critical path, every edge, where the crew is, and `parkedTwice` issues.", | |
| 2311 | 2313 | inputSchema: { |
| 2312 | 2314 | type: "object", |
| 2313 | 2315 | properties: { app: APP_ARG }, |
| @@ -2340,7 +2342,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2340 | 2342 | { |
| 2341 | 2343 | name: "milestones", |
| 2342 | 2344 | description: |
| 2343 | "How far each thing asked for is, read from the dependency tree. With `app` alone: the plan's top-level limbs (the pillars and Later, or the plan's roots), each with done and total, open issues beneath it, how many are building, pulls merged in the last 7 days, its last movement, spend when you may see it, and `trim` when it is a candidate to trim. With `issue`: that milestone opened — who is building what, what is blocked and on what, what merged, an estimate from this week's pace, its own limbs and its leaves. The same numbers as the milestones pages.", | |
| 2345 | "How far each thing asked for is. With `app`: the plan's top-level limbs with done/total, building and recent merges.", | |
| 2344 | 2346 | inputSchema: { |
| 2345 | 2347 | type: "object", |
| 2346 | 2348 | properties: { |
| @@ -2376,7 +2378,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2376 | 2378 | { |
| 2377 | 2379 | name: "watch", |
| 2378 | 2380 | description: |
| 2379 | "Watch a milestone, or stop (`watch: false`): its progress then shows as one item in your feed, and your phone is told once, when it lands. Yours alone; `milestones` with `issue` says whether you watch it.", | |
| 2381 | "Watch a milestone, or stop (`watch: false`): its progress shows as one feed item, and your phone is told once, when it lands.", | |
| 2380 | 2382 | inputSchema: { |
| 2381 | 2383 | type: "object", |
| 2382 | 2384 | properties: { |
| @@ -2415,7 +2417,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2415 | 2417 | { |
| 2416 | 2418 | name: "runs", |
| 2417 | 2419 | description: |
| 2418 | "A repository's recent runs, newest first: id, kind, state, the issue or pull number, started and ended; the same rules as the runs page.", | |
| 2420 | "A repository's runs, newest first: id, kind, state, the issue or pull number, started and ended.", | |
| 2419 | 2421 | inputSchema: { |
| 2420 | 2422 | type: "object", |
| 2421 | 2423 | properties: { |
| @@ -2459,12 +2461,11 @@ const TOOLS: readonly Tool[] = [ | ||
| 2459 | 2461 | type: "object", |
| 2460 | 2462 | properties: { |
| 2461 | 2463 | repo: { ...REPO_ARG, maxLength: 140 }, |
| 2462 | run: { type: "string", maxLength: 80, description: "The run's id." }, | |
| 2464 | run: { type: "string", maxLength: 80 }, | |
| 2463 | 2465 | tail: { |
| 2464 | 2466 | type: "integer", |
| 2465 | 2467 | minimum: 1, |
| 2466 | 2468 | maximum: 2000, |
| 2467 | description: "Only the last this many lines.", | |
| 2468 | 2469 | }, |
| 2469 | 2470 | }, |
| 2470 | 2471 | required: ["repo", "run"], |
| @@ -2501,7 +2502,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2501 | 2502 | { |
| 2502 | 2503 | name: "limb", |
| 2503 | 2504 | description: |
| 2504 | "One action on a whole limb of the plan — a milestone and everything beneath it that only it needs. `defer` moves the limb behind the Later gate, so the crew picks none of it; `resume` brings it back, under `pillar` when you name one; `close` closes every open issue of the limb as not doing, with `reason` said on each; `raise` and `lower` move it one place among its siblings — `under` says whose limbs when it is a limb of several, or `top` — and the crew takes ready work from the higher limb first; the dry run answers with the new `order` and the top of the ready list `before` and `after`. `replan` hands the limb to the planner to re-plan as fewer, larger pieces: the dry run lists every open issue it will be given, applying owes the planner's next pass once and holds the limb from the crew until it answers, and `takeback` withdraws it before the planner starts. `undo` reverses an `operation` by its id. A dry run unless `dryRun` is false: it answers with every row before and after, what the action takes along (`taken`) and what stays because something outside the limb still waits for it (`kept`), and changes nothing. Applying and undoing take an admin token of a person who administers the app, and answer with the receipt.", | |
| 2505 | "One action on a plan limb: `defer`, `resume` (`pillar`), `close` (`reason`), `raise`/`lower` (`under`), `replan`, `takeback`, `undo`.", | |
| 2505 | 2506 | inputSchema: { |
| 2506 | 2507 | type: "object", |
| 2507 | 2508 | properties: { |
| @@ -2509,7 +2510,6 @@ const TOOLS: readonly Tool[] = [ | ||
| 2509 | 2510 | issue: { |
| 2510 | 2511 | type: "integer", |
| 2511 | 2512 | minimum: 1, |
| 2512 | description: "The limb: the number of its issue.", | |
| 2513 | 2513 | }, |
| 2514 | 2514 | action: { type: "string", enum: ["defer", "resume", "close", "raise", "lower", "replan", "takeback", "undo"] }, |
| 2515 | 2515 | reason: { |
| @@ -2519,16 +2519,15 @@ const TOOLS: readonly Tool[] = [ | ||
| 2519 | 2519 | pillar: { |
| 2520 | 2520 | type: "string", |
| 2521 | 2521 | description: |
| 2522 | "For resume: the pillar to put it back under, by name or short form; under none when absent.", | |
| 2522 | "For resume: the pillar to return it under.", | |
| 2523 | 2523 | }, |
| 2524 | 2524 | under: { |
| 2525 | 2525 | type: "string", |
| 2526 | 2526 | description: |
| 2527 | "For raise and lower: among whose limbs — `top`, or the number of the milestone; needed only when the limb sits in several places.", | |
| 2527 | "For raise and lower: `top` or a milestone number; only when the limb sits in several places.", | |
| 2528 | 2528 | }, |
| 2529 | 2529 | operation: { |
| 2530 | 2530 | type: "string", |
| 2531 | description: "For undo: the id of the operation to reverse.", | |
| 2532 | 2531 | }, |
| 2533 | 2532 | dryRun: { |
| 2534 | 2533 | type: "boolean", |
| @@ -2596,11 +2595,11 @@ const TOOLS: readonly Tool[] = [ | ||
| 2596 | 2595 | { |
| 2597 | 2596 | name: "returns", |
| 2598 | 2597 | description: |
| 2599 | "What the crew's tokens bought over the last days (1–90, default 7), so you can ask before you plan: dollars and runs by state; changes landed; dollars on work that landed and the median per landed change; dollars that landed nothing (closed without a merge, or still open), with failed and cancelled runs and the costliest failure reasons; what a good run costs and takes; and the costliest issues with what came of each; and the same dollars `byPillar`, by the pillar each run's issue serves. Over every app whose owner facts you hold, or `repo` (owner/name). Read-only; an app you cannot write is not found.", | |
| 2598 | "What the crew's tokens bought over the last days (1–90, default 7): dollars, runs by state, changes landed, cost per landed change.", | |
| 2600 | 2599 | inputSchema: { |
| 2601 | 2600 | type: "object", |
| 2602 | 2601 | properties: { |
| 2603 | repo: { type: "string", description: "One app, as owner/name; every app of yours when absent." }, | |
| 2602 | repo: { type: "string" }, | |
| 2604 | 2603 | days: { type: "integer", minimum: 1, maximum: 90 }, |
| 2605 | 2604 | }, |
| 2606 | 2605 | }, |
| @@ -2621,7 +2620,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2621 | 2620 | { |
| 2622 | 2621 | name: "review", |
| 2623 | 2622 | description: |
| 2624 | "Approve a pull, or request changes with a reason (`reason` is required for changes); approving your own pull, or a verdict without write access, is refused as on the page.", | |
| 2623 | "Approve a pull, or request changes with a `reason` (required for changes); approving your own pull is refused as on the page.", | |
| 2625 | 2624 | inputSchema: { |
| 2626 | 2625 | type: "object", |
| 2627 | 2626 | properties: { |
| @@ -2677,7 +2676,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2677 | 2676 | { |
| 2678 | 2677 | name: "spend", |
| 2679 | 2678 | description: |
| 2680 | "Report what your session spent on an app's work, when the forge did not run it (a build team, your own assistant): who you are, the model, tokens in and out and/or dollars, and the issue or pull number it was for. Writers and the app's crew only. It is shown beside the crew's own spend in `returns`, never added to it, and no limit reads it.", | |
| 2679 | "Report what your session spent on an app's work the forge did not run: who, model, tokens and/or dollars, the issue or pull.", | |
| 2681 | 2680 | inputSchema: { |
| 2682 | 2681 | type: "object", |
| 2683 | 2682 | properties: { |
| @@ -2706,7 +2705,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2706 | 2705 | { |
| 2707 | 2706 | name: "usage", |
| 2708 | 2707 | description: |
| 2709 | "What the crew's runs on an app spent over the last days (1–90, default 7): tokens in and out, cost in US dollars, wall time and outcome, by subscription, agent and issue, with the newest runs, and each account's standing in its 5-hour and weekly windows (cap, used, remaining, resets at — each figure read from the provider or inferred from the ledger). The same data as /api/v1/repos/:owner/:name/usage; refused unless you can write the app.", | |
| 2708 | "What the crew's runs on an app spent over the last days (1–90, default 7): tokens, dollars, time, by agent and issue. Writers only.", | |
| 2710 | 2709 | inputSchema: { |
| 2711 | 2710 | type: "object", |
| 2712 | 2711 | properties: { |
| @@ -2737,7 +2736,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2737 | 2736 | { |
| 2738 | 2737 | name: "labels", |
| 2739 | 2738 | description: |
| 2740 | "Set the labels of an issue or a pull to exactly these (none clears them): only labels the repository has, by a person or agent who can write it.", | |
| 2739 | "Set the labels of an issue or a pull to exactly these (none clears them): only labels the repository has; writers only.", | |
| 2741 | 2740 | inputSchema: { |
| 2742 | 2741 | type: "object", |
| 2743 | 2742 | properties: { |
| @@ -2833,21 +2832,20 @@ const TOOLS: readonly Tool[] = [ | ||
| 2833 | 2832 | { |
| 2834 | 2833 | name: "attach_look", |
| 2835 | 2834 | description: |
| 2836 | "Attach a look to an open pull: a PNG or JPEG (at most 5 MB, base64 in `image`) taken at a stated width, `phone` or `desktop`, with one line in `says` about what it shows. It appears beside the crew's own looks on the change's page, for people who write the app. For a person who can write the app: an agent is refused, since the crew's looks come from the preview. It plans first — `dryRun` is true unless you say false — and checks the picture.", | |
| 2835 | "Attach a look to an open pull: a PNG or JPEG (at most 5 MB, base64 in `image`) at `phone` or `desktop` width, with one line in `says`.", | |
| 2837 | 2836 | inputSchema: { |
| 2838 | 2837 | type: "object", |
| 2839 | 2838 | properties: { |
| 2840 | 2839 | app: APP_ARG, |
| 2841 | pull: { type: "integer", description: "The pull's number." }, | |
| 2840 | pull: { type: "integer" }, | |
| 2842 | 2841 | width: { type: "string", enum: ["phone", "desktop"] }, |
| 2843 | 2842 | says: { |
| 2844 | 2843 | type: "string", |
| 2845 | description: "One line, at most 200 characters, saying what it shows.", | |
| 2846 | 2844 | }, |
| 2847 | image: { type: "string", description: "The PNG or JPEG, base64." }, | |
| 2845 | image: { type: "string" }, | |
| 2848 | 2846 | dryRun: { |
| 2849 | 2847 | type: "boolean", |
| 2850 | description: "Check only (the default). Send false to attach it.", | |
| 2848 | description: "false to apply (default plans).", | |
| 2851 | 2849 | }, |
| 2852 | 2850 | }, |
| 2853 | 2851 | required: ["app", "pull", "width", "says", "image"], |
| @@ -2888,23 +2886,22 @@ const TOOLS: readonly Tool[] = [ | ||
| 2888 | 2886 | { |
| 2889 | 2887 | name: "hold", |
| 2890 | 2888 | description: |
| 2891 | "Hold a pull, or let it go. While a pull is held, no rule sends it to the crew's reviewer and no rule lands it; a person may still merge it themselves. For a person who can write the app: an agent is refused. Give `why` in one line to hold; `release: true` lets it go. It plans first — `dryRun` is true unless you say false — and says what would happen.", | |
| 2889 | "Hold a pull, or let it go: while held, nothing reviews or lands it. App writers only. `why` holds; `release: true` lets go.", | |
| 2892 | 2890 | inputSchema: { |
| 2893 | 2891 | type: "object", |
| 2894 | 2892 | properties: { |
| 2895 | 2893 | app: APP_ARG, |
| 2896 | pull: { type: "integer", description: "The pull's number." }, | |
| 2894 | pull: { type: "integer" }, | |
| 2897 | 2895 | why: { |
| 2898 | 2896 | type: "string", |
| 2899 | 2897 | description: "To hold: why, in one line. It shows on the pull.", |
| 2900 | 2898 | }, |
| 2901 | 2899 | release: { |
| 2902 | 2900 | type: "boolean", |
| 2903 | description: "True to let the pull go instead of holding it.", | |
| 2904 | 2901 | }, |
| 2905 | 2902 | dryRun: { |
| 2906 | 2903 | type: "boolean", |
| 2907 | description: "Plan only (the default). Send false to do it.", | |
| 2904 | description: "false to apply (default plans).", | |
| 2908 | 2905 | }, |
| 2909 | 2906 | }, |
| 2910 | 2907 | required: ["app", "pull"], |
| @@ -2963,7 +2960,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 2963 | 2960 | }, |
| 2964 | 2961 | { |
| 2965 | 2962 | name: "plan", |
| 2966 | description: `Open several small issues and link them in one call: all of them or none. Each item has a key, a title, a body and optionally blockedBy, a list of keys of this plan or numbers of issues already open. Where the app has pillars (umbrella issues titled like "P0 Trust: …"), each item also says what it is for: pillar, the one it serves, or later: true with why, one sentence, which puts it behind the Later gate instead of on the ready list; an item that says neither is refused. A bad link (loop, unknown key) refuses the whole plan and names the item. At most ${PLAN_MAX} issues. Answers with the numbers opened, by key, what is ready now, and what waits for later. Where the app has "plans need a nod" on, the same checks run and one proposal is stored (optional title and why for the whole plan); nothing is opened, and it answers status proposed with the proposal id.`, | |
| 2963 | description: `Open and link several small issues, all or none (max ${PLAN_MAX}). Items: key, title, body, optional blockedBy; with pillars, \`pillar\` or \`later: true\` with \`why\`.`, | |
| 2967 | 2964 | inputSchema: { |
| 2968 | 2965 | type: "object", |
| 2969 | 2966 | properties: { |
| @@ -2979,14 +2976,12 @@ const TOOLS: readonly Tool[] = [ | ||
| 2979 | 2976 | properties: { |
| 2980 | 2977 | key: { |
| 2981 | 2978 | type: "string", |
| 2982 | description: "A short name for this item, unique in the plan.", | |
| 2983 | 2979 | }, |
| 2984 | 2980 | title: { type: "string" }, |
| 2985 | 2981 | body: { type: "string" }, |
| 2986 | 2982 | blockedBy: { |
| 2987 | 2983 | type: "array", |
| 2988 | 2984 | items: { type: ["string", "integer"] }, |
| 2989 | description: "Keys in this plan, or numbers of open issues.", | |
| 2990 | 2985 | }, |
| 2991 | 2986 | pillar: { |
| 2992 | 2987 | type: "string", |
| @@ -2996,12 +2991,12 @@ const TOOLS: readonly Tool[] = [ | ||
| 2996 | 2991 | later: { |
| 2997 | 2992 | type: "boolean", |
| 2998 | 2993 | description: |
| 2999 | "True when this serves no pillar now: it opens behind the Later gate, off the ready list. Needs why.", | |
| 2994 | "True when no pillar: opens behind the Later gate. Needs why.", | |
| 3000 | 2995 | }, |
| 3001 | 2996 | why: { |
| 3002 | 2997 | type: "string", |
| 3003 | 2998 | description: |
| 3004 | "With later: one sentence saying why it waits. It opens the issue's body.", | |
| 2999 | "With later: one sentence why.", | |
| 3005 | 3000 | }, |
| 3006 | 3001 | }, |
| 3007 | 3002 | required: ["key", "title"], |
| @@ -3046,13 +3041,13 @@ const TOOLS: readonly Tool[] = [ | ||
| 3046 | 3041 | { |
| 3047 | 3042 | name: "access", |
| 3048 | 3043 | description: |
| 3049 | "What outside agents tried: the forge's own request log, redacted, for a forge admin. Non-browser requests to public paths (/issues, /api/, /mcp, /.well-known/, /llms.txt, sign-in, sign-up), newest first, at most 500: time, host, method, path without query, status, duration in ms, user agent, whether a credential came with it, and a daily hash of the caller (never an address). `since` is an ISO time (the last day when absent; at most 14 days back), `ua` a part of the user agent, `path` the start of the path. Needs a write-scope token. The same as Settings → Access.", | |
| 3044 | "What outside agents tried: the redacted log of non-browser requests to public paths, newest first, at most 500, for a forge admin. Needs a write-scope token.", | |
| 3050 | 3045 | inputSchema: { |
| 3051 | 3046 | type: "object", |
| 3052 | 3047 | properties: { |
| 3053 | since: { type: "string", maxLength: 40 }, | |
| 3054 | ua: { type: "string", maxLength: 100 }, | |
| 3055 | path: { type: "string", maxLength: 200 }, | |
| 3048 | since: { type: "string", maxLength: 40, description: "ISO time; default the last day, at most 14 days back." }, | |
| 3049 | ua: { type: "string", maxLength: 100, description: "Part of the user agent." }, | |
outdated
|
||
| 3050 | path: { type: "string", maxLength: 200, description: "Path prefix." }, | |
| 3056 | 3051 | }, |
| 3057 | 3052 | }, |
| 3058 | 3053 | writes: true, |
| @@ -3074,7 +3069,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3074 | 3069 | { |
| 3075 | 3070 | name: "capacity", |
| 3076 | 3071 | description: |
| 3077 | "The forge's capacity and guards, live: crew runs at once (crew.concurrency), checks at once (checks.concurrency), test shards per check (checks.shards), whether a cell's undeclared egress is refused or reported (cells.egress), and whether new accounts may create repositories and forks (guard_new_accounts: on refuses accounts younger than 7 days, off is the default), and whether the platform edge's sign-in is read (signin.edge: on is the default, off ignores the edge's identity on every door). Without `key`, each setting, its value and where it comes from, and the recent changes. With `key` and `value` (\"\" goes back to the boot default), the change it would make; with dryRun false, made, by a forge admin with an admin-scope token, never an agent. A change is honoured by the next run, check or cell, never by a restart. The same as Settings → Capacity and /api/v1/admin/capacity.", | |
| 3072 | "The forge's capacity and guards, live. With `key` and `value` (\"\" restores default), plans; dryRun false makes it.", | |
| 3078 | 3073 | inputSchema: { |
| 3079 | 3074 | type: "object", |
| 3080 | 3075 | properties: { |
| @@ -3083,7 +3078,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3083 | 3078 | enum: ["crew.concurrency", "checks.concurrency", "checks.shards", "cells.egress", "guard_new_accounts", "signin.edge"], |
| 3084 | 3079 | }, |
| 3085 | 3080 | value: { type: "string", maxLength: 16 }, |
| 3086 | dryRun: { type: "boolean", description: "Plan only (the default). Send false to change it." }, | |
| 3081 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 3087 | 3082 | }, |
| 3088 | 3083 | }, |
| 3089 | 3084 | writes: true, |
| @@ -3113,7 +3108,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3113 | 3108 | { |
| 3114 | 3109 | name: "scorecard", |
| 3115 | 3110 | description: |
| 3116 | "The crew's scorecard, for a forge admin: per agent (username), its figures over all time (`all`) and the last 30 days (`recent`) — pulls opened and landed, mean review rounds, red check rate, reverts, mean ms to land (a ratio over nothing is null) — and `splits` by model and prompt. Read-only. The same as /settings/crew and /api/v1/crew/scorecard.", | |
| 3111 | "Crew scorecard (forge admin): per agent, all time and 30 days (landed, review rounds, red rate, reverts), by model and prompt.", | |
| 3117 | 3112 | inputSchema: { type: "object", properties: {} }, |
| 3118 | 3113 | writes: false, |
| 3119 | 3114 | adminOnly: true, |
| @@ -3125,7 +3120,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3125 | 3120 | { |
| 3126 | 3121 | name: "flow", |
| 3127 | 3122 | description: |
| 3128 | "The forge's pipeline, for a forge admin: ready, building, check queue, checking, review, approved, landed and live, each with how many wait, how many are being worked, when the oldest waiting one entered (oldestWaiting) and what limits it (capacity), and one sentence (says) naming the stage whose oldest has waited longest — the bottleneck. Beside it, throughput: per stage how many left it in the last hour (lastHour), in 24 hours (day) and in each of the last 24 hours (hours, 0 the last), the median ask→live in ms over the last 24 hours, and its own sentence (says). Also `conflicted`: pulls main moved under that the crew is bringing up to date, oldest first, with a sentence (says). And `requested`: open crew pulls whose reviewer last asked for changes, split into those waiting on a builder (builder: the verdict is on the head) and on a reviewer (reviewer: a push came since), each oldest first with a sentence (says). And the merge queue (queue): per repository (repo), the pulls queued for the next candidate (queued), the pulls in the candidate at the gate (gating) and the pulls landed in the last hour (landedHour). Read-only; the knobs are the `capacity` tool. The same as the flow panel on /boards.", | |
| 3123 | "The forge's pipeline for a forge admin: per stage counts, `oldestWaiting`, `capacity`, `says` naming the bottleneck, merge `queue`.", | |
| 3129 | 3124 | inputSchema: { type: "object", properties: {} }, |
| 3130 | 3125 | writes: false, |
| 3131 | 3126 | run(ctx, user) { |
| @@ -3139,7 +3134,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3139 | 3134 | { |
| 3140 | 3135 | name: "system", |
| 3141 | 3136 | description: |
| 3142 | "The machine the forge runs on, now: per-core CPU, temperatures, load, memory, disk per mount, network, and every process grouped by what launched it (which check, which app), each group with CPU cores, memory and age; and the test files running in more than one check at once (duplicates). For a forge admin, or an agent granted telemetry:read — whose view cuts out private repositories' checks and apps. Read-only; the same as /system and /api/v1/telemetry/system.", | |
| 3137 | "The machine the forge runs on, now: CPU, load, memory, disk, network, processes. Forge admin or telemetry:read.", | |
| 3143 | 3138 | inputSchema: { type: "object", properties: {} }, |
| 3144 | 3139 | writes: false, |
| 3145 | 3140 | run(ctx, user) { |
| @@ -3153,7 +3148,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3153 | 3148 | { |
| 3154 | 3149 | name: "contribute", |
| 3155 | 3150 | description: |
| 3156 | "For any agent that arrives: what this forge is, where its bottleneck is right now, the open issues in public repositories labelled “help wanted” that welcome outside work, and how to propose (fork, pull, the gate it must pass). Open to every caller.", | |
| 3151 | "For any agent that arrives: what this forge is, its bottleneck, open “help wanted” issues in public repositories, and how to propose.", | |
| 3157 | 3152 | inputSchema: { type: "object", properties: {} }, |
| 3158 | 3153 | writes: false, |
| 3159 | 3154 | run: (ctx) => good(welcome(ctx.app)), |
| @@ -3161,13 +3156,13 @@ const TOOLS: readonly Tool[] = [ | ||
| 3161 | 3156 | { |
| 3162 | 3157 | name: "limits", |
| 3163 | 3158 | description: |
| 3164 | "Every limit the forge's owner can hit, with its value, bounds and the reason it is there (people per app, pins, hidden repositories, budget ceilings, daily shares, sign-in attempts, crew and check concurrency). Without `key`, each limit and the recent changes. With `key` and `value` (\"\" goes back to the default), the change it would make; with dryRun false, made, by a forge admin with an admin-scope token, never an agent. Honoured by the next use, never by a restart. The same as /admin/limits and /api/v1/admin/limits.", | |
| 3159 | "Every limit the forge's owner can hit, with value, bounds and reason. With `key` and `value` (\"\" restores default), plans.", | |
| 3165 | 3160 | inputSchema: { |
| 3166 | 3161 | type: "object", |
| 3167 | 3162 | properties: { |
| 3168 | 3163 | key: { type: "string", enum: [...LIMIT_KEYS] }, |
| 3169 | 3164 | value: { type: "string", maxLength: 16 }, |
| 3170 | dryRun: { type: "boolean", description: "Plan only (the default). Send false to change it." }, | |
| 3165 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 3171 | 3166 | }, |
| 3172 | 3167 | }, |
| 3173 | 3168 | writes: true, |
| @@ -3197,7 +3192,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3197 | 3192 | { |
| 3198 | 3193 | name: "teams", |
| 3199 | 3194 | description: |
| 3200 | "Crew teams, for a forge admin who is a person: each team's charter, dial (its share, a whole number from 0 to 100; all dials together may not pass 100) and member agents. Without `slug`, the teams. With `slug` and any of `charter`, `dial`, `members` (agent ids; replaces the list), the change it would make; with dryRun false, made, with an admin-scope token. Nothing reads the dials yet. The same as /settings/teams and /api/v1/admin/teams.", | |
| 3195 | "Crew teams for person admins: charter, dial (0–100) and member agents.", | |
| 3201 | 3196 | inputSchema: { |
| 3202 | 3197 | type: "object", |
| 3203 | 3198 | properties: { |
| @@ -3205,7 +3200,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3205 | 3200 | charter: { type: "string", maxLength: 2000 }, |
| 3206 | 3201 | dial: { type: "integer", minimum: 0, maximum: 100 }, |
| 3207 | 3202 | members: { type: "array", maxItems: 500, items: { type: "string", maxLength: 64 } }, |
| 3208 | dryRun: { type: "boolean", description: "Plan only (the default). Send false to change it." }, | |
| 3203 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 3209 | 3204 | }, |
| 3210 | 3205 | }, |
| 3211 | 3206 | writes: true, |
| @@ -3247,7 +3242,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3247 | 3242 | { |
| 3248 | 3243 | name: "cloud_accounts", |
| 3249 | 3244 | description: |
| 3250 | "Cloud accounts, for a forge admin: each attached provider token (github, cloudflare, vercel) with what it is used for, when it was verified and last used, whether runs may write with it, and today's vendor calls (UTC day) — then every audit row of today, newest first. Never a token value. Needs a write-scope token. The same as Settings → Cloud accounts.", | |
| 3245 | "Cloud accounts for a forge admin: each attached provider token with use, verification, write permission and today's vendor calls.", | |
| 3251 | 3246 | inputSchema: { type: "object", properties: {} }, |
| 3252 | 3247 | // A read, but marked a write so a `ro` token never lists the accounts; it |
| 3253 | 3248 | // is a forge admin's and the audit rows name every vendor call. |
| @@ -3263,7 +3258,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3263 | 3258 | { |
| 3264 | 3259 | name: "cloud_account", |
| 3265 | 3260 | description: |
| 3266 | "Change a cloud account, as a forge admin with an admin-scope token. `verb` is attach (checks `value` with the provider's read-only calls, then seals it), verify (re-checks the sealed token), rotate (checks a new `value`, then replaces it), rename (to `to`; refused when the account or the secret CLOUD_<PROVIDER>_<NAME> already exists), remove, allow_writes (`allowed` true or false: whether a run that declares writes may hold the token) or audit (a read-only vendor audit, one issue per finding in `repo`). `provider` is github, cloudflare or vercel; `name` is lowercase letters, digits and underscores. The answer never contains the token. The same as Settings → Cloud accounts.", | |
| 3261 | "Change a cloud account (forge admin, admin-scope token). `verb`: attach, verify, rotate, rename, remove, allow_writes or audit.", | |
| 3267 | 3262 | inputSchema: { |
| 3268 | 3263 | type: "object", |
| 3269 | 3264 | properties: { |
| @@ -3271,7 +3266,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3271 | 3266 | provider: { type: "string", enum: [...PROVIDERS] }, |
| 3272 | 3267 | name: { type: "string", maxLength: 32 }, |
| 3273 | 3268 | value: { type: "string", maxLength: 4096, description: "The token, for attach and rotate." }, |
| 3274 | to: { type: "string", maxLength: 32, description: "The new name, for rename." }, | |
| 3269 | to: { type: "string", maxLength: 32 }, | |
| 3275 | 3270 | allowed: { type: "boolean", description: "For allow_writes: whether runs that declare writes may hold the token." }, |
| 3276 | 3271 | repo: { type: "string", maxLength: 200, description: "For audit: owner/name, where the findings are filed as issues." }, |
| 3277 | 3272 | }, |
| @@ -3318,13 +3313,13 @@ const TOOLS: readonly Tool[] = [ | ||
| 3318 | 3313 | { |
| 3319 | 3314 | name: "quotas", |
| 3320 | 3315 | description: |
| 3321 | "What one account may own: per-account quotas for repositories and apps, each a positive whole number or no limit (null, the default). Nothing enforces them yet. Without `key`, each quota and the recent changes. With `key` and `value` (\"\" clears it), the change it would make; with dryRun false, made, by a forge admin with an admin-scope token, never an agent. The same as /admin/limits (Account quotas) and /api/v1/admin/settings.", | |
| 3316 | "Per-account repository and app quotas (number or null; not enforced yet). With `key` and `value` (\"\" clears), plans; dryRun false makes it.", | |
| 3322 | 3317 | inputSchema: { |
| 3323 | 3318 | type: "object", |
| 3324 | 3319 | properties: { |
| 3325 | 3320 | key: { type: "string", enum: [...QUOTA_KEYS] }, |
| 3326 | 3321 | value: { type: "string", maxLength: 16 }, |
| 3327 | dryRun: { type: "boolean", description: "Plan only (the default). Send false to change it." }, | |
| 3322 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 3328 | 3323 | }, |
| 3329 | 3324 | }, |
| 3330 | 3325 | writes: true, |
| @@ -3356,12 +3351,12 @@ const TOOLS: readonly Tool[] = [ | ||
| 3356 | 3351 | { |
| 3357 | 3352 | name: "signup", |
| 3358 | 3353 | description: |
| 3359 | "Who may sign up: open, invite or closed (default open). open lets anyone make an account; invite lets in only a person with an open invite link (mint_invite), spent with the account; closed refuses every new account. The forge's first account is never refused. Without `mode`, the setting and the recent changes. With `mode`, the change it would make; with dryRun false, made, by a forge admin with an admin-scope token, never an agent. The same as /admin/limits (Signup) and PATCH /api/v1/admin/settings.", | |
| 3354 | "Who may sign up: open (default); invite lets in only a person with an open invite link; closed refuses every new account. With `mode`, plans.", | |
| 3360 | 3355 | inputSchema: { |
| 3361 | 3356 | type: "object", |
| 3362 | 3357 | properties: { |
| 3363 | 3358 | mode: { type: "string", enum: [...SIGNUP_MODES] }, |
| 3364 | dryRun: { type: "boolean", description: "Plan only (the default). Send false to change it." }, | |
| 3359 | dryRun: { type: "boolean", description: "false to apply (default plans)." }, | |
| 3365 | 3360 | }, |
| 3366 | 3361 | }, |
| 3367 | 3362 | writes: true, |
| @@ -3389,14 +3384,14 @@ const TOOLS: readonly Tool[] = [ | ||
| 3389 | 3384 | { |
| 3390 | 3385 | name: "boards", |
| 3391 | 3386 | description: |
| 3392 | "The forge's boards, for a forge admin who is a person (an agent is refused): each board's slug, title, range, refresh and panels. The same as /boards and GET /api/v1/boards. With `slug`, that one board. With `slug` and `panel` (its place from 0), that panel's data: {from, to, step, rows}, bucketed to `step` (300, 5m, 1h, 1d) or one the panel picks, never cut short; `from` and `to` (dates) stand in for the panel's. The same as GET /api/v1/boards/:slug/panels/:id.", | |
| 3387 | "The forge's boards for person admins: slug, title, range, refresh and panels.", | |
| 3393 | 3388 | inputSchema: { |
| 3394 | 3389 | type: "object", |
| 3395 | 3390 | properties: { |
| 3396 | slug: { type: "string", maxLength: 40, description: "One board, by slug." }, | |
| 3391 | slug: { type: "string", maxLength: 40 }, | |
| 3397 | 3392 | panel: { type: "integer", minimum: 0, maximum: 23, description: "One panel's data, by its place on the board from 0." }, |
| 3398 | from: { type: "string", maxLength: 64, description: "With panel: the window's start, a date." }, | |
| 3399 | to: { type: "string", maxLength: 64, description: "With panel: the window's end, a date." }, | |
| 3393 | from: { type: "string", maxLength: 64 }, | |
| 3394 | to: { type: "string", maxLength: 64 }, | |
| 3400 | 3395 | step: { type: "string", maxLength: 16, description: "With panel: seconds per bucket, or a count of s, m, h or d." }, |
| 3401 | 3396 | }, |
| 3402 | 3397 | }, |
| @@ -3431,12 +3426,12 @@ const TOOLS: readonly Tool[] = [ | ||
| 3431 | 3426 | { |
| 3432 | 3427 | name: "board_write", |
| 3433 | 3428 | description: |
| 3434 | "Write a whole board under a slug (a-z, 0-9 and -): made if new, replaced if there. `board` is {title, range: 1h|24h|7d|30d|all, refresh: off|10s|1m, panels: [{title, chart: line|bar|area|number|table|heat, query, w, h}]}. A panel whose query cannot run is refused. For a forge admin who is a person; an agent is refused. The same as PUT /api/v1/boards/:slug.", | |
| 3429 | "Write a whole board under a slug (made or replaced). `board` is {title, range, refresh, panels: [{title, chart, query, w, h}]}.", | |
| 3435 | 3430 | inputSchema: { |
| 3436 | 3431 | type: "object", |
| 3437 | 3432 | properties: { |
| 3438 | 3433 | slug: { type: "string", maxLength: 40 }, |
| 3439 | board: { type: "object", description: "The whole board." }, | |
| 3434 | board: { type: "object" }, | |
| 3440 | 3435 | }, |
| 3441 | 3436 | required: ["slug", "board"], |
| 3442 | 3437 | }, |
| @@ -3450,7 +3445,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3450 | 3445 | { |
| 3451 | 3446 | name: "board_pins", |
| 3452 | 3447 | description: |
| 3453 | "The panels pinned to the caller's own home, for a forge admin who is a person (an agent is refused). With no `op`, lists them in order. With `op` pin, unpin or move, changes them: `slug` and `title` name a panel of a board; `by` is -1 or 1 for move. The same as GET and POST /api/v1/boards/pins.", | |
| 3448 | "The panels pinned to the caller's home (person admins). No `op` lists them.", | |
| 3454 | 3449 | inputSchema: { |
| 3455 | 3450 | type: "object", |
| 3456 | 3451 | properties: { |
| @@ -3481,7 +3476,7 @@ const TOOLS: readonly Tool[] = [ | ||
| 3481 | 3476 | { |
| 3482 | 3477 | name: "board_delete", |
| 3483 | 3478 | description: |
| 3484 | "Delete a board by slug (the commit stays in the boards repo's history). For a forge admin who is a person; an agent is refused. The same as DELETE /api/v1/boards/:slug.", | |
| 3479 | "Delete a board by slug (history keeps it). Person admins only.", | |
| 3485 | 3480 | inputSchema: { |
| 3486 | 3481 | type: "object", |
| 3487 | 3482 | properties: { slug: { type: "string", maxLength: 40 } }, |
| @@ -3636,7 +3631,7 @@ function rpcOne( | ||
| 3636 | 3631 | const REPORT_TOOL = { |
| 3637 | 3632 | name: "report", |
| 3638 | 3633 | description: |
| 3639 | "Report a problem in a public repository, without an account. Plain text. A person who keeps the repository reads it before anything acts on it.", | |
| 3634 | "Report a problem in a public repository, without an account. Plain text; a person reads it first.", | |
| 3640 | 3635 | inputSchema: { |
| 3641 | 3636 | type: "object", |
| 3642 | 3637 | properties: { |
test/mcp-size.test.ts added +72
| @@ -0,0 +1,72 @@ | ||
| 1 | /** | |
| 2 | * Every harness session pays for `tools/list` before it does anything, so | |
| 3 | * the answer has a ceiling: past it, the longest descriptions are named so | |
| 4 | * the next cut is obvious. | |
| 5 | */ | |
| 6 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 7 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 8 | import { tmpdir } from "node:os"; | |
| 9 | import { join } from "node:path"; | |
| 10 | import { createApp, type App } from "../src/app.ts"; | |
| 11 | import { createToken } from "../src/core/tokens.ts"; | |
| 12 | import { createUser } from "../src/core/users.ts"; | |
| 13 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 14 | ||
| 15 | const CEILING = 40_000; | |
This head measures 39,986 bytes, 14 bytes under this ceiling. Trim until there is real headroom, at least 1–2 KB, so the next unrelated change on main does not go red here. |
||
| 16 | ||
| 17 | let dataDir: string; | |
| 18 | let app: App; | |
| 19 | let servers: Servers; | |
| 20 | let token: string; | |
| 21 | ||
| 22 | beforeAll(async () => { | |
| 23 | dataDir = await mkdtemp(join(tmpdir(), "forge-mcp-size-")); | |
| 24 | app = createApp({ dataDir, port: 0 }); | |
| 25 | servers = await serve(app); | |
| 26 | // The first user is a forge admin, who is listed every tool: the largest answer. | |
| 27 | const made = await createUser(app.db, { | |
| 28 | origin: "seed", | |
| 29 | trusted: true, | |
| 30 | username: "alice", | |
| 31 | email: "[email protected]", | |
| 32 | password: "a-good-passphrase", | |
| 33 | adminIfFirst: true, | |
| 34 | }); | |
| 35 | if (!made.ok) throw new Error(made.error); | |
| 36 | token = createToken(app.db, made.value.id, "rw", "rw").token; | |
| 37 | }, 30_000); | |
| 38 | ||
| 39 | afterAll(async () => { | |
| 40 | servers.stop(); | |
| 41 | app.close(); | |
| 42 | await rm(dataDir, { recursive: true, force: true }); | |
| 43 | }); | |
| 44 | ||
| 45 | test("tools/list stays under 40 KB", async () => { | |
| 46 | const res = await fetch(`${servers.url}/mcp`, { | |
| 47 | method: "POST", | |
| 48 | headers: { | |
| 49 | "content-type": "application/json", | |
| 50 | accept: "application/json, text/event-stream", | |
| 51 | authorization: `Bearer ${token}`, | |
| 52 | }, | |
| 53 | body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }), | |
| 54 | }); | |
| 55 | const text = await res.text(); | |
| 56 | const bytes = Buffer.byteLength(text); | |
| 57 | const tools = (JSON.parse(text) as any).result.tools as { | |
| 58 | name: string; | |
| 59 | description: string; | |
| 60 | }[]; | |
| 61 | const longest = tools | |
| 62 | .map((t) => `${t.name} (${Buffer.byteLength(t.description)} bytes)`) | |
| 63 | .sort( | |
| 64 | (a, b) => | |
| 65 | Number(/\((\d+) /.exec(b)![1]) - Number(/\((\d+) /.exec(a)![1]), | |
| 66 | ) | |
| 67 | .slice(0, 5); | |
| 68 | expect( | |
| 69 | bytes, | |
| 70 | `tools/list is ${bytes} bytes, over ${CEILING}; the five longest descriptions: ${longest.join(", ")}`, | |
| 71 | ).toBeLessThanOrEqual(CEILING); | |
| 72 | }); |
This dropped what a tell without
repodoes: it is kept for the forge as a whole, nothing is built, and the answer is a thought id.thoughtstill says 'fromtellwithout a repo', so the reference now points at nothing. Restore it, even as a few words.