osborn 0.9.15 → 0.9.16
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.
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Export Markdown documents as formatted PDF files.
|
|
4
4
|
|
|
5
5
|
## When to use
|
|
6
|
-
When the user
|
|
6
|
+
When the user /wants to create a PDF from a Markdown file, spec, or research findings.
|
|
7
7
|
|
|
8
8
|
## How to execute
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Automate web browser interactions — navigate pages, click buttons, fill forms, take screenshots, and extract content.
|
|
4
4
|
|
|
5
5
|
## When to use
|
|
6
|
-
- Navigate to a URL and interact with it
|
|
6
|
+
- Navigate to a - URL and interact with it
|
|
7
7
|
- Click buttons or links by their text or role
|
|
8
8
|
- Fill form fields and submit data
|
|
9
9
|
- Take screenshots of web pages
|
|
@@ -7,7 +7,7 @@ When the user wants to add UI components (buttons, dialogs, cards, forms, tables
|
|
|
7
7
|
|
|
8
8
|
## Setup (first time only)
|
|
9
9
|
|
|
10
|
-
Initialize shadcn in the project root (where package.json lives):
|
|
10
|
+
Initialize shadcn in the work/project root (where package.json lives):
|
|
11
11
|
```bash
|
|
12
12
|
npx shadcn@latest init
|
|
13
13
|
```
|
package/dist/claude-llm.d.ts
CHANGED
|
@@ -78,6 +78,11 @@ export declare class ClaudeLLM extends llm.LLM {
|
|
|
78
78
|
* Call this before sending the first message to resume from a previous session
|
|
79
79
|
*/
|
|
80
80
|
setResumeSessionId(sessionId: string | null): void;
|
|
81
|
+
/**
|
|
82
|
+
* Set the working directory for the current session
|
|
83
|
+
* Call this when resuming a session from a different project slug
|
|
84
|
+
*/
|
|
85
|
+
setWorkingDirectory(path: string): void;
|
|
81
86
|
/**
|
|
82
87
|
* Reset state for mid-conversation session switch
|
|
83
88
|
* Clears pending permissions and resets conversation tracking
|
package/dist/claude-llm.js
CHANGED
|
@@ -14,6 +14,7 @@ import { getResearchSystemPrompt, getDirectModeResearchPrompt } from './prompts.
|
|
|
14
14
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
15
15
|
import { join, dirname } from 'node:path';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { homedir } from 'node:os';
|
|
17
18
|
// Directory of this module — used to locate co-located prompt files (e.g., turn-shape reminder).
|
|
18
19
|
const __claudeLlmDir = dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const TURN_SHAPE_REMINDER_PATH = join(__claudeLlmDir, 'prompts', 'turn-shape-reminder.md');
|
|
@@ -85,6 +86,44 @@ function loadSkillsFromDir(agentDir) {
|
|
|
85
86
|
console.log(`📚 Loaded ${skills.length} skill(s) from ${skillsDir}`);
|
|
86
87
|
return `<available-skills>\n${skills.join('\n\n---\n\n')}\n</available-skills>`;
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Loads skills from both ~/.claude/skills/ (home dir) and {workingDir}/.claude/skills/ (project dir).
|
|
91
|
+
* Merges results, deduplicating by skill directory name — home dir wins on conflicts.
|
|
92
|
+
* Returns a combined <available-skills> XML block, or '' if no skills found.
|
|
93
|
+
*/
|
|
94
|
+
function loadAllSkills(workingDir) {
|
|
95
|
+
const homeSkillsDir = join(homedir(), '.claude', 'skills');
|
|
96
|
+
const projectSkillsDir = join(workingDir, '.claude', 'skills');
|
|
97
|
+
// skill name → content; home dir loaded first so it wins on conflicts
|
|
98
|
+
const skillMap = new Map();
|
|
99
|
+
const loadFromDir = (dir) => {
|
|
100
|
+
if (!existsSync(dir))
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
for (const skillName of readdirSync(dir)) {
|
|
104
|
+
if (skillMap.has(skillName))
|
|
105
|
+
continue; // home dir already set this one
|
|
106
|
+
const skillFile = join(dir, skillName, 'SKILL.md');
|
|
107
|
+
if (existsSync(skillFile)) {
|
|
108
|
+
skillMap.set(skillName, readFileSync(skillFile, 'utf-8').trim());
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
console.warn('⚠️ Failed to load skills from', dir, ':', err);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
loadFromDir(homeSkillsDir);
|
|
117
|
+
loadFromDir(projectSkillsDir);
|
|
118
|
+
if (skillMap.size === 0)
|
|
119
|
+
return '';
|
|
120
|
+
const sources = [
|
|
121
|
+
existsSync(homeSkillsDir) ? homeSkillsDir : null,
|
|
122
|
+
existsSync(projectSkillsDir) ? projectSkillsDir : null,
|
|
123
|
+
].filter(Boolean).join(', ');
|
|
124
|
+
console.log(`📚 Loaded ${skillMap.size} skill(s) from ${sources}`);
|
|
125
|
+
return `<available-skills>\n${[...skillMap.values()].join('\n\n---\n\n')}\n</available-skills>`;
|
|
126
|
+
}
|
|
88
127
|
// Research mode tools — full research capabilities
|
|
89
128
|
const RESEARCH_TOOLS = [
|
|
90
129
|
'Read', 'Write', 'Edit', 'Glob', 'Grep',
|
|
@@ -301,6 +340,13 @@ export class ClaudeLLM extends llm.LLM {
|
|
|
301
340
|
console.log(`🔄 Will resume session: ${sessionId}`);
|
|
302
341
|
}
|
|
303
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* Set the working directory for the current session
|
|
345
|
+
* Call this when resuming a session from a different project slug
|
|
346
|
+
*/
|
|
347
|
+
setWorkingDirectory(path) {
|
|
348
|
+
this.#opts.workingDirectory = path;
|
|
349
|
+
}
|
|
304
350
|
/**
|
|
305
351
|
* Reset state for mid-conversation session switch
|
|
306
352
|
* Clears pending permissions and resets conversation tracking
|
|
@@ -762,6 +808,7 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
762
808
|
// model: this.#opts.model || 'haiku', // haiku for speed with limited tools, sonnet for full research capabilities (including tool use trace in response)
|
|
763
809
|
model: this.#opts.model || 'claude-sonnet-4-6', // Sonnet orchestrator with named sub-agents (Haiku tested but ignored delegation rules)
|
|
764
810
|
enableFileCheckpointing: true,
|
|
811
|
+
settingSources: ['project', 'user'],
|
|
765
812
|
extraArgs: { 'replay-user-messages': null },
|
|
766
813
|
...(this.#abortController && { abortController: this.#abortController }),
|
|
767
814
|
...(resumeSessionId && { resume: resumeSessionId }),
|
|
@@ -773,7 +820,7 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
773
820
|
this.#opts.voiceMode === 'direct'
|
|
774
821
|
? getDirectModeResearchPrompt(workspacePath)
|
|
775
822
|
: getResearchSystemPrompt(workspacePath),
|
|
776
|
-
|
|
823
|
+
loadAllSkills(this.#opts.sessionBaseDir || this.#opts.workingDirectory || process.cwd()),
|
|
777
824
|
].filter(Boolean).join('\n\n'),
|
|
778
825
|
canUseTool: async (toolName, input, _options) => {
|
|
779
826
|
// Auto-approve writes to session workspace (but block spec.md and library/ — fast brain manages those)
|
|
@@ -943,7 +990,7 @@ class ClaudeLLMStream extends llm.LLMStream {
|
|
|
943
990
|
try {
|
|
944
991
|
const summary = input?.compact_summary || '';
|
|
945
992
|
const { mkdirSync, writeFileSync: writeSyncFs, readFileSync: readSyncFs, existsSync: existsSyncFs } = await import('node:fs');
|
|
946
|
-
const skillDir =
|
|
993
|
+
const skillDir = homedir();
|
|
947
994
|
const today = new Date().toISOString().split('T')[0];
|
|
948
995
|
const sessionId = this.#sessionId || 'unknown';
|
|
949
996
|
let skillsWritten = 0;
|
package/dist/index.js
CHANGED
|
@@ -457,6 +457,10 @@ function startApiServer(workingDir, port) {
|
|
|
457
457
|
}
|
|
458
458
|
}
|
|
459
459
|
filesWritten++;
|
|
460
|
+
const recoveredPath = effectiveSlug.replace(/^-/, '/').replace(/--/g, '/.').replace(/-/g, '/');
|
|
461
|
+
if (recoveredPath && recoveredPath !== '/') {
|
|
462
|
+
mkdirSync(recoveredPath, { recursive: true });
|
|
463
|
+
}
|
|
460
464
|
}
|
|
461
465
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
462
466
|
res.end(JSON.stringify({ ok: true, filesWritten, remapped }));
|
|
@@ -579,24 +583,11 @@ function startApiServer(workingDir, port) {
|
|
|
579
583
|
// The archive should contain a 'projects' subdirectory
|
|
580
584
|
const extractedProjects = join(tmpExtractDir, 'projects');
|
|
581
585
|
const sourceDir = existsSync(extractedProjects) ? extractedProjects : tmpExtractDir;
|
|
582
|
-
// Optionally remap slug: if targetWorkDir is provided, find slug(s)
|
|
583
|
-
// that don't match the target and rename them
|
|
584
|
-
const remapped = {};
|
|
585
|
-
if (targetWorkDir) {
|
|
586
|
-
const targetSlug = targetWorkDir.replace(/\//g, '-');
|
|
587
|
-
const sourceSlugs = readdirSync(sourceDir);
|
|
588
|
-
for (const slug of sourceSlugs) {
|
|
589
|
-
if (slug !== targetSlug && !slug.startsWith('.')) {
|
|
590
|
-
remapped[slug] = targetSlug;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
586
|
// Copy subdirectories into ~/.claude/projects/, merging and updating existing files
|
|
595
587
|
let filesWritten = 0;
|
|
596
588
|
const slugsInSource = readdirSync(sourceDir);
|
|
597
589
|
for (const slug of slugsInSource) {
|
|
598
|
-
const
|
|
599
|
-
const destSlug = join(projectsDir, effectiveSlug);
|
|
590
|
+
const destSlug = join(projectsDir, slug);
|
|
600
591
|
mkdirSync(destSlug, { recursive: true });
|
|
601
592
|
try {
|
|
602
593
|
renameSync(join(sourceDir, slug), destSlug);
|
|
@@ -610,10 +601,15 @@ function startApiServer(workingDir, port) {
|
|
|
610
601
|
throw e;
|
|
611
602
|
}
|
|
612
603
|
}
|
|
604
|
+
// Also create the corresponding workspace directory so Claude can resume
|
|
605
|
+
const recoveredPath = slug.replace(/^-/, '/').replace(/--/g, '/.').replace(/-/g, '/');
|
|
606
|
+
if (recoveredPath && recoveredPath !== '/') {
|
|
607
|
+
mkdirSync(recoveredPath, { recursive: true });
|
|
608
|
+
}
|
|
613
609
|
filesWritten++;
|
|
614
610
|
}
|
|
615
611
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
616
|
-
res.end(JSON.stringify({ ok: true, filesWritten
|
|
612
|
+
res.end(JSON.stringify({ ok: true, filesWritten }));
|
|
617
613
|
}
|
|
618
614
|
catch (err) {
|
|
619
615
|
console.error('[import-finalize] merge error:', err);
|
|
@@ -2993,13 +2989,59 @@ async function main() {
|
|
|
2993
2989
|
}
|
|
2994
2990
|
}
|
|
2995
2991
|
else {
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
2992
|
+
// Try to find the session in any slug directory
|
|
2993
|
+
let found = false;
|
|
2994
|
+
const projectsDir = join(homedir(), '.claude', 'projects');
|
|
2995
|
+
if (existsSync(projectsDir)) {
|
|
2996
|
+
const slugDirs = readdirSync(projectsDir);
|
|
2997
|
+
for (const slug of slugDirs) {
|
|
2998
|
+
const candidate = join(projectsDir, slug, `${sessionId}.jsonl`);
|
|
2999
|
+
if (existsSync(candidate)) {
|
|
3000
|
+
// Recover the original path from the slug
|
|
3001
|
+
const recoveredPath = slug.replace(/^-/, '/').replace(/--/g, '/.').replace(/-/g, '/');
|
|
3002
|
+
if (recoveredPath && recoveredPath !== '/') {
|
|
3003
|
+
mkdirSync(recoveredPath, { recursive: true });
|
|
3004
|
+
workingDir = recoveredPath;
|
|
3005
|
+
currentLLM.setWorkingDirectory(recoveredPath);
|
|
3006
|
+
console.log(`🔄 Found session in slug ${slug}, using path: ${recoveredPath}`);
|
|
3007
|
+
found = true;
|
|
3008
|
+
// Proceed with the same success path
|
|
3009
|
+
currentLLM.setResumeSessionId(sessionId);
|
|
3010
|
+
currentResumeSessionId = sessionId;
|
|
3011
|
+
console.log(`🔄 Will resume session: ${sessionId}`);
|
|
3012
|
+
await sendToFrontend({
|
|
3013
|
+
type: 'session_resume_set',
|
|
3014
|
+
sessionId,
|
|
3015
|
+
success: true,
|
|
3016
|
+
});
|
|
3017
|
+
const artifacts = listWorkspaceArtifacts(workingDir, sessionId);
|
|
3018
|
+
if (artifacts.length > 0) {
|
|
3019
|
+
console.log(`📁 Sending ${artifacts.length} session artifacts to frontend`);
|
|
3020
|
+
await sendToFrontend({
|
|
3021
|
+
type: 'session_artifacts',
|
|
3022
|
+
sessionId,
|
|
3023
|
+
artifacts: artifacts.map(a => ({
|
|
3024
|
+
filePath: a.filePath,
|
|
3025
|
+
fileName: a.fileName,
|
|
3026
|
+
type: a.type,
|
|
3027
|
+
updatedAt: a.updatedAt,
|
|
3028
|
+
}))
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
break;
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
if (!found) {
|
|
3037
|
+
console.error(`❌ Session not found: ${sessionId}`);
|
|
3038
|
+
await sendToFrontend({
|
|
3039
|
+
type: 'session_resume_set',
|
|
3040
|
+
sessionId,
|
|
3041
|
+
success: false,
|
|
3042
|
+
error: 'Session not found',
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3003
3045
|
}
|
|
3004
3046
|
}
|
|
3005
3047
|
else if (data.type === 'continue_session' && currentLLM) {
|