osborn 0.9.212 → 0.9.214

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.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: browser-apply
3
+ description: Interactively automate Workday and other multi-step JS-heavy job application forms using Playwright MCP browser tools, filling one page at a time with a screenshot and user confirmation before advancing. Use when applying to a Workday ATS or any staged online job application needing visible, confirmable progress.
4
+ ---
5
+
1
6
  # Skill: Browser Apply — Step-by-Step Workday Application
2
7
 
3
8
  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.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: browser-screen-recorder
3
+ description: Drive any web app in a real browser and record proof — per-action screenshots, video clips, audio capture, DevTools diagnostics, and metrics, with mic synthesis and natural-language clicking. Use as the QA protocol after building or changing any frontend, for debugging web-reachable issues, verifying deployed changes, or reproducing user reports.
4
+ ---
5
+
1
6
  # Browser Screen Recorder — drive any web app (voice, hands + a brain) and record proof
2
7
 
3
8
  ## SKILL IDENTITY
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: deploy-workflow
3
+ description: The end-to-end release procedure for shipping osborn agent changes — build and verify locally, bump the package.json version, push to GitHub, publish to NPM from the Mac, and update the running Sprite from the registry. Use every time code or prompt changes are ready to ship.
4
+ ---
5
+
1
6
  # Deploy Workflow
2
7
 
3
8
  Use this every time changes are ready to ship.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: ground-assumptions
3
+ description: Treat every load-bearing assumption as a hypothesis to verify against real evidence before recommending an implementation strategy, rather than trusting training-data intuition or "it should work". Use whenever the conversation enters a planning, design, or architecture phase, or before making any performance, behavioral, or comparative claim.
4
+ ---
5
+
1
6
  # Ground Assumptions
2
7
 
3
8
  ## SKILL IDENTITY
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: markdown-to-pdf
3
+ description: Export a Markdown document, spec, or research findings as a formatted PDF using md-to-pdf, pandoc, or markdown-pdf, saving the result to the session workspace. Use when the user wants to create or generate a PDF from a Markdown file.
4
+ ---
5
+
1
6
  # Skill: Markdown to PDF
2
7
 
3
8
  Export Markdown documents as formatted PDF files.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: meetings
3
+ description: Silently take notes and track TODOs while osborn sits in a live meeting via a Recall.ai bot, and pull transcripts on demand, delegating file and transcript work to the writer sub-agent to avoid the tool-call budget. Use for auto-tagged [MEETING] transcript chunks or when the user asks to grab, summarize, or write notes/todos from a meeting.
4
+ ---
5
+
1
6
  # Skill: Meetings
2
7
 
3
8
  Silent note-taking and TODO-tracking when osborn is sitting in a live meeting,
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: pdf-to-markdown
3
+ description: Convert a PDF document to readable Markdown text using the built-in Read tool, pdf-parse-cli, or pdftotext, preserving headings and tables and saving the result to the session workspace. Use when the user provides a PDF file path and wants to read, search, or work with its contents.
4
+ ---
5
+
1
6
  # Skill: PDF to Markdown
2
7
 
3
8
  Convert PDF documents to readable Markdown text.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: playwright-browser
3
+ description: Automate web browser interactions via the @playwright/cli — navigate pages, click buttons and links, fill and submit forms, take screenshots, and extract text or structured data using token-efficient element references. Use to interact with a URL, automate a multi-step web workflow, or test a UI flow.
4
+ ---
5
+
1
6
  # Skill: Playwright Browser Automation
2
7
 
3
8
  Automate web browser interactions — navigate pages, click buttons, fill forms, take screenshots, and extract content.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: recall
3
+ description: Query this session's full untruncated history — every message, thinking block, and tool call stored in a per-session SQLite database with keyword and semantic search via the osborn-recall command. Use when you need an earlier decision, file, error, number, or name no longer in context, or to check prior work before acting as a grounded agent.
4
+ ---
5
+
1
6
  # Recall
2
7
 
3
8
  ## SKILL IDENTITY
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: send-media
3
+ description: Upload a local file to the osborn public upload endpoint (Supabase storage) to get a permanent public URL, then render it inline in your next reply as a markdown image or link. Use whenever you have a local screenshot, image, PDF, clip, or log the user should see rather than have described.
4
+ ---
5
+
1
6
  # send-media — deliver screenshots, images, and files to the user INLINE in chat
2
7
 
3
8
  > 📅 Last updated: 2026-08-09. Use whenever you have a local file the user should SEE
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: shadcn
3
+ description: Add and configure shadcn/ui components — the copy-paste library built on Radix UI and Tailwind CSS — in a Next.js or React project via the shadcn CLI, including first-time init and adding single or multiple components. Use when the user wants to add UI components like buttons, dialogs, cards, forms, or tables.
4
+ ---
5
+
1
6
  # Skill: shadcn/ui Components
2
7
 
3
8
  Add and configure shadcn/ui components in a Next.js or React project.
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: slack-readonly
3
+ description: Search and read Slack messages, threads, and channels from the shell via read-only GET calls to the Slack Web API using a SLACK_TOKEN, with no posting, reactions, or edits by design. Use when asked to find, read, or summarize Slack content; refuse any request to send or write.
4
+ ---
5
+
1
6
  # Slack (read-only) — search and read Slack from the shell
2
7
 
3
8
  > 📅 **Last updated: 2026-08-09.** Recreated from scratch after the Mac data-loss events wiped the
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: voice-native-sync
3
+ description: Sync osborn sessions between the local Mac and cloud machines, starting with a mandatory served-version check that self-updates and aborts on a stale local copy to prevent unresumable-session corruption. Use when uploading or downloading sessions for the voice-native osborn setup.
4
+ ---
5
+
1
6
  # Voice Native Sync Skill
2
7
 
3
8
  ## SKILL IDENTITY
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: youtube-transcript
3
+ description: Fetch a YouTube video's auto-generated English subtitles with yt-dlp, strip the SRT timing markers to clean transcript text, and save it to the session workspace. Use when the user asks to get a transcript, subtitles, captions, or summary from a YouTube video URL.
4
+ ---
5
+
1
6
  # Skill: YouTube Transcript
2
7
 
3
8
  Fetch and save transcripts from YouTube videos.
@@ -199,6 +199,68 @@ function loadAllSkills(_workingDir) {
199
199
  console.log(`📚 Loaded ${skillMap.size} skill(s) from ${homeSkillsDir}`);
200
200
  return `<available-skills>\n${[...skillMap.values()].join('\n\n---\n\n')}\n</available-skills>`;
201
201
  }
202
+ /**
203
+ * Enumerate the agent's CURRENT skills (name + one-line description) from
204
+ * ~/.claude/skills, for injection into the PreCompact instruction. This is the
205
+ * list the compaction model dedupes/merges against so it stops re-emitting
206
+ * duplicate skills every session. Description is taken from YAML frontmatter
207
+ * (`description:`) when present, else the WHEN: line, else the first non-heading
208
+ * line. Returns one `- name: description` per line, or '' if none.
209
+ */
210
+ function enumerateSkillsForCompaction() {
211
+ const dir = join(homedir(), '.claude', 'skills');
212
+ if (!existsSync(dir))
213
+ return '';
214
+ const lines = [];
215
+ try {
216
+ for (const name of readdirSync(dir).sort()) {
217
+ const file = join(dir, name, 'SKILL.md');
218
+ if (!existsSync(file))
219
+ continue;
220
+ let desc = '';
221
+ try {
222
+ const raw = readFileSync(file, 'utf-8');
223
+ const fm = raw.match(/^---\n([\s\S]*?)\n---/);
224
+ if (fm) {
225
+ const m = fm[1].match(/^description:\s*(.+)$/m);
226
+ if (m)
227
+ desc = m[1].trim();
228
+ }
229
+ if (!desc) {
230
+ const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, '');
231
+ const when = body.match(/^\s*WHEN:\s*(.+)$/mi);
232
+ if (when)
233
+ desc = when[1].trim();
234
+ }
235
+ if (!desc) {
236
+ const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, '');
237
+ const first = body.split('\n').map(l => l.trim()).find(l => l && !l.startsWith('#'));
238
+ desc = first || '(no description)';
239
+ }
240
+ // Option A: expose the skill's SECTION HEADINGS so the compaction model can
241
+ // target a specific section for a surgical update (via UPDATE_SECTION) rather
242
+ // than re-dumping a whole new skill. The summarizer has no tool access — this
243
+ // is the only way it can "see inside" the file.
244
+ const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, '');
245
+ const headings = (body.match(/^#{2,3}\s+.+$/gm) || [])
246
+ .map(h => h.replace(/^#{2,3}\s+/, '').trim())
247
+ .filter(Boolean);
248
+ if (desc.length > 180)
249
+ desc = desc.slice(0, 177) + '…';
250
+ lines.push(`- ${name}: ${desc}`);
251
+ if (headings.length)
252
+ lines.push(` sections: ${headings.join(' | ')}`);
253
+ }
254
+ catch {
255
+ lines.push(`- ${name}: (unreadable)`);
256
+ }
257
+ }
258
+ }
259
+ catch (err) {
260
+ console.warn('⚠️ enumerateSkillsForCompaction failed:', err instanceof Error ? err.message : err);
261
+ }
262
+ return lines.join('\n');
263
+ }
202
264
  // Compaction threshold: Fable 5 runs a 1M context window, so let sessions use
203
265
  // all of it before auto-compacting. autoCompactWindow max is 1_000_000; the SDK
204
266
  // reads it from settings.json (settingSources includes 'user'), so merge it into
@@ -2105,8 +2167,38 @@ class ClaudeLLMStream extends llm.LLMStream {
2105
2167
  try {
2106
2168
  this.#opts.onCompactionEvent?.({ type: 'compaction_started', trigger: input?.trigger });
2107
2169
  const instructionPath = join(__claudeLlmDir, 'prompts', 'compact-learnings-instruction.md');
2108
- const instruction = existsSync(instructionPath) ? readFileSync(instructionPath, 'utf-8') : '';
2109
- console.log(`🧠 PreCompact: injecting instruction (${instruction.length} chars, trigger=${input?.trigger || 'unknown'})`);
2170
+ const instructionRaw = existsSync(instructionPath) ? readFileSync(instructionPath, 'utf-8') : '';
2171
+ // Populate the EXISTING SKILLS section the instruction .md already refers to
2172
+ // (lines 42 & 55) but that nothing used to fill. Without this, the model is told
2173
+ // to "not re-emit skills already shown" against a list it never received — so it
2174
+ // proliferated duplicates. We hand it the current skill set + an adversarial,
2175
+ // self-critical directive to merge/refine rather than create anew.
2176
+ const existingSkills = enumerateSkillsForCompaction();
2177
+ // Count only skill lines (`- name: …`), not the ` sections:` continuation lines.
2178
+ const skillCount = existingSkills
2179
+ ? existingSkills.split('\n').filter(l => l.startsWith('- ')).length
2180
+ : 0;
2181
+ const criticBlock = existingSkills
2182
+ ? `\n\n---\n\n=== EXISTING SKILLS (${skillCount}) ===\n`
2183
+ + `These skills ALREADY EXIST for this user. Each is listed as \`- name: description\`, and where known, `
2184
+ + `an indented \`sections:\` line lists that skill's section headings (\`## \`/\`### \`). `
2185
+ + `Before you emit SKILL_CANDIDATES or BEHAVIORAL_LEARNINGS, act as an ADVERSARIAL reviewer of your own output:\n`
2186
+ + `1. If what you learned refines ONE section of an existing skill, do NOT re-emit the whole skill. Instead emit a `
2187
+ + `surgical section update inside SKILL_CANDIDATES using this exact block form (the heading must match a `
2188
+ + `\`sections:\` entry below verbatim):\n`
2189
+ + ` --- UPDATE_SECTION: <skill-name> > <exact section heading> ---\n`
2190
+ + ` <the new markdown that should REPLACE that section's body>\n`
2191
+ + ` --- END UPDATE_SECTION ---\n`
2192
+ + `2. If a candidate duplicates or substantially overlaps one below but isn't a single-section tweak, `
2193
+ + `re-emit it under the EXACT SAME kebab-case name, and only if it needs a substantive update; otherwise omit it.\n`
2194
+ + `3. Propose a brand-new skill ONLY if nothing below covers it, it was CONFIRMED working this session, `
2195
+ + `and it generalizes to future sessions on different tasks.\n`
2196
+ + `4. Prefer refining/section-updating over proliferating. A small set of sharp, non-overlapping skills is the goal — `
2197
+ + `reject your own low-signal or one-off candidates.\n\n`
2198
+ + `${existingSkills}\n`
2199
+ : '';
2200
+ const instruction = instructionRaw + criticBlock;
2201
+ console.log(`🧠 PreCompact: injecting instruction (${instruction.length} chars, ${skillCount} existing skills, trigger=${input?.trigger || 'unknown'})`);
2110
2202
  return { systemMessage: instruction };
2111
2203
  }
2112
2204
  catch (err) {
@@ -2166,7 +2258,7 @@ class ClaudeLLMStream extends llm.LLMStream {
2166
2258
  const decPath = join(decFolder, 'SKILL.md');
2167
2259
  mkdirSync(decFolder, { recursive: true });
2168
2260
  const existing = existsSyncFs(decPath) ? readSyncFs(decPath, 'utf-8') : '';
2169
- const header = existing ? '' : `# Project Decisions\n\nAuto-extracted from compact summaries.\n\n`;
2261
+ const header = existing ? '' : `---\nname: decisions\ndescription: "Project-scoped architectural and implementation decisions auto-extracted from compaction summaries; consult before revisiting settled choices."\nmetadata:\n type: learned\n source: postcompact\n---\n\n# Project Decisions\n\nAuto-extracted from compact summaries.\n\n`;
2170
2262
  const entry = `\n## ${today} (session ${sessionId.substring(0, 8)})\n${projectLines.join('\n')}\n`;
2171
2263
  writeSyncFs(decPath, header + existing + entry, 'utf-8');
2172
2264
  console.log(`🧠 PostCompact: appended ${projectLines.length} decision(s) to ${decPath}`);
@@ -2196,8 +2288,14 @@ class ClaudeLLMStream extends llm.LLMStream {
2196
2288
  const skillFolder = join(skillDir, '.claude', 'skills', name);
2197
2289
  const skillPath = join(skillFolder, 'SKILL.md');
2198
2290
  mkdirSync(skillFolder, { recursive: true });
2199
- const header = `# ${name}\nAuto-extracted: ${today} | Session: ${sessionId.substring(0, 8)}\n\n`;
2200
- writeSyncFs(skillPath, header + body + '\n', 'utf-8');
2291
+ // Emit STANDARD skill format: YAML frontmatter (name + description) so
2292
+ // learned skills conform to the same shape the loader/other agents expect.
2293
+ // Description is derived from the candidate's WHEN: line; JSON.stringify
2294
+ // yields a safely-quoted YAML scalar even when it contains colons/quotes.
2295
+ const whenMatch = body.match(/^\s*WHEN:\s*(.+)$/mi);
2296
+ const desc = (whenMatch ? whenMatch[1].trim() : `Learned procedure: ${name}`).replace(/\s+/g, ' ').slice(0, 200);
2297
+ const frontmatter = `---\nname: ${name}\ndescription: ${JSON.stringify(desc)}\nmetadata:\n type: learned\n source: postcompact\n session: ${sessionId.substring(0, 8)}\n updated: ${today}\n---\n\n`;
2298
+ writeSyncFs(skillPath, frontmatter + `# ${name}\n\n` + body + '\n', 'utf-8');
2201
2299
  console.log(`🧠 PostCompact: wrote skill '${name}' to ${skillPath}`);
2202
2300
  skillsWritten++;
2203
2301
  skillNames.push(name);
@@ -2208,6 +2306,64 @@ class ClaudeLLMStream extends llm.LLMStream {
2208
2306
  catch (skillErr) {
2209
2307
  console.error('⚠️ PostCompact: SKILL_CANDIDATES write failed:', skillErr instanceof Error ? skillErr.message : skillErr);
2210
2308
  }
2309
+ // ── Section 3b: UPDATE_SECTION — surgical, section-level edits to EXISTING skills ──
2310
+ // The model (via the PreCompact preamble) emits blocks of the form:
2311
+ // --- UPDATE_SECTION: <skill-name> > <exact heading> ---
2312
+ // <replacement markdown for that section body>
2313
+ // --- END UPDATE_SECTION ---
2314
+ // We splice the new body under the matching `## `/`### ` heading, replacing
2315
+ // everything down to the next heading of the same-or-higher level. We NEVER
2316
+ // create a file here — an unknown skill/heading is logged and skipped, so the
2317
+ // model can't silently mint a new skill through this path.
2318
+ try {
2319
+ const updateRe = /---\s*UPDATE_SECTION:\s*([a-z][a-z0-9-]{1,39})\s*>\s*([^\n]+?)\s*---\n([\s\S]*?)---\s*END UPDATE_SECTION\s*---/g;
2320
+ let um;
2321
+ while ((um = updateRe.exec(summary)) !== null) {
2322
+ const targetSkill = um[1].trim();
2323
+ const targetHeading = um[2].trim();
2324
+ const newBody = um[3].trim();
2325
+ const skillPath = join(skillDir, '.claude', 'skills', targetSkill, 'SKILL.md');
2326
+ if (!existsSyncFs(skillPath)) {
2327
+ console.warn(`⚠️ PostCompact: UPDATE_SECTION skipped — no such skill '${targetSkill}'`);
2328
+ continue;
2329
+ }
2330
+ const content = readSyncFs(skillPath, 'utf-8');
2331
+ const lines2 = content.split('\n');
2332
+ // Find the heading line (## or ###) whose text matches targetHeading (case-insensitive).
2333
+ const norm = (s) => s.replace(/^#{2,3}\s+/, '').trim().toLowerCase();
2334
+ const hIdx = lines2.findIndex(l => /^#{2,3}\s+/.test(l) && norm(l) === targetHeading.toLowerCase());
2335
+ if (hIdx === -1) {
2336
+ console.warn(`⚠️ PostCompact: UPDATE_SECTION skipped — heading '${targetHeading}' not found in '${targetSkill}'`);
2337
+ continue;
2338
+ }
2339
+ const level = (lines2[hIdx].match(/^#+/) || ['##'])[0].length;
2340
+ // Section ends at the next heading of the same-or-higher level (fewer/equal #s).
2341
+ let end = lines2.length;
2342
+ for (let i = hIdx + 1; i < lines2.length; i++) {
2343
+ const m2 = lines2[i].match(/^(#{1,6})\s+/);
2344
+ if (m2 && m2[1].length <= level) {
2345
+ end = i;
2346
+ break;
2347
+ }
2348
+ }
2349
+ const rebuilt = [
2350
+ ...lines2.slice(0, hIdx + 1),
2351
+ '',
2352
+ newBody,
2353
+ '',
2354
+ ...lines2.slice(end),
2355
+ ].join('\n');
2356
+ writeSyncFs(skillPath, rebuilt, 'utf-8');
2357
+ console.log(`🧠 PostCompact: UPDATE_SECTION applied — '${targetSkill}' › '${targetHeading}' (${newBody.length} chars)`);
2358
+ skillsWritten++;
2359
+ if (!skillNames.includes(targetSkill))
2360
+ skillNames.push(targetSkill);
2361
+ progress('Updated section', `${targetSkill} › ${targetHeading}`);
2362
+ }
2363
+ }
2364
+ catch (updErr) {
2365
+ console.error('⚠️ PostCompact: UPDATE_SECTION apply failed:', updErr instanceof Error ? updErr.message : updErr);
2366
+ }
2211
2367
  // ── Section 4: BEHAVIORAL_LEARNINGS — write to learned-behaviors/SKILL.md ──
2212
2368
  try {
2213
2369
  const learnings = extractSection('=== BEHAVIORAL_LEARNINGS ===');
@@ -2216,7 +2372,7 @@ class ClaudeLLMStream extends llm.LLMStream {
2216
2372
  const skillFolder = join(skillDir, '.claude', 'skills', 'learned-behaviors');
2217
2373
  const skillPath = join(skillFolder, 'SKILL.md');
2218
2374
  mkdirSync(skillFolder, { recursive: true });
2219
- const header = `# Learned Behaviors\n\nAuto-extracted from voice sessions via PostCompact.\nLast updated: ${today} | Session: ${sessionId.substring(0, 8)}...\n\n`;
2375
+ const header = `---\nname: learned-behaviors\ndescription: "User corrections, preferences, domain knowledge, effective patterns, and anti-patterns learned across sessions; apply to align with how this user works."\nmetadata:\n type: learned\n source: postcompact\n updated: ${today}\n---\n\n# Learned Behaviors\n\nAuto-extracted from voice sessions via PostCompact.\nLast updated: ${today} | Session: ${sessionId.substring(0, 8)}...\n\n`;
2220
2376
  writeSyncFs(skillPath, header + learnings + '\n', 'utf-8');
2221
2377
  console.log(`🧠 PostCompact: wrote learned behaviors to ${skillPath} (${learnings.length} chars)`);
2222
2378
  skillsWritten++;
package/dist/index.js CHANGED
@@ -559,10 +559,12 @@ function startApiServer(workingDir, port) {
559
559
  if (req.method === 'GET' && url.pathname === '/skills') {
560
560
  // Installed skills — same list the chat's get_skills data-channel message
561
561
  // returns, exposed over HTTP so the DASHBOARD (no LiveKit connection) can
562
- // render the skills manager too. process.cwd() === sessionBaseDir (the
563
- // osborn install dir where .claude/skills lives — see main()).
562
+ // render the skills manager too. Reads ~/.claude/skills (homedir) — the SINGLE
563
+ // location the agent ingests from (loadAllSkills) and PostCompact writes to.
564
+ // Previously used process.cwd()/.claude/skills, which the agent never read —
565
+ // so UI-created skills were invisible to the agent (the "divergence").
564
566
  res.writeHead(200, { 'Content-Type': 'application/json' });
565
- res.end(JSON.stringify({ skills: loadSkillsList(process.cwd()) }));
567
+ res.end(JSON.stringify({ skills: loadSkillsList(homedir()) }));
566
568
  return;
567
569
  }
568
570
  if (req.method === 'GET' && url.pathname === '/agents') {
@@ -4630,7 +4632,7 @@ async function main() {
4630
4632
  mcpServers: getMcpServerStatusList(config),
4631
4633
  enabledMcpServers: enabledMcpNames,
4632
4634
  workingDirectory: workingDir,
4633
- skills: loadSkillsList(sessionBaseDir),
4635
+ skills: loadSkillsList(homedir()),
4634
4636
  namedAgents: Object.entries(NAMED_AGENTS).map(([name, a]) => ({
4635
4637
  name, description: a.description, model: a.model, tools: a.tools,
4636
4638
  })),
@@ -5330,7 +5332,7 @@ async function main() {
5330
5332
  else if (data.type === 'get_skills') {
5331
5333
  await sendToFrontend({
5332
5334
  type: 'skills_status',
5333
- skills: loadSkillsList(sessionBaseDir),
5335
+ skills: loadSkillsList(homedir()),
5334
5336
  });
5335
5337
  }
5336
5338
  else if (data.type === 'get_agents') {
@@ -5404,11 +5406,11 @@ async function main() {
5404
5406
  }
5405
5407
  else {
5406
5408
  try {
5407
- const skillDir = join(sessionBaseDir, '.claude', 'skills', skillName);
5409
+ const skillDir = join(homedir(), '.claude', 'skills', skillName);
5408
5410
  mkdirSync(skillDir, { recursive: true });
5409
5411
  writeFileSync(join(skillDir, 'SKILL.md'), skillContent, 'utf-8');
5410
5412
  console.log(`📚 Skill added: ${skillName}`);
5411
- const skills = loadSkillsList(sessionBaseDir);
5413
+ const skills = loadSkillsList(homedir());
5412
5414
  await sendToFrontend({ type: 'skill_add_result', success: true, skills });
5413
5415
  }
5414
5416
  catch (err) {
@@ -5419,7 +5421,7 @@ async function main() {
5419
5421
  }
5420
5422
  else if (data.type === 'skill_get') {
5421
5423
  const folder = (data.name || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
5422
- const p = join(sessionBaseDir, '.claude', 'skills', folder, 'SKILL.md');
5424
+ const p = join(homedir(), '.claude', 'skills', folder, 'SKILL.md');
5423
5425
  if (folder && existsSync(p)) {
5424
5426
  await sendToFrontend({ type: 'skill_content', name: folder, content: readFileSync(p, 'utf-8') });
5425
5427
  }
@@ -5429,7 +5431,7 @@ async function main() {
5429
5431
  }
5430
5432
  else if (data.type === 'skill_remove') {
5431
5433
  const folder = (data.name || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
5432
- const dir = join(sessionBaseDir, '.claude', 'skills', folder);
5434
+ const dir = join(homedir(), '.claude', 'skills', folder);
5433
5435
  if (!folder || !existsSync(dir)) {
5434
5436
  await sendToFrontend({ type: 'skill_remove_result', success: false, error: 'skill not found' });
5435
5437
  }
@@ -5437,7 +5439,7 @@ async function main() {
5437
5439
  try {
5438
5440
  rmSync(dir, { recursive: true, force: true });
5439
5441
  console.log(`🗑️ Skill removed: ${folder}`);
5440
- await sendToFrontend({ type: 'skill_remove_result', success: true, skills: loadSkillsList(sessionBaseDir) });
5442
+ await sendToFrontend({ type: 'skill_remove_result', success: true, skills: loadSkillsList(homedir()) });
5441
5443
  }
5442
5444
  catch (err) {
5443
5445
  await sendToFrontend({ type: 'skill_remove_result', success: false, error: String(err) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.212",
3
+ "version": "0.9.214",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,46 +0,0 @@
1
- You are analyzing a voice AI assistant conversation to extract behavioral learnings that should persist across sessions.
2
-
3
- The conversation is between a user and "Osborn" — a voice AI thinking partner. Your job is to identify:
4
-
5
- 1. USER CORRECTIONS — things the user explicitly told the agent to stop doing or start doing differently
6
- 2. USER PREFERENCES — recurring patterns in how the user wants to work (tools, approaches, communication style)
7
- 3. DOMAIN KNOWLEDGE — specific technical facts learned during the session (API behaviors, selectors, platform quirks, vendor-specific details)
8
- 4. EFFECTIVE PATTERNS — approaches that worked well and the user confirmed or accepted without pushback
9
- 5. ANTI-PATTERNS — approaches that failed, got the user frustrated, or had to be abandoned
10
-
11
- For each item, include:
12
- - The specific learning (concrete, actionable)
13
- - Brief context for WHY (so future sessions can judge if it still applies)
14
- - Confidence level: HIGH (user explicitly stated it), MEDIUM (inferred from user behavior), LOW (observed but not confirmed)
15
-
16
- Output as markdown in this exact format:
17
-
18
- ```markdown
19
- # Session Learnings — {date}
20
-
21
- ## User Corrections (HIGH confidence)
22
- - {correction}: {context}
23
-
24
- ## User Preferences (MEDIUM-HIGH confidence)
25
- - {preference}: {context}
26
-
27
- ## Domain Knowledge Learned (varies)
28
- - [{confidence}] {fact}: {how it was verified}
29
-
30
- ## Effective Patterns (MEDIUM confidence)
31
- - {pattern}: {when it worked}
32
-
33
- ## Anti-Patterns to Avoid (HIGH confidence)
34
- - {anti-pattern}: {what went wrong}
35
- ```
36
-
37
- Be SELECTIVE. Only include items that are:
38
- - Generalizable to future sessions (not one-off task details)
39
- - Actionable (the agent can actually change behavior based on this)
40
- - Non-obvious (things the agent wouldn't know from its system prompt alone)
41
-
42
- Do NOT include:
43
- - Task-specific details (file paths, variable names, specific code changes)
44
- - Things already in the system prompt
45
- - Trivial confirmations or greetings
46
- - Speculative patterns not grounded in the conversation