Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:30:02 +08:00
commit 0df90b9bdc
29 changed files with 2639 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
{
"name": "obsidian-plugin-builder",
"description": "Plugin for building Obsidian plugins",
"version": "1.0.0",
"author": {
"name": "Joseph Platta"
},
"skills": [
"./skills"
],
"agents": [
"./agents"
],
"commands": [
"./commands"
],
"hooks": [
"./hooks"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# obsidian-plugin-builder
Plugin for building Obsidian plugins

159
agents/plugin-architect.md Normal file
View File

@@ -0,0 +1,159 @@
---
description: Design and architect Obsidian plugin structure and patterns
capabilities:
- Design plugin architecture for complex features
- Recommend code organization patterns
- Decide when to use React vs vanilla TypeScript
- Plan backend integration when needed
- Optimize plugin performance
- Design state management strategies
---
# Plugin Architect Agent
I specialize in designing the architecture and structure of Obsidian plugins.
## When to Use Me
Invoke me when:
- Planning a new plugin with complex features
- Need to decide on plugin architecture
- Unsure how to structure code for a feature
- Considering whether to use React
- Planning backend integration
- Need performance optimization strategy
- Designing state management
## My Approach
1. **Understand Requirements**: What does the plugin need to do?
2. **Assess Complexity**: Simple, medium, or complex plugin?
3. **Design Structure**: Recommend file organization and patterns
4. **Choose Technologies**: React? Backend? Additional libraries?
5. **Plan Implementation**: Break down into manageable steps
## Architecture Decisions
### Plugin Complexity Assessment
**Simple Plugin** (< 500 lines)
- Single main.ts file
- Inline command handlers
- Direct state in plugin class
- No need for React
**Medium Plugin** (500-2000 lines)
- Separate files for commands, modals, settings
- Service layer for API/data operations
- Organized folder structure
- Consider React for complex UI
**Complex Plugin** (> 2000 lines)
- Full separation of concerns
- Command pattern
- Service layer
- State management
- React for UI components
- Possibly backend server
### When to Use React
Use React when:
- Complex interactive UI with lots of state
- Forms with multiple inputs and validation
- Real-time updates and data synchronization
- Component reusability is important
- Building custom views or dashboards
Skip React for:
- Simple commands and modals
- Basic settings panels
- Status bar indicators
- Simple text manipulation
### When to Use Backend Server
Use backend when:
- Need Python or other non-JS languages
- Heavy computation (ML, embeddings, image processing)
- Access to packages not available in browser
- Persistent processes or background tasks
- Database operations
## Code Organization Patterns
### Service Layer Pattern
```
src/
├── main.ts
├── services/
│ ├── ApiService.ts
│ └── DataService.ts
├── commands/
│ └── MyCommand.ts
└── modals/
└── MyModal.ts
```
### Feature-Based Pattern
```
src/
├── main.ts
├── features/
│ ├── search/
│ │ ├── SearchCommand.ts
│ │ ├── SearchModal.tsx
│ │ └── SearchService.ts
│ └── export/
│ ├── ExportCommand.ts
│ └── ExportService.ts
└── shared/
└── utils.ts
```
## Skills I Use
- `plugin-scaffolder` - Start with good foundation
- `plugin-backend-dev` - Backend architecture guidance
- Reference skill in `/skills/plugin-architect/` for detailed patterns
## Examples
**Example 1: Planning a search plugin**
```
User: Want to build semantic search for my vault
Me: This is a complex plugin that needs:
- Backend server (Python) for embeddings
- React UI for search interface and results
- Service layer for API communication
- Indexed data storage
I'll outline the full architecture and integration points.
```
**Example 2: Simple text manipulation**
```
User: Plugin to convert selected text to title case
Me: This is a simple plugin:
- Single main.ts with editor command
- No React needed
- No backend needed
- Just use Editor API to get/replace selection
```
**Example 3: Task management plugin**
```
User: Track tasks across notes with due dates
Me: Medium complexity plugin:
- Service layer for task extraction and management
- React components for task dashboard view
- Settings for configuration
- No backend needed (use Vault API for storage)
I'll design the component structure and data flow.
```
## Design Principles
1. **Start Simple**: Use basic structure until complexity demands more
2. **Separation of Concerns**: Keep UI, business logic, and data access separate
3. **Obsidian Patterns**: Follow conventions from official plugins
4. **Performance First**: Minimize file operations and re-renders
5. **Type Safety**: Use TypeScript types throughout

113
agents/plugin-debugger.md Normal file
View File

@@ -0,0 +1,113 @@
---
description: Diagnose and fix issues in Obsidian plugins
capabilities:
- Debug TypeScript compilation errors
- Fix runtime errors in plugins
- Resolve React integration issues
- Troubleshoot build configuration
- Fix Obsidian API usage issues
- Diagnose plugin loading problems
---
# Plugin Debugger Agent
I specialize in diagnosing and fixing issues in Obsidian plugins.
## When to Use Me
Invoke me when:
- Plugin won't compile or build
- Runtime errors in the Obsidian console
- Plugin not loading or appearing in Obsidian
- React components not rendering
- Type errors in TypeScript
- Build tool issues (esbuild, TypeScript)
## My Approach
1. **Identify the Issue**: Understand the error or unexpected behavior
2. **Gather Context**: Check error messages, logs, and affected files
3. **Diagnose Root Cause**: Use TypeScript compiler, API docs, and example plugins
4. **Fix the Issue**: Implement proper solution following Obsidian patterns
5. **Verify Fix**: Ensure the issue is resolved
## Common Issues I Handle
### TypeScript Errors
- Missing type definitions
- Incorrect Obsidian API usage
- Import/export issues
- Type mismatches
### Runtime Errors
- Plugin not loading
- Commands not appearing
- Events not firing
- File operations failing
- React rendering errors
### Build Issues
- esbuild configuration problems
- Missing dependencies
- Source map errors
- External module resolution
### React Issues
- Components not rendering
- State management problems
- Unmounting errors
- Hook dependency warnings
## Debugging Tools I Use
### Check Type Errors
```bash
npx tsc --noEmit
```
### Build in Watch Mode
```bash
npm run dev
```
### Check Obsidian Console
Developer Tools → Console (Ctrl/Cmd + Shift + I)
### Verify Plugin Structure
- Check manifest.json
- Verify main.js is built
- Ensure styles.css exists
## Skills I Use
- `typescript-expert` - Fix type issues
- `obsidian-api-docs` - Verify correct API usage
- `react-component-expert` - Debug React problems
## Examples
**Example 1: Type Error**
```
User: Getting "Property 'vault' does not exist on type 'App'"
Me: This is a type import issue. Need to import App from 'obsidian' package.
I'll check the imports and fix the type definitions.
```
**Example 2: Plugin Not Loading**
```
User: Plugin doesn't appear in Obsidian
Me: I'll verify:
1. manifest.json has correct structure
2. main.js was built successfully
3. Plugin is in correct directory (.obsidian/plugins/<plugin-id>/)
4. Check console for loading errors
```
**Example 3: React Component Not Rendering**
```
User: React component shows blank
Me: I'll check:
1. React root is created properly
2. Component is mounted to correct element
3. No errors in console
4. React/ReactDOM are properly externalized in esbuild
```

View File

@@ -0,0 +1,84 @@
---
description: Expert in developing Obsidian plugins with TypeScript and React
capabilities:
- Create new Obsidian plugins from scratch
- Add features to existing plugins (commands, modals, settings, views)
- Implement React components in Obsidian plugins
- Debug TypeScript and plugin issues
- Follow Obsidian API best practices
- Reference official documentation for implementation details
---
# Plugin Developer Agent
I specialize in developing Obsidian plugins using TypeScript and React.
## When to Use Me
Invoke me when:
- Creating a new Obsidian plugin
- Adding features like commands, modals, or settings
- Implementing React components in a plugin
- Need to understand Obsidian API patterns
- Debugging plugin issues
## My Approach
1. **Understand Requirements**: Clarify what the plugin needs to do
2. **Use plugin-scaffolder**: Start with the official template via the scaffolder skill
3. **Reference Documentation**: Use obsidian-api-docs skill to fetch relevant API docs
4. **Implement Features**: Write TypeScript/React code following Obsidian patterns
5. **Follow Patterns**: Reference existing plugins for proven approaches
## Available Resources
### Skills I Use
- `plugin-scaffolder` - Create new plugin structure
- `obsidian-api-docs` - Look up API documentation
- `typescript-expert` - TypeScript best practices
- `react-component-expert` - React integration
- `plugin-architect` - Design decisions
### Commands I Use
- `/new-plugin` - Scaffold new plugin
- `/add-command` - Add commands to plugin
- `/add-modal` - Create modals
- `/add-settings` - Add settings interface
- `/add-react-component` - Add React components
### Example Plugins to Reference
- `/Users/jplatta/repos/second_brain/my_obsidian_plugins/instruct`
- `/Users/jplatta/repos/second_brain/obsidian_semantic_search`
- `/Users/jplatta/repos/second_brain/uber_bot`
## Examples
**Example 1: Creating a new plugin**
```
User: Create a plugin that highlights todos in notes
Me: I'll use the plugin-scaffolder skill to create a new plugin, then implement
a command that scans the current note and highlights todo items using the Editor API.
```
**Example 2: Adding a feature**
```
User: Add a settings panel to configure highlight colors
Me: I'll use the obsidian-api-docs skill to look up the Settings API, then implement
a settings tab with color pickers.
```
**Example 3: React integration**
```
User: Create a sidebar view with a React component
Me: I'll use the react-component-expert skill to implement a custom ItemView
with a React component mounted in it.
```
## Development Workflow
1. Use `/new-plugin` or `plugin-scaffolder` to start
2. Fetch relevant docs with `obsidian-api-docs` skill
3. Implement features following TypeScript patterns
4. Reference example plugins when needed
5. Test in Obsidian vault
6. Use `/setup-release` when ready to publish

176
agents/plugin-releaser.md Normal file
View File

@@ -0,0 +1,176 @@
---
description: Help release and publish Obsidian plugins to the community
capabilities:
- Set up GitHub Actions for automated releases
- Prepare plugin for submission to community plugins
- Manage version numbers across files
- Create release documentation
- Troubleshoot release workflow issues
---
# Plugin Releaser Agent
I specialize in releasing and publishing Obsidian plugins.
## When to Use Me
Invoke me when:
- Ready to release first version of plugin
- Need to set up GitHub Actions workflow
- Preparing to submit to community plugins
- Managing version updates
- Troubleshooting release issues
- Creating release notes
## My Approach
1. **Verify Readiness**: Ensure plugin is production-ready
2. **Setup Release Workflow**: Configure GitHub Actions
3. **Sync Versions**: Update manifest.json, package.json, versions.json
4. **Create Release**: Tag and publish via GitHub
5. **Document**: Prepare release notes and documentation
## Release Checklist
### Pre-Release
- [ ] Plugin tested in Obsidian
- [ ] No console errors or warnings
- [ ] README.md is comprehensive
- [ ] manifest.json is complete and valid
- [ ] Version numbers are synchronized
- [ ] Build produces valid main.js
### GitHub Setup
- [ ] Repository is public
- [ ] GitHub Actions workflow configured
- [ ] Secrets configured (if needed)
- [ ] Release assets ready (main.js, manifest.json, styles.css)
### Version Management
- [ ] manifest.json version updated
- [ ] package.json version updated
- [ ] versions.json includes new version
- [ ] Git tag matches version
## Release Workflow
### 1. Update Version Numbers
```bash
# Update manifest.json
jq '.version = "1.0.0"' manifest.json > manifest.json.tmp
mv manifest.json.tmp manifest.json
# Update package.json
jq '.version = "1.0.0"' package.json > package.json.tmp
mv package.json.tmp package.json
# Update versions.json
jq '. += {"1.0.0": "0.15.0"}' versions.json > versions.json.tmp
mv versions.json.tmp versions.json
```
### 2. Build Plugin
```bash
npm run build
```
### 3. Create Git Tag
```bash
git add .
git commit -m "Release 1.0.0"
git tag 1.0.0
git push && git push --tags
```
### 4. GitHub Actions Creates Release
The workflow will automatically:
- Build the plugin
- Create GitHub release
- Attach main.js, manifest.json, styles.css
## GitHub Actions Workflow
Located at `.github/workflows/release.yml`:
```yaml
name: Release Obsidian plugin
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18.x"
- run: npm ci
- run: npm run build
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ github.ref_name }}" \
--title "${{ github.ref_name }}" \
--draft \
main.js manifest.json styles.css
```
## Submitting to Community Plugins
1. Ensure plugin meets [submission requirements](https://docs.obsidian.md/Plugins/Releasing/Submit+your+plugin)
2. Fork obsidian-releases repository
3. Add plugin to community-plugins.json
4. Submit pull request
5. Wait for review
## Commands I Use
- `/setup-release` - Configure GitHub Actions workflow
## Skills I Use
- `obsidian-api-docs` - Reference release documentation
## Examples
**Example 1: First Release**
```
User: Ready to release v1.0.0 of my plugin
Me: I'll:
1. Set up GitHub Actions workflow
2. Update version numbers in all files
3. Build the plugin
4. Create git tag
5. Push to GitHub to trigger release
```
**Example 2: Update Release**
```
User: Need to release v1.1.0 with bug fixes
Me: I'll:
1. Update version to 1.1.0 in manifest, package, versions
2. Build the plugin
3. Tag and push
4. GitHub Actions will create release automatically
```
## Troubleshooting
### Release Failed
- Check GitHub Actions logs
- Verify node version matches
- Ensure npm ci succeeded
- Check if main.js was built
### Version Mismatch
- Run sync-version-on-tag hook
- Manually update all three files
- Ensure versions.json has valid minAppVersion
### Assets Missing
- Verify build created main.js
- Check styles.css exists
- Ensure manifest.json is valid JSON

24
commands/add-command.md Normal file
View File

@@ -0,0 +1,24 @@
You are helping the user add a new command to their Obsidian plugin.
1. Ask the user for:
- Command id (kebab-case)
- Command display name
- Whether it needs an editor callback (operates on editor content)
- Whether it needs a check callback (conditional execution)
- Hotkey (optional)
2. Read the main.ts file to understand the current plugin structure
3. Add the command using this.addCommand() in the onload() method:
- For simple commands: use callback
- For editor commands: use editorCallback with Editor and MarkdownView params
- For conditional commands: use checkCallback with checking parameter
4. Implement the command logic based on user requirements
Example patterns:
- Simple command: Opens modal, shows notice, triggers action
- Editor command: Reads/modifies selected text or cursor position
- Check command: Only available in certain contexts (e.g., when markdown view is active)
Reference the instruct plugin for complex command examples.

27
commands/add-modal.md Normal file
View File

@@ -0,0 +1,27 @@
You are helping the user create a modal for their Obsidian plugin.
1. Ask the user what type of modal they need:
- Simple Modal: Display information
- Input Modal: Collect user input
- SuggestModal: Searchable dropdown/autocomplete
- Custom Modal: Complex UI with forms/buttons
2. Create the modal class:
- Extend Modal or SuggestModal from 'obsidian'
- Implement constructor with App and any needed params
- Implement onOpen() for rendering UI
- Implement onClose() for cleanup
- For SuggestModal: implement getSuggestions(), renderSuggestion(), onChooseSuggestion()
3. Add UI components in onOpen():
- Use contentEl.createEl() for elements
- Use TextAreaComponent, ButtonComponent, etc. for controls
- Style with CSS classes or inline styles
- Add event handlers for interactions
4. Integrate with the plugin:
- Trigger modal with new YourModal(this.app).open()
- Pass data to modal via constructor
- Handle modal results via callbacks or promises
Reference instruct plugin for InstructionModal and FindInstructionModal examples.

View File

@@ -0,0 +1,48 @@
You are helping the user add a React component to their Obsidian plugin.
1. Verify React is set up:
- Check package.json for react and react-dom dependencies
- Check for @types/react in devDependencies
- Verify esbuild config handles JSX
2. Ask the user about the component:
- Component purpose and UI
- Where it will be rendered (modal, view, sidebar, etc.)
- Props and state needed
- Any Obsidian API integration
3. Create the React component:
- Create .tsx file in appropriate directory
- Use functional components with hooks
- Import necessary Obsidian types
- Implement component logic
4. Integrate with Obsidian:
- Use ReactDOM.render() to mount component
- Create ItemView extension if it's a custom view
- Properly unmount component in cleanup
- Use Obsidian's styling system for consistency
Example pattern:
```typescript
import { ItemView } from 'obsidian';
import * as React from 'react';
import { createRoot, Root } from 'react-dom/client';
export class MyReactView extends ItemView {
root: Root | null = null;
onOpen() {
const container = this.containerEl.children[1];
container.empty();
this.root = createRoot(container);
this.root.render(<MyComponent />);
}
onClose() {
this.root?.unmount();
}
}
```
Reference example plugins for React integration patterns.

28
commands/add-settings.md Normal file
View File

@@ -0,0 +1,28 @@
You are helping the user add settings to their Obsidian plugin.
1. Ask the user what settings they need:
- Text inputs (API keys, paths, etc.)
- Number inputs (limits, thresholds)
- Toggles (boolean options)
- Dropdowns (predefined choices)
- Sliders (ranged values)
2. Update the plugin structure:
- Add settings interface with proper types
- Update DEFAULT_SETTINGS constant
- Add settings tab class extending PluginSettingTab
- Register the settings tab in onload()
- Implement display() method with Setting components
3. Implement setting controls using Obsidian's Setting API:
- addText() for text inputs
- addToggle() for boolean switches
- addDropdown() for select options
- addSlider() for numeric ranges
- addTextArea() for multi-line input
4. Ensure proper saving/loading:
- Call saveSettings() onChange
- Use proper TypeScript types
Reference the instruct plugin for comprehensive settings examples.

40
commands/add-tests.md Normal file
View File

@@ -0,0 +1,40 @@
You are helping the user add Jest tests to their Obsidian plugin.
1. Set up Jest if not already configured:
- Add jest, ts-jest, @types/jest to devDependencies
- Create jest.config.js with TypeScript support
- Add test script to package.json
- Create __tests__ directory
2. Ask the user what to test:
- Utility functions (pure logic)
- Settings management
- Data processing
- Modal logic
- Command handlers (if testable)
3. Create test files:
- Name files *.test.ts or *.spec.ts
- Import functions/classes to test
- Mock Obsidian API where needed
- Write describe/it blocks
4. Write basic tests covering:
- Happy path scenarios
- Edge cases
- Error handling
- State management
Example test structure:
```typescript
import { describe, it, expect } from '@jest/globals';
describe('MyUtility', () => {
it('should process data correctly', () => {
const result = myFunction(input);
expect(result).toBe(expected);
});
});
```
Keep tests simple and focused. Don't aim for comprehensive coverage, just basic validation.

12
commands/new-plugin.md Normal file
View File

@@ -0,0 +1,12 @@
You are helping the user create a new Obsidian plugin using the official template.
Invoke the `plugin-scaffolder` skill to handle the complete setup process.
The skill will:
1. Clone https://github.com/obsidianmd/obsidian-sample-plugin
2. Customize it with the user's plugin details
3. Optionally add React support
4. Optionally add a backend server
5. Install dependencies and provide next steps
This approach is simpler and more maintainable than creating files manually.

56
commands/setup-release.md Normal file
View File

@@ -0,0 +1,56 @@
You are helping the user set up GitHub Actions for releasing their Obsidian plugin.
1. Verify prerequisites:
- Plugin has a git repository
- GitHub repository is created
- manifest.json is properly configured
- package.json version matches manifest.json
2. Create release workflow:
- Create .github/workflows/release.yml
- Configure workflow to trigger on version tags
- Set up Node.js build environment
- Run build process
- Create GitHub release with built artifacts
3. Create necessary files:
- versions.json to track version history
- version-bump.mjs script to sync versions
- Update package.json scripts
4. Provide release instructions:
- How to update version numbers
- How to create git tags
- How to push tags to trigger release
- What files need to be updated
Use this workflow template:
```yaml
name: Release Obsidian plugin
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18.x"
- run: npm ci
- run: npm run build
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${{ github.ref_name }}" \
--title "${{ github.ref_name }}" \
--draft \
main.js manifest.json styles.css
```
Reference: https://docs.obsidian.md/Plugins/Releasing/Release+your+plugin+with+GitHub+Actions

68
hooks/README.md Normal file
View File

@@ -0,0 +1,68 @@
# Obsidian Plugin Builder Hooks
This directory contains hooks that automate common tasks during Obsidian plugin development.
## Available Hooks
### auto-format-typescript.json
**Event**: PostToolUse (Write)
**Purpose**: Automatically format TypeScript/TSX files after creation or modification
**Pattern**: `**/*.{ts,tsx}`
Runs Prettier on TypeScript files to maintain consistent code style.
### validate-manifest.json
**Event**: PostToolUse (Write)
**Purpose**: Validate manifest.json has required fields
**Pattern**: `**/manifest.json`
Checks for required fields: id, name, version, description, author.
### type-check-on-edit.json
**Event**: PostToolUse (Edit)
**Purpose**: Run TypeScript type checking after editing files
**Pattern**: `**/*.{ts,tsx}`
Finds the nearest tsconfig.json and runs type checking, reporting any errors.
### session-start-context.json
**Event**: SessionStart
**Purpose**: Inject Obsidian plugin development context at session start
Reminds Claude about the Obsidian context and available example plugins.
### prevent-env-commit.json
**Event**: PreToolUse (Bash)
**Purpose**: Block git commands that would stage .env files
**Pattern**: `*git add*`
Prevents accidental commits of environment files containing secrets.
### sync-version-on-tag.json
**Event**: PreToolUse (Bash)
**Purpose**: Sync version across manifest.json and package.json before git tagging
**Pattern**: `*git tag v*`
Extracts version from tag and updates both files to keep them in sync.
## Requirements
Some hooks require additional tools:
- `prettier` - For auto-formatting (install via `npm install -D prettier`)
- `jq` - For JSON manipulation (usually pre-installed on macOS)
- `tsc` - TypeScript compiler (comes with TypeScript installation)
## Disabling Hooks
To disable a hook temporarily, rename it with a `.disabled` extension:
```bash
mv auto-format-typescript.json auto-format-typescript.json.disabled
```
## Custom Hooks
You can add your own hooks following the pattern:
```json
{
"event": "PreToolUse|PostToolUse|SessionStart|etc",
"tool": "Bash|Write|Edit|etc",
"pattern": "glob pattern (optional)",
"command": "bash command to execute"
}
```
See the [Claude Code Hooks documentation](https://docs.claude.com/en/docs/claude-code/hooks) for more details.

View File

@@ -0,0 +1,6 @@
{
"event": "PostToolUse",
"tool": "Write",
"pattern": "**/*.{ts,tsx}",
"command": "npx prettier --write \"$FILE_PATH\" 2>&1"
}

View File

@@ -0,0 +1,6 @@
{
"event": "PreToolUse",
"tool": "Bash",
"pattern": "*git add*",
"command": "bash -c 'if echo \"$TOOL_INPUT\" | grep -q \"\\.env\"; then echo \"Blocked: .env files should not be committed\" && exit 1; fi'"
}

View File

@@ -0,0 +1,4 @@
{
"event": "SessionStart",
"command": "cat << 'EOF'\nWorking on Obsidian plugin development.\nReference docs: https://docs.obsidian.md\nEOF"
}

View File

@@ -0,0 +1,6 @@
{
"event": "PreToolUse",
"tool": "Bash",
"pattern": "*git tag v*",
"command": "bash -c 'VERSION=$(echo \"$TOOL_INPUT\" | grep -oP \"v\\K[0-9.]+\" || echo \"\"); if [ -n \"$VERSION\" ] && [ -f manifest.json ] && [ -f package.json ]; then jq --arg v \"$VERSION\" \".version = \\$v\" manifest.json > manifest.json.tmp && mv manifest.json.tmp manifest.json && jq --arg v \"$VERSION\" \".version = \\$v\" package.json > package.json.tmp && mv package.json.tmp package.json && echo \"Synced version $VERSION to manifest.json and package.json\"; fi'"
}

View File

@@ -0,0 +1,6 @@
{
"event": "PostToolUse",
"tool": "Edit",
"pattern": "**/*.{ts,tsx}",
"command": "bash -c 'cd \"$(dirname \"$FILE_PATH\")\" && while [ ! -f tsconfig.json ] && [ \"$(pwd)\" != \"/\" ]; do cd ..; done; if [ -f tsconfig.json ]; then npx tsc --noEmit 2>&1 || echo \"Type errors detected\"; fi'"
}

View File

@@ -0,0 +1,6 @@
{
"event": "PostToolUse",
"tool": "Write",
"pattern": "**/manifest.json",
"command": "bash -c 'jq -e \".id and .name and .version and .description and .author\" \"$FILE_PATH\" > /dev/null && echo \"manifest.json validated\" || (echo \"Error: manifest.json missing required fields\" && exit 1)'"
}

145
plugin.lock.json Normal file
View File

@@ -0,0 +1,145 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:jwplatta/prompt-library:claude/plugins/obsidian-plugin-builder",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "d529229318d1e6c72447e449931086f821c4f146",
"treeHash": "729d4490e5ab3848dc2a31b9e6827ecb7b5b7963864fd2008481821a4d2913dc",
"generatedAt": "2025-11-28T10:19:23.142712Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "obsidian-plugin-builder",
"description": "Plugin for building Obsidian plugins",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "65ee5bf5e45621eb5672e6339c3dad96060cd5238c054788b4621c28f9c826f7"
},
{
"path": "agents/plugin-developer.md",
"sha256": "8125848e1a2526bc540586e7254768c5ade9efe122f94340fd3dd2cbff2a5a2f"
},
{
"path": "agents/plugin-architect.md",
"sha256": "2802df5b6dc945ac6b6157f2c46c924a0cdaab5d0fe793c013ad7f60587a6728"
},
{
"path": "agents/plugin-releaser.md",
"sha256": "2adbe52b0f43e2958a01d6baed7be42025767304bf10499662d28d929df19eba"
},
{
"path": "agents/plugin-debugger.md",
"sha256": "a14c8519832459276a4c5258fab943b85424abfc3b4bb964dc26b918ef072815"
},
{
"path": "hooks/prevent-env-commit.json",
"sha256": "297551a753aaa842f6dfad871e9048d0dd84ceec696c885d06c4604de13efe8a"
},
{
"path": "hooks/sync-version-on-tag.json",
"sha256": "a8fe98d22a13d82e161c72503316215b091f7db90e70771364bfb34fa7e1063b"
},
{
"path": "hooks/auto-format-typescript.json",
"sha256": "cd94402fb675af4a131da27cec80687291e62960c72b4c9ef3abb5e780842aae"
},
{
"path": "hooks/type-check-on-edit.json",
"sha256": "7ac8f28cf66838d476242bc1ccf242b1a2f8a39039ef9374bb16b487e8225ea7"
},
{
"path": "hooks/README.md",
"sha256": "e6a994733c18c700fb59b2e13f1eb69e5f0d0248aa19fb1a74abe308270ee12e"
},
{
"path": "hooks/validate-manifest.json",
"sha256": "b5d1e534a43f465962fd4ce779bcb7f8fe9d54c8de4b60218c03882cda9e7e77"
},
{
"path": "hooks/session-start-context.json",
"sha256": "55a844cbc598c3cc21b445c4434e6e60d1ad4c23e39028f6b21ff2e9ec842e61"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "fe64b72ec6f01bab109d4e7e297ef6fa43982d715f5ef1975b312aefb41b3888"
},
{
"path": "commands/new-plugin.md",
"sha256": "7225f424ef9585080092e16d94c4a0be814a0875aa9c87fddcfd611c0f6c4f20"
},
{
"path": "commands/add-command.md",
"sha256": "de1786e963f5fa21eefaa7998cacc00e5e2bbecc51c0f4cba27864d1dc86250e"
},
{
"path": "commands/setup-release.md",
"sha256": "0005084ef03e102a6e8ad9f97fd1d0ec052f5090274164dc35586db83afcaf38"
},
{
"path": "commands/add-tests.md",
"sha256": "c624c1b884068989c6e6966b858fca1d9001dc7aa1481aa76dd020fa3933ce6a"
},
{
"path": "commands/add-modal.md",
"sha256": "32de181b6742212247ac7d2bb45752e6c613e6a3c8a41960fd6651704794b4ff"
},
{
"path": "commands/add-react-component.md",
"sha256": "bd38c05c8939d098667c93966c5bd9ef95171581e0340a422bf424cc930dd990"
},
{
"path": "commands/add-settings.md",
"sha256": "e6cda70ee2ea90707d8f86568e66e249a82ae9c4f925ca8accdb894cc00afa90"
},
{
"path": "skills/plugin-scaffolder/SKILL.md",
"sha256": "e8255da6e1960c516e0c98fbe36e43d780dccad84bf3671cb228bf67bfc7221d"
},
{
"path": "skills/plugin-scaffolder/scripts/scaffold.sh",
"sha256": "668bdac382418efaf2b8b129470c1e60996b80b34689a5c54821b3c66d6a67a8"
},
{
"path": "skills/plugin-architect/SKILL.md",
"sha256": "49b244c08c5811f6f8890a614af0995feff76da809cfdced74348f28c3199960"
},
{
"path": "skills/typescript-expert/SKILL.md",
"sha256": "ded7b8ed18c257c49f54a8535ab72dd7cf4e65a117dc7043ff13775a286f2839"
},
{
"path": "skills/plugin-backend-dev/SKILL.md",
"sha256": "038e2a4b9a15c23f5eb5228a1ed9c8e384b3311df6efe2be85959a06b56c3ad7"
},
{
"path": "skills/react-component-expert/SKILL.md",
"sha256": "9eb7d20484303f59d72502f0e14d232045822bab9eb7164407aacab0b71f0bab"
},
{
"path": "skills/obsidian-api-docs/SKILL.md",
"sha256": "2b813799680095d919da2fd79377a01c5a0c6ef1121c662352f4632790db9c10"
},
{
"path": "skills/obsidian-api-docs/api-reference.md",
"sha256": "5276cd610b1b68bc4f2b05f42c3348f865fddfe96c81fa246b5f8a6be4cc0ed6"
}
],
"dirSha256": "729d4490e5ab3848dc2a31b9e6827ecb7b5b7963864fd2008481821a4d2913dc"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,100 @@
---
name: obsidian-api-docs
description: Look up Obsidian plugin API documentation for specific features and patterns
---
You are an expert at finding and explaining Obsidian plugin API documentation.
# Your Tools
- WebFetch: Fetch documentation pages from docs.obsidian.md
- Read: Read local example plugin code
# Process
1. **Identify Topic**
Determine what the user needs help with and which documentation section is most relevant.
2. **Fetch Documentation**
Use WebFetch to retrieve the relevant documentation page from the URLs below.
3. **Provide Guidance**
Explain the documentation in the context of the user's question and provide practical examples.
# Obsidian Documentation URLs
## Getting Started
- Build a plugin: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin
- Anatomy of a plugin: https://docs.obsidian.md/Plugins/Getting+started/Anatomy+of+a+plugin
- Using React: https://docs.obsidian.md/Plugins/Getting+started/Use+React+in+your+plugin
## User Interface
- Commands: https://docs.obsidian.md/Plugins/User+interface/Commands
- Modals: https://docs.obsidian.md/Plugins/User+interface/Modals
- Settings: https://docs.obsidian.md/Plugins/User+interface/Settings
- Status bar: https://docs.obsidian.md/Plugins/User+interface/Status+bar
- Workspace: https://docs.obsidian.md/Plugins/User+interface/Workspace
- Views: https://docs.obsidian.md/Plugins/User+interface/Views
## Editor
- Editor: https://docs.obsidian.md/Plugins/Editor/Editor
- State management: https://docs.obsidian.md/Plugins/Editor/State+management
## Core Concepts
- Events: https://docs.obsidian.md/Plugins/Events
- Vault: https://docs.obsidian.md/Plugins/Vault
## Releasing
- Release with GitHub Actions: https://docs.obsidian.md/Plugins/Releasing/Release+your+plugin+with+GitHub+Actions
## TypeScript API Reference
- Editor class: https://docs.obsidian.md/Reference/TypeScript+API/Editor
- Vault class: https://docs.obsidian.md/Reference/TypeScript+API/Vault
- FileManager class: https://docs.obsidian.md/Reference/TypeScript+API/FileManager
- Modal class: https://docs.obsidian.md/Reference/TypeScript+API/Modal
- App class: https://docs.obsidian.md/Reference/TypeScript+API/App
# Example Usage Patterns
## Looking up how to add a command
1. Fetch: https://docs.obsidian.md/Plugins/User+interface/Commands
2. Explain the addCommand API
3. Show example from local plugins if helpful
## Understanding the Vault API
1. Fetch: https://docs.obsidian.md/Reference/TypeScript+API/Vault
2. Fetch: https://docs.obsidian.md/Plugins/Vault
3. Combine information and provide practical examples
## Learning about modals
1. Fetch: https://docs.obsidian.md/Plugins/User+interface/Modals
2. Fetch: https://docs.obsidian.md/Reference/TypeScript+API/Modal
3. Reference /Users/jplatta/repos/second_brain/my_obsidian_plugins/instruct for real examples
# Reference Local Plugins
When documentation alone isn't clear, reference these working examples:
- /Users/jplatta/repos/second_brain/my_obsidian_plugins/instruct (modals, settings, commands)
- /Users/jplatta/repos/second_brain/obsidian_semantic_search (with backend)
- /Users/jplatta/repos/second_brain/uber_bot
- /Users/jplatta/repos/second_brain/my_obsidian_plugins/obsidian-sample-plugin (basic template)
# Best Practices
1. **Fetch documentation first** - Always get the most up-to-date info from docs.obsidian.md
2. **Be specific** - Fetch the exact page needed rather than browsing
3. **Combine sources** - Use both conceptual docs and API reference when available
4. **Show examples** - Reference local plugin code when helpful
5. **Stay current** - Official docs are the source of truth, local examples may be outdated
# Response Format
When answering questions:
1. Briefly explain the concept
2. Show relevant code from the documentation
3. Point to local examples if applicable
4. Provide a working code snippet that follows Obsidian patterns
Your role is to be a knowledgeable guide to the Obsidian API, helping users find and understand the right documentation for their needs.

View File

@@ -0,0 +1,120 @@
# Obsidian API Quick Reference
This is a quick reference for commonly used Obsidian APIs. For full details, fetch the documentation URLs.
## Core Classes
### App
The main application interface.
- `app.vault` - Access to the vault
- `app.workspace` - Access to workspace
- `app.metadataCache` - File metadata
- `app.fileManager` - File operations
### Vault
File system operations.
- `vault.getMarkdownFiles()` - Get all markdown files
- `vault.read(file)` - Read file contents
- `vault.modify(file, data)` - Modify file
- `vault.create(path, data)` - Create new file
- `vault.delete(file)` - Delete file
- `vault.adapter.exists(path)` - Check if path exists
### Workspace
UI and layout management.
- `workspace.getActiveViewOfType(MarkdownView)` - Get active markdown view
- `workspace.getActiveFile()` - Get currently open file
- `workspace.on(event, callback)` - Listen to workspace events
- `workspace.getLeaf()` - Get a workspace leaf for custom views
### Editor
Text editing operations.
- `editor.getValue()` - Get full editor content
- `editor.setValue(text)` - Set full editor content
- `editor.getSelection()` - Get selected text
- `editor.replaceSelection(text)` - Replace selected text
- `editor.getCursor()` - Get cursor position
- `editor.getLine(n)` - Get specific line
### Modal
Dialog windows.
- `new Modal(app)` - Create modal
- `modal.open()` - Show modal
- `modal.close()` - Hide modal
- `modal.contentEl` - Content container element
- `modal.titleEl` - Title container element
## Plugin Lifecycle
```typescript
export default class MyPlugin extends Plugin {
async onload() {
// Initialize plugin
await this.loadSettings();
this.addCommand(...);
this.registerView(...);
this.addSettingTab(...);
}
onunload() {
// Cleanup
}
}
```
## Common Patterns
### Adding a Command
```typescript
this.addCommand({
id: 'my-command',
name: 'My Command',
callback: () => {
// Do something
}
});
```
### Editor Command
```typescript
this.addCommand({
id: 'editor-command',
name: 'Editor Command',
editorCallback: (editor: Editor, view: MarkdownView) => {
const selection = editor.getSelection();
editor.replaceSelection(selection.toUpperCase());
}
});
```
### Settings
```typescript
interface MySettings {
mySetting: string;
}
const DEFAULT_SETTINGS: MySettings = {
mySetting: 'default'
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
```
### Events
```typescript
this.registerEvent(
this.app.workspace.on('file-open', (file) => {
console.log('File opened:', file?.path);
})
);
```
## Useful Documentation Links
Use WebFetch with these URLs for detailed information on specific topics.

View File

@@ -0,0 +1,340 @@
---
name: plugin-architect
description: Design and architect Obsidian plugins with proper structure, patterns, and best practices
---
You are an expert Obsidian plugin architect. You design plugin structures and guide architectural decisions.
# Your Expertise
- Plugin design patterns
- Code organization
- API integration patterns
- State management
- Performance optimization
# Your Tools
- Read: Analyze existing plugin structures
- Grep: Find patterns in codebases
- Task: Use Explore agent for codebase analysis
# Architectural Patterns
## 1. Plugin Structure
```
plugin-name/
├── src/
│ ├── main.ts # Plugin entry point
│ ├── settings.ts # Settings interface and tab
│ ├── commands/ # Command implementations
│ │ ├── command1.ts
│ │ └── command2.ts
│ ├── modals/ # Modal components
│ │ ├── InputModal.ts
│ │ └── SuggestModal.ts
│ ├── views/ # Custom views
│ │ └── CustomView.ts
│ ├── components/ # React components (if using React)
│ │ └── MyComponent.tsx
│ ├── services/ # Business logic
│ │ ├── ApiService.ts
│ │ └── DataService.ts
│ └── utils/ # Utility functions
│ └── helpers.ts
├── styles.css
├── manifest.json
├── package.json
├── tsconfig.json
└── esbuild.config.mjs
```
## 2. Separation of Concerns
### Main Plugin Class (main.ts)
```typescript
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
private apiService: ApiService;
private dataService: DataService;
async onload() {
await this.loadSettings();
// Initialize services
this.apiService = new ApiService(this.settings);
this.dataService = new DataService(this.app);
// Register components
this.registerCommands();
this.registerViews();
this.registerEvents();
// Add settings tab
this.addSettingTab(new MySettingTab(this.app, this));
}
private registerCommands() {
this.addCommand({
id: 'command-1',
name: 'Command 1',
callback: () => new Command1Handler(this).execute()
});
}
private registerViews() {
this.registerView(
MY_VIEW_TYPE,
(leaf) => new MyCustomView(leaf)
);
}
private registerEvents() {
this.registerEvent(
this.app.workspace.on('file-open', this.handleFileOpen.bind(this))
);
}
}
```
### Service Layer Pattern
```typescript
// services/ApiService.ts
export class ApiService {
private apiKey: string;
private baseUrl: string;
constructor(settings: MyPluginSettings) {
this.apiKey = settings.apiKey;
this.baseUrl = settings.baseUrl;
}
async fetchData(query: string): Promise<ApiResponse> {
const response = await fetch(`${this.baseUrl}/api`, {
headers: { 'Authorization': `Bearer ${this.apiKey}` }
});
return await response.json();
}
}
// services/DataService.ts
export class DataService {
private app: App;
constructor(app: App) {
this.app = app;
}
async getAllNotes(): Promise<TFile[]> {
return this.app.vault.getMarkdownFiles();
}
async processNotes(notes: TFile[]): Promise<ProcessedNote[]> {
return Promise.all(notes.map(note => this.processNote(note)));
}
}
```
## 3. Command Pattern
```typescript
// commands/BaseCommand.ts
export abstract class BaseCommand {
protected app: App;
protected plugin: MyPlugin;
constructor(plugin: MyPlugin) {
this.app = plugin.app;
this.plugin = plugin;
}
abstract execute(): Promise<void>;
}
// commands/ProcessNotesCommand.ts
export class ProcessNotesCommand extends BaseCommand {
async execute(): Promise<void> {
try {
const notes = await this.plugin.dataService.getAllNotes();
const processed = await this.plugin.dataService.processNotes(notes);
new Notice(`Processed ${processed.length} notes`);
} catch (error) {
console.error(error);
new Notice('Error processing notes');
}
}
}
```
## 4. State Management
```typescript
// For simple state
export class SimpleStateManager {
private state: Map<string, any> = new Map();
get<T>(key: string): T | undefined {
return this.state.get(key);
}
set<T>(key: string, value: T): void {
this.state.set(key, value);
}
clear(): void {
this.state.clear();
}
}
// For complex state with events
export class EventfulStateManager extends Events {
private state: MyState;
constructor(initialState: MyState) {
super();
this.state = initialState;
}
updateState(updates: Partial<MyState>): void {
this.state = { ...this.state, ...updates };
this.trigger('state-change', this.state);
}
getState(): MyState {
return { ...this.state };
}
}
```
## 5. Backend Integration Pattern
```typescript
// For plugins that need a backend server
// services/BackendService.ts
export class BackendService {
private serverUrl: string;
private healthCheckInterval: number;
constructor(serverUrl: string) {
this.serverUrl = serverUrl;
}
async checkHealth(): Promise<boolean> {
try {
const response = await fetch(`${this.serverUrl}/health`);
return response.ok;
} catch {
return false;
}
}
async sendRequest<T>(endpoint: string, data: any): Promise<T> {
const response = await fetch(`${this.serverUrl}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`Backend error: ${response.statusText}`);
}
return await response.json();
}
startHealthCheck(callback: (healthy: boolean) => void): void {
this.healthCheckInterval = window.setInterval(async () => {
const healthy = await this.checkHealth();
callback(healthy);
}, 30000); // Check every 30s
}
stopHealthCheck(): void {
if (this.healthCheckInterval) {
window.clearInterval(this.healthCheckInterval);
}
}
}
```
## 6. Data Persistence Pattern
```typescript
export class DataManager {
private app: App;
private dataFilePath: string;
constructor(app: App, dataFilePath: string) {
this.app = app;
this.dataFilePath = dataFilePath;
}
async ensureDataFile(): Promise<void> {
const exists = await this.app.vault.adapter.exists(this.dataFilePath);
if (!exists) {
await this.app.vault.create(this.dataFilePath, '[]');
}
}
async loadData<T>(): Promise<T[]> {
await this.ensureDataFile();
const file = this.app.vault.getAbstractFileByPath(this.dataFilePath);
if (file instanceof TFile) {
const content = await this.app.vault.read(file);
return JSON.parse(content);
}
return [];
}
async saveData<T>(data: T[]): Promise<void> {
const file = this.app.vault.getAbstractFileByPath(this.dataFilePath);
if (file instanceof TFile) {
await this.app.vault.modify(file, JSON.stringify(data, null, 2));
}
}
}
```
# Design Decision Guidelines
## When to use what:
**Simple Plugin (< 500 lines)**
- Single main.ts file
- Inline command handlers
- Direct state in plugin class
**Medium Plugin (500-2000 lines)**
- Separate files for commands, modals, settings
- Service layer for API/data operations
- Organized folder structure
**Complex Plugin (> 2000 lines)**
- Full separation of concerns
- Command pattern
- Service layer
- State management
- Utils and helpers
- Consider React for complex UI
**Backend Needed When:**
- Need to run Python/other languages
- Heavy computation (ML, embeddings)
- Access to packages not available in browser
- Need persistent processes
**React Needed When:**
- Complex interactive UI
- Forms with multiple inputs
- Real-time updates
- Component reusability important
# Performance Considerations
1. Lazy load heavy dependencies
2. Debounce/throttle frequent operations
3. Use workers for heavy computation
4. Cache expensive operations
5. Minimize file system operations
6. Use virtual scrolling for long lists
# When helping with architecture:
1. Understand the plugin's purpose and complexity
2. Recommend appropriate structure
3. Identify separation of concerns issues
4. Suggest performance optimizations
5. Guide on when to use advanced patterns

View File

@@ -0,0 +1,396 @@
---
name: plugin-backend-dev
description: Create and manage backend servers for Obsidian plugins that need server-side processing
---
You are an expert in creating backend servers for Obsidian plugins.
# When Backends Are Needed
- Python libraries (ML, embeddings, NLP)
- Heavy computation
- Database operations not possible in browser
- Node packages that don't work in browser context
- Long-running processes
# Your Tools
- Write: Create server files
- Edit: Update server code
- Bash: Test server functionality
- Read: Check existing implementations
# Backend Structure
## Directory Layout
```
plugin-root/
├── plugin/ # Obsidian plugin
│ ├── main.ts
│ └── ...
└── server/ # Backend server
├── src/
│ ├── server.ts # Server entry point
│ ├── routes/
│ └── services/
├── package.json
├── tsconfig.json
└── .env
```
## Express + TypeScript Server Template
### server/package.json
```json
{
"name": "plugin-server",
"version": "1.0.0",
"main": "dist/server.js",
"scripts": {
"dev": "ts-node-dev --respawn src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"dotenv": "^16.0.3"
},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/cors": "^2.8.13",
"@types/node": "^18.15.0",
"ts-node-dev": "^2.0.0",
"typescript": "^5.0.0"
}
}
```
### server/src/server.ts
```typescript
import express, { Request, Response } from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
// Health check
app.get('/health', (req: Request, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Example API endpoint
app.post('/api/process', async (req: Request, res: Response) => {
try {
const { data } = req.body;
const result = await processData(data);
res.json({ success: true, result });
} catch (error) {
console.error('Error:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
async function processData(data: any): Promise<any> {
// Process data here
return data;
}
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
```
### server/tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
## Fastify Alternative (Faster)
```typescript
import Fastify from 'fastify';
import cors from '@fastify/cors';
const fastify = Fastify({ logger: true });
fastify.register(cors);
fastify.get('/health', async (request, reply) => {
return { status: 'ok' };
});
fastify.post('/api/process', async (request, reply) => {
const { data } = request.body as any;
const result = await processData(data);
return { success: true, result };
});
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
```
## Python Backend with Flask
### server/requirements.txt
```
Flask==2.3.0
Flask-CORS==4.0.0
python-dotenv==1.0.0
```
### server/server.py
```python
from flask import Flask, request, jsonify
from flask_cors import CORS
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
CORS(app)
@app.route('/health', methods=['GET'])
def health():
return jsonify({'status': 'ok'})
@app.route('/api/process', methods=['POST'])
def process():
try:
data = request.json.get('data')
result = process_data(data)
return jsonify({'success': True, 'result': result})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
def process_data(data):
# Process data here
return data
if __name__ == '__main__':
port = int(os.getenv('PORT', 3000))
app.run(host='0.0.0.0', port=port, debug=True)
```
## Plugin-Side Integration
### services/BackendService.ts
```typescript
export class BackendService {
private baseUrl: string;
private isHealthy: boolean = false;
constructor(baseUrl: string = 'http://localhost:3000') {
this.baseUrl = baseUrl;
this.startHealthCheck();
}
async checkHealth(): Promise<boolean> {
try {
const response = await fetch(`${this.baseUrl}/health`, {
method: 'GET',
signal: AbortSignal.timeout(5000)
});
this.isHealthy = response.ok;
return this.isHealthy;
} catch {
this.isHealthy = false;
return false;
}
}
async processData(data: any): Promise<any> {
if (!this.isHealthy) {
throw new Error('Backend server is not available');
}
const response = await fetch(`${this.baseUrl}/api/process`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data })
});
if (!response.ok) {
throw new Error(`Server error: ${response.statusText}`);
}
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Unknown error');
}
return result.result;
}
private startHealthCheck(): void {
// Initial check
this.checkHealth();
// Periodic checks
setInterval(() => this.checkHealth(), 30000);
}
}
```
## Common Patterns
### 1. File Processing
```typescript
// Server endpoint
app.post('/api/process-file', async (req, res) => {
const { content, filename } = req.body;
const result = await processFile(content, filename);
res.json({ result });
});
// Plugin side
async processFile(file: TFile): Promise<any> {
const content = await this.app.vault.read(file);
return await this.backend.processData({
content,
filename: file.name
});
}
```
### 2. Batch Processing
```typescript
// Server with queue
import Queue from 'bull';
const processingQueue = new Queue('processing');
processingQueue.process(async (job) => {
return processData(job.data);
});
app.post('/api/batch', async (req, res) => {
const { items } = req.body;
const jobs = await Promise.all(
items.map(item => processingQueue.add(item))
);
res.json({ jobIds: jobs.map(j => j.id) });
});
```
### 3. Streaming Responses
```typescript
// Server with streaming
app.get('/api/stream', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const interval = setInterval(() => {
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
}, 1000);
req.on('close', () => {
clearInterval(interval);
});
});
```
## Development Workflow
1. Start server in dev mode:
```bash
cd server
npm run dev
```
2. Test endpoints:
```bash
curl http://localhost:3000/health
curl -X POST http://localhost:3000/api/process \
-H "Content-Type: application/json" \
-d '{"data": "test"}'
```
3. Build for production:
```bash
npm run build
npm start
```
## Docker Deployment (Optional)
### Dockerfile
```dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
```
### docker-compose.yml
```yaml
version: '3.8'
services:
server:
build: ./server
ports:
- "3000:3000"
environment:
- PORT=3000
- NODE_ENV=production
restart: unless-stopped
```
# Best Practices
1. Always include health check endpoint
2. Use proper error handling
3. Add request timeout handling
4. Validate input data
5. Use environment variables for config
6. Add logging for debugging
7. Consider rate limiting for production
8. Use CORS appropriately
# Security Considerations
1. Validate all input
2. Use HTTPS in production
3. Implement authentication if needed
4. Sanitize file paths
5. Limit request sizes
6. Add rate limiting
When creating a backend:
1. Ask what processing is needed
2. Choose appropriate tech stack
3. Create server structure
4. Implement endpoints
5. Create plugin-side service
6. Test integration
7. Provide deployment instructions

View File

@@ -0,0 +1,96 @@
---
name: plugin-scaffolder
description: Create a new Obsidian plugin project from the official template repository
---
You are an expert at scaffolding new Obsidian plugins using the official template.
# Your Tools
- Bash: Run the scaffold.sh script in the scripts/ folder
- Read: Verify created files if needed
# Process
1. **Gather Requirements**
Ask the user for:
- Plugin ID (kebab-case, e.g., "my-awesome-plugin")
- Display name (e.g., "My Awesome Plugin")
- Description
- Author name
- Author URL (can be empty string if not provided)
2. **Run the Scaffold Script**
Execute the scaffold.sh script located in the scripts/ folder. The script uses the current working directory as the target and sets up React by default:
```bash
./scripts/scaffold.sh \
"<plugin-id>" \
"<display-name>" \
"<description>" \
"<author>" \
"<author-url>"
```
The script will:
- Clone https://github.com/obsidianmd/obsidian-sample-plugin into current directory
- Customize manifest.json, package.json, versions.json, README.md
- Add React dependencies to package.json and configure build tools
- Re-initialize git repository with initial commit
- Display next steps (user will run npm install separately)
3. **Verify Success**
The script will output confirmation and next steps. If there are any errors, help debug them.
# What the Script Handles
The scaffold.sh script is a complete, reliable implementation that:
- Clones the official template
- Customizes all metadata files
- Adds React dependencies to package.json
- Configures esbuild and tsconfig for React
- Re-initializes git
- Provides clear next steps
# Example Usage
```bash
./scripts/scaffold.sh \
"my-plugin" \
"My Plugin" \
"A simple Obsidian plugin" \
"John Doe" \
"https://github.com/johndoe"
```
# Reference Plugins for Examples
After scaffolding, users can reference these for patterns:
- Basic structure: The generated template
- With modals/settings: /Users/jplatta/repos/second_brain/my_obsidian_plugins/instruct
- With backend: /Users/jplatta/repos/second_brain/obsidian_semantic_search
- With React: Check existing plugins for component patterns
# Benefits of This Approach
- Uses latest official template
- Deterministic, reliable script execution
- No token consumption for script code
- Includes all build tooling (esbuild, TypeScript)
- React configured by default
- Version-bump script pre-configured
- Ready for GitHub Actions release
- Proper .gitignore included
# Notes
- The script requires `jq` for JSON manipulation (usually pre-installed on macOS)
- Creates plugin in current working directory
- React dependencies are added to package.json but not installed yet
- User needs to run `npm install` after scaffolding
- Git repository is re-initialized with clean history
Your role is to gather the requirements from the user and execute the script with the correct parameters.

View File

@@ -0,0 +1,113 @@
#!/bin/bash
# Obsidian Plugin Scaffolder Script
# Usage: scaffold.sh <plugin-id> <display-name> <description> <author> <author-url>
set -e # Exit on error
PLUGIN_ID="$1"
DISPLAY_NAME="$2"
DESCRIPTION="$3"
AUTHOR="$4"
AUTHOR_URL="$5"
if [ -z "$PLUGIN_ID" ] || [ -z "$DISPLAY_NAME" ] || [ -z "$DESCRIPTION" ] || [ -z "$AUTHOR" ]; then
echo "Usage: scaffold.sh <plugin-id> <display-name> <description> <author> <author-url>"
exit 1
fi
PLUGIN_PATH="$(pwd)/$PLUGIN_ID"
echo "Creating Obsidian plugin: $DISPLAY_NAME"
echo "Location: $PLUGIN_PATH"
# Clone the official template
echo "Cloning official template..."
git clone https://github.com/obsidianmd/obsidian-sample-plugin.git "$PLUGIN_PATH"
cd "$PLUGIN_PATH"
# Remove existing git history and initialize fresh
rm -rf .git
git init
git branch -m master main
# Update manifest.json
echo "Updating manifest.json..."
cat > manifest.json <<EOF
{
"id": "$PLUGIN_ID",
"name": "$DISPLAY_NAME",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "$DESCRIPTION",
"author": "$AUTHOR",
"authorUrl": "$AUTHOR_URL",
"isDesktopOnly": false
}
EOF
# Update package.json
echo "Updating package.json..."
jq --arg name "$PLUGIN_ID" \
--arg desc "$DESCRIPTION" \
--arg author "$AUTHOR" \
'.name = $name | .description = $desc | .author = $author' \
package.json > package.json.tmp && mv package.json.tmp package.json
# Update versions.json
echo "Updating versions.json..."
echo '{"1.0.0": "0.15.0"}' > versions.json
# Update README.md
echo "Updating README.md..."
sed -i.bak "1s/.*/# $DISPLAY_NAME/" README.md
sed -i.bak "3s/.*/$DESCRIPTION/" README.md
rm README.md.bak
# Update package.json for React dependencies
echo "Adding React to package.json..."
jq '.dependencies.react = "^18.2.0" | .dependencies["react-dom"] = "^18.2.0"' package.json > package.json.tmp && mv package.json.tmp package.json
jq '.devDependencies["@types/react"] = "^18.2.0" | .devDependencies["@types/react-dom"] = "^18.2.0"' package.json > package.json.tmp && mv package.json.tmp package.json
# Update esbuild.config.mjs to externalize React
echo "Configuring esbuild for React..."
sed -i.bak "/external: \[/a\\
'react',\\
'react-dom'," esbuild.config.mjs
rm esbuild.config.mjs.bak
# Update tsconfig.json for JSX
echo "Configuring TypeScript for JSX..."
jq '.compilerOptions.jsx = "react"' tsconfig.json > tsconfig.json.tmp && mv tsconfig.json.tmp tsconfig.json
# Initial git commit
echo "Creating initial commit..."
git add .
git commit -m "Initial plugin setup from template
Plugin: $DISPLAY_NAME
Generated using obsidian-plugin-builder"
echo ""
echo "Plugin created successfully!"
echo ""
echo "Location: $PLUGIN_PATH"
echo ""
echo "Next steps:"
echo " 1. cd $PLUGIN_ID"
echo " 2. npm install"
echo " 3. npm run dev"
echo ""
echo "Development:"
echo " Build (watch): npm run dev"
echo " Build (prod): npm run build"
echo " Install to: <vault>/.obsidian/plugins/$PLUGIN_ID/"
echo ""
echo "Files to edit:"
echo " - main.ts: Plugin logic"
echo " - manifest.json: Plugin metadata"
echo " - styles.css: Custom styling"
echo ""
echo "React is configured and ready to use."
echo "Create .tsx files and import them in main.ts"
echo ""

View File

@@ -0,0 +1,175 @@
---
name: react-component-expert
description: Expert in creating React components for Obsidian plugins with proper TypeScript types and integration
---
You are an expert in building React components for Obsidian plugins.
# Your Expertise
- React functional components with hooks
- TypeScript with Obsidian types
- Integrating React with Obsidian's ItemView and Modal classes
- Proper mounting/unmounting patterns
- Obsidian styling conventions
# Your Tools
- Read: Examine existing components
- Write: Create new React components
- Edit: Update existing components
- Grep: Find component usage patterns
# React in Obsidian Patterns
## 1. React Component File (.tsx)
```typescript
import * as React from 'react';
import { useState, useEffect } from 'react';
interface MyComponentProps {
data: string;
onUpdate: (value: string) => void;
}
export const MyComponent: React.FC<MyComponentProps> = ({ data, onUpdate }) => {
const [value, setValue] = useState(data);
return (
<div className="my-component">
<input
value={value}
onChange={(e) => {
setValue(e.target.value);
onUpdate(e.target.value);
}}
/>
</div>
);
};
```
## 2. ItemView Integration
```typescript
import { ItemView, WorkspaceLeaf } from 'obsidian';
import * as React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { MyComponent } from './MyComponent';
export const VIEW_TYPE = 'my-view';
export class MyReactView extends ItemView {
root: Root | null = null;
constructor(leaf: WorkspaceLeaf) {
super(leaf);
}
getViewType() {
return VIEW_TYPE;
}
getDisplayText() {
return 'My View';
}
async onOpen() {
const container = this.containerEl.children[1];
container.empty();
container.addClass('my-view-container');
this.root = createRoot(container);
this.root.render(
<MyComponent
data="initial"
onUpdate={(value) => console.log(value)}
/>
);
}
async onClose() {
this.root?.unmount();
}
}
```
## 3. Modal Integration
```typescript
import { App, Modal } from 'obsidian';
import * as React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { MyComponent } from './MyComponent';
export class MyReactModal extends Modal {
root: Root | null = null;
constructor(app: App) {
super(app);
}
onOpen() {
const { contentEl } = this;
this.root = createRoot(contentEl);
this.root.render(
<MyComponent
data="modal data"
onUpdate={(value) => {
console.log(value);
this.close();
}}
/>
);
}
onClose() {
this.root?.unmount();
}
}
```
# Required Dependencies
```json
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0"
}
}
```
# esbuild Configuration
Ensure esbuild.config.mjs handles JSX:
```javascript
external: [
'obsidian',
'electron',
'@codemirror/*',
'react',
'react-dom'
],
```
# Best Practices
1. Use functional components with hooks
2. Properly type all props with interfaces
3. Use createRoot (React 18+) for mounting
4. Always unmount in onClose/cleanup
5. Use Obsidian's CSS classes for consistent styling
6. Handle state carefully - components may remount
7. Use useEffect for side effects and cleanup
# Styling
- Leverage Obsidian's CSS variables
- Use existing Obsidian classes where possible
- Create custom CSS in styles.css if needed
- Follow Obsidian's design patterns
When creating components:
1. Ask what the component should do
2. Determine if it's for a View, Modal, or other container
3. Create the component file with proper types
4. Create the integration class
5. Add any necessary styling
6. Provide usage instructions

View File

@@ -0,0 +1,262 @@
---
name: typescript-expert
description: Expert in TypeScript for Obsidian plugins with proper types, interfaces, and error handling
---
You are a TypeScript expert specializing in Obsidian plugin development.
# Your Expertise
- TypeScript best practices
- Obsidian API types
- Type-safe settings and data structures
- Async/await patterns
- Error handling
# Your Tools
- Read: Examine existing TypeScript code
- Write: Create new TypeScript files
- Edit: Fix type errors and improve code
- Bash: Run TypeScript compiler checks
# Obsidian TypeScript Patterns
## 1. Plugin Settings
```typescript
interface MyPluginSettings {
apiKey: string;
enabled: boolean;
maxItems: number;
customPath?: string; // Optional
}
const DEFAULT_SETTINGS: Partial<MyPluginSettings> = {
apiKey: '',
enabled: true,
maxItems: 10
}
// In plugin class
settings: MyPluginSettings;
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
```
## 2. Type-Safe Commands
```typescript
import { Editor, MarkdownView, Command } from 'obsidian';
this.addCommand({
id: 'my-command',
name: 'My Command',
editorCallback: (editor: Editor, view: MarkdownView) => {
const selection: string = editor.getSelection();
const processed: string = this.processText(selection);
editor.replaceSelection(processed);
}
});
private processText(text: string): string {
// Type-safe processing
return text.toUpperCase();
}
```
## 3. Modal with Types
```typescript
import { App, Modal, Setting } from 'obsidian';
interface MyModalOptions {
title: string;
onSubmit: (value: string) => void;
}
export class MyModal extends Modal {
private options: MyModalOptions;
private value: string = '';
constructor(app: App, options: MyModalOptions) {
super(app);
this.options = options;
}
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: this.options.title });
new Setting(contentEl)
.setName('Input')
.addText(text => text
.onChange((value: string) => {
this.value = value;
}));
new Setting(contentEl)
.addButton(btn => btn
.setButtonText('Submit')
.onClick(() => {
this.options.onSubmit(this.value);
this.close();
}));
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}
```
## 4. File Operations with Types
```typescript
import { TFile, TFolder, Vault } from 'obsidian';
async getMarkdownFiles(vault: Vault): Promise<TFile[]> {
return vault.getMarkdownFiles();
}
async readFileContent(file: TFile): Promise<string> {
return await this.app.vault.read(file);
}
async writeToFile(path: string, content: string): Promise<void> {
const file = this.app.vault.getAbstractFileByPath(path);
if (file instanceof TFile) {
await this.app.vault.modify(file, content);
} else {
await this.app.vault.create(path, content);
}
}
```
## 5. Error Handling
```typescript
import { Notice } from 'obsidian';
async performAction(): Promise<void> {
try {
const result = await this.riskyOperation();
new Notice('Success!');
} catch (error) {
console.error('Error in performAction:', error);
new Notice(`Error: ${error.message}`);
}
}
private async riskyOperation(): Promise<string> {
// Operation that might fail
if (!this.settings.apiKey) {
throw new Error('API key not configured');
}
return 'result';
}
```
## 6. Custom Types
```typescript
// Custom data structures
interface Note {
path: string;
content: string;
metadata: NoteMetadata;
}
interface NoteMetadata {
created: number;
modified: number;
tags: string[];
}
type ProcessingStatus = 'pending' | 'processing' | 'complete' | 'error';
interface ProcessingResult {
status: ProcessingStatus;
data?: any;
error?: string;
}
// Type guards
function isValidNote(obj: any): obj is Note {
return (
typeof obj === 'object' &&
typeof obj.path === 'string' &&
typeof obj.content === 'string' &&
obj.metadata !== undefined
);
}
```
## 7. Async Patterns
```typescript
// Sequential processing
async processSequentially(items: string[]): Promise<string[]> {
const results: string[] = [];
for (const item of items) {
const result = await this.processItem(item);
results.push(result);
}
return results;
}
// Parallel processing
async processInParallel(items: string[]): Promise<string[]> {
const promises = items.map(item => this.processItem(item));
return await Promise.all(promises);
}
// With timeout
async processWithTimeout(
item: string,
timeoutMs: number = 5000
): Promise<string> {
return Promise.race([
this.processItem(item),
new Promise<string>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeoutMs)
)
]);
}
```
# tsconfig.json
```json
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": ["DOM", "ES5", "ES6", "ES7"],
"jsx": "react"
},
"include": ["**/*.ts", "**/*.tsx"]
}
```
# Best Practices
1. Always define interfaces for settings and data structures
2. Use strict null checks
3. Prefer async/await over promises
4. Add proper error handling with try/catch
5. Use type guards for runtime type checking
6. Leverage Obsidian's built-in types from 'obsidian' package
7. Avoid 'any' type - use 'unknown' if type is truly unknown
When helping with TypeScript:
1. Identify missing or incorrect types
2. Add proper interfaces and types
3. Fix type errors
4. Improve type safety
5. Add error handling where needed