@kud/gh-cockpit 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,162 +1,233 @@
1
- # @kud/gh-cockpit
1
+ <div align="center">
2
2
 
3
- A configurable GitHub cockpit for the terminal — your PRs, reviews and issues in one Ink TUI, grouped by **whose move it is**.
3
+ 🛫
4
4
 
5
- A tab tells you which relationship you are looking at. It never told you whether there was anything to _do_ once you got there, so red CI, merge conflicts and drafts sat interleaved with the rows you could actually act on. This bands every tab into `Your move` / `Their move` instead.
5
+ # Cockpit
6
6
 
7
- ```
8
- Review (20)
7
+ ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white)
8
+ ![Node.js](https://img.shields.io/badge/Node.js-339933?style=flat-square&logo=node.js&logoColor=white)
9
+ ![npm](https://img.shields.io/npm/v/@kud/gh-cockpit?style=flat-square&color=CB3837)
10
+ ![MIT](https://img.shields.io/badge/licence-MIT-22C55E?style=flat-square)
9
11
 
10
- » Your move (2)
11
- ── acme/api-gateway ─────
12
- ◆ ← #1495 support statementPeriodIds… 6d
13
- ── acme/event-bus ───────
14
- · #290 delete the legacy path 14h
12
+ **A configurable GitHub cockpit for the terminal — your PRs, reviews and issues in one Ink TUI, grouped by whose move it is.**
15
13
 
16
- » Their move (9)
17
- ── acme/monorepo ────────
18
- ~ #10 add internalNotes field 1w
19
- ── acme/api-gateway ─────
20
- ✗ ← #1496 point history at the route 2d
21
- ! #1449 guard null ids 6w
22
- ```
14
+ [Features](#-features) [Quick Start](#-quick-start) • [Build a CLI](#️-build-your-own-cli) • [API Reference](#-api-reference)
23
15
 
24
- ## Install
16
+ </div>
17
+
18
+ ## 🌟 Features
19
+
20
+ - 🎯 **Whose-move bands** — every tab splits into `Your move` and `Their move`, so the two rows you can act on stop hiding among the eighteen you can't
21
+ - 🧭 **Standing, not guesswork** — the same `✗` is your afternoon on a PR you wrote and the author's on one you were asked to review; the band knows which
22
+ - 🔍 **`--here` scoping** — resolves the repo from your git remote and scopes every search server-side, so other repos are never fetched at all
23
+ - 🎛️ **Pattern filtering** — `--include acme/*,me/acme-*` and `--exclude`, replacing any built-in notion of which repos are "yours"
24
+ - 🔌 **Bring your own CI** — GitHub Actions logs are built in; register a drill-in for anything else, or let it open in a browser
25
+ - ♿ **Readable without colour** — every state carries its own glyph, so the list survives being piped, logged, or read by someone who can't separate green from orange
26
+ - 🪶 **No opinions baked in** — repo ranking, checkout locations and cache lifetime are all supplied by the host; the defaults are empty on purpose
27
+
28
+ ## 🚀 Quick Start
25
29
 
26
30
  ```sh
27
31
  npm install @kud/gh-cockpit
28
32
  ```
29
33
 
30
- It is a **library, not a CLI**: you write the thin entry point, because the interesting decisions — which searches are tabs, which repos rank first, what a check drills into are yours and not portable.
31
-
32
- ## Whose move is it
34
+ It is a library, not a CLI there is no `bin`, because which searches become tabs and which repos rank first are not portable decisions. You write a short entry point; see [Build your own CLI](#️-build-your-own-cli) for a complete one.
33
35
 
34
- The band comes from the row's health _and_ where you stand relative to it. Those are different questions, and the same token means opposite things depending on the answer:
36
+ What it renders:
35
37
 
36
- | | `authored`<br>your PR | `queued`<br>a review was asked of you | `spoken`<br>you already reviewed |
37
- | ----------------------------------------------------- | --------------------- | ------------------------------------- | -------------------------------- |
38
- | `✗` CI failing · `!` conflict · `±` changes requested | **you** | them | them |
39
- | `·` awaiting review · `*` checks running | them | **you** | them |
40
- | `✓` approved | **you** | **you** | them |
41
- | `◆` open threads | **you** | **you** | **you** |
42
- | `~` draft | them | them | them |
38
+ ```console
39
+ $ cockpit
43
40
 
44
- Red CI on a PR you wrote is your afternoon. The same red CI on one you were asked to review is the author's, and reviewing it is wasted. Nothing on the row records that difference — it is a property of the _search_ the row arrived from, which is why `standing` is declared per search rather than inferred.
41
+ 🚀 Cockpit 29 items · @you updated 2m ago
42
+ Mine (7) Review (20) Incoming (4) Issues (3) Done (13)
43
+ ───────────
45
44
 
46
- > [!NOTE]
47
- > Every state has its own glyph, never a colour alone. Colour reinforces; it never carries meaning by itself.
45
+ » Your move (2)
48
46
 
49
- ## Configure
47
+ ── acme/api-gateway ─────
48
+ ◆ ← #1495 support statementPeriodIds in the report 6d
49
+ ── acme/event-bus ───────
50
+ · #290 delete the legacy serving path 14h
50
51
 
51
- ```ts
52
- import { defineCockpit } from "@kud/gh-cockpit"
53
- import { delegateExtension } from "@kud/gh-cockpit/extensions"
52
+ » Their move (18)
54
53
 
55
- export default defineCockpit({
56
- repoPriority: ["acme/monorepo", "acme/", "me/"],
57
- tabs: [
58
- {
59
- label: "Mine",
60
- help: "your PRs, draft and open",
61
- searches: ["is:open author:@me"],
62
- },
63
- {
64
- label: "Review",
65
- help: "theirs — asked of you, or you reviewed",
66
- searches: [
67
- { q: "is:open review-requested:@me", standing: "queued" },
68
- {
69
- q: "is:open reviewed-by:@me -author:@me -review-requested:@me",
70
- standing: "spoken",
71
- },
72
- ],
73
- },
74
- ],
75
- extensions: [delegateExtension],
76
- })
54
+ ── acme/monorepo ────────
55
+ ~ #10 add internalNotes to Contract 1w
56
+ ── acme/api-gateway ─────
57
+ ✗ ← #1496 point history at the generic route 2d
58
+ ! #1449 guard null ids before per-id fetches 6w
77
59
  ```
78
60
 
79
- `defineCockpit` is identity, but typed — a config file is checked against the schema at build time rather than producing an empty tab at runtime.
61
+ ## 🛠️ Build your own CLI
80
62
 
81
- ### Repo priority
63
+ The package has no `bin`, so this is the file you write. It is short — fetch, assemble, render — and everything opinionated lives in it rather than in the package.
82
64
 
83
- Ordered, best first. An entry ending in `/` matches an owner; anything else must equal `owner/name`, so a single repo can outrank the owner containing it. Repos matching nothing sort last, together.
84
-
85
- Grouping is a separate key from ranking: repos stay clustered whatever the priority list says, because repo headers depend on same-repo rows being adjacent.
86
-
87
- ## Filtering
65
+ ```tsx
66
+ #!/usr/bin/env node
67
+ import { $ } from "zx"
68
+ import React from "react"
69
+ import { render } from "ink"
70
+ import {
71
+ App,
72
+ buildInboxQuery,
73
+ configureInbox,
74
+ detailFor,
75
+ layoutGHItems,
76
+ signalPath,
77
+ toGHItem,
78
+ withRetry,
79
+ type Section,
80
+ } from "@kud/gh-cockpit"
81
+
82
+ $.verbose = false
83
+
84
+ // Everything the library refuses to assume. Call once, before rendering.
85
+ configureInbox({
86
+ repoPriority: ["acme/monorepo", "acme/", "me/"],
87
+ checkoutDir: `${process.env.HOME}/src`,
88
+ cacheNamespace: "my-cockpit",
89
+ cacheTtlMs: 20 * 60_000,
90
+ })
88
91
 
89
- ```sh
90
- gh-cockpit --include 'acme/*,me/acme-*'
92
+ // One GraphQL round trip for every tab. `withRetry` matters: GitHub returns 502
93
+ // under load, and a 502 is the server declining to compute an answer, not an answer.
94
+ const fetchCockpit = async (): Promise<{ sections: Section[]; login: string }> => {
95
+ const result = await withRetry(() =>
96
+ $`gh api graphql -f query=${buildInboxQuery({})}`.quiet(),
97
+ )
98
+ const data = JSON.parse(result.stdout).data
99
+
100
+ const rows = (nodes: any[], standing?: "authored" | "queued" | "spoken") =>
101
+ (nodes ?? []).map((n) => ({ ...toGHItem(n), ...(standing ? { standing } : {}) }))
102
+
103
+ return {
104
+ login: data.viewer.login,
105
+ sections: [
106
+ {
107
+ id: "mine",
108
+ label: "Mine",
109
+ items: layoutGHItems(rows(data.myPRs.nodes, "authored"), "mine"),
110
+ },
111
+ {
112
+ // Two searches, one tab. `standing` is stamped per search because nothing
113
+ // on a row can tell them apart afterwards.
114
+ id: "review",
115
+ label: "Review",
116
+ items: layoutGHItems(
117
+ [
118
+ ...rows(data.reviewRequests.nodes, "queued"),
119
+ ...rows(data.reviewed.nodes, "spoken"),
120
+ ],
121
+ "review",
122
+ ),
123
+ },
124
+ { id: "done", label: "Done", items: layoutGHItems(rows(data.recentlyDone.nodes), "done") },
125
+ ].filter((s) => s.items.some((i) => i.kind !== "repo-header")),
126
+ }
127
+ }
128
+
129
+ render(
130
+ <App
131
+ fetcher={fetchCockpit}
132
+ cacheKey="cockpit"
133
+ title="cockpit"
134
+ detailFor={detailFor}
135
+ tabHelp={[
136
+ ["Mine", "your PRs, draft and open"],
137
+ ["Review", "theirs — asked of you, or you reviewed"],
138
+ ["Done", "your PRs closed < 14d"],
139
+ ]}
140
+ emptyHint="Nothing open across your repos."
141
+ watchPath={signalPath()}
142
+ watchDebounceMs={2_000}
143
+ />,
144
+ { alternateScreen: true },
145
+ )
91
146
  ```
92
147
 
93
- `*` matches within one path segment and never across the `/`, so `acme/*` cannot reach another owner and `acme` will not claim `acmecorp`. A bare owner is sugar for all its repos.
148
+ Point a `bin` at it in your own `package.json` and it is a command.
94
149
 
95
- ```sh
96
- gh-cockpit --exclude 'acme/legacy'
97
- ```
150
+ ### Refreshing without polling
98
151
 
99
- Exclude is applied after include and wins, so you can take a whole org and drop one repo without listing the rest.
152
+ `watchPath` is a file the cockpit watches; writing a byte to it makes every open cockpit refetch, debounced. Have whatever mutates GitHub on your behalf — a merge script, an editor hook write to `signalPath()`.
100
153
 
101
- ```sh
102
- gh-cockpit --here
103
- ```
104
-
105
- `--here` is not a filter. It resolves the repo from your git remote — `upstream` if present, else `origin` — and scopes every search **server-side** with `repo:owner/name`, so other repos are never fetched at all. On a fork that matters: `origin` is your copy and `upstream` is where the PRs actually live.
154
+ A poller is wrong twice over: it spends quota on the long stretches where nothing changed, and it is still up to a full interval late when something did. The filesystem is the whole broker — no daemon, no socket, and the fan-out is free.
106
155
 
107
- ## Extensions
156
+ > [!IMPORTANT]
157
+ > Write a byte rather than `touch`ing. At second-granularity mtime with no size change, two touches inside the same second are indistinguishable and some watch backends coalesce them away.
108
158
 
109
- Extensions live behind their own entry point because they are a choice, not a default — each one claims a keybinding on a surface where every key is already spoken for.
159
+ ## 📖 API Reference
110
160
 
111
- ```ts
112
- import {
113
- delegateExtension,
114
- copyPromptExtension,
115
- } from "@kud/gh-cockpit/extensions"
116
- ```
161
+ | Export | Purpose |
162
+ | ---------------------------------------- | ---------------------------------------------------------------------------------------- |
163
+ | `defineCockpit(config)` | Types your own config object. A convenience for hosts — nothing internal reads it |
164
+ | `configureInbox(config)` | Host opinions: `repoPriority`, `profiles`, `checkoutDir`, `cacheNamespace`, `cacheTtlMs` |
165
+ | `parseArgs(argv)` | Parses `--here`, `--include`, `--exclude` and a positional filter name. You decide what they mean |
166
+ | `registerCheckDrills(drills)` | Where a CI check drills in; unregistered checks open in a browser |
167
+ | `detailFor(ctx)` | The drill-in view for a row — `PrView` or `IssueView` |
168
+ | `PrView` · `IssueView` | The drill-in views themselves, if you want to wrap them |
169
+ | `withRetry(fn)` | Retries transient `50x` responses; GitHub returns them under load |
170
+ | `toGHItem(node)` · `computeHealth(node)` | GraphQL node → row, and the health token behind each glyph |
117
171
 
118
- Both act on the row under the cursor and shell out to nothing, so neither assumes anything about what you have installed.
172
+ ### Whose move is it
119
173
 
120
- ## Check drill-ins
174
+ | | `authored`<br>your PR | `queued`<br>asked of you | `spoken`<br>you reviewed |
175
+ | ----------------------------------------------------- | --------------------- | ------------------------ | ------------------------ |
176
+ | `✗` CI failing · `!` conflict · `±` changes requested | **you** | them | them |
177
+ | `·` awaiting review · `*` checks running | them | **you** | them |
178
+ | `✓` approved | **you** | **you** | them |
179
+ | `◆` open threads | **you** | **you** | **you** |
180
+ | `~` draft | them | them | them |
121
181
 
122
- Activating a CI check opens its log. GitHub Actions is built in; anything else is yours to register, and an unregistered check opens in a browser rather than drilling into a view that renders nothing.
182
+ `standing` is declared per search rather than inferred, because nothing on a row records it: a PR awaiting review and one you already reviewed are the same PR to GitHub's API, and opposite things to you.
123
183
 
124
- ```ts
125
- import { registerCheckDrills } from "@kud/gh-cockpit"
184
+ ### Filtering
126
185
 
127
- registerCheckDrills([
128
- {
129
- match: (url) => url.includes("ci.example"),
130
- render: ({ url, name, onBack }) => <MyBuildView url={url} name={name} onBack={onBack} />,
131
- },
132
- ])
186
+ ```sh
187
+ cockpit --include 'acme/*,me/acme-*'
133
188
  ```
134
189
 
135
- ## Host configuration
190
+ `*` matches within one path segment and never across the `/`, so `acme/*` cannot reach another owner and `acme` will not claim `acmecorp`. A bare owner is sugar for all its repos. `--exclude` is applied after `--include` and wins, so you can take a whole org and drop one repo without listing the rest.
191
+
192
+ ## 🔧 Development
136
193
 
137
- The library holds no opinion about your machine. `configureInbox` (from `@kud/gh-ink`) is where you supply one, and **every default is empty** — an unconfigured host gets flat repo ranking and no local-checkout resolution, never a guess at where you keep your code.
194
+ ```sh
195
+ git clone https://github.com/kud/gh.git
196
+ ```
138
197
 
139
- ```ts
140
- import { configureInbox } from "@kud/gh-cockpit"
198
+ ```sh
199
+ cd gh && npm install
200
+ ```
141
201
 
142
- configureInbox({
143
- repoPriority: ["acme/", "me/"],
144
- checkoutDir: `${process.env.HOME}/src`,
145
- cacheNamespace: "my-cockpit",
146
- cacheTtlMs: 20 * 60_000,
147
- })
202
+ ```sh
203
+ npm run build --workspace @kud/gh-cockpit
148
204
  ```
149
205
 
150
- > [!TIP]
151
- > `cacheTtlMs` is the single knob between "always current" and "always slow, and drawing 502s from the API" — every launch past it pays the full query. It can be generous: acting on a row drops the cache entry outright, and `r` refetches on demand.
206
+ | Script | Does |
207
+ | ------------------- | ---------------------------------- |
208
+ | `npm run build` | Compile `src/` → `dist/` with tsup |
209
+ | `npm run dev` | Same, in watch mode |
210
+ | `npm run typecheck` | `tsc --noEmit` |
211
+ | `npm test` | Vitest |
152
212
 
153
- ## Refreshing
213
+ ```
214
+ src/
215
+ config.ts defineCockpit and the config types
216
+ args.ts --here / --include / --exclude parsing
217
+ lib.ts shared layer, re-exports @kud/gh-ink
218
+ views/ PrView, IssueView, and the drill-in views
219
+ extensions/ opt-in extensions, exported separately
220
+ ```
154
221
 
155
- The cockpit does not poll. A poller spends quota on the long stretches where nothing changed and is still up to a full interval late when something did. Instead, pass `watchPath` and have whatever mutates GitHub write a byte to that file — every open cockpit refetches, debounced. The filesystem is the broker: no daemon, no socket, no fan-out to manage.
222
+ ## 🏗️ Tech Stack
156
223
 
157
- > [!IMPORTANT]
158
- > Write a byte rather than `touch`ing. At second-granularity mtime with no size change, two touches inside the same second are indistinguishable and some watch backends coalesce them away.
224
+ | | |
225
+ | -------------------------------------------------------- | -------------------------------------------------- |
226
+ | [@kud/gh](https://www.npmjs.com/package/@kud/gh) | Surface-agnostic core: queries, health computation |
227
+ | [@kud/gh-ink](https://www.npmjs.com/package/@kud/gh-ink) | Ink components, whose-move banding, layout |
228
+ | [@kud/ink-ui](https://www.npmjs.com/package/@kud/ink-ui) | Terminal UI primitives |
229
+ | [zx](https://www.npmjs.com/package/zx) | Shelling out to `gh` |
159
230
 
160
- ## Licence
231
+ ---
161
232
 
162
- MIT © Erwann Mest
233
+ MIT © [kud](https://github.com/kud) — Made with ❤️
@@ -1,4 +1,4 @@
1
- import { AiLauncher, seedPromptFor, CopyPromptNotice } from '../chunk-A3NXVFEI.js';
1
+ import { AiLauncher, seedPromptFor, CopyPromptNotice } from '../chunk-TEWCRLXY.js';
2
2
  import { useEffect } from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
package/dist/index.d.ts CHANGED
@@ -98,6 +98,13 @@ declare const hasPatterns: (f: RepoFilter) => boolean;
98
98
 
99
99
  declare const detailFor: (ctx: DetailContext) => React.JSX.Element;
100
100
 
101
+ declare const DrillView: ({ title, subtitle, hints, children, }: {
102
+ title: string;
103
+ subtitle?: string;
104
+ hints: [string, string][];
105
+ children: React.ReactNode;
106
+ }) => React.JSX.Element;
107
+
101
108
  type Tab = "health" | "conversation";
102
109
  declare const PrView: ({ item, login, onBack, defaultTab, onRefresh, onRemove, onMerged, }: {
103
110
  item: GHItem;
@@ -121,4 +128,4 @@ declare const IssueView: ({ item, login, onBack, }: {
121
128
  onBack: () => void;
122
129
  }) => React.JSX.Element;
123
130
 
124
- export { type CheckDrill, type CheckDrillContext, type CockpitArgs, type CockpitConfig, type CockpitItem, IssueView, PrView, type TabSearch, type TabSpec, checkDrillFor, computeHealth, defineCockpit, detailFor, hasPatterns, parseArgs, registerCheckDrills, signalPath, toGHItem, withRetry };
131
+ export { type CheckDrill, type CheckDrillContext, type CockpitArgs, type CockpitConfig, type CockpitItem, DrillView, IssueView, PrView, type TabSearch, type TabSpec, checkDrillFor, computeHealth, defineCockpit, detailFor, hasPatterns, parseArgs, registerCheckDrills, signalPath, toGHItem, withRetry };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { __export, withRetry, toGHItem, signalPath, computeHealth, buildInboxQuery, lib_exports, AiLauncher, seedPromptFor, CopyPromptNotice, DrillView, __reExport } from './chunk-A3NXVFEI.js';
2
- export { buildInboxQuery, computeHealth, signalPath, toGHItem, withRetry } from './chunk-A3NXVFEI.js';
1
+ import { __export, withRetry, toGHItem, signalPath, computeHealth, buildInboxQuery, DrillView, lib_exports, AiLauncher, seedPromptFor, CopyPromptNotice, __reExport } from './chunk-TEWCRLXY.js';
2
+ export { DrillView, buildInboxQuery, computeHealth, signalPath, toGHItem, withRetry } from './chunk-TEWCRLXY.js';
3
3
  import { parsePatterns, HealthPanel, CommentsPanel, inboxConfig } from '@kud/gh-ink';
4
4
  import { $ } from 'zx';
5
5
  import { useState, useEffect } from 'react';
@@ -14,6 +14,7 @@ import { join } from 'path';
14
14
  // src/index.ts
15
15
  var index_exports = {};
16
16
  __export(index_exports, {
17
+ DrillView: () => DrillView,
17
18
  IssueView: () => IssueView,
18
19
  PrView: () => PrView,
19
20
  buildInboxQuery: () => buildInboxQuery,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kud/gh-cockpit",
3
- "version": "0.1.0",
4
- "description": "A configurable GitHub cockpit for the terminal \u2014 your PRs, reviews and issues in one Ink TUI, grouped by whose move it is.",
3
+ "version": "0.1.2",
4
+ "description": "A configurable GitHub cockpit for the terminal your PRs, reviews and issues in one Ink TUI, grouped by whose move it is.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -54,7 +54,7 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@kud/gh": "0.5.1",
57
- "@kud/gh-ink": "0.21.0",
57
+ "@kud/gh-ink": "0.22.1",
58
58
  "@kud/ink-ui": "0.14.0",
59
59
  "zx": "8.8.5"
60
60
  },
@@ -1,3 +1,6 @@
1
+ import { useWindowSize, Box, Text, useInput } from 'ink';
2
+ import { Panel, colors, FooterHints, useListCursor } from '@kud/ink-ui';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
1
4
  import * as gh_ink_star from '@kud/gh-ink';
2
5
  import { relativeTime, inboxConfig } from '@kud/gh-ink';
3
6
  import { mkdirSync } from 'fs';
@@ -5,9 +8,6 @@ import { homedir } from 'os';
5
8
  import { join } from 'path';
6
9
  import { buildInboxQuery, computeHealth as computeHealth$1, latestChecks, isPendingCheck, isFailCheck, isPassCheck } from '@kud/gh';
7
10
  export { buildInboxQuery } from '@kud/gh';
8
- import { useWindowSize, Box, Text, useInput } from 'ink';
9
- import { Panel, colors, FooterHints, useListCursor } from '@kud/ink-ui';
10
- import { jsx, jsxs } from 'react/jsx-runtime';
11
11
  import { $ } from 'zx';
12
12
  import { useRef, useEffect, useState } from 'react';
13
13
 
@@ -28,6 +28,25 @@ var __copyProps = (to, from, except, desc) => {
28
28
  return to;
29
29
  };
30
30
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
31
+ var DrillView = ({
32
+ title,
33
+ subtitle,
34
+ hints,
35
+ children
36
+ }) => {
37
+ const { rows } = useWindowSize();
38
+ return /* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: rows - 2, paddingX: 1, children: [
39
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
40
+ /* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: title }),
41
+ subtitle ? /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
42
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Title: " }),
43
+ /* @__PURE__ */ jsx(Text, { children: subtitle })
44
+ ] }) : null
45
+ ] }),
46
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, children }),
47
+ /* @__PURE__ */ jsx(FooterHints, { hints })
48
+ ] }) });
49
+ };
31
50
 
32
51
  // src/lib.ts
33
52
  var lib_exports = {};
@@ -151,25 +170,6 @@ var signalPath = () => {
151
170
  mkdirSync(dir, { recursive: true });
152
171
  return join(dir, "cockpit-dirty");
153
172
  };
154
- var DrillView = ({
155
- title,
156
- subtitle,
157
- hints,
158
- children
159
- }) => {
160
- const { rows } = useWindowSize();
161
- return /* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: rows - 2, paddingX: 1, children: [
162
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
163
- /* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: title }),
164
- subtitle ? /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
165
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Title: " }),
166
- /* @__PURE__ */ jsx(Text, { children: subtitle })
167
- ] }) : null
168
- ] }),
169
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, children }),
170
- /* @__PURE__ */ jsx(FooterHints, { hints })
171
- ] }) });
172
- };
173
173
  var CANDIDATES = [
174
174
  { id: "claude", label: "Claude Code", cmd: "claude", acceptsPrompt: true },
175
175
  { id: "opencode", label: "opencode", cmd: "opencode" },