opencode-plugin-kit 1.0.0-alpha.5 → 1.0.0-alpha.7

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
@@ -5,7 +5,7 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
7
  Shared building blocks for [OpenCode](https://opencode.ai) sidebar plugins —
8
- the pieces that every sidebar widget ends up reimplementing. Used by five
8
+ the pieces that every sidebar widget ends up reimplementing. Used by four
9
9
  production plugins with **100% test coverage**.
10
10
 
11
11
  ## Install
@@ -16,24 +16,22 @@ Published on [npm](https://www.npmjs.com/package/opencode-plugin-kit):
16
16
  npm install opencode-plugin-kit
17
17
  ```
18
18
 
19
- Plugins depending on the kit declare it in `dependencies`; the SDK peer
20
- (`@opencode-ai/plugin`) must match your OpenCode runtime version (see each
19
+ > **Still a prerelease.** `latest` currently resolves to `1.0.0-alpha.6` — the
20
+ > first OpenCode v2 build. The publish workflow tags every prerelease under
21
+ > its own identifier (`alpha`, `beta`, …) and leaves `latest` alone, so later
22
+ > alphas will _not_ move `latest` automatically. To track the newest build,
23
+ > install `opencode-plugin-kit@alpha`.
24
+
25
+ Plugins depending on the kit declare it in `dependencies`; the host peer
26
+ (`@opencode/plugin`) must match your OpenCode v2 runtime version (see each
21
27
  plugin's README).
22
28
 
23
29
  ## Status
24
30
 
25
- **v1.0.0-alpha.1** — API stabilized, ready for integration testing.
31
+ **v1.0.0-alpha.6** — API stabilized, ready for integration testing.
26
32
 
27
33
  ## Quick Start
28
34
 
29
- ```sh
30
- # npm
31
- npm install opencode-plugin-kit
32
-
33
- # bun
34
- bun add opencode-plugin-kit
35
- ```
36
-
37
35
  ```ts
38
36
  import { createViewPicker, createCachedStore } from "opencode-plugin-kit"
39
37
 
@@ -95,7 +93,7 @@ their unique data and rendering.
95
93
  | `commands.ts` | `createToggle()`, `registerKeymapCommand()` | Toggle command + keymap registration |
96
94
  | `workspace.ts` | `resolveLocation()`, `workspaceDirectory()` | Workspace location resolution |
97
95
  | `toast.ts` | `showToast()` | Toast helper |
98
- | `host.ts` | `KitContext`, `KitMessageShape` | Structural host-contract types |
96
+ | `host.ts` | `KitContext`, `KitMessageShape`, `KitModelShape` | Structural host-contract types |
99
97
 
100
98
  Everything is re-exported from the package root (`src/index.ts`).
101
99
 
@@ -197,24 +195,75 @@ const totals = walkMessages(
197
195
  const { input, output, cost } = sumProviderTokens(context, sessionID, "opencode")
198
196
  ```
199
197
 
198
+ ### Collapsible
199
+
200
+ Imported from the `collapsible` subpath so the Solid JSX stays out of the main
201
+ entry.
202
+
203
+ ```ts
204
+ import { CollapsibleSection, CollapsibleGroup } from "opencode-plugin-kit/collapsible"
205
+
206
+ // From a host component that already has the plugin context:
207
+ const { theme } = usePlugin()
208
+
209
+ CollapsibleSection({
210
+ title: "Skills",
211
+ count: items.length,
212
+ threshold: 2, // Collapse the body at or below this count
213
+ pinned: <Note />, // Always visible between header and body
214
+ theme, // Header colors; pass the host's theme
215
+ children: <List />,
216
+ })
217
+
218
+ CollapsibleGroup({
219
+ title: "Providers",
220
+ count: ids.length,
221
+ defaultCollapsed: true,
222
+ theme,
223
+ children: (collapsed) => (collapsed() ? <Summary /> : <List />),
224
+ })
225
+ ```
226
+
227
+ `theme` is optional but **recommended**. Without it the kit falls back to
228
+ `usePlugin()`, which throws when the kit is installed from `node_modules`
229
+ (it resolves its own copy of `@opencode/plugin` / `solid-js`, so the host's
230
+ plugin context is not visible). If the context is missing the header falls back
231
+ to unthemed text rather than throwing.
232
+
200
233
  ## Consuming Plugins
201
234
 
202
- | Plugin | What it does |
203
- | --------------------------------------------------------------- | -------------------------------------------------------------- |
204
- | [opencode-usage-quota-tracker](../opencode-usage-quota-tracker) | Live provider quota + usage in sidebar footer |
205
- | [opencode-model-recommender](../opencode-model-recommender) | Model recommendations by cache ratio, token cost, session cost |
206
- | [opencode-skill-lister](../opencode-skill-lister) | Skills list in sidebar |
207
- | [opencode-plugin-manager](../opencode-plugin-manager) | Plugin manager in sidebar |
235
+ | Plugin | What it does |
236
+ | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
237
+ | [opencode-usage-quota-tracker](https://github.com/ranjithrajv/opencode-usage-quota-tracker) | Live provider quota + usage in sidebar footer |
238
+ | [opencode-model-recommender](https://github.com/ranjithrajv/opencode-model-recommender) | Model recommendations by cache ratio, token cost, session cost |
239
+ | [opencode-skill-lister](https://github.com/ranjithrajv/opencode-skill-lister) | Skills list in sidebar |
240
+ | [opencode-plugin-manager](https://github.com/ranjithrajv/opencode-plugin-manager) | Plugin manager in sidebar |
241
+
242
+ Install from the registry:
243
+
244
+ ```sh
245
+ npm install opencode-plugin-kit@alpha
246
+ ```
247
+
248
+ The `latest` dist-tag tracks the newest v1 alpha; v2 releases ship under the
249
+ `alpha` dist-tag, so pin `opencode-plugin-kit@alpha` (or an explicit range) until
250
+ `latest` moves to v2.
208
251
 
209
- Link locally with `"opencode-plugin-kit": "file:../opencode-plugin-kit"` in the
210
- consumer's `package.json`, then `npm install` (or `bun install`).
252
+ For local development, link the sibling checkout with
253
+ `"opencode-plugin-kit": "file:../opencode-plugin-kit"` in the consumer's
254
+ `package.json`, then `npm install` (or `bun install`). Note that a linked
255
+ checkout shares one module graph with the host, so the collapsible components
256
+ find the plugin context without a `theme` prop; a registry install does not.
211
257
 
212
258
  ## Compatibility
213
259
 
214
- The host plugin API is beta; its types are the spec. Kit consumes the context
260
+ The OpenCode v2 host plugin API's types are the spec. Kit consumes the context
215
261
  structurally — anything the host ships with the expected members satisfies it.
216
262
 
217
- - **No runtime dependency** on `@opencode-ai/plugin` — types only
263
+ - **Floor: `@opencode/plugin` 2.0.3.** Earlier 2.x releases have no `./tui`
264
+ subpath export, so nothing that imports `@opencode/plugin/tui` can resolve it.
265
+ Verified by typecheck and the type-tripwire against 2.0.3, 2.0.10 and 2.0.18.
266
+ - **No runtime dependency** on `@opencode/plugin` — types only
218
267
  - **Optional peer** — declared as optional so consumers share one copy
219
268
  - **CI tripwire** — `host.test-d.ts` pins published host types against kit's
220
269
  contract; drift fails CI with a readable diff
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plugin-kit",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.7",
4
4
  "description": "Shared building blocks for OpenCode sidebar plugins: provider vocabulary, row formatting, defensive message traversal, connected-provider tracking, polling fetcher, cached resources, and a persisted view/filter picker (registry + slash command + dialog + toast).",
5
5
  "keywords": [
6
6
  "opencode",
@@ -12,7 +12,7 @@
12
12
  "author": "Ranjith Raj",
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git+https://github.com/ranjithraj/opencode-plugin-kit.git"
15
+ "url": "git+https://github.com/ranjithrajv/opencode-plugin-kit.git"
16
16
  },
17
17
  "files": [
18
18
  "src",
@@ -32,12 +32,14 @@
32
32
  "fmt": "vp fmt",
33
33
  "lint": "vp lint",
34
34
  "typecheck": "tsc --noEmit",
35
- "test:types": "vitest run --typecheck",
36
- "test:coverage": "vitest run --coverage.enabled"
35
+ "test": "vp test",
36
+ "test:types": "vp test --typecheck",
37
+ "test:coverage": "vp exec vitest run --coverage"
37
38
  },
38
39
  "devDependencies": {
39
- "@opencode-ai/plugin": "0.0.0-beta-19242",
40
- "@opencode-ai/sdk": "0.0.0-beta-19234",
40
+ "@opencode/plugin": "2.0.15",
41
+ "@opencode/sdk": "2.0.15",
42
+ "@opencode/theme": "2.0.15",
41
43
  "@opentui/core": "^0.5.11",
42
44
  "@opentui/solid": "^0.5.11",
43
45
  "@types/node": "^26.5.0",
@@ -52,12 +54,12 @@
52
54
  "zod": "^4.5.4"
53
55
  },
54
56
  "peerDependencies": {
55
- "@opencode-ai/plugin": "0.0.0-beta-19242",
57
+ "@opencode/plugin": "^2.0.3",
56
58
  "solid-js": "^1.9.12",
57
59
  "zod": "^4.5.4"
58
60
  },
59
61
  "peerDependenciesMeta": {
60
- "@opencode-ai/plugin": {
62
+ "@opencode/plugin": {
61
63
  "optional": true
62
64
  }
63
65
  },
@@ -12,7 +12,40 @@
12
12
  // count, always toggleable.
13
13
  import { createSignal, Show } from "solid-js"
14
14
  import type { JSX } from "solid-js"
15
- import { usePlugin } from "@opencode-ai/plugin/tui"
15
+ import { usePlugin } from "@opencode/plugin/tui"
16
+
17
+ /**
18
+ * Theme tokens a collapsible header needs. Derived from the host theme so
19
+ * callers can pass their `usePlugin().theme` straight through, and so we
20
+ * don't pin the host's color type (e.g. `RGBA`) here.
21
+ */
22
+ type HostTheme = ReturnType<typeof usePlugin>["theme"]
23
+ export interface CollapsibleTheme {
24
+ text: {
25
+ base?: HostTheme["text"]["base"]
26
+ muted?: HostTheme["text"]["muted"]
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Resolve the theme for a header.
32
+ *
33
+ * Prefer an explicitly passed `theme`. When this package is consumed from
34
+ * `node_modules`, `usePlugin()` here resolves a different copy of
35
+ * `@opencode/plugin` (and `solid-js`) than the host renders with, so the
36
+ * host's plugin context is not visible and `usePlugin()` throws
37
+ * "PluginContextProvider is missing". Callers that already hold the host
38
+ * context pass `theme` down; the `usePlugin()` fallback keeps working for
39
+ * consumers whose module graph is deduped (tests, monorepos).
40
+ */
41
+ function resolveTheme(explicit?: CollapsibleTheme): CollapsibleTheme {
42
+ if (explicit) return explicit
43
+ try {
44
+ return usePlugin().theme
45
+ } catch {
46
+ return { text: {} }
47
+ }
48
+ }
16
49
 
17
50
  /** The top-level collapsible sidebar section. */
18
51
  export function CollapsibleSection(props: {
@@ -26,9 +59,11 @@ export function CollapsibleSection(props: {
26
59
  /** Always-visible content between the header and the collapsible body
27
60
  * (e.g. the plugin manager's status note). */
28
61
  pinned?: JSX.Element
62
+ /** Header colors; pass the host's `usePlugin().theme` from the consumer. */
63
+ theme?: CollapsibleTheme
29
64
  children: JSX.Element
30
65
  }) {
31
- const theme = usePlugin().theme
66
+ const theme = resolveTheme(props.theme)
32
67
  const [expanded, setExpanded] = createSignal(false)
33
68
  const count = () => props.count ?? 0
34
69
  const threshold = () => props.threshold ?? 2
@@ -37,13 +72,13 @@ export function CollapsibleSection(props: {
37
72
  <box flexDirection="column">
38
73
  <box flexDirection="row" gap={1} onMouseDown={() => expandable() && setExpanded((e) => !e)}>
39
74
  <Show when={expandable()}>
40
- <text fg={theme.text.default}>{expanded() ? "▼" : "▶"}</text>
75
+ <text fg={theme.text.base}>{expanded() ? "▼" : "▶"}</text>
41
76
  </Show>
42
- <text fg={theme.text.default}>
77
+ <text fg={theme.text.base}>
43
78
  <b>{props.title}</b>
44
79
  </text>
45
80
  <Show when={!expanded()}>
46
- <text fg={theme.text.subdued}> ({props.summary ?? count()})</text>
81
+ <text fg={theme.text.muted}> ({props.summary ?? count()})</text>
47
82
  </Show>
48
83
  </box>
49
84
  {props.pinned}
@@ -62,15 +97,17 @@ export function CollapsibleGroup(props: {
62
97
  title: string
63
98
  count: number
64
99
  defaultCollapsed?: boolean
100
+ /** Header colors; pass the host's `usePlugin().theme` from the consumer. */
101
+ theme?: CollapsibleTheme
65
102
  children: JSX.Element | ((collapsed: () => boolean) => JSX.Element)
66
103
  }) {
67
- const theme = usePlugin().theme
104
+ const theme = resolveTheme(props.theme)
68
105
  const [collapsed, setCollapsed] = createSignal(props.defaultCollapsed ?? false)
69
106
  return (
70
107
  <box flexDirection="column">
71
108
  <box flexDirection="row" gap={1} onMouseDown={() => setCollapsed((c) => !c)}>
72
- <text fg={theme.text.subdued}>{collapsed() ? "▸" : "▾"}</text>
73
- <text fg={theme.text.subdued}>
109
+ <text fg={theme.text.muted}>{collapsed() ? "▸" : "▾"}</text>
110
+ <text fg={theme.text.muted}>
74
111
  {props.title} ({props.count})
75
112
  </text>
76
113
  </box>
package/src/host.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Narrow host-contract types for opencode-plugin-kit.
2
2
  //
3
- // `@opencode-ai/plugin` is beta: its types ARE the spec. Kit consumes only a
3
+ // `@opencode/plugin` (v2) types ARE the spec. Kit consumes only a
4
4
  // small slice of the host context, so instead of accepting `any` everywhere
5
5
  // (which silently survives host upgrades and breaks at runtime), this module
6
6
  // defines the *structural minimum* kit needs and re-exports the exact host
@@ -14,7 +14,7 @@
14
14
  // Host types kit re-exports (single import point for consumers)
15
15
  // ---------------------------------------------------------------------------
16
16
 
17
- /** Toast payload — mirrored from the host's TuiToast; host drift here breaks CI. */
17
+ /** Toast payload — structural subset of the host's v2 `ToastOptions`; drift breaks CI. */
18
18
  export type ToastInput = {
19
19
  message: string
20
20
  variant?: "success" | "error" | "warning" | "info"
@@ -32,10 +32,10 @@ export type SelectOption<Value = string> = {
32
32
  // Structural minimum of the host context kit touches
33
33
  // ---------------------------------------------------------------------------
34
34
 
35
- /** `context.storage.store(key, { initial })` — the older host returns
36
- * `[value, ref]` (mutating `ref` persists); the newer floating `beta` host
37
- * types it as `readonly [T, (mutation: (draft: T) => void) => Promise<void>]`.
38
- * Both shapes satisfy this union; kit code reads `[0]` and mutates it, which
35
+ /** `context.storage.store(key, { initial })` — v2 types this as
36
+ * `readonly [Store<T>, (mutation: (draft: T) => void) => Promise<void>]`;
37
+ * the older host returned `[T, T]` (mutating the second entry persisted).
38
+ * Both shapes satisfy this union; kit reads `[0]` and mutates it, which
39
39
  * both hosts persist. */
40
40
  export interface KitStorage {
41
41
  store<T extends object>(
@@ -60,7 +60,7 @@ export interface KitUI {
60
60
  alert(input: { title: string; message: string }): Promise<unknown>
61
61
  select(input: {
62
62
  title: string
63
- message: string
63
+ placeholder?: string
64
64
  current?: string
65
65
  options: readonly SelectOption[]
66
66
  }): Promise<unknown>
@@ -75,9 +75,9 @@ export interface KitCommandEntry {
75
75
  readonly title: string
76
76
  readonly description: string
77
77
  readonly group: string
78
- readonly palette?: boolean
78
+ readonly palette?: true
79
79
  readonly suggested?: boolean
80
- readonly slash?: { name: string; aliases?: string[]; arguments?: boolean }
80
+ readonly slash?: { name: string; aliases?: string[]; arguments?: true }
81
81
  run: (input?: string) => void
82
82
  }
83
83
 
@@ -102,7 +102,7 @@ export interface KitClient {
102
102
  }
103
103
 
104
104
  /** The structural minimum context for every kit factory. Deliberately
105
- * narrower than the host `TuiPluginApi`: kit only reads these surfaces, so
105
+ * narrower than the host `Plugin.Context`: kit only reads these surfaces, so
106
106
  * consumers can pass their real context directly. */
107
107
  export interface KitContext {
108
108
  readonly storage: KitStorage
@@ -117,9 +117,20 @@ export interface KitContext {
117
117
  // Structural message/model shapes the defensive readers walk
118
118
  // ---------------------------------------------------------------------------
119
119
 
120
- /** A message in either of the two beta-API shapes: a discriminated
121
- * `type`-tagged object or `{ info: Message }` envelope. Kit's readers
122
- * (`unwrap`, `isAssistant`, `providerId`, `modelId`) accept both. */
120
+ /** A model-list entry (host `ModelInfo`) — what the model accessors accept.
121
+ * Deliberately omits `cost`, which is a number on messages but an array on
122
+ * model entries, so the same accessors serve both. */
123
+ export interface KitModelShape {
124
+ readonly id?: string
125
+ readonly modelID?: string
126
+ readonly providerID?: string
127
+ readonly name?: string
128
+ readonly model?: { readonly providerID?: string; readonly modelID?: string; readonly id?: string }
129
+ }
130
+
131
+ /** A message in either of the two v2 shapes: a discriminated `type`-tagged
132
+ * object or `{ info: Message }` envelope. Kit's readers (`unwrap`,
133
+ * `isAssistant`, `providerId`, `modelId`) accept both. */
123
134
  export interface KitMessageShape {
124
135
  readonly type?: string
125
136
  readonly role?: string
package/src/providers.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  // Shared provider vocabulary for OpenCode sidebar widgets.
2
2
  // Provider IDs are the OpenCode workspace provider ids ("opencode" = Zen,
3
3
  // "opencode-go" = Go).
4
- import { readFileSync } from "node:fs"
4
+ import { existsSync, readFileSync } from "node:fs"
5
5
  import { homedir } from "node:os"
6
6
  import { join } from "node:path"
7
+ import { createRequire } from "node:module"
7
8
  import { createSignal } from "solid-js"
8
9
  import { connectedProviderIds } from "./schemas.ts"
9
- import type { KitContext, KitMessageShape } from "./host.ts"
10
+ import type { KitContext, KitMessageShape, KitModelShape } from "./host.ts"
10
11
 
11
12
  export const ZEN_PROVIDER = "opencode"
12
13
  export const GO_PROVIDER = "opencode-go"
@@ -52,17 +53,17 @@ export function asArray<T = any>(out: unknown): T[] {
52
53
  }
53
54
 
54
55
  /** Model id from either a model-list object or a message/message-part shape. */
55
- export function modelId(m: KitMessageShape): string {
56
+ export function modelId(m: KitModelShape): string {
56
57
  return m?.model?.modelID ?? m?.modelID ?? m?.model?.id ?? m?.id ?? ""
57
58
  }
58
59
 
59
60
  /** Provider id from either a model-list object or a message/message-part shape. */
60
- export function providerId(m: KitMessageShape): string {
61
+ export function providerId(m: KitModelShape): string {
61
62
  return m?.model?.providerID ?? m?.providerID ?? ""
62
63
  }
63
64
 
64
65
  /** Display name, falling back to the model id when the API omits `name`. */
65
- export function modelName(m: KitMessageShape): string {
66
+ export function modelName(m: KitModelShape): string {
66
67
  return m?.name ?? modelId(m)
67
68
  }
68
69
 
@@ -71,21 +72,85 @@ export function modelName(m: KitMessageShape): string {
71
72
  // ---------------------------------------------------------------------------
72
73
 
73
74
  const AUTH_PATH = () => join(homedir(), ".local/share/opencode/auth.json")
75
+ const DB_PATH = () => join(homedir(), ".local/share/opencode/opencode.db")
74
76
 
75
- /** Single defensive read of auth.json, keyed by provider id.
76
- * Every consumer shares this parse so the path and shape handling can't drift. */
77
+ /** Run a query with either SQLite driver's API and return rows. */
78
+ function queryRows(db: any, sql: string): any[] {
79
+ try {
80
+ return db.query(sql).all()
81
+ } catch {}
82
+ try {
83
+ return db.prepare(sql).all()
84
+ } catch {}
85
+ return []
86
+ }
87
+
88
+ /** Open OpenCode 2's SQLite store read-only, resolving the driver across
89
+ * Bun (`bun:sqlite`) and Node 22+ (`node:sqlite`). Returns null when the
90
+ * store is absent or no driver is available. */
91
+ function openCredentialDb(): any | null {
92
+ if (!existsSync(DB_PATH())) return null
93
+ const attempts: Array<() => any> = [
94
+ () => (import.meta as any).require?.("bun:sqlite"),
95
+ () => (globalThis as any).require?.("bun:sqlite"),
96
+ () => createRequire(import.meta.url)("bun:sqlite"),
97
+ () => (Function("return require")() as any)("bun:sqlite"),
98
+ ]
99
+ for (const attempt of attempts) {
100
+ try {
101
+ const { Database } = attempt()
102
+ if (Database) return new Database(DB_PATH(), { readonly: true })
103
+ } catch {}
104
+ }
105
+ try {
106
+ const { DatabaseSync } = createRequire(import.meta.url)("node:sqlite")
107
+ if (DatabaseSync) return new DatabaseSync(DB_PATH(), { readOnly: true })
108
+ } catch {}
109
+ return null
110
+ }
111
+
112
+ let _dbCache: { at: number; keys: Record<string, string> } | null = null
113
+
114
+ /** API keys from OpenCode 2's SQLite `credential` table (integration_id →
115
+ * key). Cached briefly because `readAuth` runs on every render. */
116
+ function readAuthDb(): Record<string, string> {
117
+ const now = Date.now()
118
+ if (_dbCache && now - _dbCache.at < 30_000) return _dbCache.keys
119
+ const keys: Record<string, string> = {}
120
+ try {
121
+ const db = openCredentialDb()
122
+ if (db) {
123
+ for (const row of queryRows(db, "SELECT integration_id, value FROM credential")) {
124
+ const id = String(row?.integration_id ?? "").trim()
125
+ try {
126
+ const key = JSON.parse(String(row?.value ?? ""))?.key
127
+ if (id && typeof key === "string" && key.trim()) keys[id] = key.trim()
128
+ } catch {}
129
+ }
130
+ try {
131
+ db.close?.()
132
+ } catch {}
133
+ }
134
+ } catch {}
135
+ _dbCache = { at: now, keys }
136
+ return keys
137
+ }
138
+
139
+ /** Single defensive read of the auth sources, keyed by provider id.
140
+ * OpenCode 2 keeps credentials in its SQLite store; `auth.json` is the
141
+ * legacy V1 store. Both are read (SQLite wins on conflict) so consumers see
142
+ * the same keys regardless of OpenCode version. */
77
143
  export function readAuth(): Record<string, string> {
144
+ const out: Record<string, string> = {}
78
145
  try {
79
146
  const auth = JSON.parse(readFileSync(AUTH_PATH(), "utf8"))
80
- const out: Record<string, string> = {}
81
147
  for (const [id, cfg] of Object.entries(auth)) {
82
148
  const key = (cfg as any)?.key
83
149
  if (typeof key === "string" && key.trim()) out[id] = key.trim()
84
150
  }
85
- return out
86
- } catch {
87
- return {}
88
- }
151
+ } catch {}
152
+ for (const [id, key] of Object.entries(readAuthDb())) out[id] = key
153
+ return out
89
154
  }
90
155
 
91
156
  /** The API key for one provider, or "" when absent. */
package/src/toast.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // The host's toast UI is optional at the edges (beta API): every plugin
4
4
  // hand-rolled the same try/catch fallback around `ui.toast.show`.
5
- import type { KitContext, KitUI, ToastInput } from "./host.ts"
5
+ import type { KitUI, ToastInput } from "./host.ts"
6
6
 
7
7
  /** Show a toast; silently no-ops when the host has no toast UI or it throws.
8
8
  * Takes a minimal structural slice — the full host context's `ui.slot` is
package/src/viewPicker.ts CHANGED
@@ -131,7 +131,7 @@ export function createViewPicker<T extends PickerOption>(context: KitContext, co
131
131
  try {
132
132
  const selected = await context.ui.dialog.select({
133
133
  title: config.dialog.title,
134
- message: config.dialog.message,
134
+ placeholder: config.dialog.message,
135
135
  current: currentID(),
136
136
  options: registry.filter(selectable).map((e) => ({
137
137
  title: e.title,
package/tsconfig.json CHANGED
@@ -11,6 +11,5 @@
11
11
  "jsxImportSource": "@opentui/solid",
12
12
  "types": ["node"]
13
13
  },
14
- "include": ["src", "tests"],
15
- "exclude": ["tests/**/*.test-d.ts"]
14
+ "include": ["src", "tests"]
16
15
  }