docs: rewrite AGENTS.md to reflect actual project state

Replace generic Obsidian sample plugin template with project-specific
documentation covering all 15 source files, 6 commands, full settings
schema, and architecture details for each module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca G. Oelfke 2026-02-06 11:05:03 +01:00
parent d61ce3a5dd
commit e922523dc9
No known key found for this signature in database
GPG key ID: E22BABF67200F864

366
AGENTS.md
View file

@ -1,251 +1,187 @@
# Obsidian community plugin # Claude Context Obsidian Plugin
## Project overview ## Project overview
- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript). An Obsidian community plugin that manages and copies vault context to clipboard for use with LLMs. Stores conventions, structure, and rules in a dedicated folder and provides one-hotkey copy with multi-LLM output formatting, prompt templates, frontmatter presets, granular section selection, and context history.
- Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian.
- Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`. - **Entry point**: `src/main.ts` → compiled to `main.js` via esbuild
- **Release artifacts**: `main.js`, `manifest.json`, `styles.css` (optional)
- **Plugin ID**: `claude-context`
- **Desktop only**: `true` (uses Node.js `fs` and `child_process` for external sources)
## Environment & tooling ## Environment & tooling
- Node.js: use current LTS (Node 18+ recommended). - **Runtime**: Obsidian (Electron/Chromium)
- **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies). - **Language**: TypeScript (`strict: true`)
- **Bundler: esbuild** (required for this sample - `esbuild.config.mjs` and build scripts depend on it). Alternative bundlers like Rollup or webpack are acceptable for other projects if they bundle all external dependencies into `main.js`. - **Package manager**: npm
- Types: `obsidian` type definitions. - **Bundler**: esbuild (config in `esbuild.config.mjs`)
- **Target**: ES2018, CommonJS output
- **Dependencies**: only `obsidian` (latest) no external runtime deps
**Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you can choose different tools, but you'll need to replace the build configuration accordingly. ### Commands
### Install
```bash ```bash
npm install npm install # Install dependencies
npm run dev # Watch mode (incremental, inline sourcemaps)
npm run build # Type-check + production build (minified, no sourcemaps)
``` ```
### Dev (watch) ### Testing
```bash Manual only copy `main.js`, `manifest.json`, `styles.css` to `<Vault>/.obsidian/plugins/claude-context/`, reload Obsidian, enable plugin.
npm run dev
```
### Production build ## Source file structure
```bash
npm run build
```
## Linting
- To use eslint install eslint from terminal: `npm install -g eslint`
- To use eslint to analyze this project use this command: `eslint main.ts`
- eslint will then create a report with suggestions for code improvement by file and line number.
- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: `eslint ./src/`
## File & folder conventions
- **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`.
- Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands).
- **Example file structure**:
``` ```
src/ src/
main.ts # Plugin entry point, lifecycle management ├── main.ts # Plugin class, command registration, core copy logic
settings.ts # Settings interface and defaults ├── settings.ts # ClaudeContextSettings interface, defaults, settings tab UI
commands/ # Command implementations ├── generator.ts # Context generator modal (vault structure setup wizard)
command1.ts ├── sources.ts # External context sources (freetext, file, folder, shell)
command2.ts ├── source-modal.ts # Modal for adding/editing context sources
ui/ # UI components, modals, views ├── presets.ts # Frontmatter preset parser & executor (ai-context block)
modal.ts ├── targets.ts # Multi-target output system (format, truncation, token limits)
view.ts ├── target-modal.ts # Modal for adding/editing output targets
utils/ # Utility functions, helpers ├── templates.ts # Prompt template engine & 5 starter templates
helpers.ts ├── template-modal.ts # Modal for adding/editing templates + import/export
constants.ts ├── history.ts # Context history manager (save, cleanup, versioning)
types.ts # TypeScript interfaces and types ├── history-modal.ts # History viewer modal (browse, diff, restore)
├── content-selector.ts # Heading/block tree parser for granular selection
├── file-selector-modal.ts # Modal for selecting files and sections
├── preview.ts # Preview modal before copying
``` ```
- **Do not commit build artifacts**: Never commit `node_modules/`, `main.js`, or other generated files to version control.
- Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages.
- Generated output should be placed at the plugin root or `dist/` depending on your build setup. Release artifacts must end up at the top level of the plugin folder in the vault (`main.js`, `manifest.json`, `styles.css`).
## Manifest rules (`manifest.json`) ## Plugin commands
- Must include (non-exhaustive): | ID | Name | Description |
- `id` (plugin ID; for local dev it should match the folder name) |----|------|-------------|
- `name` | `copy-context` | Copy context to clipboard | Main command copies all context files + sources |
- `version` (Semantic Versioning `x.y.z`) | `copy-context-with-note` | Copy context with current note | Same as above but always includes active note |
- `minAppVersion` | `copy-context-selective` | Copy context (select sections) | Opens file/heading selector modal |
- `description` | `copy-context-from-preset` | Copy context from frontmatter preset | Reads `ai-context` YAML block from active note |
- `isDesktopOnly` (boolean) | `generate-context` | Generate context files | Opens setup wizard to create initial context folder |
- Optional: `author`, `authorUrl`, `fundingUrl` (string or map) | `view-history` | View context history | Opens history browser with diff and restore |
- Never change `id` after release. Treat it as stable API.
- Keep `minAppVersion` accurate when using newer APIs.
- Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml
## Testing Ribbon icon: `clipboard-copy` → triggers `copy-context`.
- Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to: ## Settings (`ClaudeContextSettings`)
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `contextFolder` | `string` | `'_claude'` | Vault folder containing context `.md` files |
| `separator` | `string` | `'---'` | Text separator between files in output |
| `includeFilenames` | `boolean` | `true` | Add `# === filename ===` headers |
| `showPreview` | `boolean` | `false` | Show preview modal before copying |
| `includeActiveNote` | `boolean` | `false` | Always append the currently open note |
| `excludedFiles` | `string[]` | `[]` | Filenames to skip (case-insensitive) |
| `sources` | `ContextSource[]` | `[]` | External context sources |
| `showSourceLabels` | `boolean` | `true` | Add position/name labels to source output |
| `promptTemplates` | `PromptTemplate[]` | `[]` | Prompt templates with placeholders |
| `defaultTemplateId` | `string \| null` | `null` | Template applied automatically on copy |
| `history` | `HistorySettings` | see below | History configuration |
| `targets` | `OutputTarget[]` | `[]` | Multi-LLM output targets |
| `primaryTargetId` | `string \| null` | `null` | Target that goes to clipboard |
| `targetOutputFolder` | `string` | `'_claude/outputs'` | Folder for secondary target files |
### History settings
| Setting | Type | Default |
|---------|------|---------|
| `enabled` | `boolean` | `false` |
| `storageFolder` | `string` | `'.context-history'` |
| `maxEntries` | `number` | `50` |
| `autoCleanupDays` | `number` | `30` |
## Architecture & key modules
### Context sources (`sources.ts`)
Four source types, each with `position: 'prefix' | 'suffix'`:
| Type | Config | Notes |
|------|--------|-------|
| `freetext` | `{ content: string }` | Inline text snippets |
| `file` | `{ path: string }` | External file (absolute path, requires Node.js `fs`) |
| `folder` | `{ path: string; recursive: boolean; pattern?: string }` | Directory with optional glob filter |
| `shell` | `{ command: string; args: string[]; cwd?: string; timeout?: number }` | Command output (default timeout 5s, max 1MB) |
### Output targets (`targets.ts`)
Three built-in targets (can add custom):
| Name | Tokens | Format | Strategy |
|------|--------|--------|----------|
| Claude | 200,000 | `xml` | `summarize-headers` |
| GPT-4o | 128,000 | `markdown` | `summarize-headers` |
| Compact (8k) | 8,000 | `plain` | `drop-sections` |
**Formats**: `markdown` (default), `xml` (wraps in `<file name="...">` tags), `plain` (strips markdown).
**Truncation strategies**: `truncate` (hard cut), `summarize-headers` (keep headers, collapse content), `drop-sections` (remove low-priority sections entirely).
**Priority order for truncation**: VAULT.md > context files > conventions > structure > active note > examples/templates.
### Prompt templates (`templates.ts`)
Handlebars-like engine with variables: `{{context}}`, `{{selection}}`, `{{active_note}}`, `{{active_note_name}}`, `{{date}}`, `{{time}}`, `{{datetime}}`, `{{vault_name}}`. Supports `{{#if var}}...{{else}}...{{/if}}` conditionals.
Five starter templates: Code Review, Summary, Q&A, Continue Writing, Explain.
### Frontmatter presets (`presets.ts`)
Reads `ai-context` YAML block from the active note's frontmatter:
```yaml
ai-context:
template: "Template Name"
include-linked: true
link-depth: 2 # 0-10
include-tags: [tag1]
exclude-paths: [archive/]
exclude-tags: [draft]
max-tokens: 50000
include-active-note: true
``` ```
<Vault>/.obsidian/plugins/<plugin-id>/
```
- Reload Obsidian and enable the plugin in **Settings → Community plugins**.
## Commands & settings Features: hierarchical tag matching, link traversal with cycle detection, token budgeting, full validation with errors/warnings.
- Any user-facing commands should be added via `this.addCommand(...)`. ### Content selector (`content-selector.ts`)
- If the plugin has configuration, provide a settings tab and sensible defaults.
- Persist settings using `this.loadData()` / `this.saveData()`.
- Use stable command IDs; avoid renaming once released.
## Versioning & releases Parses heading tree (H1H6) and block IDs (`^blockid`) from markdown files. Enables partial file selection via `FileSelectorModal`. Output marks partial files with `(partial)` suffix.
- Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version → minimum app version. ### History (`history.ts`)
- Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`.
- Attach `manifest.json`, `main.js`, and `styles.css` (if present) to the release as individual assets.
- After the initial release, follow the process to add/update your plugin in the community catalog as required.
## Security, privacy, and compliance Stores each generated context as JSON in vault. Tracks metadata: template used, included files/sources, active note, char/token counts, user notes. Auto-cleanup by age and entry count.
Follow Obsidian's **Developer Policies** and **Plugin Guidelines**. In particular:
- Default to local/offline operation. Only make network requests when essential to the feature.
- No hidden telemetry. If you collect optional analytics or call third-party services, require explicit opt-in and document clearly in `README.md` and in settings.
- Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases.
- Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault.
- Clearly disclose any external services used, data sent, and risks.
- Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented.
- Avoid deceptive patterns, ads, or spammy notifications.
- Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely.
## UX & copy guidelines (for UI text, commands, settings)
- Prefer sentence case for headings, buttons, and titles.
- Use clear, action-oriented imperatives in step-by-step copy.
- Use **bold** to indicate literal UI labels. Prefer "select" for interactions.
- Use arrow notation for navigation: **Settings → Community plugins**.
- Keep in-app strings short, consistent, and free of jargon.
## Performance
- Keep startup light. Defer heavy work until needed.
- Avoid long-running tasks during `onload`; use lazy initialization.
- Batch disk access and avoid excessive vault scans.
- Debounce/throttle expensive operations in response to file system events.
## Coding conventions ## Coding conventions
- TypeScript with `"strict": true` preferred. - `main.ts` handles plugin lifecycle and command registration all feature logic lives in dedicated modules.
- **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules. - Each module exports its types/interfaces alongside implementation.
- **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules. - Modals are in separate `*-modal.ts` files, business logic in the corresponding `.ts` file.
- **Use clear module boundaries**: Each file should have a single, well-defined responsibility. - Settings are persisted via `loadData()`/`saveData()` with `Object.assign` merge pattern.
- Bundle everything into `main.js` (no unbundled runtime deps). - VAULT.md is always sorted first in context output.
- Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly. - Token estimation: `chars / 4` (rough approximation).
- Prefer `async/await` over promise chains; handle errors gracefully. - No external runtime dependencies everything bundled into `main.js`.
## Mobile ## Obsidian plugin conventions
- Where feasible, test on iOS and Android. - Add commands with stable IDs never rename after release.
- Don't assume desktop-only behavior unless `isDesktopOnly` is `true`. - Use `this.register*` helpers for cleanup on unload.
- Avoid large in-memory structures; be mindful of memory and storage constraints. - No network calls without explicit user opt-in and documentation.
- No telemetry, no remote code execution.
- Keep `minAppVersion` accurate (`0.15.0` currently).
- Bump `version` in `manifest.json` (SemVer), update `versions.json`, create GitHub release (tag = version, no `v` prefix).
## Agent do/don't ## Manifest (`manifest.json`)
**Do** ```json
- Add commands with stable IDs (don't rename once released). {
- Provide defaults and validation in settings. "id": "claude-context",
- Write idempotent code paths so reload/unload doesn't leak listeners or intervals. "name": "Claude Context",
- Use `this.register*` helpers for everything that needs cleanup. "version": "1.0.0",
"minAppVersion": "0.15.0",
**Don't** "description": "Copy .claude/ context files to clipboard with one hotkey.",
- Introduce network calls without an obvious user-facing reason and documentation. "author": "Luca",
- Ship features that require cloud services without clear disclosure and explicit opt-in. "isDesktopOnly": true
- Store or transmit vault contents unless essential and consented.
## Common tasks
### Organize code across multiple files
**main.ts** (minimal, lifecycle only):
```ts
import { Plugin } from "obsidian";
import { MySettings, DEFAULT_SETTINGS } from "./settings";
import { registerCommands } from "./commands";
export default class MyPlugin extends Plugin {
settings: MySettings;
async onload() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
registerCommands(this);
}
} }
``` ```
**settings.ts**:
```ts
export interface MySettings {
enabled: boolean;
apiKey: string;
}
export const DEFAULT_SETTINGS: MySettings = {
enabled: true,
apiKey: "",
};
```
**commands/index.ts**:
```ts
import { Plugin } from "obsidian";
import { doSomething } from "./my-command";
export function registerCommands(plugin: Plugin) {
plugin.addCommand({
id: "do-something",
name: "Do something",
callback: () => doSomething(plugin),
});
}
```
### Add a command
```ts
this.addCommand({
id: "your-command-id",
name: "Do the thing",
callback: () => this.doTheThing(),
});
```
### Persist settings
```ts
interface MySettings { enabled: boolean }
const DEFAULT_SETTINGS: MySettings = { enabled: true };
async onload() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
await this.saveData(this.settings);
}
```
### Register listeners safely
```ts
this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ }));
this.registerDomEvent(window, "resize", () => { /* ... */ });
this.registerInterval(window.setInterval(() => { /* ... */ }, 1000));
```
## Troubleshooting
- Plugin doesn't load after build: ensure `main.js` and `manifest.json` are at the top level of the plugin folder under `<Vault>/.obsidian/plugins/<plugin-id>/`.
- Build issues: if `main.js` is missing, run `npm run build` or `npm run dev` to compile your TypeScript source code.
- Commands not appearing: verify `addCommand` runs after `onload` and IDs are unique.
- Settings not persisting: ensure `loadData`/`saveData` are awaited and you re-render the UI after changes.
- Mobile-only issues: confirm you're not using desktop-only APIs; check `isDesktopOnly` and adjust.
## References
- Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin
- API documentation: https://docs.obsidian.md
- Developer policies: https://docs.obsidian.md/Developer+policies
- Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines
- Style guide: https://help.obsidian.md/style-guide