osborn 0.5.5 → 0.8.1

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.
@@ -45,6 +45,12 @@ npx @playwright/cli hover e12
45
45
  npx @playwright/cli screenshot --path=/tmp/page.png
46
46
  ```
47
47
 
48
+ ### Take a screenshot at a specific viewport size (mobile check)
49
+ ```bash
50
+ npx @playwright/cli screenshot --viewport-size=375,812 --path=/tmp/page-mobile.png
51
+ ```
52
+ Common mobile sizes: `375,812` (iPhone 14), `390,844` (iPhone 14 Pro), `412,915` (Pixel 7), `768,1024` (iPad).
53
+
48
54
  ### Close the browser
49
55
  ```bash
50
56
  npx @playwright/cli close
@@ -69,7 +75,16 @@ npx @playwright/cli screenshot --path=/tmp/osborn-joined.png
69
75
  npx @playwright/cli close
70
76
  ```
71
77
 
78
+ ## Complete example — check mobile layout
79
+ ```bash
80
+ npx @playwright/cli open http://localhost:3000
81
+ npx @playwright/cli screenshot --viewport-size=375,812 --path=/tmp/mobile-375.png
82
+ npx @playwright/cli close
83
+ ```
84
+
72
85
  ## Notes
73
86
  - Runs headless by default. Add --headed to see the browser window.
74
87
  - Install browsers first if needed: npx playwright install chromium
75
88
  - Element IDs are session-scoped — run snapshot again after page changes
89
+ - Use `--viewport-size=WIDTH,HEIGHT` to simulate mobile screen sizes (e.g. `375,812` for iPhone 14)
90
+ - Use `--storage-state=/tmp/state.json` to save and restore session state (cookies, localStorage) across runs
@@ -0,0 +1,232 @@
1
+ # Skill: shadcn/ui Components
2
+
3
+ Add and configure shadcn/ui components in a Next.js or React project.
4
+
5
+ ## When to use
6
+ When the user wants to add UI components (buttons, dialogs, cards, forms, tables, etc.) using shadcn/ui — the copy-paste component library built on Radix UI and Tailwind CSS.
7
+
8
+ ## Setup (first time only)
9
+
10
+ Initialize shadcn in the project root (where package.json lives):
11
+ ```bash
12
+ npx shadcn@latest init
13
+ ```
14
+
15
+ This creates `components.json` and sets up `src/components/ui/`. Answer the prompts to match your project's style preferences.
16
+
17
+ ## Add a component
18
+
19
+ ```bash
20
+ npx shadcn@latest add <component-name>
21
+ ```
22
+
23
+ ## Add multiple components at once
24
+ ```bash
25
+ npx shadcn@latest add button card dialog input form
26
+ ```
27
+
28
+ ## Commonly used components
29
+
30
+ | Component | Install name | Description |
31
+ |-----------|-------------|-------------|
32
+ | Button | `button` | Clickable button with variants |
33
+ | Card | `card` | Container with header/content/footer |
34
+ | Input | `input` | Text input field |
35
+ | Label | `label` | Form label |
36
+ | Textarea | `textarea` | Multi-line text input |
37
+ | Select | `select` | Dropdown select |
38
+ | Checkbox | `checkbox` | Checkbox with label |
39
+ | Switch | `switch` | Toggle switch |
40
+ | Dialog | `dialog` | Modal dialog |
41
+ | Sheet | `sheet` | Slide-in panel (drawer) |
42
+ | Popover | `popover` | Floating content panel |
43
+ | Tooltip | `tooltip` | Hover tooltip |
44
+ | Dropdown Menu | `dropdown-menu` | Contextual dropdown |
45
+ | Command | `command` | Command palette / search |
46
+ | Badge | `badge` | Small status indicator |
47
+ | Avatar | `avatar` | User avatar with fallback |
48
+ | Separator | `separator` | Visual divider |
49
+ | Table | `table` | Data table |
50
+ | Tabs | `tabs` | Tabbed interface |
51
+ | Accordion | `accordion` | Collapsible sections |
52
+ | Sonner | `sonner` | Toast notifications (preferred over toast) |
53
+ | Alert | `alert` | Inline alert message |
54
+ | Alert Dialog | `alert-dialog` | Confirmation dialog |
55
+ | Progress | `progress` | Progress bar |
56
+ | Skeleton | `skeleton` | Loading placeholder |
57
+ | Scroll Area | `scroll-area` | Custom scrollbar container |
58
+ | Calendar | `calendar` | Date picker calendar |
59
+ | Form | `form` | React Hook Form integration |
60
+ | Slider | `slider` | Range slider |
61
+ | Toggle | `toggle` | Toggle button |
62
+ | Navigation Menu | `navigation-menu` | Site navigation |
63
+ | Breadcrumb | `breadcrumb` | Page navigation breadcrumbs |
64
+ | Collapsible | `collapsible` | Expandable/collapsible section |
65
+ | Context Menu | `context-menu` | Right-click context menu |
66
+ | Menubar | `menubar` | Application menu bar |
67
+ | Resizable | `resizable` | Resizable panel groups |
68
+
69
+ ## Import pattern
70
+
71
+ ```tsx
72
+ import { Button } from "@/components/ui/button"
73
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"
74
+ import { Input } from "@/components/ui/input"
75
+ import { Label } from "@/components/ui/label"
76
+ import {
77
+ Dialog,
78
+ DialogContent,
79
+ DialogDescription,
80
+ DialogHeader,
81
+ DialogTitle,
82
+ DialogTrigger,
83
+ } from "@/components/ui/dialog"
84
+ ```
85
+
86
+ ## Example — Button variants
87
+
88
+ ```tsx
89
+ <Button>Default</Button>
90
+ <Button variant="destructive">Delete</Button>
91
+ <Button variant="outline">Cancel</Button>
92
+ <Button variant="ghost">Ghost</Button>
93
+ <Button variant="link">Link</Button>
94
+ <Button size="sm">Small</Button>
95
+ <Button size="lg">Large</Button>
96
+ <Button disabled>Disabled</Button>
97
+ ```
98
+
99
+ ## Example — Card
100
+
101
+ ```tsx
102
+ <Card>
103
+ <CardHeader>
104
+ <CardTitle>Card Title</CardTitle>
105
+ <CardDescription>Card description goes here</CardDescription>
106
+ </CardHeader>
107
+ <CardContent>
108
+ <p>Card content here</p>
109
+ </CardContent>
110
+ <CardFooter>
111
+ <Button>Action</Button>
112
+ </CardFooter>
113
+ </Card>
114
+ ```
115
+
116
+ ## Example — Form with validation (React Hook Form + Zod)
117
+
118
+ ```bash
119
+ npx shadcn@latest add form input
120
+ npm install zod react-hook-form @hookform/resolvers
121
+ ```
122
+
123
+ ```tsx
124
+ import { useForm } from "react-hook-form"
125
+ import { zodResolver } from "@hookform/resolvers/zod"
126
+ import * as z from "zod"
127
+ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
128
+ import { Input } from "@/components/ui/input"
129
+ import { Button } from "@/components/ui/button"
130
+
131
+ const formSchema = z.object({
132
+ email: z.string().email("Invalid email address"),
133
+ password: z.string().min(8, "Password must be at least 8 characters"),
134
+ })
135
+
136
+ export function LoginForm() {
137
+ const form = useForm<z.infer<typeof formSchema>>({
138
+ resolver: zodResolver(formSchema),
139
+ defaultValues: { email: "", password: "" },
140
+ })
141
+
142
+ function onSubmit(values: z.infer<typeof formSchema>) {
143
+ console.log(values)
144
+ }
145
+
146
+ return (
147
+ <Form {...form}>
148
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
149
+ <FormField
150
+ control={form.control}
151
+ name="email"
152
+ render={({ field }) => (
153
+ <FormItem>
154
+ <FormLabel>Email</FormLabel>
155
+ <FormControl>
156
+ <Input placeholder="you@example.com" {...field} />
157
+ </FormControl>
158
+ <FormMessage />
159
+ </FormItem>
160
+ )}
161
+ />
162
+ <FormField
163
+ control={form.control}
164
+ name="password"
165
+ render={({ field }) => (
166
+ <FormItem>
167
+ <FormLabel>Password</FormLabel>
168
+ <FormControl>
169
+ <Input type="password" {...field} />
170
+ </FormControl>
171
+ <FormMessage />
172
+ </FormItem>
173
+ )}
174
+ />
175
+ <Button type="submit" className="w-full">Sign in</Button>
176
+ </form>
177
+ </Form>
178
+ )
179
+ }
180
+ ```
181
+
182
+ ## Example — Toast notifications (Sonner)
183
+
184
+ ```bash
185
+ npx shadcn@latest add sonner
186
+ ```
187
+
188
+ ```tsx
189
+ // In your root layout — add the Toaster once
190
+ import { Toaster } from "@/components/ui/sonner"
191
+ export default function RootLayout({ children }) {
192
+ return (
193
+ <html>
194
+ <body>
195
+ {children}
196
+ <Toaster />
197
+ </body>
198
+ </html>
199
+ )
200
+ }
201
+
202
+ // Then anywhere in your app
203
+ import { toast } from "sonner"
204
+
205
+ toast("Event created")
206
+ toast.success("Profile saved")
207
+ toast.error("Something went wrong")
208
+ toast.promise(saveData(), {
209
+ loading: "Saving...",
210
+ success: "Saved!",
211
+ error: "Failed to save",
212
+ })
213
+ ```
214
+
215
+ ## The cn() utility
216
+
217
+ shadcn uses `clsx` + `tailwind-merge` via a `cn()` helper for conditional classes:
218
+
219
+ ```tsx
220
+ import { cn } from "@/lib/utils"
221
+
222
+ <div className={cn("base-class", isActive && "active-class", className)} />
223
+ ```
224
+
225
+ ## Notes
226
+ - Components are **copied into your project** (not a node_modules dependency) — edit them freely
227
+ - Requires **Tailwind CSS** configured in the project
228
+ - Uses **Radix UI** primitives for accessibility
229
+ - Components land in `src/components/ui/` by default
230
+ - Run `npx shadcn@latest diff` to see if upstream components have changed
231
+ - Check `components.json` for path and style config
232
+ - Use Sonner (`sonner`) instead of the legacy `toast` component for notifications
Binary file
package/.dockerignore ADDED
@@ -0,0 +1,13 @@
1
+ node_modules
2
+ dist
3
+ .osborn
4
+ .env
5
+ .env.*
6
+ *.log
7
+ .git
8
+ .playwright-cli
9
+
10
+ # Exclude sensitive Claude config but keep skills (shipped as defaults)
11
+ .claude/settings*
12
+ .claude/projects
13
+ .claude/credentials*
package/Dockerfile ADDED
@@ -0,0 +1,103 @@
1
+ # syntax = docker/dockerfile:1
2
+ #
3
+ # Osborn Agent — Fly.io Deployment
4
+ # Self-hosted voice AI research assistant with Claude Code CLI
5
+ #
6
+ # Build: fly deploy
7
+ # Local: docker build -t osborn-agent . && docker run -p 8741:8741 --env-file .env osborn-agent
8
+
9
+ ARG NODE_VERSION=20
10
+ FROM node:${NODE_VERSION}-slim AS base
11
+ WORKDIR /app
12
+ ENV NODE_ENV=production
13
+
14
+ # ── Build stage: native deps (node-pty, ripgrep) ──
15
+ FROM base AS build
16
+
17
+ RUN apt-get update -qq && \
18
+ apt-get install --no-install-recommends -y \
19
+ build-essential \
20
+ python-is-python3 \
21
+ pkg-config \
22
+ curl \
23
+ git && \
24
+ rm -rf /var/lib/apt/lists/*
25
+
26
+ COPY package*.json ./
27
+ RUN npm ci
28
+
29
+ COPY . .
30
+
31
+ # ── Final stage: lean runtime ──
32
+ FROM base
33
+
34
+ # Runtime deps: python for node-pty, git for Claude Code, curl for health checks,
35
+ # ca-certificates for HTTPS (LiveKit, Deepgram, Anthropic API connections)
36
+ RUN apt-get update -qq && \
37
+ apt-get install --no-install-recommends -y \
38
+ python-is-python3 \
39
+ ca-certificates \
40
+ curl \
41
+ git && \
42
+ rm -rf /var/lib/apt/lists/*
43
+
44
+ # Claude Code CLI — installed globally so it's on PATH for the agent SDK
45
+ RUN npm install -g @anthropic-ai/claude-code
46
+
47
+ # Playwright Chromium browser for browser automation skill
48
+ RUN npx playwright install chromium --with-deps 2>/dev/null || true
49
+
50
+ # Copy built app from build stage
51
+ COPY --from=build /app /app
52
+
53
+ # Persistent workspace volume mount point (Fly.io mounts here)
54
+ RUN mkdir -p /workspace
55
+
56
+ # Config
57
+ ENV HOST=0.0.0.0
58
+ ENV PORT=8741
59
+ ENV OSBORN_CWD=/workspace
60
+
61
+ EXPOSE 8741
62
+
63
+ # Entrypoint script: sets up Claude credential symlinks, onboarding bypass, then starts agent
64
+ COPY <<'ENTRYPOINT' /entrypoint.sh
65
+ #!/bin/sh
66
+ set -e
67
+
68
+ # ── Claude credential persistence ──
69
+ # /workspace is the Fly.io persistent volume. Credentials survive deploys/restarts.
70
+ # Remove any stale symlink or directory, then create fresh symlink.
71
+ mkdir -p /workspace/.claude
72
+ rm -rf /root/.claude
73
+ ln -sf /workspace/.claude /root/.claude
74
+
75
+ # ── Claude onboarding suppression (learned from claudebox) ──
76
+ # Claude Code shows 5+ interactive prompts on first run. Pre-write config to skip all of them.
77
+ # Must write to ALL THREE config paths (different CLI versions check different files).
78
+ ONBOARDING_JSON='{"numStartups":10,"installMethod":"npm","autoUpdates":false,"hasCompletedOnboarding":true,"hasTrustDialogAccepted":true,"hasTrustDialogHooksAccepted":true,"hasCompletedProjectOnboarding":true,"hasAcknowledgedCostThreshold":true,"effortCalloutV2Dismissed":true,"theme":"dark","projects":{"/workspace":{"hasTrustDialogAccepted":true,"hasTrustDialogHooksAccepted":true,"hasCompletedProjectOnboarding":true}}}'
79
+
80
+ echo "$ONBOARDING_JSON" > /root/.claude.json
81
+ mkdir -p /workspace/.claude
82
+ echo "$ONBOARDING_JSON" > /workspace/.claude/.config.json
83
+ echo "$ONBOARDING_JSON" > /workspace/.claude/claude.json
84
+
85
+ # ── Session workspace persistence ──
86
+ # Session workspace (.osborn/sessions/) defaults to sessionBaseDir (/app).
87
+ # Symlink to volume so spec.md, library/ files survive deploys.
88
+ mkdir -p /workspace/.osborn
89
+ rm -rf /app/.osborn
90
+ ln -sf /workspace/.osborn /app/.osborn
91
+
92
+ # ── Restore CLAUDE_CODE_OAUTH_TOKEN from persisted volume ──
93
+ if [ -f /workspace/.claude/.oauth-token ]; then
94
+ export CLAUDE_CODE_OAUTH_TOKEN="$(cat /workspace/.claude/.oauth-token)"
95
+ echo "✅ Restored CLAUDE_CODE_OAUTH_TOKEN from volume"
96
+ fi
97
+
98
+ # ── Start Osborn agent ──
99
+ exec node --import tsx/esm src/index.ts
100
+ ENTRYPOINT
101
+ RUN chmod +x /entrypoint.sh
102
+
103
+ CMD ["/entrypoint.sh"]
package/bin/cli.js CHANGED
@@ -41,10 +41,6 @@ Example:
41
41
  process.exit(0)
42
42
  }
43
43
 
44
- // Run the agent using tsx
45
- const agentPath = join(__dirname, '..', 'src', 'index.ts')
46
- const tsxPath = join(__dirname, '..', 'node_modules', '.bin', 'tsx')
47
-
48
44
  // Determine mode (default to 'dev' if no mode specified)
49
45
  let mode = 'dev'
50
46
  if (args.includes('start')) {
@@ -54,11 +50,31 @@ if (args.includes('start')) {
54
50
  args.splice(args.indexOf('dev'), 1)
55
51
  }
56
52
 
57
- const child = spawn(tsxPath, [agentPath, mode, ...args], {
58
- stdio: 'inherit',
59
- cwd: join(__dirname, '..'),
60
- env: process.env,
61
- })
53
+ // Use src/index.ts (dev) if available, otherwise dist/index.js (npm install)
54
+ import { existsSync } from 'fs'
55
+ const srcPath = join(__dirname, '..', 'src', 'index.ts')
56
+ const distPath = join(__dirname, '..', 'dist', 'index.js')
57
+
58
+ let child
59
+ if (existsSync(srcPath)) {
60
+ // Dev mode: run via tsx
61
+ const tsxPath = join(__dirname, '..', 'node_modules', '.bin', 'tsx')
62
+ child = spawn(tsxPath, [srcPath, mode, ...args], {
63
+ stdio: 'inherit',
64
+ cwd: join(__dirname, '..'),
65
+ env: process.env,
66
+ })
67
+ } else if (existsSync(distPath)) {
68
+ // Production: run compiled JS directly
69
+ child = spawn('node', [distPath, mode, ...args], {
70
+ stdio: 'inherit',
71
+ cwd: join(__dirname, '..'),
72
+ env: process.env,
73
+ })
74
+ } else {
75
+ console.error('Error: Neither src/index.ts nor dist/index.js found')
76
+ process.exit(1)
77
+ }
62
78
 
63
79
  child.on('error', (err) => {
64
80
  console.error('Failed to start agent:', err.message)
package/deploy.sh ADDED
@@ -0,0 +1,70 @@
1
+ #!/bin/bash
2
+ # Osborn — Fly.io Deploy Script
3
+ # Usage: ./deploy.sh <app-name> [region]
4
+ # Example: ./deploy.sh osborn-agent-john iad
5
+ #
6
+ # Prerequisites:
7
+ # 1. flyctl installed (curl -L https://fly.io/install.sh | sh)
8
+ # 2. fly auth login
9
+ # 3. API keys ready (LIVEKIT, ANTHROPIC, GOOGLE, DEEPGRAM)
10
+
11
+ set -e
12
+
13
+ APP_NAME=${1:-"osborn-agent"}
14
+ REGION=${2:-"iad"}
15
+
16
+ echo "🚀 Deploying Osborn to Fly.io as: $APP_NAME (region: $REGION)"
17
+ echo ""
18
+
19
+ # 1. Create app (no deploy yet)
20
+ echo "📦 Creating Fly app..."
21
+ fly launch --name "$APP_NAME" --region "$REGION" --no-deploy --yes
22
+
23
+ # 2. Create persistent workspace volume
24
+ echo "💾 Creating persistent volume (5GB)..."
25
+ fly volumes create workspace --size 5 --region "$REGION" --app "$APP_NAME" --yes
26
+
27
+ # 3. Set secrets
28
+ echo ""
29
+ echo "🔑 Setting API keys..."
30
+ echo " Required: LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET"
31
+ echo " Required: DEEPGRAM_API_KEY"
32
+ echo " Optional: ANTHROPIC_API_KEY, GOOGLE_API_KEY, OPENAI_API_KEY"
33
+ echo ""
34
+
35
+ if [ -z "$LIVEKIT_URL" ]; then
36
+ echo "⚠️ Set environment variables before running, or pass them inline:"
37
+ echo " LIVEKIT_URL=wss://... LIVEKIT_API_KEY=... ./deploy.sh $APP_NAME"
38
+ echo ""
39
+ echo " Or set them manually after deploy:"
40
+ echo " fly secrets set LIVEKIT_URL=wss://... --app $APP_NAME"
41
+ echo ""
42
+ else
43
+ fly secrets set \
44
+ LIVEKIT_URL="$LIVEKIT_URL" \
45
+ LIVEKIT_API_KEY="$LIVEKIT_API_KEY" \
46
+ LIVEKIT_API_SECRET="$LIVEKIT_API_SECRET" \
47
+ DEEPGRAM_API_KEY="$DEEPGRAM_API_KEY" \
48
+ ${ANTHROPIC_API_KEY:+ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY"} \
49
+ ${GOOGLE_API_KEY:+GOOGLE_API_KEY="$GOOGLE_API_KEY"} \
50
+ ${OPENAI_API_KEY:+OPENAI_API_KEY="$OPENAI_API_KEY"} \
51
+ --app "$APP_NAME"
52
+ fi
53
+
54
+ # 4. Deploy
55
+ echo ""
56
+ echo "🏗️ Building and deploying..."
57
+ fly deploy --app "$APP_NAME"
58
+
59
+ echo ""
60
+ echo "✅ Deployed! Your Osborn agent is live at: https://$APP_NAME.fly.dev"
61
+ echo ""
62
+ echo "Next steps:"
63
+ echo " 1. Open https://osborn.app"
64
+ echo " 2. Enter the room code shown in: fly logs --app $APP_NAME"
65
+ echo " 3. On first connect, authenticate Claude via the login link"
66
+ echo ""
67
+ echo "Useful commands:"
68
+ echo " fly logs --app $APP_NAME # Watch agent logs"
69
+ echo " fly ssh console --app $APP_NAME # SSH into the container"
70
+ echo " fly secrets set KEY=val --app $APP_NAME # Add/update secrets"
@@ -0,0 +1,60 @@
1
+ /**
2
+ * claude-auth.ts — Claude Code CLI OAuth flow
3
+ *
4
+ * Handles authentication for Claude Code in headless/cloud environments.
5
+ * Learned from claudebox (etokarev/claude-code-docker, vutran1710/claudebox).
6
+ *
7
+ * Auth priority:
8
+ * 1. CLAUDE_CODE_OAUTH_TOKEN env var (set via `claude setup-token` on local machine)
9
+ * 2. ~/.claude/.credentials.json file (persisted on Fly.io volume)
10
+ * 3. `claude auth status --json` CLI check
11
+ * 4. Interactive OAuth flow via `claude setup-token` + pty
12
+ *
13
+ * On Linux/Docker, credentials go to ~/.claude/.credentials.json (file-based, no keyring).
14
+ * The Fly.io volume at /workspace/.claude is symlinked to ~/.claude for persistence.
15
+ */
16
+ export interface ClaudeAuthCallbacks {
17
+ onUrl: (url: string) => void;
18
+ onWaitingForCode: () => void;
19
+ onComplete: () => void;
20
+ onError: (message: string) => void;
21
+ onOutput?: (text: string) => void;
22
+ }
23
+ export interface ClaudeAuthHandle {
24
+ submitCode: (code: string) => void;
25
+ }
26
+ /**
27
+ * Check if credentials file exists with a valid access token.
28
+ * On Linux/Docker, Claude Code stores OAuth creds at ~/.claude/.credentials.json
29
+ */
30
+ export declare function isClaudeAuthenticated(): boolean;
31
+ /**
32
+ * Check auth via `claude auth status` (most reliable).
33
+ * Uses resolved path to avoid posix_spawnp PATH issues.
34
+ */
35
+ export declare function checkClaudeAuthStatus(): Promise<boolean>;
36
+ /**
37
+ * Run the Claude CLI OAuth flow via `claude setup-token` in a pseudo-terminal.
38
+ *
39
+ * setup-token provides the Ink UI with a "Paste code here if prompted >" input.
40
+ * Unlike `auth login` which ignores pty stdin, setup-token accepts typed input.
41
+ *
42
+ * Code is written in chunks to simulate real typing (Ink reads raw keypresses).
43
+ */
44
+ export declare function runClaudeAuthFlow(callbacks: ClaudeAuthCallbacks): {
45
+ handle: ClaudeAuthHandle;
46
+ done: Promise<void>;
47
+ };
48
+ /**
49
+ * Ensure Claude is authenticated before proceeding.
50
+ *
51
+ * Check order:
52
+ * 1. CLAUDE_CODE_OAUTH_TOKEN env var
53
+ * 2. ~/.claude/.credentials.json file
54
+ * 3. `claude auth status --json`
55
+ * 4. Interactive OAuth flow (setup-token)
56
+ */
57
+ export declare function ensureClaudeAuth(sendToFrontend: (type: string, payload: unknown) => void): Promise<{
58
+ submitCode?: (code: string) => void;
59
+ done?: Promise<void>;
60
+ }>;