197 lines · 8.1 KB · TypeScript
RawDownload
// ---------------------------------------------------------------------------// The daily-brief fixture set, as an in-memory file set (path -> contents).//// Adapted from prototypes/executor-artifacts to be executable against the// implemented contract: `tools/issues-sync.ts` writes the scope `issues` table,// `workflows/morning-sync.ts` schedules it and flags stale issues,// `ui/dashboard.tsx` reads the table, `skills/issues-brief/SKILL.md` routes the// agent. These are payloads (author code), not package source, so they live as// strings rather than compiled files.// --------------------------------------------------------------------------- export const DAILY_BRIEF_MANIFEST = `{  "$schema": "https://executor.sh/schemas/scope-manifest.json",  "scope": "rhys",  "description": "Personal scope artifacts — GitHub issues brief.",  "connections": { "github": "github/rhys" },  "artifacts": { "skills": "skills/", "tools": "tools/", "workflows": "workflows/", "ui": "ui/" }}`; export const ISSUES_SYNC_TS = `import { z } from "zod";import { defineTool, connection } from "executor:app"; export default defineTool({  description:    "Refresh the scope \\\`issues\\\` table from GitHub. Syncs open issues across the given repos (default: every repo the connection can see).",  connections: {    github: connection("github", { description: "GitHub account whose issues to sync" }),  },  input: z.object({    repos: z.array(z.string()).optional().describe("owner/repo entries; omit to sync all accessible repos"),    since: z.string().optional().describe("ISO timestamp — only issues updated after this"),  }),  output: z.object({ synced: z.number(), repos: z.number() }),  annotations: { readOnly: false, destructive: false },  async handler({ repos, since }, { github, db }) {    const targets =      repos ??      (await github.repos.listForAuthenticatedUser({ per_page: 100 })).map((r) => r.full_name);     await db.sql\`      CREATE TABLE IF NOT EXISTS issues (        repo TEXT NOT NULL, number INTEGER NOT NULL, title TEXT NOT NULL,        labels TEXT NOT NULL DEFAULT '[]', assignee TEXT, updated_at TEXT NOT NULL, url TEXT NOT NULL,        PRIMARY KEY (repo, number)      )\`;     let synced = 0;    for (const target of targets) {      const [owner, repo] = target.split("/");      const issues = await github.issues.listForRepo({ owner, repo, state: "open", since, per_page: 100 });      for (const issue of issues) {        if (issue.pull_request) continue;        await db.sql\`          INSERT INTO issues (repo, number, title, labels, assignee, updated_at, url)          VALUES (\${target}, \${issue.number}, \${issue.title},            \${JSON.stringify(issue.labels.map((l) => l.name))},            \${issue.assignee?.login ?? null}, \${issue.updated_at}, \${issue.html_url})          ON CONFLICT (repo, number) DO UPDATE SET            title = excluded.title, labels = excluded.labels,            assignee = excluded.assignee, updated_at = excluded.updated_at\`;        synced++;      }    }    return { synced, repos: targets.length };  },});`; export const SEARCH_ALL_MAIL_TS = `import { z } from "zod";import { defineTool, connections } from "executor:app"; export default defineTool({  description:    "Search across all connected Gmail accounts. Returns matches newest-first, tagged with which inbox they came from.",  connections: {    inboxes: connections("gmail", { description: "Gmail accounts to search across" }),  },  input: z.object({    query: z.string().describe("Gmail search syntax, e.g. from:acme subject:invoice"),    limit: z.number().int().min(1).max(100).default(25),  }),  output: z.object({    results: z.array(z.object({      inbox: z.string(), id: z.string(), from: z.string(),      subject: z.string(), snippet: z.string(), date: z.string(),    })),  }),  annotations: { readOnly: true },  async handler({ query, limit }, { inboxes }) {    const perInbox = await Promise.all(      inboxes.map(async (inbox) => {        const { messages } = await inbox.messages.search({ q: query, maxResults: limit });        return messages.map((m) => ({          inbox: inbox.account.email, id: m.id, from: m.from,          subject: m.subject, snippet: m.snippet, date: m.date,        }));      }),    );    const results = perInbox.flat().sort((a, b) => b.date.localeCompare(a.date)).slice(0, limit);    return { results };  },});`; export const MORNING_SYNC_TS = `import { defineWorkflow } from "executor:app"; export default defineWorkflow({  description: "Refresh GitHub issues at 9am and flag stale ones.",  schedule: { cron: "0 9 * * 1-5", timezone: "America/New_York" },  async run(step, { db }) {    const { synced } = await step.tool("issues-sync", {});    const stale = await step.do("find-stale", () =>      db.sql\`        SELECT repo, number, title FROM issues        WHERE updated_at < datetime('now', '-14 days')        ORDER BY updated_at ASC\`,    );    if (stale.length > 0) {      await step.notify({        title: \`\${stale.length} issues stale >14 days\`,        body: stale.slice(0, 5).map((i) => \`\${i.repo}#\${i.number} — \${i.title}\`).join("\\n"),        link: "ui://dashboard",      });    }    return { synced, stale: stale.length };  },});`; export const DASHBOARD_TSX = `import { useQuery, useTool, config } from "executor:ui";import { Card, CardContent, CardHeader, CardTitle, Badge, Button, Input } from "executor:ui/components";import { useState } from "react"; config({ maxHeight: 720, title: "GitHub Issues" }); export default function App() {  const [filter, setFilter] = useState("");  const issues = useQuery((db) => db.sql\`SELECT * FROM issues ORDER BY updated_at DESC\`);  const sync = useTool("issues-sync");  if (issues.isLoading) return <Card><CardContent>Loading issues…</CardContent></Card>;  const rows = issues.data.filter(    (i) => !filter || i.repo.includes(filter) || i.title.toLowerCase().includes(filter.toLowerCase()),  );  return (    <div className="flex flex-col gap-4 p-1">      <div className="flex items-center gap-2">        <Input placeholder="Filter…" value={filter} onChange={(e) => setFilter(e.target.value)} />        <Button disabled={sync.isRunning} onClick={() => sync.run({}).then(() => issues.refetch())}>          {sync.isRunning ? "Syncing…" : "Sync now"}        </Button>        <span className="ml-auto">{rows.length} open issues</span>      </div>      <Card>        <CardHeader><CardTitle>Open issues</CardTitle></CardHeader>        <CardContent>          {rows.slice(0, 30).map((issue) => (            <a key={\`\${issue.repo}#\${issue.number}\`} href={issue.url}>              <span>{issue.repo}#{issue.number}</span> <span>{issue.title}</span>              {JSON.parse(issue.labels).map((l) => (<Badge key={l}>{l}</Badge>))}            </a>          ))}        </CardContent>      </Card>    </div>  );}`; export const ISSUES_BRIEF_SKILL = `---name: issues-briefdescription: Answer questions about open GitHub issues from the scope \`issues\` database instead of paging the GitHub API; sync first if stale. Use when the user asks about their issues, the issue backlog, stale issues, or anything GitHub-issue related.--- # GitHub issues brief The scope database has an \`issues\` table maintained by the \`issues-sync\`tool and refreshed weekday mornings by the \`morning-sync\` workflow. Answerissue questions from this table — do not page the GitHub API directly. ## Recipe 1. Check freshness: \`SELECT MAX(updated_at) FROM issues\`. If older than ~24h, call \`issues-sync\` first.2. Query the table for what was asked.3. For anything visual, open the authored dashboard: \`ui://dashboard\`.`; /** The daily-brief file set as a path -> contents map. */export const dailyBriefFileSet = (): Map<string, string> =>  new Map<string, string>([    ["executor.json", DAILY_BRIEF_MANIFEST],    ["tools/issues-sync.ts", ISSUES_SYNC_TS],    ["tools/search-all-mail.ts", SEARCH_ALL_MAIL_TS],    ["workflows/morning-sync.ts", MORNING_SYNC_TS],    ["ui/dashboard.tsx", DASHBOARD_TSX],    ["skills/issues-brief/SKILL.md", ISSUES_BRIEF_SKILL],  ]);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t