// the /lore command surface: what the picker offers while typing, and the help card. // one place, so the picker, the help, and the handler cannot drift apart. import type { Theme } from "@earendil-works/pi-coding-agent"; import { Text } from "@earendil-works/pi-tui"; import type { AutocompleteItem } from "@earendil-works/pi-tui"; export type Subcommand = { name: string; args?: string; what: string; group: "read" | "write" | "share" | "account" }; export const SUBCOMMANDS: readonly Subcommand[] = [ { name: "", what: "this project's space: a few lines and a link", group: "read" }, { name: "all", what: "every line", group: "read" }, { name: "mine", what: "only lines you wrote", group: "read" }, { name: "note", args: "", what: "save text exactly as typed", group: "write" }, { name: "use", args: "", what: "use a space you name for this project", group: "write" }, { name: "share", args: "", what: "let someone read and add to this space", group: "share" }, { name: "unshare", args: "", what: "take that back", group: "share" }, { name: "members", what: "who has been added", group: "share" }, { name: "join", args: " ", what: "use a space someone shared with you", group: "share" }, { name: "leave", what: "stop using the joined space here", group: "share" }, { name: "as", args: "[handle]", what: "switch account; no handle opens a picker", group: "account" }, { name: "login", args: "[handle]", what: "sign in through your PDS in the browser", group: "account" }, { name: "logout", args: "[handle]", what: "drop an account", group: "account" }, { name: "help", what: "this list", group: "read" }, ] as const; export const DESCRIPTION = "breadcrumbs on your PDS. /lore help lists the subcommands"; const GROUPS = ["read", "write", "share", "account"] as const; export type Sources = { accounts: readonly string[]; actors: (q: string) => Promise; /** keys of the spaces the current account owns, for `use` */ keys: () => Promise; }; /** items for the picker: subcommands until the first space, then whatever the subcommand takes. */ export function completions(prefix: string, { accounts, actors, keys }: Sources): AutocompleteItem[] | Promise | null { const m = prefix.match(/^(\S*)(?:\s+(.*))?$/); const head = m?.[1] ?? ""; const rest = m?.[2]; if (rest === undefined) { return SUBCOMMANDS.filter((c) => c.name && c.name.startsWith(head)).map((c) => ({ value: c.name, label: c.args ? `${c.name} ${c.args}` : c.name, description: c.what, })); } if (head === "as" || head === "logout") { return accounts.filter((h) => h.startsWith(rest)).map((h) => ({ value: `${head} ${h}`, label: h, description: "account" })); } if ((head === "share" || head === "unshare" || head === "join" || head === "login") && !rest.includes(" ") && rest.length >= 2) { return actors(rest).then((items) => items.map((i) => ({ ...i, value: `${head} ${i.value}` }))); } if (head === "use" && !rest.includes(" ")) { return keys().then((all) => all.filter((k) => k.startsWith(rest)).map((k) => ({ value: `use ${k}`, label: k, description: "a space you own" }))); } return null; } type HelpTheme = Pick; export function renderHelp(account: string | null, accounts: readonly string[], theme: HelpTheme): Text { const lines: string[] = []; const who = account ? `${theme.fg("accent", account)}${accounts.length > 1 ? theme.fg("dim", ` · also ${accounts.filter((a) => a !== account).join(", ")} · /lore as switches`) : ""}` : theme.fg("warning", "not signed in · /lore login "); lines.push(`${theme.fg("accent", theme.bold("/lore"))} ${who}`); for (const group of GROUPS) { lines.push("", theme.fg("muted", theme.bold(group))); for (const c of SUBCOMMANDS.filter((s) => s.group === group)) { const cmd = `/lore${c.name ? ` ${c.name}` : ""}${c.args ? ` ${c.args}` : ""}`; lines.push(` ${theme.fg("accent", cmd.padEnd(28))}${theme.fg("text", c.what)}`); } } lines.push("", theme.fg("dim", " a space someone shared with you: /lore join ")); return new Text(lines.join("\n"), 0, 0); }