# Cortex Documentation LLM-readable documentation corpus for Cortex. Canonical index: https://cortex-md.tech/llms.txt # Welcome Start with the Cortex model: local Markdown, a native workspace, optional encrypted sync, and extension points that keep files portable. Source: https://cortex-md.tech/docs/get-started/welcome Section: Get Started Cortex is a local-first Markdown workspace. It opens a folder you control, keeps notes as plain `.md` files, and gives the writing surface enough structure to stay fast without taking ownership of the format. ## Start here Use the first pass through the docs to understand three things: where your notes live, how Cortex reads Markdown, and which product surfaces are ready for extension work. ## What Cortex is Cortex is designed for people who already trust Markdown but still want a calmer workspace for reading, writing, searching, and arranging notes. The app keeps the local folder as the source of truth, so the same files remain usable in other editors. ## What is still growing The public documentation starts with the product model, workspace basics, sync architecture, and developer surfaces. Public download, license, platform release, and registry details should only be documented when the repository adds those contracts. ## Product boundaries Sync, plugins, themes, and the CLI are separate stories. Sync is about ownership, client-side encryption, history, and self-hosting. Plugins extend workflows through reviewed APIs. Themes change the app surface through CSS without making notes less portable. --- # Quickstart Open a local Markdown folder and move through the core Cortex workflow without importing or converting notes. Source: https://cortex-md.tech/docs/get-started/quickstart Section: Get Started The fastest way to understand Cortex is to open an existing Markdown folder and follow the file from disk, through the editor, and back out again. ## Open a folder Choose a folder that already contains Markdown files, or create a small vault for testing. Cortex reads the folder directly instead of copying notes into a proprietary workspace. ```sh title="Open an existing notes folder" ~/notes ├── inbox.md ├── projects │ └── cortex.md └── reading └── markdown.md ``` ## Write in Live Preview Live Preview keeps Markdown readable while you type. Headings, links, callouts, code, and task lists render close to how they will read, but the source stays plain Markdown. ```md meta="future-hints" ## Meeting notes - [ ] Confirm sync recovery flow - [x] Keep the source file readable > [!note] > This callout remains Markdown. ``` ## Navigate without rebuilding your system Use search, folders, tags, bookmarks, tabs, and split panes together. Cortex should help you move through the vault you already have, not force a new hierarchy before you can write. ## Next steps --- # Local Markdown Cortex keeps Markdown files on disk as the source of truth, so notes stay readable with or without the app. Source: https://cortex-md.tech/docs/workspace/local-markdown Section: Workspace Cortex treats the folder on your computer as the durable workspace. The app adds navigation, search, preview, and extension surfaces around the files instead of replacing them. ## Source of truth The file on disk is the canonical note. You can open it in another editor, back it up, version it with Git, or move it into another folder without asking Cortex for an export. ```md # Research note Tags: #writing #local-first The note is still text. Cortex adds the workspace around it. ``` ## Markdown compatibility The renderer supports common Markdown patterns used in technical notes: headings, lists, links, tables, code blocks, task lists, callouts, and wiki-style references where the app can resolve them. ## Metadata stays inspectable Properties and tags should remain understandable from the file. Cortex can make metadata easier to scan, but the content should still make sense in plain text. ## Related docs --- # Organizing Notes Use folders, tags, bookmarks, and properties together while keeping the workspace readable as files. Source: https://cortex-md.tech/docs/workspace/organizing Section: Workspace Organization in Cortex is meant to be layered. A folder can hold the broad shape, tags can connect ideas, bookmarks can keep active notes close, and properties can record lightweight state. ## Folders Folders are the structure that survives everywhere. Use them for areas, projects, archives, and anything that benefits from being visible in the file system. ## Tags Tags are lightweight relationships. They are useful when a note belongs to more than one context, or when a future search should collect notes across folders. ```md --- status: draft area: product --- # Plugin API notes #plugin #markdown #review ``` ## Bookmarks Bookmarks should be temporary and practical. They are for notes you need to reach often while a project is active, not a second permanent taxonomy. ## Properties Properties make repeated note shapes easier to scan. Keep them simple enough that the raw file still communicates the same thing. --- # Sync Overview Cortex Sync is optional and built around client-side encryption with encrypted blob storage. Source: https://cortex-md.tech/docs/sync/overview Section: Sync Cortex Sync is an optional layer for moving notes between devices while preserving the local folder model. The service should not become the source of truth for your notes. ## Ownership model Markdown files stay on your devices. The sync service is responsible for encrypted blob storage, transfer, version history, and conflict recovery around those local files. ## Client-side encryption Notes are encrypted on the client before upload. The server stores encrypted blobs and the metadata needed to coordinate sync, rather than readable note contents. ```ts title="Conceptual flow" const localChange = await readMarkdownFile(path) const encryptedBlob = await encryptOnDevice(localChange, deviceKey) await syncClient.upload({ vaultId, path, blob: encryptedBlob, }) ``` ## History and conflicts History should make changes understandable across devices. When conflicts happen, Cortex should preserve enough context for recovery instead of silently overwriting local work. ## Self-hosting path The sync architecture should support a hosted service and a self-hostable path. Documentation should avoid absolute security promises and describe the actual boundaries clearly. ## Related docs --- # Community Plugins Build reviewed, vault-scoped Cortex plugins with the public API while keeping notes portable. Source: https://cortex-md.tech/docs/plugins/overview Section: Plugin Development Community plugins extend Cortex through the public `@cortex.md/api` package. The host creates a plugin instance, injects `this.manifest` and `this.api`, calls `onload()`, and disposes tracked registrations when the plugin unloads. ## Extension surfaces Plugins can add commands, editor behavior, Markdown rendering, property types, portable views, settings, sidebar entries, status bar items, context menus, vault-aware workflows, and plugin-owned data. Each surface is guarded by explicit capabilities so extension behavior can be reviewed before listing. ## Install location During development, place or symlink a plugin directory here: ```text /.cortex/plugins// manifest.json dist/index.js styles.css # optional, Markdown-surface styles only ``` Cortex watches the vault plugin directory and reloads enabled community plugins when plugin files change. ## Start here ## Rules of thumb - Import only from `@cortex.md/api` and your own bundled code. - Declare every required capability in `manifest.json`. - Use vault-relative paths with forward slashes. - Use declarative views instead of React or DOM APIs. - Keep view state serializable and update it through actions and reducers. - Dispose manual timers, external subscriptions, and caches in `onunload()`. ## Deeper references --- # Getting Started With Plugins Create a small command plugin, build it, and load it from a local Cortex vault. Source: https://cortex-md.tech/docs/plugins/getting-started Section: Plugin Development This guide creates a small command plugin that inserts text into the active editor. ## Minimal Folder ```text hello-cortex/ manifest.json package.json src/index.ts ``` ## Manifest ```json { "id": "hello-cortex", "name": "Hello Cortex", "version": "0.1.0", "minAppVersion": "0.1.0", "author": "Your Name", "description": "Adds a sample command.", "icon": "sparkles", "main": "dist/index.js", "capabilities": ["commands", "editor:write"] } ``` `main` must be a safe relative path inside the plugin directory. Absolute paths, drive-letter paths, empty paths, and paths containing `..` are rejected. ## Package ```json { "name": "hello-cortex", "version": "0.1.0", "type": "module", "scripts": { "build": "bun build ./src/index.ts --outfile ./dist/index.js --target browser --format cjs --external @cortex.md/api" }, "dependencies": { "@cortex.md/api": "^0.1.0" }, "devDependencies": { "typescript": "~5.8.3" } } ``` The desktop host can load CommonJS bundles through a host-provided `@cortex.md/api` external. ESM bundles are also supported when the final bundle is self-contained and exports a default plugin class. ## Plugin Entry ```ts import { CortexPlugin } from "@cortex.md/api" export default class HelloCortexPlugin extends CortexPlugin { onload(): void { this.addCommand({ id: "insert-hello", label: "Insert Hello", icon: "sparkles", defaultHotkey: "mod+shift+h", execute: () => { this.api.editor.insertAtCursor("Hello from Cortex") }, }) } } ``` ## Build Output After `bun run build`, copy or symlink the plugin into the active vault: ```text /.cortex/plugins/hello-cortex/ manifest.json dist/index.js ``` Keep `manifest.json` at the plugin root. Keep the built file at the path declared by `main`. ## Manual development link When you are not using the CLI, the manual development workflow is: ```bash mkdir -p "/.cortex/plugins" ln -s "/absolute/path/to/hello-cortex" "/.cortex/plugins/hello-cortex" ``` Restart Cortex or edit a plugin file to trigger the plugin watcher. The plugin list should show the manifest name and icon if the bundle loads successfully. ## Next steps --- # Manifest and Capabilities Define plugin identity, bundle paths, and explicit capabilities for reviewed extension behavior. Source: https://cortex-md.tech/docs/plugins/manifest-and-capabilities Section: Plugin Development Every community plugin needs a `manifest.json` at the plugin root. ## Manifest Shape ```json { "id": "example-plugin", "name": "Example Plugin", "version": "0.1.0", "minAppVersion": "0.1.0", "author": "Your Name", "authorUrl": "https://example.com", "description": "Adds a sample command and sidebar view.", "icon": "puzzle", "main": "dist/index.js", "capabilities": ["commands", "ui:views", "ui:sidebar"] } ``` | Field | Required | Notes | | --- | --- | --- | | `id` | Yes | Stable id unique inside a vault. Reviewed listings must match this id. | | `name` | Yes | User-facing name. | | `version` | Yes | Use semver for update comparisons. | | `minAppVersion` | Yes | Minimum Cortex version the plugin expects. | | `author` | Yes | Displayed in plugin and reviewed listing surfaces. | | `authorUrl` | No | Public URL for the author or organization. | | `description` | Yes | Short install/review summary. | | `icon` | Yes | Lucide icon name or host-supported icon identifier. | | `main` | Yes | Safe relative path to the plugin bundle. | | `capabilities` | No | Required before guarded API calls work. | ## Capability Reference | Capability | Enables | | --- | --- | | `commands` | `api.commands.register`, `api.commands.execute`, command palette, hotkeys, Vim names. | | `settings` | Plugin settings storage, schemas, settings tabs, and `onChange` listeners. | | `vault:read` | Vault path, file reads, file listings, `exists`, metadata reads, and tag reads. | | `vault:write` | Writing text files in the active vault. | | `vault:delete` | Deleting files from the active vault. | | `vault:watch` | Subscribing to vault file events. | | `editor:read` | Active file path and active editor content reads. | | `editor:write` | Cursor insertion and selection replacement. | | `editor:extensions` | Host-specific editor extensions. | | `editor:folding` | Portable line-based fold providers. | | `markdown:extensions` | Inline, semantic, callout, preprocessor, processor, and plugin `styles.css` support. | | `properties:types` | Custom note property type registration. | | `ui:views` | Declarative host-rendered views. | | `ui:sidebar` | Sidebar items that open registered views. | | `ui:statusbar` | Status bar items. | | `ui:contextmenu` | File, editor, and tab context menu items. | | `ui:modals` | Opening and closing registered modal views. | | `workspace:tabs` | Opening files, views, and temporary Markdown tabs. | | `theme:read` | Active theme name and theme change subscriptions. | | `bookmarks:read` | Bookmark list, lookup, and change subscriptions. | | `bookmarks:write` | Add, remove, and toggle bookmarks. | | `data` | Plugin-owned data files. | | `notifications` | Native notifications and lightweight notices. | ## Best Practices - Request the narrowest capabilities you need. - Treat capabilities as user-visible permissions. - Do not call APIs defensively and ignore capability failures. Declare the capability or remove the feature. - Use local ids for plugin commands, views, settings, and context menu items. Cortex prefixes where global uniqueness is needed. --- # Lifecycle and Bundling Export a Cortex plugin class, bundle safely, and clean up registrations when the plugin unloads. Source: https://cortex-md.tech/docs/plugins/lifecycle-and-bundling Section: Plugin Development Plugins extend `CortexPlugin` from `@cortex.md/api`. ## Lifecycle ```ts import { CortexPlugin, type Disposable } from "@cortex.md/api" export default class WatchPlugin extends CortexPlugin { private watcher?: Disposable private timer?: ReturnType onload(): void { this.watcher = this.api.vault.onFileEvent((event) => { console.log("Vault changed", event.path) }) this.timer = setInterval(() => { console.log("Still alive") }, 60_000) } onunload(): void { this.watcher?.dispose() if (this.timer) clearInterval(this.timer) } } ``` Helper methods such as `addCommand`, `registerView`, `registerSettingsTab`, and `registerMarkdownInline` track disposables automatically. Use `onunload()` for things Cortex cannot track for you, such as timers, in-memory caches, network subscriptions, and external resources. ## Bundle Contract The plugin bundle must export a default plugin class: ```ts export default class MyPlugin extends CortexPlugin { onload(): void {} } ``` The desktop host loads: - CommonJS bundles through a controlled `require` stub. - ESM bundles through a generated `data:` URL import when the bundle looks like ESM. For the most predictable development setup, emit one CommonJS browser-targeted bundle and keep `@cortex.md/api` external. ## Imports Allowed: ```ts import { CortexPlugin, type ViewDescriptor } from "@cortex.md/api" import { helper } from "./helper" ``` Avoid: ```ts import { useVaultStore } from "@cortex/core" import { EditorView } from "@cortex/editor/editor-view" import { getPlatform } from "@cortex/platform" ``` Community plugins must not import Cortex internals. Public APIs are exposed through `this.api`. ## Dependency Guidance - Bundle third-party runtime dependencies into your plugin output. - Keep large dependencies lazy inside your own code when the feature is rarely used. - Avoid global side effects at module top level. Register behavior in `onload()`. - Keep plugin state serializable when it flows through views, settings, or workspace tabs. --- # Commands and Hotkeys Register command palette actions, default hotkeys, and Vim command names through the host registry. Source: https://cortex-md.tech/docs/plugins/commands-and-hotkeys Section: Plugin Development Plugin commands enter the same command registry used by Cortex's command palette, configurable hotkeys, menus, and Vim command-line choices. ## Register a Command ```ts this.addCommand({ id: "open-dashboard", label: "Open Dashboard", category: "Dashboard", aliases: ["stats", "overview"], icon: "layout-dashboard", defaultHotkey: "mod+shift+d", execute: () => { this.api.workspace.openView("dashboard") }, }) ``` Declare `commands` in `manifest.json`. ## Command Ids Public command ids are local to the plugin. Cortex prefixes them internally before they enter the global command registry, so `open-dashboard` can safely exist in more than one plugin. Use stable command ids because users may assign custom hotkeys to them. ## Hotkeys Use `defaultHotkey` for the suggested shortcut: ```ts defaultHotkey: "mod+shift+d" ``` The user can reassign or disable the binding in Cortex. Do not implement a second hotkey system in your plugin. ## Vim Names Cortex derives Vim command-line choices from the same command registry. Keep labels and ids clear because Vim names are generated from the command id and shown to users in command hints. ## Execution Guidelines - Keep command handlers small and responsive. - For longer work, show progress through a view, status bar item, notification, or generated Markdown tab. - Use `api.commands.execute(id)` only for commands your plugin owns or intentionally composes with. --- # Vault, Editor, and Workspace APIs Use vault files, the active editor, fold providers, and workspace tabs from a plugin. Source: https://cortex-md.tech/docs/plugins/vault-editor-workspace Section: Plugin Development Use these APIs when a plugin needs user files, the active editor, or workspace tabs. ## Vault Paths Vault APIs use vault-relative paths with forward slashes: ```ts const content = await this.api.vault.readFile("Projects/Plan.md") await this.api.vault.writeFile("Projects/Plan.md", `${content}\n\nUpdated by plugin.`) ``` Do not pass absolute paths, `..`, or platform-specific separators. The host validates paths before touching the filesystem. ## Vault Events ```ts const disposable = this.api.vault.onFileEvent((event) => { if (event.path.endsWith(".md")) { console.log(event.type, event.path) } }) ``` Declare `vault:watch`. Dispose subscriptions you create manually. ## Editor Reads and Writes ```ts const filePath = this.api.editor.getActiveFilePath() const content = this.api.editor.getActiveFileContent() if (filePath && content !== null) { this.api.editor.replaceSelection("**important**") } ``` Use `editor:read` for reads and `editor:write` for writes. Editor paths are still vault-relative when available. ## Fold Providers Fold providers are portable and line-based. They do not expose CodeMirror or DOM objects. ```ts this.registerFoldProvider({ id: "spoiler-blocks", label: "Spoiler blocks", getFoldRange: (context) => { if (context.lineText !== ":::spoiler") return null for (let line = context.lineNumber + 1; line <= context.lineCount; line += 1) { if (context.getLine(line) === ":::") return { toLine: line, placeholder: "spoiler" } } return null }, }) ``` Declare `editor:folding`. ## Workspace Tabs Open vault files: ```ts this.api.workspace.openFile("Projects/Plan.md", { target: "right", newTab: true }) ``` Open a registered plugin view: ```ts this.api.workspace.openView("dashboard", { target: "active" }) ``` Open generated Markdown without writing a file: ```ts this.openMarkdownTab({ title: "Plugin Report", content: "# Report\n\nGenerated by my plugin.", }) ``` Workspace open methods require `workspace:tabs`. Opening plugin views and generated Markdown tabs also requires `ui:views`. --- # Markdown and Properties Extend Markdown rendering, callouts, plugin-scoped styles, and note property types. Source: https://cortex-md.tech/docs/plugins/markdown-and-properties Section: Plugin Development Markdown extensions require the `markdown:extensions` capability. Property type extensions require `properties:types`. ## Inline Replacements Use inline registrations for simple regex replacements: ```ts this.registerMarkdownInline({ id: "smile-shortcode", pattern: ":smile:", replacement: { type: "text", content: "smile" }, }) ``` Inline registrations are lightweight and shared by editor and renderer consumers. ## Semantic Transforms Use semantic registrations when output should work across Live Preview, Reading View, and export. Semantic output must be portable nodes, not arbitrary DOM. ```ts this.registerMarkdownSemantic({ id: "ticket-links", selector: { type: "text" }, priority: 10, transform: ({ node }) => { if (node.type !== "text" || !node.value.includes("APP-")) return null return node }, }) ``` Supported portable nodes include text, container, span, link, image, and code. ## Callout Types ```ts this.registerCalloutType({ type: "decision", aliases: ["decide"], label: "Decision", color: "var(--accent)", backgroundColor: "var(--accent-subtle)", }) ``` Later registrations win. Disposing a registration restores the previous definition. ## Preprocessors and Unified Processors Preprocessors and Unified processors run only on `reading-view` and `export` surfaces: ```ts this.registerMarkdownPreprocessor({ id: "append-footer", surfaces: ["reading-view"], preprocess: (markdown) => `${markdown}\n\n---\nRendered by plugin.`, }) ``` Live Preview integrations should use inline registrations, semantic registrations, callouts, fold providers, or editor extensions instead. ## Plugin `styles.css` A plugin can include a root `styles.css` file only when it declares `markdown:extensions`. Plugin CSS is scoped to `.markdown-surface`. Selectors that do not already target `.markdown-surface` are prefixed by the host. Selectors that escape the Markdown surface with sibling combinators are rejected. Allowed at-rules: - `@container` - `@layer` - `@media` - `@supports` Blocked at-rules include `@font-face`, `@import`, `@keyframes`, `@namespace`, and `@page`. ## Property Types ```ts this.registerPropertyType({ type: "rating", baseType: "number", displayName: "Rating", icon: "star", deserialize: (value) => Number(value ?? 0), serialize: (value) => Number(value ?? 0), validate: (value) => ({ valid: typeof value === "number" && value >= 0 && value <= 5, message: "Rating must be between 0 and 5", }), }) ``` Property type ids are namespaced internally by plugin id. --- # Views, Settings, and UI Build portable host-rendered plugin views, settings, sidebar items, status items, and menus. Source: https://cortex-md.tech/docs/plugins/views-settings-and-ui Section: Plugin Development Plugins render UI through portable descriptors. Cortex owns the actual React/web rendering so plugin UI can remain portable across hosts. ## Register a View ```ts import type { ViewDescriptor, ViewDispatch, ViewState } from "@cortex.md/api" function renderCounter(state: ViewState, _dispatch: ViewDispatch): ViewDescriptor { const count = Number(state.state.count ?? 0) return { type: "stack", gap: "sm", children: [ { type: "heading", value: "Counter" }, { type: "text", value: `Count: ${count}` }, { type: "button", label: "Increment", action: "increment", variant: "primary" }, ], } } this.registerView({ id: "counter", label: "Counter", icon: "plus", location: "sidebar-left", initialState: { count: 0 }, reduce: (state, action) => { if (action === "increment") return { ...state, count: Number(state.count ?? 0) + 1 } return state }, render: renderCounter, }) ``` Declare `ui:views`. ## View Locations Views can render in: - `tab` - `sidebar-left` - `sidebar-right` - `modal` Modal views also require `ui:modals`. ## Sidebar, Status Bar, and Context Menus ```ts this.registerSidebarItem({ id: "counter", label: "Counter", icon: "plus", viewId: "counter", }) this.registerStatusBarItem({ id: "counter-status", position: "right", icon: "plus", text: "Counter", tooltip: "Open counter", onClick: () => this.api.workspace.openView("counter"), }) this.registerContextMenuItem({ id: "copy-path", label: "Copy Path", location: "file", action: async (context) => { if (context.location === "file") await this.api.data.write("last-path.txt", context.filePath) }, }) ``` Required capabilities are `ui:sidebar`, `ui:statusbar`, and `ui:contextmenu`. ## Settings Tabs ```ts this.registerSettingsTab({ id: "counter-settings", label: "Counter", icon: "settings", settings: [ { key: "enabled", label: "Enabled", type: "boolean", default: true, }, ], }) ``` Supported setting field types are `text`, `number`, `boolean`, `select`, `slider`, `color`, and `folder-path`. ## Portable UI Nodes Use the descriptor nodes exposed by `@cortex.md/api`: stack, row, text, heading, button, icon-button, input, textarea, toggle, checkbox, select, slider, icon, separator, list, list-item, scroll-area, badge, progress, empty, markdown, setting-row, item, alert, tabs, and table. Do not expose React components, DOM nodes, inline styles, arbitrary event handlers, or free `className` values. --- # Storage, Bookmarks, and Notifications Store plugin-owned data and integrate with bookmarks, metadata, theme reads, notices, and notifications. Source: https://cortex-md.tech/docs/plugins/storage-bookmarks-notifications Section: Plugin Development These APIs integrate plugins with host-owned data, user bookmarks, metadata, themes, and native notifications. ## Plugin Data Plugin data is vault-scoped and stored under the plugin directory: ```text /.cortex/plugins//data/ ``` ```ts await this.api.data.write("cache.json", JSON.stringify({ lastRun: Date.now() })) const cached = await this.api.data.read("cache.json") await this.api.data.delete("cache.json") ``` Declare `data`. Filenames must not contain `..` and must not start with `/` or `\`. Use data files for plugin-owned state and caches. Use vault APIs for user-visible notes. ## Metadata Metadata APIs require `vault:read`: ```ts const frontmatter = await this.api.metadata.getFrontmatter("Projects/Plan.md") const tags = await this.api.metadata.getTags("Projects/Plan.md") const allTags = this.api.metadata.getAllTags() ``` Metadata paths are vault-relative Markdown paths. ## Bookmarks ```ts const bookmarks = this.api.bookmarks.list() await this.api.bookmarks.toggle("Projects/Plan.md") const isBookmarked = this.api.bookmarks.isBookmarked("Projects/Plan.md") ``` Reads require `bookmarks:read`. Mutations require `bookmarks:write`. Bookmark paths are vault-relative Markdown paths. ## Theme Read API ```ts const activeTheme = this.api.theme.getActiveThemeName() const disposable = this.api.theme.onThemeChange((name) => { console.log("Theme changed", name) }) ``` Declare `theme:read`. Plugins can observe the active theme name, but they should not mutate themes. ## Notifications and Notices ```ts const result = await this.notify({ title: "Task complete", body: "Your plugin finished processing notes.", kind: "success", }) if (!result.delivered) { console.warn(result.reason) } ``` Declare `notifications`. Plugins cannot request OS notification permission directly; Cortex owns permission prompts and platform support. `api.ui.showNotice(message)` also requires `notifications` and is delivered through the host notice path. Notifications are rate-limited per plugin. Keep them user-meaningful and avoid sending repeated status spam. --- # Publishing and Review Prepare plugin release assets for review and community listing without promising an unreviewed install path. Source: https://cortex-md.tech/docs/plugins/publishing-and-review Section: Plugin Development Reviewed community listing is release-asset based with a source-archive fallback during early development. ## Required Release Assets Publish these assets in the latest release: ```text manifest.json dist/index.js # or the basename/path referenced by manifest.main styles.css # optional, Markdown-surface styles only ``` Cortex downloads `manifest.json`, reads `main`, then downloads the matching main asset. It accepts either the exact relative path from `main` or the basename of that path. ## Manifest checks The reviewed listing flow verifies: - `manifest.json` is valid JSON. - `manifest.id` matches the listing entry id. - `manifest.main` is present and safe. - The main bundle exists in release assets or in the source archive fallback. ## Source Archive Fallback If release assets are incomplete, Cortex can fall back to the GitHub source archive. It searches for `manifest.json`, resolves the declared `main` file, and copies optional `styles.css` into staging. This fallback is useful during early development, but reviewed plugins should still include release assets explicitly. ## Install and rollback behavior Cortex installs into: ```text /.cortex/plugins/ ``` Install and update use a staging directory. The previous plugin directory is preserved until the staged plugin is promoted, the plugin host reloads, and the new plugin registration is visible. If anything fails, Cortex removes staging and restores the previous plugin best effort. ## Release checklist 1. Build a single plugin bundle. 2. Include `manifest.json` at the release root. 3. Include the file referenced by `manifest.main`. 4. Include `styles.css` only when the plugin declares `markdown:extensions`. 5. Keep all paths relative and free of `..`. 6. Test install, reload, disable, and uninstall in a temporary vault. --- # Example: GitHub Emoji Plugin Study a practical plugin that registers Markdown inline replacements, commands, settings, and a portable view. Source: https://cortex-md.tech/docs/plugins/github-emoji-example Section: Plugin Development The bundled GitHub emoji plugin is the best current example of a practical Cortex plugin. Source files: - `plugins/github-emoji/package.json` - `plugins/github-emoji/src/index.ts` - `plugins/github-emoji/src/emojiMap.ts` ## What it demonstrates - Importing `CortexPlugin` from `@cortex.md/api`. - Registering Markdown inline replacements. - Adding command palette entries and default hotkeys. - Writing to the active editor. - Adding a status bar item. - Registering a settings tab. - Registering a declarative sidebar view with serializable state. ## Main plugin shape ```ts import { CortexPlugin } from "@cortex.md/api" export default class GitHubEmojiPlugin extends CortexPlugin { onload() { this.registerMarkdownInline({ id: "github-emoji", pattern: ":([a-z0-9_+-]+):", flags: "gi", replacement: { type: "text", content: (match) => GITHUB_EMOJI_MAP[match[1].toLowerCase()] ?? match[0], }, }) } } ``` ## Commands The plugin registers commands with plugin-local ids: - `insert-emoji` - `emoji-reference` The `insert-emoji` command uses `editor:write` to insert text at the cursor. The command also declares a default hotkey, so Cortex can mirror it into the user's configurable hotkeys. ## Settings The settings tab uses host-rendered fields: - Boolean setting for Live Preview behavior. - Select setting for emoji size. - Select setting for skin tone. - Boolean setting for status bar display. The `onChange` handler shows a notice, so the plugin needs `notifications` if it uses that behavior in a community manifest. ## Sidebar View The emoji browser view is declarative. It returns portable nodes such as stack, row, scroll-area, button, heading, and text. State changes go through view actions and a reducer instead of React state owned by the plugin. Use this pattern for plugin UI that should work in tabs, sidebars, and modals. ## Related docs --- # Community Themes Build vault-scoped Cortex themes with paired light and dark CSS files. Source: https://cortex-md.tech/docs/themes/overview Section: Theme Development Community themes are vault-scoped CSS themes. Cortex discovers theme folders in the active vault, parses each `manifest.json`, registers a light/dark theme family, and loads only the active CSS variant on demand. ## Install location ```text /.cortex/themes// manifest.json light.css dark.css ``` ## Start here ## Mental Model - The manifest describes identity and stylesheet paths. - The CSS files define variables and targeted rules. - Cortex injects community CSS directly and lets the browser own the cascade. - Theme CSS should be scoped to `body.theme--light` and `body.theme--dark`. ## Deeper references --- # Getting Started With Themes Create a minimal Cortex theme with a manifest, light.css, dark.css, and vault install location. Source: https://cortex-md.tech/docs/themes/getting-started Section: Theme Development This guide creates a minimal light/dark community theme. ## Folder ```text warm-notes/ manifest.json light.css dark.css ``` Place or symlink it into the active vault: ```text /.cortex/themes/warm-notes/ ``` ## Manifest ```json { "id": "warm-notes", "name": "warm-notes", "displayName": "Warm Notes", "author": "Your Name", "version": "0.1.0", "minAppVersion": "0.1.0", "colorschemes": { "light": "light.css", "dark": "dark.css" } } ``` `name` becomes the theme family name. Cortex registers two runtime theme names: - `warm-notes-light` - `warm-notes-dark` ## Light CSS ```css body.theme-warm-notes-light { color-scheme: light; --bg-primary: #fbfaf6; --bg-secondary: #f2efe8; --bg-elevated: #ffffff; --text-primary: #2a2621; --text-muted: #756d62; --accent: #b84f35; --accent-hover: #a6462f; --accent-subtle: #f5ded6; --border: #ded7ca; --modal-bg: #ffffff; --menu-bg: #ffffff; --markdown-content-width: 760px; } ``` ## Dark CSS ```css body.theme-warm-notes-dark { color-scheme: dark; --bg-primary: #201f1d; --bg-secondary: #2a2825; --bg-elevated: #302e2a; --text-primary: #f2ece2; --text-muted: #aaa197; --accent: #e0795d; --accent-hover: #ee8a6e; --accent-subtle: #4a2b25; --border: #403b35; --modal-bg: #2a2825; --menu-bg: #2a2825; --markdown-content-width: 760px; } ``` ## Reload Cortex watches `/.cortex/themes`. Editing a theme file triggers a reload. If the active theme is the edited family, Cortex reapplies the current appearance settings after reload. ## Next steps --- # Theme Manifest Define a community theme family, display metadata, and safe light and dark stylesheet paths. Source: https://cortex-md.tech/docs/themes/manifest Section: Theme Development Every community theme needs a root `manifest.json`. ## Shape ```json { "id": "warm-notes", "name": "warm-notes", "displayName": "Warm Notes", "author": "Your Name", "authorUrl": "https://example.com", "version": "0.1.0", "minAppVersion": "0.1.0", "colorschemes": { "light": "light.css", "dark": "dark.css" } } ``` | Field | Required | Notes | | --- | --- | --- | | `id` | Yes | Theme install id. Reviewed listings must match this id. | | `name` | Yes | Theme family slug. Must match `^[a-z0-9][a-z0-9-]*$`. | | `displayName` | Yes | User-facing family name. | | `author` | Yes | Displayed in theme and reviewed listing surfaces. | | `authorUrl` | No | Valid URL for the author or organization. | | `version` | Yes | Use semver for update comparisons. | | `minAppVersion` | No | Minimum Cortex version expected by the theme. | | `colorschemes.light` | Yes | Safe relative path to the light CSS file. | | `colorschemes.dark` | Yes | Safe relative path to the dark CSS file. | ## Safe Stylesheet Paths Stylesheet paths must be relative to the theme folder. Cortex rejects: - Empty paths. - Absolute paths. - Windows drive-letter paths. - Paths containing `..`. Good: ```json { "light": "light.css", "dark": "schemes/dark.css" } ``` Bad: ```json { "light": "../light.css", "dark": "/tmp/dark.css" } ``` ## Runtime Theme Names For a manifest with `"name": "warm-notes"`, Cortex registers: - `warm-notes-light` - `warm-notes-dark` The active runtime theme is applied to the body as: ```html ``` Write CSS selectors against those runtime body classes. --- # CSS Variables Customize Cortex surfaces through documented semantic and component CSS variables. Source: https://cortex-md.tech/docs/themes/css-variables Section: Theme Development Community themes customize Cortex by setting CSS variables. The variables below are generated by the built-in theme system and are safe targets for community CSS. ## Primitive Scales Built-in themes expose scale variables for each token key: - `--stone-*` and `--mist-*` - `--ink-*` and `--slate-*` - `--amber-*` and `--rose-*` - `--amber-d-*` and `--rose-d-*` - `--red-*` - `--green-*` - `--yellow-*` Community themes do not need to define every primitive scale. Prefer semantic variables first. ## App Surfaces ```css --bg-primary --bg-secondary --bg-tertiary --bg-elevated --bg-hover --bg-active --bg-selected --bg-code --bg-tag --background --foreground --card --card-foreground --popover --popover-foreground ``` ## Text, Accent, Links, and Borders ```css --text-primary --text-secondary --text-muted --text-disabled --text-placeholder --text-on-accent --accent --accent-hover --accent-active --accent-subtle --accent-border --accent-text --brand --brand-hover --brand-active --brand-subtle --brand-border --brand-text --link --link-hover --link-broken --border --border-subtle --border-strong --border-focus --ring ``` ## Typography ```css --font-ui --font-editor --font-mono --ui-font-size --ui-font-weight --ui-line-height --editor-font-size --editor-font-weight --editor-line-height --editor-paragraph-spacing ``` User appearance settings may override font families and font sizes. Themes should still define weights and line heights. ## Components and Chrome ```css --btn-primary-bg --btn-primary-text --btn-primary-hover --input-bg --input-border --input-focus-ring --menu-bg --menu-border --menu-shadow --menu-hover --modal-bg --modal-border --modal-shadow --tooltip-bg --tooltip-text --sidebar-bg --sidebar-border --sidebar-tree-guide --settings-group-bg --settings-group-border --settings-group-divider --tab-bg --tab-active-bg --tab-accent --statusbar-bg --statusbar-border --scrollbar-thumb --scrollbar-hover --shadow-raised --shadow-floating --shadow-overlay --radius ``` ## Status Colors ```css --status-error --error-bg --status-error-foreground --status-error-border --status-error-on-solid --status-success --success-bg --status-success-foreground --status-success-border --status-success-on-solid --status-warning --warning-bg --status-warning-foreground --status-warning-border --status-warning-on-solid --destructive --destructive-foreground ``` ## Editor, Syntax, and Markdown ```css --syntax-keyword --syntax-string --syntax-comment --syntax-number --syntax-function --syntax-type --syntax-operator --syntax-property --syntax-heading --syntax-meta --editor-selection-bg --editor-search-match-bg --editor-search-match-active-bg --markdown-content-width --markdown-content-gutter --markdown-block-radius --markdown-block-spacing --markdown-code-font-family --markdown-code-font-size --markdown-code-padding-inline --markdown-code-padding-block --markdown-callout-padding-block --markdown-callout-padding-inline-start --markdown-callout-padding-inline-end ``` ## Headings and Tags ```css --heading-font-weight --normal-weight --inline-title-margin-bottom --h1-font-size --h2-font-size --h3-font-size --h4-font-size --h5-font-size --h6-font-size --h1-color --h2-color --h3-color --h4-color --h5-color --h6-color --tag-bg --tag-active-bg --tag-text --tag-active-text --tag-font-size --tag-font-weight --tag-border-radius --tag-padding ``` --- # Stable Hooks and Selectors Use documented body classes and data hooks when theme variables are not enough. Source: https://cortex-md.tech/docs/themes/stable-hooks-and-selectors Section: Theme Development Prefer CSS variables first. When variables are not enough, use stable hooks that Cortex exposes for themes and surfaces. ## Theme Root Scope each variant to its runtime body class: ```css body.theme-warm-notes-light { color-scheme: light; --bg-primary: #fbfaf6; } body.theme-warm-notes-dark { color-scheme: dark; --bg-primary: #201f1d; } ``` Use `body[data-theme-scheme="dark"]` and `body[data-theme-scheme="light"]` when a rule should follow the effective color scheme instead of a specific theme family. ## Markdown Use `.markdown-surface` for rendered Markdown and editor-projected Markdown styling: ```css body.theme-warm-notes-light .markdown-surface blockquote { border-left-color: var(--accent); } ``` Plugin `styles.css` files are also scoped to `.markdown-surface`, but community themes can style the broader app. ## Overlays and Popups Stable overlay hooks include: - `[data-popup-surface]` - `[data-command-surface]` - `[data-slot="dialog-content"]` - `[data-slot="alert-dialog-content"]` - `[data-slot="dropdown-menu-content"]` - `[data-slot="context-menu-content"]` - `[data-slot="select-content"]` - `[data-slot="popover-content"]` - `[data-slot="hover-card-content"]` Example: ```css body.theme-warm-notes-light [data-popup-surface] { box-shadow: 0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent), 0 18px 48px color-mix(in srgb, black 12%, transparent); } ``` ## App Shell Use shell selectors sparingly: - `.app-shell` - `.app-titlebar` - `.app-content` - `.app-sidebar` - `.app-main` - `.tab-bar` - `.tab-trigger` - `.statusbar-item` - `.file-tree-item` These are stronger than tokens and should be reserved for theme-specific polish that variables cannot express. ## Avoid - Private generated class names. - Selectors that depend on deep child order. - Global resets such as `* { ... }`. - `!important` unless overriding explicit user appearance settings is truly intended. - Built-in theme selectors such as `.theme-ink` for community theme behavior. --- # Markdown and Callouts Style Cortex Markdown surfaces, code blocks, headings, and callout colors through theme CSS. Source: https://cortex-md.tech/docs/themes/markdown-and-callouts Section: Theme Development Markdown styling is shared by Live Preview, Reading View, and rendered plugin Markdown nodes. Theme changes should keep those surfaces visually aligned. ## Layout Variables ```css body.theme-warm-notes-light { --markdown-content-width: 760px; --markdown-content-gutter: 40px; --markdown-block-radius: 8px; --markdown-block-spacing: 1em; } ``` Avoid adding vertical padding, margins, transforms, or non-baseline alignment to editable CodeMirror line content. Prefer variables and non-disruptive visual chrome. ## Code ```css body.theme-warm-notes-light { --markdown-code-font-family: Menlo, Monaco, Consolas, "Liberation Mono", monospace; --markdown-code-font-size: 14px; --markdown-code-padding-inline: 16px; --markdown-code-padding-block: 8px; --bg-code: #f2efe8; } ``` Markdown code spans and fenced code blocks use editor typography by default. Smaller typography belongs on code block chrome, not necessarily the code itself. ## Headings ```css body.theme-warm-notes-light { --heading-font-weight: 650; --h1-font-size: 1.45em; --h2-font-size: 1.25em; --h3-font-size: 1.08em; --h1-color: var(--syntax-heading); --h2-color: var(--syntax-heading); } ``` Keep heading scale compact enough that editing remains calm and dense. ## Callouts Standard callout variables use this pattern: ```css body.theme-warm-notes-light { --callout-note-color: var(--accent); --callout-note-bg: var(--accent-subtle); --callout-warning-color: var(--status-warning); --callout-warning-bg: var(--warning-bg); } ``` Common built-in callout names include `note`, `abstract`, `info`, `todo`, `tip`, `success`, `question`, `warning`, `failure`, `danger`, `bug`, `example`, and `quote`. Plugins can register or override callout types. Explicit plugin colors take precedence over theme defaults for those registered callouts. --- # Light, Dark, and System Modes Pair community theme CSS files with Cortex appearance settings and effective color scheme. Source: https://cortex-md.tech/docs/themes/light-dark-and-system Section: Theme Development Community themes provide paired light and dark CSS files. Cortex chooses the active variant from appearance settings and system color-scheme when the user selects system mode. ## Runtime Classes For manifest name `warm-notes`, Cortex applies: ```html ``` or: ```html ``` Write variant-specific variables inside the runtime class. ## Scheme Selectors Use `data-theme-scheme` for rules that should follow the effective scheme: ```css body[data-theme-scheme="dark"] .markdown-surface img { filter: brightness(0.92); } ``` Do not write community theme behavior against built-in selectors such as `.theme-ink` or `.theme-paper`. ## Color Scheme Set the browser color scheme in each file: ```css body.theme-warm-notes-light { color-scheme: light; } body.theme-warm-notes-dark { color-scheme: dark; } ``` This helps native form controls, scrollbars, and browser defaults align with the theme. ## Pairing Guidance - Keep variable names aligned between `light.css` and `dark.css`. - Test overlays, settings, editor selection, search matches, callouts, and code blocks in both variants. - Keep dark backgrounds neutral enough that syntax colors and accent states stay readable. - Recompute foregrounds for contrast instead of reusing light-mode foregrounds in dark mode. --- # Publishing and Review Prepare theme release assets for review and community listing. Source: https://cortex-md.tech/docs/themes/publishing-and-review Section: Theme Development Reviewed community listing installs theme assets into the active vault. ## Required Release Assets Publish: ```text manifest.json light.css dark.css ``` The CSS filenames can differ when the manifest references different safe relative paths. For each colorscheme, Cortex looks for: 1. The exact relative path from `manifest.colorschemes`. 2. The basename of that path. 3. `.css`, such as `light.css` or `dark.css`. ## Manifest checks The reviewed listing flow verifies: - `manifest.json` is valid JSON. - `manifest.id` matches the listing entry id. - `colorschemes.light` and `colorschemes.dark` are safe relative paths. - Both stylesheet assets exist before replacing an installed theme. ## Install and rollback behavior Cortex installs into: ```text /.cortex/themes/ ``` Theme install and update use staging inside the vault themes directory. Cortex preserves the previous installation until staging is promoted and community themes reload successfully. On failure, it removes staging and restores the previous theme best effort. ## Release checklist 1. Include `manifest.json`. 2. Include both light and dark CSS assets. 3. Keep stylesheet paths relative and free of `..`. 4. Test applying the theme from light, dark, and system appearance modes. 5. Test uninstall while the theme is active. --- # Theme Best Practices Keep community themes native-feeling, readable, performant, and respectful of user overrides. Source: https://cortex-md.tech/docs/themes/best-practices Section: Theme Development Good community themes feel native to Cortex while still having a clear voice. ## Use tokens first Prefer semantic variables such as `--bg-primary`, `--text-primary`, `--accent`, `--modal-bg`, and `--markdown-content-width` before writing component selectors. Tokens survive UI refactors better than deep selectors. ## Keep contrast strong - Body text should meet at least 4.5:1 contrast. - Focus indicators should meet at least 3:1 contrast against adjacent surfaces. - Test disabled, muted, warning, error, and selected states. - Do not rely on color alone for important state. ## Respect user overrides Users may override font family, UI font size, editor font size, and accent preferences. Avoid heavy-handed `!important` rules that fight explicit user choices. ## Keep CSS cheap - Avoid expensive global selectors. - Avoid broad `backdrop-filter` rules. - Avoid large data URLs and embedded images. - Avoid animating layout properties. - Prefer `opacity`, `transform`, and token changes for subtle motion. ## Scope Carefully Good: ```css body.theme-warm-notes-light [data-popup-surface] { border-radius: 12px; } ``` Risky: ```css div > div > div:nth-child(2) { border-radius: 12px; } ``` ## Test checklist - Main editor in source, Live Preview, Reading View, and Side-by-Side. - Command palette, quick switcher, dropdowns, popovers, context menus, and modals. - Settings window/modal fallback. - File explorer, tabs, status bar, bookmarks, tags, and properties. - Markdown headings, tables, code blocks, links, callouts, task lists, and blockquotes. - Light, dark, and system appearance modes. --- # Cortex CLI Use the CLI for local plugin and theme development against a real Cortex vault. Source: https://cortex-md.tech/docs/developers/cli Section: Developer Tools The CLI mirrors the extension loop: create a project, run against a local vault, build, validate, and prepare artifacts when the work is ready. ## Create Start with a small plugin or theme project. Keep the project close to the vault you use for testing so the feedback loop stays short. ```sh title="Create a plugin" npm install -g @cortex.md/cli cortex plugin create github-emoji cd github-emoji bun install ``` ## Dev vault Use a real local folder while developing. The goal is to test the same Markdown files, commands, and views that a user will touch. ```sh title="Run against a local vault" cortex plugin dev --vault ~/notes ``` ## Build and validate Build production output and validate structure before publishing or sharing artifacts. ```sh title="Prepare release output" cortex plugin build cortex plugin validate cortex plugin publish ``` ## Related docs