obsidian-dev-skills 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ <!--
2
+ Source: Based on Obsidian Sample Plugin and TypeScript best practices
3
+ Last synced: See sync-status.json for authoritative sync dates
4
+ Update frequency: Check Obsidian Sample Plugin repo for updates
5
+ Applicability: Plugin
6
+ -->
7
+
8
+ # Coding conventions
9
+
10
+ **Note**: This file is specific to plugin development (TypeScript). For theme development, see CSS/SCSS best practices in other files.
11
+
12
+ - TypeScript with `"strict": true` preferred.
13
+ - **Avoid `any` type**: Use proper types, `unknown`, or type assertions instead. `any` defeats TypeScript's type safety benefits.
14
+ - **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules.
15
+ - **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules.
16
+ - **Use clear module boundaries**: Each file should have a single, well-defined responsibility.
17
+ - Bundle everything into `main.js` (no unbundled runtime deps).
18
+ - Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly.
19
+ - Prefer `async/await` over promise chains; handle errors gracefully.
20
+
21
+
@@ -0,0 +1,24 @@
1
+ <!--
2
+ Source: Based on Obsidian Sample Plugin and community plugin guidelines
3
+ Last synced: See sync-status.json for authoritative sync dates
4
+ Update frequency: Check Obsidian Sample Plugin repo for updates
5
+ Applicability: Plugin
6
+ -->
7
+
8
+ # Commands & settings
9
+
10
+ **Note**: This file is specific to plugin development. Themes do not have commands or settings.
11
+
12
+ - Any user-facing commands should be added via `this.addCommand(...)`.
13
+ - If the plugin has configuration, provide a settings tab and sensible defaults.
14
+ - Persist settings using `this.loadData()` / `this.saveData()`.
15
+ - Use stable command IDs; avoid renaming once released.
16
+
17
+ ## Version Considerations
18
+
19
+ When using newer API features (e.g., `SettingGroup` since API 1.11.0), consider backward compatibility:
20
+ - **For new plugins**: You can set `minAppVersion: "1.11.0"` in `manifest.json` and use the feature directly
21
+ - **For existing plugins**: Use version checking with `requireApiVersion()` to support both newer and older Obsidian versions
22
+ - See [code-patterns.md](code-patterns.md) for backward compatibility patterns, including a complete example for `SettingGroup`
23
+
24
+
@@ -0,0 +1,429 @@
1
+ <!--
2
+ Source: Based on Obsidian Sample Plugin
3
+ Last synced: See sync-status.json for authoritative sync dates
4
+ Update frequency: Check Obsidian Sample Plugin repo for updates
5
+ -->
6
+
7
+ # Common tasks
8
+
9
+ **Note**: The examples below are for plugin development (TypeScript).
10
+
11
+ **When to use this vs [code-patterns.md](code-patterns.md)**:
12
+ - **common-tasks.md**: Quick snippets and basic patterns for common operations
13
+ - **code-patterns.md**: Complete, production-ready examples with full context and error handling
14
+
15
+ > **Note**: If user asks "what does the Obsidian API say about X?" or similar, check `.ref/obsidian-api/obsidian.d.ts` first. See [ref-instructions.md](ref-instructions.md) for when to check `.ref` setup.
16
+
17
+ ## Organize code across multiple files
18
+
19
+ **main.ts** (minimal, lifecycle only):
20
+ ```ts
21
+ import { Plugin } from "obsidian";
22
+ import { MySettings, DEFAULT_SETTINGS } from "./settings";
23
+ import { registerCommands } from "./commands";
24
+
25
+ export default class MyPlugin extends Plugin {
26
+ settings: MySettings;
27
+
28
+ async onload() {
29
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
30
+ registerCommands(this);
31
+ }
32
+ }
33
+ ```
34
+
35
+ **settings.ts**:
36
+ ```ts
37
+ export interface MySettings {
38
+ enabled: boolean;
39
+ apiKey: string;
40
+ }
41
+
42
+ export const DEFAULT_SETTINGS: MySettings = {
43
+ enabled: true,
44
+ apiKey: "",
45
+ };
46
+ ```
47
+
48
+ **commands/index.ts**:
49
+ ```ts
50
+ import { Plugin } from "obsidian";
51
+ import { doSomething } from "./my-command";
52
+
53
+ export function registerCommands(plugin: Plugin) {
54
+ plugin.addCommand({
55
+ id: "do-something",
56
+ name: "Do something",
57
+ callback: () => doSomething(plugin),
58
+ });
59
+ }
60
+ ```
61
+
62
+ ## Add a command
63
+
64
+ ```ts
65
+ this.addCommand({
66
+ id: "your-command-id",
67
+ name: "Do the thing",
68
+ callback: () => this.doTheThing(),
69
+ });
70
+ ```
71
+
72
+ ## Persist settings
73
+
74
+ ```ts
75
+ interface MySettings { enabled: boolean }
76
+ const DEFAULT_SETTINGS: MySettings = { enabled: true };
77
+
78
+ async onload() {
79
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
80
+ await this.saveData(this.settings);
81
+ }
82
+ ```
83
+
84
+ ## Register listeners safely
85
+
86
+ ```ts
87
+ this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ }));
88
+ this.registerDomEvent(window, "resize", () => { /* ... */ });
89
+ this.registerInterval(window.setInterval(() => { /* ... */ }, 1000));
90
+ ```
91
+
92
+ ## Settings Tab Implementation
93
+
94
+ **Source**: Based on `.ref/obsidian-sample-plugin/main.ts` and `.ref/obsidian-api/obsidian.d.ts` (API is authoritative for SettingGroup - available since 1.11.0)
95
+
96
+ Basic settings tab:
97
+
98
+ ```ts
99
+ import { App, PluginSettingTab, Setting } from "obsidian";
100
+
101
+ class MySettingTab extends PluginSettingTab {
102
+ plugin: MyPlugin;
103
+
104
+ constructor(app: App, plugin: MyPlugin) {
105
+ super(app, plugin);
106
+ this.plugin = plugin;
107
+ }
108
+
109
+ display(): void {
110
+ const { containerEl } = this;
111
+ containerEl.empty();
112
+
113
+ new Setting(containerEl)
114
+ .setName("Setting name")
115
+ .setDesc("Setting description")
116
+ .addText((text) =>
117
+ text
118
+ .setPlaceholder("Enter value")
119
+ .setValue(this.plugin.settings.mySetting)
120
+ .onChange(async (value) => {
121
+ this.plugin.settings.mySetting = value;
122
+ await this.plugin.saveSettings();
123
+ })
124
+ );
125
+ }
126
+ }
127
+
128
+ // In main plugin class:
129
+ this.addSettingTab(new MySettingTab(this.app, this));
130
+ ```
131
+
132
+ **Note**: For settings groups (available since API 1.11.0), use `SettingGroup` from the API. Plugin docs may not yet document this feature - always check `.ref/obsidian-api/obsidian.d.ts` for the latest API.
133
+
134
+ **SettingGroup Methods** (available since 1.11.0):
135
+ - `setHeading(heading: string)` - Set the group heading
136
+ - `addSetting(cb: (setting: Setting) => void)` - Add a setting to the group
137
+ - `addSearch(cb: (component: SearchComponent) => any)` - Add a search input at the beginning of the group (useful for filtering)
138
+ - `addExtraButton(cb: (component: ExtraButtonComponent) => any)` - Add an extra button to the group
139
+
140
+ **Backward Compatibility**: To support users on both Obsidian 1.11.0+ and older versions, use a compatibility utility. See [code-patterns.md](code-patterns.md) for the complete implementation with `createSettingsGroup()` utility. Alternatively, you can force `minAppVersion: "1.11.0"` in `manifest.json` if you don't need to support older versions.
141
+
142
+ ## Secret Storage
143
+
144
+ **Source**: Based on [SecretStorage and SecretComponent guide](https://docs.obsidian.md/plugins/guides/secret-storage) (available since Obsidian 1.11.4)
145
+
146
+ **Important**: Always use `SecretStorage` and `SecretComponent` for storing sensitive data like API keys, tokens, or passwords. Never store secrets directly in your plugin's `data.json` file.
147
+
148
+ ### Using SecretComponent in Settings
149
+
150
+ Store only the secret *name* (ID) in your settings, not the actual secret value:
151
+
152
+ ```ts
153
+ import { App, PluginSettingTab, SecretComponent, Setting } from "obsidian";
154
+
155
+ export interface MyPluginSettings {
156
+ apiKeySecretId: string; // Store the secret name, not the value
157
+ }
158
+
159
+ export class MySettingTab extends PluginSettingTab {
160
+ plugin: MyPlugin;
161
+
162
+ constructor(app: App, plugin: MyPlugin) {
163
+ super(app, plugin);
164
+ this.plugin = plugin;
165
+ }
166
+
167
+ display(): void {
168
+ const { containerEl } = this;
169
+ containerEl.empty();
170
+
171
+ new Setting(containerEl)
172
+ .setName("API key")
173
+ .setDesc("Select a secret from SecretStorage")
174
+ .addComponent((el) =>
175
+ new SecretComponent(this.app, el)
176
+ .setValue(this.plugin.settings.apiKeySecretId)
177
+ .onChange(async (value) => {
178
+ this.plugin.settings.apiKeySecretId = value;
179
+ await this.plugin.saveSettings();
180
+ })
181
+ );
182
+ }
183
+ }
184
+ ```
185
+
186
+ **Note**: `SecretComponent` requires the `App` instance in its constructor, so it must be used with `Setting#addComponent()` rather than methods like `addText()`.
187
+
188
+ ### Retrieving Secrets
189
+
190
+ When you need the actual secret value, retrieve it from `SecretStorage`:
191
+
192
+ ```ts
193
+ // Get a secret by its ID (name)
194
+ const secret = this.app.secretStorage.getSecret(this.settings.apiKeySecretId);
195
+
196
+ if (secret) {
197
+ // Use the secret value
198
+ console.log("API key retrieved");
199
+ } else {
200
+ // Secret not found - handle gracefully
201
+ console.warn("API key secret not found");
202
+ }
203
+ ```
204
+
205
+ ### Managing Secrets Programmatically
206
+
207
+ You can also manage secrets programmatically (though typically users manage them through the UI):
208
+
209
+ ```ts
210
+ // Set a secret
211
+ this.app.secretStorage.setSecret("my-api-key", "actual-secret-value");
212
+
213
+ // List all secrets
214
+ const allSecrets = this.app.secretStorage.listSecrets();
215
+ // Returns: ["my-api-key", "another-secret", ...]
216
+
217
+ // Get a secret
218
+ const value = this.app.secretStorage.getSecret("my-api-key");
219
+ // Returns: "actual-secret-value" or null if not found
220
+ ```
221
+
222
+ **Important**: Secret IDs must be lowercase alphanumeric with optional dashes (e.g., `my-plugin-api-key`). Invalid IDs will throw an error.
223
+
224
+ See [security-privacy.md](security-privacy.md) for security best practices and [code-patterns.md](code-patterns.md) for comprehensive examples with error handling.
225
+
226
+ ## Modal Patterns
227
+
228
+ **Source**: Based on `.ref/obsidian-sample-plugin/main.ts` and `.ref/obsidian-plugin-docs/docs/guides/modals.md`
229
+
230
+ Simple modal:
231
+
232
+ ```ts
233
+ import { App, Modal } from "obsidian";
234
+
235
+ class MyModal extends Modal {
236
+ constructor(app: App) {
237
+ super(app);
238
+ }
239
+
240
+ onOpen() {
241
+ const { contentEl } = this;
242
+ contentEl.setText("Modal content");
243
+ }
244
+
245
+ onClose() {
246
+ const { contentEl } = this;
247
+ contentEl.empty();
248
+ }
249
+ }
250
+
251
+ // Open modal:
252
+ new MyModal(this.app).open();
253
+ ```
254
+
255
+ Modal with user input:
256
+
257
+ ```ts
258
+ import { App, Modal, Setting } from "obsidian";
259
+
260
+ class InputModal extends Modal {
261
+ result: string;
262
+ onSubmit: (result: string) => void;
263
+
264
+ constructor(app: App, onSubmit: (result: string) => void) {
265
+ super(app);
266
+ this.onSubmit = onSubmit;
267
+ }
268
+
269
+ onOpen() {
270
+ const { contentEl } = this;
271
+ contentEl.createEl("h1", { text: "Enter value" });
272
+
273
+ new Setting(contentEl)
274
+ .setName("Name")
275
+ .addText((text) =>
276
+ text.onChange((value) => {
277
+ this.result = value;
278
+ })
279
+ );
280
+
281
+ new Setting(contentEl)
282
+ .addButton((btn) =>
283
+ btn
284
+ .setButtonText("Submit")
285
+ .setCta()
286
+ .onClick(() => {
287
+ this.close();
288
+ this.onSubmit(this.result);
289
+ })
290
+ );
291
+ }
292
+
293
+ onClose() {
294
+ const { contentEl } = this;
295
+ contentEl.empty();
296
+ }
297
+ }
298
+ ```
299
+
300
+ ## Custom Views
301
+
302
+ **Source**: Based on `.ref/obsidian-plugin-docs/docs/guides/custom-views.md`
303
+
304
+ ```ts
305
+ import { ItemView, WorkspaceLeaf } from "obsidian";
306
+
307
+ export const VIEW_TYPE_MY_VIEW = "my-view";
308
+
309
+ export class MyView extends ItemView {
310
+ constructor(leaf: WorkspaceLeaf) {
311
+ super(leaf);
312
+ }
313
+
314
+ getViewType() {
315
+ return VIEW_TYPE_MY_VIEW;
316
+ }
317
+
318
+ getDisplayText() {
319
+ return "My View";
320
+ }
321
+
322
+ async onOpen() {
323
+ const container = this.containerEl.children[1];
324
+ container.empty();
325
+ container.createEl("h4", { text: "My View Content" });
326
+ }
327
+
328
+ async onClose() {
329
+ // Clean up resources
330
+ }
331
+ }
332
+
333
+ // In main plugin class:
334
+ async onload() {
335
+ this.registerView(VIEW_TYPE_MY_VIEW, (leaf) => new MyView(leaf));
336
+
337
+ // Activate view:
338
+ await this.activateView();
339
+ }
340
+
341
+ async activateView() {
342
+ const { workspace } = this.app;
343
+ let leaf = workspace.getLeavesOfType(VIEW_TYPE_MY_VIEW)[0];
344
+
345
+ if (!leaf) {
346
+ leaf = workspace.getRightLeaf(false);
347
+ await leaf.setViewState({ type: VIEW_TYPE_MY_VIEW, active: true });
348
+ }
349
+
350
+ workspace.revealLeaf(leaf);
351
+ }
352
+
353
+ async onunload() {
354
+ this.app.workspace.detachLeavesOfType(VIEW_TYPE_MY_VIEW);
355
+ }
356
+ ```
357
+
358
+ **Warning**: Never store references to views. Use `getLeavesOfType()` to access view instances.
359
+
360
+ ## Status Bar Items
361
+
362
+ **Source**: Based on `.ref/obsidian-sample-plugin/main.ts` and `.ref/obsidian-plugin-docs/docs/guides/status-bar.md`
363
+
364
+ ```ts
365
+ // Add status bar item (not supported on mobile)
366
+ const statusBarItemEl = this.addStatusBarItem();
367
+ statusBarItemEl.setText("Status text");
368
+
369
+ // Or create custom elements:
370
+ const statusBarItemEl = this.addStatusBarItem();
371
+ statusBarItemEl.createEl("span", { text: "Status: " });
372
+ statusBarItemEl.createEl("span", { text: "Active" });
373
+ ```
374
+
375
+ ## Ribbon Icons
376
+
377
+ **Source**: Based on `.ref/obsidian-sample-plugin/main.ts` and `.ref/obsidian-plugin-docs/docs/guides/ribbon-actions.md`
378
+
379
+ ```ts
380
+ const ribbonIconEl = this.addRibbonIcon("dice", "My Plugin", (evt: MouseEvent) => {
381
+ new Notice("Ribbon clicked!");
382
+ });
383
+
384
+ // Add CSS class for styling:
385
+ ribbonIconEl.addClass("my-plugin-ribbon-class");
386
+ ```
387
+
388
+ ## Editor Commands
389
+
390
+ **Source**: Based on `.ref/obsidian-sample-plugin/main.ts`
391
+
392
+ ```ts
393
+ this.addCommand({
394
+ id: "editor-command",
395
+ name: "Editor command",
396
+ editorCallback: (editor: Editor, view: MarkdownView) => {
397
+ const selection = editor.getSelection();
398
+ editor.replaceSelection("Replaced text");
399
+ },
400
+ });
401
+ ```
402
+
403
+ ## Complex Commands with Conditions
404
+
405
+ **Source**: Based on `.ref/obsidian-sample-plugin/main.ts`
406
+
407
+ ```ts
408
+ this.addCommand({
409
+ id: "conditional-command",
410
+ name: "Conditional command",
411
+ checkCallback: (checking: boolean) => {
412
+ const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
413
+ if (markdownView) {
414
+ if (!checking) {
415
+ // Execute command
416
+ this.doAction();
417
+ }
418
+ return true; // Command is available
419
+ }
420
+ return false; // Command is not available
421
+ },
422
+ });
423
+ ```
424
+
425
+ The `checkCallback` receives a `checking` boolean:
426
+ - When `true`: Only check if command can run (don't execute)
427
+ - When `false`: Actually execute the command
428
+
429
+
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: obsidian-dev-themes
3
+ description: CSS/SCSS development patterns for Obsidian themes. Load when working with theme.css, SCSS variables, or CSS selectors.
4
+ ---
5
+
6
+ # Obsidian Theme Development Skill
7
+
8
+ This skill provides patterns and rules for developing Obsidian themes, focusing on CSS/SCSS development, styling conventions, and theme-specific coding practices.
9
+
10
+ ## Purpose
11
+
12
+ To ensure consistent theme development, proper CSS organization, and adherence to Obsidian's theming patterns and CSS variable usage.
13
+
14
+ ## When to Use
15
+
16
+ Load this skill when:
17
+ - Writing or modifying CSS/SCSS for `theme.css`
18
+ - Working with Obsidian's CSS variables and theming system
19
+ - Implementing responsive design or dark/light mode support
20
+ - Debugging CSS layout or styling issues
21
+ - Following CSS/SCSS coding conventions
22
+
23
+ ## Core Rules
24
+
25
+ - **Use Obsidian CSS Variables**: Always prefer Obsidian's built-in CSS variables over hardcoded values
26
+ - **Follow BEM Methodology**: Use Block-Element-Modifier naming for CSS classes
27
+ - **Mobile-First**: Consider mobile layouts and responsive design
28
+ - **Dark/Light Mode Support**: Test themes in both light and dark modes
29
+ - **Performance**: Minimize CSS complexity and avoid expensive selectors
30
+
31
+ ## Bundled Resources
32
+
33
+ - `references/theme-best-practices.md`: Essential CSS patterns and Obsidian variable usage
34
+ - `references/theme-coding-conventions.md`: CSS/SCSS style guidelines and naming conventions
@@ -0,0 +1,50 @@
1
+ # Theme Development Best Practices
2
+
3
+ ## CSS Organization
4
+ - Group related styles together (Editor, UI, Sidebar, etc.)
5
+ - Use comments to separate major sections
6
+ - Keep specificity low for easy customization
7
+
8
+ ## Obsidian CSS Variables
9
+ Always prefer Obsidian's built-in CSS variables over hardcoded values:
10
+
11
+ ```css
12
+ /* Good */
13
+ .theme-dark {
14
+ --my-accent: var(--interactive-accent);
15
+ background-color: var(--background-primary);
16
+ color: var(--text-normal);
17
+ }
18
+
19
+ /* Avoid */
20
+ .theme-dark {
21
+ background-color: #2d2d30;
22
+ color: #cccccc;
23
+ }
24
+ ```
25
+
26
+ ## Responsive Design
27
+ ```css
28
+ /* Mobile-first approach */
29
+ .my-component {
30
+ width: 100%;
31
+ }
32
+
33
+ @media (min-width: 768px) {
34
+ .my-component {
35
+ width: 50%;
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Dark/Light Mode Support
41
+ ```css
42
+ /* Support both themes */
43
+ .theme-dark .my-element {
44
+ background: var(--background-primary);
45
+ }
46
+
47
+ .theme-light .my-element {
48
+ background: var(--background-secondary);
49
+ }
50
+ ```
@@ -0,0 +1,45 @@
1
+ # Theme Coding Conventions
2
+
3
+ ## CSS/SCSS Guidelines
4
+
5
+ ### Naming Conventions
6
+ - Use lowercase with hyphens for class names: `.my-component`
7
+ - Follow BEM methodology: `.block__element--modifier`
8
+ - Prefix custom classes: `.obsidian-my-theme-`
9
+
10
+ ### Structure
11
+ ```scss
12
+ // Variables at top
13
+ :root {
14
+ --my-theme-primary: #007acc;
15
+ --my-theme-secondary: #cccccc;
16
+ }
17
+
18
+ // Component styles
19
+ .obsidian-my-theme {
20
+ &__header {
21
+ background: var(--my-theme-primary);
22
+
23
+ &--dark {
24
+ background: darken(var(--my-theme-primary), 20%);
25
+ }
26
+ }
27
+
28
+ &__content {
29
+ color: var(--text-normal);
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### Best Practices
35
+ - Use SCSS nesting sparingly (max 3 levels)
36
+ - Always use Obsidian CSS variables when available
37
+ - Comment complex selectors and their purpose
38
+ - Group related styles together
39
+ - Test in both light and dark themes
40
+
41
+ ### Performance
42
+ - Avoid universal selectors (`*`)
43
+ - Minimize CSS specificity
44
+ - Use efficient selectors (prefer classes over complex selectors)
45
+ - Keep CSS bundle size reasonable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obsidian-dev-skills",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Agent skills for Obsidian plugin and theme development",
5
5
  "keywords": [
6
6
  "obsidian",
@@ -19,10 +19,18 @@
19
19
  "license": "MIT",
20
20
  "author": "David V. Kimball",
21
21
  "files": [
22
- "obsidian-dev/",
22
+ "obsidian-dev-plugins/",
23
+ "obsidian-dev-themes/",
23
24
  "obsidian-ops/",
24
- "obsidian-ref/"
25
+ "obsidian-ref/",
26
+ "scripts/"
25
27
  ],
28
+ "bin": {
29
+ "obsidian-dev-skills": "./scripts/init.mjs"
30
+ },
31
+ "scripts": {
32
+ "postinstall": "node ./scripts/init.mjs"
33
+ },
26
34
  "engines": {
27
35
  "node": ">=16.0.0"
28
36
  }