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:
parent
d61ce3a5dd
commit
e922523dc9
1 changed files with 157 additions and 221 deletions
378
AGENTS.md
378
AGENTS.md
|
|
@ -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
|
|
||||||
|
## Source file structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.ts # Plugin class, command registration, core copy logic
|
||||||
|
├── settings.ts # ClaudeContextSettings interface, defaults, settings tab UI
|
||||||
|
├── generator.ts # Context generator modal (vault structure setup wizard)
|
||||||
|
├── sources.ts # External context sources (freetext, file, folder, shell)
|
||||||
|
├── source-modal.ts # Modal for adding/editing context sources
|
||||||
|
├── presets.ts # Frontmatter preset parser & executor (ai-context block)
|
||||||
|
├── targets.ts # Multi-target output system (format, truncation, token limits)
|
||||||
|
├── target-modal.ts # Modal for adding/editing output targets
|
||||||
|
├── templates.ts # Prompt template engine & 5 starter templates
|
||||||
|
├── template-modal.ts # Modal for adding/editing templates + import/export
|
||||||
|
├── history.ts # Context history manager (save, cleanup, versioning)
|
||||||
|
├── 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
|
||||||
```
|
```
|
||||||
|
|
||||||
### Production build
|
## Plugin commands
|
||||||
|
|
||||||
```bash
|
| ID | Name | Description |
|
||||||
npm run build
|
|----|------|-------------|
|
||||||
|
| `copy-context` | Copy context to clipboard | Main command – copies all context files + sources |
|
||||||
|
| `copy-context-with-note` | Copy context with current note | Same as above but always includes active note |
|
||||||
|
| `copy-context-selective` | Copy context (select sections) | Opens file/heading selector modal |
|
||||||
|
| `copy-context-from-preset` | Copy context from frontmatter preset | Reads `ai-context` YAML block from active note |
|
||||||
|
| `generate-context` | Generate context files | Opens setup wizard to create initial context folder |
|
||||||
|
| `view-history` | View context history | Opens history browser with diff and restore |
|
||||||
|
|
||||||
|
Ribbon icon: `clipboard-copy` → triggers `copy-context`.
|
||||||
|
|
||||||
|
## 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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Linting
|
Features: hierarchical tag matching, link traversal with cycle detection, token budgeting, full validation with errors/warnings.
|
||||||
|
|
||||||
- To use eslint install eslint from terminal: `npm install -g eslint`
|
### Content selector (`content-selector.ts`)
|
||||||
- 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
|
Parses heading tree (H1–H6) and block IDs (`^blockid`) from markdown files. Enables partial file selection via `FileSelectorModal`. Output marks partial files with `(partial)` suffix.
|
||||||
|
|
||||||
- **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`.
|
### History (`history.ts`)
|
||||||
- Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands).
|
|
||||||
- **Example file structure**:
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
main.ts # Plugin entry point, lifecycle management
|
|
||||||
settings.ts # Settings interface and defaults
|
|
||||||
commands/ # Command implementations
|
|
||||||
command1.ts
|
|
||||||
command2.ts
|
|
||||||
ui/ # UI components, modals, views
|
|
||||||
modal.ts
|
|
||||||
view.ts
|
|
||||||
utils/ # Utility functions, helpers
|
|
||||||
helpers.ts
|
|
||||||
constants.ts
|
|
||||||
types.ts # TypeScript interfaces and types
|
|
||||||
```
|
|
||||||
- **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`)
|
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.
|
||||||
|
|
||||||
- Must include (non-exhaustive):
|
|
||||||
- `id` (plugin ID; for local dev it should match the folder name)
|
|
||||||
- `name`
|
|
||||||
- `version` (Semantic Versioning `x.y.z`)
|
|
||||||
- `minAppVersion`
|
|
||||||
- `description`
|
|
||||||
- `isDesktopOnly` (boolean)
|
|
||||||
- Optional: `author`, `authorUrl`, `fundingUrl` (string or map)
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to:
|
|
||||||
```
|
|
||||||
<Vault>/.obsidian/plugins/<plugin-id>/
|
|
||||||
```
|
|
||||||
- Reload Obsidian and enable the plugin in **Settings → Community plugins**.
|
|
||||||
|
|
||||||
## Commands & settings
|
|
||||||
|
|
||||||
- Any user-facing commands should be added via `this.addCommand(...)`.
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version → minimum app version.
|
|
||||||
- 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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue