Imported from ljantzen/obsidian-youtube-transcript (
AGENTS.md). Install upstream withnpx skills add ljantzen/obsidian-youtube-transcript. Copyright stays with the author.
AI Agent Guidelines for obsidian-ytt
This document provides context and guidelines for AI coding assistants working on the obsidian-ytt (YouTube Transcript) Obsidian plugin.
Project Overview
What this plugin does:
- Fetches YouTube video transcripts and embeds them in Obsidian notes
- Supports optional LLM processing (OpenAI, Gemini, Claude) for transcript cleanup and summarization
- Offers both Markdown and PDF output formats
- Provides flexible directory organization with cover notes for PDFs
Technology stack:
- TypeScript
- Obsidian Plugin API
- Vitest for testing
- ESBuild for bundling
- ESLint for linting
Architecture
Core Components
src/
├── main.ts # Plugin entry point, orchestrates everything
├── youtube.ts # YouTube API interaction, transcript fetching
├── llm/ # LLM provider integrations
│ ├── openai.ts
│ ├── gemini.ts
│ ├── claude.ts
│ ├── modelFetcher.ts # Fetches available models from APIs
│ └── parser.ts # Parses LLM responses
├── modals.ts # User-facing dialogs
├── settings.ts # Default settings and constants
├── settingsTab.ts # Settings UI
├── pdfGenerator.ts # PDF generation from transcripts
├── types.ts # TypeScript interfaces
└── utils.ts # Shared utilities
Key Data Flow
- User triggers command →
main.ts:fetchTranscript() - URL modal opens →
modals.ts:YouTubeUrlModal - Video ID extracted →
utils.ts:extractVideoId() - Transcript fetched →
youtube.ts:getYouTubeTranscript() - Optional LLM processing →
llm/{provider}.ts:processWithProvider() - File created →
main.ts:createTranscriptFile()- PDF:
pdfGenerator.ts:generatePdfFromMarkdown() - Markdown: Direct file creation
- PDF:
Development Workflows
Making Changes
-
Always run tests after changes:
npm test -
Run linter to catch issues:
npm run lint # Check npm run lint:fix # Auto-fix -
Build to verify TypeScript:
npm run build -
For active development:
npm run dev # Watch mode with auto-rebuild
Testing Philosophy
- Comprehensive coverage: Over 400 tests covering most functionality
- Test files mirror source structure:
test/featureName.test.ts - Uses Vitest with happy-dom for DOM APIs
- Obsidian APIs are mocked where needed
- Pure functions are tested thoroughly (e.g.,
utils.ts)
Common Test Patterns
// Basic function test
it('should sanitize filename correctly', () => {
expect(sanitizeFilename('Video: Title!')).toBe('Video Title');
});
// Testing with settings
it('should respect setting', () => {
const settings = { ...DEFAULT_SETTINGS, someSetting: true };
// Test logic
});
// Async operations
it('should fetch transcript', async () => {
const result = await getYouTubeTranscript('videoId');
expect(result).toBeDefined();
});
Key Conventions
Settings Management
- All settings defined in:
types.ts:YouTubeTranscriptPluginSettings - Defaults in:
settings.ts:DEFAULT_SETTINGS - Backward compatibility pattern:
// In main.ts onload() if (this.settings.newSetting === undefined) { this.settings.newSetting = DEFAULT_SETTINGS.newSetting; await this.saveSettings(); }
Error Handling
- User-facing errors: Use
Noticeclassnew Notice('Error message shown to user'); - LLM operations: Use
RetryConfirmationModalfor failures - Validation errors: Throw with descriptive messages
File Organization
- Keep functions focused: Single responsibility
- Extract utilities: Pure functions go in
utils.ts - Provider pattern: Each LLM provider exports
processWithProvider() - No circular dependencies: Main imports from modules, not vice versa
Common Tasks
Adding a New LLM Provider
- Create
src/llm/newprovider.tswithprocessWithProvider()function - Add provider to
types.ts:LLMProviderunion type - Update
main.ts:hasProviderKey()for API key validation - Add model fetcher to
llm/modelFetcher.ts - Update
settingsTab.tswith settings UI - Add tests in
test/llmProviderIntegration.test.ts
Adding a New Setting
- Add to
types.ts:YouTubeTranscriptPluginSettings - Add default to
settings.ts:DEFAULT_SETTINGS - Add backward compatibility check in
main.ts:onload() - Add UI in
settingsTab.ts - Update affected logic
- Add tests
Modifying Directory Selection
Critical file: src/main.ts:createTranscriptFile()
Current logic (simplified):
if (selectedDirectory !== null) {
directory = selectedDirectory;
} else if (activeFile) {
directory = activeFile.path.substring(0, activeFile.path.lastIndexOf("/"));
} else {
throw new Error("Cannot determine directory...");
}
Important: PDF nesting happens AFTER basic directory selection. See lines 595-620.
Working with PDFs
- Generation:
pdfGenerator.ts:generatePdfFromMarkdown() - Cover notes: Created when
createPdfCoverNote = true - Nesting logic: In
main.ts:createTranscriptFile()around line 595 - Documentation: See
PDF-HANDLING.mdfor complete specification
Codebase Navigation Tips
Finding Functionality
| Task | File | Function/Area |
|---|---|---|
| Transcript fetching | youtube.ts |
getYouTubeTranscript() |
| LLM processing | llm/{provider}.ts |
processWithProvider() |
| Directory selection | main.ts |
createTranscriptFile() lines 580-594 |
| PDF generation | pdfGenerator.ts |
generatePdfFromMarkdown() |
| URL parsing | utils.ts |
extractVideoId() |
| Filename sanitization | utils.ts |
sanitizeFilename() |
| Settings UI | settingsTab.ts |
display() |
| Modal dialogs | modals.ts |
Various modal classes |
Understanding Settings
// Core settings
fileFormats: ("markdown" | "pdf" | "srt")[] // Available output formats
createNewFile: boolean // New file vs insert into current
defaultDirectory: string | null // Default location for files
// PDF-specific settings
createPdfCoverNote: boolean // Create MD note alongside PDF
pdfCoverNoteLocation: string // Where to put cover notes
// LLM settings
useLLMProcessing: boolean // Enable LLM cleanup
llmProvider: "openai" | "gemini" | "claude"
generateSummary: boolean // Create summary section
Important Quirks
-
Template variables are replaced in multiple places:
- File names:
defaultNoteName,defaultCoverNoteName - Paths:
pdfCoverNoteLocation - Variables:
{VideoName},{ChannelName},{PdfDirectory}
- File names:
-
Directory creation timing:
- Must happen AFTER all path calculations
- Including PDF nesting logic
- See lines 623-640 in
main.ts
-
Sanitization is critical:
- All filenames go through
sanitizeFilename() - Removes/replaces invalid filesystem characters
- Tested extensively in
test/sanitizeFilename.test.ts
- All filenames go through
-
Language selection:
preferredLanguagecan be comma-separated (e.g., "no,en,de")- Falls back through list until match found
- Empty string = auto-select
Testing Best Practices
When Adding Features
- Write tests alongside code - not after
- Test edge cases:
- Empty strings
- Missing/null values
- No active file
- Invalid input
- Test settings interactions:
- Multiple settings enabled
- Conflicting settings
- Missing required settings
Test Organization
describe('Feature Name', () => {
describe('sub-feature or edge case', () => {
it('should handle specific scenario', () => {
// Test
});
});
});
Running Specific Tests
npx vitest run extractVideoId # Run single test file
npm run test:watch # Watch mode
npm run test:coverage # Generate coverage report
Common Pitfalls to Avoid
- Don't break backward compatibility without migration code
- Don't forget to update tests when changing behavior
- Don't modify
utils.tswithout updating tests - utilities are heavily tested - Don't create directories too early - wait until final path is determined
- Don't assume active file exists - always check for null
- Don't hard-code paths - use path manipulation functions
- Don't ignore linter warnings - they often catch real issues
Documentation
When to Update Docs
- README.md: User-facing features, installation, basic usage
- PDF-HANDLING.md: PDF-related behavior, settings, examples
- TESTING.md: Testing infrastructure, commands, coverage
- AGENTS.md: (this file) - Agent-specific guidance
- GEMINI.md: Gemini-specific context and conventions
Documentation Style
- Be concise but complete
- Use code examples where helpful
- Include settings JSON examples
- Explain "why" not just "what"
- Update examples when behavior changes
Debugging Tips
Common Issues
"Cannot determine directory" error:
- Check if
selectedDirectoryis passed correctly - Verify
activeFileexists when expected - Look at directory selection logic in
main.ts
PDF not created:
- Check
pdfGenerator.tsfor errors - Verify markdown input is valid
- Check directory exists and is writable
LLM processing fails:
- Verify API key is set and valid
- Check timeout settings (
openaiTimeout) - Look at provider-specific error handling
Tests failing after changes:
- Run
npm testto see specific failures - Check if you updated default settings
- Verify backward compatibility isn't broken
Working with This Codebase
Before Making Changes
- Read relevant test files to understand expected behavior
- Check
PDF-HANDLING.mdif touching PDF logic - Look at existing similar features for patterns
- Understand the full data flow (see Architecture section)
After Making Changes
- Run full test suite:
npm test - Run linter:
npm run lint - Build:
npm run build - Update documentation if user-facing behavior changed
- Consider adding tests for new edge cases
Code Review Checklist
- Tests added/updated
- Linter passes
- Build succeeds
- Backward compatibility maintained
- Documentation updated if needed
- No hard-coded values
- Error handling in place
- Settings validated
File Size Reference
- main.ts: ~900 lines - plugin core logic
- youtube.ts: ~300 lines - transcript fetching
- settingsTab.ts: ~600 lines - settings UI
- pdfGenerator.ts: ~200 lines - PDF generation
- utils.ts: ~250 lines - shared utilities
Large files are well-organized with clear function boundaries. Don't hesitate to extract logic into new files if it improves clarity.
Getting Help
- Tests are documentation: Look at test files to understand expected behavior
- Custom instructions: See custom_instruction sections (project overview, conventions)
- Obsidian API: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin
- TypeScript: Strict mode enabled, pay attention to type errors
Version Information
This document is for agents working with the obsidian-ytt plugin codebase. Last updated during the "Remove core attachment folder support" refactoring (February 2026).
Current test count: 422 tests across 28 test files
Build system: ESBuild with TypeScript
Node version: Compatible with Obsidian's bundled version