osborn 0.5.3 → 0.8.0
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/.claude/settings.local.json +9 -0
- package/.claude/skills/markdown-to-pdf/SKILL.md +29 -0
- package/.claude/skills/pdf-to-markdown/SKILL.md +28 -0
- package/.claude/skills/playwright-browser/SKILL.md +90 -0
- package/.claude/skills/shadcn/SKILL.md +232 -0
- package/.claude/skills/shadcn/image.png +0 -0
- package/.claude/skills/youtube-transcript/SKILL.md +24 -0
- package/.dockerignore +13 -0
- package/Dockerfile +103 -0
- package/deploy.sh +70 -0
- package/dist/claude-auth.d.ts +60 -0
- package/dist/claude-auth.js +334 -0
- package/dist/claude-llm.d.ts +51 -2
- package/dist/claude-llm.js +619 -86
- package/dist/config.d.ts +5 -1
- package/dist/config.js +4 -1
- package/dist/fast-brain.d.ts +70 -16
- package/dist/fast-brain.js +662 -99
- package/dist/index-3-2-26-legacy.d.ts +1 -0
- package/dist/index-3-2-26-legacy.js +2233 -0
- package/dist/index.js +979 -429
- package/dist/jsonl-search.d.ts +66 -0
- package/dist/jsonl-search.js +274 -0
- package/dist/leagcyprompts2.d.ts +0 -0
- package/dist/leagcyprompts2.js +573 -0
- package/dist/pipeline-direct-llm.d.ts +77 -0
- package/dist/pipeline-direct-llm.js +221 -0
- package/dist/pipeline-fastbrain.d.ts +45 -0
- package/dist/pipeline-fastbrain.js +373 -0
- package/dist/prompts-2-25-26.d.ts +0 -0
- package/dist/prompts-2-25-26.js +518 -0
- package/dist/prompts-3-2-26.d.ts +78 -0
- package/dist/prompts-3-2-26.js +1319 -0
- package/dist/prompts.d.ts +83 -12
- package/dist/prompts.js +2064 -587
- package/dist/recall-client.d.ts +33 -0
- package/dist/recall-client.js +101 -0
- package/dist/session-access.d.ts +24 -0
- package/dist/session-access.js +74 -0
- package/dist/summary-index.d.ts +87 -0
- package/dist/summary-index.js +570 -0
- package/dist/turn-detector-shim.d.ts +24 -0
- package/dist/turn-detector-shim.js +83 -0
- package/dist/voice-io.d.ts +15 -5
- package/dist/voice-io.js +52 -20
- package/fly.toml +30 -0
- package/package.json +18 -13
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Skill: Markdown to PDF
|
|
2
|
+
|
|
3
|
+
Export Markdown documents as formatted PDF files.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
When the user wants to create a PDF from a Markdown file, spec, or research findings.
|
|
7
|
+
|
|
8
|
+
## How to execute
|
|
9
|
+
|
|
10
|
+
Option 1 — Using md-to-pdf (best quality):
|
|
11
|
+
```bash
|
|
12
|
+
npx --yes md-to-pdf "<MARKDOWN_PATH>"
|
|
13
|
+
```
|
|
14
|
+
This creates a PDF alongside the source file with the same name.
|
|
15
|
+
|
|
16
|
+
Option 2 — Using pandoc (if available):
|
|
17
|
+
```bash
|
|
18
|
+
pandoc "<MARKDOWN_PATH>" -o "<OUTPUT_PATH>.pdf" --pdf-engine=wkhtmltopdf
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Option 3 — Using markdown-pdf:
|
|
22
|
+
```bash
|
|
23
|
+
npx --yes markdown-pdf "<MARKDOWN_PATH>" -o "<OUTPUT_PATH>.pdf"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
- Save the PDF to the session workspace (e.g., `library/{name}.pdf`)
|
|
28
|
+
- Confirm the output path and file size to the user
|
|
29
|
+
- If the source is spec.md, name the output `spec-export.pdf`
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Skill: PDF to Markdown
|
|
2
|
+
|
|
3
|
+
Convert PDF documents to readable Markdown text.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
When the user provides a PDF file path and wants to read, search, or work with its contents.
|
|
7
|
+
|
|
8
|
+
## How to execute
|
|
9
|
+
|
|
10
|
+
Option 1 — Using the built-in Read tool:
|
|
11
|
+
The Read tool can directly read PDF files. Use `pages` parameter for large PDFs (max 20 pages per request).
|
|
12
|
+
|
|
13
|
+
Option 2 — Full extraction via CLI (for better formatting or batch processing):
|
|
14
|
+
```bash
|
|
15
|
+
npx --yes pdf-parse-cli "<PDF_PATH>"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Option 3 — Using pdftotext (if available):
|
|
19
|
+
```bash
|
|
20
|
+
pdftotext -layout "<PDF_PATH>" -
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Output
|
|
24
|
+
Save the converted content to the session workspace as `library/{filename}.md` with:
|
|
25
|
+
- Document title and source path at the top
|
|
26
|
+
- Preserved heading structure where detectable
|
|
27
|
+
- Tables converted to Markdown tables where possible
|
|
28
|
+
- Page numbers as section markers
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Skill: Playwright Browser Automation
|
|
2
|
+
|
|
3
|
+
Automate web browser interactions — navigate pages, click buttons, fill forms, take screenshots, and extract content.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
- Navigate to a URL and interact with it
|
|
7
|
+
- Click buttons or links by their text or role
|
|
8
|
+
- Fill form fields and submit data
|
|
9
|
+
- Take screenshots of web pages
|
|
10
|
+
- Extract text or structured data from pages
|
|
11
|
+
- Automate multi-step web workflows (e.g. join a room, test a UI flow)
|
|
12
|
+
|
|
13
|
+
## How to execute
|
|
14
|
+
|
|
15
|
+
Uses `@playwright/cli` via npx — no global install needed. Token-efficient: uses element references (e.g. `e15`) instead of pixel coordinates.
|
|
16
|
+
|
|
17
|
+
### First time only — install browser binaries
|
|
18
|
+
```bash
|
|
19
|
+
npx playwright install chromium
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Step 1 — Open a URL
|
|
23
|
+
```bash
|
|
24
|
+
npx @playwright/cli open https://localhost:3000
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Step 2 — Get page structure and element references
|
|
28
|
+
```bash
|
|
29
|
+
npx @playwright/cli snapshot
|
|
30
|
+
```
|
|
31
|
+
Returns an accessibility tree with element IDs like e1, e2, e15. Use these in subsequent commands.
|
|
32
|
+
|
|
33
|
+
### Step 3 — Interact with elements
|
|
34
|
+
```bash
|
|
35
|
+
npx @playwright/cli click e15
|
|
36
|
+
npx @playwright/cli fill e3 "some text"
|
|
37
|
+
npx @playwright/cli press e3 Enter
|
|
38
|
+
npx @playwright/cli select e7 "optionValue"
|
|
39
|
+
npx @playwright/cli check e9
|
|
40
|
+
npx @playwright/cli hover e12
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Take a screenshot
|
|
44
|
+
```bash
|
|
45
|
+
npx @playwright/cli screenshot --path=/tmp/page.png
|
|
46
|
+
```
|
|
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
|
+
|
|
54
|
+
### Close the browser
|
|
55
|
+
```bash
|
|
56
|
+
npx @playwright/cli close
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Named sessions (persistent state across commands)
|
|
60
|
+
```bash
|
|
61
|
+
npx @playwright/cli -s=myflow open https://localhost:3000
|
|
62
|
+
npx @playwright/cli -s=myflow snapshot
|
|
63
|
+
npx @playwright/cli -s=myflow fill e3 "abc123"
|
|
64
|
+
npx @playwright/cli -s=myflow click e5
|
|
65
|
+
npx @playwright/cli -s=myflow close
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Complete example — join Osborn voice room
|
|
69
|
+
```bash
|
|
70
|
+
npx @playwright/cli open http://localhost:3000
|
|
71
|
+
npx @playwright/cli snapshot
|
|
72
|
+
npx @playwright/cli fill e3 "abc123"
|
|
73
|
+
npx @playwright/cli click e4
|
|
74
|
+
npx @playwright/cli screenshot --path=/tmp/osborn-joined.png
|
|
75
|
+
npx @playwright/cli close
|
|
76
|
+
```
|
|
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
|
+
|
|
85
|
+
## Notes
|
|
86
|
+
- Runs headless by default. Add --headed to see the browser window.
|
|
87
|
+
- Install browsers first if needed: npx playwright install chromium
|
|
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
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Skill: YouTube Transcript
|
|
2
|
+
|
|
3
|
+
Fetch and save transcripts from YouTube videos.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
When the user asks to get a transcript, subtitles, captions, or summary from a YouTube video URL.
|
|
7
|
+
|
|
8
|
+
## How to execute
|
|
9
|
+
|
|
10
|
+
yt-dlp is installed on this system. Use this exact command:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
yt-dlp --skip-download --write-auto-sub --sub-lang en --convert-subs srt -o "/tmp/yt-%(id)s" "<VIDEO_URL>"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This downloads auto-generated English subtitles as an SRT file to /tmp/yt-{video-id}.en.srt
|
|
17
|
+
|
|
18
|
+
Then read the SRT file and strip the timing markers to get clean transcript text.
|
|
19
|
+
|
|
20
|
+
## Output
|
|
21
|
+
Save the cleaned transcript to the session workspace as `library/youtube-{video-id}-transcript.md` with:
|
|
22
|
+
- Video title and URL at the top
|
|
23
|
+
- Cleaned transcript text (strip SRT timing markers and duplicate lines)
|
|
24
|
+
- Key timestamps preserved as section markers if meaningful breaks exist
|
package/.dockerignore
ADDED
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/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 --json` (most reliable).
|
|
33
|
+
* Uses execSync to avoid node-pty PATH issues on macOS.
|
|
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
|
+
}>;
|