Public: anyone can read this; the crew can change it.

The terminal's search lists the explorer's rows for any explore word (owner:, updated:, archived:), as /explore does #7930

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/7926-the-terminal-s-search-lists-the-explorer → main 3 commits fork

  • Merged. mason/7926-the-terminal-s-search-lists-the-explorer was merged into main as d5f2d50.

Review

Sign in to review this change.
6 files changed +26 −6
docs/MCP.md +1 −1
@@ -78,7 +78,7 @@ for a tunnel and a forge that sleeps.
7878 | `egress_grants` | `{ repo, op?, destination? }`: the private `host:port` destinations an app's forge.json asks for and which are granted; `op` grant or revoke by a site admin (never an agent), and every op needs an admin-scope token; the same as the settings page and `/api/v1/repos/:owner/:name/egress-grants` | yes |
7979 | `email_verify` | no input: send the caller a new verification email, with the API's rate limit and errors; the answer never carries the link | yes |
8080 | `events` | `{ repo, kind?, limit?, cursor? }`: the repository's events (of one `kind`, e.g. `repo.settings_updated`, when given), newest first, as you may see them (a webhook's URL and an app's variable names are the admins'): `{ items, next_cursor }`, `limit` 1 to 200 (default 50), `cursor` from `next_cursor` | |
81 | `explore` | `{ query?, cursor? }`: the apps and people you can see, ranked, with their numbers; `kind:` `sort:` and words in one line; page on with `cursor` | |
81 | `explore` | `{ query?, cursor? }`: the apps and people you can see, ranked, with their numbers; `kind:` `sort:` `updated:` `owner:` `archived:` and words in one line; page on with `cursor` | |
8282 | `family` | the forge that sent this one and, for a site admin, those it sent, with state and version | |
8383 | `feed` | `{ limit?, cursor? }`: your home timeline — tells, replies, runs, changes and looks — ranked, with who, what, why and a link; the same items as `GET /api/v1/feed`; `digest` (first page only) is home's "Since you were here" card, `{ heading, lines: [{ repo, says, href }] }` or null; `quietRuns` counts agent runs of the last 24 h that pushed, opened or said nothing, which the timeline leaves out; an item with `lines: [{ text, link }]` and `more` is a folded burst (3+ issues one author opened in one repository within 2 minutes: `planned N issues in owner/name`, linking to its map) — `lines` are the first three, `more` the rest | |
8484 | `feed_chips` | `{ kind?, value?, hidden? }`: the feed chips you switched off, which `feed` and `asks` leave out; with `kind` (`kind` or `person`), `value` and `hidden` it switches one off or on | yes |
src/actions/explore.ts +7
@@ -92,6 +92,13 @@ export function parseExploreQuery(line: string): ExploreQuery {
9292 return readExploreLine(line).query;
9393 }
9494
95 const EXPLORE_KEYS = /(^|\s)(kind|sort|updated|owner|archived):/i;
96
97 /** Whether a line has any word the explorer reads as a key, so every surface takes it as the explorer's. */
98 export function isExploreLine(line: string): boolean {
99 return EXPLORE_KEYS.test(line);
100 }
101
95102 /** The query and the `kind:`/`sort:` words it did not know, as typed. */
96103 export interface ExploreRead {
97104 readonly query: ExploreQuery;
src/http/mcp/tools/explore.ts +2 −2
@@ -3,7 +3,7 @@ import { bad, good, type Tool, str } from "../shared.ts";
33
44 export const tool: Tool = {
55 name: "explore",
6 doc: "`{ query?, cursor? }`: the apps and people you can see, ranked, with their numbers; `kind:` `sort:` and words in one line; page on with `cursor`",
6 doc: "`{ query?, cursor? }`: the apps and people you can see, ranked, with their numbers; `kind:` `sort:` `updated:` `owner:` `archived:` and words in one line; page on with `cursor`",
77 description:
88 "The apps, people (and, for an admin, forges) you can see, ranked.",
99 inputSchema: {
@@ -12,7 +12,7 @@ export const tool: Tool = {
1212 query: {
1313 type: "string",
1414 maxLength: 200,
15 description: "Words; kind: sort: updated:7d",
15 description: "Words; kind: sort: updated:7d owner: archived:",
1616 },
1717 cursor: {
1818 type: "string",
src/tui/screens/dashboard.ts +2 −2
@@ -14,7 +14,7 @@
1414 * whole of this screen's design.
1515 */
1616
17 import { exploreLine, exploreNote, explorePage, parseExploreQuery, readExploreLine, type ExploreRow } from "../../actions/explore.ts";
17 import { exploreLine, isExploreLine, exploreNote, explorePage, parseExploreQuery, readExploreLine, type ExploreRow } from "../../actions/explore.ts";
1818 import { protectionNotice } from "../../kernel/protection.ts";
1919 import { holdWords, plannerHold } from "../../crew/autonomy.ts";
2020 import { governorSeen } from "../../crew/governed.ts";
@@ -221,7 +221,7 @@ export function dashboardScreen(f: Session, ctx: Ctx): Screen {
221221 */
222222 let explored: { line: string; rows: readonly ExploreRow[] } | undefined;
223223 let askedLine = "";
224 const exploring = (): boolean => /(^|\s)(kind|sort):/i.test(filter.query);
224 const exploring = (): boolean => isExploreLine(filter.query);
225225 const exploredRows = (): readonly ExploreRow[] => (exploring() ? (explored?.rows ?? []) : []);
226226 const syncExplore = (): void => {
227227 if (!exploring()) {
test/explore.test.ts +7 −1
@@ -9,7 +9,7 @@ import { mkdtemp, rm } from "node:fs/promises";
99 import { tmpdir } from "node:os";
1010 import { join } from "node:path";
1111 import { createApp, type App } from "../src/app.ts";
12 import { explorePage, exploreLine, parseExploreQuery } from "../src/actions/explore.ts";
12 import { explorePage, exploreLine, isExploreLine, parseExploreQuery } from "../src/actions/explore.ts";
1313 import { suspendAccount } from "../src/actions/accounts.ts";
1414 import { createRepo } from "../src/actions/repos.ts";
1515 import { createApp as insertApp } from "../src/core/apps.ts";
@@ -624,3 +624,9 @@ describe("explore past the first page", () => {
624624 expect(seen.length).toBe(COUNT - 14 + 1);
625625 });
626626 });
627
628 test("isExploreLine is true for each explore key and false for plain words", () => {
629 for (const line of ["kind:app", "sort:forks", "updated:7d", "owner:trevato", "archived:yes", "garden OWNER:x"])
630 expect(isExploreLine(line)).toBe(true);
631 for (const line of ["", "garden", "zed-one", "downer:x"]) expect(isExploreLine(line)).toBe(false);
632 });
test/tui-dashboard-explore.test.ts +7
@@ -162,3 +162,10 @@ test("an unknown sort is named under Read as, with the words it takes", async ()
162162 const text = await typed("kind:app sort:nonsense");
163163 expect(text).toContain("sort:nonsense is not a sort — use score, forks");
164164 });
165
166 test("owner:<name> alone lists that owner's apps as the explorer", async () => {
167 const text = await typed("owner:alice");
168 expect(text).toContain("Read as: owner:alice");
169 expect(text).toContain("alice/zed");
170 expect(text).not.toContain("bob/zed-one");
171 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs