osborn 0.8.34 → 0.9.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/Dockerfile.sandbox +2 -0
- package/dist/index.js +166 -7
- package/dist/meeting-output.html +32 -0
- package/package.json +2 -2
- package/.claude/settings.local.json +0 -9
- package/.claude/skills/browser-apply/SKILL.md +0 -114
- package/.claude/skills/markdown-to-pdf/SKILL.md +0 -29
- package/.claude/skills/pdf-to-markdown/SKILL.md +0 -28
- package/.claude/skills/playwright-browser/SKILL.md +0 -90
- package/.claude/skills/shadcn/SKILL.md +0 -232
- package/.claude/skills/shadcn/image.png +0 -0
- package/.claude/skills/youtube-transcript/SKILL.md +0 -24
- package/caresource-apply.js +0 -50
- package/caresource-apply.mjs +0 -34
- package/dist/conversation-brain.d.ts +0 -92
- package/dist/conversation-brain.js +0 -360
- package/dist/fast-llm.d.ts +0 -15
- package/dist/fast-llm.js +0 -81
package/Dockerfile.sandbox
CHANGED
package/dist/index.js
CHANGED
|
@@ -10,8 +10,16 @@ initializeLogger({ pretty: true, level: 'info' });
|
|
|
10
10
|
import { setMaxListeners } from 'node:events';
|
|
11
11
|
setMaxListeners(50);
|
|
12
12
|
import { createServer } from 'http';
|
|
13
|
-
import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
14
|
-
import { join } from 'node:path';
|
|
13
|
+
import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, mkdtempSync, cpSync } from 'node:fs';
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { spawn } from 'node:child_process';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
18
|
+
// Resolve __dirname for this ESM module so we can find sibling files (e.g.
|
|
19
|
+
// meeting-output.html) relative to the compiled JS location, NOT process.cwd().
|
|
20
|
+
// In production cwd is the user's workspace; the static file lives next to dist/index.js.
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
15
23
|
import { createPatch } from 'diff';
|
|
16
24
|
import { loadConfig, getMcpServers, getEnabledMcpServerNames, getVoiceMode, getRealtimeConfig, getDirectConfig, listAllClaudeSessions, getMostRecentSessionId, sessionExists, getSessionSummary, getConversationHistory, ensureSessionWorkspace, getSessionWorkspace, getMcpServerStatusList, buildMcpServersForKeys, listWorkspaceArtifacts } from './config.js';
|
|
17
25
|
import { createSTT, createTTS, createRealtimeModelFromConfig, DIRECT_MODE_STT, DIRECT_MODE_TTS } from './voice-io.js';
|
|
@@ -148,6 +156,7 @@ function startApiServer(workingDir, port) {
|
|
|
148
156
|
return;
|
|
149
157
|
}
|
|
150
158
|
const url = new URL(req.url || '/', `http://localhost:${port}`);
|
|
159
|
+
const syncToken = process.env.OSBORN_SYNC_TOKEN;
|
|
151
160
|
if (req.method === 'GET' && url.pathname === '/sessions') {
|
|
152
161
|
try {
|
|
153
162
|
const limit = parseInt(url.searchParams.get('limit') || '100', 10);
|
|
@@ -200,15 +209,35 @@ function startApiServer(workingDir, port) {
|
|
|
200
209
|
});
|
|
201
210
|
return;
|
|
202
211
|
}
|
|
203
|
-
// GET /meeting-output — Output Media webpage for Recall.ai bot audio
|
|
212
|
+
// GET /meeting-output — Output Media webpage for Recall.ai bot audio.
|
|
213
|
+
//
|
|
214
|
+
// The file lives next to this compiled JS (copied by the build script from
|
|
215
|
+
// src/ to dist/). Resolve via __dirname rather than process.cwd() — in
|
|
216
|
+
// production cwd is the user's workspace, NOT the osborn package directory.
|
|
204
217
|
if (req.method === 'GET' && url.pathname === '/meeting-output') {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
218
|
+
// Try the package-relative path first (post-build location), then fall
|
|
219
|
+
// back to source path for `tsx src/index.ts` dev runs.
|
|
220
|
+
const candidates = [
|
|
221
|
+
join(__dirname, 'meeting-output.html'), // dist/ (production)
|
|
222
|
+
join(__dirname, '..', 'src', 'meeting-output.html'), // dev: dist/ → src/
|
|
223
|
+
join(__dirname, '..', 'meeting-output.html'), // tsx run from src/
|
|
224
|
+
];
|
|
225
|
+
let html = null;
|
|
226
|
+
let foundPath = null;
|
|
227
|
+
for (const p of candidates) {
|
|
228
|
+
try {
|
|
229
|
+
html = readFileSync(p, 'utf-8');
|
|
230
|
+
foundPath = p;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
}
|
|
235
|
+
if (html) {
|
|
208
236
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
209
237
|
res.end(html);
|
|
210
238
|
}
|
|
211
|
-
|
|
239
|
+
else {
|
|
240
|
+
console.warn(`[meeting-output] not found in any of: ${candidates.join(', ')}`);
|
|
212
241
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
213
242
|
res.end('meeting-output.html not found');
|
|
214
243
|
}
|
|
@@ -268,6 +297,136 @@ function startApiServer(workingDir, port) {
|
|
|
268
297
|
console.log('[events] SSE client connected');
|
|
269
298
|
return;
|
|
270
299
|
}
|
|
300
|
+
// GET /sessions/export — stream a gzipped tar of ~/.claude/projects/ to the client
|
|
301
|
+
// Optional ?workDir= query param: if present, export only that project's slug folder.
|
|
302
|
+
if (req.method === 'GET' && url.pathname === '/sessions/export') {
|
|
303
|
+
if (syncToken) {
|
|
304
|
+
const authHeader = req.headers['authorization'] ?? '';
|
|
305
|
+
if (authHeader !== `Bearer ${syncToken}`) {
|
|
306
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
307
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const projectsDir = join(homedir(), '.claude', 'projects');
|
|
312
|
+
const workDir = url.searchParams.get('workDir');
|
|
313
|
+
if (workDir) {
|
|
314
|
+
const slug = workDir.replace(/\//g, '-');
|
|
315
|
+
const slugDir = join(projectsDir, slug);
|
|
316
|
+
if (!existsSync(slugDir)) {
|
|
317
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
318
|
+
res.end(JSON.stringify({ error: 'Project not found', slug }));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
res.writeHead(200, {
|
|
322
|
+
'Content-Type': 'application/gzip',
|
|
323
|
+
'Content-Disposition': `attachment; filename="claude-sessions-${slug}.tar.gz"`,
|
|
324
|
+
'Access-Control-Allow-Origin': '*',
|
|
325
|
+
});
|
|
326
|
+
const tar = spawn('tar', ['-czf', '-', '-C', projectsDir, slug]);
|
|
327
|
+
tar.stdout.pipe(res);
|
|
328
|
+
tar.stderr.on('data', (d) => console.error('[export]', d.toString()));
|
|
329
|
+
tar.on('close', (code) => { if (code !== 0)
|
|
330
|
+
res.destroy(); });
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!existsSync(projectsDir)) {
|
|
334
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
335
|
+
res.end(JSON.stringify({ error: 'No sessions found' }));
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
res.writeHead(200, {
|
|
339
|
+
'Content-Type': 'application/gzip',
|
|
340
|
+
'Content-Disposition': 'attachment; filename="claude-sessions.tar.gz"',
|
|
341
|
+
'Access-Control-Allow-Origin': '*',
|
|
342
|
+
});
|
|
343
|
+
// Stream tar output directly to response
|
|
344
|
+
const tar = spawn('tar', ['-czf', '-', '-C', join(homedir(), '.claude'), 'projects']);
|
|
345
|
+
tar.stdout.pipe(res);
|
|
346
|
+
tar.stderr.on('data', (d) => console.error('[export]', d.toString()));
|
|
347
|
+
tar.on('close', (code) => { if (code !== 0)
|
|
348
|
+
res.destroy(); });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// POST /sessions/import — accept a gzipped tar and extract into ~/.claude/projects/
|
|
352
|
+
if (req.method === 'POST' && url.pathname === '/sessions/import') {
|
|
353
|
+
if (syncToken) {
|
|
354
|
+
const authHeader = req.headers['authorization'] ?? '';
|
|
355
|
+
if (authHeader !== `Bearer ${syncToken}`) {
|
|
356
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
357
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const targetWorkDir = url.searchParams.get('targetWorkDir');
|
|
362
|
+
const chunks = [];
|
|
363
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
364
|
+
req.on('end', () => {
|
|
365
|
+
const body = Buffer.concat(chunks);
|
|
366
|
+
const tmpDir = mkdtempSync('/tmp/osborn-import-');
|
|
367
|
+
try {
|
|
368
|
+
// Extract archive into temp dir
|
|
369
|
+
const tarProc = spawn('tar', ['-xzf', '-', '-C', tmpDir]);
|
|
370
|
+
tarProc.stdin.write(body);
|
|
371
|
+
tarProc.stdin.end();
|
|
372
|
+
tarProc.stderr.on('data', (d) => console.error('[import]', d.toString()));
|
|
373
|
+
tarProc.on('close', (code) => {
|
|
374
|
+
if (code !== 0) {
|
|
375
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
376
|
+
res.end(JSON.stringify({ error: 'tar extraction failed', code }));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const projectsDir = join(homedir(), '.claude', 'projects');
|
|
381
|
+
mkdirSync(projectsDir, { recursive: true });
|
|
382
|
+
// The archive should contain a 'projects' subdirectory
|
|
383
|
+
const extractedProjects = join(tmpDir, 'projects');
|
|
384
|
+
const sourceDir = existsSync(extractedProjects) ? extractedProjects : tmpDir;
|
|
385
|
+
// Optionally remap slug: if targetWorkDir is provided, find slug(s)
|
|
386
|
+
// that don't match the target and rename them
|
|
387
|
+
let remapped = {};
|
|
388
|
+
if (targetWorkDir) {
|
|
389
|
+
const targetSlug = targetWorkDir.replace(/\//g, '-');
|
|
390
|
+
const sourceSlugs = readdirSync(sourceDir);
|
|
391
|
+
for (const slug of sourceSlugs) {
|
|
392
|
+
if (slug !== targetSlug && !slug.startsWith('.')) {
|
|
393
|
+
const newSlug = targetSlug;
|
|
394
|
+
remapped[slug] = newSlug;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Copy subdirectories into ~/.claude/projects/, merging without overwriting
|
|
399
|
+
let filesWritten = 0;
|
|
400
|
+
const slugsInSource = readdirSync(sourceDir);
|
|
401
|
+
for (const slug of slugsInSource) {
|
|
402
|
+
const effectiveSlug = remapped[slug] ?? slug;
|
|
403
|
+
const destSlug = join(projectsDir, effectiveSlug);
|
|
404
|
+
mkdirSync(destSlug, { recursive: true });
|
|
405
|
+
cpSync(join(sourceDir, slug), destSlug, {
|
|
406
|
+
recursive: true,
|
|
407
|
+
force: false, // don't overwrite existing files
|
|
408
|
+
errorOnExist: false,
|
|
409
|
+
});
|
|
410
|
+
filesWritten++;
|
|
411
|
+
}
|
|
412
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
413
|
+
res.end(JSON.stringify({ ok: true, filesWritten, remapped }));
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
console.error('[import] merge error:', err);
|
|
417
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
418
|
+
res.end(JSON.stringify({ error: 'Failed to merge sessions', detail: String(err) }));
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
console.error('[import] spawn error:', err);
|
|
424
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
425
|
+
res.end(JSON.stringify({ error: 'Failed to start extraction', detail: String(err) }));
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
271
430
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
272
431
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
273
432
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head><title>Osborn Meeting Output</title></head>
|
|
4
|
+
<body>
|
|
5
|
+
<script>
|
|
6
|
+
const botId = new URLSearchParams(window.location.search).get('bot_id') || 'unknown'
|
|
7
|
+
|
|
8
|
+
function connect() {
|
|
9
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
10
|
+
const ws = new WebSocket(`${protocol}//${window.location.host}/meeting-audio?bot_id=${botId}`)
|
|
11
|
+
|
|
12
|
+
ws.onmessage = async (event) => {
|
|
13
|
+
try {
|
|
14
|
+
const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
|
|
15
|
+
const arrayBuffer = await event.data.arrayBuffer()
|
|
16
|
+
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer)
|
|
17
|
+
const source = audioCtx.createBufferSource()
|
|
18
|
+
source.buffer = audioBuffer
|
|
19
|
+
source.connect(audioCtx.destination)
|
|
20
|
+
source.start()
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.error('Audio playback error:', e)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ws.onclose = () => setTimeout(connect, 1000)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
connect()
|
|
30
|
+
</script>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osborn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"dev:logged": "tsx scripts/dev-logged.ts",
|
|
12
12
|
"review": "tsx scripts/review.ts",
|
|
13
13
|
"start": "tsx src/index.ts",
|
|
14
|
-
"build": "tsc && rm -rf dist/prompts && cp -r src/prompts dist/prompts",
|
|
14
|
+
"build": "tsc && rm -rf dist/prompts && cp -r src/prompts dist/prompts && cp src/meeting-output.html dist/",
|
|
15
15
|
"room": "tsx src/index.ts --room",
|
|
16
16
|
"prepublishOnly": "npm run build"
|
|
17
17
|
},
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# Skill: Browser Apply — Step-by-Step Workday Application
|
|
2
|
-
|
|
3
|
-
Automate Workday job applications interactively, one step at a time. Each step takes a screenshot, confirms what's on screen, fills the current page, and waits before proceeding.
|
|
4
|
-
|
|
5
|
-
**This skill uses the Playwright MCP tools** (`mcp__playwright__browser_*`) for direct browser control — no scripts needed.
|
|
6
|
-
|
|
7
|
-
## When to Use
|
|
8
|
-
- Any Workday ATS application (`*.wd1.myworkdayjobs.com`)
|
|
9
|
-
- Any multi-step JS-heavy job application form
|
|
10
|
-
- When you want visible, confirmable progress at each step
|
|
11
|
-
|
|
12
|
-
## Key Principle: Step-by-Step, Not One Big Script
|
|
13
|
-
|
|
14
|
-
Do NOT write a monolithic automation script. Instead:
|
|
15
|
-
1. Navigate to the URL
|
|
16
|
-
2. Take a screenshot — confirm what's on screen
|
|
17
|
-
3. Fill only the current step's fields
|
|
18
|
-
4. Take another screenshot — confirm fields filled correctly
|
|
19
|
-
5. Ask the user "Ready for next step?" before clicking Next
|
|
20
|
-
6. Click Next, wait for page load, screenshot again
|
|
21
|
-
7. Repeat for each step
|
|
22
|
-
|
|
23
|
-
This approach catches rendering issues, unexpected fields, and errors before they cascade.
|
|
24
|
-
|
|
25
|
-
## Step-by-Step Execution Pattern
|
|
26
|
-
|
|
27
|
-
### Step 0 — Open the browser
|
|
28
|
-
Use: `mcp__playwright__browser_navigate` with the applyManually URL
|
|
29
|
-
|
|
30
|
-
Then: `mcp__playwright__browser_take_screenshot` — show the user what loaded
|
|
31
|
-
|
|
32
|
-
### Step 1 — Create Account / Sign In
|
|
33
|
-
Take a snapshot with `mcp__playwright__browser_snapshot` to see element refs.
|
|
34
|
-
Fill fields using `mcp__playwright__browser_fill_form` or individual `mcp__playwright__browser_type` calls.
|
|
35
|
-
Screenshot to confirm. Then ask user before clicking Create Account / Sign In.
|
|
36
|
-
|
|
37
|
-
### Step 2 — Start Application
|
|
38
|
-
If "Start Your Application" screen appears with Apply Manually button:
|
|
39
|
-
Screenshot it. Click "Apply Manually" using `mcp__playwright__browser_click`.
|
|
40
|
-
Screenshot after.
|
|
41
|
-
|
|
42
|
-
### Step 3 — My Information
|
|
43
|
-
Snapshot → fill each field → screenshot → ask user before clicking Next.
|
|
44
|
-
|
|
45
|
-
Fields to fill:
|
|
46
|
-
- First Name, Last Name, Phone
|
|
47
|
-
- Address, City, State (dropdown), Zip
|
|
48
|
-
- Work authorization: Yes
|
|
49
|
-
- Sponsorship: No
|
|
50
|
-
|
|
51
|
-
### Step 4 — My Experience
|
|
52
|
-
Snapshot → click Add for each job entry → fill title/company/dates/description → save each → screenshot.
|
|
53
|
-
Then add education entries.
|
|
54
|
-
Ask user before clicking Next.
|
|
55
|
-
|
|
56
|
-
### Step 5 — Application Questions
|
|
57
|
-
Snapshot to see all questions. Fill each one. **Always confirm salary expectation with user before filling** — never guess. Screenshot. Ask before Next.
|
|
58
|
-
|
|
59
|
-
### Step 6 — Voluntary Disclosures
|
|
60
|
-
Select "I do not wish to answer" / "Prefer not to disclose" for all. Screenshot. Ask before Next.
|
|
61
|
-
|
|
62
|
-
### Step 7 — Self Identify
|
|
63
|
-
Fill name and date. Select disability option. Screenshot. Ask before Next.
|
|
64
|
-
|
|
65
|
-
### Step 8 — Review
|
|
66
|
-
Screenshot the full review page. Confirm with user before clicking Submit.
|
|
67
|
-
|
|
68
|
-
### Step 9 — Confirm submission
|
|
69
|
-
Screenshot the confirmation dialog. Save it.
|
|
70
|
-
|
|
71
|
-
## Candidate Data (Osborn Ojure)
|
|
72
|
-
|
|
73
|
-
- Email: osbornojure@gmail.com
|
|
74
|
-
- Password: Workday2026!
|
|
75
|
-
- First: Osborn, Last: Ojure
|
|
76
|
-
- Phone: 3127185561
|
|
77
|
-
- Address: 1234 N Michigan Ave, Chicago, IL 60601
|
|
78
|
-
|
|
79
|
-
Jobs:
|
|
80
|
-
1. Meta API Consultant at Prehype / Audos — April 2024 to Present
|
|
81
|
-
2. Full Stack Developer, Freelance — January 2016 to Present
|
|
82
|
-
|
|
83
|
-
Education:
|
|
84
|
-
1. A.S. Information Systems
|
|
85
|
-
2. B.S. Psychology
|
|
86
|
-
|
|
87
|
-
## Workday data-automation-id Selector Reference
|
|
88
|
-
|
|
89
|
-
| Field | Selector |
|
|
90
|
-
|---|---|
|
|
91
|
-
| Email | `input[type="email"]` |
|
|
92
|
-
| Password | `input[type="password"]` |
|
|
93
|
-
| First name | `[data-automation-id="legalNameSection_firstName"]` |
|
|
94
|
-
| Last name | `[data-automation-id="legalNameSection_lastName"]` |
|
|
95
|
-
| Phone | `[data-automation-id="phone-number"]` |
|
|
96
|
-
| Address | `[data-automation-id="addressSection_addressLine1"]` |
|
|
97
|
-
| City | `[data-automation-id="addressSection_city"]` |
|
|
98
|
-
| Zip | `[data-automation-id="addressSection_postalCode"]` |
|
|
99
|
-
| Job title | `[data-automation-id="jobTitle"]` |
|
|
100
|
-
| Company | `[data-automation-id="company"]` |
|
|
101
|
-
| Description | `[data-automation-id="description"]` |
|
|
102
|
-
| Next button | `[data-automation-id="bottom-navigation-next-btn"]` |
|
|
103
|
-
| Create Account | `[data-automation-id="click_filter"][aria-label="Create Account"]` |
|
|
104
|
-
|
|
105
|
-
## Critical Rules
|
|
106
|
-
- headless: false always (Workday renders blank in headless)
|
|
107
|
-
- Confirm salary with user before every submission — never auto-fill
|
|
108
|
-
- After "You already applied to this job" error — that confirms a previous submission worked
|
|
109
|
-
- Use `{ force: true }` on Workday buttons — overlay click filters block normal clicks
|
|
110
|
-
- Always wait for networkidle or waitForSelector after navigation before interacting
|
|
111
|
-
|
|
112
|
-
## Playwright Install Location
|
|
113
|
-
Run scripts from: `/Users/newupgrade/Desktop/Developer/osborn/frontend`
|
|
114
|
-
(playwright is in `node_modules` there)
|
|
@@ -1,29 +0,0 @@
|
|
|
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`
|
|
@@ -1,28 +0,0 @@
|
|
|
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
|
|
@@ -1,90 +0,0 @@
|
|
|
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
|