From 6d09ce3e39c4e48d756d83e7b51583676939a5a7 Mon Sep 17 00:00:00 2001 From: Johannes Theiner Date: Mon, 27 Jan 2025 21:38:01 +0000 Subject: [PATCH 1/8] Add license, fixes #98 --- LICENSE | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..287f37a --- /dev/null +++ b/LICENSE @@ -0,0 +1,5 @@ +Copyright (C) 2020-2025 by Dynalist Inc. + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file From 3fe07677b56e319e129b27ec1e4d791b060c4166 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 28 Apr 2025 08:38:38 +0200 Subject: [PATCH 2/8] build: only write new minAppVersion requirements to `versions.json` Only add a new version requirements if `minAppVersion` is not already in `versions.json`. Should declutter `versions.json`. --- version-bump.mjs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/version-bump.mjs b/version-bump.mjs index d409fa0..55d631f 100644 --- a/version-bump.mjs +++ b/version-bump.mjs @@ -3,12 +3,15 @@ import { readFileSync, writeFileSync } from "fs"; const targetVersion = process.env.npm_package_version; // read minAppVersion from manifest.json and bump version to target version -let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); +const manifest = JSON.parse(readFileSync("manifest.json", "utf8")); const { minAppVersion } = manifest; manifest.version = targetVersion; writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); // update versions.json with target version and minAppVersion from manifest.json -let versions = JSON.parse(readFileSync("versions.json", "utf8")); -versions[targetVersion] = minAppVersion; -writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); +// but only if the target version is not already in versions.json +const versions = JSON.parse(readFileSync('versions.json', 'utf8')); +if (!Object.values(versions).includes(minAppVersion)) { + versions[targetVersion] = minAppVersion; + writeFileSync('versions.json', JSON.stringify(versions, null, '\t')); +} From f16c1401b3dec586453547e9497b4b555361898d Mon Sep 17 00:00:00 2001 From: Adam Millerchip Date: Thu, 28 Aug 2025 14:38:40 +0900 Subject: [PATCH 3/8] Prefix unused variables with _ --- main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index 2d07212..471bc8e 100644 --- a/main.ts +++ b/main.ts @@ -17,7 +17,7 @@ export default class MyPlugin extends Plugin { await this.loadSettings(); // This creates an icon in the left ribbon. - const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => { + const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (_evt: MouseEvent) => { // Called when the user clicks the icon. new Notice('This is a notice!'); }); @@ -40,7 +40,7 @@ export default class MyPlugin extends Plugin { this.addCommand({ id: 'sample-editor-command', name: 'Sample editor command', - editorCallback: (editor: Editor, view: MarkdownView) => { + editorCallback: (editor: Editor, _view: MarkdownView) => { console.log(editor.getSelection()); editor.replaceSelection('Sample Editor Command'); } From ce4fc8c2092d1b0828da5ed62741353975ab71d9 Mon Sep 17 00:00:00 2001 From: Steph Ango <10565871+kepano@users.noreply.github.com> Date: Thu, 4 Sep 2025 15:28:25 -0700 Subject: [PATCH 4/8] First pass --- AGENTS.md | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2fbe85c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,248 @@ +# Obsidian community 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`. + +## Environment & tooling + +- Node.js: use current LTS (Node 18+ recommended). +- Package manager: npm. +- Bundler: esbuild (preferred). Rollup or webpack are acceptable if they bundle all external dependencies into `main.js`. +- Types: `obsidian` type definitions. + + +### Install + +```bash +npm install +``` + +### Dev (watch) + +```bash +npm run dev +``` + +### Production build + +```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). +- **Recommended 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 `node_modules/` or build artifacts. +- 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`) + +- 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. + +## Building & running in Obsidian + +- 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` (avoid a leading `v` unless your repo uses it consistently). +- 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 + +- 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. + +## Mobile + +- 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. + +## Agent do/don't + +**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); + } +} +``` + +**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//`. +- 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 From a4398b8ecc75e738b2d8174e40c97dec35a6e951 Mon Sep 17 00:00:00 2001 From: Steph Ango <10565871+kepano@users.noreply.github.com> Date: Fri, 5 Sep 2025 08:58:49 -0700 Subject: [PATCH 5/8] Corrections based on feedback --- AGENTS.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2fbe85c..8ed7e10 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,10 +9,12 @@ ## Environment & tooling - Node.js: use current LTS (Node 18+ recommended). -- Package manager: npm. -- Bundler: esbuild (preferred). Rollup or webpack are acceptable if they bundle all external dependencies into `main.js`. +- **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. +**Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you could choose different tools, but you'll need to replace the build configuration accordingly. + ### Install @@ -59,7 +61,7 @@ npm run build constants.ts types.ts # TypeScript interfaces and types ``` -- Do not commit `node_modules/` or build artifacts. +- **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`). @@ -94,7 +96,7 @@ npm run build ## 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` (avoid a leading `v` unless your repo uses it consistently). +- 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. @@ -234,7 +236,8 @@ 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//`. +- 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. From 188bb6120f9c201402490384e812116a0427261b Mon Sep 17 00:00:00 2001 From: Steph Ango <10565871+kepano@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:01:58 -0700 Subject: [PATCH 6/8] small copy tweaks --- AGENTS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8ed7e10..04fa1f1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,7 +45,7 @@ npm run build - **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). -- **Recommended file structure**: +- **Example file structure**: ``` src/ main.ts # Plugin entry point, lifecycle management @@ -77,8 +77,9 @@ npm run build - 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 -## Building & running in Obsidian +## Testing - Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to: ``` From 33075ecd13f9843f53adebd1b003f08f7c70ab17 Mon Sep 17 00:00:00 2001 From: Steph Ango <10565871+kepano@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:04:04 -0700 Subject: [PATCH 7/8] use forward slashes for cross OS compatibility --- AGENTS.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 04fa1f1..689e160 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ npm run build - 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\` +- 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 diff --git a/README.md b/README.md index c773152..23d9526 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Quick starting guide for new plugin devs: - `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\` + - `eslint ./src/` ## Funding URL From 9673533aa976cddf46122214b264bcf1236e1b6c Mon Sep 17 00:00:00 2001 From: Steph Ango <10565871+kepano@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:10:47 -0700 Subject: [PATCH 8/8] language --- AGENTS.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 689e160..3f4274a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,8 +13,7 @@ - **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. -**Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you could choose different tools, but you'll need to replace the build configuration accordingly. - +**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