diff --git a/AGENTS.md b/AGENTS.md index 3f4274a..c914c99 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,251 +1,187 @@ -# Obsidian community plugin +# Claude Context – Obsidian Plugin ## Project overview -- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript). -- Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian. -- Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`. +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**: `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 -- Node.js: use current LTS (Node 18+ recommended). -- **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies). -- **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`. -- Types: `obsidian` type definitions. +- **Runtime**: Obsidian (Electron/Chromium) +- **Language**: TypeScript (`strict: true`) +- **Package manager**: npm +- **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. - -### Install +### Commands ```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 -npm run dev +Manual only – copy `main.js`, `manifest.json`, `styles.css` to `/.obsidian/plugins/claude-context/`, reload Obsidian, enable plugin. + +## 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 -npm run build +| ID | Name | Description | +|----|------|-------------| +| `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 `` 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` -- 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/` +### Content selector (`content-selector.ts`) -## 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`. -- 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`). +### History (`history.ts`) -## Manifest rules (`manifest.json`) - -- 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: - ``` - /.obsidian/plugins// - ``` -- 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. +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. ## Coding conventions -- TypeScript with `"strict": true` preferred. -- **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules. -- **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules. -- **Use clear module boundaries**: Each file should have a single, well-defined responsibility. -- Bundle everything into `main.js` (no unbundled runtime deps). -- Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly. -- Prefer `async/await` over promise chains; handle errors gracefully. +- `main.ts` handles plugin lifecycle and command registration – all feature logic lives in dedicated modules. +- Each module exports its types/interfaces alongside implementation. +- Modals are in separate `*-modal.ts` files, business logic in the corresponding `.ts` file. +- Settings are persisted via `loadData()`/`saveData()` with `Object.assign` merge pattern. +- VAULT.md is always sorted first in context output. +- Token estimation: `chars / 4` (rough approximation). +- No external runtime dependencies – everything bundled into `main.js`. -## Mobile +## Obsidian plugin conventions -- Where feasible, test on iOS and Android. -- Don't assume desktop-only behavior unless `isDesktopOnly` is `true`. -- Avoid large in-memory structures; be mindful of memory and storage constraints. +- Add commands with stable IDs – never rename after release. +- Use `this.register*` helpers for cleanup on unload. +- 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** -- Add commands with stable IDs (don't rename once released). -- Provide defaults and validation in settings. -- Write idempotent code paths so reload/unload doesn't leak listeners or intervals. -- Use `this.register*` helpers for everything that needs cleanup. - -**Don't** -- Introduce network calls without an obvious user-facing reason and documentation. -- Ship features that require cloud services without clear disclosure and explicit opt-in. -- 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); - } +```json +{ + "id": "claude-context", + "name": "Claude Context", + "version": "1.0.0", + "minAppVersion": "0.15.0", + "description": "Copy .claude/ context files to clipboard with one hotkey.", + "author": "Luca", + "isDesktopOnly": true } ``` - -**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 `/.obsidian/plugins//`. -- 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