Development Guide
July 22, 2026 · View on GitHub
Prerequisites
- Node.js 22 or newer (AI SDK 7 requirement); Node.js 24 LTS is used in CI
- npm 10 or newer (bundled with Node.js 24)
AI SDK 7 packages are ESM-only. Source code and build scripts use ESM imports, while esbuild bundles dependencies into the CommonJS main.js format required by Obsidian.
Build and Development Commands
# Install exactly from the lockfile
npm ci
# Development/watch build
npm run dev
# Production build with type checking
npm run build
# Tests
npm test -- --runInBand
npm run test:watch
npm run test:coverage
# Linting and formatting
npm run lint
npm run lint:fix
npm run format:check
# Bundle analysis
npm run build:analyze
npm run analyze
npm run build:size
npm run build:full-analysis
Use npm for installation and validation. package-lock.json is the dependency source of truth.
Build process
The plugin is bundled with esbuild.
- Entry point:
src/main.ts - Output:
main.js - Obsidian/Electron/CodeMirror modules are externalized.
- Production builds type-check with
tsc -noEmit -skipLibCheck, then bundle with esbuild. - Development builds run esbuild in watch mode.
main.js is generated. Do not edit it by hand.
Current project structure
src/
├── main.ts # Plugin entry point
├── Constants.ts # Shared constants and command IDs
├── core/
│ └── ServiceContainer.ts # Explicit dependency wiring
├── Commands/
│ ├── CommandHandler.ts # Command interfaces
│ ├── CommandRegistrar.ts # Registration helpers
│ └── *Handler.ts # Command implementations
├── Services/
│ ├── AiProviderService.ts # Unified AI request facade
│ ├── Adapters/ # Provider-specific adapters
│ ├── SettingsService.ts # Settings + effective frontmatter config
│ ├── ToolService.ts # Tool registration/approval/execution
│ └── *.ts # Editor, file, message, API, template services
├── Views/ # Obsidian settings tab and modals
├── Models/ # Internal data models
├── Types/ # Cross-service type contracts
├── Utilities/ # Stateless helpers
└── __mocks__/ # Jest mocks
Architecture overview
ServiceContainer
src/core/ServiceContainer.ts is the only place where services are wired together. It uses explicit constructor injection and exposes service instances as readonly properties.
Do not add a string-based service locator or decorator-based DI framework.
Command handlers
Commands live in src/Commands/. They should:
- read the editor/view/settings state they need,
- call services,
- show high-level status/notifications,
- avoid embedding provider/tool business logic.
Use CommandRegistrar where possible.
Provider adapters
AI providers are represented by adapters in src/Services/Adapters/ and orchestrated by AiProviderService.
The provider registry owns operational defaults, credential/URL setting keys, and factories. Adapters own protocol differences such as authentication headers for discovery, defensive model-list parsing, model-name extraction, API path suffixes, and exceptional tool-support behavior.
Settings/frontmatter
SettingsService loads plugin settings, runs migrations, and resolves effective per-note config. Preserve merge priority:
- provider defaults
- default chat frontmatter
- global settings
- agent frontmatter/body
- note frontmatter
Common tasks
Add a command
- Create a handler in
src/Commands/. - Implement
EditorCommandHandler,EditorViewCommandHandler, orCallbackCommandHandlerfromCommandHandler.ts. - Register the handler in
src/main.ts. - Add/update tests for extracted pure logic when possible.
Add provider behavior
See docs/CREATE_SERVICE.md. Provider support uses the operational registry plus adapters rather than one service class per provider.
Add a setting
- Update
ChatGPT_MDSettingsinsrc/Models/Config.ts. - Update
DEFAULT_SETTINGS. - Update
ChatGPT_MDSettingsTabUI schema/rendering. - Add migration logic if existing user data needs conversion.
- Document the setting if user-facing.
Test locally in Obsidian
-
Build:
npm run build -
Reload Obsidian or copy the plugin folder into a test vault's
.obsidian/plugins/chatgpt-md/folder. -
Open Obsidian developer tools and check the console.
Code style
- TypeScript only for source changes.
- Prefer
async/await. - Prefer small pure helpers for refactors.
- Avoid
anyin new code; useunknownplus small type guards at external boundaries. - Do not add new dependencies unless the benefit is clear and documented.
Refactoring guidance
The current maintainability roadmap and completed baseline are documented in planning/opensource-maintainability/.