quickadd Documentation

TypeScript obsidian & obsidian-md repository by @chhoumann. AI agent rules & system prompts with 2.4k stars.

2,383 stars TypeScript TypeScript 74 files · ~127,720 tokens #obsidian#obsidian-md#obsidian-plugin
RAW Doc

Technical Documentation: chhoumann/quickadd

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

What you can build

Each workflow you set up in QuickAdd is called a choice. There are four kinds:

  • Template creates a note from a template, with its name, folder, and properties filled in. For example, a meeting note named with today's date in your Meetings folder.
  • Capture adds text to a note without opening it. For example, a timestamped line under the Log heading of today's daily note.
  • Macro runs several steps in a row: other choices, Obsidian commands, scripts, and AI prompts. For example, look up a book online and create a note with its details.
  • Multi groups choices into a folder in the QuickAdd menu. For example, a Journal folder holding your daily log and gratitude captures.

Placeholders fill in the details each time a choice runs: {{DATE}} inserts today's date, {{VALUE}} asks you for text, and {{FIELD:project}} suggests the project values your notes already use. Others insert links, your selection, or the clipboard. See format syntax for all of them.

QuickAdd also has:

QuickAdd works on desktop and mobile. The Obsidian CLI is desktop-only.

Get started

  1. In Obsidian, open Settings → Community plugins, search for QuickAdd, then install and enable it.
  2. Build your first choice with the one-minute first workflow guide.
  3. In Settings → QuickAdd, click the ⚡ next to a choice to add it to the command palette (on mobile, tap ⋮ → Enable in command palette). Then give it a hotkey in Settings → Hotkeys. You can also run any choice with the QuickAdd: Run command.

Using Templater? Add your template folder in QuickAdd's settings and QuickAdd: New note from template works right away. Coming from Templater shows the QuickAdd way to do each Templater job.

Learn more

  • Examples: complete workflows for daily notes, inboxes, meetings, books, movies, and more.
  • Scripting guide: writing your own scripts when the built-in steps aren't enough.
  • FAQ: answers to common questions.
  • llms.txt and the MCP server at https://quickadd.obsidian.guide/mcp: the docs in a form AI assistants can read.

Help and feedback

  • Ask questions and share workflows in Discussions.
  • Report bugs and request features in Issues.
  • Report security problems privately, as described in the security policy.

Contributing

Contributions are welcome. Read CONTRIBUTING.md before you start: comment on the issue you want to work on and wait for a go-ahead before writing code.

Support QuickAdd

If QuickAdd saves you time, you can buy me a coffee.

License

MIT


Overview (`docs/README.md`)

QuickAdd docs

The docs site (https://quickadd.obsidian.guide), built with Astro Starlight.

Layout

  • src/content/docs/docs/ - the documentation pages (markdown). Each page sets
    slug: explicitly to keep its historical URL, so renaming a file does not
    change its URL. If you do change a slug, add a 301 in public/_redirects.
  • src/pages/index.astro - the landing page (self-contained, zero client JS).
  • astro.config.mjs - Starlight config, including the sidebar. Markdown is
    rendered by Sätteri with headingAttributes on, which supports the
    ## Heading {#custom-id} anchors.
  • public/ - static assets served as-is (img/, downloadable example
    scripts/, _redirects, _headers).

Note on dependencies: zod is a direct dependency on purpose. This package
lives inside the plugin repo, and under pnpm's isolated layout an import that
isn't declared here (Astro's prerender bundle imports zod/v4) can resolve to
the PARENT repo's node_modules (a different major of zod broke the build this
way).

Commands

sh
pnpm install
pnpm dev      # local dev server
pnpm build    # production build into build/
pnpm preview  # serve the production build
python3 scripts/check-links.py  # verify internal links/anchors in build/

LLM and agent surface

Built artifacts for AI tooling, all served from the same deployment:

  • /llms.txt, /llms-small.txt, /llms-full.txt - llms.txt-standard indexes
    (via starlight-llms-txt).
  • .md - every docs page as raw markdown
    (e.g. /docs/FormatSyntax.md); the "Copy Markdown" button uses these.
  • /docs-index.json - machine-readable page manifest (slug, title,
    description, headings, text).
  • /mcp - an MCP server (Streamable HTTP, stateless; functions/mcp.ts runs
    as a Cloudflare Pages Function) exposing search_quickadd_docs and
    get_quickadd_doc. Point any MCP client at
    https://quickadd.obsidian.guide/mcp.

Deployment

Cloudflare Pages (GitHub integration) builds this directory and publishes
build/. Build watch paths are set to docs/*, so only pushes that touch
docs/ trigger a build; src-only commits, release PRs, and dependency bumps
produce no Cloudflare check at all. Pull requests that change docs get preview
deployments, except on dependabot/* and release-run/* branches.

The "Cloudflare Pages" check is informational and not required for merge. The
account runs one build at a time, so a build can sit at "Build in progress"
while an earlier one runs or is queued; a normal build takes about two
minutes. Cloudflare does not update its PR comment after the PR is closed, so a
comment still reading "Build in progress" on a merged PR is expected.

The required "Docs" check in GitHub CI (.github/workflows/ci.yml) builds the
site the same way on every PR (.node-version, frozen lockfile) and runs
scripts/check-links.py, so a broken docs build is caught before merge, even
on branches Cloudflare does not preview.

Docs are single-version: pages go live when they land on master. When
documenting a feature that has not shipped in a plugin release yet, add an
_Introduced in QuickAdd X.Y.Z._ line at the section (see AGENTS.md).

Release-note screenshots: commit new ones under public/img/release-X.Y.Z/,
then reference them in the GitHub release body via SHA-pinned
raw.githubusercontent.com URLs. Once the release is published, the folder
can be deleted from HEAD - the pinned URLs resolve from git history forever
(that is why no historical release folders live here).


Overview (`tooling/README.md`)

Tooling checks

pnpm run lint runs Oxlint, the
svelte-policy component
checker, tsc --noEmit --noCheck, and
svelte-check --tsgo-experimental-api --fail-on-warnings in parallel.
pnpm runs at most four scripts at once, so a fifth lint: script would wait
for a free slot.
pnpm run build-with-lint runs the same checks before producing the
application bundle. CI also runs the policy tests through pnpm run test.

Oxlint checks TypeScript and extracted Svelte scripts. Its JavaScript plugin
API cannot inspect Svelte template expressions, so the svelte-policy
checker uses the Svelte compiler's AST for complete components. The package
also provides the Oxlint plugin, which .oxlintrc.syntax.json loads under
the quickadd prefix. svelte-policy.json gives both tools the ten original
selectors and messages. For components, it also turns on the store and runes
rules and sets the no-console message.

Oxlint's JavaScript plugin API is alpha and does not have a semver guarantee.
The dependency is pinned. The selectors execute in JavaScript, while built-in
Oxlint rules execute natively. tsc uses the native compiler, and so does
svelte-check through the native compiler's API. svelte-check 4.7.6 exits 0
when that compiler cannot start, so patches/[email protected]
backports the upstream fix (sveltejs/language-tools#3133). pnpm refuses to
install with a patch for a version that is no longer installed, so the
next svelte-check upgrade must delete it, which is why Dependabot skips
svelte-check. svelte-check still loads the TypeScript 6 package.

svelte-check checks every file in the tsconfig program, but its FILES count
on this path covers only components and files with diagnostics. It also stays
silent when the tsconfig matches no input files, so lint also runs
tsc --noEmit --noCheck. It fails with TS18003 in that case and skips a second
type check.

All current components use Svelte 5 runes. Svelte checking and production
compilation require runes mode. The policy checker rejects $: statements
and svelte/store imports. Shared .svelte.ts modules receive the same
restrictions in Oxlint. These checks prevent legacy reactive statements and
store usage from returning.

Svelte rule migration

This migration preserves the custom guards and the listed safety policies.
It does not reproduce every heuristic in eslint-plugin-svelte/recommended.
Compiler and type diagnostics are authoritative for syntax, event-handler
types, and accessibility. CSS style suggestions and refactoring suggestions
listed as retired below now depend on review.

Former rule Replacement or disposition
comment-directive Removed with ESLint's suppression system. Svelte compiler suppressions remain.
system Removed with ESLint's parser and processor.
valid-compile Strict Svelte checking fails on compiler errors and warnings, including accessibility and unused CSS.
infinite-reactive-loop Legacy reactive statements are rejected. This does not statically prove that every $effect terminates.
no-immutable-reactive-statements Legacy reactive statements are rejected.
no-reactive-functions Legacy reactive statements are rejected.
no-reactive-literals Legacy reactive statements are rejected.
no-reactive-reassign Legacy reactive statements are rejected.
no-store-async no-svelte-store rejects legacy store imports in component code.
require-store-reactive-access no-svelte-store rejects legacy store imports in component code. Custom store objects are not inferred by the new checker.
require-event-dispatcher-types runes-only rejects named dispatcher imports, including aliases. Use callback props. Namespace and dynamic access are not traced.
no-at-html-tags Compiler-AST policy rejects {@html}.
require-each-key Compiler-AST policy requires a key for every each block with an item.
valid-each-key Compiler-AST policy requires the key to reference the block's item or index. This is not a uniqueness proof.
no-at-debug-tags Compiler-AST policy reports a warning.
no-inspect no-inspect reports a warning in components and shared .svelte.ts modules.
no-dom-manipulating Compiler-AST policy rejects structural or text changes to the named binding used by bind:this. It distinguishes module, instance, and local scopes but does not follow arbitrary aliases.
no-dupe-else-if-blocks Compiler-AST policy checks duplicate and covered conditions using identifiers, literals, Boolean operators, and strict equality. It skips chains containing calls, property reads, mutations, or coercive operators because their values can change between evaluations.
no-dupe-on-directives Compiler-AST policy rejects identical event directives.
no-dupe-use-directives Compiler-AST policy rejects identical action directives.
no-not-function-handler Compiler-AST policy rejects a literal, object, array, or class as an on* attribute or on: directive value, directly or through a const. Svelte type checking also verifies event-handler types.
no-object-in-text-mustaches Compiler-AST policy rejects object and function literals in text interpolation.
no-raw-special-elements Compiler-AST policy requires the svelte: prefix for special elements.
no-svelte-internal no-svelte-internal rejects imports and re-exports from Svelte internals in components and all TypeScript files.
no-unused-svelte-ignore Compiler-AST policy compares suppressed warnings with compiler diagnostics for the target element.
no-inner-declarations Removed. Svelte scripts are modules, and block-scoped functions were already allowed.
no-unused-props Removed. This rule required TypeScript parser services that the remaining ESLint fallback did not enable.
no-dupe-style-properties Retired. The compiler still rejects duplicate style directives; it does not check conflicts with a style string.
no-shorthand-style-property-overrides Retired. CSS shorthand conflicts require review.
no-unknown-style-directive-property Retired. CSS property spelling requires review.
no-unnecessary-state-wrap Retired refactoring suggestion.
no-useless-children-snippet Retired refactoring suggestion.
no-useless-mustaches Retired style suggestion.
prefer-writable-derived Retired refactoring suggestion.
prefer-svelte-reactivity Was disabled before the migration. Immutable Set and Map reassignment remains allowed.
no-export-load-in-svelte-module-in-kit-pages Inapplicable. QuickAdd is not a SvelteKit app.
no-navigation-without-resolve Inapplicable. QuickAdd is not a SvelteKit app.
valid-prop-names-in-kit-pages Inapplicable. QuickAdd is not a SvelteKit app.

The component policy checker also applies no-console, no-new-func, and
literal string-timer guards to template expressions. These expressions are
outside Oxlint's extracted scripts. These guards respect local script and
template bindings. String-timer checks apply to global timer functions and
methods on unshadowed window, globalThis, self, and global objects.
Type-aware promise, deprecation, and implied-eval checks still run on TypeScript
through Oxlint, with the existing test and user-script exceptions.

The package closes gaps in the checker this migration first added:

  • svelte-ignore codes may be dashed, comma-separated, or on the next line,
    and a markup comment applies to the next node past any text.
  • svelte-ignore comments in legacy components are checked.
  • Object, array, function, and class literals are rejected in attribute
    values that mix text and mustaches.
  • Each blocks without an item no longer need a key.
  • no-not-function-handler is back as a compiler-AST rule.
  • Member names written as backtick strings are resolved for
    no-implied-eval and no-dom-manipulating.
  • A svelte-ignore without a code is reported, and script svelte-ignore
    comments are checked.
  • Ignore codes are read per compile mode, including Svelte's legacy code
    replacements.

Verification

tests/tooling/sveltePolicy.test.ts checks QuickAdd's wiring of the package.
Oxlint with .oxlintrc.syntax.json reports all ten forbidden examples in
TypeScript with the component checker's messages, the store, dispatcher, and
$inspect rules in shared .svelte.ts modules, and the internals guard in
every TypeScript file. Valid examples and test files pass the syntax guards.
The component checker applies the store, runes, and no-console settings
from svelte-policy.json. The package's own tests cover the policies,
including every fixture this file held before. Temporary fixtures are
private and removed after the test. tests/tooling/svelteCheck.test.ts
requires pnpm run check's command to fail on compiler warnings, legacy
reactivity, type errors, components that do not parse, imports of missing
components, and a native compiler that cannot start or crashes. It also
requires pnpm run lint to run lint:tsconfig, and that check to fail when
the tsconfig matches no input files.

sh
pnpm exec vitest run tests/tooling/sveltePolicy.test.ts tests/tooling/svelteCheck.test.ts
pnpm run build-with-lint
pnpm run test

Follow-up refinements

These refinements follow the tooling migration:

  • Reconsider the blanket svelte/store ban. Stores remain supported in Svelte
    5, and removing ESLint does not require banning them.
  • Review the retired CSS checks and decide which merit separate enforcement.
  • Move checks to upstream Oxlint or Svelte implementations when they cover
    template expressions, reducing the code maintained in svelte-policy.
  • Remove the TypeScript 6 compatibility package when svelte-check no longer
    loads the typescript package.

Overview (`.agents/capture/README.md`)

QuickAdd docs captures

QuickAdd-specific fixtures and driver scripts for documentation screenshots and
GIFs. The generic tooling (capture instance, verified window/theme/font setup,
DPR-accurate crops, real-time recording, GIF conversion) is obsidian-e2e capture; see its README section "Screenshots And Recordings". Nothing here is
a test suite or required workflow: copy a script, change it, run it.

One-time setup (Linux orb)

.agents/setup installs Obsidian, Xvfb, fonts-inter and gifsicle
(ffmpeg ships with orbs). Build the plugin first: pnpm run build.

bash
# A throwaway capture vault with the plugin build linked and the demo fixture.
pnpm exec obsidian-e2e provision --root /tmp/qa-capture --vault Notes \
  --data .agents/capture/demo-data.json
rm /tmp/qa-capture/Notes/.obsidian/core-plugins.json   # [] disables the command palette
printf '# Inbox\n\n- Buy oat milk\n' > /tmp/qa-capture/Notes/Inbox.md

# A dedicated instance (never the E2E test instance) on CDP port 9333, DPR 2.
eval "$(pnpm exec obsidian-e2e capture launch --vault /tmp/qa-capture/Notes --print-env)"
amp orb service start quickadd-capture \
  --command "$PWD/node_modules/.bin/obsidian-e2e capture launch --vault /tmp/qa-capture/Notes"
# `obsidian` is the CLI bridge from .agents/setup (~/.local/bin/obsidian).
# On Linux the CLI socket follows XDG_RUNTIME_DIR, which the capture app sets to its HOME.
HOME=$OBSIDIAN_E2E_CAPTURE_HOME XDG_RUNTIME_DIR=$OBSIDIAN_E2E_CAPTURE_HOME obsidian vault=Notes plugins:restrict off
HOME=$OBSIDIAN_E2E_CAPTURE_HOME XDG_RUNTIME_DIR=$OBSIDIAN_E2E_CAPTURE_HOME obsidian vault=Notes quickadd:list   # lists "Add to inbox"

# After the restrict-off reload (any reload drops the injected capture CSS):
pnpm exec obsidian-e2e capture prepare --width 1280 --height 800 --scale 2 \
  --theme light --font Inter --hide-secret-warning

After pnpm run build, reload the plugin in the capture instance with
HOME=$OBSIDIAN_E2E_CAPTURE_HOME XDG_RUNTIME_DIR=$OBSIDIAN_E2E_CAPTURE_HOME obsidian vault=Notes plugin:reload id=quickadd,
then re-run pnpm exec obsidian-e2e capture prepare (any app reload drops the injected capture CSS).

Recipes

  • GIF of a flow: pnpm exec obsidian-e2e capture record inbox.webm --cursor -- .agents/capture/record-inbox-capture.sh,
    then pnpm exec obsidian-e2e capture sheet inbox.webm review.png to review and
    pnpm exec obsidian-e2e capture gif inbox.webm docs/src/content/docs/docs/Images/<name>.gif.
    The script asserts the note really changed; if anything fails (or you
    Ctrl-C) the take is discarded and an existing file is left untouched.
    On the capture instance's Xvfb display the recorder uses x11grab: use the
    default --fps 10 for GIFs and heavy views, --fps 30 for a smooth WebM.
    Keep the window's size fixed during a take (no prepare inside the driver)
    and keep the Settings popout closed, or the take is rejected.

  • Typing: use pnpm exec obsidian-e2e capture type "..." (one in-page call per string, paced
    and real-time) rather than one agent-browser call per key.

  • Active modal (prompts, suggesters):
    pnpm exec obsidian-e2e capture screenshot out.png --modal --pad 16 --clean.

  • Choice builders are taller than the window (a Capture builder expands to
    about 1900 CSS px): pnpm exec obsidian-e2e capture prepare --height 2100, then
    pnpm exec obsidian-e2e capture screenshot out.png --modal --expand --clean. Screenshots refuse
    targets that do not fit the viewport instead of clipping them. Open a builder
    from Settings -> QuickAdd with the choice's "Configure " button.

  • Crop a builder up to a setting: --rect-js with the in-page helpers, e.g.
    ending just above the "Behavior" heading row (same tall viewport):

    bash
    pnpm exec obsidian-e2e capture screenshot out.png --expand --clean --rect-js '(() => {
      const c = __obsidianE2ECapture, m = c.rect(c.modal()), r = c.rect(c.settingItem("Behavior"));
      return { x: m.x, y: m.y, width: m.width, height: r.y - m.y - 4 }; })()'

    Run pnpm exec obsidian-e2e capture prepare --height 800 afterwards to restore the normal window.

  • Settings: app.setting.open(); app.setting.openTabById("quickadd"), then
    --modal. If Settings opens as a popout window, add --window Settings
    to prepare/screenshot.

QuickAdd gotchas

  • Seed choices through data.json before launch (demo-data.json), or edit
    them through the UI. Direct runtime writes to plugin.settings can be
    overwritten by the settings store; plugin:reload if that happens.
  • Hand-written choices need every nested object QuickAdd expects: a Capture
    choice without insertAfter fails with "Cannot read properties of undefined
    (reading 'enabled')".
  • Capture prepend: true means append to the bottom (legacy name).
  • Do not press Escape to dismiss a suggester inside a builder: it closes the
    builder. Use --clean on the screenshot instead.
  • Keep API keys out of recordings: select them from Keychain rather than typing.
  • Disable Obsidian Sync in the capture vault if its red status icon shows:
    app.internalPlugins.plugins.sync.disable(true).

Desktop mobile-layout captures (app.emulateMobile(true) plus a phone-sized
prepare) show the mobile layout inside desktop Obsidian. They are not
real-device captures; real mobile hardware testing runs on agents-fsn1.


Overview (`docs/packages/README.md`)

Example packages

An example workflow in the docs ships as a QuickAdd package the reader can
copy from the page and import in Settings → QuickAdd → Import package….
This folder is the source of truth for those packages.

text
docs/packages/<id>/package.json     manifest (hand-authored, committed)
docs/packages/<id>/files/*.md       templates the package bundles (optional)
docs/public/scripts/*.js            user scripts the package bundles
docs/public/packages/<id>.quickadd.json   built package (generated, committed)

A page offers its package by setting package: in its frontmatter. The
PackageCard component then renders the "Get this workflow" card from the
manifest, and /packages/.quickadd.json is what the Copy button copies.

Manifest format

A manifest is a QuickAdd package (the same JSON Export package… produces)
with two differences:

  • Assets carry a source path relative to the manifest instead of base64
    content. Scripts must come from ../../public/scripts/, so the page's
    download link and the package always bundle the same file. Templates live in
    files/ next to the manifest.
  • An optional install block feeds the card:
    • requires: things the reader needs before importing (an account, another
      plugin, a minimum Obsidian version).
    • afterImport: settings to fill in afterwards, as short imperative lines.
      Lines support `code`, **bold** and [links](/docs/...); a link
      target must be site-relative or https://.

Choice ids must be qa-pkg- or qa-pkg--<suffix>. Stable ids let a
reader re-import an updated package and get "Overwrite" instead of a duplicate.

Secrets (API keys, tokens) are never in a package. Leave the setting out of the
script's settings object and tell the reader where to paste it in
afterImport.

Authoring a package

  1. Build the workflow in a vault, then Settings → QuickAdd → Export package…
    and copy the JSON.
  2. Create docs/packages//package.json, paste the choices and
    rootChoiceIds, and rename every choice id to the qa-pkg- namespace.
    Set quickAddVersion to the plugin version the workflow needs: the card
    shows it as "Needs QuickAdd X or later". If the package leans on import
    behaviour that is merged but unreleased, name the next minor release
    (releases are cut from Conventional Commits); the test suite allows at most
    one minor above manifest.json.
  3. Replace each asset's content with a source path relative to the
    manifest: ../../public/scripts/<name>.js for scripts, files/<name>.md
    for templates.
  4. Add the install block and set package: on the docs page.
  5. Run pnpm run packages:build and commit the generated file.

pnpm run test (the root Vitest suite, run on every PR) fails when a built
package is stale, when a choice's shape drifts from what the plugin stores,
when a package references a file it does not bundle, when a secret value is
present, or when a page and a manifest do not match one to one.


Src Content Docs Docs Index (`docs/src/content/docs/docs/index.md`)


title: Getting Started slug: docs

QuickAdd turns your repetitive Obsidian actions - creating a note from a
template, logging a line to your journal, running a script - into single
commands you trigger with a hotkey. Set a workflow up once, then run it in a
keystroke from anywhere in your vault.

New here? Build your first workflow below in about a minute.

Install QuickAdd

Install QuickAdd from Obsidian's Community plugins browser, then enable it.

If you cannot use the plugin browser, follow the
manual installation guide.

Choose the right choice type

If you want to... Use this Start here
Create a new note from a reusable file Template choice Template Choices
Append text to a journal, log, task list, or existing file Capture choice Capture Choices
Run one or more Obsidian commands, scripts, or choices Macro choice Macro Choices
Group choices into a nested menu Multi choice Multi Choices
Share configured workflows across vaults Package Share QuickAdd Packages

Most workflows start with either a Template choice or a Capture choice. Add a
Macro choice when you need scripting, multiple steps, or data from another
plugin or API.

First workflow

Let's build a capture that adds a timestamped line to your daily journal. It
takes about a minute.

  1. Open Settings → QuickAdd, click New choice, and pick Capture. Its
    settings open right away, as a page of the settings window.
  2. Set Name to Add to journal. (Before QuickAdd 2.30.0, the settings open
    in a dialog; click the name at the top to rename it.)
  3. Set Capture to to Journal/{{DATE}}.md - the note today's entries land in.
  4. Turn on Create file if it doesn't exist, so the first capture of the day
    creates today's note instead of stopping with a "Target file missing" notice.
  5. In Capture format, enter - {{DATE:HH:mm}} {{VALUE}} - the shape
    of one entry. (Before QuickAdd 2.30.0, turn on the Capture format
    toggle first.)
  6. Close the settings. Open the command palette (Ctrl/Cmd+P), run
    QuickAdd: Run, pick Add to journal, and type your entry.

QuickAdd writes a line like - 09:42 Standup moved to Wednesday to the top of
today's journal note, without opening it.

Once it works the way you want, click the ⚡ icon next to the choice to add it
to the command palette, then give it a hotkey in Obsidian's Settings →
Hotkeys
.

The {{DATE}} and {{VALUE}} above are format syntax:
placeholders QuickAdd fills in each time you run the choice. There are
placeholders for dates, your answers, links, clipboard content, and more. When a
prompt asks you for text, the suggester system lets you
type [[ or # to pull in a file, tag, or heading from your vault.

Common paths

I want examples first

Use the examples overview to pick a complete workflow by choice
type, difficulty, prerequisites, and outcome.

Good first examples:

I want to automate with scripts

Start with the scripting overview, then move to
User Scripts and the QuickAdd API reference
when you need exact method details.

I want to call QuickAdd from outside Obsidian

Use Obsidian URI for URI-triggered workflows, or the
QuickAdd CLI for shell scripts and external automation.


Src Content Docs Docs Advanced APIOverview (`docs/src/content/docs/docs/Advanced/APIOverview.md`)


title: API Overview slug: docs/Advanced/APIOverview

This page maps the QuickAdd API: where you can call it from, and which method
family handles each kind of task. For exact signatures and edge cases, follow
the links into the full reference.

Reach for the API when a workflow needs scripted input, file operations,
formatting, model calls, or access from another plugin. If you only need to
create a note or append text, start with
Template Choices or
Capture Choices - add the API when the workflow
needs logic.

Where the API is available

Context Access pattern Use it for
Macro user script params.quickAddApi Scripted macro steps
Inline script this.quickAddApi Small transformations inside templates or captures
Other plugin app.plugins.plugins.quickadd.api Calling QuickAdd from plugin code
Templater script app.plugins.plugins.quickadd.api Prompting or running choices from Templater

Common tasks

Task Method family Reference
Ask for text, selections, dates, or grouped inputs User input methods QuickAdd API Reference
Run another choice from a script Choice execution Choice Execution
Read selected text or clipboard content Utility module Utility Module
Format dates Date module Date Module
Call configured AI providers AI module AI Module
Read field suggestions from the vault Field suggestions module Field Suggestions Module
  1. Read the scripting overview if you have not written a
    QuickAdd script before.
  2. Copy a working pattern from the examples overview.
  3. Use the QuickAdd API reference for exact signatures and
    edge-case behavior.

Minimal macro script

javascript
module.exports = async ({ quickAddApi }) => {
  const title = await quickAddApi.inputPrompt("Book title");
  return `# ${title}`;
};

The returned value can be used by later macro steps or inserted through format
syntax.


Src Content Docs Docs Examples Index (`docs/src/content/docs/docs/Examples/index.md`)


title: Examples slug: docs/Examples

Use these examples when you want to copy a working pattern instead of starting
from a blank choice. Every workflow below ships as a package: on its page,
check what the Get this workflow card says it needs, click Copy
package
, import it in Obsidian, and follow After importing under How
to install
in the card. See
Install an example from the docs
for the full walkthrough.

Workflow Choice type Setup Prerequisites What it creates
Capture to Your Daily Note Capture Beginner Daily note path Timestamped entries, tasks, quotes, callouts, and table rows
Capture to Inbox with GPS Macro Intermediate Obsidian mobile 1.11+ for GPS A timestamped inbox line with coordinates
Brain dump Macro and Capture Beginner QuickAdd 2.29.0 or later Several inbox lines in one go, one per entry
Add a Task to a Kanban Board Capture Beginner Obsidian Kanban plugin A task in a board section
Fetch Tasks from Todoist Capture and Macro Intermediate Todoist API token Imported Todoist tasks
Canvas Capture Capture Intermediate An Obsidian Canvas file Text added to a selected or targeted card
Add an Inbox Item Template Beginner Inbox folder or note A new inbox note
Meeting notes and project updates Template Beginner QuickAdd 2.27.0 or later Dated notes and updates, ready to type in
Create an MOC Note with a Link Dashboard Template Intermediate Base template file A note with an embedded Base dashboard
Automatic Book Notes from Readwise Template and Macro Advanced Readwise account and access token Book notes with highlights
Book Finder Macro Intermediate Book lookup script A populated book note
Movie and Series Script Macro Intermediate OMDb API key Media notes with metadata
Move Notes with a Tag Macro Intermediate Tagged notes Notes moved into a target folder
Zettelizer Macro Intermediate Headings in an existing note New notes split from headings
Toggl Manager Macro Advanced Toggl Track account and integration plugin Preset time entries

Pick by goal

Capture information faster

Start with Capture to Your Daily Note for daily-note
captures. Use Capture to Inbox with GPS when the
line should also store device coordinates. Use Brain dump
to type several entries in a row without reopening the Capture. Move to
Canvas Capture when your target is a Canvas card
instead of a Markdown note.

Create structured notes

Start with Add an Inbox Item for a small template.
Try meeting notes and project updates
to create a dated note or add a section, then start typing where you put the cursor marker.
Use Create an MOC Note with a Link Dashboard
when you want a generated note to include a live Base dashboard.

Run scripted workflows

Start with Book Finder to see the common macro pattern:
prompt for input, call a script, write a note, and open the result.


Package.Json (`docs/package.json`)

{
"name": "docs",
"version": "0.0.0",
"private": true,
"type": "module",
"packageManager": "[email protected]",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/markdown-satteri": "^0.4.4",
"@astrojs/starlight": "^0.42.6",
"astro": "^7.4.0",
"sharp": "^0.35.5",
"starlight-llms-txt": "^0.12.0",
"zod": "4.6.7"
},
"pnpm": {
"overrides": {
"postcss-nested": "7.0.2"
}
}
}


Tsconfig.Json (`docs/tsconfig.json`)

{
"extends": "astro/tsconfigs/base",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["build", "node_modules"]
}


Packages Book Finder Package.Json (`docs/packages/book-finder/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-book-finder"
],
"choices": [
{
"choice": {
"id": "qa-pkg-book-finder",
"name": "Add book note",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-book-finder-macro",
"name": "Add book note",
"commands": [
{
"id": "qa-pkg-book-finder-script",
"name": "BookFinder",
"type": "UserScript",
"path": "scripts/BookFinder.js",
"settings": {}
},
{
"id": "qa-pkg-book-finder-nested",
"name": "Book note",
"type": "NestedChoice",
"choice": {
"id": "qa-pkg-book-finder-template",
"name": "Book note",
"type": "Template",
"command": false,
"templatePath": "Templates/Book.md",
"fileNameFormat": {
"enabled": true,
"format": "{{VALUE:fileName}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Books"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
}
}
]
}
},
"pathHint": [
"Add book note"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/BookFinder.js",
"source": "../../public/scripts/BookFinder.js"
},
{
"kind": "template",
"originalPath": "Templates/Book.md",
"source": "files/Book.md"
}
],
"install": {
"requires": [
"an internet connection (titles are looked up on Google Books; no API key needed)"
],
"afterImport": [
"Notes land in Books/. To use another folder, click the cog next to Add book note in Settings → QuickAdd, then the cog on the Book note step, and change Folder path.",
"Run Add book note from the command palette and enter a book title (a short clipboard text is prefilled). The first Google Books match becomes the note; there is no picker.",
"If Google Books answers with a quota error, try again later. It does not mean the book was not found."
]
}
}


Packages Brain Dump Package.Json (`docs/packages/brain-dump/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.29.0",
"createdAt": "2026-09-28T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-brain-dump",
"qa-pkg-brain-dump-inbox"
],
"choices": [
{
"choice": {
"id": "qa-pkg-brain-dump",
"name": "Brain dump",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-brain-dump-macro",
"name": "Brain dump",
"commands": [
{
"id": "qa-pkg-brain-dump-script",
"name": "brainDump",
"type": "UserScript",
"path": "scripts/brainDump.js",
"settings": {}
}
]
}
},
"pathHint": [
"Brain dump"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-brain-dump-inbox",
"name": "Brain dump entry",
"type": "Capture",
"command": false,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "Inbox.md",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "- {{VALUE}}"
},
"useSelectionAsCaptureValue": false,
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": true,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Brain dump entry"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/brainDump.js",
"source": "../../public/scripts/brainDump.js"
}
],
"install": {
"afterImport": [
"Run Brain dump from the command palette, or give it a hotkey in Settings → Hotkeys.",
"Optional: to put entries somewhere other than Inbox.md, hover over Brain dump entry in Settings → QuickAdd, click its cog, and change Capture to.",
"Optional: to repeat a Capture you already have, hover over Brain dump and click its cog, then click the cog on the brainDump step and enter that Capture's name in Capture choice."
]
}
}


Packages Canvas Capture Package.Json (`docs/packages/canvas-capture/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-canvas-capture"
],
"choices": [
{
"choice": {
"id": "qa-pkg-canvas-capture",
"name": "Add idea to card",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "",
"captureToActiveFile": true,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": false,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "- {{VALUE}}"
},
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Add idea to card"
],
"parentChoiceId": null
}
],
"assets": [],
"install": {
"afterImport": [
"Open a canvas and select exactly one text card or Markdown-file card. Run Add idea to card from the command palette and type your idea. It is added as a bullet at the bottom of the card.",
"Optional: to always write to the same card, click the cog next to Add idea to card in Settings → QuickAdd, turn off Capture to active file, set Capture to to your .canvas file, and pick the card under Target canvas node."
]
}
}


Packages Capture Inbox Gps Package.Json (`docs/packages/capture-inbox-gps/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.23.0",
"createdAt": "2026-08-31T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-capture-inbox-gps"
],
"choices": [
{
"choice": {
"id": "qa-pkg-capture-inbox-gps",
"name": "Capture to Inbox with GPS",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-capture-inbox-gps-macro",
"name": "Capture to Inbox with GPS",
"commands": [
{
"id": "qa-pkg-capture-inbox-gps-script",
"name": "captureInboxGps",
"type": "UserScript",
"path": "scripts/captureInboxGps.js",
"settings": {}
}
]
}
},
"pathHint": [
"Capture to Inbox with GPS"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/captureInboxGps.js",
"source": "../../public/scripts/captureInboxGps.js"
}
],
"install": {
"requires": [
"Obsidian mobile 1.11 or newer for GPS (desktop saves the line without coordinates)"
],
"afterImport": [
"Optional: in Settings → QuickAdd, click the cog next to Capture to Inbox with GPS, then the cog on the captureInboxGps step, to change Inbox path (default Inbox.md).",
"Run Capture to Inbox with GPS from the command palette, or give it a hotkey. On mobile, allow location access when asked."
]
}
}


Packages Change Daily Property Package.Json (`docs/packages/change-daily-property/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-change-daily-property"
],
"choices": [
{
"choice": {
"id": "qa-pkg-change-daily-property",
"name": "Change daily note property",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-change-daily-property-macro",
"name": "Change daily note property",
"commands": [
{
"id": "qa-pkg-change-daily-property-script",
"name": "changeDailyProperty",
"type": "UserScript",
"path": "scripts/changeDailyProperty.js",
"settings": {}
}
]
}
},
"pathHint": [
"Change daily note property"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/changeDailyProperty.js",
"source": "../../public/scripts/changeDailyProperty.js"
}
],
"install": {
"requires": [
"either the Daily notes core plugin turned on or a custom Daily note path (see below)"
],
"afterImport": [
"Create today's daily note and give it the properties you want to change; the menu only lists properties that already exist, and only text, number and checkbox ones.",
"Run Change daily note property from the command palette, pick a property, and enter its new value.",
"Daily notes somewhere else? Click the cog next to Change daily note property in Settings → QuickAdd, then the cog on the changeDailyProperty step, and set Daily note path (for example bins/daily/{{DATE:YYYY-MM-DD - ddd MMM D}}.md). Leave it empty to use the Daily notes plugin's folder and date format."
]
}
}


Packages Daily Note Captures Package.Json (`docs/packages/daily-note-captures/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.30.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-daily-note-captures"
],
"choices": [
{
"choice": {
"id": "qa-pkg-daily-note-captures",
"name": "Daily note",
"type": "Multi",
"command": false,
"choices": [
{
"id": "qa-pkg-daily-note-captures-journal",
"name": "Journal entry",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "- {{DATE:HH:mm}} {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "## Journal",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
{
"id": "qa-pkg-daily-note-captures-task",
"name": "Task",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{VALUE}}"
},
"insertAfter": {
"enabled": true,
"after": "## Tasks",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
{
"id": "qa-pkg-daily-note-captures-task-due",
"name": "Task with due date",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{VALUE}} due {{VDATE:due,YYYY-MM-DD}}"
},
"insertAfter": {
"enabled": true,
"after": "## Tasks",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
{
"id": "qa-pkg-daily-note-captures-quote",
"name": "Quote",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "> {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "## Quotes",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
{
"id": "qa-pkg-daily-note-captures-callout",
"name": "Callout",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "> {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "> [!info]- Captured today",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
}
],
"collapsed": false
},
"pathHint": [
"Daily note"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-journal",
"name": "Journal entry",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "- {{DATE:HH:mm}} {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "## Journal",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Journal entry"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-task",
"name": "Task",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{VALUE}}"
},
"insertAfter": {
"enabled": true,
"after": "## Tasks",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Task"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-task-due",
"name": "Task with due date",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{VALUE}} due {{VDATE:due,YYYY-MM-DD}}"
},
"insertAfter": {
"enabled": true,
"after": "## Tasks",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Task with due date"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-quote",
"name": "Quote",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "> {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "## Quotes",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Quote"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-callout",
"name": "Callout",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "> {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "> [!info]- Captured today",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Callout"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
}
],
"assets": [],
"install": {
"requires": [
"the Daily notes core plugin, or Periodic Notes 0.0.17, managing your daily notes (Periodic Notes 1.0 beta isn't supported)"
],
"afterImport": [
"Each capture writes to {{DAILY}}, the note Open today's daily note opens. A missing note is created from your daily notes template.",
"Run any of the five from the command palette, or run QuickAdd: Run, choose Daily note, and pick one. The heading or callout it writes under is created when missing."
]
}
}


Packages Inbox Item Package.Json (`docs/packages/inbox-item/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-inbox-item"
],
"choices": [
{
"choice": {
"id": "qa-pkg-inbox-item",
"name": "Inbox item",
"type": "Template",
"command": true,
"templatePath": "Templates/Inbox Template.md",
"fileNameFormat": {
"enabled": true,
"format": "{{DATE:YYYY-MM-DD-HH-mm-ss}} {{NAME}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Inbox"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
},
"pathHint": [
"Inbox item"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "template",
"originalPath": "Templates/Inbox Template.md",
"source": "files/Inbox Template.md"
}
],
"install": {
"afterImport": [
"Run Inbox item from the command palette and type a short name. QuickAdd creates and opens a timestamped note in Inbox/.",
"Optional: to use another folder, click the cog next to Inbox item in Settings → QuickAdd, remove Inbox from the list under New note location, type your folder in Folder path, and click Add."
]
}
}


Packages Kanban Task Package.Json (`docs/packages/kanban-task/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-kanban-task"
],
"choices": [
{
"choice": {
"id": "qa-pkg-kanban-task",
"name": "Add task to board",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "cursor",
"createFileIfItDoesntExist": {
"enabled": false,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": false,
"format": ""
},
"insertAfter": {
"enabled": true,
"after": "## Backlog",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Add task to board"
],
"parentChoiceId": null
}
],
"assets": [],
"install": {
"requires": [
"the Kanban community plugin",
"a board with at least one lane"
],
"afterImport": [
"In Settings → QuickAdd, click the cog next to Add task to board and set Capture to to your board file. Until you do, QuickAdd asks which file to capture to on every run.",
"Cards go to the top of the ## Backlog lane. If your lane has another name, change Insert after to match its heading, for example ## To do.",
"Run Add task to board from the command palette and type your task."
]
}
}


Packages Location From Address Package.Json (`docs/packages/location-from-address/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-location-from-address"
],
"choices": [
{
"choice": {
"id": "qa-pkg-location-from-address",
"name": "Add location from address",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-location-from-address-macro",
"name": "Add location from address",
"commands": [
{
"id": "qa-pkg-location-from-address-script",
"name": "getLongLatFromAddress",
"type": "UserScript",
"path": "scripts/getLongLatFromAddress.js",
"settings": {}
}
]
}
},
"pathHint": [
"Add location from address"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/getLongLatFromAddress.js",
"source": "../../public/scripts/getLongLatFromAddress.js"
}
],
"install": {
"requires": [
"an internet connection (addresses are looked up on OpenStreetMap's Nominatim service; no account needed)"
],
"afterImport": [
"Open the note you want to place, run Add location from address from the command palette, and enter an address. A specific address (street, city, country) gives the best match.",
"The note's location property becomes lat,lng, replacing any earlier value. Map View is optional: install it if you want to see the note on a map."
]
}
}


Packages Log Book Package.Json (`docs/packages/log-book/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-log-book"
],
"choices": [
{
"choice": {
"id": "qa-pkg-log-book",
"name": "Log book",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-log-book-macro",
"name": "Log book",
"commands": [
{
"id": "qa-pkg-log-book-script",
"name": "logBook",
"type": "UserScript",
"path": "scripts/logBook.js",
"settings": {}
}
]
}
},
"pathHint": [
"Log book"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/logBook.js",
"source": "../../public/scripts/logBook.js"
}
],
"install": {
"requires": [
"either the Daily notes core plugin turned on or a custom Daily note path (see below)"
],
"afterImport": [
"Create today's daily note if you have not yet; the macro does not create one.",
"Run Log book from the command palette and enter a title. It sets the Book property of today's note, replacing any earlier value.",
"Daily notes somewhere else, or another property? Click the cog next to Log book in Settings → QuickAdd, then the cog on the logBook step, and set Daily note path (for example bins/daily/{{DATE:YYYY-MM-DD - ddd MMM D}}.md) or Property name. Leave the path empty to use the Daily notes plugin's folder and date format."
]
}
}


Packages Meeting Notes Package.Json (`docs/packages/meeting-notes/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.30.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-meeting-notes",
"qa-pkg-meeting-notes-project-update"
],
"choices": [
{
"choice": {
"id": "qa-pkg-meeting-notes",
"name": "New meeting",
"type": "Template",
"command": true,
"templatePath": "Templates/Meeting.md",
"fileNameFormat": {
"enabled": true,
"format": "{{DATE:YYYY-MM-DD}} {{VALUE:Meeting}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Meetings"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "reuse",
"direction": "vertical",
"mode": "live",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
},
"pathHint": [
"New meeting"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-meeting-notes-project-update",
"name": "Add project update",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "",
"captureToActiveFile": true,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": false,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "\n{{TEMPLATE:Templates/Project update.md}}"
},
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Add project update"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "template",
"originalPath": "Templates/Meeting.md",
"source": "files/Meeting.md"
},
{
"kind": "template",
"originalPath": "Templates/Project update.md",
"source": "files/Project update.md"
}
],
"install": {
"afterImport": [
"Run New meeting from the command palette and answer the prompt. The note lands in Meetings/; change Folder path on the choice to use another folder.",
"Open any project note in Live Preview, click in the note body, and run Add project update to append a dated section and start typing under Progress."
]
}
}


Packages Migrate Dataview Properties Package.Json (`docs/packages/migrate-dataview-properties/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-migrate-dataview-properties"
],
"choices": [
{
"choice": {
"id": "qa-pkg-migrate-dataview-properties",
"name": "Migrate Dataview properties",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-migrate-dataview-properties-macro",
"name": "Migrate Dataview properties",
"commands": [
{
"id": "qa-pkg-migrate-dataview-properties-script",
"name": "migrateDataviewToFrontmatter",
"type": "UserScript",
"path": "scripts/migrateDataviewToFrontmatter.js",
"settings": {
"Migrate All Properties": false,
"Properties to Migrate": "Reference, Related"
}
}
]
}
},
"pathHint": [
"Migrate Dataview properties"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/migrateDataviewToFrontmatter.js",
"source": "../../public/scripts/migrateDataviewToFrontmatter.js"
}
],
"install": {
"afterImport": [
"Try it on a copy of a note first: migrated inline fields are removed from the note body.",
"In Settings → QuickAdd, click the cog next to Migrate Dataview properties, then the cog on the migrateDataviewToFrontmatter step. Set Properties to Migrate to the fields you want moved (default Reference, Related); turn on Migrate All Properties only if you want every inline field moved.",
"Open the note you want to migrate and run Migrate Dataview properties from the command palette. Only that note changes."
]
}
}


{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-moc-link-dashboard"
],
"choices": [
{
"choice": {
"id": "qa-pkg-moc-link-dashboard",
"name": "New MOC note",
"type": "Template",
"command": true,
"templatePath": "Templates/MOC Link Dashboard.md",
"fileNameFormat": {
"enabled": true,
"format": "{{VALUE:moc_title}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"MOCs"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "apply",
"mode": "increment"
}
},
"pathHint": [
"New MOC note"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "template",
"originalPath": "Templates/MOC Link Dashboard.md",
"source": "files/MOC Link Dashboard.md"
},
{
"kind": "template",
"originalPath": "Templates/MOC Link Dashboard.base",
"source": "files/MOC Link Dashboard.base"
}
],
"install": {
"requires": [
"the Bases core plugin, enabled in Settings → Core plugins"
],
"afterImport": [
"Run New MOC note from the command palette and enter a title. The note lands in MOCs/; change Folder path on the choice to use another folder."
]
}
}


{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-moc-related-notes"
],
"choices": [
{
"choice": {
"id": "qa-pkg-moc-related-notes",
"name": "Insert related notes base",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "",
"captureToActiveFile": true,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "top",
"createFileIfItDoesntExist": {
"enabled": false,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "## Related Notes\n\nbase\n{{TEMPLATE:Templates/MOC Related Notes.base}}\n\n\nContext: {{VALUE}}"
},
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Insert related notes base"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "template",
"originalPath": "Templates/MOC Related Notes.base",
"source": "files/MOC Related Notes.base"
}
],
"install": {
"requires": [
"the Bases core plugin"
],
"afterImport": [
"MOC Related Notes.base lands in your QuickAdd template folder if you set one, otherwise in Templates/. You can change that path under Files while importing.",
"Open your MOC note, run Insert related notes base from the command palette, and type a line of context."
]
}
}


Packages Move Tagged Notes Package.Json (`docs/packages/move-tagged-notes/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-move-tagged-notes"
],
"choices": [
{
"choice": {
"id": "qa-pkg-move-tagged-notes",
"name": "Move notes with a tag",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-move-tagged-notes-macro",
"name": "Move notes with a tag",
"commands": [
{
"id": "qa-pkg-move-tagged-notes-script",
"name": "moveNotesWithTag",
"type": "UserScript",
"path": "scripts/moveNotesWithTag.js",
"settings": {}
}
]
}
},
"pathHint": [
"Move notes with a tag"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/moveNotesWithTag.js",
"source": "../../public/scripts/moveNotesWithTag.js"
}
],
"install": {
"afterImport": [
"Back up your vault first. Picking the destination folder moves every matching note at once, with no preview or undo; to reverse it, move the notes back.",
"Run Move notes with a tag from the command palette, pick a tag, and choose whether nested tags count too (#project/work when you picked #project). The folder menu shows how many notes will move.",
"Notes whose path contains template (in any case, such as Templates/) are left where they are."
]
}
}


Packages Movie Notes Package.Json (`docs/packages/movie-notes/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-movie-notes"
],
"choices": [
{
"choice": {
"id": "qa-pkg-movie-notes",
"name": "Add movie note",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-movie-notes-macro",
"name": "Add movie note",
"commands": [
{
"id": "qa-pkg-movie-notes-script",
"name": "movies",
"type": "UserScript",
"path": "scripts/movies.js",
"settings": {}
},
{
"id": "qa-pkg-movie-notes-nested",
"name": "Movie note",
"type": "NestedChoice",
"choice": {
"id": "qa-pkg-movie-notes-template",
"name": "Movie note",
"type": "Template",
"command": false,
"templatePath": "Templates/Movie.md",
"fileNameFormat": {
"enabled": true,
"format": "{{VALUE:fileName}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Movies"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
}
}
]
}
},
"pathHint": [
"Add movie note"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/movies.js",
"source": "../../public/scripts/movies.js"
},
{
"kind": "template",
"originalPath": "Templates/Movie.md",
"source": "files/Movie.md"
}
],
"install": {
"requires": [
"an OMDb API key (free at omdbapi.com)"
],
"afterImport": [
"In Settings → QuickAdd, click the cog next to Add movie note, then the cog on the movies step. Paste your key into OMDb API Key and click the save icon next to the field. QuickAdd keeps it in Obsidian's secret storage, not in data.json.",
"Run Add movie note from the command palette and search for a title or IMDb id (like tt0111161). The note lands in Movies/; change Folder path on the Movie note step to use another folder."
]
}
}


Packages Readwise Book Notes Package.Json (`docs/packages/readwise-book-notes/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-readwise-book-notes",
"qa-pkg-readwise-book-notes-macro"
],
"choices": [
{
"choice": {
"id": "qa-pkg-readwise-book-notes",
"name": "Add Book Notes",
"type": "Template",
"command": true,
"onePageInput": "never",
"templatePath": "Templates/Book Notes.md",
"fileNameFormat": {
"enabled": true,
"format": "{{MACRO:Readwise::getBooks}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Books"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
},
"pathHint": [
"Add Book Notes"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-readwise-book-notes-macro",
"name": "Readwise",
"type": "Macro",
"command": false,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-readwise-book-notes-macro-body",
"name": "Readwise",
"commands": [
{
"id": "qa-pkg-readwise-book-notes-script",
"name": "readwise",
"type": "UserScript",
"path": "scripts/readwise.js",
"settings": {}
}
]
}
},
"pathHint": [
"Readwise"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/readwise.js",
"source": "../../public/scripts/readwise.js"
},
{
"kind": "template",
"originalPath": "Templates/Book Notes.md",
"source": "files/Book Notes.md"
}
],
"install": {
"requires": [
"a Readwise account and its access token"
],
"afterImport": [
"In Settings → QuickAdd, click the cog next to Readwise, then the cog on the readwise step. Paste your token into Readwise access token and click the save icon next to the field. QuickAdd keeps it in Obsidian's secret storage, not in data.json.",
"Run Add Book Notes from the command palette and pick a book. The note is named after it and lands in Books/, with the book's highlights under Highlights & Notes; change Folder path on the choice to use another folder."
]
}
}


Packages Todoist Tasks Package.Json (`docs/packages/todoist-tasks/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-todoist-tasks",
"qa-pkg-todoist-tasks-macro"
],
"choices": [
{
"choice": {
"id": "qa-pkg-todoist-tasks",
"name": "Import Todoist tasks",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "Todoist tasks.md",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "cursor",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{MACRO:Todoist}}"
},
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Import Todoist tasks"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-todoist-tasks-macro",
"name": "Todoist",
"type": "Macro",
"command": false,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-todoist-tasks-macro-body",
"name": "Todoist",
"commands": [
{
"id": "qa-pkg-todoist-tasks-script",
"name": "todoistTaskSync",
"type": "UserScript",
"path": "scripts/todoistTaskSync.js",
"settings": {}
}
]
}
},
"pathHint": [
"Todoist"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/todoistTaskSync.js",
"source": "../../public/scripts/TodoistScript.js"
}
],
"install": {
"requires": [
"a Todoist API token, copied from Settings → Integrations → Developer in Todoist"
],
"afterImport": [
"In Settings → QuickAdd, click the cog next to Todoist, then the cog on the todoistTaskSync step. Paste your token into Todoist API token and click the save icon next to the field. QuickAdd keeps it in Obsidian's secret storage, not in data.json.",
"Imported tasks are completed in Todoist by default, and recurring tasks move to their next occurrence. Untick Complete imported tasks in Todoist in the same settings to leave them open.",
"Run Import Todoist tasks from the command palette and pick which tasks to fetch: all of them, one project, or one section. They are written to Todoist tasks.md; change Capture to on the choice to use another note."
]
}
}


Packages Toggl Manager Package.Json (`docs/packages/toggl-manager/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-toggl-manager"
],
"choices": [
{
"choice": {
"id": "qa-pkg-toggl-manager",
"name": "Toggl manager",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-toggl-manager-macro",
"name": "Toggl manager",
"commands": [
{
"id": "qa-pkg-toggl-manager-script",
"name": "togglManager",
"type": "UserScript",
"path": "scripts/togglManager.js",
"settings": {}
}
]
}
},
"pathHint": [
"Toggl manager"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/togglManager.js",
"source": "../../public/scripts/togglManager.js"
}
],
"install": {
"requires": [
"a Toggl Track API token",
"the Toggl Track integration community plugin"
],
"afterImport": [
"If you have not already, install and enable Toggl Track integration from Settings → Community plugins → Browse. In its settings, paste the token from the bottom of your Toggl profile into API Token, click connect to check it, then pick your workspace under Toggl Workspace (click its refresh icon if the list is empty). The token lives in the Toggl plugin, not in QuickAdd.",
"Open scripts/togglManager.js (or wherever you put it during import) in a text editor and edit the menu object: each sub-menu has a togglProjectName, which must match a project in that workspace exactly, and menuOptions, where the left side is the label you see and the right side is the description Toggl gets. Save the file.",
"Run Toggl manager from the command palette, pick a project, then an entry to start the timer."
]
}
}


Packages Zettelizer Package.Json (`docs/packages/zettelizer/package.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-zettelizer"
],
"choices": [
{
"choice": {
"id": "qa-pkg-zettelizer",
"name": "Zettelize headings",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-zettelizer-macro",
"name": "Zettelize headings",
"commands": [
{
"id": "qa-pkg-zettelizer-script",
"name": "zettelizer",
"type": "UserScript",
"path": "scripts/zettelizer.js",
"settings": {}
}
]
}
},
"pathHint": [
"Zettelize headings"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/zettelizer.js",
"source": "../../public/scripts/zettelizer.js"
}
],
"install": {
"afterImport": [
"By default each ### heading gets a note in Zettels/ (created if missing) that embeds that section; the source note is not changed. The file name is the heading minus its first word, so one-word headings are skipped, and so are names that already exist.",
"To change the folder or heading level, click the cog next to Zettelize headings in Settings → QuickAdd, then the cog on the zettelizer step: Folder for new notes (empty means the vault root) and Heading level.",
"Open the note you want to split up and run Zettelize headings from the command palette."
]
}
}


Public Fonts LICENSE (`docs/public/fonts/LICENSE.txt`)

Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

  1. Neither the Font Software nor any of its individual components,
    in Original or Modified Versions, may be sold by itself.

  2. Original or Modified Versions of the Font Software may be bundled,
    redistributed and/or sold with any software, provided that each copy
    contains the above copyright notice and this license. These can be
    included either as stand-alone text files, human-readable headers or
    in the appropriate machine-readable metadata fields within text or
    binary files as long as those fields can be easily viewed by the user.

  3. No Modified Version of the Font Software may use the Reserved Font
    Name(s) unless explicit written permission is granted by the corresponding
    Copyright Holder. This restriction only applies to the primary font name as
    presented to the users.

  4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font
    Software shall not be used to promote, endorse or advertise any
    Modified Version, except to acknowledge the contribution(s) of the
    Copyright Holder(s) and the Author(s) or with their explicit written
    permission.

  5. The Font Software, modified or unmodified, in part or in whole,
    must be distributed entirely under this license, and must not be
    distributed under any other license. The requirement for fonts to
    remain under this license does not apply to any document created
    using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.


Public Packages Book Finder.Quickadd.Json (`docs/public/packages/book-finder.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-book-finder"
],
"choices": [
{
"choice": {
"id": "qa-pkg-book-finder",
"name": "Add book note",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-book-finder-macro",
"name": "Add book note",
"commands": [
{
"id": "qa-pkg-book-finder-script",
"name": "BookFinder",
"type": "UserScript",
"path": "scripts/BookFinder.js",
"settings": {}
},
{
"id": "qa-pkg-book-finder-nested",
"name": "Book note",
"type": "NestedChoice",
"choice": {
"id": "qa-pkg-book-finder-template",
"name": "Book note",
"type": "Template",
"command": false,
"templatePath": "Templates/Book.md",
"fileNameFormat": {
"enabled": true,
"format": "{{VALUE:fileName}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Books"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
}
}
]
}
},
"pathHint": [
"Add book note"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/BookFinder.js",
"contentEncoding": "base64",
"content": "Y29uc3Qgbm90aWNlID0gKG1zZykgPT4gbmV3IE5vdGljZShtc2csIDUwMDApOwpjb25zdCBsb2cgPSAobXNnKSA9PiBjb25zb2xlLmxvZyhtc2cpOwoKY29uc3QgR09PR0xFX0JPT0tTX0FQSV9VUkwgPSAiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vYm9va3MvdjEvdm9sdW1lcyI7CmNvbnN0IEdPT0dMRV9CT09LU19USVRMRV9URVJNID0gImludGl0bGU6IgoKbGV0IFF1aWNrQWRkOwoKbW9kdWxlLmV4cG9ydHMgPSAgYXN5bmMgZnVuY3Rpb24gc3RhcnQocGFyYW1zKSB7CiAgUXVpY2tBZGQgPSBwYXJhbXM7CgogIC8vIFByZWZpbGwgdGhlIHByb21wdCBmcm9tIHRoZSBjbGlwYm9hcmQgd2hlbiBpdCBsb29rcyBsaWtlIGEgdGl0bGUsIG5vdCB3aGVuIGl0CiAgLy8gaG9sZHMgc29tZXRoaW5nIGxvbmcgb3IgbXVsdGktbGluZSBzdWNoIGFzIGEgcGFzdGVkIGFydGljbGUgb3IgYSBwYWNrYWdlLgogIGNvbnN0IGNsaXBCb2FyZENvbnRlbnRzID0gU3RyaW5nKChhd2FpdCBRdWlja0FkZC5xdWlja0FkZEFwaS51dGlsaXR5LmdldENsaXBib2FyZCgpLmNhdGNoKCgpID0+ICIiKSkgPz8gIiIpLnRyaW0oKTsKICBjb25zdCBwcmVmaWxsID0gY2xpcEJvYXJkQ29udGVudHMubGVuZ3RoIDw9IDEyMCAmJiAhY2xpcEJvYXJkQ29udGVudHMuaW5jbHVkZXMoIlxuIikgPyBjbGlwQm9hcmRDb250ZW50cyA6ICIiOwogIGNvbnN0IHRpdGxlID0gYXdhaXQgUXVpY2tBZGQucXVpY2tBZGRBcGkuaW5wdXRQcm9tcHQoCiAgICAiRW50ZXIgQm9vayB0aXRsZTogIiwgcHJlZmlsbCwgcHJlZmlsbCAvLyBwcmVmaWxsIGlzIGFkZGVkIG9uY2UgYXMgdGhlIHBsYWNlaG9sZGVyIGFuZCBvbmNlIGFzIHRoZSBkZWZhdWx0IHZhbHVlCiAgKTsKICBpZiAoIXRpdGxlKSB7CiAgICBub3RpY2UoIk5vIHRpdGxlIGVudGVyZWQuIik7CiAgICB0aHJvdyBuZXcgRXJyb3IoIk5vIHRpdGxlIGVudGVyZWQuIik7CiAgfQoKICBjb25zdCBlbmNvZGVkVGl0bGUgPSBlbmNvZGVVUklDb21wb25lbnQoR09PR0xFX0JPT0tTX1RJVExFX1RFUk0gKyB0aXRsZSk7CiAgY29uc3QgZmluYWxVUkwgPSBHT09HTEVfQk9PS1NfQVBJX1VSTCArICI/cT0iICsgZW5jb2RlZFRpdGxlICsgIiZtYXhSZXN1bHRzPTEwIjsKICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKGZpbmFsVVJMKTsKICAvLyBBIHByb3h5IG9yIG91dGFnZSBjYW4gYW5zd2VyIHdpdGggYW4gSFRNTCBwYWdlIHJhdGhlciB0aGFuIEpTT04sIGV2ZW4gd2l0aCBhIDJ4eCBzdGF0dXMuCiAgY29uc3QgYm9va0Rlc2MgPSBhd2FpdCByZXNwb25zZS5qc29uKCkuY2F0Y2goKCkgPT4gbnVsbCk7CgogIC8vIEtleWxlc3MgcmVxdWVzdHMgc2hhcmUgYSBwZXItbmV0d29yayBkYWlseSBxdW90YSwgc28gYSA0MjkgaGVyZSBpcyBub3QgIm5vIHJlc3VsdHMiLgogIGlmICghcmVzcG9uc2Uub2sgfHwgIWJvb2tEZXNjIHx8IGJvb2tEZXNjLmVycm9yKSB7CiAgICBjb25zdCByZWFzb24gPSBib29rRGVzYz8uZXJyb3I/Lm1lc3NhZ2UgPz8gKHJlc3BvbnNlLm9rID8gInRoZSByZXNwb25zZSB3YXMgbm90IEpTT04iIDogYEhUVFAgJHtyZXNwb25zZS5zdGF0dXN9YCk7CiAgICBub3RpY2UoIkdvb2dsZSBCb29rcyByZXF1ZXN0IGZhaWxlZDogIiArIHJlYXNvbik7CiAgICB0aHJvdyBuZXcgRXJyb3IoIkdvb2dsZSBCb29rcyByZXF1ZXN0IGZhaWxlZDogIiArIHJlYXNvbik7CiAgfQoKICAvLyBUaGUgR29vZ2xlIEJvb2tzIEFQSSBvbWl0cyBgaXRlbXNgIGVudGlyZWx5IHdoZW4gYSB0aXRsZSB5aWVsZHMgbm8gbWF0Y2hlcy4KICBpZiAoIWJvb2tEZXNjLml0ZW1zIHx8IGJvb2tEZXNjLml0ZW1zLmxlbmd0aCA9PT0gMCkgewogICAgbm90aWNlKCJObyByZXN1bHRzIGZvdW5kIGZvcjogIiArIHRpdGxlKTsKICAgIHRocm93IG5ldyBFcnJvcigiTm8gcmVzdWx0cyBmb3VuZCBmb3I6ICIgKyB0aXRsZSk7CiAgfQoKICAvLyBJbiBhbiBpZGVhbCB3b3JsZCB3ZSB3b3VsZCBwb3B1cCBhIHBpY2tlciB0aGF0IHNob3dzIHRoZSB1c2VyOiBCb29rIFRpdGxlLCBBdXRob3IocykgYW5kIGNvdmVyLiBUaGV5IHdvdWxkIHNlbGVjdCB0aGUgY29ycmVjdCB2ZXJzaW9uIGZyb20gdGhlcmUKICBjb25zdCBib29rID0gYm9va0Rlc2MuaXRlbXNbMF07CiAgY29uc3Qgdm9sdW1lSW5mbyA9IGJvb2sudm9sdW1lSW5mbzsKCiAgLy8gQXV0aG9ycywgY2F0ZWdvcmllcywgZGVzY3JpcHRpb24gYW5kIGNvdmVyIGFyZSBhbGwgb3B0aW9uYWwgaW4gdGhlIEFQSS4KICAvLyBRdWlja0FkZCB0cmVhdHMgYW4gdW5kZWZpbmVkIHZhcmlhYmxlIGFzICJub3QgYW5zd2VyZWQgeWV0IiBhbmQgd291bGQgcHJvbXB0CiAgLy8gZm9yIGl0LCBzbyBmYWxsIGJhY2sgdG8gYW4gZW1wdHkgdmFsdWUgaW5zdGVhZC4KICBRdWlja0FkZC52YXJpYWJsZXMgPSB7CiAgICAuLi5ib29rLAogICAgdGl0bGU6IHZvbHVtZUluZm8udGl0bGUsCiAgICAvLyBIb3cgdG8gZ2V0IG11dGlwbGUgYXV0aG9ycyBvciBjYXRlZ29yaWVzIG91dCB3aXRoIGNvbW1hcyBiZXR3ZWVuIHRoZW0KICAgIGF1dGhvcnM6IHZvbHVtZUluZm8uYXV0aG9ycyA/PyAiIiwKICAgIGNhdGVnb3JpZXM6IHZvbHVtZUluZm8uY2F0ZWdvcmllcyA/PyAiIiwKICAgIGRlc2NyaXB0aW9uOiB2b2x1bWVJbmZvLmRlc2NyaXB0aW9uID8/ICIiLAogICAgZmlsZU5hbWU6IHJlcGxhY2VJbGxlZ2FsRmlsZU5hbWVDaGFyYWN0ZXJzSW5TdHJpbmcodm9sdW1lSW5mby50aXRsZSksCiAgICBQb3N0ZXI6IHZvbHVtZUluZm8uaW1hZ2VMaW5rcz8uc21hbGxUaHVtYm5haWwgPz8gIiIKICB9Owp9CgpmdW5jdGlvbiByZXBsYWNlSWxsZWdhbEZpbGVOYW1lQ2hhcmFjdGVyc0luU3RyaW5nKHN0cmluZykgewogIHJldHVybiBzdHJpbmcucmVwbGFjZSgvW1xcLCMlJlx7XH1cLyo8Pj8kXCdcIjpAXSovZywgIiIpOwp9"
},
{
"kind": "template",
"originalPath": "Templates/Book.md",
"contentEncoding": "base64",
"content": "IVtwb3N0ZXJdKHt7VkFMVUU6UG9zdGVyfX0pCgoqKkF1dGhvcioqOjoge3tWQUxVRTphdXRob3JzfX0KKipUaXRsZSoqOjoge3tWQUxVRTp0aXRsZX19CioqQ2F0ZWdvcnkqKjo6e3tWQUxVRTpjYXRlZ29yaWVzfX0KKipTdGF0dXMqKjo6IPCfk6UKCioqUmVsYXRlZCBCb29rcyoqCiMjIyBDb3JlIFF1ZXN0aW9ucyBmb3IgTWUKCiMjIyBBY3Rpb25zCgojIyMgTXkgTm90ZXMKCiMjIERldGFpbHMKe3tWQUxVRTpkZXNjcmlwdGlvbn19Cg=="
}
]
}


Public Packages Brain Dump.Quickadd.Json (`docs/public/packages/brain-dump.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.29.0",
"createdAt": "2026-09-28T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-brain-dump",
"qa-pkg-brain-dump-inbox"
],
"choices": [
{
"choice": {
"id": "qa-pkg-brain-dump",
"name": "Brain dump",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-brain-dump-macro",
"name": "Brain dump",
"commands": [
{
"id": "qa-pkg-brain-dump-script",
"name": "brainDump",
"type": "UserScript",
"path": "scripts/brainDump.js",
"settings": {}
}
]
}
},
"pathHint": [
"Brain dump"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-brain-dump-inbox",
"name": "Brain dump entry",
"type": "Capture",
"command": false,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "Inbox.md",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "- {{VALUE}}"
},
"useSelectionAsCaptureValue": false,
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": true,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Brain dump entry"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/brainDump.js",
"contentEncoding": "base64",
"content": "Y29uc3QgQ0FQVFVSRV9DSE9JQ0UgPSAiQ2FwdHVyZSBjaG9pY2UiOwoKbW9kdWxlLmV4cG9ydHMgPSB7CgllbnRyeTogc3RhcnQsCglzZXR0aW5nczogewoJCW5hbWU6ICJCcmFpbiBkdW1wIiwKCQlhdXRob3I6ICJRdWlja0FkZCIsCgkJb3B0aW9uczogewoJCQlbQ0FQVFVSRV9DSE9JQ0VdOiB7CgkJCQl0eXBlOiAidGV4dCIsCgkJCQlkZWZhdWx0VmFsdWU6ICJCcmFpbiBkdW1wIGVudHJ5IiwKCQkJCXBsYWNlaG9sZGVyOiAiQnJhaW4gZHVtcCBlbnRyeSIsCgkJCQlkZXNjcmlwdGlvbjogIlRoZSBDYXB0dXJlIGNob2ljZSB0byBvcGVuIGFnYWluIGFmdGVyIGVhY2ggZW50cnkiLAoJCQl9LAoJCX0sCgl9LAp9OwoKYXN5bmMgZnVuY3Rpb24gc3RhcnQocGFyYW1zLCBzZXR0aW5ncykgewoJY29uc3QgY2hvaWNlID0KCQlTdHJpbmcoc2V0dGluZ3M/LltDQVBUVVJFX0NIT0lDRV0gPz8gIiIpLnRyaW0oKSB8fCAiQnJhaW4gZHVtcCBlbnRyeSI7CgoJLy8gRWFjaCBlbnRyeSBydW5zIHRoZSBDYXB0dXJlIG9uY2UuIEVzYyAob3IgQ2FuY2VsKSByZWplY3RzLCB3aGljaCBlbmRzIHRoZQoJLy8gbG9vcC4gQSBDYXB0dXJlIHRoYXQgbmV2ZXIgYXNrcyBmb3IgaW5wdXQgd291bGQgcmVwZWF0IGZvcmV2ZXIsIHNvIHN0b3AuCgl3aGlsZSAoYXdhaXQgYXNrZWRGb3JJbnB1dCgoKSA9PiBwYXJhbXMucXVpY2tBZGRBcGkuZXhlY3V0ZUNob2ljZShjaG9pY2UpKSkge30KCgluZXcgcGFyYW1zLm9ic2lkaWFuLk5vdGljZSgKCQlgQnJhaW4gZHVtcCBzdG9wcGVkOiAiJHtjaG9pY2V9IiBkaWRuJ3QgYXNrIGZvciBhbnkgaW5wdXQuYCwKCSk7Cn0KCi8vIFJ1bnMgYHJ1bmAgYW5kIHJlcG9ydHMgd2hldGhlciBhIGRpYWxvZywgc3VjaCBhcyBhIHByb21wdCwgb3BlbmVkIG1lYW53aGlsZS4KYXN5bmMgZnVuY3Rpb24gYXNrZWRGb3JJbnB1dChydW4pIHsKCWxldCBvcGVuZWQgPSBmYWxzZTsKCWNvbnN0IGNoZWNrID0gKHJlY29yZHMpID0+IHsKCQlvcGVuZWQgfHw9IHJlY29yZHMuc29tZSgocmVjb3JkKSA9PgoJCQlbLi4ucmVjb3JkLmFkZGVkTm9kZXNdLnNvbWUoKG5vZGUpID0+CgkJCQlub2RlLmNsYXNzTGlzdD8uY29udGFpbnMoIm1vZGFsLWNvbnRhaW5lciIpLAoJCQkpLAoJCSk7Cgl9OwoJY29uc3Qgb2JzZXJ2ZXIgPSBuZXcgTXV0YXRpb25PYnNlcnZlcihjaGVjayk7CglvYnNlcnZlci5vYnNlcnZlKGFjdGl2ZURvY3VtZW50LmJvZHksIHsgY2hpbGRMaXN0OiB0cnVlIH0pOwoJdHJ5IHsKCQlhd2FpdCBydW4oKTsKCX0gZmluYWxseSB7CgkJY2hlY2sob2JzZXJ2ZXIudGFrZVJlY29yZHMoKSk7CgkJb2JzZXJ2ZXIuZGlzY29ubmVjdCgpOwoJfQoJcmV0dXJuIG9wZW5lZDsKfQo="
}
]
}


Public Packages Canvas Capture.Quickadd.Json (`docs/public/packages/canvas-capture.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-canvas-capture"
],
"choices": [
{
"choice": {
"id": "qa-pkg-canvas-capture",
"name": "Add idea to card",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "",
"captureToActiveFile": true,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": false,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "- {{VALUE}}"
},
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Add idea to card"
],
"parentChoiceId": null
}
],
"assets": []
}


Public Packages Capture Inbox Gps.Quickadd.Json (`docs/public/packages/capture-inbox-gps.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.23.0",
"createdAt": "2026-08-31T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-capture-inbox-gps"
],
"choices": [
{
"choice": {
"id": "qa-pkg-capture-inbox-gps",
"name": "Capture to Inbox with GPS",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-capture-inbox-gps-macro",
"name": "Capture to Inbox with GPS",
"commands": [
{
"id": "qa-pkg-capture-inbox-gps-script",
"name": "captureInboxGps",
"type": "UserScript",
"path": "scripts/captureInboxGps.js",
"settings": {}
}
]
}
},
"pathHint": [
"Capture to Inbox with GPS"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/captureInboxGps.js",
"contentEncoding": "base64",
"content": "Y29uc3QgSU5CT1hfUEFUSCA9ICJJbmJveCBwYXRoIjsKY29uc3QgQ1JFQVRFX0lGX01JU1NJTkcgPSAiQ3JlYXRlIEluYm94IGlmIG1pc3NpbmciOwoKbW9kdWxlLmV4cG9ydHMgPSB7CgllbnRyeTogc3RhcnQsCglzZXR0aW5nczogewoJCW5hbWU6ICJDYXB0dXJlIHRvIEluYm94IHdpdGggR1BTIiwKCQlhdXRob3I6ICJRdWlja0FkZCIsCgkJb3B0aW9uczogewoJCQlbSU5CT1hfUEFUSF06IHsKCQkJCXR5cGU6ICJ0ZXh0IiwKCQkJCWRlZmF1bHRWYWx1ZTogIkluYm94Lm1kIiwKCQkJCXBsYWNlaG9sZGVyOiAiSW5ib3gubWQiLAoJCQkJZGVzY3JpcHRpb246ICJWYXVsdCBwYXRoIG9mIHRoZSBub3RlIHRvIGFwcGVuZCB0byIsCgkJCX0sCgkJCVtDUkVBVEVfSUZfTUlTU0lOR106IHsKCQkJCXR5cGU6ICJ0b2dnbGUiLAoJCQkJZGVmYXVsdFZhbHVlOiB0cnVlLAoJCQkJZGVzY3JpcHRpb246ICJDcmVhdGUgdGhlIEluYm94IG5vdGUgd2hlbiBpdCBkb2VzIG5vdCBleGlzdCIsCgkJCX0sCgkJfSwKCX0sCn07Cgphc3luYyBmdW5jdGlvbiBzdGFydChwYXJhbXMsIHNldHRpbmdzKSB7Cgljb25zdCB7IE5vdGljZSwgVEZpbGUsIG5vcm1hbGl6ZVBhdGggfSA9IHBhcmFtcy5vYnNpZGlhbjsKCWNvbnN0IGluYm94UGF0aCA9IG5vcm1hbGl6ZVBhdGgoCgkJU3RyaW5nKHNldHRpbmdzPy5bSU5CT1hfUEFUSF0gfHwgIkluYm94Lm1kIikudHJpbSgpIHx8ICJJbmJveC5tZCIsCgkpOwoJY29uc3QgY3JlYXRlSWZNaXNzaW5nID0gc2V0dGluZ3M/LltDUkVBVEVfSUZfTUlTU0lOR10gIT09IGZhbHNlOwoKCWNvbnN0IGNvb3Jkc1Byb21pc2UgPSByZXNvbHZlQ29vcmRpbmF0ZXMocGFyYW1zLnZhcmlhYmxlcyk7Cgljb25zdCB0ZXh0ID0gYXdhaXQgcmVzb2x2ZU5vdGVUZXh0KHBhcmFtcyk7CglpZiAoIXRleHQpIHJldHVybjsKCgljb25zdCBjb29yZHMgPSBhd2FpdCBjb29yZHNQcm9taXNlOwoJY29uc3Qgc3RhbXAgPSBmb3JtYXRTdGFtcCgpOwoJY29uc3Qgc3VmZml4ID0gY29vcmRzID8gYCAoJHtjb29yZHN9KWAgOiAiIjsKCWNvbnN0IGxpbmUgPSBgLSAke3N0YW1wfSAke3RleHR9JHtzdWZmaXh9XG5gOwoKCWNvbnN0IGV4aXN0aW5nID0gcGFyYW1zLmFwcC52YXVsdC5nZXRBYnN0cmFjdEZpbGVCeVBhdGgoaW5ib3hQYXRoKTsKCWlmIChleGlzdGluZyBpbnN0YW5jZW9mIFRGaWxlKSB7CgkJLy8gT3RoZXIgY2FwdHVyZXMgYW5kIHRoZSBlZGl0b3IgbGVhdmUgdGhlIGxhc3QgbGluZSB1bnRlcm1pbmF0ZWQuCgkJYXdhaXQgcGFyYW1zLmFwcC52YXVsdC5wcm9jZXNzKGV4aXN0aW5nLCAoZGF0YSkgPT4KCQkJZGF0YSA9PT0gIiIgfHwgZGF0YS5lbmRzV2l0aCgiXG4iKSA/IGRhdGEgKyBsaW5lIDogYCR7ZGF0YX1cbiR7bGluZX1gLAoJCSk7Cgl9IGVsc2UgaWYgKGV4aXN0aW5nKSB7CgkJbmV3IE5vdGljZShgUXVpY2tBZGQ6ICR7aW5ib3hQYXRofSBpcyBhIGZvbGRlciwgbm90IGEgbm90ZWApOwoJCXJldHVybjsKCX0gZWxzZSBpZiAoY3JlYXRlSWZNaXNzaW5nKSB7CgkJYXdhaXQgZW5zdXJlUGFyZW50Rm9sZGVycyhwYXJhbXMuYXBwLCBpbmJveFBhdGgpOwoJCWF3YWl0IHBhcmFtcy5hcHAudmF1bHQuY3JlYXRlKGluYm94UGF0aCwgbGluZSk7Cgl9IGVsc2UgewoJCW5ldyBOb3RpY2UoYFF1aWNrQWRkOiAke2luYm94UGF0aH0gbm90IGZvdW5kYCk7CgkJcmV0dXJuOwoJfQoKCWlmICghY29vcmRzKSB7CgkJbmV3IE5vdGljZSgiU2F2ZWQgd2l0aG91dCBHUFMgKG5vIGZpeCwgcGVybWlzc2lvbiBkZW5pZWQsIG9yIGRlc2t0b3ApIik7Cgl9Cn0KCmFzeW5jIGZ1bmN0aW9uIHJlc29sdmVOb3RlVGV4dChwYXJhbXMpIHsKCWNvbnN0IHByZXNldCA9IHBhcmFtcy52YXJpYWJsZXM/LnZhbHVlOwoJaWYgKHR5cGVvZiBwcmVzZXQgPT09ICJzdHJpbmciKSB7CgkJcmV0dXJuIHByZXNldC50cmltKCk7Cgl9Cgljb25zdCB0eXBlZCA9IGF3YWl0IHBhcmFtcy5xdWlja0FkZEFwaS5pbnB1dFByb21wdCgiSW5ib3giKTsKCXJldHVybiB0eXBlb2YgdHlwZWQgPT09ICJzdHJpbmciID8gdHlwZWQudHJpbSgpIDogIiI7Cn0KCmZ1bmN0aW9uIHJlc29sdmVDb29yZGluYXRlcyh2YXJpYWJsZXMpIHsKCWNvbnN0IHByZXNldCA9IHZhcmlhYmxlcz8uY29vcmRpbmF0ZXM7CglpZiAodHlwZW9mIHByZXNldCA9PT0gInN0cmluZyIgJiYgcHJlc2V0LnRyaW0oKSkgewoJCXJldHVybiBQcm9taXNlLnJlc29sdmUocHJlc2V0LnRyaW0oKSk7Cgl9CgoJcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7CgkJaWYgKHR5cGVvZiBuYXZpZ2F0b3IgPT09ICJ1bmRlZmluZWQiIHx8ICFuYXZpZ2F0b3IuZ2VvbG9jYXRpb24pIHsKCQkJcmVzb2x2ZSgiIik7CgkJCXJldHVybjsKCQl9CgoJCW5hdmlnYXRvci5nZW9sb2NhdGlvbi5nZXRDdXJyZW50UG9zaXRpb24oCgkJCShwb3MpID0+IHsKCQkJCXJlc29sdmUoCgkJCQkJYCR7cG9zLmNvb3Jkcy5sYXRpdHVkZS50b0ZpeGVkKDYpfSwgJHtwb3MuY29vcmRzLmxvbmdpdHVkZS50b0ZpeGVkKDYpfWAsCgkJCQkpOwoJCQl9LAoJCQkoKSA9PiByZXNvbHZlKCIiKSwKCQkJewoJCQkJZW5hYmxlSGlnaEFjY3VyYWN5OiB0cnVlLAoJCQkJdGltZW91dDogMzAwMDAsCgkJCQltYXhpbXVtQWdlOiA2MDAwMCwKCQkJfSwKCQkpOwoJfSk7Cn0KCmZ1bmN0aW9uIGZvcm1hdFN0YW1wKCkgewoJaWYgKHR5cGVvZiB3aW5kb3cgIT09ICJ1bmRlZmluZWQiICYmIHdpbmRvdy5tb21lbnQpIHsKCQlyZXR1cm4gd2luZG93Lm1vbWVudCgpLmZvcm1hdCgiWVlZWS1NTS1ERCBISDptbSIpOwoJfQoJcmV0dXJuIG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKS5zbGljZSgwLCAxNikucmVwbGFjZSgiVCIsICIgIik7Cn0KCmFzeW5jIGZ1bmN0aW9uIGVuc3VyZVBhcmVudEZvbGRlcnMoYXBwLCBmaWxlUGF0aCkgewoJY29uc3QgcGFydHMgPSBmaWxlUGF0aC5zcGxpdCgiLyIpLmZpbHRlcihCb29sZWFuKTsKCXBhcnRzLnBvcCgpOwoJbGV0IGZvbGRlciA9ICIiOwoJZm9yIChjb25zdCBwYXJ0IG9mIHBhcnRzKSB7CgkJZm9sZGVyID0gZm9sZGVyID8gYCR7Zm9sZGVyfS8ke3BhcnR9YCA6IHBhcnQ7CgkJaWYgKCFhcHAudmF1bHQuZ2V0QWJzdHJhY3RGaWxlQnlQYXRoKGZvbGRlcikpIHsKCQkJYXdhaXQgYXBwLnZhdWx0LmNyZWF0ZUZvbGRlcihmb2xkZXIpOwoJCX0KCX0KfQo="
}
]
}


Public Packages Change Daily Property.Quickadd.Json (`docs/public/packages/change-daily-property.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-change-daily-property"
],
"choices": [
{
"choice": {
"id": "qa-pkg-change-daily-property",
"name": "Change daily note property",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-change-daily-property-macro",
"name": "Change daily note property",
"commands": [
{
"id": "qa-pkg-change-daily-property-script",
"name": "changeDailyProperty",
"type": "UserScript",
"path": "scripts/changeDailyProperty.js",
"settings": {}
}
]
}
},
"pathHint": [
"Change daily note property"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/changeDailyProperty.js",
"contentEncoding": "base64",
"content": "Ly8gTGlzdHMgdGhlIHByb3BlcnRpZXMgaW4gdG9kYXkncyBkYWlseSBub3RlLCBhc2tzIGZvciBhIG5ldyB2YWx1ZSwgYW5kIHdyaXRlcyBpdCBiYWNrLgovLyBVc2VzIE9ic2lkaWFuJ3Mgb3duIGFwcC5maWxlTWFuYWdlci5wcm9jZXNzRnJvbnRNYXR0ZXI7IG5vIG90aGVyIHBsdWdpbnMgbmVlZGVkLgovLyBEb2NzOiBodHRwczovL3F1aWNrYWRkLm9ic2lkaWFuLmd1aWRlL2RvY3MvRXhhbXBsZXMvTWFjcm9fQ2hhbmdlUHJvcGVydHlJbkRhaWx5Tm90ZXMKY29uc3QgREFJTFlfTk9URV9QQVRIID0gIkRhaWx5IG5vdGUgcGF0aCI7Cgptb2R1bGUuZXhwb3J0cyA9IHsKCWVudHJ5OiBzdGFydCwKCXNldHRpbmdzOiB7CgkJbmFtZTogIkNoYW5nZSBkYWlseSBub3RlIHByb3BlcnR5IiwKCQlhdXRob3I6ICJDaHJpc3RpYW4gQi4gQi4gSG91bWFubiIsCgkJb3B0aW9uczogewoJCQlbREFJTFlfTk9URV9QQVRIXTogewoJCQkJdHlwZTogImZvcm1hdCIsCgkJCQlkZWZhdWx0VmFsdWU6ICIiLAoJCQkJcGxhY2Vob2xkZXI6ICJEYWlseS97e0RBVEU6WVlZWS1NTS1ERH19Lm1kIiwKCQkJCWRlc2NyaXB0aW9uOgoJCQkJCSJQYXRoIG9mIHRvZGF5J3MgZGFpbHkgbm90ZSwgd2l0aCBRdWlja0FkZCBkYXRlIHN5bnRheC4gTGVhdmUgZW1wdHkgdG8gdXNlIHRoZSBEYWlseSBub3RlcyBjb3JlIHBsdWdpbidzIGZvbGRlciBhbmQgZGF0ZSBmb3JtYXQuIiwKCQkJfSwKCQl9LAoJfSwKfTsKCmFzeW5jIGZ1bmN0aW9uIHN0YXJ0KHBhcmFtcywgc2V0dGluZ3MpIHsKCWNvbnN0IHsgYXBwLCBxdWlja0FkZEFwaSwgb2JzaWRpYW4gfSA9IHBhcmFtczsKCWNvbnN0IHBhdGggPSBhd2FpdCB0b2RheXNEYWlseU5vdGVQYXRoKGFwcCwgcXVpY2tBZGRBcGksIHNldHRpbmdzW0RBSUxZX05PVEVfUEFUSF0pOwoJaWYgKCFwYXRoKSB7CgkJbmV3IG9ic2lkaWFuLk5vdGljZSgiRW5hYmxlIHRoZSBEYWlseSBub3RlcyBjb3JlIHBsdWdpbiBvciBzZXQgRGFpbHkgbm90ZSBwYXRoIGluIHRoZSBzY3JpcHQgc2V0dGluZ3MuIiwgODAwMCk7CgkJcmV0dXJuOwoJfQoJY29uc3QgZmlsZSA9IGFwcC52YXVsdC5nZXRBYnN0cmFjdEZpbGVCeVBhdGgocGF0aCk7CglpZiAoIShmaWxlIGluc3RhbmNlb2Ygb2JzaWRpYW4uVEZpbGUpKSB7CgkJbmV3IG9ic2lkaWFuLk5vdGljZShgTm8gZGFpbHkgbm90ZSBhdCAke3BhdGh9LmApOwoJCXJldHVybjsKCX0KCgkvLyBPbmx5IHNpbmdsZS12YWx1ZSBwcm9wZXJ0aWVzOiBhIG9uZS1saW5lIHByb21wdCBjYW5ub3QgZWRpdCBsaXN0cyBvciBuZXN0ZWQgb2JqZWN0cy4KCWNvbnN0IGZyb250bWF0dGVyID0gYXBwLm1ldGFkYXRhQ2FjaGUuZ2V0RmlsZUNhY2hlKGZpbGUpPy5mcm9udG1hdHRlciA/PyB7fTsKCWNvbnN0IGtleXMgPSBPYmplY3Qua2V5cyhmcm9udG1hdHRlcikuZmlsdGVyKAoJCShrZXkpID0+IGtleSAhPT0gInBvc2l0aW9uIiAmJiAoZnJvbnRtYXR0ZXJba2V5XSA9PT0gbnVsbCB8fCB0eXBlb2YgZnJvbnRtYXR0ZXJba2V5XSAhPT0gIm9iamVjdCIpCgkpOwoJaWYgKGtleXMubGVuZ3RoID09PSAwKSB7CgkJbmV3IG9ic2lkaWFuLk5vdGljZShgTm8gdGV4dCwgbnVtYmVyIG9yIGNoZWNrYm94IHByb3BlcnRpZXMgaW4gJHtmaWxlLmJhc2VuYW1lfWApOwoJCXJldHVybjsKCX0KCgljb25zdCBrZXkgPSBhd2FpdCBxdWlja0FkZEFwaS5zdWdnZXN0ZXIoa2V5cywga2V5cyk7CglpZiAoIWtleSkgcmV0dXJuOwoKCWNvbnN0IGN1cnJlbnQgPSBmcm9udG1hdHRlcltrZXldOwoJY29uc3Qgc2hvd24gPSBTdHJpbmcoY3VycmVudCA/PyAiIik7Cgljb25zdCBuZXdWYWx1ZSA9IGF3YWl0IHF1aWNrQWRkQXBpLmlucHV0UHJvbXB0KGBMb2cgJHtrZXl9YCwgc2hvd24sIHNob3duKTsKCWlmIChuZXdWYWx1ZSA9PT0gdW5kZWZpbmVkIHx8IG5ld1ZhbHVlID09PSBudWxsKSByZXR1cm47CgoJYXdhaXQgYXBwLmZpbGVNYW5hZ2VyLnByb2Nlc3NGcm9udE1hdHRlcihmaWxlLCAoZm0pID0+IHsKCQlmbVtrZXldID0ga2VlcFR5cGUoY3VycmVudCwgbmV3VmFsdWUpOwoJfSk7Cn0KCi8vIEFuIGV4cGxpY2l0IHBhdGggd2luczsgb3RoZXJ3aXNlIHRvZGF5J3Mgbm90ZSBpcyB3aGVyZSB0aGUgRGFpbHkgbm90ZXMgY29yZQovLyBwbHVnaW4gd291bGQgY3JlYXRlIGl0LiBSZXR1cm5zIG51bGwgd2hlbiBuZWl0aGVyIGlzIGF2YWlsYWJsZS4KYXN5bmMgZnVuY3Rpb24gdG9kYXlzRGFpbHlOb3RlUGF0aChhcHAsIHF1aWNrQWRkQXBpLCBzZXR0aW5nKSB7Cgljb25zdCBleHBsaWNpdCA9IFN0cmluZyhzZXR0aW5nID8/ICIiKS50cmltKCk7CglpZiAoZXhwbGljaXQpIHJldHVybiBxdWlja0FkZEFwaS5mb3JtYXQoZXhwbGljaXQsIHt9KTsKCgljb25zdCBkYWlseU5vdGVzID0gYXBwLmludGVybmFsUGx1Z2lucy5wbHVnaW5zWyJkYWlseS1ub3RlcyJdOwoJaWYgKCFkYWlseU5vdGVzPy5lbmFibGVkKSByZXR1cm4gbnVsbDsKCWNvbnN0IHsgZm9sZGVyID0gIiIsIGZvcm1hdCA9ICJZWVlZLU1NLUREIiB9ID0gZGFpbHlOb3Rlcy5pbnN0YW5jZS5vcHRpb25zID8/IHt9OwoJY29uc3QgbmFtZSA9IHdpbmRvdy5tb21lbnQoKS5mb3JtYXQoZm9ybWF0IHx8ICJZWVlZLU1NLUREIik7Cgljb25zdCBkaXIgPSBTdHJpbmcoZm9sZGVyKS50cmltKCkucmVwbGFjZSgvXlwvK3xcLyskL2csICIiKTsKCXJldHVybiBkaXIgPyBgJHtkaXJ9LyR7bmFtZX0ubWRgIDogYCR7bmFtZX0ubWRgOwp9CgovLyBXcml0ZSBudW1iZXJzIGFuZCBib29sZWFucyBiYWNrIGFzIG51bWJlcnMgYW5kIGJvb2xlYW5zIHdoZW4gdGhlIG5ldyB0ZXh0IHN0aWxsIGlzIG9uZS4KZnVuY3Rpb24ga2VlcFR5cGUoY3VycmVudCwgdGV4dCkgewoJaWYgKHR5cGVvZiBjdXJyZW50ID09PSAibnVtYmVyIiAmJiB0ZXh0LnRyaW0oKSAhPT0gIiIgJiYgTnVtYmVyLmlzRmluaXRlKE51bWJlcih0ZXh0KSkpIHsKCQlyZXR1cm4gTnVtYmVyKHRleHQpOwoJfQoJaWYgKHR5cGVvZiBjdXJyZW50ID09PSAiYm9vbGVhbiIgJiYgKHRleHQgPT09ICJ0cnVlIiB8fCB0ZXh0ID09PSAiZmFsc2UiKSkgewoJCXJldHVybiB0ZXh0ID09PSAidHJ1ZSI7Cgl9CglyZXR1cm4gdGV4dDsKfQo="
}
]
}


Public Packages Daily Note Captures.Quickadd.Json (`docs/public/packages/daily-note-captures.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.30.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-daily-note-captures"
],
"choices": [
{
"choice": {
"id": "qa-pkg-daily-note-captures",
"name": "Daily note",
"type": "Multi",
"command": false,
"choices": [
{
"id": "qa-pkg-daily-note-captures-journal",
"name": "Journal entry",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "- {{DATE:HH:mm}} {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "## Journal",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
{
"id": "qa-pkg-daily-note-captures-task",
"name": "Task",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{VALUE}}"
},
"insertAfter": {
"enabled": true,
"after": "## Tasks",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
{
"id": "qa-pkg-daily-note-captures-task-due",
"name": "Task with due date",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{VALUE}} due {{VDATE:due,YYYY-MM-DD}}"
},
"insertAfter": {
"enabled": true,
"after": "## Tasks",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
{
"id": "qa-pkg-daily-note-captures-quote",
"name": "Quote",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "> {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "## Quotes",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
{
"id": "qa-pkg-daily-note-captures-callout",
"name": "Callout",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "> {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "> [!info]- Captured today",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
}
],
"collapsed": false
},
"pathHint": [
"Daily note"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-journal",
"name": "Journal entry",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "- {{DATE:HH:mm}} {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "## Journal",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Journal entry"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-task",
"name": "Task",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{VALUE}}"
},
"insertAfter": {
"enabled": true,
"after": "## Tasks",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Task"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-task-due",
"name": "Task with due date",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{VALUE}} due {{VDATE:due,YYYY-MM-DD}}"
},
"insertAfter": {
"enabled": true,
"after": "## Tasks",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Task with due date"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-quote",
"name": "Quote",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "> {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "## Quotes",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "bottom",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Quote"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
},
{
"choice": {
"id": "qa-pkg-daily-note-captures-callout",
"name": "Callout",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "{{DAILY}}",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "> {{VALUE}}\n"
},
"insertAfter": {
"enabled": true,
"after": "> [!info]- Captured today",
"insertAtEnd": true,
"considerSubsections": false,
"createIfNotFound": true,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Daily note",
"Callout"
],
"parentChoiceId": "qa-pkg-daily-note-captures"
}
],
"assets": []
}


Public Packages Inbox Item.Quickadd.Json (`docs/public/packages/inbox-item.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-inbox-item"
],
"choices": [
{
"choice": {
"id": "qa-pkg-inbox-item",
"name": "Inbox item",
"type": "Template",
"command": true,
"templatePath": "Templates/Inbox Template.md",
"fileNameFormat": {
"enabled": true,
"format": "{{DATE:YYYY-MM-DD-HH-mm-ss}} {{NAME}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Inbox"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
},
"pathHint": [
"Inbox item"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "template",
"originalPath": "Templates/Inbox Template.md",
"contentEncoding": "base64",
"content": "LS0tCmNyZWF0ZWQ6IHt7REFURTpZWVlZLU1NLUREIEhIOm1tfX0KdGFnczogW2luYm94XQotLS0KIyB7e05BTUV9fQoK"
}
]
}


Public Packages Kanban Task.Quickadd.Json (`docs/public/packages/kanban-task.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-kanban-task"
],
"choices": [
{
"choice": {
"id": "qa-pkg-kanban-task",
"name": "Add task to board",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "cursor",
"createFileIfItDoesntExist": {
"enabled": false,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": false,
"format": ""
},
"insertAfter": {
"enabled": true,
"after": "## Backlog",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": true,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Add task to board"
],
"parentChoiceId": null
}
],
"assets": []
}


Public Packages Location From Address.Quickadd.Json (`docs/public/packages/location-from-address.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-location-from-address"
],
"choices": [
{
"choice": {
"id": "qa-pkg-location-from-address",
"name": "Add location from address",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-location-from-address-macro",
"name": "Add location from address",
"commands": [
{
"id": "qa-pkg-location-from-address-script",
"name": "getLongLatFromAddress",
"type": "UserScript",
"path": "scripts/getLongLatFromAddress.js",
"settings": {}
}
]
}
},
"pathHint": [
"Add location from address"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/getLongLatFromAddress.js",
"contentEncoding": "base64",
"content": "Ly8gR2VvY29kZXMgYW4gYWRkcmVzcyB3aXRoIE9wZW5TdHJlZXRNYXAncyBmcmVlIE5vbWluYXRpbSBBUEkgYW5kIHN0b3JlcyB0aGUKLy8gY29vcmRpbmF0ZXMgaW4gdGhlIGFjdGl2ZSBub3RlJ3MgYGxvY2F0aW9uYCBwcm9wZXJ0eSBhcyBgbGF0LGxuZ2AsIHRoZQovLyBmcm9udCBtYXR0ZXIgZm9ybWF0IHRoYXQgdGhlIE1hcCBWaWV3IHBsdWdpbiByZWFkcyBhbmQgd3JpdGVzIGl0c2VsZi4KbW9kdWxlLmV4cG9ydHMgPSBhc3luYyAocGFyYW1zKSA9PiB7CiAgICBjb25zdCB7YXBwLCBvYnNpZGlhbiwgcXVpY2tBZGRBcGl9ID0gcGFyYW1zOwoKICAgIC8vIFRoZSBub3RlIG9wZW4gd2hlbiB0aGUgbWFjcm8gc3RhcnRzIGlzIHRoZSBvbmUgdGhhdCBnZXRzIHRoZSBsb2NhdGlvbi4KICAgIC8vIENhcHR1cmUgaXQgbm93OiB0aGUgcHJvbXB0J3MgUGVlayBsZXRzIHRoZSB1c2VyIG9wZW4gb3RoZXIgbm90ZXMgKGZvcgogICAgLy8gZXhhbXBsZSB0byBjb3B5IHRoZSBhZGRyZXNzKSBiZWZvcmUgc3VibWl0dGluZy4KICAgIGNvbnN0IGFjdGl2ZUZpbGUgPSBhcHAud29ya3NwYWNlLmdldEFjdGl2ZUZpbGUoKTsKICAgIGlmICghYWN0aXZlRmlsZSkgewogICAgICAgIG5ldyBvYnNpZGlhbi5Ob3RpY2UoIk5vIGFjdGl2ZSBmaWxlIiwgNTAwMCk7CiAgICAgICAgcmV0dXJuOwogICAgfQoKICAgIGNvbnN0IGFkZHJlc3MgPSBhd2FpdCBxdWlja0FkZEFwaS5pbnB1dFByb21wdChg8J+PoCBBZGRyZXNzIGZvciAke2FjdGl2ZUZpbGUucGF0aH1gKTsKICAgIGlmICghYWRkcmVzcykgewogICAgICAgIG5ldyBvYnNpZGlhbi5Ob3RpY2UoIk5vIGFkZHJlc3MgZ2l2ZW4iLCA1MDAwKTsKICAgICAgICByZXR1cm47CiAgICB9CgogICAgbGV0IHJlc3VsdHM7CiAgICB0cnkgewogICAgICAgIHJlc3VsdHMgPSBhd2FpdCBnZW9jb2RlKG9ic2lkaWFuLCBhZGRyZXNzKTsKICAgIH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgICAgY29uc29sZS5lcnJvcigiZ2V0TG9uZ0xhdEZyb21BZGRyZXNzOiBnZW9jb2RpbmcgZmFpbGVkIiwgZXJyb3IpOwogICAgICAgIG5ldyBvYnNpZGlhbi5Ob3RpY2UoYENvdWxkIG5vdCBsb29rIHVwICIke2FkZHJlc3N9IjogJHtlcnJvcj8ubWVzc2FnZSA/PyBlcnJvcn1gLCA4MDAwKTsKICAgICAgICByZXR1cm47CiAgICB9CiAgICBpZiAoIXJlc3VsdHMubGVuZ3RoKSB7CiAgICAgICAgbmV3IG9ic2lkaWFuLk5vdGljZShgTm8gcmVzdWx0cyBmb3VuZCBmb3IgIiR7YWRkcmVzc30iYCwgNTAwMCk7CiAgICAgICAgcmV0dXJuOwogICAgfQoKICAgIGNvbnN0IHtsYXQsIGxvbn0gPSByZXN1bHRzWzBdOwogICAgYXdhaXQgYXBwLmZpbGVNYW5hZ2VyLnByb2Nlc3NGcm9udE1hdHRlcihhY3RpdmVGaWxlLCAoZnJvbnRtYXR0ZXIpID0+IHsKICAgICAgICBmcm9udG1hdHRlci5sb2NhdGlvbiA9IGAke2xhdH0sJHtsb259YDsKICAgIH0pOwp9OwoKYXN5bmMgZnVuY3Rpb24gZ2VvY29kZShvYnNpZGlhbiwgYWRkcmVzcykgewogICAgY29uc3QgdXJsID0gbmV3IFVSTCgiaHR0cHM6Ly9ub21pbmF0aW0ub3BlbnN0cmVldG1hcC5vcmcvc2VhcmNoIik7CiAgICAvLyBVUkxTZWFyY2hQYXJhbXMgZW5jb2RlcyB0aGUgYWRkcmVzcywgc28gY2hhcmFjdGVycyBsaWtlIGAmYCBhbmQgYCNgCiAgICAvLyBzdGF5IHBhcnQgb2YgdGhlIHF1ZXJ5IGluc3RlYWQgb2YgY3V0dGluZyBpdCBzaG9ydC4KICAgIHVybC5zZWFyY2ggPSBuZXcgVVJMU2VhcmNoUGFyYW1zKHtxOiBhZGRyZXNzLCBmb3JtYXQ6ICJqc29uIiwgbGltaXQ6ICIxIn0pLnRvU3RyaW5nKCk7CgogICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBvYnNpZGlhbi5yZXF1ZXN0VXJsKHt1cmw6IHVybC50b1N0cmluZygpfSk7CiAgICByZXR1cm4gcmVzcG9uc2UuanNvbjsKfQo="
}
]
}


Public Packages Log Book.Quickadd.Json (`docs/public/packages/log-book.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-log-book"
],
"choices": [
{
"choice": {
"id": "qa-pkg-log-book",
"name": "Log book",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-log-book-macro",
"name": "Log book",
"commands": [
{
"id": "qa-pkg-log-book-script",
"name": "logBook",
"type": "UserScript",
"path": "scripts/logBook.js",
"settings": {}
}
]
}
},
"pathHint": [
"Log book"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/logBook.js",
"contentEncoding": "base64",
"content": "Ly8gQXNrcyB3aGljaCBib29rIHlvdSBhcmUgcmVhZGluZyBhbmQgd3JpdGVzIGl0IHRvIGEgcHJvcGVydHkgaW4gdG9kYXkncyBkYWlseSBub3RlLgovLyBVc2VzIE9ic2lkaWFuJ3Mgb3duIGFwcC5maWxlTWFuYWdlci5wcm9jZXNzRnJvbnRNYXR0ZXI7IG5vIG90aGVyIHBsdWdpbnMgbmVlZGVkLgovLyBEb2NzOiBodHRwczovL3F1aWNrYWRkLm9ic2lkaWFuLmd1aWRlL2RvY3MvRXhhbXBsZXMvTWFjcm9fTG9nQm9va1RvRGFpbHlKb3VybmFsCmNvbnN0IERBSUxZX05PVEVfUEFUSCA9ICJEYWlseSBub3RlIHBhdGgiOwpjb25zdCBQUk9QRVJUWV9OQU1FID0gIlByb3BlcnR5IG5hbWUiOwoKbW9kdWxlLmV4cG9ydHMgPSB7CgllbnRyeTogc3RhcnQsCglzZXR0aW5nczogewoJCW5hbWU6ICJMb2cgYm9vayIsCgkJYXV0aG9yOiAiQ2hyaXN0aWFuIEIuIEIuIEhvdW1hbm4iLAoJCW9wdGlvbnM6IHsKCQkJW0RBSUxZX05PVEVfUEFUSF06IHsKCQkJCXR5cGU6ICJmb3JtYXQiLAoJCQkJZGVmYXVsdFZhbHVlOiAiIiwKCQkJCXBsYWNlaG9sZGVyOiAiRGFpbHkve3tEQVRFOllZWVktTU0tRER9fS5tZCIsCgkJCQlkZXNjcmlwdGlvbjoKCQkJCQkiUGF0aCBvZiB0b2RheSdzIGRhaWx5IG5vdGUsIHdpdGggUXVpY2tBZGQgZGF0ZSBzeW50YXguIExlYXZlIGVtcHR5IHRvIHVzZSB0aGUgRGFpbHkgbm90ZXMgY29yZSBwbHVnaW4ncyBmb2xkZXIgYW5kIGRhdGUgZm9ybWF0LiIsCgkJCX0sCgkJCVtQUk9QRVJUWV9OQU1FXTogewoJCQkJdHlwZTogInRleHQiLAoJCQkJZGVmYXVsdFZhbHVlOiAiQm9vayIsCgkJCQlwbGFjZWhvbGRlcjogIkJvb2siLAoJCQkJZGVzY3JpcHRpb246ICJGcm9udG1hdHRlciBwcm9wZXJ0eSB0byB3cml0ZS4iLAoJCQl9LAoJCX0sCgl9LAp9OwoKYXN5bmMgZnVuY3Rpb24gc3RhcnQocGFyYW1zLCBzZXR0aW5ncykgewoJY29uc3QgeyBhcHAsIHF1aWNrQWRkQXBpLCBvYnNpZGlhbiB9ID0gcGFyYW1zOwoJY29uc3QgcGF0aCA9IGF3YWl0IHRvZGF5c0RhaWx5Tm90ZVBhdGgoYXBwLCBxdWlja0FkZEFwaSwgc2V0dGluZ3NbREFJTFlfTk9URV9QQVRIXSk7CglpZiAoIXBhdGgpIHsKCQluZXcgb2JzaWRpYW4uTm90aWNlKCJFbmFibGUgdGhlIERhaWx5IG5vdGVzIGNvcmUgcGx1Z2luIG9yIHNldCBEYWlseSBub3RlIHBhdGggaW4gdGhlIHNjcmlwdCBzZXR0aW5ncy4iLCA4MDAwKTsKCQlyZXR1cm47Cgl9Cgljb25zdCBmaWxlID0gYXBwLnZhdWx0LmdldEFic3RyYWN0RmlsZUJ5UGF0aChwYXRoKTsKCWlmICghKGZpbGUgaW5zdGFuY2VvZiBvYnNpZGlhbi5URmlsZSkpIHsKCQluZXcgb2JzaWRpYW4uTm90aWNlKGBObyBkYWlseSBub3RlIGF0ICR7cGF0aH0uYCk7CgkJcmV0dXJuOwoJfQoKCWNvbnN0IHByb3AgPSBTdHJpbmcoc2V0dGluZ3NbUFJPUEVSVFlfTkFNRV0gfHwgIkJvb2siKS50cmltKCkgfHwgIkJvb2siOwoJY29uc3QgdmFsdWUgPSBhd2FpdCBxdWlja0FkZEFwaS5pbnB1dFByb21wdCgi8J+TliBCb29rIG5hbWUiKTsKCWlmICghdmFsdWUpIHJldHVybjsKCglhd2FpdCBhcHAuZmlsZU1hbmFnZXIucHJvY2Vzc0Zyb250TWF0dGVyKGZpbGUsIChmbSkgPT4gewoJCWZtW3Byb3BdID0gdmFsdWU7Cgl9KTsKCW5ldyBvYnNpZGlhbi5Ob3RpY2UoYExvZ2dlZCAiJHt2YWx1ZX0iIHRvICR7cHJvcH0gaW4gJHtmaWxlLmJhc2VuYW1lfWApOwp9CgovLyBBbiBleHBsaWNpdCBwYXRoIHdpbnM7IG90aGVyd2lzZSB0b2RheSdzIG5vdGUgaXMgd2hlcmUgdGhlIERhaWx5IG5vdGVzIGNvcmUKLy8gcGx1Z2luIHdvdWxkIGNyZWF0ZSBpdC4gUmV0dXJucyBudWxsIHdoZW4gbmVpdGhlciBpcyBhdmFpbGFibGUuCmFzeW5jIGZ1bmN0aW9uIHRvZGF5c0RhaWx5Tm90ZVBhdGgoYXBwLCBxdWlja0FkZEFwaSwgc2V0dGluZykgewoJY29uc3QgZXhwbGljaXQgPSBTdHJpbmcoc2V0dGluZyA/PyAiIikudHJpbSgpOwoJaWYgKGV4cGxpY2l0KSByZXR1cm4gcXVpY2tBZGRBcGkuZm9ybWF0KGV4cGxpY2l0LCB7fSk7CgoJY29uc3QgZGFpbHlOb3RlcyA9IGFwcC5pbnRlcm5hbFBsdWdpbnMucGx1Z2luc1siZGFpbHktbm90ZXMiXTsKCWlmICghZGFpbHlOb3Rlcz8uZW5hYmxlZCkgcmV0dXJuIG51bGw7Cgljb25zdCB7IGZvbGRlciA9ICIiLCBmb3JtYXQgPSAiWVlZWS1NTS1ERCIgfSA9IGRhaWx5Tm90ZXMuaW5zdGFuY2Uub3B0aW9ucyA/PyB7fTsKCWNvbnN0IG5hbWUgPSB3aW5kb3cubW9tZW50KCkuZm9ybWF0KGZvcm1hdCB8fCAiWVlZWS1NTS1ERCIpOwoJY29uc3QgZGlyID0gU3RyaW5nKGZvbGRlcikudHJpbSgpLnJlcGxhY2UoL15cLyt8XC8rJC9nLCAiIik7CglyZXR1cm4gZGlyID8gYCR7ZGlyfS8ke25hbWV9Lm1kYCA6IGAke25hbWV9Lm1kYDsKfQo="
}
]
}


Public Packages Meeting Notes.Quickadd.Json (`docs/public/packages/meeting-notes.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.30.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-meeting-notes",
"qa-pkg-meeting-notes-project-update"
],
"choices": [
{
"choice": {
"id": "qa-pkg-meeting-notes",
"name": "New meeting",
"type": "Template",
"command": true,
"templatePath": "Templates/Meeting.md",
"fileNameFormat": {
"enabled": true,
"format": "{{DATE:YYYY-MM-DD}} {{VALUE:Meeting}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Meetings"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "reuse",
"direction": "vertical",
"mode": "live",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
},
"pathHint": [
"New meeting"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-meeting-notes-project-update",
"name": "Add project update",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "",
"captureToActiveFile": true,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "bottom",
"createFileIfItDoesntExist": {
"enabled": false,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "\n{{TEMPLATE:Templates/Project update.md}}"
},
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Add project update"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "template",
"originalPath": "Templates/Meeting.md",
"contentEncoding": "base64",
"content": "IyB7e1ZBTFVFOk1lZXRpbmd9fQoKRGF0ZToge3tEQVRFOllZWVktTU0tRER9fQoKIyMgTm90ZXMKLSB7e0NVUlNPUn19CgojIyBOZXh0IHN0ZXBzCi0gWyBdIAo="
},
{
"kind": "template",
"originalPath": "Templates/Project update.md",
"contentEncoding": "base64",
"content": "IyMgVXBkYXRlIC0ge3tEQVRFOllZWVktTU0tRER9fQoKIyMjIFByb2dyZXNzCi0ge3tDVVJTT1J9fQoKIyMjIE5leHQgc3RlcHMKLSBbIF0gCg=="
}
]
}


Public Packages Migrate Dataview Properties.Quickadd.Json (`docs/public/packages/migrate-dataview-properties.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-migrate-dataview-properties"
],
"choices": [
{
"choice": {
"id": "qa-pkg-migrate-dataview-properties",
"name": "Migrate Dataview properties",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-migrate-dataview-properties-macro",
"name": "Migrate Dataview properties",
"commands": [
{
"id": "qa-pkg-migrate-dataview-properties-script",
"name": "migrateDataviewToFrontmatter",
"type": "UserScript",
"path": "scripts/migrateDataviewToFrontmatter.js",
"settings": {
"Migrate All Properties": false,
"Properties to Migrate": "Reference, Related"
}
}
]
}
},
"pathHint": [
"Migrate Dataview properties"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/migrateDataviewToFrontmatter.js",
"contentEncoding": "base64",
"content": "LyoqCiAqIE1pZ3JhdGUgRGF0YXZpZXcgSW5saW5lIFByb3BlcnRpZXMgdG8gRnJvbnRtYXR0ZXIKICoKICogVGhpcyBzY3JpcHQgbWlncmF0ZXMgaW5saW5lIGRhdGF2aWV3IHByb3BlcnRpZXMgKGUuZy4sICJSZWZlcmVuY2U6OiBbW2xpbmtdXSIpCiAqIHRvIFlBTUwgZnJvbnRtYXR0ZXIuIEl0IGhhbmRsZXMgY29tbWEtc2VwYXJhdGVkIHZhbHVlcyB3aXRoIHNwZWNpYWwgY2FyZSBmb3IKICogY29tbWFzIGluc2lkZSB3aWtpbGlua3MuCiAqCiAqIEJFRk9SRToKICogICBSZWZlcmVuY2U6OiBbWzIwMjUtMTAtMTIgLSBTdW4gT2N0IDEyXV0KICogICBSZWxhdGVkOjogW1tBZ2VudGljIEVuZ2luZWVyaW5nfEFnZW50aWMgY29kaW5nXV0sIFtbTm90ZSwgd2l0aCBjb21tYV1dCiAqICAgVGFnczo6ICNwcm9qZWN0LCAjaW1wb3J0YW50CiAqCiAqIEFGVEVSOgogKiAgIC0tLQogKiAgIFJlZmVyZW5jZTogIltbMjAyNS0xMC0xMiAtIFN1biBPY3QgMTJdXSIKICogICBSZWxhdGVkOgogKiAgICAgLSAiW1tBZ2VudGljIEVuZ2luZWVyaW5nfEFnZW50aWMgY29kaW5nXV0iCiAqICAgICAtICJbW05vdGUsIHdpdGggY29tbWFdXSIKICogICB0YWdzOgogKiAgICAgLSAicHJvamVjdCIKICogICAgIC0gImltcG9ydGFudCIKICogICAtLS0KICoKICogU3BlY2lhbCBoYW5kbGluZyBmb3IgT2JzaWRpYW4gcmVzZXJ2ZWQgcHJvcGVydGllczoKICogLSAidGFncyIgaXMgbm9ybWFsaXplZCB0byBsb3dlcmNhc2UgKFRhZ3MgLT4gdGFncykKICogLSAiIyIgc3ltYm9scyBhcmUgc3RyaXBwZWQgZnJvbSB0YWcgdmFsdWVzICgjcHJvamVjdCAtPiBwcm9qZWN0KQogKgogKiBDb25maWd1cmUgd2hpY2ggcHJvcGVydGllcyB0byBtaWdyYXRlIGluIHRoZSBzY3JpcHQgc2V0dGluZ3MuCiAqCiAqIFVzYWdlOgogKiAxLiBBZGQgdGhpcyBzY3JpcHQgdG8gYSBRdWlja0FkZCBtYWNybyBhcyBhIFVzZXIgU2NyaXB0CiAqIDIuIENvbmZpZ3VyZSB0aGUgc2NyaXB0IHNldHRpbmdzIHRvIHNwZWNpZnkgd2hpY2ggcHJvcGVydGllcyB0byBtaWdyYXRlCiAqIDMuIE9wZW4gYSBub3RlIHdpdGggaW5saW5lIERhdGF2aWV3IHByb3BlcnRpZXMKICogNC4gUnVuIHRoZSBtYWNybyB0byBtaWdyYXRlIHByb3BlcnRpZXMgdG8gZnJvbnRtYXR0ZXIKICovCgptb2R1bGUuZXhwb3J0cyA9IHsKICAgIGVudHJ5OiBzdGFydCwKICAgIHNldHRpbmdzOiB7CiAgICAgICAgbmFtZTogIk1pZ3JhdGUgRGF0YXZpZXcgUHJvcGVydGllcyB0byBGcm9udG1hdHRlciIsCiAgICAgICAgYXV0aG9yOiAiUXVpY2tBZGQiLAogICAgICAgIG9wdGlvbnM6IHsKICAgICAgICAgICAgIk1pZ3JhdGUgQWxsIFByb3BlcnRpZXMiOiB7CiAgICAgICAgICAgICAgICB0eXBlOiAidG9nZ2xlIiwKICAgICAgICAgICAgICAgIGRlZmF1bHRWYWx1ZTogZmFsc2UsCiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbjogIk1pZ3JhdGUgYWxsIGlubGluZSBwcm9wZXJ0aWVzIChpZ25vcmVzIHRoZSBwcm9wZXJ0eSBsaXN0IGJlbG93KSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgIlByb3BlcnRpZXMgdG8gTWlncmF0ZSI6IHsKICAgICAgICAgICAgICAgIHR5cGU6ICJ0ZXh0IiwKICAgICAgICAgICAgICAgIGRlZmF1bHRWYWx1ZTogIlJlZmVyZW5jZSwgUmVsYXRlZCIsCiAgICAgICAgICAgICAgICBwbGFjZWhvbGRlcjogIlByb3BlcnR5MSwgUHJvcGVydHkyLCBQcm9wZXJ0eTMiLAogICAgICAgICAgICAgICAgZGVzY3JpcHRpb246ICJDb21tYS1zZXBhcmF0ZWQgbGlzdCBvZiBwcm9wZXJ0eSBuYW1lcyB0byBtaWdyYXRlIChjYXNlLWluc2Vuc2l0aXZlKS4gTGVhdmUgZW1wdHkgd2l0aCB0b2dnbGUgb2ZmIHRvIHVzZSBkZWZhdWx0cy4iCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICB9Cn07Cgphc3luYyBmdW5jdGlvbiBzdGFydChwYXJhbXMsIHNldHRpbmdzKSB7CiAgICBjb25zdCB7IGFwcCwgb2JzaWRpYW4gfSA9IHBhcmFtczsKCiAgICAvLyBEZXRlcm1pbmUgd2hpY2ggcHJvcGVydGllcyB0byBtaWdyYXRlCiAgICBsZXQgcHJvcGVydGllc1RvTWlncmF0ZSA9IFtdOwogICAgY29uc3QgbWlncmF0ZUFsbCA9IHNldHRpbmdzWyJNaWdyYXRlIEFsbCBQcm9wZXJ0aWVzIl07CgogICAgaWYgKCFtaWdyYXRlQWxsKSB7CiAgICAgICAgY29uc3QgcHJvcGVydGllc0lucHV0ID0gc2V0dGluZ3NbIlByb3BlcnRpZXMgdG8gTWlncmF0ZSJdOwogICAgICAgIGlmIChwcm9wZXJ0aWVzSW5wdXQgJiYgcHJvcGVydGllc0lucHV0LnRyaW0oKSkgewogICAgICAgICAgICAvLyBQYXJzZSBjb21tYS1zZXBhcmF0ZWQgcHJvcGVydHkgbmFtZXMKICAgICAgICAgICAgcHJvcGVydGllc1RvTWlncmF0ZSA9IHByb3BlcnRpZXNJbnB1dAogICAgICAgICAgICAgICAgLnNwbGl0KCcsJykKICAgICAgICAgICAgICAgIC5tYXAocCA9PiBwLnRyaW0oKS50b0xvd2VyQ2FzZSgpKQogICAgICAgICAgICAgICAgLmZpbHRlcihwID0+IHAubGVuZ3RoID4gMCk7CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgLy8gRGVmYXVsdCB0byBSZWZlcmVuY2UgYW5kIFJlbGF0ZWQgaWYgbm90aGluZyBzcGVjaWZpZWQKICAgICAgICAgICAgcHJvcGVydGllc1RvTWlncmF0ZSA9IFsncmVmZXJlbmNlJywgJ3JlbGF0ZWQnXTsKICAgICAgICB9CiAgICB9CgogICAgLy8gR2V0IHRoZSBhY3RpdmUgZmlsZQogICAgY29uc3QgYWN0aXZlRmlsZSA9IGFwcC53b3Jrc3BhY2UuZ2V0QWN0aXZlRmlsZSgpOwogICAgaWYgKCFhY3RpdmVGaWxlKSB7CiAgICAgICAgbmV3IG9ic2lkaWFuLk5vdGljZSgiTm8gYWN0aXZlIGZpbGUuIFBsZWFzZSBvcGVuIGEgZmlsZSBmaXJzdC4iKTsKICAgICAgICByZXR1cm47CiAgICB9CgogICAgdHJ5IHsKICAgICAgICAvLyBSZWFkIHRoZSBmaWxlIGNvbnRlbnQKICAgICAgICBjb25zdCBjb250ZW50ID0gYXdhaXQgYXBwLnZhdWx0LnJlYWQoYWN0aXZlRmlsZSk7CgogICAgICAgIC8vIFBhcnNlIGlubGluZSBmaWVsZHMKICAgICAgICBjb25zdCB7IGZpZWxkcywgY2xlYW5lZENvbnRlbnQgfSA9IHBhcnNlSW5saW5lRmllbGRzV2l0aFdpa2lsaW5rcyhjb250ZW50LCBwcm9wZXJ0aWVzVG9NaWdyYXRlLCBtaWdyYXRlQWxsKTsKCiAgICAgICAgaWYgKGZpZWxkcy5zaXplID09PSAwKSB7CiAgICAgICAgICAgIGNvbnN0IG1lc3NhZ2UgPSBtaWdyYXRlQWxsCiAgICAgICAgICAgICAgICA/ICJObyBpbmxpbmUgZGF0YXZpZXcgcHJvcGVydGllcyBmb3VuZCBpbiB0aGlzIGZpbGUuIgogICAgICAgICAgICAgICAgOiBgTm8gJHtwcm9wZXJ0aWVzVG9NaWdyYXRlLmpvaW4oJywgJyl9IHByb3BlcnRpZXMgZm91bmQgaW4gdGhpcyBmaWxlLmA7CiAgICAgICAgICAgIG5ldyBvYnNpZGlhbi5Ob3RpY2UobWVzc2FnZSk7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICB9CgogICAgICAgIC8vIFVwZGF0ZSBmcm9udG1hdHRlciBBTkQgY29udGVudCBpbiBhIHNpbmdsZSBvcGVyYXRpb24KICAgICAgICAvLyBUaGlzIHByZXZlbnRzIHJhY2UgY29uZGl0aW9ucyBmcm9tIHByb2Nlc3NGcm9udE1hdHRlciArIHZhdWx0Lm1vZGlmeQogICAgICAgIGF3YWl0IGFwcC5maWxlTWFuYWdlci5wcm9jZXNzRnJvbnRNYXR0ZXIoYWN0aXZlRmlsZSwgKGZyb250bWF0dGVyKSA9PiB7CiAgICAgICAgICAgIGZpZWxkcy5mb3JFYWNoKCh2YWx1ZXMsIGZpZWxkTmFtZSkgPT4gewogICAgICAgICAgICAgICAgLy8gQ29udmVydCBTZXQgdG8gYXJyYXkKICAgICAgICAgICAgICAgIGxldCB2YWx1ZXNBcnJheSA9IEFycmF5LmZyb20odmFsdWVzKTsKCiAgICAgICAgICAgICAgICAvLyBIYW5kbGUgc3BlY2lhbCBwcm9wZXJ0eSBuYW1lcyAoT2JzaWRpYW4gcmVzZXJ2ZWQgZmllbGRzKQogICAgICAgICAgICAgICAgLy8gInRhZ3MiIG11c3QgYmUgbG93ZXJjYXNlIGFuZCB0YWcgdmFsdWVzIHNob3VsZCBub3QgaGF2ZSAiIyIgcHJlZml4CiAgICAgICAgICAgICAgICBjb25zdCBpc1RhZ3MgPSBmaWVsZE5hbWUudG9Mb3dlckNhc2UoKSA9PT0gJ3RhZ3MnOwogICAgICAgICAgICAgICAgY29uc3Qgbm9ybWFsaXplZEZpZWxkTmFtZSA9IGlzVGFncyA/ICd0YWdzJyA6IGZpZWxkTmFtZTsKCiAgICAgICAgICAgICAgICBpZiAoaXNUYWdzKSB7CiAgICAgICAgICAgICAgICAgICAgLy8gU3RyaXAgIiMiIGZyb20gdGFnIHZhbHVlcwogICAgICAgICAgICAgICAgICAgIHZhbHVlc0FycmF5ID0gdmFsdWVzQXJyYXkubWFwKHRhZyA9PiB7CiAgICAgICAgICAgICAgICAgICAgICAgIC8vIFJlbW92ZSBsZWFkaW5nICIjIiBpZiBwcmVzZW50CiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiB0YWcuc3RhcnRzV2l0aCgnIycpID8gdGFnLnN1YnN0cmluZygxKSA6IHRhZzsKICAgICAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICAvLyBGcm9udG1hdHRlciBwcm9wZXJ0eSBuYW1lcyBiZWhhdmUgY2FzZS1pbnNlbnNpdGl2ZWx5IGluIE9ic2lkaWFuLAogICAgICAgICAgICAgICAgLy8gc28gY29sbGVjdCBFVkVSWSBrZXkgdGhhdCBtYXRjaGVzIGNhc2UtaW5zZW5zaXRpdmVseS4gQSBoYW5kLWVkaXRlZAogICAgICAgICAgICAgICAgLy8gb3Igc3luY2VkIG5vdGUgKG9yIGEgbm90ZSBtaWdyYXRlZCBieSBhbiBvbGRlciB2ZXJzaW9uIG9mIHRoaXMKICAgICAgICAgICAgICAgIC8vIHNjcmlwdCkgY2FuIGhvbGQgbW9yZSB0aGFuIG9uZSBjYXNlIHZhcmlhbnQgLSBlLmcuIGJvdGggIlJlZmVyZW5jZSIKICAgICAgICAgICAgICAgIC8vIGFuZCAicmVmZXJlbmNlIiwgb3IgYm90aCAiVGFncyIgYW5kICJ0YWdzIiAtIGFuZCB3ZSBtdXN0IG1lcmdlIHRoZW0KICAgICAgICAgICAgICAgIC8vIGFsbCBpbnRvIG9uZSBrZXkgcmF0aGVyIHRoYW4gc3RyYW5kaW5nIChvciBkcm9wcGluZykgYSB2YXJpYW50LgogICAgICAgICAgICAgICAgY29uc3QgbWF0Y2hpbmdLZXlzID0gT2JqZWN0LmtleXMoZnJvbnRtYXR0ZXIpLmZpbHRlcigKICAgICAgICAgICAgICAgICAgICBrZXkgPT4ga2V5LnRvTG93ZXJDYXNlKCkgPT09IG5vcm1hbGl6ZWRGaWVsZE5hbWUudG9Mb3dlckNhc2UoKQogICAgICAgICAgICAgICAgKTsKCiAgICAgICAgICAgICAgICAvLyBUaGUgcmVzZXJ2ZWQgInRhZ3MiIHByb3BlcnR5IG11c3QgYmUgc3RvcmVkIGxvd2VyY2FzZSwgc28gYWx3YXlzCiAgICAgICAgICAgICAgICAvLyB0YXJnZXQgInRhZ3MiOyBvdGhlciBwcm9wZXJ0aWVzIHJldXNlIHRoZSBmaXJzdCBleGlzdGluZyBrZXkncyBjYXNlCiAgICAgICAgICAgICAgICAvLyAob3IgdGhlIGZpZWxkIG5hbWUpLgogICAgICAgICAgICAgICAgY29uc3QgdGFyZ2V0S2V5ID0gaXNUYWdzID8gJ3RhZ3MnIDogKG1hdGNoaW5nS2V5c1swXSA/PyBub3JtYWxpemVkRmllbGROYW1lKTsKCiAgICAgICAgICAgICAgICAvLyBHYXRoZXIgdGhlIGV4aXN0aW5nIHZhbHVlcyBmcm9tIGV2ZXJ5IGNhc2UgdmFyaWFudCwgdGhlbiBkcm9wIHRoZQogICAgICAgICAgICAgICAgLy8gbm9uLXRhcmdldCBrZXlzIHNvIG5vIGR1cGxpY2F0ZSBzdXJ2aXZlcy4KICAgICAgICAgICAgICAgIGNvbnN0IGV4aXN0aW5nVmFsdWVzID0gW107CiAgICAgICAgICAgICAgICBmb3IgKGNvbnN0IGtleSBvZiBtYXRjaGluZ0tleXMpIHsKICAgICAgICAgICAgICAgICAgICBjb25zdCB2YWx1ZSA9IGZyb250bWF0dGVyW2tleV07CiAgICAgICAgICAgICAgICAgICAgaWYgKHZhbHVlICE9PSB1bmRlZmluZWQgJiYgdmFsdWUgIT09IG51bGwpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSBleGlzdGluZ1ZhbHVlcy5wdXNoKC4uLnZhbHVlKTsKICAgICAgICAgICAgICAgICAgICAgICAgZWxzZSBleGlzdGluZ1ZhbHVlcy5wdXNoKHZhbHVlKTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgaWYgKGtleSAhPT0gdGFyZ2V0S2V5KSBkZWxldGUgZnJvbnRtYXR0ZXJba2V5XTsKICAgICAgICAgICAgICAgIH0KCiAgICAgICAgICAgICAgICBpZiAoZXhpc3RpbmdWYWx1ZXMubGVuZ3RoID4gMCkgewogICAgICAgICAgICAgICAgICAgIC8vIENvbWJpbmUsIHRoZW4gZGVkdXBsaWNhdGUgYnkgc3RyaW5nIGZvcm0gV0lUSE9VVCBjb2VyY2luZyB0aGUKICAgICAgICAgICAgICAgICAgICAvLyBzdG9yZWQgdmFsdWVzIC0ga2VlcCB0aGUgb3JpZ2luYWwgdHlwZWQgdmFsdWVzIChudW1iZXJzLAogICAgICAgICAgICAgICAgICAgIC8vIGJvb2xlYW5zKSBzbyBhbiBleGlzdGluZyAicmF0aW5nOiA1IiBzdGF5cyBhIG51bWJlci4KICAgICAgICAgICAgICAgICAgICBjb25zdCBjb21iaW5lZCA9IFsuLi5leGlzdGluZ1ZhbHVlcywgLi4udmFsdWVzQXJyYXldOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IHNlZW4gPSBuZXcgU2V0KCk7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgZGVkdXBsaWNhdGVkID0gW107CiAgICAgICAgICAgICAgICAgICAgZm9yIChjb25zdCB2YWx1ZSBvZiBjb21iaW5lZCkgewogICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBrZXkgPSBTdHJpbmcodmFsdWUpOwogICAgICAgICAgICAgICAgICAgICAgICBpZiAoIXNlZW4uaGFzKGtleSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZW4uYWRkKGtleSk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWR1cGxpY2F0ZWQucHVzaCh2YWx1ZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgIC8vIFN0b3JlIGFzIHN0cmluZyBvciBhcnJheSBkZXBlbmRpbmcgb24gY291bnQKICAgICAgICAgICAgICAgICAgICBmcm9udG1hdHRlclt0YXJnZXRLZXldID0gZGVkdXBsaWNhdGVkLmxlbmd0aCA9PT0gMQogICAgICAgICAgICAgICAgICAgICAgICA/IGRlZHVwbGljYXRlZFswXQogICAgICAgICAgICAgICAgICAgICAgICA6IGRlZHVwbGljYXRlZDsKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgLy8gUHJvcGVydHkgZG9lc24ndCBleGlzdCAtIGFkZCBpdAogICAgICAgICAgICAgICAgICAgIGlmICh2YWx1ZXNBcnJheS5sZW5ndGggPT09IDEpIHsKICAgICAgICAgICAgICAgICAgICAgICAgZnJvbnRtYXR0ZXJbdGFyZ2V0S2V5XSA9IHZhbHVlc0FycmF5WzBdOwogICAgICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAodmFsdWVzQXJyYXkubGVuZ3RoID4gMSkgewogICAgICAgICAgICAgICAgICAgICAgICBmcm9udG1hdHRlclt0YXJnZXRLZXldID0gdmFsdWVzQXJyYXk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9KTsKICAgICAgICB9KTsKCiAgICAgICAgLy8gTm93IHN0cmlwIHRoZSBtaWdyYXRlZCBpbmxpbmUgcHJvcGVydGllcyBmcm9tIHRoZSBib2R5LiBVc2UgdmF1bHQucHJvY2VzcwogICAgICAgIC8vIHNvIHRoZSByZWFkICsgcmV3cml0ZSBpcyBhIHNpbmdsZSBhdG9taWMgb3BlcmF0aW9uIChwcm9jZXNzRnJvbnRNYXR0ZXIKICAgICAgICAvLyBhbHJlYWR5IHBlcnNpc3RlZCB0aGUgZnJvbnRtYXR0ZXI7IGEgc2VwYXJhdGUgcmVhZC10aGVuLW1vZGlmeSBjb3VsZAogICAgICAgIC8vIGNsb2JiZXIgYSBjb25jdXJyZW50IGVkaXQgb3Igc3luYyB0aGF0IGxhbmRlZCBpbiBiZXR3ZWVuKS4KICAgICAgICBhd2FpdCBhcHAudmF1bHQucHJvY2VzcyhhY3RpdmVGaWxlLCAoZGF0YSkgPT4gewogICAgICAgICAgICBjb25zdCB7IGNsZWFuZWRDb250ZW50IH0gPSBwYXJzZUlubGluZUZpZWxkc1dpdGhXaWtpbGlua3MoZGF0YSwgcHJvcGVydGllc1RvTWlncmF0ZSwgbWlncmF0ZUFsbCk7CiAgICAgICAgICAgIHJldHVybiBjbGVhbmVkQ29udGVudDsKICAgICAgICB9KTsKCiAgICAgICAgLy8gU2hvdyBzdWNjZXNzIG1lc3NhZ2UKICAgICAgICBjb25zdCBmaWVsZE5hbWVzID0gQXJyYXkuZnJvbShmaWVsZHMua2V5cygpKS5qb2luKCIsICIpOwogICAgICAgIG5ldyBvYnNpZGlhbi5Ob3RpY2UoCiAgICAgICAgICAgIGBNaWdyYXRlZCAke2ZpZWxkcy5zaXplfSBwcm9wZXJ0eS9wcm9wZXJ0aWVzIHRvIGZyb250bWF0dGVyOiAke2ZpZWxkTmFtZXN9YCwKICAgICAgICAgICAgNTAwMAogICAgICAgICk7CgogICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgICBjb25zb2xlLmVycm9yKCJFcnJvciBtaWdyYXRpbmcgZGF0YXZpZXcgcHJvcGVydGllczoiLCBlcnJvcik7CiAgICAgICAgbmV3IG9ic2lkaWFuLk5vdGljZShgRXJyb3I6ICR7ZXJyb3IubWVzc2FnZX1gLCA1MDAwKTsKICAgICAgICB0aHJvdyBlcnJvcjsKICAgIH0KfQoKLyoqCiAqIFBhcnNlIGlubGluZSBmaWVsZHMgd2l0aCBzcGVjaWFsIGhhbmRsaW5nIGZvciB3aWtpbGlua3MgaW4gY29tbWEtc2VwYXJhdGVkIHZhbHVlcwogKiBAcGFyYW0ge3N0cmluZ30gY29udGVudCAtIFRoZSBmaWxlIGNvbnRlbnQKICogQHBhcmFtIHtzdHJpbmdbXX0gYWxsb3dlZFByb3BlcnRpZXMgLSBBcnJheSBvZiBwcm9wZXJ0eSBuYW1lcyB0byBtaWdyYXRlIChjYXNlLWluc2Vuc2l0aXZlKQogKiBAcGFyYW0ge2Jvb2xlYW59IG1pZ3JhdGVBbGwgLSBJZiB0cnVlLCBtaWdyYXRlIGFsbCBwcm9wZXJ0aWVzIHJlZ2FyZGxlc3Mgb2YgYWxsb3dlZFByb3BlcnRpZXMKICogQHJldHVybnMge3tmaWVsZHM6IE1hcDxzdHJpbmcsIFNldDxzdHJpbmc+PiwgY2xlYW5lZENvbnRlbnQ6IHN0cmluZ319CiAqLwpmdW5jdGlvbiBwYXJzZUlubGluZUZpZWxkc1dpdGhXaWtpbGlua3MoY29udGVudCwgYWxsb3dlZFByb3BlcnRpZXMgPSBbXSwgbWlncmF0ZUFsbCA9IGZhbHNlKSB7CiAgICBjb25zdCBmaWVsZHMgPSBuZXcgTWFwKCk7CiAgICBjb25zdCBhbGxvd2VkUHJvcGVydGllc0xvd2VyID0gYWxsb3dlZFByb3BlcnRpZXMubWFwKHAgPT4gcC50b0xvd2VyQ2FzZSgpKTsKCiAgICAvLyBTcGxpdCBjb250ZW50IGludG8gZnJvbnRtYXR0ZXIgYW5kIGJvZHkuIEZyb250bWF0dGVyIGlzIHByZXNlcnZlZCB2ZXJiYXRpbQogICAgLy8gYW5kIGlzIG5ldmVyIHNjYW5uZWQgZm9yIGZpZWxkcyBvciBjb2xsYXBzZWQuCiAgICBjb25zdCB7IGZyb250bWF0dGVyRW5kLCBoYXNFeGlzdGluZ0Zyb250bWF0dGVyLCBmcm9udG1hdHRlclRleHQgfSA9IGZpbmRGcm9udG1hdHRlckVuZChjb250ZW50KTsKICAgIGNvbnN0IGJvZHlDb250ZW50ID0gaGFzRXhpc3RpbmdGcm9udG1hdHRlciA/IGNvbnRlbnQuc2xpY2UoZnJvbnRtYXR0ZXJFbmQpIDogY29udGVudDsKCiAgICAvLyBQcm9jZXNzIGVhY2ggbGluZSBpbiB0aGUgYm9keS4KICAgIGNvbnN0IGJvZHlMaW5lcyA9IGJvZHlDb250ZW50LnNwbGl0KCdcbicpOwogICAgY29uc3QgY2xlYW5lZEJvZHlMaW5lcyA9IFtdOwogICAgLy8gUGFyYWxsZWwgdG8gY2xlYW5lZEJvZHlMaW5lczogYSAicHJvdGVjdGVkIiBsaW5lIChhIGZlbmNlIGRlbGltaXRlciBvciBhbnkKICAgIC8vIGxpbmUgaW5zaWRlIGEgZmVuY2VkIGNvZGUgYmxvY2spIGlzIGtlcHQgdmVyYmF0aW0gYW5kIG5ldmVyIGNvbGxhcHNlZC4KICAgIGNvbnN0IHByb3RlY3RlZEZsYWdzID0gW107CgogICAgLy8gVHJhY2sgdGhlIGN1cnJlbnRseSBvcGVuIGZlbmNlZCBjb2RlIGJsb2NrLCBpZiBhbnkuIGZlbmNlRGVwdGggaXMgdGhlCiAgICAvLyBibG9ja3F1b3RlIG5lc3RpbmcgbGV2ZWwgdGhlIGZlbmNlIG9wZW5lZCBhdCAoMCA9IHRvcCBsZXZlbCksIHNvIGEgZmVuY2UKICAgIC8vIGluc2lkZSAiPiAuLi4iIGlzIGNsb3NlZCBieSBhIGNsb3NlciBhdCB0aGUgc2FtZSBxdW90ZSBkZXB0aC4gZmVuY2VMaXN0SW5kZW50CiAgICAvLyBpcyB0aGUgbGlzdC1pdGVtIGNvbnRlbnQgaW5kZW50IHRoZSBmZW5jZSBvcGVuZWQgYXQgKDAgPSBub3QgaW4gYSBsaXN0KSwgc28gYQogICAgLy8gZmVuY2Ugb3BlbmVkIGJ5ICItIGBgYCIgaGFzIGl0cyBib2R5IGFuZCBjbG9zaW5nIGZlbmNlIHRyYWNrZWQgYXQgdGhhdCBpbmRlbnQuCiAgICBsZXQgZmVuY2VDaGFyID0gbnVsbDsgLy8gJ2AnIG9yICd+JwogICAgbGV0IGZlbmNlTGVuID0gMDsKICAgIGxldCBmZW5jZURlcHRoID0gMDsKICAgIGxldCBmZW5jZUxpc3RJbmRlbnQgPSAwOwoKICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYm9keUxpbmVzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgY29uc3QgbGluZSA9IGJvZHlMaW5lc1tpXTsKCiAgICAgICAgaWYgKGZlbmNlQ2hhciAhPT0gbnVsbCkgewogICAgICAgICAgICAvLyBJbnNpZGUgYSBmZW5jZWQgY29kZSBibG9jay4gU3RyaXAgZXhhY3RseSB0aGUgZmVuY2UncyBibG9ja3F1b3RlCiAgICAgICAgICAgIC8vIGNvbnRhaW5lcjsgaWYgdGhpcyBsaW5lIGNhcnJpZXMgZmV3ZXIgbWFya2VycywgdGhlIGJsb2NrcXVvdGUgKGFuZAogICAgICAgICAgICAvLyB0aGVyZWZvcmUgdGhlIGZlbmNlZCBibG9jaykgaGFzIGVuZGVkIC0gcmVwcm9jZXNzIHRoZSBsaW5lIGJlbG93IGFzCiAgICAgICAgICAgIC8vIG5vbi1mZW5jZSBzbyBhIHJlYWwgZmllbGQgYWZ0ZXIgdGhlIGJsb2NrIHN0aWxsIG1pZ3JhdGVzLgogICAgICAgICAgICBjb25zdCB7IGRlcHRoLCBjb250ZW50IH0gPSBzdHJpcEJsb2NrcXVvdGUobGluZSwgZmVuY2VEZXB0aCk7CiAgICAgICAgICAgIGlmIChkZXB0aCA+PSBmZW5jZURlcHRoKSB7CiAgICAgICAgICAgICAgICAvLyBJZiB0aGUgZmVuY2Ugb3BlbmVkIGluc2lkZSBhIGxpc3QgaXRlbSwgaXRzIGJvZHkgYW5kIGNsb3NpbmcgZmVuY2UKICAgICAgICAgICAgICAgIC8vIHNpdCBhdCB0aGUgbGlzdCdzIGNvbnRlbnQgaW5kZW50IC0gc3RyaXAgdGhhdCBiZWZvcmUgbWF0Y2hpbmcgdGhlCiAgICAgICAgICAgICAgICAvLyBjbG9zZXIuIEZvciBhIG5vbi1saXN0IGZlbmNlIGZlbmNlTGlzdEluZGVudCBpcyAwLCBzbyB0aGlzIGlzIGEKICAgICAgICAgICAgICAgIC8vIG5vLW9wIGFuZCBiZWhhdmlvdXIgaXMgaWRlbnRpY2FsIHRvIGEgcGxhaW4gdG9wLWxldmVsIGZlbmNlLgogICAgICAgICAgICAgICAgY29uc3QgeyByZXN0LCBpbmRlbnQgfSA9IHN0cmlwTGVhZGluZ1NwYWNlcyhjb250ZW50LCBmZW5jZUxpc3RJbmRlbnQpOwogICAgICAgICAgICAgICAgaWYgKGZlbmNlTGlzdEluZGVudCA+IDAgJiYgaW5kZW50IDwgZmVuY2VMaXN0SW5kZW50ICYmIHJlc3QudHJpbSgpICE9PSAnJykgewogICAgICAgICAgICAgICAgICAgIC8vIEEgbm9uLWJsYW5rIGxpbmUgaW5kZW50ZWQgTEVTUyB0aGFuIHRoZSBsaXN0IGNvbnRlbnQgaGFzIGxlZnQKICAgICAgICAgICAgICAgICAgICAvLyB0aGUgbGlzdCBpdGVtLCBzbyBwZXIgQ29tbW9uTWFyayB0aGUgKHVuY2xvc2VkKSBmZW5jZSBlbmRzCiAgICAgICAgICAgICAgICAgICAgLy8gaGVyZS4gRXhpdCBhbmQgZmFsbCB0aHJvdWdoIHRvIHJlcHJvY2VzcyB0aGUgbGluZSAtIGl0IG1heSBiZSBhCiAgICAgICAgICAgICAgICAgICAgLy8gcmVhbCBmaWVsZCBvciBhIE5FVyB0b3AtbGV2ZWwgZmVuY2UgKGUuZy4gYSBiYXJlIGBgYCB0aGF0IHdvdWxkCiAgICAgICAgICAgICAgICAgICAgLy8gb3RoZXJ3aXNlIGJlIG1pc3Rha2VuIGZvciB0aGlzIGZlbmNlJ3MgY2xvc2VyKS4gQmxhbmsgbGluZXMgc3RheQogICAgICAgICAgICAgICAgICAgIC8vIGluIHRoZSBibG9jay4KICAgICAgICAgICAgICAgICAgICBmZW5jZUNoYXIgPSBudWxsOwogICAgICAgICAgICAgICAgICAgIGZlbmNlTGVuID0gMDsKICAgICAgICAgICAgICAgICAgICBmZW5jZURlcHRoID0gMDsKICAgICAgICAgICAgICAgICAgICBmZW5jZUxpc3RJbmRlbnQgPSAwOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAvLyBTdGlsbCBpbnNpZGUgdGhlIGJsb2NrOiBwcmVzZXJ2ZSB2ZXJiYXRpbSwgbmV2ZXIgbWlncmF0ZS4gT25seSBhCiAgICAgICAgICAgICAgICAgICAgLy8gbWF0Y2hpbmcgY2xvc2luZyBmZW5jZSAoc2FtZSBtYXJrZXIsIGxvbmcgZW5vdWdoLCBubyBpbmZvIHN0cmluZykKICAgICAgICAgICAgICAgICAgICAvLyBhdCB0aGUgc2FtZSBxdW90ZSArIGxpc3QgZGVwdGggZW5kcyBpdC4KICAgICAgICAgICAgICAgICAgICBpZiAoaXNDbG9zaW5nRmVuY2UocmVzdCwgZmVuY2VDaGFyLCBmZW5jZUxlbikpIHsKICAgICAgICAgICAgICAgICAgICAgICAgZmVuY2VDaGFyID0gbnVsbDsKICAgICAgICAgICAgICAgICAgICAgICAgZmVuY2VMZW4gPSAwOwogICAgICAgICAgICAgICAgICAgICAgICBmZW5jZURlcHRoID0gMDsKICAgICAgICAgICAgICAgICAgICAgICAgZmVuY2VMaXN0SW5kZW50ID0gMDsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgY2xlYW5lZEJvZHlMaW5lcy5wdXNoKGxpbmUpOwogICAgICAgICAgICAgICAgICAgIHByb3RlY3RlZEZsYWdzLnB1c2godHJ1ZSk7CiAgICAgICAgICAgICAgICAgICAgY29udGludWU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAvLyBCbG9ja3F1b3RlIGNvbnRhaW5lciBleGl0ZWQ6IGVuZCB0aGUgZmVuY2UgYW5kIGZhbGwgdGhyb3VnaCBzbyB0aGlzCiAgICAgICAgICAgICAgICAvLyBsaW5lIGlzIGhhbmRsZWQgbm9ybWFsbHkuCiAgICAgICAgICAgICAgICBmZW5jZUNoYXIgPSBudWxsOwogICAgICAgICAgICAgICAgZmVuY2VMZW4gPSAwOwogICAgICAgICAgICAgICAgZmVuY2VEZXB0aCA9IDA7CiAgICAgICAgICAgICAgICBmZW5jZUxpc3RJbmRlbnQgPSAwOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICAvLyBOb3QgaW5zaWRlIGEgY29kZSBibG9jazogYW4gb3BlbmluZyBmZW5jZSAodG9wLWxldmVsLCBibG9ja3F1b3RlZCwgb3IgYXMKICAgICAgICAvLyB0aGUgZmlyc3QgY29udGVudCBvZiBhIGxpc3QgaXRlbSkgc3RhcnRzIG9uZS4gRGV0ZWN0IGl0IG9uIHRoZQogICAgICAgIC8vIGJsb2NrcXVvdGUtc3RyaXBwZWQgY29udGVudCwgYWZ0ZXIgb3B0aW9uYWxseSBzdHJpcHBpbmcgYSBsaXN0IG1hcmtlciwgYW5kCiAgICAgICAgLy8gcmVtZW1iZXIgdGhlIHF1b3RlIGRlcHRoICsgbGlzdCBpbmRlbnQgc28gdGhlIG1hdGNoaW5nIGNsb3NlciBpcyBmb3VuZC4KICAgICAgICB7CiAgICAgICAgICAgIGNvbnN0IHsgZGVwdGgsIGNvbnRlbnQgfSA9IHN0cmlwQmxvY2txdW90ZShsaW5lLCBJbmZpbml0eSk7CiAgICAgICAgICAgIGNvbnN0IHsgcmVzdCwgaW5kZW50IH0gPSBzdHJpcExpc3RNYXJrZXIoY29udGVudCk7CiAgICAgICAgICAgIGNvbnN0IGZlbmNlID0gb3BlbmluZ0ZlbmNlKHJlc3QpOwogICAgICAgICAgICBpZiAoZmVuY2UpIHsKICAgICAgICAgICAgICAgIGZlbmNlQ2hhciA9IGZlbmNlLmNoYXI7CiAgICAgICAgICAgICAgICBmZW5jZUxlbiA9IGZlbmNlLmxlbjsKICAgICAgICAgICAgICAgIGZlbmNlRGVwdGggPSBkZXB0aDsKICAgICAgICAgICAgICAgIGZlbmNlTGlzdEluZGVudCA9IGluZGVudDsKICAgICAgICAgICAgICAgIGNsZWFuZWRCb2R5TGluZXMucHVzaChsaW5lKTsKICAgICAgICAgICAgICAgIHByb3RlY3RlZEZsYWdzLnB1c2godHJ1ZSk7CiAgICAgICAgICAgICAgICBjb250aW51ZTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLy8gT3V0c2lkZSBhbnkgY29kZSBmZW5jZTogZGVjaWRlIHdoZXRoZXIgdGhpcyBsaW5lIGlzIGEgbWlncmF0YWJsZSBmaWVsZC4KICAgICAgICBjb25zdCB0cmltbWVkTGluZSA9IGxpbmUudHJpbSgpOwogICAgICAgIGxldCBpc1Byb3BlcnR5VG9SZW1vdmUgPSBmYWxzZTsKCiAgICAgICAgaWYgKAogICAgICAgICAgICB0cmltbWVkTGluZS5pbmNsdWRlcygnOjonKSAmJgogICAgICAgICAgICAvLyBTa2lwIHRhc2sgY2hlY2tib3hlcyAoZS5nLiAiLSBbIF0gZG86OiB0aGluZyIpLgogICAgICAgICAgICAhL15bLSorXVxzK1xbW3hYIF1cXS8udGVzdCh0cmltbWVkTGluZSkKICAgICAgICApIHsKICAgICAgICAgICAgLy8gTWFzayBpbmxpbmUgY29kZSBzcGFucyBzbyBhICI6OiIgaW5zaWRlIGNvZGUgaXMgbm90IHJlYWQgYXMgYSBmaWVsZAogICAgICAgICAgICAvLyBzZXBhcmF0b3IuIE1hc2tpbmcgcHJlc2VydmVzIGxlbmd0aCBzbyBwb3NpdGlvbnMgc3RheSBhbGlnbmVkIHdpdGgKICAgICAgICAgICAgLy8gdGhlIG9yaWdpbmFsIGxpbmUuCiAgICAgICAgICAgIGNvbnN0IG1hc2tlZExpbmUgPSBtYXNrSW5saW5lQ29kZSh0cmltbWVkTGluZSk7CiAgICAgICAgICAgIGNvbnN0IGZpZWxkTWF0Y2ggPSBtYXNrZWRMaW5lLm1hdGNoKC9eKFteOl0rPyk6OiguKikkLyk7CgogICAgICAgICAgICBpZiAoZmllbGRNYXRjaCkgewogICAgICAgICAgICAgICAgY29uc3QgbmFtZUVuZCA9IGZpZWxkTWF0Y2hbMV0ubGVuZ3RoOwogICAgICAgICAgICAgICAgLy8gVGhlIGZpZWxkIG5hbWUgbXVzdCBiZSBmcmVlIG9mIGlubGluZSBjb2RlLiBJZiBtYXNraW5nIGNoYW5nZWQKICAgICAgICAgICAgICAgIC8vIHRoZSBuYW1lIHJlZ2lvbiwgdGhlIGxpbmUgaXMgbWlkLWNvbnRlbnQgKGUuZy4gYSBjb2RlIHNwYW4gc2l0cwogICAgICAgICAgICAgICAgLy8gYmVmb3JlIHRoZSBjb2xvbiksIG5vdCBhIGNsZWFuICJuYW1lOjogdmFsdWUiIGZpZWxkIC0gbGVhdmUgaXQKICAgICAgICAgICAgICAgIC8vIHZlcmJhdGltIHNvIHdlIG5ldmVyIGRlbGV0ZSB0aGUgY29kZSBzcGFuIHRoYXQgcHJlY2VkZXMgaXQuCiAgICAgICAgICAgICAgICBjb25zdCBuYW1lSXNDbGVhbiA9CiAgICAgICAgICAgICAgICAgICAgbWFza2VkTGluZS5zbGljZSgwLCBuYW1lRW5kKSA9PT0gdHJpbW1lZExpbmUuc2xpY2UoMCwgbmFtZUVuZCk7CgogICAgICAgICAgICAgICAgaWYgKG5hbWVJc0NsZWFuKSB7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgZmllbGROYW1lID0gdHJpbW1lZExpbmUuc2xpY2UoMCwgbmFtZUVuZCkudHJpbSgpOwogICAgICAgICAgICAgICAgICAgIC8vIFNraXAgdGhlICI6OiIgc2VwYXJhdG9yOyByZWNvdmVyIHRoZSByZWFsIHZhbHVlIChpbmNsdWRpbmcgYW55CiAgICAgICAgICAgICAgICAgICAgLy8gYmFja3RpY2tzKSBmcm9tIHRoZSBvcmlnaW5hbCBsaW5lLgogICAgICAgICAgICAgICAgICAgIGNvbnN0IGZpZWxkVmFsdWUgPSB0cmltbWVkTGluZS5zbGljZShuYW1lRW5kICsgMikudHJpbSgpOwoKICAgICAgICAgICAgICAgICAgICBpZiAoZmllbGROYW1lICYmIGZpZWxkVmFsdWUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgZmllbGROYW1lTG93ZXIgPSBmaWVsZE5hbWUudG9Mb3dlckNhc2UoKTsKICAgICAgICAgICAgICAgICAgICAgICAgLy8gYGZyb250bWF0dGVyWydfX3Byb3RvX18nXSA9IHZhbHVlYCBoaXRzIHRoZSBPYmplY3QucHJvdG90eXBlCiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGFjY2Vzc29yIGluc3RlYWQgb2YgY3JlYXRpbmcgYW4gb3duIHByb3BlcnR5LCBzbyB0aGUgdmFsdWUKICAgICAgICAgICAgICAgICAgICAgICAgLy8gd291bGQgYmUgc2lsZW50bHkgZHJvcHBlZCB3aGlsZSB0aGUgYm9keSBsaW5lIGlzIGRlbGV0ZWQgLQogICAgICAgICAgICAgICAgICAgICAgICAvLyBkYXRhIGxvc3MuIExlYXZlIHN1Y2ggYSBsaW5lIGluIHRoZSBib2R5IHZlcmJhdGltIGluc3RlYWQKICAgICAgICAgICAgICAgICAgICAgICAgLy8gKG1pcnJvcnMgdGhlIGNvcmUgcGx1Z2luJ3MgZHVuZGVyLWtleSBndWFyZCwgd2hpY2ggYmxvY2tzCiAgICAgICAgICAgICAgICAgICAgICAgIC8vIF9fcHJvdG9fXyBpbiBhbnkgcG9zaXRpb24pLgogICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBpc1Vud3JpdGFibGVOYW1lID0gZmllbGROYW1lID09PSAnX19wcm90b19fJzsKICAgICAgICAgICAgICAgICAgICAgICAgY29uc3Qgc2hvdWxkTWlncmF0ZSA9ICFpc1Vud3JpdGFibGVOYW1lICYmIChtaWdyYXRlQWxsIHx8CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAoYWxsb3dlZFByb3BlcnRpZXNMb3dlci5sZW5ndGggPiAwICYmIGFsbG93ZWRQcm9wZXJ0aWVzTG93ZXIuaW5jbHVkZXMoZmllbGROYW1lTG93ZXIpKSk7CgogICAgICAgICAgICAgICAgICAgICAgICBpZiAoc2hvdWxkTWlncmF0ZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gVGhpcyBsaW5lIHNob3VsZCBiZSByZW1vdmVkIChtb3ZlZCB0byBmcm9udG1hdHRlcikuCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpc1Byb3BlcnR5VG9SZW1vdmUgPSB0cnVlOwoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghZmllbGRzLmhhcyhmaWVsZE5hbWUpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmllbGRzLnNldChmaWVsZE5hbWUsIG5ldyBTZXQoKSk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gUGFyc2UgdGhlIHZhbHVlIHdpdGggc3BlY2lhbCBoYW5kbGluZyBmb3Igd2lraWxpbmtzLgogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgcGFyc2VkVmFsdWVzID0gcGFyc2VDb21tYVNlcGFyYXRlZFdpdGhXaWtpbGlua3MoZmllbGRWYWx1ZSk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJzZWRWYWx1ZXMuZm9yRWFjaCh2YWx1ZSA9PiBmaWVsZHMuZ2V0KGZpZWxkTmFtZSkuYWRkKHZhbHVlKSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIC8vIEtlZXAgdGhlIGxpbmUgaWYgaXQncyBub3QgYSBwcm9wZXJ0eSB0byByZW1vdmUuCiAgICAgICAgaWYgKCFpc1Byb3BlcnR5VG9SZW1vdmUpIHsKICAgICAgICAgICAgY2xlYW5lZEJvZHlMaW5lcy5wdXNoKGxpbmUpOwogICAgICAgICAgICBwcm90ZWN0ZWRGbGFncy5wdXNoKGZhbHNlKTsKICAgICAgICB9CiAgICB9CgogICAgLy8gQ29sbGFwc2UgcnVucyBvZiAyKyBjb25zZWN1dGl2ZSBibGFuayBsaW5lcyBkb3duIHRvIGEgc2luZ2xlIGJsYW5rIGxpbmUsIGJ1dAogICAgLy8gbmV2ZXIgdG91Y2ggYmxhbmsgbGluZXMgaW5zaWRlIGZlbmNlZCBjb2RlIGJsb2NrcyAodmVyYmF0aW0gZ3VhcmFudGVlKS4gVGhpcwogICAgLy8gYWxzbyBsZWF2ZXMgdGhlIGZyb250bWF0dGVyIHVudG91Y2hlZCwgdW5saWtlIGEgZ2xvYmFsIG5ld2xpbmUgcmVnZXguCiAgICBjb25zdCBjb2xsYXBzZWRMaW5lcyA9IFtdOwogICAgbGV0IGJsYW5rUnVuID0gMDsKICAgIGZvciAobGV0IGkgPSAwOyBpIDwgY2xlYW5lZEJvZHlMaW5lcy5sZW5ndGg7IGkrKykgewogICAgICAgIGNvbnN0IGJvZHlMaW5lID0gY2xlYW5lZEJvZHlMaW5lc1tpXTsKICAgICAgICBpZiAoIXByb3RlY3RlZEZsYWdzW2ldICYmIGJvZHlMaW5lLnRyaW0oKSA9PT0gJycpIHsKICAgICAgICAgICAgYmxhbmtSdW4gKz0gMTsKICAgICAgICAgICAgaWYgKGJsYW5rUnVuIDw9IDEpIHsKICAgICAgICAgICAgICAgIGNvbGxhcHNlZExpbmVzLnB1c2goYm9keUxpbmUpOwogICAgICAgICAgICB9CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgYmxhbmtSdW4gPSAwOwogICAgICAgICAgICBjb2xsYXBzZWRMaW5lcy5wdXNoKGJvZHlMaW5lKTsKICAgICAgICB9CiAgICB9CgogICAgY29uc3QgY2xlYW5lZEJvZHkgPSBjb2xsYXBzZWRMaW5lcy5qb2luKCdcbicpOwogICAgY29uc3QgY2xlYW5lZENvbnRlbnQgPSBoYXNFeGlzdGluZ0Zyb250bWF0dGVyCiAgICAgICAgPyBmcm9udG1hdHRlclRleHQgKyBjbGVhbmVkQm9keQogICAgICAgIDogY2xlYW5lZEJvZHk7CgogICAgcmV0dXJuIHsgZmllbGRzLCBjbGVhbmVkQ29udGVudCB9Owp9CgovKioKICogU3RyaXAgdXAgdG8gYG1heGAgbGVhZGluZyBibG9ja3F1b3RlIG1hcmtlcnMgZnJvbSBhIGxpbmUuIEEgbWFya2VyIGlzIHVwIHRvIDMKICogc3BhY2VzIG9mIGluZGVudGF0aW9uLCBhICI+IiwgYW5kIG9uZSBvcHRpb25hbCBmb2xsb3dpbmcgc3BhY2UsIGFuZCBtYXJrZXJzCiAqIG5lc3QgKCI+ID4gIiBvciAiPj4gIikuIFJldHVybnMgdGhlIG51bWJlciBvZiBtYXJrZXJzIHN0cmlwcGVkIChgZGVwdGhgKSBhbmQgdGhlCiAqIHJlbWFpbmluZyBgY29udGVudGAuIFRoaXMgbGV0cyBmZW5jZWQgY29kZSBibG9ja3MgYmUgdHJhY2tlZCBpbnNpZGUgYmxvY2txdW90ZXM6CiAqIHRoZSBibG9jaydzIGNvbnRlbnQgYW5kIGl0cyBjbG9zaW5nIGZlbmNlIHNpdCBhdCB0aGUgc2FtZSBxdW90ZSBkZXB0aCBhcyB0aGUKICogb3BlbmVyLCBhbmQgYSBsaW5lIHdpdGggZmV3ZXIgbWFya2VycyBtZWFucyB0aGUgYmxvY2txdW90ZSAoYW5kIHRoZSBibG9jaykgZW5kZWQuCiAqLwpmdW5jdGlvbiBzdHJpcEJsb2NrcXVvdGUobGluZSwgbWF4KSB7CiAgICBsZXQgY29udGVudCA9IGxpbmU7CiAgICBsZXQgZGVwdGggPSAwOwogICAgd2hpbGUgKGRlcHRoIDwgbWF4KSB7CiAgICAgICAgY29uc3QgbWF0Y2ggPSBjb250ZW50Lm1hdGNoKC9eIHswLDN9PiggPykvKTsKICAgICAgICBpZiAoIW1hdGNoKSBicmVhazsKICAgICAgICBkZXB0aCArPSAxOwogICAgICAgIGNvbnRlbnQgPSBjb250ZW50LnNsaWNlKG1hdGNoWzBdLmxlbmd0aCk7CiAgICB9CiAgICByZXR1cm4geyBkZXB0aCwgY29udGVudCB9Owp9CgovKioKICogU3RyaXAgYW4gb3B0aW9uYWwgbGVhZGluZyBsaXN0LWl0ZW0gbWFya2VyICgiLSAiLCAiKiAiLCAiKyAiLCAiMS4gIiwgIjEpICIpCiAqIGZyb20gYSBsaW5lIHdob3NlIGJsb2NrcXVvdGUgbWFya2VycyBoYXZlIGFscmVhZHkgYmVlbiByZW1vdmVkLiBSZXR1cm5zIHRoZQogKiByZW1haW5pbmcgdGV4dCBhbmQgdGhlIG51bWJlciBvZiBjb2x1bW5zIHRoZSBtYXJrZXIgY29uc3VtZWQgKHRoZSBsaXN0IGNvbnRlbnQKICogaW5kZW50KS4gVGhpcyBsZXRzIGEgY29kZSBmZW5jZSB0aGF0IG9wZW5zIGFzIHRoZSBmaXJzdCBjb250ZW50IG9mIGEgbGlzdCBpdGVtCiAqIChlLmcuICItIGBgYCIpIGJlIHJlY29nbmlzZWQsIHdpdGggaXRzIGJvZHkgYW5kIGNsb3NpbmcgZmVuY2UgdHJhY2tlZCBhdCB0aGF0CiAqIGluZGVudC4gQSBsaXN0IG1hcmtlciBpcyBhbiB1bmFtYmlndW91cyBibG9jayBjb250YWluZXIsIHNvIGRldGVjdGluZyBpdCBoZXJlIGlzCiAqIHNhZmUgLSB1bmxpa2UgYSBiYXJlIGluZGVudCwgaXQgY2Fubm90IGJlIGNvbmZ1c2VkIHdpdGggb3JkaW5hcnkgdGV4dC4KICovCmZ1bmN0aW9uIHN0cmlwTGlzdE1hcmtlcihjb250ZW50KSB7CiAgICBjb25zdCBtYXRjaCA9IGNvbnRlbnQubWF0Y2goL14gezAsM30oPzpbLSorXXxcZHsxLDl9Wy4pXSlccysvKTsKICAgIGlmICghbWF0Y2gpIHJldHVybiB7IHJlc3Q6IGNvbnRlbnQsIGluZGVudDogMCB9OwogICAgcmV0dXJuIHsgcmVzdDogY29udGVudC5zbGljZShtYXRjaFswXS5sZW5ndGgpLCBpbmRlbnQ6IG1hdGNoWzBdLmxlbmd0aCB9Owp9CgovKioKICogU3RyaXAgdXAgdG8gYG1heGAgbGVhZGluZyBzcGFjZXMgZnJvbSBhIGxpbmUuIFJldHVybnMgdGhlIHJlbWFpbmluZyB0ZXh0IGFuZCB0aGUKICogbnVtYmVyIG9mIHNwYWNlcyBhY3R1YWxseSByZW1vdmVkLgogKi8KZnVuY3Rpb24gc3RyaXBMZWFkaW5nU3BhY2VzKGNvbnRlbnQsIG1heCkgewogICAgbGV0IGluZGVudCA9IDA7CiAgICB3aGlsZSAoaW5kZW50IDwgbWF4ICYmIGNvbnRlbnRbaW5kZW50XSA9PT0gJyAnKSBpbmRlbnQgKz0gMTsKICAgIHJldHVybiB7IHJlc3Q6IGNvbnRlbnQuc2xpY2UoaW5kZW50KSwgaW5kZW50IH07Cn0KCi8qKgogKiBEZXRlY3QgYW4gb3BlbmluZyBjb2RlIGZlbmNlIChgYGAgb3Igfn5+KSBvbiBhIGxpbmUgd2hvc2UgYmxvY2txdW90ZSBhbmQgbGlzdAogKiBtYXJrZXJzIGhhdmUgYWxyZWFkeSBiZWVuIHN0cmlwcGVkLiBQZXIgQ29tbW9uTWFyayBhIGZlbmNlIG1heSBiZSBpbmRlbnRlZCAwLTMKICogc3BhY2VzICg0KyBpcyBhbiBpbmRlbnRlZCBjb2RlIGJsb2NrKSBhbmQgbWF5IGNhcnJ5IGFuIGluZm8gc3RyaW5nLiBSZXR1cm5zCiAqIHsgY2hhciwgbGVuIH0gZm9yIHRoZSBtYXJrZXIsIG9yIG51bGwuCiAqCiAqIE91dCBvZiBzY29wZSAtIDQtc3BhY2UgSU5ERU5URUQgY29kZSBibG9ja3MgYXJlIG5vdCBkZXRlY3RlZC4gVGVsbGluZyBhIGdlbnVpbmUKICogaW5kZW50ZWQgY29kZSBibG9jayBhcGFydCBmcm9tIGluZGVudGVkIHRleHQgbmVlZHMgcGFyYWdyYXBoIGJsb2NrIGNvbnRleHQKICogKENvbW1vbk1hcms6IGluZGVudGVkIGNvZGUgY2Fubm90IGludGVycnVwdCBhIHBhcmFncmFwaCwgYW5kIGEgNC1zcGFjZSBpbmRlbnQKICogdW5kZXIgYSBsaXN0IGl0ZW0gaXMgbGlzdCBjb250ZW50IHJhdGhlciB0aGFuIGNvZGUpLiBBIGxpbmUtYmFzZWQgc2NyaXB0IGxhY2tzCiAqIHRoYXQgY29udGV4dCwgYW5kIGEgcGFydGlhbCBoZXVyaXN0aWMgd291bGQgZWl0aGVyIHJlaW50cm9kdWNlIGRhdGEgbG9zcwogKiAoZGVsZXRpbmcgaW5kZW50ZWQgY29kZSkgb3IgY2F1c2UgdGhlIG9wcG9zaXRlIGJ1ZyAtIHNpbGVudGx5IHNraXBwaW5nIGEgcmVhbAogKiBpbmxpbmUgZmllbGQgbWVyZWx5IGluZGVudGVkIHVuZGVyIGEgbGlzdCAoRGF0YXZpZXcgcGFyc2VzIHRob3NlIGFzIGZpZWxkcywgc28KICogdXNlcnMgZXhwZWN0IHRoZW0gdG8gbWlncmF0ZSkuIEZlbmNlZCBjb2RlICh0b3AtbGV2ZWwsIGJsb2NrcXVvdGVkLCBBTkQgbGlzdC0KICogbmVzdGVkKSBhbmQgaW5saW5lIGNvZGUgQVJFIGRldGVjdGVkIGFuZCBwcmVzZXJ2ZWQ7IG9ubHkgYW4gaW5saW5lLWZpZWxkLWxpa2UKICogbGluZSBpbnNpZGUgYSBnZW51aW5lIElOREVOVEVEIGNvZGUgYmxvY2sgbWF5IHN0aWxsIG1pZ3JhdGUuCiAqLwpmdW5jdGlvbiBvcGVuaW5nRmVuY2UobGluZSkgewogICAgY29uc3QgbWF0Y2ggPSBsaW5lLm1hdGNoKC9eIHswLDN9KGB7Myx9fH57Myx9KS8pOwogICAgaWYgKCFtYXRjaCkgcmV0dXJuIG51bGw7CiAgICByZXR1cm4geyBjaGFyOiBtYXRjaFsxXVswXSwgbGVuOiBtYXRjaFsxXS5sZW5ndGggfTsKfQoKLyoqCiAqIEEgY2xvc2luZyBmZW5jZSB1c2VzIHRoZSBzYW1lIG1hcmtlciBjaGFyYWN0ZXIsIGlzIGF0IGxlYXN0IGFzIGxvbmcgYXMgdGhlCiAqIG9wZW5pbmcgZmVuY2UsIGFuZCBjYXJyaWVzIG5vIGluZm8gc3RyaW5nIChvbmx5IG9wdGlvbmFsIHRyYWlsaW5nIHdoaXRlc3BhY2UpLgogKi8KZnVuY3Rpb24gaXNDbG9zaW5nRmVuY2UobGluZSwgZmVuY2VDaGFyLCBmZW5jZUxlbikgewogICAgLy8gQWxsb3cgYSB0cmFpbGluZyBDUiBzbyBDUkxGIGZpbGVzIGNsb3NlIHRoZWlyIGZlbmNlcyBjb3JyZWN0bHkuCiAgICBjb25zdCBtYXRjaCA9IGxpbmUubWF0Y2goL14gezAsM30oYHszLH18fnszLH0pWyBcdF0qXHI/JC8pOwogICAgaWYgKCFtYXRjaCkgcmV0dXJuIGZhbHNlOwogICAgY29uc3QgbWFya2VyID0gbWF0Y2hbMV07CiAgICByZXR1cm4gbWFya2VyWzBdID09PSBmZW5jZUNoYXIgJiYgbWFya2VyLmxlbmd0aCA+PSBmZW5jZUxlbjsKfQoKLyoqCiAqIFJlcGxhY2UgaW5saW5lIGNvZGUgc3BhbnMgd2l0aCBlcXVhbC1sZW5ndGggYmxhbmtzIHNvIGEgIjo6IiBpbnNpZGUgY29kZSBpcyBub3QKICogbWlzdGFrZW4gZm9yIGFuIGlubGluZS1maWVsZCBzZXBhcmF0b3IuIFN1cHBvcnRzIGFyYml0cmFyeSBiYWNrdGljay1ydW4gbGVuZ3RocwogKiAoYGNvZGVgLCBgYGNvYGRlYGApLiBDaGFyYWN0ZXIgcG9zaXRpb25zIGFyZSBwcmVzZXJ2ZWQgc28gdGhlIGNhbGxlciBjYW4gc2xpY2UKICogdGhlIG9yaWdpbmFsIGxpbmUgYnkgb2Zmc2V0cyBjb21wdXRlZCBvbiB0aGUgbWFza2VkIGxpbmUuCiAqCiAqIEltcGxlbWVudGVkIGFzIGEgbGluZWFyLXRpbWUgc2NhbiBvdmVyIHRoZSBpbmRleGVkIGJhY2t0aWNrIHJ1bnMgKG5vCiAqIGJhY2t0cmFja2luZzsgaXQgaW5kZXhlcyB0aGUgcnVucywgdGhlbiB3YWxrcyB0aGVtIG9uY2UpLiBUaGUgZWFybGllciByZWdleAogKiAvKGArKSg/Oig/IVwxKVtcc1xTXSkqP1wxL2cgY29tYmluZWQgYSBiYWNrcmVmZXJlbmNlZCBvcGVuZXIsIGEgbGF6eSBib2R5LCBhbmQgYQogKiBuZWdhdGl2ZSBsb29rYWhlYWQsIHdoaWNoIGJhY2t0cmFja3Mgc3VwZXItbGluZWFybHkgKHNlY29uZHMgb2YgbWFpbi10aHJlYWQKICogZnJlZXplKSBvbiBhIGxvbmcgYmFja3RpY2sgcnVuIHdpdGggbm8gbWF0Y2hpbmctbGVuZ3RoIGNsb3NlciAtIHJlYWNoYWJsZSBmcm9tIGEKICogc3luY2VkL3NoYXJlZCBub3RlIHRoYXQgdGhlIHVzZXIgbWlncmF0ZXMuIFRoaXMgc2Nhbm5lciByZXByb2R1Y2VzIHRoZSByZWdleCdzCiAqIGV4YWN0IG1hc2tpbmcgYmVoYXZpb3VyIHdpdGggTyhuKSB3b3JrOiBhIHJ1biBvZiBrIGJhY2t0aWNrcyBpcyBjbG9zZWQgYnkgdGhlCiAqIGZpcnN0IHJ1biBvZiBhdCBsZWFzdCBrIGJhY2t0aWNrcyAoaXRzIGZpcnN0IGsgYmFja3RpY2tzIGZvcm0gdGhlIGNsb3NlciwgdGhlCiAqIHJlZ2V4J3MgYFwxYCksIHByZWZlcnJpbmcgdGhlIGxvbmdlc3Qgb3BlbmVyIGxlbmd0aCB0aGUgZW5naW5lIHdvdWxkIGhhdmUKICogbWF0Y2hlZC4gQW4gdW5jbG9zZWQgb3BlbmVyIHN0YXlzIHZlcmJhdGltLgogKi8KZnVuY3Rpb24gbWFza0lubGluZUNvZGUodGV4dCkgewogICAgY29uc3QgbiA9IHRleHQubGVuZ3RoOwoKICAgIC8vIEluZGV4IGV2ZXJ5IG1heGltYWwgcnVuIG9mIGJhY2t0aWNrcyBvbmNlLgogICAgY29uc3QgcnVuU3RhcnQgPSBbXTsKICAgIGNvbnN0IHJ1bkxlbiA9IFtdOwogICAgZm9yIChsZXQgaSA9IDA7IGkgPCBuOyApIHsKICAgICAgICBpZiAodGV4dC5jaGFyQ29kZUF0KGkpID09PSA5NiAvKiBgICovKSB7CiAgICAgICAgICAgIGxldCBqID0gaSArIDE7CiAgICAgICAgICAgIHdoaWxlIChqIDwgbiAmJiB0ZXh0LmNoYXJDb2RlQXQoaikgPT09IDk2KSBqKys7CiAgICAgICAgICAgIHJ1blN0YXJ0LnB1c2goaSk7CiAgICAgICAgICAgIHJ1bkxlbi5wdXNoKGogLSBpKTsKICAgICAgICAgICAgaSA9IGo7CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgaSsrOwogICAgICAgIH0KICAgIH0KICAgIGNvbnN0IHJ1bkNvdW50ID0gcnVuU3RhcnQubGVuZ3RoOwogICAgaWYgKHJ1bkNvdW50ID09PSAwKSByZXR1cm4gdGV4dDsKCiAgICAvLyBzdWZmaXhNYXhbYV0gPSBsb25nZXN0IHJ1biBvdmVyIHJ1bnNbYS4uZW5kXTsgc3VmZml4TWF4W3J1bkNvdW50XSA9IDAuIExldHMgdGhlCiAgICAvLyBiZXN0IGNsb3NlciBmb3IgYSBnaXZlbiBvcGVuZXIgYmUgZm91bmQgd2l0aG91dCByZS1zY2FubmluZyBsYXRlciBydW5zLgogICAgY29uc3Qgc3VmZml4TWF4ID0gbmV3IEFycmF5KHJ1bkNvdW50ICsgMSk7CiAgICBzdWZmaXhNYXhbcnVuQ291bnRdID0gMDsKICAgIGZvciAobGV0IGEgPSBydW5Db3VudCAtIDE7IGEgPj0gMDsgYS0tKSB7CiAgICAgICAgc3VmZml4TWF4W2FdID0gTWF0aC5tYXgocnVuTGVuW2FdLCBzdWZmaXhNYXhbYSArIDFdKTsKICAgIH0KCiAgICBjb25zdCBvdXQgPSB0ZXh0LnNwbGl0KCcnKTsKICAgIGNvbnN0IGJsYW5rID0gKGZyb20sIHRvKSA9PiB7IGZvciAobGV0IHAgPSBmcm9tOyBwIDwgdG87IHArKykgb3V0W3BdID0gJyAnOyB9OwoKICAgIC8vIFdhbGsgZWFjaCBsb2dpY2FsIG9wZW5lci4gYGFgIGlzIHRoZSBydW4gd2Ugc2l0IGluOyBgaWAvYG1gIGFyZSB0aGUgb3BlbmVyJ3MKICAgIC8vIHN0YXJ0IHBvc2l0aW9uIGFuZCByZW1haW5pbmcgYmFja3RpY2sgY291bnQgKG0gPCBydW5MZW5bYV0gb25seSB3aGVuIGEgcHJpb3IKICAgIC8vIGNsb3NlciBhbHJlYWR5IGNvbnN1bWVkIGEgcHJlZml4IG9mIHRoaXMgcnVuKS4KICAgIGxldCBhID0gMDsKICAgIGxldCBpID0gcnVuU3RhcnRbMF07CiAgICBsZXQgbSA9IHJ1bkxlblswXTsKICAgIHdoaWxlICh0cnVlKSB7CiAgICAgICAgY29uc3QgaGFsZiA9IG0gPj4gMTsgICAgICAgICAgICAvLyBmbG9vcihtLzIpOiBsb25nZXN0IG9wZW5lciB0aGF0IGNsb3NlcyBpbnNpZGUgdGhpcyBydW4KICAgICAgICBjb25zdCBtYXhMYXRlciA9IHN1ZmZpeE1heFthICsgMV07CiAgICAgICAgY29uc3Qga1VwcGVyID0gTWF0aC5taW4obSwgbWF4TGF0ZXIpOyAvLyBsb25nZXN0IG9wZW5lciB0aGF0IGNsb3NlcyBpbiBhIGxhdGVyIHJ1bgoKICAgICAgICBpZiAoa1VwcGVyID4gaGFsZikgewogICAgICAgICAgICAvLyBHcmVlZHkgb3BlbmVyIG9mIGxlbmd0aCBrVXBwZXI7IGNsb3NlciBpcyB0aGUgZmlyc3QgbGF0ZXIgcnVuIHRoYXQgbG9uZy4KICAgICAgICAgICAgY29uc3QgayA9IGtVcHBlcjsKICAgICAgICAgICAgbGV0IGIgPSBhICsgMTsKICAgICAgICAgICAgd2hpbGUgKHJ1bkxlbltiXSA8IGspIGIrKzsgIC8vIGV4aXN0cyBiZWNhdXNlIG1heExhdGVyID49IGsKICAgICAgICAgICAgY29uc3QgbWF0Y2hFbmQgPSBydW5TdGFydFtiXSArIGs7CiAgICAgICAgICAgIGJsYW5rKGksIG1hdGNoRW5kKTsKICAgICAgICAgICAgY29uc3QgbGVmdG92ZXIgPSBydW5MZW5bYl0gLSBrOwogICAgICAgICAgICBpZiAobGVmdG92ZXIgPiAwKSB7IGEgPSBiOyBpID0gbWF0Y2hFbmQ7IG0gPSBsZWZ0b3ZlcjsgY29udGludWU7IH0KICAgICAgICAgICAgYSA9IGIgKyAxOwogICAgICAgIH0gZWxzZSBpZiAoaGFsZiA+IDApIHsKICAgICAgICAgICAgLy8gQ2xvc2VyIGlzIHRoZSBuZXh0IGBoYWxmYCBiYWNrdGlja3MgaW5zaWRlIHRoaXMgc2FtZSBydW4uCiAgICAgICAgICAgIGNvbnN0IG1hdGNoRW5kID0gaSArIDIgKiBoYWxmOwogICAgICAgICAgICBibGFuayhpLCBtYXRjaEVuZCk7CiAgICAgICAgICAgIGNvbnN0IGxlZnRvdmVyID0gbSAtIDIgKiBoYWxmOyAvLyAwIChldmVuIHJ1bikgb3IgMSAob2RkIHJ1bikKICAgICAgICAgICAgaWYgKGxlZnRvdmVyID4gMCkgeyBpID0gbWF0Y2hFbmQ7IG0gPSBsZWZ0b3ZlcjsgY29udGludWU7IH0KICAgICAgICAgICAgYSA9IGEgKyAxOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIC8vIG0gPT09IDEgd2l0aCBubyBsYXRlciBiYWNrdGlja3M6IGEgbG9uZSBiYWNrdGljayBzdGF5cyBsaXRlcmFsLgogICAgICAgICAgICBhID0gYSArIDE7CiAgICAgICAgfQoKICAgICAgICBpZiAoYSA+PSBydW5Db3VudCkgYnJlYWs7CiAgICAgICAgaSA9IHJ1blN0YXJ0W2FdOwogICAgICAgIG0gPSBydW5MZW5bYV07CiAgICB9CiAgICByZXR1cm4gb3V0LmpvaW4oJycpOwp9CgovKioKICogRmluZCB0aGUgZW5kIHBvc2l0aW9uIG9mIGZyb250bWF0dGVyCiAqIEBwYXJhbSB7c3RyaW5nfSBjb250ZW50IC0gVGhlIGZpbGUgY29udGVudAogKiBAcmV0dXJucyB7e2Zyb250bWF0dGVyRW5kOiBudW1iZXIsIGhhc0V4aXN0aW5nRnJvbnRtYXR0ZXI6IGJvb2xlYW4sIGZyb250bWF0dGVyVGV4dDogc3RyaW5nfX0KICovCmZ1bmN0aW9uIGZpbmRGcm9udG1hdHRlckVuZChjb250ZW50KSB7CiAgICBjb25zdCBmcm9udG1hdHRlclJlZ2V4ID0gL14tLS1ccj9cbihbXHNcU10qPylccj9cbi0tLVxyP1xuLzsKICAgIGNvbnN0IG1hdGNoID0gY29udGVudC5tYXRjaChmcm9udG1hdHRlclJlZ2V4KTsKCiAgICBpZiAobWF0Y2gpIHsKICAgICAgICByZXR1cm4gewogICAgICAgICAgICBmcm9udG1hdHRlckVuZDogbWF0Y2hbMF0ubGVuZ3RoLAogICAgICAgICAgICBoYXNFeGlzdGluZ0Zyb250bWF0dGVyOiB0cnVlLAogICAgICAgICAgICBmcm9udG1hdHRlclRleHQ6IG1hdGNoWzBdCiAgICAgICAgfTsKICAgIH0KCiAgICByZXR1cm4gewogICAgICAgIGZyb250bWF0dGVyRW5kOiAwLAogICAgICAgIGhhc0V4aXN0aW5nRnJvbnRtYXR0ZXI6IGZhbHNlLAogICAgICAgIGZyb250bWF0dGVyVGV4dDogJycKICAgIH07Cn0KCi8qKgogKiBQYXJzZSBjb21tYS1zZXBhcmF0ZWQgdmFsdWVzIHdpdGggc3BlY2lhbCBoYW5kbGluZyBmb3Igd2lraWxpbmtzCiAqIEVuc3VyZXMgY29tbWFzIGluc2lkZSBbW3dpa2lsaW5rc11dIGFyZSBub3QgdHJlYXRlZCBhcyBzZXBhcmF0b3JzCiAqIEBwYXJhbSB7c3RyaW5nfSB2YWx1ZSAtIFRoZSBmaWVsZCB2YWx1ZSB0byBwYXJzZQogKiBAcmV0dXJucyB7c3RyaW5nW119IEFycmF5IG9mIHBhcnNlZCB2YWx1ZXMKICovCmZ1bmN0aW9uIHBhcnNlQ29tbWFTZXBhcmF0ZWRXaXRoV2lraWxpbmtzKHZhbHVlKSB7CiAgICAvLyBJZiBubyBjb21tYXMsIHJldHVybiBhcyBzaW5nbGUgdmFsdWUKICAgIGlmICghdmFsdWUuaW5jbHVkZXMoJywnKSkgewogICAgICAgIHJldHVybiBbdmFsdWUudHJpbSgpXTsKICAgIH0KCiAgICBjb25zdCB2YWx1ZXMgPSBbXTsKICAgIGxldCBjdXJyZW50VmFsdWUgPSAnJzsKICAgIGxldCBpbnNpZGVXaWtpbGluayA9IGZhbHNlOwoKICAgIGZvciAobGV0IGkgPSAwOyBpIDwgdmFsdWUubGVuZ3RoOyBpKyspIHsKICAgICAgICBjb25zdCBjaGFyID0gdmFsdWVbaV07CiAgICAgICAgY29uc3QgbmV4dENoYXIgPSB2YWx1ZVtpICsgMV07CgogICAgICAgIC8vIENoZWNrIGZvciB3aWtpbGluayBvcGVuaW5nIFtbCiAgICAgICAgaWYgKGNoYXIgPT09ICdbJyAmJiBuZXh0Q2hhciA9PT0gJ1snKSB7CiAgICAgICAgICAgIGluc2lkZVdpa2lsaW5rID0gdHJ1ZTsKICAgICAgICAgICAgY3VycmVudFZhbHVlICs9IGNoYXI7CiAgICAgICAgICAgIGNvbnRpbnVlOwogICAgICAgIH0KCiAgICAgICAgLy8gQ2hlY2sgZm9yIHdpa2lsaW5rIGNsb3NpbmcgXV0KICAgICAgICBpZiAoY2hhciA9PT0gJ10nICYmIG5leHRDaGFyID09PSAnXScpIHsKICAgICAgICAgICAgaW5zaWRlV2lraWxpbmsgPSBmYWxzZTsKICAgICAgICAgICAgY3VycmVudFZhbHVlICs9IGNoYXI7CiAgICAgICAgICAgIGNvbnRpbnVlOwogICAgICAgIH0KCiAgICAgICAgLy8gSWYgd2UgaGl0IGEgY29tbWEgb3V0c2lkZSB3aWtpbGlua3MsIHNwbGl0CiAgICAgICAgaWYgKGNoYXIgPT09ICcsJyAmJiAhaW5zaWRlV2lraWxpbmspIHsKICAgICAgICAgICAgY29uc3QgdHJpbW1lZCA9IGN1cnJlbnRWYWx1ZS50cmltKCk7CiAgICAgICAgICAgIGlmICh0cmltbWVkKSB7CiAgICAgICAgICAgICAgICB2YWx1ZXMucHVzaCh0cmltbWVkKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBjdXJyZW50VmFsdWUgPSAnJzsKICAgICAgICAgICAgY29udGludWU7CiAgICAgICAgfQoKICAgICAgICBjdXJyZW50VmFsdWUgKz0gY2hhcjsKICAgIH0KCiAgICAvLyBBZGQgdGhlIGxhc3QgdmFsdWUKICAgIGNvbnN0IHRyaW1tZWQgPSBjdXJyZW50VmFsdWUudHJpbSgpOwogICAgaWYgKHRyaW1tZWQpIHsKICAgICAgICB2YWx1ZXMucHVzaCh0cmltbWVkKTsKICAgIH0KCiAgICByZXR1cm4gdmFsdWVzLmxlbmd0aCA+IDAgPyB2YWx1ZXMgOiBbdmFsdWUudHJpbSgpXTsKfQo="
}
]
}


{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-moc-link-dashboard"
],
"choices": [
{
"choice": {
"id": "qa-pkg-moc-link-dashboard",
"name": "New MOC note",
"type": "Template",
"command": true,
"templatePath": "Templates/MOC Link Dashboard.md",
"fileNameFormat": {
"enabled": true,
"format": "{{VALUE:moc_title}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"MOCs"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "apply",
"mode": "increment"
}
},
"pathHint": [
"New MOC note"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "template",
"originalPath": "Templates/MOC Link Dashboard.md",
"contentEncoding": "base64",
"content": "LS0tCnRhZ3M6CiAgLSBtb2MKLS0tCgojIHt7VkFMVUU6bW9jX3RpdGxlfX0KCiMjIExpbmsgRGFzaGJvYXJkCgpVc2UgdGhlIHZpZXcgcGlja2VyIGluIHRoaXMgZW1iZWRkZWQgYmFzZSB0byBzd2l0Y2ggYmV0d2VlbiBiYWNrbGlua3MgYW5kCm91dGdvaW5nIGxpbmtzIGZvciB0aGlzIG5vdGUuCgpgYGBiYXNlCnt7VEVNUExBVEU6VGVtcGxhdGVzL01PQyBMaW5rIERhc2hib2FyZC5iYXNlfX0KYGBgCgojIyBOb3RlcwoKLSBTdGFydCBsaW5raW5nIHRoaXMgbm90ZSB0byByZWxhdGVkIGlkZWFzLgo="
},
{
"kind": "template",
"originalPath": "Templates/MOC Link Dashboard.base",
"contentEncoding": "base64",
"content": "Zm9ybXVsYXM6CiAgbm90ZV9saW5rOiAiZmlsZS5hc0xpbmsoKSIKcHJvcGVydGllczoKICBmb3JtdWxhLm5vdGVfbGluazoKICAgIGRpc3BsYXlOYW1lOiBOb3RlCiAgZmlsZS5mb2xkZXI6CiAgICBkaXNwbGF5TmFtZTogRm9sZGVyCiAgZmlsZS5tdGltZToKICAgIGRpc3BsYXlOYW1lOiBVcGRhdGVkCnZpZXdzOgogIC0gdHlwZTogdGFibGUKICAgIG5hbWU6IEJhY2tsaW5rcwogICAgZmlsdGVyczoKICAgICAgYW5kOgogICAgICAgIC0gJ2ZpbGUuZXh0ID09ICJtZCInCiAgICAgICAgLSAiZmlsZS5oYXNMaW5rKHRoaXMuZmlsZSkiCiAgICAgICAgLSAiZmlsZS5wYXRoICE9IHRoaXMuZmlsZS5wYXRoIgogICAgb3JkZXI6CiAgICAgIC0gZm9ybXVsYS5ub3RlX2xpbmsKICAgICAgLSBmaWxlLmZvbGRlcgogICAgICAtIGZpbGUubXRpbWUKICAtIHR5cGU6IHRhYmxlCiAgICBuYW1lOiBPdXRnb2luZyBsaW5rcwogICAgZmlsdGVyczoKICAgICAgYW5kOgogICAgICAgIC0gJ2ZpbGUuZXh0ID09ICJtZCInCiAgICAgICAgLSAidGhpcy5maWxlLmhhc0xpbmsoZmlsZSkiCiAgICAgICAgLSAiZmlsZS5wYXRoICE9IHRoaXMuZmlsZS5wYXRoIgogICAgb3JkZXI6CiAgICAgIC0gZm9ybXVsYS5ub3RlX2xpbmsKICAgICAgLSBmaWxlLmZvbGRlcgogICAgICAtIGZpbGUubXRpbWUK"
}
]
}


{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-moc-related-notes"
],
"choices": [
{
"choice": {
"id": "qa-pkg-moc-related-notes",
"name": "Insert related notes base",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "",
"captureToActiveFile": true,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "top",
"createFileIfItDoesntExist": {
"enabled": false,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "## Related Notes\n\nbase\n{{TEMPLATE:Templates/MOC Related Notes.base}}\n\n\nContext: {{VALUE}}"
},
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Insert related notes base"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "template",
"originalPath": "Templates/MOC Related Notes.base",
"contentEncoding": "base64",
"content": "ZmlsdGVyczoKICBhbmQ6CiAgICAtICdmaWxlLmV4dCA9PSAibWQiJwogICAgLSAiZmlsZS5oYXNMaW5rKHRoaXMuZmlsZSkiCiAgICAtICJmaWxlLnBhdGggIT0gdGhpcy5maWxlLnBhdGgiCnZpZXdzOgogIC0gdHlwZTogdGFibGUKICAgIG5hbWU6IFJlbGF0ZWQgbm90ZXMK"
}
]
}


Public Packages Move Tagged Notes.Quickadd.Json (`docs/public/packages/move-tagged-notes.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-move-tagged-notes"
],
"choices": [
{
"choice": {
"id": "qa-pkg-move-tagged-notes",
"name": "Move notes with a tag",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-move-tagged-notes-macro",
"name": "Move notes with a tag",
"commands": [
{
"id": "qa-pkg-move-tagged-notes-script",
"name": "moveNotesWithTag",
"type": "UserScript",
"path": "scripts/moveNotesWithTag.js",
"settings": {}
}
]
}
},
"pathHint": [
"Move notes with a tag"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/moveNotesWithTag.js",
"contentEncoding": "base64",
"content": "Ly8gTW92ZXMgZXZlcnkgbm90ZSBjYXJyeWluZyBhIGNob3NlbiB0YWcgKGZyb250bWF0dGVyIG9yIGlubGluZSkgdG8gYSBjaG9zZW4gZm9sZGVyLgovLyBEb2NzOiBodHRwczovL3F1aWNrYWRkLm9ic2lkaWFuLmd1aWRlL2RvY3MvRXhhbXBsZXMvTWFjcm9fTW92ZU5vdGVzV2l0aEFUYWdUb0FGb2xkZXIKbW9kdWxlLmV4cG9ydHMgPSBhc3luYyBmdW5jdGlvbiBtb3ZlRmlsZXNXaXRoVGFnKHBhcmFtcykgewoJY29uc3QgewoJCWFwcCwKCQlxdWlja0FkZEFwaTogeyBzdWdnZXN0ZXIsIHllc05vUHJvbXB0IH0sCgl9ID0gcGFyYW1zOwoJY29uc3QgYWxsVGFncyA9IE9iamVjdC5rZXlzKGFwcC5tZXRhZGF0YUNhY2hlLmdldFRhZ3MoKSk7Cgljb25zdCB0YWcgPSBhd2FpdCBzdWdnZXN0ZXIoYWxsVGFncywgYWxsVGFncyk7CglpZiAoIXRhZykgcmV0dXJuOwoJY29uc3Qgc2hvdWxkTW92ZU5lc3RlZCA9IGF3YWl0IHllc05vUHJvbXB0KAoJCSJTaG91bGQgSSBtb3ZlIG5lc3RlZCB0YWdzLCB0b28/IiwKCQlgSWYgeW91IHNheSBubywgSSdsbCBvbmx5IG1vdmUgdGFncyB0aGF0IGFyZSBzdHJpY3RseSBlcXVhbCB0byB3aGF0IHlvdSd2ZSBjaG9zZW4uIElmIHlvdSBzYXkgeWVzLCBJJ2xsIG1vdmUgdGFncyB0aGF0IGFyZSBuZXN0ZWQgdW5kZXIgJHt0YWd9LmAKCSk7CgoJY29uc3QgY2FjaGUgPSBhcHAubWV0YWRhdGFDYWNoZS5nZXRDYWNoZWRGaWxlcygpOwoJbGV0IGZpbGVzVG9Nb3ZlID0gW107CgoJLy8gSGVscGVyIGZ1bmN0aW9uIHRvIGdldCB0YWdzIGFzIGFycmF5IGZyb20gZnJvbnRtYXR0ZXIKCS8vIEhhbmRsZXMgYm90aCBzdHJpbmcgZm9ybWF0ICgidGFnMSB0YWcyIiwgInRhZzEsIHRhZzIiKSBhbmQgYXJyYXkgZm9ybWF0IChbInRhZzEiLCAidGFnMiJdKQoJZnVuY3Rpb24gZ2V0VGFnc0FzQXJyYXkodGFnVmFsdWUpIHsKCQlpZiAoIXRhZ1ZhbHVlKSByZXR1cm4gW107CgkJaWYgKEFycmF5LmlzQXJyYXkodGFnVmFsdWUpKSByZXR1cm4gdGFnVmFsdWU7CgkJaWYgKHR5cGVvZiB0YWdWYWx1ZSA9PT0gJ3N0cmluZycpIHJldHVybiB0YWdWYWx1ZS5zcGxpdCgvW1xzLF0rLykuZmlsdGVyKEJvb2xlYW4pOwoJCXJldHVybiBbXTsKCX0KCgkvLyBgI3Byb2plY3RgIG1hdGNoZXMgYCNwcm9qZWN0YCwgYW5kIHdpdGggbmVzdGVkIHRhZ3MgYWxzbyBgI3Byb2plY3Qvd29ya2AsCgkvLyBidXQgbmV2ZXIgYCNwcm9qZWN0aWxlYC4KCWNvbnN0IGNsZWFuVGFnID0gdGFnLnJlcGxhY2UoL14jLywgIiIpOwoJY29uc3QgbWF0Y2hlcyA9IChjYW5kaWRhdGUpID0+IHsKCQljb25zdCBjbGVhbiA9IFN0cmluZyhjYW5kaWRhdGUpLnJlcGxhY2UoL14jLywgIiIpOwoJCXJldHVybiBjbGVhbiA9PT0gY2xlYW5UYWcgfHwgKHNob3VsZE1vdmVOZXN0ZWQgJiYgY2xlYW4uc3RhcnRzV2l0aChjbGVhblRhZyArICIvIikpOwoJfTsKCgljYWNoZS5mb3JFYWNoKChrZXkpID0+IHsKCQkvLyBTa2lwIHRlbXBsYXRlIG5vdGVzIHdoZXJldmVyIHRoZXkgbGl2ZSwgZm9yIGV4YW1wbGUgVGVtcGxhdGVzLyBvciBfdGVtcGxhdGVzLy4KCQlpZiAoa2V5LnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoInRlbXBsYXRlIikpIHJldHVybjsKCQljb25zdCBmaWxlQ2FjaGUgPSBhcHAubWV0YWRhdGFDYWNoZS5nZXRDYWNoZShrZXkpOwoJCWlmICghZmlsZUNhY2hlKSByZXR1cm47CgoJCS8vIENoZWNrIGlmIGZpbGUgaGFzIHRoZSB0YWcgd2UncmUgbG9va2luZyBmb3IKCQlsZXQgaGFzTWF0Y2hpbmdUYWcgPSBmYWxzZTsKCgkJLy8gQ2hlY2sgZnJvbnRtYXR0ZXIgdGFncyAoc3VwcG9ydHMgdGFncywgVGFncywgdGFnLCBUYWcpCgkJaWYgKGZpbGVDYWNoZS5mcm9udG1hdHRlcikgewoJCQljb25zdCB0YWdGaWVsZHMgPSBbJ3RhZ3MnLCAnVGFncycsICd0YWcnLCAnVGFnJ107CgkJCWhhc01hdGNoaW5nVGFnID0gdGFnRmllbGRzLnNvbWUoKGZpZWxkKSA9PgoJCQkJZ2V0VGFnc0FzQXJyYXkoZmlsZUNhY2hlLmZyb250bWF0dGVyW2ZpZWxkXSkuc29tZShtYXRjaGVzKQoJCQkpOwoJCX0KCgkJLy8gQ2hlY2sgaW5saW5lIHRhZ3MgKCN0YWcgaW4gdGhlIG5vdGUgY29udGVudCkKCQlpZiAoIWhhc01hdGNoaW5nVGFnICYmIGZpbGVDYWNoZS50YWdzKSB7CgkJCWhhc01hdGNoaW5nVGFnID0gZmlsZUNhY2hlLnRhZ3Muc29tZSgodCkgPT4gbWF0Y2hlcyh0LnRhZykpOwoJCX0KCgkJaWYgKGhhc01hdGNoaW5nVGFnKSBmaWxlc1RvTW92ZS5wdXNoKGtleSk7Cgl9KTsKCglpZiAoZmlsZXNUb01vdmUubGVuZ3RoID09PSAwKSB7CgkJbmV3IE5vdGljZShgTm8gbm90ZXMgY2FycnkgJHt0YWd9LmApOwoJCXJldHVybjsKCX0KCgljb25zdCBmb2xkZXJzID0gYXBwLnZhdWx0CgkJLmdldEFsbExvYWRlZEZpbGVzKCkKCQkuZmlsdGVyKChmKSA9PiBmLmNoaWxkcmVuKQoJCS5tYXAoKGYpID0+IGYucGF0aCk7Cgljb25zdCBub3VuID0gZmlsZXNUb01vdmUubGVuZ3RoID09PSAxID8gIm5vdGUiIDogIm5vdGVzIjsKCWNvbnN0IHRhcmdldEZvbGRlciA9IGF3YWl0IHN1Z2dlc3Rlcihmb2xkZXJzLCBmb2xkZXJzLCBgTW92ZSAke2ZpbGVzVG9Nb3ZlLmxlbmd0aH0gJHtub3VufSB0b+KApmApOwoJaWYgKCF0YXJnZXRGb2xkZXIpIHJldHVybjsKCglsZXQgbW92ZWQgPSAwOwoJbGV0IGFscmVhZHlUaGVyZSA9IDA7Cgljb25zdCBuYW1lVGFrZW4gPSBbXTsKCWZvciAoY29uc3QgZmlsZSBvZiBmaWxlc1RvTW92ZSkgewoJCS8vIEEgbm90ZSBjYW4gYmUgZGVsZXRlZCBvciBtb3ZlZCB3aGlsZSB0aGUgZm9sZGVyIHByb21wdCBpcyBvcGVuLgoJCWNvbnN0IHRmaWxlID0gYXBwLnZhdWx0LmdldEFic3RyYWN0RmlsZUJ5UGF0aChmaWxlKTsKCQlpZiAoIXRmaWxlKSBjb250aW51ZTsKCQljb25zdCBuZXdQYXRoID0gdGFyZ2V0Rm9sZGVyID09PSAiLyIgPyB0ZmlsZS5uYW1lIDogYCR7dGFyZ2V0Rm9sZGVyfS8ke3RmaWxlLm5hbWV9YDsKCQlpZiAobmV3UGF0aCA9PT0gdGZpbGUucGF0aCkgewoJCQlhbHJlYWR5VGhlcmUrKzsKCQkJY29udGludWU7CgkJfQoJCS8vIHJlbmFtZUZpbGUgdGhyb3dzIG9uIGFuIG9jY3VwaWVkIGRlc3RpbmF0aW9uLCB3aGljaCB3b3VsZCBhYm9ydCB0aGUKCQkvLyBiYXRjaCBoYWxmd2F5OyBza2lwIHRoZSBub3RlIGFuZCBzYXkgc28gaW5zdGVhZC4gVGhpcyBhbHNvIGNhdGNoZXMgdHdvCgkJLy8gdGFnZ2VkIG5vdGVzIHRoYXQgc2hhcmUgYSBuYW1lLCBzaW5jZSB0aGUgZmlyc3Qgb25lIHRvIG1vdmUgdGFrZXMgaXQuCgkJaWYgKGFwcC52YXVsdC5nZXRBYnN0cmFjdEZpbGVCeVBhdGgobmV3UGF0aCkpIHsKCQkJbmFtZVRha2VuLnB1c2godGZpbGUucGF0aCk7CgkJCWNvbnRpbnVlOwoJCX0KCQlhd2FpdCBhcHAuZmlsZU1hbmFnZXIucmVuYW1lRmlsZSh0ZmlsZSwgbmV3UGF0aCk7CgkJbW92ZWQrKzsKCX0KCWNvbnN0IHBhcnRzID0gW2BNb3ZlZCAke21vdmVkfSAke21vdmVkID09PSAxID8gIm5vdGUiIDogIm5vdGVzIn0gdG8gJHt0YXJnZXRGb2xkZXJ9LmBdOwoJaWYgKGFscmVhZHlUaGVyZSkgcGFydHMucHVzaChgJHthbHJlYWR5VGhlcmV9IGFscmVhZHkgdGhlcmUuYCk7CglpZiAobmFtZVRha2VuLmxlbmd0aCkgewoJCXBhcnRzLnB1c2goYFNraXBwZWQgJHtuYW1lVGFrZW4ubGVuZ3RofSB3aG9zZSBuYW1lIGlzIHRha2VuIHRoZXJlOiAke25hbWVUYWtlbi5qb2luKCIsICIpfS5gKTsKCX0KCW5ldyBOb3RpY2UocGFydHMuam9pbigiICIpLCBuYW1lVGFrZW4ubGVuZ3RoID8gMTAwMDAgOiB1bmRlZmluZWQpOwp9Owo="
}
]
}


Public Packages Movie Notes.Quickadd.Json (`docs/public/packages/movie-notes.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-movie-notes"
],
"choices": [
{
"choice": {
"id": "qa-pkg-movie-notes",
"name": "Add movie note",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-movie-notes-macro",
"name": "Add movie note",
"commands": [
{
"id": "qa-pkg-movie-notes-script",
"name": "movies",
"type": "UserScript",
"path": "scripts/movies.js",
"settings": {}
},
{
"id": "qa-pkg-movie-notes-nested",
"name": "Movie note",
"type": "NestedChoice",
"choice": {
"id": "qa-pkg-movie-notes-template",
"name": "Movie note",
"type": "Template",
"command": false,
"templatePath": "Templates/Movie.md",
"fileNameFormat": {
"enabled": true,
"format": "{{VALUE:fileName}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Movies"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
}
}
]
}
},
"pathHint": [
"Add movie note"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/movies.js",
"contentEncoding": "base64",
"content": "Y29uc3Qgbm90aWNlID0gbXNnID0+IG5ldyBOb3RpY2UobXNnLCA1MDAwKTsKY29uc3QgbG9nID0gbXNnID0+IGNvbnNvbGUubG9nKG1zZyk7Cgpjb25zdCBBUElfS0VZX09QVElPTiA9ICJPTURiIEFQSSBLZXkiOwpjb25zdCBBUElfVVJMID0gImh0dHBzOi8vd3d3Lm9tZGJhcGkuY29tLyI7CmNvbnN0IElNREJfQkFTRV9VUkwgPSAiaHR0cHM6Ly93d3cuaW1kYi5jb20vdGl0bGUvIjsKCm1vZHVsZS5leHBvcnRzID0gewogICAgZW50cnk6IHN0YXJ0LAogICAgc2V0dGluZ3M6IHsKICAgICAgICBuYW1lOiAiTW92aWUgU2NyaXB0IiwKICAgICAgICBhdXRob3I6ICJDaHJpc3RpYW4gQi4gQi4gSG91bWFubiIsCiAgICAgICAgb3B0aW9uczogewogICAgICAgICAgICBbQVBJX0tFWV9PUFRJT05dOiB7CiAgICAgICAgICAgICAgICB0eXBlOiAic2VjcmV0IiwKICAgICAgICAgICAgICAgIGlkOiAib21kYi1hcGkta2V5IiwKICAgICAgICAgICAgICAgIHBsYWNlaG9sZGVyOiAiUGFzdGUgT01EYiBBUEkga2V5IiwKICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uOiAiRnJlZSBhdCBvbWRiYXBpLmNvbS4iLAogICAgICAgICAgICB9LAogICAgICAgIH0KICAgIH0KfQoKbGV0IFF1aWNrQWRkOwpsZXQgU2V0dGluZ3M7Cgphc3luYyBmdW5jdGlvbiBzdGFydChwYXJhbXMsIHNldHRpbmdzKSB7CiAgICBRdWlja0FkZCA9IHBhcmFtczsKICAgIFNldHRpbmdzID0gc2V0dGluZ3M7CgogICAgY29uc3QgcXVlcnkgPSBhd2FpdCBRdWlja0FkZC5xdWlja0FkZEFwaS5pbnB1dFByb21wdCgiRW50ZXIgbW92aWUgdGl0bGUgb3IgSU1EQiBJRDogIik7CiAgICBpZiAoIXF1ZXJ5KSB7CiAgICAgICAgbm90aWNlKCJObyBxdWVyeSBlbnRlcmVkLiIpOwogICAgICAgIHRocm93IG5ldyBFcnJvcigiTm8gcXVlcnkgZW50ZXJlZC4iKTsKICAgIH0KCiAgICBsZXQgc2VsZWN0ZWRTaG93OwoKICAgIGlmIChpc0ltZGJJZChxdWVyeSkpIHsKICAgICAgICBzZWxlY3RlZFNob3cgPSBhd2FpdCBnZXRCeUltZGJJZChxdWVyeSk7CiAgICB9IGVsc2UgewogICAgICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBnZXRCeVF1ZXJ5KHF1ZXJ5KTsKCiAgICAgICAgY29uc3QgY2hvaWNlID0gYXdhaXQgUXVpY2tBZGQucXVpY2tBZGRBcGkuc3VnZ2VzdGVyKHJlc3VsdHMubWFwKGZvcm1hdFRpdGxlRm9yU3VnZ2VzdGlvbiksIHJlc3VsdHMpOwogICAgICAgIGlmICghY2hvaWNlKSB7CiAgICAgICAgICAgIG5vdGljZSgiTm8gY2hvaWNlIHNlbGVjdGVkLiIpOwogICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoIk5vIGNob2ljZSBzZWxlY3RlZC4iKTsKICAgICAgICB9CgogICAgICAgIHNlbGVjdGVkU2hvdyA9IGF3YWl0IGdldEJ5SW1kYklkKGNob2ljZS5pbWRiSUQpOwogICAgfQoKICAgIFF1aWNrQWRkLnZhcmlhYmxlcyA9IHsKICAgICAgICAuLi5zZWxlY3RlZFNob3csCiAgICAgICAgaW1kYlVybDogSU1EQl9CQVNFX1VSTCArIHNlbGVjdGVkU2hvdy5pbWRiSUQsCiAgICAgICAgUmVsZWFzZWQ6IGZvcm1hdERhdGVTdHJpbmcoc2VsZWN0ZWRTaG93LlJlbGVhc2VkKSwKICAgICAgICBhY3RvckxpbmtzOiBsaW5raWZ5TGlzdChzZWxlY3RlZFNob3cuQWN0b3JzLnNwbGl0KCIsIikpLAogICAgICAgIGdlbnJlTGlua3M6IGxpbmtpZnlMaXN0KHNlbGVjdGVkU2hvdy5HZW5yZS5zcGxpdCgiLCIpKSwKICAgICAgICBkaXJlY3Rvckxpbms6IGxpbmtpZnlMaXN0KHNlbGVjdGVkU2hvdy5EaXJlY3Rvci5zcGxpdCgiLCIpKSwKICAgICAgICBmaWxlTmFtZTogcmVwbGFjZUlsbGVnYWxGaWxlTmFtZUNoYXJhY3RlcnNJblN0cmluZyhzZWxlY3RlZFNob3cuVGl0bGUpLAogICAgICAgIHR5cGVMaW5rOiBgW1ske3NlbGVjdGVkU2hvdy5UeXBlID09PSAibW92aWUiID8gIk1vdmllcyIgOiAiU2VyaWVzIn1dXWAsCiAgICAgICAgbGFuZ3VhZ2VMb3dlcjogc2VsZWN0ZWRTaG93Lkxhbmd1YWdlLnRvTG93ZXJDYXNlKCksCiAgICB9Cn0KCmZ1bmN0aW9uIGlzSW1kYklkKHN0cikgewogICAgcmV0dXJuIC9edHRcZCskLy50ZXN0KHN0cik7Cn0KCmZ1bmN0aW9uIGZvcm1hdFRpdGxlRm9yU3VnZ2VzdGlvbihyZXN1bHRJdGVtKSB7CiAgICByZXR1cm4gYCgke3Jlc3VsdEl0ZW0uVHlwZSA9PT0gIm1vdmllIiA/ICJNIiA6ICJUViJ9KSAke3Jlc3VsdEl0ZW0uVGl0bGV9ICgke3Jlc3VsdEl0ZW0uWWVhcn0pYDsKfQoKZnVuY3Rpb24gZm9ybWF0RGF0ZVN0cmluZyhkYXRlU3RyaW5nKSB7CiAgICBpZiAoIWRhdGVTdHJpbmcgfHwgZGF0ZVN0cmluZyA9PT0gIk4vQSIpIHJldHVybiAiIjsKICAgIGNvbnN0IHBhcnRzID0gZGF0ZVN0cmluZy5zcGxpdCgnICcpOwogICAgaWYgKHBhcnRzLmxlbmd0aCAhPT0gMykgcmV0dXJuIGRhdGVTdHJpbmc7CiAgICBjb25zdCBbZGF5LCBtb250aCwgeWVhcl0gPSBwYXJ0czsKICAgIGNvbnN0IG1vbnRoTmFtZXMgPSBbJ0phbicsICdGZWInLCAnTWFyJywgJ0FwcicsICdNYXknLCAnSnVuJywgJ0p1bCcsICdBdWcnLCAnU2VwJywgJ09jdCcsICdOb3YnLCAnRGVjJ107CiAgICBjb25zdCBtb250aEluZGV4ID0gbW9udGhOYW1lcy5pbmRleE9mKG1vbnRoKTsKICAgIGlmIChtb250aEluZGV4IDwgMCkgcmV0dXJuIGRhdGVTdHJpbmc7CgogICAgY29uc3QgZGF0ZSA9IG5ldyBEYXRlKE51bWJlcih5ZWFyKSwgbW9udGhJbmRleCwgTnVtYmVyKGRheSkpOwogICAgaWYgKGlzTmFOKGRhdGUuZ2V0VGltZSgpKSkgcmV0dXJuIGRhdGVTdHJpbmc7CgogICAgLy8gRm9ybWF0IHRoZSBkYXRlIGFzIHl5eXktbW0tZGQKICAgIGNvbnN0IGZvcm1hdHRlZFllYXIgPSBkYXRlLmdldEZ1bGxZZWFyKCk7CiAgICBjb25zdCBmb3JtYXR0ZWRNb250aCA9IFN0cmluZyhkYXRlLmdldE1vbnRoKCkgKyAxKS5wYWRTdGFydCgyLCAnMCcpOwogICAgY29uc3QgZm9ybWF0dGVkRGF5ID0gU3RyaW5nKGRhdGUuZ2V0RGF0ZSgpKS5wYWRTdGFydCgyLCAnMCcpOwoKICAgIHJldHVybiBgJHtmb3JtYXR0ZWRZZWFyfS0ke2Zvcm1hdHRlZE1vbnRofS0ke2Zvcm1hdHRlZERheX1gOwp9Cgphc3luYyBmdW5jdGlvbiBnZXRCeVF1ZXJ5KHF1ZXJ5KSB7CiAgICBjb25zdCBzZWFyY2hSZXN1bHRzID0gYXdhaXQgYXBpR2V0KEFQSV9VUkwsIHsKICAgICAgICAicyI6IHF1ZXJ5LAogICAgfSk7CgogICAgaWYgKCFzZWFyY2hSZXN1bHRzLlNlYXJjaCB8fCAhc2VhcmNoUmVzdWx0cy5TZWFyY2gubGVuZ3RoKSB7CiAgICAgICAgbm90aWNlKCJObyByZXN1bHRzIGZvdW5kLiIpOwogICAgICAgIHRocm93IG5ldyBFcnJvcigiTm8gcmVzdWx0cyBmb3VuZC4iKTsKICAgIH0KCiAgICByZXR1cm4gc2VhcmNoUmVzdWx0cy5TZWFyY2g7Cn0KCmFzeW5jIGZ1bmN0aW9uIGdldEJ5SW1kYklkKGlkKSB7CiAgICBjb25zdCByZXMgPSBhd2FpdCBhcGlHZXQoQVBJX1VSTCwgewogICAgICAgICJpIjogaWQKICAgIH0pOwoKICAgIGlmICghcmVzKSB7CiAgICAgICAgbm90aWNlKCJObyByZXN1bHRzIGZvdW5kLiIpOwogICAgICAgIHRocm93IG5ldyBFcnJvcigiTm8gcmVzdWx0cyBmb3VuZC4iKTsKICAgIH0KCiAgICByZXR1cm4gcmVzOwp9CgpmdW5jdGlvbiBsaW5raWZ5TGlzdChsaXN0KSB7CiAgICAvLyBSZXR1cm4gYSByZWFsIGFycmF5IG9mIHdpa2lsaW5rIHN0cmluZ3MuIFF1aWNrQWRkIHdyaXRlcyBhcnJheXMgaW4gYQogICAgLy8gdGVtcGxhdGUncyBmcm9udCBtYXR0ZXIgYXMgcHJvcGVyIE9ic2lkaWFuIExpc3QgcHJvcGVydGllcyAoZWFjaCBpdGVtIGEKICAgIC8vIGNsaWNrYWJsZSBsaW5rKSB2aWEgT2JzaWRpYW4ncyBvd24gWUFNTCBzZXJpYWxpemVyLiBSZXR1cm5pbmcgYSBzdHJpbmcKICAgIC8vIGhlcmUgd291bGQgaW5zdGVhZCBicmVhayB0aGUgWUFNTCAoc2VlIGlzc3VlICM2NjIpLgogICAgaWYgKCFBcnJheS5pc0FycmF5KGxpc3QpKSByZXR1cm4gW107CgogICAgcmV0dXJuIGxpc3QKICAgICAgICAubWFwKGl0ZW0gPT4gU3RyaW5nKGl0ZW0pLnRyaW0oKSkKICAgICAgICAuZmlsdGVyKGl0ZW0gPT4gaXRlbS5sZW5ndGggPiAwKQogICAgICAgIC5tYXAoaXRlbSA9PiBgW1ske2l0ZW19XV1gKTsKfQoKZnVuY3Rpb24gcmVwbGFjZUlsbGVnYWxGaWxlTmFtZUNoYXJhY3RlcnNJblN0cmluZyhpbnB1dCkgewogICAgaWYgKCFpbnB1dCkgcmV0dXJuICIiOwogICAgcmV0dXJuIGlucHV0LnJlcGxhY2UoL1tcXCwjJSZce1x9XC8qPD4kJ1wiOkBdL2csICcnKS50cmltKCk7Cn0KCmFzeW5jIGZ1bmN0aW9uIGFwaUdldChfdXJsLCBkYXRhKSB7CiAgICBjb25zdCBwYXJhbXMgPSBuZXcgVVJMU2VhcmNoUGFyYW1zKCk7CiAgICBpZiAoZGF0YSkgewogICAgICAgIE9iamVjdC5lbnRyaWVzKGRhdGEpLmZvckVhY2goKFtrZXksIHZhbHVlXSkgPT4gewogICAgICAgICAgICBpZiAodmFsdWUgIT0gbnVsbCAmJiB2YWx1ZSAhPT0gJycpIHBhcmFtcy5hcHBlbmQoa2V5LCBTdHJpbmcodmFsdWUpKTsKICAgICAgICB9KTsKICAgIH0KCiAgICBjb25zdCBhcGlLZXkgPSBTZXR0aW5ncz8uW0FQSV9LRVlfT1BUSU9OXTsKICAgIGlmICghYXBpS2V5IHx8IFN0cmluZyhhcGlLZXkpLnRyaW0oKSA9PT0gJycpIHsKICAgICAgICBub3RpY2UoJ1BsZWFzZSBzZXQgeW91ciBPTURiIEFQSSBrZXkgaW4gdGhlIHNjcmlwdCBzZXR0aW5ncy4nKTsKICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01pc3NpbmcgT01EYiBBUEkga2V5LicpOwogICAgfQogICAgcGFyYW1zLmFwcGVuZCgnYXBpa2V5JywgU3RyaW5nKGFwaUtleSkudHJpbSgpKTsKCiAgICBjb25zdCBocmVmID0gYCR7QVBJX1VSTH0/JHtwYXJhbXMudG9TdHJpbmcoKX1gOwogICAgY29uc3QgcmVzID0gYXdhaXQgcmVxdWVzdCh7CiAgICAgICAgdXJsOiBocmVmLAogICAgICAgIG1ldGhvZDogJ0dFVCcsCiAgICAgICAgY2FjaGU6ICduby1jYWNoZScsCiAgICAgICAgaGVhZGVyczogewogICAgICAgICAgICAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nLAogICAgICAgIH0sCiAgICB9KTsKCiAgICByZXR1cm4gSlNPTi5wYXJzZShyZXMpOwp9Cg=="
},
{
"kind": "template",
"originalPath": "Templates/Movie.md",
"contentEncoding": "base64",
"content": "LS0tCmNhdGVnb3J5OiAie3tWQUxVRTp0eXBlTGlua319IgpkaXJlY3Rvcjoge3tWQUxVRTpkaXJlY3Rvckxpbmt9fQpnZW5yZToge3tWQUxVRTpnZW5yZUxpbmtzfX0KY2FzdDoge3tWQUxVRTphY3RvckxpbmtzfX0KeWVhcjogInt7VkFMVUU6WWVhcn19IgppbWRiSWQ6ICJ7e1ZBTFVFOmltZGJJRH19IgppbWRiOiAiW0lNRGJdKHt7VkFMVUU6aW1kYlVybH19KSIKcmF0aW5nSW1kYjogInt7VkFMVUU6aW1kYlJhdGluZ319IgpyYXRpbmc6IApjb3ZlcjogInt7VkFMVUU6UG9zdGVyfX0iCi0tLQoKIVtwb3N0ZXJdKHt7VkFMVUU6UG9zdGVyfX0pCgp7e1ZBTFVFOlBsb3R9fQo="
}
]
}


Public Packages Readwise Book Notes.Quickadd.Json (`docs/public/packages/readwise-book-notes.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.28.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-readwise-book-notes",
"qa-pkg-readwise-book-notes-macro"
],
"choices": [
{
"choice": {
"id": "qa-pkg-readwise-book-notes",
"name": "Add Book Notes",
"type": "Template",
"command": true,
"onePageInput": "never",
"templatePath": "Templates/Book Notes.md",
"fileNameFormat": {
"enabled": true,
"format": "{{MACRO:Readwise::getBooks}}"
},
"discoverExistingNotesBeforeCreate": false,
"folder": {
"enabled": true,
"folders": [
"Books"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"copyLinkToClipboard": false,
"openFile": true,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsBehavior": {
"kind": "prompt"
}
},
"pathHint": [
"Add Book Notes"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-readwise-book-notes-macro",
"name": "Readwise",
"type": "Macro",
"command": false,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-readwise-book-notes-macro-body",
"name": "Readwise",
"commands": [
{
"id": "qa-pkg-readwise-book-notes-script",
"name": "readwise",
"type": "UserScript",
"path": "scripts/readwise.js",
"settings": {}
}
]
}
},
"pathHint": [
"Readwise"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/readwise.js",
"contentEncoding": "base64",
"content": "Ly8gUHVsbHMgYm9va3MsIGFydGljbGVzIGFuZCBoaWdobGlnaHRzIGZyb20gUmVhZHdpc2UgaW50byBub3Rlcy4KLy8gZW50cnkgbGV0cyB5b3UgcGljayBhIGNhdGVnb3J5IGFuZCBpdGVtOyBnZXRCb29rcyBwaWNrcyBhIGJvb2sgdGl0bGUgKHVzZSBpdCBpbiBhCi8vIGZpbGUgbmFtZSBmb3JtYXQpLCBpbnN0YUZldGNoQm9vayB3cml0ZXMgdGhhdCBib29rJ3MgaGlnaGxpZ2h0cywgZ2V0RGFpbHlRdW90ZQovLyByZXR1cm5zIGEgcmFuZG9tIHN1cHBsZW1lbnRhbCBoaWdobGlnaHQgYXMgYSBxdW90ZS4KLy8gRG9jczogaHR0cHM6Ly9xdWlja2FkZC5vYnNpZGlhbi5ndWlkZS9kb2NzL0V4YW1wbGVzL1RlbXBsYXRlX0F1dG9tYXRpY0Jvb2tOb3Rlc0Zyb21SZWFkd2lzZQpjb25zdCBBQ0NFU1NfVE9LRU4gPSAiUmVhZHdpc2UgYWNjZXNzIHRva2VuIjsKCm1vZHVsZS5leHBvcnRzID0gewoJZW50cnk6IHN0YXJ0LAoJc2V0dGluZ3M6IHsKCQluYW1lOiAiUmVhZHdpc2UiLAoJCWF1dGhvcjogIkNocmlzdGlhbiBCLiBCLiBIb3VtYW5uIiwKCQlvcHRpb25zOiB7CgkJCVtBQ0NFU1NfVE9LRU5dOiB7CgkJCQl0eXBlOiAic2VjcmV0IiwKCQkJCWlkOiAicmVhZHdpc2UtYWNjZXNzLXRva2VuIiwKCQkJCXBsYWNlaG9sZGVyOiAiUGFzdGUgYWNjZXNzIHRva2VuIiwKCQkJCWRlc2NyaXB0aW9uOiAiRnJvbSByZWFkd2lzZS5pby9hY2Nlc3NfdG9rZW4uIiwKCQkJfSwKCQl9LAoJfSwKCWdldERhaWx5UXVvdGUsCglpbnN0YUZldGNoQm9vaywKCWdldEJvb2tzLAp9Owpjb25zdCBhcGlVcmwgPSAiaHR0cHM6Ly9yZWFkd2lzZS5pby9hcGkvdjIvIjsKY29uc3QgYm9va3MgPSAi8J+TmiBCb29rcyIsCglhcnRpY2xlcyA9ICLwn5OwIEFydGljbGVzIiwKCXR3ZWV0cyA9ICLwn5CkIFR3ZWV0cyIsCglzdXBwbGVtZW50YWxzID0gIvCfkq0gU3VwcGxlbWVudGFscyIsCglwb2RjYXN0cyA9ICLwn46ZIFBvZGNhc3RzIiwKCXNlYXJjaEFsbCA9ICLwn5SNIFNlYXJjaCBBbGwgSGlnaGxpZ2h0cyAoc2xvdyEpIjsKY29uc3QgY2F0ZWdvcmllcyA9IHsKCWJvb2tzLAoJYXJ0aWNsZXMsCgl0d2VldHMsCglzdXBwbGVtZW50YWxzLAoJcG9kY2FzdHMsCglzZWFyY2hBbGwsCn07CmNvbnN0IHJhbmRvbU51bWJlckluUmFuZ2UgPSAobWF4KSA9PiBNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiBtYXgpOwpsZXQgdG9rZW47CmxldCBxdWlja0FkZEFwaTsKbGV0IG5vdGljZSA9ICgpID0+IHt9OwoKZnVuY3Rpb24gdXNlVG9rZW4ocGFyYW1zLCBzZXR0aW5ncykgewoJbm90aWNlID0gKG1lc3NhZ2UsIHRpbWVvdXQpID0+IG5ldyBwYXJhbXMub2JzaWRpYW4uTm90aWNlKG1lc3NhZ2UsIHRpbWVvdXQpOwoJdG9rZW4gPSAoc2V0dGluZ3M/LltBQ0NFU1NfVE9LRU5dID8/ICIiKS50cmltKCk7CglpZiAoIXRva2VuKSB7CgkJbm90aWNlKCJTZXQgeW91ciBSZWFkd2lzZSBhY2Nlc3MgdG9rZW4gaW4gdGhlIHNjcmlwdCBzZXR0aW5ncy4iKTsKCQl0aHJvdyBuZXcgRXJyb3IoIlJlYWR3aXNlIGFjY2VzcyB0b2tlbiBpcyBub3Qgc2V0LiIpOwoJfQp9Cgphc3luYyBmdW5jdGlvbiBzdGFydChwYXJhbXMsIHNldHRpbmdzKSB7Cgl1c2VUb2tlbihwYXJhbXMsIHNldHRpbmdzKTsKCSh7IHF1aWNrQWRkQXBpIH0gPSBwYXJhbXMpOwoJbGV0IGhpZ2hsaWdodHM7Cgljb25zdCBjYXRlZ29yeSA9IGF3YWl0IGNhdGVnb3J5UHJvbXB0SGFuZGxlcigpOwoJaWYgKCFjYXRlZ29yeSkgcmV0dXJuOwoKCWlmIChjYXRlZ29yeSA9PT0gInNlYXJjaEFsbCIpIHsKCQloaWdobGlnaHRzID0gYXdhaXQgZ2V0QWxsSGlnaGxpZ2h0cygpOwoJfSBlbHNlIHsKCQljb25zdCBpdGVtcyA9IGF3YWl0IGdldEhpZ2hsaWdodHNCeUNhdGVnb3J5KGNhdGVnb3J5KTsKCQlpZiAoaXRlbXMubGVuZ3RoID09PSAwKSB7CgkJCW5vdGljZShgTm90aGluZyB1bmRlciAke2NhdGVnb3JpZXNbY2F0ZWdvcnldfSBpbiBSZWFkd2lzZSB5ZXQuYCk7CgkJCXJldHVybiAiIjsKCQl9CgoJCWNvbnN0IGl0ZW0gPSBhd2FpdCBxdWlja0FkZEFwaS5zdWdnZXN0ZXIoCgkJCWl0ZW1zLm1hcCgoaXRlbSkgPT4gaXRlbS50aXRsZSksCgkJCWl0ZW1zCgkJKTsKCQlpZiAoIWl0ZW0pIHJldHVybjsKCgkJcGFyYW1zLnZhcmlhYmxlc1siYXV0aG9yIl0gPSBgW1ske2l0ZW0uYXV0aG9yfV1dYDsKCgkJaGlnaGxpZ2h0cyA9IChhd2FpdCBnZXRIaWdobGlnaHRzRm9yRWxlbWVudChpdGVtKSkucmV2ZXJzZSgpOwoJfQoKCWlmIChoaWdobGlnaHRzLmxlbmd0aCA9PT0gMCkgewoJCW5vdGljZSgiTm8gaGlnaGxpZ2h0cyB0aGVyZSB5ZXQuIik7CgkJcmV0dXJuICIiOwoJfQoKCWNvbnN0IHRleHRUb0FwcGVuZCA9IGF3YWl0IGhpZ2hsaWdodHNQcm9tcHRIYW5kbGVyKGhpZ2hsaWdodHMpOwoJcmV0dXJuICF0ZXh0VG9BcHBlbmQgPyAiIiA6IHRleHRUb0FwcGVuZDsKfQoKLy8gVXNlIGluIGEgZmlsZSBuYW1lIGZvcm1hdDogcGlja3MgYSBib29rIGFuZCByZXR1cm5zIGEgbmFtZSBhIG5vdGUgY2FuIGhhdmUuCi8vIFRoZSBib29rJ3MgcmVhbCB0aXRsZSBhbmQgaWQgYXJlIGtlcHQgaW4gdmFyaWFibGVzIGZvciBpbnN0YUZldGNoQm9vayBhbmQKLy8gZm9yIHt7VkFMVUU6Qm9vayBUaXRsZX19IGluIHRoZSB0ZW1wbGF0ZS4KYXN5bmMgZnVuY3Rpb24gZ2V0Qm9va3MocGFyYW1zLCBzZXR0aW5ncykgewoJdXNlVG9rZW4ocGFyYW1zLCBzZXR0aW5ncyk7Cgljb25zdCBib29rcyA9IGF3YWl0IGdldEhpZ2hsaWdodHNCeUNhdGVnb3J5KCJib29rcyIpOwoJaWYgKGJvb2tzLmxlbmd0aCA9PT0gMCkgewoJCW5vdGljZSgiTm8gYm9va3MgaW4gUmVhZHdpc2UgeWV0LiIpOwoJCXRocm93IG5ldyBFcnJvcigiTm8gYm9va3MgaW4gUmVhZHdpc2UgeWV0LiIpOwoJfQoKCWNvbnN0IGJvb2sgPSBhd2FpdCBwYXJhbXMucXVpY2tBZGRBcGkuc3VnZ2VzdGVyKAoJCWJvb2tzLm1hcCgoYm9vaykgPT4gYm9vay50aXRsZSksCgkJYm9va3MKCSk7CglpZiAoIWJvb2spIHRocm93IG5ldyBFcnJvcigiTm8gYm9vayBzZWxlY3RlZC4iKTsKCglwYXJhbXMudmFyaWFibGVzWyJCb29rIFRpdGxlIl0gPSBib29rLnRpdGxlOwoJcGFyYW1zLnZhcmlhYmxlc1siQm9vayBJZCJdID0gU3RyaW5nKGJvb2suaWQpOwoJcmV0dXJuIGZpbGVOYW1lRm9yKGJvb2sudGl0bGUpOwp9CgovLyBPYnNpZGlhbiByZWZ1c2VzICI6IiBpbiBhIG5hbWUgYW5kIHJlYWRzICIvIiBhbmQgIlwiIGFzIGZvbGRlcnM7IFdpbmRvd3MgYWxzbwovLyByZWZ1c2VzICogPyAiIDwgPiB8LCBhbmQgWyBdICMgXiB8IHdvdWxkIGJyZWFrIHRoZSBbW3t7VElUTEV9fV1dIGxpbmsuCmZ1bmN0aW9uIGZpbGVOYW1lRm9yKHRpdGxlKSB7CglyZXR1cm4gKAoJCXRpdGxlCgkJCS5yZXBsYWNlKC9ccyo6XHMqL2csICIgLSAiKQoJCQkucmVwbGFjZSgvW1xcLyo/Ijw+fCNeW1xdXS9nLCAiIikKCQkJLnJlcGxhY2UoL1xzKy9nLCAiICIpCgkJCS50cmltKCkgfHwgIlVudGl0bGVkIgoJKTsKfQoKYXN5bmMgZnVuY3Rpb24gaW5zdGFGZXRjaEJvb2socGFyYW1zLCBzZXR0aW5ncykgewoJdXNlVG9rZW4ocGFyYW1zLCBzZXR0aW5ncyk7Cgljb25zdCBib29rSWQgPSBwYXJhbXMudmFyaWFibGVzWyJCb29rIElkIl07Cgljb25zdCBib29rVGl0bGUgPSBwYXJhbXMudmFyaWFibGVzWyJCb29rIFRpdGxlIl07CglpZiAoIWJvb2tJZCAmJiAhYm9va1RpdGxlKSByZXR1cm4gYXdhaXQgc3RhcnQocGFyYW1zLCBzZXR0aW5ncyk7CgoJY29uc3QgYm9va3MgPSBhd2FpdCBnZXRIaWdobGlnaHRzQnlDYXRlZ29yeSgiYm9va3MiKTsKCS8vIGdldEJvb2tzIHN0b3JlZCB0aGUgaWQsIHNvIHRoZSBsb29rdXAgaXMgZXhhY3QuIEEgdGl0bGUgc2V0IHNvbWUgb3RoZXIgd2F5CgkvLyAoYW4gb2xkZXIgdGVtcGxhdGUsIGEge3tWQUxVRTpCb29rIFRpdGxlfX0gcHJvbXB0KSBtYXRjaGVzIGV4YWN0bHkgZmlyc3QsCgkvLyB0aGVuIHBhcnRpYWxseSwgc28gIkR1bmUiIGlzIG5vdCBtaXN0YWtlbiBmb3IgIkR1bmUgTWVzc2lhaCIuCgljb25zdCB3YW50ZWQgPSBTdHJpbmcoYm9va1RpdGxlID8/ICIiKS50b0xvd2VyQ2FzZSgpOwoJY29uc3QgYm9vayA9CgkJYm9va3MuZmluZCgoYikgPT4gU3RyaW5nKGIuaWQpID09PSBTdHJpbmcoYm9va0lkKSkgPz8KCQlib29rcy5maW5kKChiKSA9PiBiLnRpdGxlLnRvTG93ZXJDYXNlKCkgPT09IHdhbnRlZCkgPz8KCQlib29rcy5maW5kKChiKSA9PiB3YW50ZWQgJiYgYi50aXRsZS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKHdhbnRlZCkpOwoJaWYgKCFib29rKSB0aHJvdyBuZXcgRXJyb3IoIkJvb2sgIiArIGJvb2tUaXRsZSArICIgbm90IGZvdW5kLiIpOwoKCXBhcmFtcy52YXJpYWJsZXNbImF1dGhvciJdID0gYFtbJHtib29rLmF1dGhvcn1dXWA7CgoJY29uc3QgaGlnaGxpZ2h0cyA9IChhd2FpdCBnZXRIaWdobGlnaHRzRm9yRWxlbWVudChib29rKSkucmV2ZXJzZSgpOwoJcmV0dXJuIHdyaXRlQWxsSGFuZGxlcihoaWdobGlnaHRzKTsKfQoKYXN5bmMgZnVuY3Rpb24gZ2V0RGFpbHlRdW90ZShwYXJhbXMsIHNldHRpbmdzKSB7Cgl1c2VUb2tlbihwYXJhbXMsIHNldHRpbmdzKTsKCWNvbnN0IGl0ZW1zID0gYXdhaXQgZ2V0SGlnaGxpZ2h0c0J5Q2F0ZWdvcnkoInN1cHBsZW1lbnRhbHMiKTsKCWlmIChpdGVtcy5sZW5ndGggPT09IDApIHsKCQlub3RpY2UoIk5vIHN1cHBsZW1lbnRhbCBoaWdobGlnaHRzIGluIFJlYWR3aXNlIHlldC4iKTsKCQlyZXR1cm4gIiI7Cgl9Cgljb25zdCB0YXJnZXRJdGVtID0gaXRlbXNbcmFuZG9tTnVtYmVySW5SYW5nZShpdGVtcy5sZW5ndGgpXTsKCgljb25zdCBoaWdobGlnaHRzID0gYXdhaXQgZ2V0SGlnaGxpZ2h0c0ZvckVsZW1lbnQodGFyZ2V0SXRlbSk7CglpZiAoaGlnaGxpZ2h0cy5sZW5ndGggPT09IDApIHsKCQlub3RpY2UoYE5vIGhpZ2hsaWdodHMgaW4gJHt0YXJnZXRJdGVtLnRpdGxlfSB5ZXQuYCk7CgkJcmV0dXJuICIiOwoJfQoKCWNvbnN0IHJhbmRvbUhpZ2hsaWdodCA9IGhpZ2hsaWdodHNbcmFuZG9tTnVtYmVySW5SYW5nZShoaWdobGlnaHRzLmxlbmd0aCldOwoKCXJldHVybiBmb3JtYXREYWlseVF1b3RlKHJhbmRvbUhpZ2hsaWdodC50ZXh0LCB0YXJnZXRJdGVtKTsKfQoKYXN5bmMgZnVuY3Rpb24gY2F0ZWdvcnlQcm9tcHRIYW5kbGVyKCkgewoJY29uc3QgY2hvaWNlID0gYXdhaXQgcXVpY2tBZGRBcGkuc3VnZ2VzdGVyKAoJCU9iamVjdC52YWx1ZXMoY2F0ZWdvcmllcyksCgkJT2JqZWN0LmtleXMoY2F0ZWdvcmllcykKCSk7CglpZiAoIWNob2ljZSkgcmV0dXJuIG51bGw7CgoJcmV0dXJuIGNob2ljZTsKfQoKYXN5bmMgZnVuY3Rpb24gaGlnaGxpZ2h0c1Byb21wdEhhbmRsZXIoaGlnaGxpZ2h0cykgewoJY29uc3Qgd3JpdGVBbGwgPSAiV3JpdGUgYWxsIGhpZ2hsaWdodHMgdG8gcGFnZSIsCgkJd3JpdGVPbmUgPSAiV3JpdGUgb25lIGhpZ2hsaWdodCB0byBwYWdlIjsKCWNvbnN0IGNob2ljZXMgPSBbd3JpdGVBbGwsIHdyaXRlT25lXTsKCgljb25zdCBjaG9pY2UgPSBhd2FpdCBxdWlja0FkZEFwaS5zdWdnZXN0ZXIoY2hvaWNlcywgY2hvaWNlcyk7CglpZiAoIWNob2ljZSkgcmV0dXJuIG51bGw7CgoJaWYgKGNob2ljZSA9PSB3cml0ZUFsbCkgcmV0dXJuIHdyaXRlQWxsSGFuZGxlcihoaWdobGlnaHRzKTsKCWVsc2UgcmV0dXJuIGF3YWl0IHdyaXRlT25lSGFuZGxlcihoaWdobGlnaHRzKTsKfQoKZnVuY3Rpb24gd3JpdGVBbGxIYW5kbGVyKGhpZ2hsaWdodHMpIHsKCXJldHVybiBoaWdobGlnaHRzCgkJLm1hcCgoaGwpID0+IHsKCQkJaWYgKGhsLnRleHQgPT0gIk5vIHRpdGxlIikgcmV0dXJuOwoJCQljb25zdCB7IHF1b3RlLCBub3RlIH0gPSB0ZXh0Rm9ybWF0dGVyKGhsLnRleHQsIGhsLm5vdGUpOwoJCQlyZXR1cm4gYCR7cXVvdGV9JHtub3RlfWA7CgkJfSkKCQkuam9pbigiXG5cbiIpOwp9Cgphc3luYyBmdW5jdGlvbiB3cml0ZU9uZUhhbmRsZXIoaGlnaGxpZ2h0cykgewoJY29uc3QgY2hvc2VuSGlnaGxpZ2h0ID0gYXdhaXQgcXVpY2tBZGRBcGkuc3VnZ2VzdGVyKAoJCWhpZ2hsaWdodHMubWFwKChobCkgPT4gaGwudGV4dCksCgkJaGlnaGxpZ2h0cwoJKTsKCWlmICghY2hvc2VuSGlnaGxpZ2h0KSByZXR1cm4gbnVsbDsKCgljb25zdCB7IHF1b3RlLCBub3RlIH0gPSB0ZXh0Rm9ybWF0dGVyKAoJCWNob3NlbkhpZ2hsaWdodC50ZXh0LAoJCWNob3NlbkhpZ2hsaWdodC5ub3RlCgkpOwoKCXJldHVybiBgJHtxdW90ZX0ke25vdGV9YDsKfQoKZnVuY3Rpb24gZm9ybWF0RGFpbHlRdW90ZShzb3VyY2VUZXh0LCBzb3VyY2VJdGVtKSB7CglsZXQgcXVvdGUgPSBzb3VyY2VUZXh0CgkJLnNwbGl0KCJcbiIpCgkJLmZpbHRlcigobGluZSkgPT4gbGluZSAhPSAiIikKCQkubWFwKChsaW5lKSA9PiB7CgkJCXJldHVybiBgPiAke2xpbmV9YDsKCQl9KTsKCgljb25zdCBhdHRyID0gYFxuPlxcLSAke3NvdXJjZUl0ZW0uYXV0aG9yfSwgXyR7c291cmNlSXRlbS50aXRsZX1fYDsKCglyZXR1cm4gYCR7cXVvdGUuam9pbigiXG4iKX0ke2F0dHJ9YDsKfQoKZnVuY3Rpb24gdGV4dEZvcm1hdHRlcihzb3VyY2VUZXh0LCByYXdTb3VyY2VOb3RlKSB7CgkvLyBSZWFkd2lzZSBjYW4gcmV0dXJuIGEgaGlnaGxpZ2h0IHdpdGhvdXQgYSBub3RlIChudWxsL3VuZGVmaW5lZCByYXRoZXIKCS8vIHRoYW4gIiIpOyBub3JtYWxpemUgb25jZSBzbyB0aGUgLmluY2x1ZGVzIHByb2JlIGJlbG93IGNhbid0IHRocm93IGFuZAoJLy8gYWJvcnQgdGhlIHdob2xlIGltcG9ydCBvbiBhIHNpbmdsZSBub3RlLWxlc3MgaGlnaGxpZ2h0LgoJY29uc3Qgc291cmNlTm90ZSA9IHJhd1NvdXJjZU5vdGUgPz8gIiI7CglsZXQgcXVvdGUgPSBzb3VyY2VUZXh0CgkJLnNwbGl0KCJcbiIpCgkJLmZpbHRlcigobGluZSkgPT4gbGluZSAhPSAiIikKCQkubWFwKChsaW5lKSA9PiB7CgkJCWlmIChzb3VyY2VOb3RlLmluY2x1ZGVzKCIuaDEiKSkgcmV0dXJuIGAjIyAke2xpbmV9YDsKCQkJZWxzZSByZXR1cm4gYD4gJHtsaW5lfWA7CgkJfSkKCQkuam9pbigiXG4iKTsKCglsZXQgbm90ZTsKCglpZiAoc291cmNlTm90ZS5pbmNsdWRlcygiLmgxIikgfHwgc291cmNlTm90ZSA9PSAiIiB8fCAhc291cmNlTm90ZSkgewoJCW5vdGUgPSAiIjsKCX0gZWxzZSB7CgkJbm90ZSA9ICJcblxuIiArIHNvdXJjZU5vdGU7Cgl9CgoJcmV0dXJuIHsgcXVvdGUsIG5vdGUgfTsKfQoKLy8gUmVhZHdpc2UgbGlzdHMgYXQgbW9zdCAxMDAwIGl0ZW1zIHBlciBwYWdlOyB0aGVzZSByZXR1cm4gZXZlcnkgcGFnZSdzIGl0ZW1zLgpjb25zdCBQQUdFX1NJWkUgPSAxMDAwOwoKYXN5bmMgZnVuY3Rpb24gZ2V0SGlnaGxpZ2h0c0J5Q2F0ZWdvcnkoY2F0ZWdvcnkpIHsKCXJldHVybiBhcGlHZXRBbGwoYCR7YXBpVXJsfWJvb2tzYCwgeyBjYXRlZ29yeSwgcGFnZV9zaXplOiBQQUdFX1NJWkUgfSk7Cn0KCmFzeW5jIGZ1bmN0aW9uIGdldEhpZ2hsaWdodHNGb3JFbGVtZW50KGVsZW1lbnQpIHsKCXJldHVybiBhcGlHZXRBbGwoYCR7YXBpVXJsfWhpZ2hsaWdodHNgLCB7CgkJYm9va19pZDogZWxlbWVudC5pZCwKCQlwYWdlX3NpemU6IFBBR0VfU0laRSwKCX0pOwp9Cgphc3luYyBmdW5jdGlvbiBnZXRBbGxIaWdobGlnaHRzKCkgewoJcmV0dXJuIGFwaUdldEFsbChgJHthcGlVcmx9aGlnaGxpZ2h0c2AsIHsgcGFnZV9zaXplOiBQQUdFX1NJWkUgfSk7Cn0KCi8vIEZvbGxvd3MgYG5leHRgIG9uZSBwYWdlIGF0IGEgdGltZTogdGhlIGxpc3QgZW5kcG9pbnRzIGFsbG93IDIwIHJlcXVlc3RzIGEKLy8gbWludXRlLCBzbyBmZXRjaGluZyBwYWdlcyBpbiBwYXJhbGxlbCB3b3VsZCB0cmlwIHRoZSBsaW1pdCBvbiBiaWcgbGlicmFyaWVzLgphc3luYyBmdW5jdGlvbiBhcGlHZXRBbGwodXJsLCBkYXRhKSB7Cgljb25zdCByZXN1bHRzID0gW107CglsZXQgbmV4dCA9IHdpdGhRdWVyeSh1cmwsIGRhdGEpOwoJd2hpbGUgKG5leHQpIHsKCQljb25zdCBwYWdlID0gYXdhaXQgYXBpR2V0KG5leHQpOwoJCXJlc3VsdHMucHVzaCguLi5wYWdlLnJlc3VsdHMpOwoJCW5leHQgPSBwYWdlLm5leHQ7Cgl9CglyZXR1cm4gcmVzdWx0czsKfQoKZnVuY3Rpb24gd2l0aFF1ZXJ5KHVybCwgZGF0YSkgewoJY29uc3QgZmluYWxVUkwgPSBuZXcgVVJMKHVybCk7Cglmb3IgKGNvbnN0IFtrZXksIHZhbHVlXSBvZiBPYmplY3QuZW50cmllcyhkYXRhID8/IHt9KSkgewoJCWZpbmFsVVJMLnNlYXJjaFBhcmFtcy5zZXQoa2V5LCB2YWx1ZSk7Cgl9CglyZXR1cm4gZmluYWxVUkwudG9TdHJpbmcoKTsKfQoKYXN5bmMgZnVuY3Rpb24gYXBpR2V0KHVybCwgZGF0YSkgewoJY29uc3QgZmluYWxVUkwgPSB3aXRoUXVlcnkodXJsLCBkYXRhKTsKCWZvciAobGV0IGF0dGVtcHQgPSAxOyA7IGF0dGVtcHQrKykgewoJCWNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKGZpbmFsVVJMLCB7CgkJCW1ldGhvZDogIkdFVCIsCgkJCWNhY2hlOiAibm8tY2FjaGUiLAoJCQloZWFkZXJzOiB7CgkJCQkiQ29udGVudC1UeXBlIjogImFwcGxpY2F0aW9uL2pzb24iLAoJCQkJQXV0aG9yaXphdGlvbjogYFRva2VuICR7dG9rZW59YCwKCQkJfSwKCQl9KTsKCQkvLyBPdmVyIHRoZSByYXRlIGxpbWl0OiBSZWFkd2lzZSBzYXlzIGhvdyBsb25nIHRvIHdhaXQsIHNvIHdhaXQgYW5kIGdvIGFnYWluLgoJCWlmIChyZXMuc3RhdHVzID09PSA0MjkgJiYgYXR0ZW1wdCA8IDQpIHsKCQkJY29uc3Qgc2Vjb25kcyA9IE51bWJlcihyZXMuaGVhZGVycy5nZXQoIlJldHJ5LUFmdGVyIikpIHx8IDYwOwoJCQlub3RpY2UoYFJlYWR3aXNlIHJhdGUgbGltaXQgcmVhY2hlZCwgd2FpdGluZyAke3NlY29uZHN9IHPigKZgLCBzZWNvbmRzICogMTAwMCk7CgkJCWF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIHNlY29uZHMgKiAxMDAwKSk7CgkJCWNvbnRpbnVlOwoJCX0KCQlpZiAoIXJlcy5vaykgewoJCQljb25zdCBoaW50ID0KCQkJCXJlcy5zdGF0dXMgPT09IDQwMSB8fCByZXMuc3RhdHVzID09PSA0MDMgPyAiY2hlY2sgeW91ciBhY2Nlc3MgdG9rZW4iCgkJCQk6IHJlcy5zdGF0dXMgPT09IDQyOSA/ICJyYXRlIGxpbWl0IHJlYWNoZWQsIHRyeSBhZ2FpbiBpbiBhIG1pbnV0ZSIKCQkJCTogInJlcXVlc3QgZmFpbGVkIjsKCQkJdGhyb3cgbmV3IEVycm9yKGBSZWFkd2lzZSBBUEkgZXJyb3IgJHtyZXMuc3RhdHVzfTogJHtoaW50fS5gKTsKCQl9CgkJcmV0dXJuIGF3YWl0IHJlcy5qc29uKCk7Cgl9Cn0K"
},
{
"kind": "template",
"originalPath": "Templates/Book Notes.md",
"contentEncoding": "base64",
"content": "LS0tCmltYWdlOgp0YWdzOiBpbi9ib29rcwphbGlhc2VzOgogICAgLSAie3tWQUxVRTpCb29rIFRpdGxlfX0iCmNzc2NsYXNzOgotLS0KCiMgVGl0bGU6IFtbe3tUSVRMRX19XV0KCiMjIE1ldGFkYXRhCgpUYWdzOjoKVHlwZTo6IFtbQm9va11dCkF1dGhvcjo6IHt7VkFMVUU6YXV0aG9yfX0KUmVmZXJlbmNlOjoKUmF0aW5nOjoKUmV2aWV3ZWQgRGF0ZTo6IFtbe3tEQVRFOllZWVktTU0tREQgLSBkZGQgTU1NIER9fV1dCkZpbmlzaGVkIFllYXI6OiBbW3t7REFURTpZWVlZfX1dXQoKIyBUaG91Z2h0cwoKIyBBY3Rpb25zIFRha2VuIC8gQ2hhbmdlcwoKIyBTdW1tYXJ5IG9mIEtleSBQb2ludHMKCiMgSGlnaGxpZ2h0cyAmIE5vdGVzCgp7e01BQ1JPOlJlYWR3aXNlOjppbnN0YUZldGNoQm9va319Cg=="
}
]
}


Public Packages Todoist Tasks.Quickadd.Json (`docs/public/packages/todoist-tasks.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-todoist-tasks",
"qa-pkg-todoist-tasks-macro"
],
"choices": [
{
"choice": {
"id": "qa-pkg-todoist-tasks",
"name": "Import Todoist tasks",
"type": "Capture",
"command": true,
"appendLink": false,
"copyLinkToClipboard": false,
"captureTo": "Todoist tasks.md",
"captureToActiveFile": false,
"captureToCanvasNodeId": "",
"activeFileWritePosition": "cursor",
"createFileIfItDoesntExist": {
"enabled": true,
"createWithTemplate": false,
"template": ""
},
"format": {
"enabled": true,
"format": "{{MACRO:Todoist}}"
},
"insertAfter": {
"enabled": false,
"after": "",
"insertAtEnd": false,
"considerSubsections": false,
"createIfNotFound": false,
"createIfNotFoundLocation": "top",
"inline": false,
"replaceExisting": false,
"blankLineAfterMatchMode": "auto",
"promptHeading": false
},
"insertBefore": {
"enabled": false,
"before": "",
"createIfNotFound": false,
"createIfNotFoundLocation": "top"
},
"newLineCapture": {
"enabled": false,
"direction": "below"
},
"prepend": false,
"task": false,
"openFile": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"mode": "default",
"focus": true
},
"templater": {
"afterCapture": "none"
}
},
"pathHint": [
"Import Todoist tasks"
],
"parentChoiceId": null
},
{
"choice": {
"id": "qa-pkg-todoist-tasks-macro",
"name": "Todoist",
"type": "Macro",
"command": false,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-todoist-tasks-macro-body",
"name": "Todoist",
"commands": [
{
"id": "qa-pkg-todoist-tasks-script",
"name": "todoistTaskSync",
"type": "UserScript",
"path": "scripts/todoistTaskSync.js",
"settings": {}
}
]
}
},
"pathHint": [
"Todoist"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/todoistTaskSync.js",
"contentEncoding": "base64",
"content": "Ly8gSW1wb3J0cyBUb2RvaXN0IHRhc2tzIGludG8gYSBub3RlIGFzIFRhc2tzLXBsdWdpbiBjaGVja2xpc3QgaXRlbXMuCi8vIFRhbGtzIHRvIHRoZSBUb2RvaXN0IEFQSSB2MSBkaXJlY3RseTogaHR0cHM6Ly9kZXZlbG9wZXIudG9kb2lzdC5jb20vYXBpL3YxLwovLyBEb2NzOiBodHRwczovL3F1aWNrYWRkLm9ic2lkaWFuLmd1aWRlL2RvY3MvRXhhbXBsZXMvQ2FwdHVyZV9GZXRjaFRhc2tzRnJvbVRvZG9pc3QKY29uc3QgQVBJX1RPS0VOID0gIlRvZG9pc3QgQVBJIHRva2VuIjsKY29uc3QgQ09NUExFVEVfVEFTS1MgPSAiQ29tcGxldGUgaW1wb3J0ZWQgdGFza3MgaW4gVG9kb2lzdCI7CmNvbnN0IFRPRE9JU1RfQVBJID0gImh0dHBzOi8vYXBpLnRvZG9pc3QuY29tL2FwaS92MSI7Cgptb2R1bGUuZXhwb3J0cyA9IHsKICAgIGVudHJ5OiBhc3luYyAocGFyYW1zLCBzZXR0aW5ncykgPT4gewogICAgICAgIGNvbnN0IGV4cG9ydHMgPSB7U2VsZWN0RnJvbUFsbFRhc2tzLCBHZXRBbGxUYXNrc0Zyb21Qcm9qZWN0LCBHZXRBbGxUYXNrc0Zyb21TZWN0aW9ufTsKICAgICAgICBjb25zdCBuYW1lcyA9IE9iamVjdC5rZXlzKGV4cG9ydHMpOwogICAgICAgIGNvbnN0IHNlbGVjdGVkID0gYXdhaXQgcGFyYW1zLnF1aWNrQWRkQXBpLnN1Z2dlc3RlcihuYW1lcywgbmFtZXMsICJJbXBvcnQgVG9kb2lzdCB0YXNrcyIpOwogICAgICAgIGlmICghc2VsZWN0ZWQpIHJldHVybiAiIjsKICAgICAgICByZXR1cm4gZXhwb3J0c1tzZWxlY3RlZF0ocGFyYW1zLCBzZXR0aW5ncyk7CiAgICB9LAogICAgc2V0dGluZ3M6IHsKICAgICAgICBuYW1lOiAiVG9kb2lzdCIsCiAgICAgICAgYXV0aG9yOiAiQ2hyaXN0aWFuIEIuIEIuIEhvdW1hbm4iLAogICAgICAgIG9wdGlvbnM6IHsKICAgICAgICAgICAgW0FQSV9UT0tFTl06IHsKICAgICAgICAgICAgICAgIHR5cGU6ICJzZWNyZXQiLAogICAgICAgICAgICAgICAgaWQ6ICJ0b2RvaXN0LWFwaS10b2tlbiIsCiAgICAgICAgICAgICAgICBwbGFjZWhvbGRlcjogIlBhc3RlIEFQSSB0b2tlbiIsCiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbjogIkZyb20gVG9kb2lzdCdzIERldmVsb3BlciBzZXR0aW5ncy4iLAogICAgICAgICAgICB9LAogICAgICAgICAgICBbQ09NUExFVEVfVEFTS1NdOiB7CiAgICAgICAgICAgICAgICB0eXBlOiAiY2hlY2tib3giLAogICAgICAgICAgICAgICAgZGVmYXVsdFZhbHVlOiB0cnVlLAogICAgICAgICAgICAgICAgZGVzY3JpcHRpb246ICJNYXJrIGVhY2ggaW1wb3J0ZWQgdGFzayBhcyBjb21wbGV0ZSBpbiBUb2RvaXN0LCBzbyBpdCBpcyBvbmx5IGltcG9ydGVkIG9uY2UuIiwKICAgICAgICAgICAgfSwKICAgICAgICB9LAogICAgfSwKICAgIFNlbGVjdEZyb21BbGxUYXNrcywKICAgIEdldEFsbFRhc2tzRnJvbVByb2plY3QsCiAgICBHZXRBbGxUYXNrc0Zyb21TZWN0aW9uLAp9OwoKLyogRXhwb3J0cyAqLwphc3luYyBmdW5jdGlvbiBTZWxlY3RGcm9tQWxsVGFza3MocGFyYW1zLCBzZXR0aW5ncykgewogICAgY29uc3QgdG9kb2lzdCA9IGNyZWF0ZVRvZG9pc3RDbGllbnQocGFyYW1zLCBzZXR0aW5ncyk7CiAgICBjb25zdCB0YXNrcyA9IGF3YWl0IHRvZG9pc3QuZ2V0QWxsKCIvdGFza3MiKTsKICAgIGlmICh0YXNrcy5sZW5ndGggPT09IDApIHsKICAgICAgICBuZXcgcGFyYW1zLm9ic2lkaWFuLk5vdGljZSgiTm8gdGFza3MgaW4gVG9kb2lzdC4iKTsKICAgICAgICByZXR1cm4gIiI7CiAgICB9CgogICAgY29uc3QgbGFiZWxzID0gdGFza3MubWFwKCh0YXNrLCBpbmRleCkgPT4gYCR7aW5kZXggKyAxfS4gJHt0YXNrLmNvbnRlbnR9YCk7CiAgICBjb25zdCBzZWxlY3RlZExhYmVscyA9IGF3YWl0IHBhcmFtcy5xdWlja0FkZEFwaS5jaGVja2JveFByb21wdChsYWJlbHMpOwogICAgY29uc3Qgc2VsZWN0ZWRUYXNrcyA9IHRhc2tzLmZpbHRlcigoXywgaW5kZXgpID0+IHNlbGVjdGVkTGFiZWxzLmluY2x1ZGVzKGxhYmVsc1tpbmRleF0pKTsKCiAgICByZXR1cm4gaW1wb3J0VGFza3MocGFyYW1zLCBzZXR0aW5ncywgdG9kb2lzdCwgc2VsZWN0ZWRUYXNrcywgInlvdXIgc2VsZWN0aW9uIik7Cn0KCmFzeW5jIGZ1bmN0aW9uIEdldEFsbFRhc2tzRnJvbVByb2plY3QocGFyYW1zLCBzZXR0aW5ncykgewogICAgY29uc3QgdG9kb2lzdCA9IGNyZWF0ZVRvZG9pc3RDbGllbnQocGFyYW1zLCBzZXR0aW5ncyk7CiAgICBjb25zdCBbcHJvamVjdHMsIHRhc2tzXSA9IGF3YWl0IFByb21pc2UuYWxsKFt0b2RvaXN0LmdldEFsbCgiL3Byb2plY3RzIiksIHRvZG9pc3QuZ2V0QWxsKCIvdGFza3MiKV0pOwogICAgY29uc3QgdGFza3NJbiA9IChwcm9qZWN0KSA9PiB0YXNrcy5maWx0ZXIodGFzayA9PiB0YXNrLnByb2plY3RfaWQgPT09IHByb2plY3QuaWQpOwoKICAgIGNvbnN0IHByb2plY3QgPSBhd2FpdCBwYXJhbXMucXVpY2tBZGRBcGkuc3VnZ2VzdGVyKAogICAgICAgIHByb2plY3RzLm1hcChwcm9qZWN0ID0+IGAke3Byb2plY3QubmFtZX0gKCR7dGFza3NJbihwcm9qZWN0KS5sZW5ndGh9KWApLAogICAgICAgIHByb2plY3RzLAogICAgICAgICJJbXBvcnQgYWxsIHRhc2tzIGZyb20gcHJvamVjdCIsCiAgICApOwogICAgaWYgKCFwcm9qZWN0KSByZXR1cm4gIiI7CgogICAgcmV0dXJuIGltcG9ydFRhc2tzKHBhcmFtcywgc2V0dGluZ3MsIHRvZG9pc3QsIHRhc2tzSW4ocHJvamVjdCksIGAnJHtwcm9qZWN0Lm5hbWV9J2ApOwp9Cgphc3luYyBmdW5jdGlvbiBHZXRBbGxUYXNrc0Zyb21TZWN0aW9uKHBhcmFtcywgc2V0dGluZ3MpIHsKICAgIGNvbnN0IHRvZG9pc3QgPSBjcmVhdGVUb2RvaXN0Q2xpZW50KHBhcmFtcywgc2V0dGluZ3MpOwogICAgY29uc3QgW3Byb2plY3RzLCBzZWN0aW9ucywgdGFza3NdID0gYXdhaXQgUHJvbWlzZS5hbGwoWwogICAgICAgIHRvZG9pc3QuZ2V0QWxsKCIvcHJvamVjdHMiKSwKICAgICAgICB0b2RvaXN0LmdldEFsbCgiL3NlY3Rpb25zIiksCiAgICAgICAgdG9kb2lzdC5nZXRBbGwoIi90YXNrcyIpLAogICAgXSk7CiAgICBjb25zdCB0YXNrc0luID0gKHNlY3Rpb24pID0+IHRhc2tzLmZpbHRlcih0YXNrID0+IHRhc2suc2VjdGlvbl9pZCA9PT0gc2VjdGlvbi5pZCk7CiAgICBjb25zdCBwcm9qZWN0TmFtZSA9IChzZWN0aW9uKSA9PiBwcm9qZWN0cy5maW5kKHByb2plY3QgPT4gcHJvamVjdC5pZCA9PT0gc2VjdGlvbi5wcm9qZWN0X2lkKT8ubmFtZSA/PyAiPyI7CgogICAgY29uc3Qgc2VjdGlvbiA9IGF3YWl0IHBhcmFtcy5xdWlja0FkZEFwaS5zdWdnZXN0ZXIoCiAgICAgICAgc2VjdGlvbnMubWFwKHNlY3Rpb24gPT4gYCR7cHJvamVjdE5hbWUoc2VjdGlvbil9ID4gJHtzZWN0aW9uLm5hbWV9ICgke3Rhc2tzSW4oc2VjdGlvbikubGVuZ3RofSlgKSwKICAgICAgICBzZWN0aW9ucywKICAgICAgICAiSW1wb3J0IGFsbCB0YXNrcyBmcm9tIHNlY3Rpb24iLAogICAgKTsKICAgIGlmICghc2VjdGlvbikgcmV0dXJuICIiOwoKICAgIHJldHVybiBpbXBvcnRUYXNrcyhwYXJhbXMsIHNldHRpbmdzLCB0b2RvaXN0LCB0YXNrc0luKHNlY3Rpb24pLCBgJyR7c2VjdGlvbi5uYW1lfSdgKTsKfQoKLyogSGVscGVycyAqLwphc3luYyBmdW5jdGlvbiBpbXBvcnRUYXNrcyhwYXJhbXMsIHNldHRpbmdzLCB0b2RvaXN0LCB0YXNrcywgc291cmNlTmFtZSkgewogICAgaWYgKHRhc2tzLmxlbmd0aCA9PT0gMCkgewogICAgICAgIG5ldyBwYXJhbXMub2JzaWRpYW4uTm90aWNlKGBObyB0YXNrcyBpbiAke3NvdXJjZU5hbWV9LmApOwogICAgICAgIHJldHVybiAiIjsKICAgIH0KCiAgICAvLyBGb3JtYXQgYmVmb3JlIGNvbXBsZXRpbmc6IGEgcmVjdXJyaW5nIHRhc2sgZ2V0cyBhIG5ldyBkdWUgZGF0ZSB3aGVuIGNvbXBsZXRlZC4KICAgIGNvbnN0IG91dHB1dCA9IGZvcm1hdFRhc2tzVG9UYXNrc1BsdWdpblRhc2sodGFza3MpOwoKICAgIC8vIElNUE9SVEFOVDogdGhpcyBjb21wbGV0ZXMgdGhlIGltcG9ydGVkIHRhc2tzIGluIFRvZG9pc3QuCiAgICAvLyBVbnRpY2sgIkNvbXBsZXRlIGltcG9ydGVkIHRhc2tzIGluIFRvZG9pc3QiIGluIHRoZSBzY3JpcHQgc2V0dGluZ3MgdG8ga2VlcCB0aGVtIG9wZW4uCiAgICBpZiAoc2V0dGluZ3NbQ09NUExFVEVfVEFTS1NdKSB7CiAgICAgICAgY29uc3QgZmFpbGVkID0gYXdhaXQgY2xvc2VTZWxlY3RlZFRhc2tzKHRvZG9pc3QsIHRhc2tzKTsKICAgICAgICBpZiAoZmFpbGVkLmxlbmd0aCA+IDApIHsKICAgICAgICAgICAgbmV3IHBhcmFtcy5vYnNpZGlhbi5Ob3RpY2UoYENvdWxkIG5vdCBjb21wbGV0ZSAke2ZhaWxlZC5sZW5ndGh9IG9mICR7dGFza3MubGVuZ3RofSBpbXBvcnRlZCB0YXNrcyBpbiBUb2RvaXN0OyB0aGV5IGFyZSBzdGlsbCBvcGVuIHRoZXJlLmAsIDEwMDAwKTsKICAgICAgICB9CiAgICB9CgogICAgbmV3IHBhcmFtcy5vYnNpZGlhbi5Ob3RpY2UoYEFkZGVkICR7dGFza3MubGVuZ3RofSAke3Rhc2tzLmxlbmd0aCA9PT0gMSA/ICJ0YXNrIiA6ICJ0YXNrcyJ9IGZyb20gJHtzb3VyY2VOYW1lfS5gKTsKICAgIHJldHVybiBvdXRwdXQ7Cn0KCi8vIFJldHVybnMgdGhlIHRhc2tzIHRoYXQgY291bGQgbm90IGJlIGNvbXBsZXRlZC4gVGhlIG5vdGUgc3RpbGwgZ2V0cyBldmVyeQovLyBpbXBvcnRlZCB0YXNrLCBzbyBhIGZhaWxlZCBjbG9zZSBuZXZlciBsb3NlcyBvbmUgdGhhdCB3YXMgYWxyZWFkeSBjbG9zZWQuCmFzeW5jIGZ1bmN0aW9uIGNsb3NlU2VsZWN0ZWRUYXNrcyh0b2RvaXN0LCB0YXNrcykgewogICAgY29uc3QgZmFpbGVkID0gW107CiAgICBmb3IgKGNvbnN0IHRhc2sgb2YgdGFza3MpIHsKICAgICAgICB0cnkgewogICAgICAgICAgICBhd2FpdCB0b2RvaXN0LnJlcXVlc3QoIlBPU1QiLCBgL3Rhc2tzLyR7dGFzay5pZH0vY2xvc2VgKTsKICAgICAgICB9IGNhdGNoIChlcnJvcikgewogICAgICAgICAgICBjb25zb2xlLmVycm9yKGBUb2RvaXN0OiBjb3VsZCBub3QgY29tcGxldGUgdGFzayAke3Rhc2suaWR9YCwgZXJyb3IpOwogICAgICAgICAgICBmYWlsZWQucHVzaCh0YXNrKTsKICAgICAgICB9CiAgICB9CiAgICByZXR1cm4gZmFpbGVkOwp9CgpmdW5jdGlvbiBmb3JtYXRUYXNrc1RvVGFza3NQbHVnaW5UYXNrKHRhc2tzKSB7CiAgICByZXR1cm4gdGFza3MubWFwKHRhc2sgPT4gewogICAgICAgIGNvbnN0IGR1ZURhdGUgPSBmb3JtYXREdWVEYXRlKHRhc2suZHVlKTsKICAgICAgICByZXR1cm4gZHVlRGF0ZSA/IGAtIFsgXSAke3Rhc2suY29udGVudH0g8J+ThSAke2R1ZURhdGV9YCA6IGAtIFsgXSAke3Rhc2suY29udGVudH1gOwogICAgfSkuam9pbigiXG4iKSArICJcbiI7Cn0KCi8vIGBkdWUuZGF0ZWAgaXMgIllZWVktTU0tREQiLCBhIGZsb2F0aW5nICJZWVlZLU1NLUREVEhIOm1tOnNzIiwgb3IgYSBVVEMKLy8gIllZWVktTU0tRERUSEg6bW06c3NaIiBmb3IgdGFza3Mgd2l0aCBhIGZpeGVkIHRpbWUgem9uZS4KZnVuY3Rpb24gZm9ybWF0RHVlRGF0ZShkdWUpIHsKICAgIGlmICghZHVlPy5kYXRlKSByZXR1cm4gbnVsbDsKICAgIGlmIChkdWUuZGF0ZS5lbmRzV2l0aCgiWiIpKSByZXR1cm4gd2luZG93Lm1vbWVudChkdWUuZGF0ZSkuZm9ybWF0KCJZWVlZLU1NLUREIik7CiAgICByZXR1cm4gZHVlLmRhdGUuc2xpY2UoMCwgMTApOwp9CgpmdW5jdGlvbiBjcmVhdGVUb2RvaXN0Q2xpZW50KHBhcmFtcywgc2V0dGluZ3MpIHsKICAgIGNvbnN0IHRva2VuID0gc2V0dGluZ3NbQVBJX1RPS0VOXTsKICAgIGlmICghdG9rZW4pIHsKICAgICAgICB0aHJvdyBuZXcgRXJyb3IoIkFkZCB5b3VyIFRvZG9pc3QgQVBJIHRva2VuIGluIHRoZSBUb2RvaXN0IHNjcmlwdCdzIHNldHRpbmdzICh0aGUgZ2VhciBuZXh0IHRvIHRoZSBzY3JpcHQgaW4geW91ciBtYWNybykuIik7CiAgICB9CgogICAgYXN5bmMgZnVuY3Rpb24gcmVxdWVzdChtZXRob2QsIHBhdGgpIHsKICAgICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHBhcmFtcy5vYnNpZGlhbi5yZXF1ZXN0VXJsKHsKICAgICAgICAgICAgdXJsOiBgJHtUT0RPSVNUX0FQSX0ke3BhdGh9YCwKICAgICAgICAgICAgbWV0aG9kLAogICAgICAgICAgICBoZWFkZXJzOiB7QXV0aG9yaXphdGlvbjogYEJlYXJlciAke3Rva2VufWB9LAogICAgICAgICAgICB0aHJvdzogZmFsc2UsCiAgICAgICAgfSk7CiAgICAgICAgaWYgKHJlc3BvbnNlLnN0YXR1cyA9PT0gNDAxIHx8IHJlc3BvbnNlLnN0YXR1cyA9PT0gNDAzKSB7CiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgVG9kb2lzdCByZWplY3RlZCB0aGUgQVBJIHRva2VuIChIVFRQICR7cmVzcG9uc2Uuc3RhdHVzfSkuIENoZWNrIHRoZSB0b2tlbiBpbiB0aGUgVG9kb2lzdCBzY3JpcHQncyBzZXR0aW5ncy5gKTsKICAgICAgICB9CiAgICAgICAgaWYgKHJlc3BvbnNlLnN0YXR1cyA+PSA0MDApIHsKICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBUb2RvaXN0IHJlcXVlc3QgJHttZXRob2R9ICR7cGF0aC5zcGxpdCgiPyIpWzBdfSBmYWlsZWQgd2l0aCBIVFRQICR7cmVzcG9uc2Uuc3RhdHVzfS5gKTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIHJlc3BvbnNlLnRleHQgPyByZXNwb25zZS5qc29uIDogbnVsbDsKICAgIH0KCiAgICAvLyBMaXN0IGVuZHBvaW50cyBhcmUgcGFnaW5hdGVkOiBmb2xsb3cgYG5leHRfY3Vyc29yYCB1bnRpbCBpdCBpcyBudWxsLgogICAgYXN5bmMgZnVuY3Rpb24gZ2V0QWxsKHBhdGgpIHsKICAgICAgICBjb25zdCByZXN1bHRzID0gW107CiAgICAgICAgbGV0IGN1cnNvciA9IG51bGw7CiAgICAgICAgZG8gewogICAgICAgICAgICBjb25zdCBxdWVyeSA9IGA/bGltaXQ9MjAwJHtjdXJzb3IgPyBgJmN1cnNvcj0ke2VuY29kZVVSSUNvbXBvbmVudChjdXJzb3IpfWAgOiAiIn1gOwogICAgICAgICAgICBjb25zdCBwYWdlID0gYXdhaXQgcmVxdWVzdCgiR0VUIiwgYCR7cGF0aH0ke3F1ZXJ5fWApOwogICAgICAgICAgICByZXN1bHRzLnB1c2goLi4ucGFnZS5yZXN1bHRzKTsKICAgICAgICAgICAgY3Vyc29yID0gcGFnZS5uZXh0X2N1cnNvcjsKICAgICAgICB9IHdoaWxlIChjdXJzb3IpOwogICAgICAgIHJldHVybiByZXN1bHRzOwogICAgfQoKICAgIHJldHVybiB7cmVxdWVzdCwgZ2V0QWxsfTsKfQo="
}
]
}


Public Packages Toggl Manager.Quickadd.Json (`docs/public/packages/toggl-manager.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-toggl-manager"
],
"choices": [
{
"choice": {
"id": "qa-pkg-toggl-manager",
"name": "Toggl manager",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-toggl-manager-macro",
"name": "Toggl manager",
"commands": [
{
"id": "qa-pkg-toggl-manager-script",
"name": "togglManager",
"type": "UserScript",
"path": "scripts/togglManager.js",
"settings": {}
}
]
}
},
"pathHint": [
"Toggl manager"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/togglManager.js",
"contentEncoding": "base64",
"content": "bGV0IHRvZ2dsQXBpOwpsZXQgcXVpY2tBZGRBcGk7CmxldCBwcm9qZWN0czsKCmNvbnN0IGJhY2sgPSAiPC0gQmFjayI7CmNvbnN0IG1lbnUgPSB7CiAgICAi8J+noCBMZWFybmluZyAmIFNraWxsIERldmVsb3BtZW50IjogewogICAgICAgIHRvZ2dsUHJvamVjdE5hbWU6ICJMZWFybmluZyAmIFNraWxsIERldmVsb3BtZW50IiwKICAgICAgICBtZW51T3B0aW9uczogewogICAgICAgICAgICAi4pyNIE5vdGUgTWFraW5nIjogIk5vdGUgTWFraW5nIiwKICAgICAgICAgICAgIvCfg48gU3BhY2VkIFJlcGV0aXRpb24iOiAiU3BhY2VkIFJlcGV0aXRpb24iLAogICAgICAgICAgICAi8J+TliBSZWFkIExhdGVyIFByb2Nlc3NpbmciOiAiUmVhZCBMYXRlciBQcm9jZXNzaW5nIiwKICAgICAgICAgICAgIvCfkajigI3wn5K7IENvbXB1dGVyIFNjaWVuY2UgJiBTb2Z0d2FyZSBFbmdpbmVlcmluZyI6ICJDb21wdXRlciBTY2llbmNlICYgU29mdHdhcmUgRW5naW5lZXJpbmciLAogICAgICAgIH0KICAgIH0sCiAgICAi8J+ktCBQZXJzb25hbCI6IHsKICAgICAgICB0b2dnbFByb2plY3ROYW1lOiAiUGVyc29uYWwiLAogICAgICAgIG1lbnVPcHRpb25zOiB7CiAgICAgICAgICAgICLwn4+L77iP4oCN4pmC77iPIEV4ZXJjaXNlIjogIkV4ZXJjaXNlIiwKICAgICAgICAgICAgIvCfp7kgQ2hvcmVzIjogIkNob3JlcyIsCiAgICAgICAgICAgICLwn5Go4oCN8J+UrCBTeXN0ZW1zIFdvcmsiOiAiU3lzdGVtcyBXb3JrIiwKICAgICAgICAgICAgIvCfjIAgV2Vla2x5IFJldmlldyI6ICJXZWVrbHkgUmV2aWV3IiwKICAgICAgICAgICAgIvCfk4YgTW9udGhseSBSZXZpZXciOiAiTW9udGhseSBSZXZpZXciLAogICAgICAgICAgICAi4pyUIFBsYW5uaW5nIjogIlBsYW5uaW5nIiwKICAgICAgICB9CiAgICB9LAogICAgIvCfkajigI3wn46TIFNjaG9vbCI6IHsKICAgICAgICB0b2dnbFByb2plY3ROYW1lOiAiU2Nob29sIiwKICAgICAgICBtZW51T3B0aW9uczogewogICAgICAgICAgICAi8J+noCBNYWNoaW5lIEludGVsbGlnZW5jZSAoTUkpIjogIk1hY2hpbmUgSW50ZWxsaWdlbmNlIChNSSkiLAogICAgICAgICAgICAi8J+SviBEYXRhYmFzZSBTeXN0ZW1zIChEQlMpIjogIkRhdGFiYXNlIFN5c3RlbXMgKERCUykiLAogICAgICAgICAgICAi8J+Pg+KAjeKZgiBBZ2lsZSBTb2Z0d2FyZSBFbmdpbmVlcmluZyAoQVNFKSI6ICJBZ2lsZSBTb2Z0d2FyZSBFbmdpbmVlcmluZyAoQVNFKSIsCiAgICAgICAgICAgICLwn5K7IFA1IjogIlA1IiwKICAgICAgICB9CiAgICB9Cn07Cgptb2R1bGUuZXhwb3J0cyA9IGFzeW5jIGZ1bmN0aW9uIHRvZ2dsTWFuYWdlcihwYXJhbXMpIHsKICAgIC8vIEd1YXJkIHRoZSBwbHVnaW4gbG9va3VwIChtaXJyb3JzIGNpdGF0aW9uc01hbmFnZXIuanMpOiB3aXRob3V0IGl0LCBhCiAgICAvLyBtaXNzaW5nL2Rpc2FibGVkIFRvZ2dsIGludGVncmF0aW9uIHRocm93cyBhbiBvcGFxdWUgVHlwZUVycm9yIGhlcmUuCiAgICBjb25zdCB0b2dnbFBsdWdpbiA9IHBhcmFtcy5hcHAucGx1Z2lucy5wbHVnaW5zWyJvYnNpZGlhbi10b2dnbC1pbnRlZ3JhdGlvbiJdOwogICAgaWYgKCF0b2dnbFBsdWdpbiB8fCAhdG9nZ2xQbHVnaW4udG9nZ2wgfHwgIXRvZ2dsUGx1Z2luLnRvZ2dsLl9hcGlNYW5hZ2VyKSB7CiAgICAgICAgbmV3IE5vdGljZSgiVG9nZ2wgVHJhY2sgaW50ZWdyYXRpb24gcGx1Z2luIGlzIG5vdCBpbnN0YWxsZWQsIGVuYWJsZWQsIG9yIGNvbm5lY3RlZCB0byB5b3VyIFRvZ2dsIGFjY291bnQuIiwgNTAwMCk7CiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCJUb2dnbCBUcmFjayBpbnRlZ3JhdGlvbiBwbHVnaW4gbm90IGZvdW5kLiIpOwogICAgfQogICAgdG9nZ2xBcGkgPSB0b2dnbFBsdWdpbi50b2dnbC5fYXBpTWFuYWdlcjsKICAgIHF1aWNrQWRkQXBpID0gcGFyYW1zLnF1aWNrQWRkQXBpOwogICAgcHJvamVjdHMgPSBhd2FpdCB0b2dnbEFwaS5nZXRQcm9qZWN0cygpOwoKICAgIGF3YWl0IG9wZW5NYWluTWVudShtZW51KTsKfQoKY29uc3QgZGF0ZUluU2Vjb25kcyA9IChkYXRlKSA9PiB7CiAgICByZXR1cm4gTWF0aC5mbG9vcihkYXRlIC8gMTAwMCk7Cn0KCmFzeW5jIGZ1bmN0aW9uIHN0YXJ0VGltZXIoZW50cnlOYW1lLCBwcm9qZWN0SUQpIHsKICAgIGF3YWl0IHRvZ2dsQXBpLnN0YXJ0VGltZXIoe2Rlc2NyaXB0aW9uOiBlbnRyeU5hbWUsIHBpZDogcHJvamVjdElEfSk7Cn0KCmFzeW5jIGZ1bmN0aW9uIG9wZW5NYWluTWVudShtZW51KSB7CiAgICBjb25zdCB7c3VnZ2VzdGVyfSA9IHF1aWNrQWRkQXBpOwogICAgY29uc3Qgb3B0aW9ucyA9IE9iamVjdC5rZXlzKG1lbnUpOwoKICAgIGNvbnN0IGNob2ljZSA9IGF3YWl0IHN1Z2dlc3RlcihvcHRpb25zLCBvcHRpb25zKTsKICAgIGlmICghY2hvaWNlKSByZXR1cm47CgogICAgY29uc3QgcHJvamVjdCA9IG1lbnVbY2hvaWNlXTsKICAgIGF3YWl0IG9wZW5TdWJNZW51KHByb2plY3QpOwp9Cgphc3luYyBmdW5jdGlvbiBvcGVuU3ViTWVudShwcm9qZWN0KSB7CiAgICBjb25zdCB7c3VnZ2VzdGVyfSA9IHF1aWNrQWRkQXBpOwogICAgY29uc3Qgb3B0aW9ucyA9IFsuLi5PYmplY3Qua2V5cyhwcm9qZWN0Lm1lbnVPcHRpb25zKSwgYmFja107CgogICAgY29uc3QgY2hvaWNlID0gYXdhaXQgc3VnZ2VzdGVyKG9wdGlvbnMsIG9wdGlvbnMpOwogICAgaWYgKCFjaG9pY2UpIHJldHVybjsKCiAgICBpZiAoY2hvaWNlID09PSBiYWNrKSB7CiAgICAgICAgcmV0dXJuIGF3YWl0IG9wZW5NYWluTWVudShtZW51KTsKICAgIH0KCiAgICBjb25zdCBlbnRyeU5hbWUgPSBwcm9qZWN0Lm1lbnVPcHRpb25zW2Nob2ljZV07CiAgICBjb25zdCB0b2dnbFByb2plY3QgPSBwcm9qZWN0cy5maW5kKHAgPT4gcC5uYW1lID09PSBwcm9qZWN0LnRvZ2dsUHJvamVjdE5hbWUpOwogICAgaWYgKCF0b2dnbFByb2plY3QpIHsKICAgICAgICBuZXcgTm90aWNlKGBUb2dnbCBwcm9qZWN0ICIke3Byb2plY3QudG9nZ2xQcm9qZWN0TmFtZX0iIG5vdCBmb3VuZC4gRWRpdCB0aGUgbWVudSBpbiB0b2dnbE1hbmFnZXIuanMgdG8gbWF0Y2ggeW91ciBUb2dnbCBwcm9qZWN0cy5gLCA1MDAwKTsKICAgICAgICByZXR1cm47CiAgICB9CgogICAgYXdhaXQgc3RhcnRUaW1lcihlbnRyeU5hbWUsIHRvZ2dsUHJvamVjdC5pZCk7Cn0K"
}
]
}


Public Packages Zettelizer.Quickadd.Json (`docs/public/packages/zettelizer.quickadd.json`)

{
"schemaVersion": 1,
"quickAddVersion": "2.27.0",
"createdAt": "2026-09-27T00:00:00.000Z",
"rootChoiceIds": [
"qa-pkg-zettelizer"
],
"choices": [
{
"choice": {
"id": "qa-pkg-zettelizer",
"name": "Zettelize headings",
"type": "Macro",
"command": true,
"runOnStartup": false,
"macro": {
"id": "qa-pkg-zettelizer-macro",
"name": "Zettelize headings",
"commands": [
{
"id": "qa-pkg-zettelizer-script",
"name": "zettelizer",
"type": "UserScript",
"path": "scripts/zettelizer.js",
"settings": {}
}
]
}
},
"pathHint": [
"Zettelize headings"
],
"parentChoiceId": null
}
],
"assets": [
{
"kind": "user-script",
"originalPath": "scripts/zettelizer.js",
"contentEncoding": "base64",
"content": "Ly8gQ3JlYXRlcyBvbmUgbGlua2VkIG5vdGUgcGVyIGhlYWRpbmcgb2YgYSBjaG9zZW4gbGV2ZWwgaW4gdGhlIGFjdGl2ZSBub3RlLgovLyBEb2NzOiBodHRwczovL3F1aWNrYWRkLm9ic2lkaWFuLmd1aWRlL2RvY3MvRXhhbXBsZXMvTWFjcm9fWmV0dGVsaXplcgpjb25zdCBGT0xERVIgPSAiRm9sZGVyIGZvciBuZXcgbm90ZXMiOwpjb25zdCBIRUFESU5HX0xFVkVMID0gIkhlYWRpbmcgbGV2ZWwiOwoKbW9kdWxlLmV4cG9ydHMgPSB7CiAgICBlbnRyeTogc3RhcnQsCiAgICBzZXR0aW5nczogewogICAgICAgIG5hbWU6ICJaZXR0ZWxpemVyIiwKICAgICAgICBhdXRob3I6ICJDaHJpc3RpYW4gQi4gQi4gSG91bWFubiIsCiAgICAgICAgb3B0aW9uczogewogICAgICAgICAgICBbRk9MREVSXTogewogICAgICAgICAgICAgICAgdHlwZTogInRleHQiLAogICAgICAgICAgICAgICAgZGVmYXVsdFZhbHVlOiAiWmV0dGVscyIsCiAgICAgICAgICAgICAgICBwbGFjZWhvbGRlcjogIlpldHRlbHMiLAogICAgICAgICAgICAgICAgZGVzY3JpcHRpb246ICJGb2xkZXIgdGhlIG5ldyBub3RlcyBnbyBpbiAoY3JlYXRlZCBpZiBtaXNzaW5nKS4gTGVhdmUgZW1wdHkgZm9yIHRoZSB2YXVsdCByb290LiIsCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIFtIRUFESU5HX0xFVkVMXTogewogICAgICAgICAgICAgICAgdHlwZTogImRyb3Bkb3duIiwKICAgICAgICAgICAgICAgIG9wdGlvbnM6IFsiMSIsICIyIiwgIjMiLCAiNCIsICI1IiwgIjYiXSwKICAgICAgICAgICAgICAgIGRlZmF1bHRWYWx1ZTogIjMiLAogICAgICAgICAgICAgICAgZGVzY3JpcHRpb246ICJPbmx5IGhlYWRpbmdzIG9mIHRoaXMgbGV2ZWwgYmVjb21lIG5vdGVzICgzIG1lYW5zICMjIyBoZWFkaW5ncykuIiwKICAgICAgICAgICAgfSwKICAgICAgICB9LAogICAgfSwKfTsKCmFzeW5jIGZ1bmN0aW9uIHN0YXJ0KHBhcmFtcywgc2V0dGluZ3MpIHsKICAgIGNvbnN0IHsgYXBwIH0gPSBwYXJhbXM7CiAgICBjb25zdCBjdXJyZW50RmlsZSA9IGFwcC53b3Jrc3BhY2UuZ2V0QWN0aXZlRmlsZSgpOwogICAgaWYgKCFjdXJyZW50RmlsZSkgewogICAgICAgIG5ldyBOb3RpY2UoIk5vIGFjdGl2ZSBmaWxlLiIpOwogICAgICAgIHJldHVybjsKICAgIH0KCiAgICBjb25zdCBjdXJyZW50RmlsZUNhY2hlID0gYXBwLm1ldGFkYXRhQ2FjaGUuZ2V0RmlsZUNhY2hlKGN1cnJlbnRGaWxlKTsKICAgIGNvbnN0IGhlYWRpbmdzSW5GaWxlID0gY3VycmVudEZpbGVDYWNoZSAmJiBjdXJyZW50RmlsZUNhY2hlLmhlYWRpbmdzOwogICAgaWYgKCFoZWFkaW5nc0luRmlsZSkgewogICAgICAgIG5ldyBOb3RpY2UoYE5vIGhlYWRlcnMgaW4gZmlsZSAke2N1cnJlbnRGaWxlLm5hbWV9YCk7CiAgICAgICAgcmV0dXJuOwogICAgfQoKICAgIGNvbnN0IGZvbGRlciA9IFN0cmluZyhzZXR0aW5nc1tGT0xERVJdID8/ICIiKS50cmltKCkucmVwbGFjZSgvXlwvK3xcLyskL2csICIiKTsKICAgIGNvbnN0IGxldmVsID0gTnVtYmVyKHNldHRpbmdzW0hFQURJTkdfTEVWRUxdIHx8IDMpOwoKICAgIGlmIChmb2xkZXIgJiYgIShhd2FpdCBhcHAudmF1bHQuYWRhcHRlci5leGlzdHMoZm9sZGVyKSkpIHsKICAgICAgICB0cnkgewogICAgICAgICAgICBhd2FpdCBhcHAudmF1bHQuY3JlYXRlRm9sZGVyKGZvbGRlcik7CiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgICAgICAgaWYgKCFTdHJpbmcoZXJyb3IgJiYgZXJyb3IubWVzc2FnZSkuaW5jbHVkZXMoImFscmVhZHkgZXhpc3RzIikpIHRocm93IGVycm9yOwogICAgICAgIH0KICAgIH0KCiAgICBmb3IgKGNvbnN0IGhlYWRpbmcgb2YgaGVhZGluZ3NJbkZpbGUpIHsKICAgICAgICBpZiAoaGVhZGluZy5sZXZlbCAhPT0gbGV2ZWwpIGNvbnRpbnVlOwoKICAgICAgICBjb25zdCBzcGxpdEhlYWRpbmcgPSBoZWFkaW5nLmhlYWRpbmcuc3BsaXQoIiAiKTsKICAgICAgICBjb25zdCBsb2NhdGlvbiA9IHNwbGl0SGVhZGluZ1swXS50cmltKCk7CiAgICAgICAgY29uc3QgdGV4dCA9IHNwbGl0SGVhZGluZy5sZW5ndGggPiAxID8gc3BsaXRIZWFkaW5nLnNsaWNlKDEpLmpvaW4oIiAiKS50cmltKCkgOiAiIjsKICAgICAgICBpZiAoIXRleHQpIGNvbnRpbnVlOwoKICAgICAgICBjb25zdCBmaWxlTmFtZSA9IGAke3RleHQucmVwbGFjZSgvW1xcLCMlJlx7XH1cLyo8PiRcJ1wiOkBdKi9nLCAiIil9Lm1kYDsKICAgICAgICBjb25zdCBwYXRoID0gZm9sZGVyID8gYCR7Zm9sZGVyfS8ke2ZpbGVOYW1lfWAgOiBmaWxlTmFtZTsKICAgICAgICBjb25zdCBjb250ZW50ID0gYCFbWyR7Y3VycmVudEZpbGUuYmFzZW5hbWV9IyR7bG9jYXRpb259ICR7dGV4dH1dXWA7CgogICAgICAgIGlmIChhd2FpdCBhcHAudmF1bHQuYWRhcHRlci5leGlzdHMocGF0aCkpIHsKICAgICAgICAgICAgbmV3IE5vdGljZShgRmlsZSAke3BhdGh9IGFscmVhZHkgZXhpc3RzLmAsIDUwMDApOwogICAgICAgICAgICBjb250aW51ZTsKICAgICAgICB9CiAgICAgICAgYXdhaXQgYXBwLnZhdWx0LmNyZWF0ZShwYXRoLCBjb250ZW50KTsKICAgIH0KfQo="
}
]
}


Packages Book Finder Files Book (`docs/packages/book-finder/files/Book.md`)

Author:: {{VALUE:authors}}
Title:: {{VALUE:title}}
Category::{{VALUE:categories}}
Status:: 📥

Related Books

Core Questions for Me

Actions

My Notes

Details

{{VALUE:description}}


Packages Inbox Item Files Inbox Template (`docs/packages/inbox-item/files/Inbox Template.md`)


created: {{DATE:YYYY-MM-DD HH:mm}} tags: [inbox]

{{NAME}}


Packages Meeting Notes Files Meeting (`docs/packages/meeting-notes/files/Meeting.md`)

{{VALUE:Meeting}}

Date: {{DATE:YYYY-MM-DD}}

Notes

  • {{CURSOR}}

Next steps

  • [ ]

Packages Meeting Notes Files Project Update (`docs/packages/meeting-notes/files/Project update.md`)

Update - {{DATE:YYYY-MM-DD}}

Progress

  • {{CURSOR}}

Next steps

  • [ ]


tags:

  • moc

{{VALUE:moc_title}}

Use the view picker in this embedded base to switch between backlinks and
outgoing links for this note.

base
{{TEMPLATE:Templates/MOC Link Dashboard.base}}

Notes

  • Start linking this note to related ideas.

Packages Movie Notes Files Movie (`docs/packages/movie-notes/files/Movie.md`)


{{VALUE:Plot}}


Packages Readwise Book Notes Files Book Notes (`docs/packages/readwise-book-notes/files/Book Notes.md`)


image:
tags: in/books
aliases:
- "{{VALUE:Book Title}}"

cssclass:

Title: [[{{TITLE}}]]

Metadata

Tags::
Type:: [[Book]]
Author:: {{VALUE:author}}
Reference::
Rating::
Reviewed Date:: [[{{DATE:YYYY-MM-DD - ddd MMM D}}]]
Finished Year:: [[{{DATE:YYYY}}]]

Thoughts

Actions Taken / Changes

Summary of Key Points

Highlights & Notes

{{MACRO:Readwise::instaFetchBook}}


Src Content Docs Docs AIAssistant (`docs/src/content/docs/docs/AIAssistant.md`)


title: AI Assistant Reference slug: docs/AIAssistant

QuickAdd's AI Assistant sends a prompt to an AI model and drops the reply back
into your workflow. There are two ways to use it:

  • From a Macro command - no code. You pick a prompt template, and QuickAdd stores the model's reply as a variable that later steps can insert.
  • From a User Script - for structured output, tool calling, or custom control flow.

By the end you have an AI step wired into a choice: generate a note title,
summarize a selection, or answer a question from your vault.

:::note
The AI Assistant settings and AI requests are available only when Disable AI &
online features
is turned off in QuickAdd settings.
:::

Setup {#setup}

  1. Create a folder for AI prompt templates, for example AI prompts.
  2. Open Settings → QuickAdd, turn off Disable AI & online features
    (under AI & online), and open AI Assistant just below it. The
    Configure AI Assistant button in the choice list (the sparkles icon at
    the bottom of the list) and the QuickAdd: Open AI Assistant settings
    command open the same page.
  3. Set Prompt template folder to the folder you created.
  4. Under Providers, set up at least one provider. OpenAI and Gemini are
    already listed: open one, link an API key secret with Link..., and click
    Sync now to pull the provider's current models. To add another provider,
    click +. See Connect a provider.
  5. Choose a Default model, or leave it as Ask me to pick a model each run.

Changes on these pages save as you make them. The settings pages need QuickAdd
2.28.0 or later; earlier versions edit providers in dialogs with Save and
Cancel.

Prompt templates are Markdown notes in your prompt template folder. They can use
QuickAdd Format Syntax, including values collected earlier
in the same macro.

:::caution[Prompt templates can run code]
A prompt template goes through the full QuickAdd format pass every time the
AI command fires - including inline scripts (js quickadd code fences), {{MACRO:}} and {{TEMPLATE:}}. That is the same
trust model as a Template choice: a prompt template is not just text sent to
the model, it is a note QuickAdd executes. Review prompt templates you did not
write yourself - for example ones that arrive with an imported
package - exactly as you would review a script.
:::

After setup, add an AI Assistant command to a Macro. The command formats the
selected prompt template, sends it to the selected model, then stores the
response as macro variables for later steps.

In the recording below, a Summarize prompt template (containing
{{SELECTED}}) feeds the selected paragraph to the model, the command stores
the reply as summary, and a Capture writes {{VALUE:summary}} to the bottom of
the active note.

What each setting does {#settings-semantics}

Some of these settings are read live on every run; two of them are only a
template for new commands. The difference matters, so it is called out per
setting:

  • Prompt template folder is the folder QuickAdd reads prompt-template notes from. Read live on every run.
  • Providers is the list of model endpoints and model ids QuickAdd can use. Read live on every run.
  • Default model and Default system prompt are the starting values for new AI Assistant Macro commands: they are copied into a command when you add it. Editing a default later does not change commands you already created - edit each command instead. Setting the default model to Ask me makes new commands open a model picker at run time.
  • Show assistant controls QuickAdd's AI progress notices. Read live on every run.
  • Confirm AI tool calls controls script-agent tool confirmation. Read live on every run. See Tool approval and safety.

The script APIs behave differently from Macro commands here:
quickAddApi.ai.prompt(), chunkedPrompt(), and ai.agent() read the live
default system prompt at call time when you pass no systemPrompt/system
override (the model is always passed explicitly).

Each AI Assistant Macro command holds its own:

  • Prompt template, which is a Markdown note in the prompt template folder, not raw prompt text.
  • Model, the model this command uses. Copied from the default at creation; Ask me opens a model picker at run time.
  • Output variable name, which controls the variable names written for later Macro steps.
  • System prompt, sent with this command's requests. Copied from the default at creation.
  • Advanced model parameters, described in Advanced sampling settings.

System prompts are sent as written {#system-prompt-is-literal}

QuickAdd resolves Format Syntax in the prompt template
only. The system prompt - both the default and a command's own - goes to the
model exactly as you typed it, so {{DATE}} in a system prompt arrives at the
model as the eight characters {{DATE}}.

Put anything that needs a token in the prompt template instead.

Connect a provider {#providers-and-local-models}

QuickAdd supports OpenAI-compatible providers, Google Gemini, and Anthropic.
Custom or unknown providers use the OpenAI-compatible request shape by default.

Built-in provider cards are available for:

  • OpenAI
  • Gemini
  • Anthropic
  • Groq
  • TogetherAI
  • OpenRouter
  • Hugging Face
  • Mistral
  • DeepSeek

:::note
Provider API keys are stored through Obsidian SecretStorage. QuickAdd stores the
secret reference in settings, not the key value. Older plaintext provider keys
are migrated to SecretStorage.
:::

Add a provider {#add-a-provider}

  1. Open Settings → QuickAdd → AI Assistant.
  2. Next to Providers, click + (on mobile, tap Add provider below
    the list).
  3. Pick a provider card, select a SecretStorage entry for the API key, then click Connect.

Connecting a provider imports its current model list right away, so you can pick
a working model immediately. If the live import fails (for example, while
offline), the built-in providers fall back to a shipped model list and refresh
automatically once the provider is reachable.

For a provider that is not listed, click Add custom... under Custom
provider
, then open the new provider in the list. Set its name, endpoint, API key secret if needed, model
source, and models manually.

To check a key, open the provider and click Test connection. QuickAdd asks
the provider's own models endpoint with the linked key and shows either how many
models it lists or the provider's error.

The Obsidian CLI runs the same
check. Name the provider by its ID or its name:

bash
obsidian vault=dev quickadd:ai-test-connection provider=openai

It prints "ok":true with modelCount, or "ok":false with the provider's
error. apiKeyLinked says whether a key is linked. The output never contains
the key.

Local models and Ollama {#local-models-and-ollama}

Use Custom provider for Ollama and most local OpenAI-compatible servers.

For Ollama:

text
Name: Ollama
Endpoint: http://localhost:11434/v1
API key: leave blank
Model source: Provider models endpoint
Models: import from the running Ollama server, or add the model name manually

Leaving the API key blank works for Ollama. Model import from /v1/models sends
no Authorization header when the key is blank. Regular OpenAI-compatible chat
requests still include an empty Bearer header. If your local server rejects
that, configure the server to allow it or select a SecretStorage entry with the
token it expects.

When adding a model manually (the + next to Models on the provider's
page), the model name must match the id your server expects, such as mistral
or llama3.1. The Context window value is the model's context window in
tokens. See Model settings and token budgets.

One name, two providers {#provider-ids-and-duplicate-model-names}

Every provider has a stable ID - a short slug like openai or my-proxy,
shown under the provider's Name on its settings page. The ID never changes, even if you rename the
provider, and scripts use it to address a model on a specific provider.

Two providers can serve models with the same name - for example, the official
OpenAI provider and an OpenAI-compatible proxy can both list gpt-4o. Model
dropdowns group models by provider so you always pick a specific provider's
model, and QuickAdd remembers that choice. Reordering providers, renaming them,
or auto-syncing new models never changes which endpoint an existing command
talks to.

If the provider a command is pinned to is later deleted, QuickAdd falls back to
the first provider that serves a model with that name and warns you about the
switch. Re-select the model in the command to pin it again.

Where QuickAdd gets the model list: Model source {#model-source}

Each provider has a Model source setting:

  • Provider models endpoint asks the provider for its model list. QuickAdd speaks each provider's native protocol here: OpenAI-compatible /v1/models, Anthropic's /v1/models, and Gemini's ListModels. This is also the usual choice for local providers like Ollama when the server is running.
  • models.dev directory imports from the public models.dev directory when that directory knows the provider.
  • Automatic tries the provider first and falls back to models.dev when QuickAdd can map the endpoint.

Imports skip entries that cannot serve chat requests (image generators,
text-to-speech voices, embedding models, realtime audio models) and pinned dated
snapshots such as gpt-4o-2024-11-20 when the undated id is listed. They carry
each model's context window, output limit, sampling support, and release date
where the source reports them.

The model list shows the newest models first and has a filter box. Models the
provider has deprecated carry a Retired badge and are listed last, the
provider's entry on the AI Assistant page shows a warning with the count, and
Remove retired clears them in one step. QuickAdd never removes
them on its own, because saved commands may still use them.

If model import fails, you can still add models manually. Use the provider's
exact model id and the model's context-window token count.

Keep model lists current: Auto-sync {#auto-sync}

Each provider has an Auto-sync models toggle. While it is on, QuickAdd
imports new models and refreshed context limits from the provider's model source
once a day and whenever you open the provider's page, so model lists stay current
without plugin updates. Auto-sync only adds models and updates metadata - it
never removes models you have configured, and it does not add models the
directory already marks as deprecated. Use Sync now to refresh on demand.
The line under the toggle shows when the provider last synced, or why the last
sync failed.
Models that arrive while the provider's page is open appear in its list right
away, and the Sync now notice counts every model added to the list you were
looking at when you clicked it.

Auto-sync is on by default for the built-in OpenAI and Gemini providers and for
providers added from a card. It does nothing while Disable AI & online
features
is on.

Model settings and token budgets {#model-settings-and-token-budgets}

Max tokens is the context window {#max-tokens}

In the provider's model list, Context (stored as maxTokens) is the
model's context window. It
is the total amount of prompt plus response context the model can handle,
according to the configured provider metadata or the value you entered manually.

QuickAdd uses this value for local estimates, model lookup, and chunk sizing. It
does not mean "make the answer this long", and setting it higher than the
provider actually supports does not increase the provider's real limit.

QuickAdd's token counts are local estimates. Providers enforce the exact limits.
For single AI Assistant prompts, QuickAdd logs when the local prompt estimate is
above the configured context value, but it still sends the request. The provider
may accept it or reject it with a context-window error.

Use these rules when choosing a value:

  • For gpt-4o-mini, enter 128000, not a smaller output limit.
  • For a local model, use the context window configured for that local model.
  • If you do not know the value, import models from the provider if possible, or use the provider's model documentation.

Max chunk tokens {#max-chunk-tokens}

Chunk sizing has no setting of its own. It is the maxChunkTokens option of
chunkedPrompt() in the script API.

Output length {#output-length}

The regular Macro AI Assistant command does not have a separate output-length
field.

In scripts, quickAddApi.ai.agent() accepts maxOutputTokens in the agent
config or per generate() call. QuickAdd maps that option to the
provider-specific output field where the provider supports one.

Anthropic requests always require an output token budget. When no explicit
maxOutputTokens is set, QuickAdd uses the model's real output limit when the
model list carries one (imported and auto-synced models do), and otherwise a
conservative default of 4096.

Advanced sampling settings {#advanced-sampling-settings}

AI Assistant commands expose advanced model parameters:

  • Temperature controls randomness. Lower values are more focused. Higher values are more varied.
  • Top P controls nucleus sampling.
  • Frequency penalty reduces repeated wording on providers that support it.
  • Presence penalty encourages new topics on providers that support it.

A parameter is only sent when you set it. Untouched settings use the provider's
defaults, and each slider has a reset button that returns it to that state.

Gemini and Anthropic requests use temperature and top P. QuickAdd does not send
frequency or presence penalties to those providers.

:::note[Fixed-sampling models are handled for you]
Many current models use fixed sampling and reject these parameters outright -
OpenAI reasoning models and Anthropic's current generation among them. When a
model is known to use fixed sampling, the parameters are not sent. When a
provider rejects one anyway, QuickAdd retries the request once without sampling
parameters and shows a notice explaining what happened. A sampling slider never
hard-fails a command.
:::

Get the reply into your notes: Macro output variables {#macro-output-variables}

An AI Assistant Macro command stores the model response in the command's
Output variable name. The default name is output.

If Output variable name is summary, QuickAdd writes:

  • summary: the response text
  • summary-quoted: the same response formatted as a Markdown blockquote, with each line prefixed by >

Later commands in the same Macro can use those values:

markdown
{{VALUE:summary}}

{{VALUE:summary-quoted}}

The variables are scoped to that Macro run. A separate QuickAdd choice run does
not receive them.

Example: AI-generated note title {#example-ai-generated-note-title}

Create a prompt-template note named Title Prompt.md in your prompt template
folder:

markdown
Generate a short filename-safe title for this text. Reply with only the title.

Text: {{VALUE}}

Then use a Macro with two steps:

  1. AI Assistant command
    • Prompt template: Title Prompt.md
    • Output variable name: aiTitle
    • Use a low temperature if you want more repeatable titles.
  2. Template command
    • File name: {{VALUE:aiTitle}} (before QuickAdd 2.30.0, this is File name format; turn its toggle on first)
    • Template body can also include {{VALUE:aiTitle}}.

The same pattern works with Capture choices and User Script commands that run
after the AI step.

Read the result in a script {#read-the-result-in-a-script}

When a User Script runs later in the same Macro, read the same variable from
params.variables:

js
module.exports = async (params) => {
  const description = params.variables.description;
  console.log(description);
};

If this is empty, check the AI Assistant command's Output variable name. It
must be description, or your script must read the default output.

Script API assignment is explicit {#script-api-assignment-is-explicit}

quickAddApi.ai.prompt() and quickAddApi.ai.chunkedPrompt() return an object
with the response variables. They write those variables into later Macro steps
only when you set shouldAssignVariables: true or assignToVariable.

js
module.exports = async ({ quickAddApi }) => {
  await quickAddApi.ai.prompt("Summarize the current selection.", "gpt-4o-mini", {
    assignToVariable: "summary",
  });
};

assignToVariable also writes summary-quoted. Avoid names that are reserved by
the formatter or variable plumbing: value, title, text, meta, names
ending in -quoted, names starting with __qa., and names containing | or
,.

Structured JSON output {#structured-json-output}

Use structured output when you want separate fields from one model response,
such as title, summary, and tags. This is a User Script workflow, not the plain
Macro AI Assistant command.

The pattern is:

  1. User Script calls quickAddApi.ai.agent().generate({ prompt, schema }).
  2. The script checks result.object.
  3. The script assigns fields to params.variables.
  4. A later Template or Capture step uses {{VALUE:name}} where each field belongs.
js
module.exports = async ({ quickAddApi, variables }) => {
  const selectedText = quickAddApi.utility.getSelection();
  if (!selectedText) {
    throw new Error("Select text before running this macro.");
  }

  const result = await quickAddApi.ai.agent({ model: "gpt-4o-mini" }).generate({
    prompt: `Extract a title, a short summary, and up to five tags from this text:\n\n${selectedText}`,
    schema: {
      type: "object",
      properties: {
        title: { type: "string" },
        summary: { type: "string" },
        tags: {
          type: "array",
          items: { type: "string" },
        },
      },
      required: ["title", "summary", "tags"],
    },
  });

  const data = result.object;
  if (!data || typeof data !== "object") {
    throw new Error("The AI response did not match the expected JSON shape.");
  }

  variables.aiTitle = String(data.title ?? "");
  variables.aiSummary = String(data.summary ?? "");
  variables.aiTags = Array.isArray(data.tags)
    ? data.tags.map(String).join(", ")
    : "";
};

Then use a Template step after the script:

markdown
# {{VALUE:aiTitle}}

{{VALUE:aiSummary}}

Tags: {{VALUE:aiTags}}

Structured output supports a small JSON Schema subset: type, properties,
required, items, enum, const, description, and title. Avoid
provider-specific schema keywords such as minLength, pattern, $ref,
format, anyOf, and allOf in QuickAdd examples.

If the model response does not parse or does not validate, QuickAdd makes one
repair attempt. If that still fails, result.object is undefined, so scripts
should handle that as shown above.

Tool and function calling {#tool-and-function-calling}

QuickAdd 2.14.0 added a script API for tool and function calling:

  • quickAddApi.ai.agent(config) creates an agent.
  • agent.generate({ prompt }) runs a bounded multi-step loop.
  • quickAddApi.ai.tool(def) declares a JavaScript function the model may call.
  • quickAddApi.ai.tools.vault(), workspace(), and system() provide opt-in built-in tools.

Tools are available from User Scripts. They are JavaScript functions, so they do
not live inside a stored AI Assistant Macro command.

js
module.exports = async ({ quickAddApi }) => {
  const agent = quickAddApi.ai.agent({
    model: "gpt-4o-mini",
    system: "Answer from the user's vault when possible.",
    tools: {
      ...quickAddApi.ai.tools.vault({
        only: ["read_note", "search_notes"],
      }),
      word_count: quickAddApi.ai.tool({
        description: "Count words in a text string.",
        inputSchema: {
          type: "object",
          properties: {
            text: { type: "string" },
          },
          required: ["text"],
        },
        readOnly: true,
        execute: ({ text }) => {
          const words = String(text ?? "")
            .trim()
            .split(/\s+/)
            .filter(Boolean);
          return { count: words.length };
        },
      }),
    },
    maxSteps: 12,
  });

  const result = await agent.generate({
    prompt: "What do my notes say about project planning?",
    assignToVariable: "answer",
  });

  return result.text;
};

Agent results include:

  • text: final assistant text
  • object: structured result, only when schema was passed
  • steps: tool-loop steps
  • toolCalls and toolResults: calls and results from the last step
  • usage: input, output, and total token counts
  • finishReason: why the run stopped

By default, agents use up to 20 steps. maxSteps is capped at 100.

Tool approval and safety {#tool-approval-and-safety}

The global Confirm AI tool calls setting defaults to Destructive tools
only (recommended)
:

  • readOnly: true tools run automatically under the default setting.
  • Tools that are not read-only ask for confirmation under the default setting.
  • needsApproval: true always asks for confirmation.
  • Always confirm every tool asks for every tool.
  • Never defers to each tool's own needsApproval.

:::caution[Tool arguments come from the model - treat them as untrusted]
Tool handlers run with the same privileges as your script, and the model chooses
tool names and arguments. Validate paths and values, never pass tool input to
quickAddApi.format(), eval, a shell, or a network request without your own
checks, and do not put secrets in tool descriptions or arguments because those
are sent to the provider.
:::

For the full script API surface, see the
QuickAdd API reference.

Troubleshooting {#troubleshooting}

The AI settings button is missing {#the-ai-settings-button-is-missing}

Turn off Disable AI & online features in QuickAdd settings. The AI
Assistant
page and its buttons are hidden while AI and online features are
disabled. With no choices yet,
the button is Configure AI Assistant below New choice; otherwise it is
the sparkles icon in the bar under the choice list.

My model is not listed {#my-model-is-not-listed}

Open Settings → QuickAdd → AI Assistant > your provider, then click
Sync now. Providers with Auto-sync models on pick up new
models automatically once a day. You can also browse and import models, or add
the model manually - the model name must exactly match what the provider expects.

A local provider does not respond {#a-local-provider-does-not-respond}

Check that the local server is running and that the endpoint includes the right
base path. For Ollama, use:

text
http://localhost:11434/v1

If the server requires auth, select an API key secret. If the API key is blank,
model import sends no Authorization header, while OpenAI-compatible chat
requests include an empty Bearer header.

Max tokens is confusing {#max-tokens-is-confusing}

Use the model's context-window size. Do not use the model's advertised output
limit. If a request is rejected for context length, shorten the prompt, pick a
model with a larger context window, or use chunked prompting.

A later Macro step cannot read the AI result {#a-later-macro-step-cannot-read-the-ai-result}

Check these in order:

  1. The AI Assistant command and the later command must be steps in the same Macro.
  2. The later step must use the AI command's Output variable name.
  3. If you did not set a name, read {{VALUE:output}}.
  4. In a script step, inspect Object.keys(params.variables) to see what arrived.
  5. If you called quickAddApi.ai.prompt() from a script, set shouldAssignVariables: true or assignToVariable.

My script does nothing {#my-script-does-nothing}

QuickAdd User Scripts must export a function. Put your code inside
module.exports:

js
module.exports = async ({ quickAddApi, variables }) => {
  const result = await quickAddApi.ai.prompt("Say hello.", "gpt-4o-mini");
  variables.output = result.output;
};

See the User Scripts Reference.

Structured output returned no object {#structured-output-returned-no-object}

result.object can be undefined when the model fails to return JSON that
matches the schema after QuickAdd's repair attempt. Keep the schema simple, use a
current model that supports structured output, and handle the missing object in
your script.

A tool run is waiting forever {#a-tool-run-is-waiting-forever}

The tool probably opened a confirmation modal. In unattended CLI runs, use only
readOnly: true tools, change Confirm AI tool calls for that vault, or
design your script so needsApproval is not required for that path.

Workflow ideas {#workflow-ideas}

  • Summarizer: summarize selected text and capture it to a note.
  • Transform Selected: rewrite the active selection with a prompt template.
  • AI title: generate a filename-safe title, then use it in a Template step.
  • Structured note: extract fields with agent.generate({ schema }), then place them in an output template.
  • Vault Q&A: use ai.agent() with read-only vault tools to answer from notes.

:::caution
All provider usage may incur provider costs. Use provider-side spending limits
where available.
:::


Src Content Docs Docs ApplyTemplateToNote (`docs/src/content/docs/docs/ApplyTemplateToNote.md`)


title: Apply Template to Note slug: docs/ApplyTemplateToNote

Created a note manually - with Cmd/Ctrl+N, the Quick Switcher, or by clicking
a link to a file that didn't exist yet - and only then realized you forgot to
use your template? You don't need to delete the note and start over. QuickAdd
can apply a template to a note you already have.

There are two ways in:

  • Run QuickAdd: Apply template to active note from the command palette to apply a template to the note currently open.
  • Right-click a Markdown file (in the file explorer, a tab header, and so on) and pick QuickAdd: Apply template to active note to target that file directly.

Either way, you pick a template, choose how it should be added, and QuickAdd
fills in the placeholders and merges the frontmatter for you.

How it works {#how-it-works}

  1. Run the command while a Markdown note is active.
  2. Pick what to apply. The picker lists your
    Template choices first (including ones
    nested in Multi choices), followed by template files from your configured
    templates folder that aren't already covered by a choice.
  3. Pick how to apply it (skipped for empty notes - see
    Smart behaviors):
    • Insert at cursor: inserts the template at the cursor. Only offered when the note is open in the active editor.
    • Insert at top: inserts the template below the note's frontmatter, or at the very top if there is none.
    • Append to bottom: adds the template content to the end of the note, after exactly one blank line.
    • Replace note content: replaces the entire note with the template.

The template runs through the full QuickAdd
format syntax pipeline. Templater syntax is also processed
when the Templater plugin is enabled.

Put {{CURSOR}} in the template body to choose
where typing continues after applying it. All four modes support the marker
when the destination is focused in an editing mode. Insert and append modes
leave existing literal markers in the destination note alone. Replace note
content
replaces the whole note, including any existing markers.

The project update example
shows how to append a dated section and keep typing in it.

:::note
Only Markdown templates can be applied. Canvas (.canvas) and Base (.base)
templates hold data for their own file types, so they're left out of the picker

  • use a regular Template choice to create
    those. The target note must be Markdown too.
    :::

Smart behaviors {#smart-behaviors}

QuickAdd handles a few common situations for you so you don't have to think
about them:

  • Empty notes skip the prompt. If the note is empty (or only whitespace), the "how to apply" step is skipped and the template becomes the note's full content. The usual case is a freshly created blank note.
  • The title fills itself in. The note already has a name, so {{TITLE}} and the unnamed {{VALUE}} / {{NAME}} resolve to the note's file name instead of prompting. Named values like {{VALUE:project}} still prompt as usual.
  • Frontmatter merges instead of stacking. For Insert at top, Append to bottom, and Insert at cursor, the template's frontmatter is not added as a second --- block. Missing or empty properties are filled from the template. If a property already contains one value, the note's value wins. Properties that can contain multiple values, such as tags, aliases, and multi-text properties, keep their existing values and add any template values that are not already present. Reapplying the same template does not duplicate them. (Replace note content replaces the whole note, frontmatter included.)
  • The note can move to match the choice. If you picked a Template choice with a folder and/or file name format, and the note's current location or name doesn't match what that choice would have produced, QuickAdd offers to move or rename the note to match. Links to the note are updated automatically. This is skipped when the choice's folder settings need a runtime folder picker, or when a file already exists at the target path.

From scripts and macros {#from-scripts-and-macros}

The QuickAdd API does the same thing without any prompts:

js
await quickAddApi.applyTemplateToActiveFile("templates/meeting.md", {
  mode: "top", // "cursor" | "top" | "bottom" | "replace"
});

When mode is omitted, empty notes get replace and non-empty notes get
bottom.


Src Content Docs Docs ComingFromTemplater (`docs/src/content/docs/docs/ComingFromTemplater.md`)


title: Coming from Templater
keywords:

  • templater
  • migration
  • template
    slug: docs/ComingFromTemplater

If you built your note workflows around the Templater plugin, this page maps each familiar job to the QuickAdd-native way to do it. Every pattern below runs on QuickAdd alone: one engine owns your prompts, dates, and file creation, which is what keeps you clear of double prompts and half-rendered template syntax (see common migration snags).

New to QuickAdd entirely? Start with Getting Started for the choice types, then come back here for the mappings.

Point QuickAdd at your template folder

You don't need to move or rewrite your template files to start. Add your existing template folder(s) under Settings → QuickAdd → Template folders. That single step powers:

  • the QuickAdd: New note from template command, which lists every template in those folders and prompts for the new note's name - no per-template setup;
  • the QuickAdd: Apply template to active note command, whose picker offers those template files too;
  • template-path autocomplete when you configure choices.

Make a Template choice for the templates that deserve their own hotkey, a fixed destination folder, or a file name format.

The quick map

The left column names the job; the middle names the Templater expression you may know it by (as a landmark only - the QuickAdd patterns don't use or require it).

The job You may know it as The QuickAdd way
Insert the note's title tp.file.title {{TITLE}}
Today's date, any format tp.date.now {{DATE:YYYY-MM-DD}}, offsets like {{DATE+7}}
Ask for a date in plain language tp.system.prompt {{VDATE:due,YYYY-MM-DD}} - natural language built in
Prompt for text tp.system.prompt {{VALUE:name}}
Pick from a list tp.system.suggester {{VALUE:Option A,Option B}}, {{FIELD:...}}, {{FILE:...}}
Include one template in another tp.file.include {{TEMPLATE:Templates/Partial.md}}
Apply a template to an existing note the insert template command Apply template to active note
Insert the clipboard tp.system.clipboard {{CLIPBOARD}}
Insert the selected text tp.selection {{SELECTED}}
Reuse a property from the note you're in tp.frontmatter {{FIELD:project|default-from:active}}; to re-render a value you prompted for, just repeat {{VALUE:name}}
Link back to the note you came from tp.file.path workarounds {{LINKCURRENT}} (a link), {{FILENAMECURRENT}} (raw name, for embeds like ` (link to the heading you're in)
Run JavaScript tp.user Inline scripts, user scripts in macros, {{MACRO:...}}
Folder templates folder templates No automatic equivalent - see Templates chosen by folder
Cursor marker tp.file.cursor {{CURSOR}} in Template or Capture bodies - see Where the cursor lands

Create new notes from templates

A Template choice creates a note from a template file, with a configurable destination folder, file name format, link insertion, and open behavior. QuickAdd resolves every token - prompts included - before the note is created, so the finished note is plain text from its first moment.

To position user input at an exact spot in the new note, put the token exactly where the text belongs. One named value can drive both the file name and the body:

File name: {{VALUE:topic}}

markdown
---
type: meeting
---
# {{TITLE}}

Topic: {{VALUE:topic}}
Date: {{DATE:YYYY-MM-DD}}

You are asked for topic once; the answer becomes the file name and fills the body, and {{TITLE}} renders the final file name.

Add to existing notes

Shared template content in new and existing notes

Keep one template file and use it both ways - no parallel template sets:

  • New notes: point a Template choice at it, or include it inside a bigger template with {{TEMPLATE:Templates/Partial.md}}.
  • Existing notes: run QuickAdd: Apply template to active note (also in a file's right-click menu). You pick the template and where it goes - cursor, top, bottom, or replace. For the insert modes, the template's frontmatter is merged into the note's existing frontmatter, with the note's own values winning (replace overwrites the whole note, frontmatter included). See Apply Template to Note.

A Capture choice whose format is {{TEMPLATE:Templates/Partial.md}} also inserts that shared content into a target note. Prefer it for body-only snippets: a capture inserts the template text as-is, so a template that starts with its own --- frontmatter block ends up as a literal second block instead of being merged. When the shared content carries frontmatter, use Apply template to active note.

Today's daily note

Appending to today's note is a Capture choice that targets the daily note - the file doesn't have to exist beforehand:

  • Capture to: {{DAILY}}, the note Open today's daily note opens. Click Daily note next to the field to fill it in (QuickAdd 2.30.0 or later).
  • Create file if it doesn't exist, with Create file with a template set to your daily template, so QuickAdd fills in its tokens
  • Insert after: ## Log, with Create line if not found
  • Capture format: - {{VALUE}}

Say today is 2026-07-06 and your daily notes live in Daily: running it and typing did a thing creates Daily/2026-07-06.md from the template on first capture and appends - did a thing under ## Log - one hotkey, with or without an existing note. Before QuickAdd 2.30.0, set Capture to to a date-formatted path such as Daily/{{DATE}}.md instead. For a step-by-step walkthrough with variations, see Capture: Add entries to your daily note; Capture choices covers every target and position option.

Templates chosen by folder

QuickAdd does not watch folders: nothing runs automatically when a note appears in a folder, no matter how it was created. What it offers instead is explicit and per-choice:

  • One Template choice per destination. Set New note location to In a specific folder and name the choice after the destination ("New person", "New project"). Each gets its own command and can get its own hotkey.
  • One choice, several folders. List multiple folders on the choice (optionally Include subfolders) and QuickAdd asks which one at run time.
  • Dynamic paths. Both the template path and the folder path accept format syntax, and one named value can drive both. A choice with template path Templates/{{VALUE:kind}}.md and folder path {{VALUE:kind}}s asks for kind once - answering Person creates the note from Templates/Person.md in the Persons folder, keeping the whole folder-to-template mapping in a single choice.
  • No setup at all: the New note from template command picks any template from your template folder and creates the note in Obsidian's default location.

Dates without another plugin

{{DATE}} renders today (YYYY-MM-DD by default), {{DATE:<format>}} takes any Moment format, and offsets travel in days: {{DATE+7}}, {{DATE:gggg-[W]WW+7}}. Snap to period boundaries with |startof: / |endof:, for example {{DATE:YYYY-MM|startof:week}} for weekly notes that file under the month the week belongs to.

For dates you enter, {{VDATE:name,format}} understands natural language out of the box - no companion plugin. Type tomorrow, next friday, or in two weeks. Enter the date once, render it many times:

markdown
Due: {{VDATE:due,YYYY-MM-DD}}
Week: {{VDATE:due,gggg-[W]WW}}

Typing tomorrow fills both lines from one prompt - with 2026-07-06 as today, that's Due: 2026-07-07 and Week: 2026-W28.

There is no token for a file's creation or modification date - reach for a script if you need those.

Prompt once, reuse everywhere

Named values are shared across an entire run. {{VALUE:topic}} in the file name, the template body, and any other step of the same run all resolve from a single prompt - the mechanism behind the Template choice example above.

That sharing spans Macro choice steps too. A classic two-step flow - log a task in today's daily note and create its note - is a macro of two choices sharing one name:

  1. A Capture choice into {{DAILY}} (before QuickAdd 2.30.0, Daily/{{DATE}}.md) with the format - [ ] [[{{VALUE:task}}]]
  2. A Template choice with file name format {{VALUE:task}} creating the note in your Tasks folder

You type the task name once. Scripts join the same pool: anything a script assigns to params.variables.task is what {{VALUE:task}} resolves to in later steps - see the scripting guide.

To gather every prompt on a single form up front instead of one dialog at a time, enable one-page input - and see Controlling Prompts for everything about prompt order, labels, defaults, and skipping.

Where the cursor lands

Use {{CURSOR}} to mark a position in a Template
or Capture body. QuickAdd moves there if the target is already focused in an
editing mode, or if Open opens and focuses it. Templater's cursor jump takes
precedence when both are present.

  • Unmarked Captures keep their existing behavior. Inserting at the cursor or on an adjacent line leaves the cursor after the inserted text. Other body Captures place it at the end of the insertion when Open opens and focuses the target in an editing mode.
  • Apply template to active note supports the marker in every mode, including Insert at cursor.
  • Without a marker, a Template choice with Open keeps its usual cursor position, typically at the top of a new note. To move to the end instead, put {{CURSOR}} at the end of your template.

Run scripts

QuickAdd runs JavaScript in two shapes:

  • Inline scripts: a fenced code block tagged js quickadd inside a template body or capture format. The block runs when the choice does, and a returned string is spliced into the output in its place.
  • User scripts: a .js file in your vault, run as a step of a Macro choice. Scripts receive app, the QuickAdd API (inputPrompt, suggester, executeChoice, and more), the shared variables map, and the obsidian module - see the scripting guide.

{{MACRO:My macro}} embeds a macro's return value anywhere format syntax is accepted, so a computed value can flow straight into a file name, template body, or capture line.

Common migration snags

These are the classic symptoms of splitting one template between two engines - each has a QuickAdd-native fix:

  • You get prompted twice. QuickAdd resolves all of its prompts before the file is created. If another engine prompts in the same template, you answer twice - once per engine. Let QuickAdd own the prompt with {{VALUE:name}} and reuse the answer everywhere it's needed.
  • Template syntax shows up unrendered. QuickAdd renders QuickAdd tokens; another engine's syntax is only rendered by that engine. If it isn't installed or doesn't run on the file, its markup stays behind as literal text. Port the line to the matching token from the map.
  • Templater code runs twice. A macro that runs Templater's Replace templates in the active file right after a QuickAdd Template or Capture step runs the templates a second time: QuickAdd already ran them when it wrote the note. That step is deprecated, and QuickAdd 2.30.0 or later shows a notice once per session when a macro runs it. Remove it from the macro.
  • Capturing into a note throws template errors. A note that keeps live template syntax can re-execute or error whenever a plugin processes the file again. QuickAdd tokens like {{DATE:YYYY-MM-DD}} render once, at creation, into plain text - later captures find nothing to re-run. Migrate the offending line to a QuickAdd token and let QuickAdd create the note so the token renders - a Capture with Create file if it doesn't exist plus that template does both (see Today's daily note).

Src Content Docs Docs ControllingPrompts (`docs/src/content/docs/docs/ControllingPrompts.md`)


title: Controlling Prompts slug: docs/ControllingPrompts

When a choice runs, every placeholder that needs input becomes a prompt: {{VALUE:title}} in a file name, {{VDATE:due,YYYY-MM-DD}} in a template, the target file of a Capture, and so on. This page covers everything you control about those prompts - which ones appear, the order they come in, how each one looks, how to make one skippable, the keys that submit or cancel, and the one-page form that asks everything at once.

For the full syntax of each placeholder and flag, see Format Syntax. For the one-page form in depth, see One-page Inputs.

What QuickAdd asks you {#where-prompts-come-from}

QuickAdd prompts whenever it meets a placeholder it cannot fill on its own:

  • {{VALUE}} / {{VALUE:name}} ask for text, or show a pick list when you give them options.
  • {{VDATE:name,format}} asks for a date, and understands natural language like tomorrow.
  • {{FIELD:name}} suggests values a property already has in your vault.
  • {{FILE:folder}} asks you to pick a note from a folder.
  • Template choices may also ask for a file name or folder; Capture choices may ask which file to capture to.

You are asked once per variable, per run. If {{VALUE:title}} appears in both the file name and the template body, you answer once and QuickAdd reuses it. A variable that already has a value never prompts: prefilled variables from a macro step, the API, or the CLI skip their prompts, and even an empty string counts as an answer.

The order prompts appear in {#prompt-order}

Prompts follow the structure of the choice, not where the placeholders sit in your text. Take this template:

markdown
Attendees: {{VALUE:attendees}}
Due: {{VDATE:due,YYYY-MM-DD}}

The due prompt appears first, even though attendees comes first in the text. That is because dates are asked before named values within one piece of text. The full order:

  1. Template choices resolve the template path first, then the folder, then the file name, and finally the template's content. A placeholder in the file name always prompts before anything in the template body.
  2. Capture choices resolve the capture target first, then the capture format.
  3. Within one piece of text (a file name, a template, a capture format), included templates ({{TEMPLATE:path}}) ask their prompts first. The rest is grouped by kind, and only inside a kind do prompts follow the order they appear. The kinds run in this order: plain {{VALUE}}/{{NAME}} first, then dates ({{VDATE}}), then named values ({{VALUE:name}}), then fields ({{FIELD}}) and file pickers ({{FILE}}), with the math prompt ({{MVALUE}}) last. Dates come before named values so that a {{VALUE:due}} reusing a {{VDATE:due,...}} answer is asked once, as a date.

To be asked for one value first when prompts come one at a time, make it the plain {{VALUE}}, and name its prompt with |label:. This format asks for the body before the heading (the one-page form still lists the heading first, in format order):

markdown
## {{VALUE:heading}}
{{VALUE|type:multiline|label:Body}}

In a Capture, a plain {{VALUE}} takes your editor selection instead of asking, when text is selected and the Capture uses the selection.

No flag reorders individual prompts. If the sequence bothers you, switch on the one-page input form: it lists every input in one form, in the order of the steps above (after the day, when Which day asks for one) with each text's fields in the order they appear, and you fill them in whatever order you like.

:::note
A pick list defined with |name: and its reuses can appear in any order within one piece of text. When a reuse comes before the definition, QuickAdd resolves the definition early so you are still asked only once.
:::

Change how a prompt looks {#shape-an-individual-prompt}

Each control is a flag you add to the placeholder. The most common ones:

You want Flag Example
Your own wording for the prompt |label: {{VALUE:attendees|label:Who attended?}}
A pre-filled default |default: {{VALUE:status|default:open}}
A large, multi-line text box |type:multiline {{VALUE:notes|type:multiline}}
A number, slider, or checkbox |type:number and friends {{VALUE:rating|type:slider|min:0|max:10}}
A pick list comma-separated options {{VALUE:low,medium,high}}
A pick list that also accepts custom text |custom {{VALUE:home,work|custom}}
Several selections at once |multi {{VALUE:a,b,c|multi}}

The full reference for every flag lives in Format Syntax.

Good to know:

  • |label: works on {{VALUE}} placeholders, {{VDATE:...}} dates, {{FIELD:...}} suggestions, and {{FILE:...}} pickers. It names the prompt: it titles a text or date prompt in place of the variable name, names the field in the one-page form, and becomes a pick list's placeholder. The variable name still identifies the answer for reuse. A FIELD label also titles the text prompt you get when the property has no values yet.
  • |type:multiline upgrades a single placeholder to the large text box and overrides the global Use multi-line input prompt setting. There is no reverse flag: with the global setting on, every plain text prompt is already multi-line.
  • |type: flags only work on single-value placeholders. A pick list ignores them.
  • Date prompts and the one-page form's date fields have a calendar button at the end of the text box. It hides or shows the calendar, and every later date prompt opens the same way.

Make a prompt skippable {#optional-prompts}

Add |optional to let a prompt be left blank, in which case it resolves to nothing:

markdown
- [ ] {{VALUE:task}} {{VDATE:due,[📅 ]YYYY-MM-DD|optional}}

An optional prompt gains a Skip button and treats an empty submission as the answer, so you are not asked again later in the run. |optional works on {{VALUE}} placeholders, option lists, {{VDATE}}, and {{FILE}}.

Skipping is an answer; pressing Esc still cancels the whole choice. If the same variable appears in several places, put |optional on every occurrence - that is the one spelling that behaves the same in both the sequential prompts and the one-page form.

Keys that submit, skip, and cancel {#submit-keys}

Prompt Submit Also useful
Single-line input (also number, slider, and date prompts) Enter
Multi-line input Ctrl/Cmd+Enter (Enter inserts a newline) Tab indents; Shift+Tab moves focus out
Pick list / suggester Enter picks the highlighted option
Math prompt ({{MVALUE}}) Ctrl/Cmd+Enter Tab jumps to the cursor marker
One-page input form Ctrl/Cmd+Enter Tab moves between fields
Any optional prompt Ctrl/Cmd+Shift+Enter skips

Esc cancels the prompt and with it the whole run - nothing is created or captured by the cancelled choice. (In a macro, steps that already ran are not undone.) To get a notice when that happens, enable Show input cancellation notifications under Settings → QuickAdd → Advanced (QuickAdd 2.30.0 or later; earlier versions show it on the main QuickAdd tab).

Peek at the note {#peek}

Single-line and multi-line text prompts, plus the one-page input form, can get out of the way so you can read or select text in the open note, then come back to the same draft.

  • Peek at note on the prompt, or Ctrl/Cmd+Shift+E on desktop, hides the prompt without cancelling the run. On the one-page form, it hides the whole form. Everything in the prompt survives the peek: the draft, its undo history, a paste still saving, even the text you had selected in the field.
  • A chip stays on screen: Insert selection replaces the field's selection (or drops in at the caret) and returns, Return comes back as-is, and Cancel aborts the run. On the one-page form, Insert selection uses the last text field you focused, or the first text field if you have not focused one. On desktop, Ctrl/Cmd+Shift+E also returns. Peek does not bind Esc in the editor, so Vim users can still leave insert mode, and your other Obsidian hotkeys keep working while the chip is up.
  • The command QuickAdd: Return to prompt is available while a peek is open. On a phone the chip is a one-line bar under the note header, so the bottom nav and home indicator stay free, and prompt actions stay on one row (Peek / Cancel / Ok) so the keyboard does not cover Cancel.
  • Peek appears on text prompts opened during a choice run or from the API, and on the one-page input form. It does not appear on QuickAdd's own settings and builder prompts, or when the prompt opens over another dialog, because there is no note to look at. Only one run can be parked: peeking a second prompt cancels the first, and returning waits until any other open prompt is closed.

Autocomplete while you type {#autocomplete-inside-prompts}

Inside a prompt, # searches your vault's tags and [[ searches your files (headings, blocks, and relative paths work too). See Suggester System for all triggers and keys.

These triggers work in single-line and multi-line prompts, and in text and textarea fields on the one-page form. Field and pick-list widgets keep their own inline suggestions.

One form instead of many prompts {#one-form-instead-of-many-prompts}

Rather than answering prompts one at a time, QuickAdd can collect everything in a single form before the choice runs. Every unanswered variable appears as the right widget - text, textarea, date with a calendar, dropdown, slider - with optional fields badged, and Template choices with a file name format get a live file name preview.

  • Turn it on for everything with One-page input for choices under Settings → Input.
  • Template and Capture choices each have a One-page input override dropdown in their builder (Follow global setting, Always, Never), so you can flip the form on or off for one choice.
  • A few inputs still run as follow-up steps after the form, such as {{FIELD:...|multi}} pickers and Capture's insert-after heading picker.
  • Cancelling the form cancels the whole run, exactly like cancelling a sequential prompt.

The full behavior - what is collected, date parsing, defaults, and script-declared inputs - is documented in One-page Inputs.

Style pickers with CSS {#style-pickers-with-css}

Requires QuickAdd 2.28.0 or later.

QuickAdd's pickers are standard Obsidian pickers, so they open near the top of the window like the command palette, while text prompts open in the middle. To change that, target these classes from a CSS snippet:

Class Picker
qa-choice-suggester The QuickAdd launcher and Multi choices
qa-suggester Single-value pick lists such as {{VALUE:a,b,c}}, the note picker for Template choices that look for existing notes, and quickAddApi.suggester in scripts

For example, this centers QuickAdd's pickers on desktop and leaves the command palette and other plugins alone:

css
body:not(.is-mobile) .prompt:is(.qa-choice-suggester, .qa-suggester) {
	top: auto;
}

A centered picker re-centers as you type, so the search box moves a little when the list gets shorter.

For script authors {#for-script-authors}

User scripts can declare their inputs so they appear in the one-page form (quickadd.inputs), and can open a one-page form of their own at runtime with quickAddApi.requestInputs. Both are covered in One-page Inputs.


Src Content Docs Docs FAQ (`docs/src/content/docs/docs/FAQ.md`)


title: FAQ slug: docs/FAQ

Syncing QuickAdd between devices

QuickAdd keeps everything - your choices, macros, global variables, and settings - in <vault>/.obsidian/plugins/quickadd/data.json. As long as your sync setup includes Obsidian's configuration folder, that one file brings your whole QuickAdd setup to the other device.

Two things don't travel with it:

  • The plugin itself. QuickAdd must be installed and enabled on the other device. Obsidian tracks enabled plugins in <vault>/.obsidian/community-plugins.json.
  • Secrets. API keys stored through Obsidian's secret storage stay on the device where you entered them, so enter them once per device.

In QuickAdd 2.30.0 or later, a device that's already running picks up the synced data.json on its own, so your changes appear without a restart. Earlier versions read data.json only when QuickAdd loads: after the files have synced, restart Obsidian on the receiving device - or toggle QuickAdd off and on in Settings -> Community plugins - and your changes appear.

If you use Obsidian Sync, also check Settings -> Sync on each device:

  • Enable Active community plugin list and Installed community plugin list under Vault configuration sync if you want Obsidian Sync to install and enable QuickAdd for you.
  • If a macro runs a standalone .js user script, enable Sync all other types. Obsidian Sync skips .js files unless that setting is on (see Sync settings) - which is why a macro can arrive on the other device with its script missing: the configuration syncs, the script doesn't, and the macro fails.
  • Scripts kept in Markdown notes sync like any other note and sidestep the file-type toggle entirely. See User Scripts for both script forms.

With iCloud, Dropbox, Git, Syncthing, or any other file-sync tool, the file-type toggle doesn't apply - just make sure the tool syncs the whole .obsidian folder plus your script files. Before QuickAdd 2.30.0, also restart or re-enable QuickAdd on the other device.

For a one-time transfer, export a QuickAdd package and import it on the other device. A package moves your QuickAdd configuration and bundled dependent scripts. Secrets still stay local, so enter those on each device.


Src Content Docs Docs FormatSyntax (`docs/src/content/docs/docs/FormatSyntax.md`)


title: Format syntax slug: docs/FormatSyntax

Format syntax lets you put placeholders in anything QuickAdd creates. When a
choice runs, each placeholder is replaced with a real value: today's date, an
answer you type, a link to the note you came from.

You can use placeholders anywhere QuickAdd asks for a format: file name fields,
capture formats, folder paths, "Insert after" targets, and inside template files.

In the format fields of a choice's settings (such as Capture format,
Capture to, File name, and the insert after/before targets), type
{{ to get a list of placeholders, then keep typing to filter it. Template
files and folder pickers don't show this list. Press Enter to insert the highlighted one. For placeholders that take
an argument, like {{VDATE:}}, the cursor lands inside so you can finish it.

For example, a Capture with this format:

markdown
- {{DATE:HH:mm}} {{VALUE}}

asks you for a value, and if you answer Standup moved to Wednesday, it inserts:

markdown
- 09:42 Standup moved to Wednesday

You describe the shape once; QuickAdd fills in the blanks every run.

Answers, selected text, clipboard contents and value- URI parameters are
inserted as written. A placeholder or Templater <% %> tag inside them stays
text. A Templater tag in the format itself can still use an answer, as in
<% "{{VALUE:title}}".toUpperCase() %>.

Quick reference {#quick-reference}

Ask for input

Placeholder What it does
{{VALUE}} Ask for text
{{VALUE:title}} Ask for text once, reuse the answer anywhere as title
{{VALUE:Red,Green,Blue}} Pick from a list
{{VDATE:due,YYYY-MM-DD}} Ask for a date ("tomorrow" works)
{{FIELD:project}} Suggest values that property already has in your vault
{{PROPERTY}} Insert the property's current value (property Captures only)
{{CURSOR}} Place the cursor inside a Template or Capture
{{FILE:People}} Pick a note from a folder
{{MVALUE}} Write a math formula (LaTeX)

Dates

Placeholder What you get
{{DATE}} Today, like 2026-07-08
{{DATE:MMMM Do}} Today, formatted your way: July 8th
{{DATE+7}} Seven days from today
{{DATE:YYYY-MM|startof:week}} The week's starting month, for weekly notes
{{TIME}} The current time, like 14:05
{{DAILY}} The daily note, like Journal/2026-07-08
{{WEEKLY}} This week's note, like Weekly/2026-W28

The note you ran QuickAdd from

Placeholder What you get
{{LINKCURRENT}} A link to it: [[That note]]
{{LINKSECTION}} A link to the section your cursor is in
{{FILENAMECURRENT}} Its file name
{{FOLDERCURRENT}} Its folder
{{SELECTED}} The text you had selected

The note being created

Placeholder What you get
{{TITLE}} The new note's file name
{{FOLDER}} The folder the new note lands in

Other content

Placeholder What it inserts
{{CLIPBOARD}} Whatever you copied last
{{TEMPLATE:Templates/Meeting.md}} The contents of a template file
{{MACRO:My Macro}} Whatever a macro returns
{{GLOBAL_VAR:Header}} A snippet you defined in settings
{{RANDOM:6}} A random ID like x7k2p9

Dates

Today's date: `{{DATE}}` {#date}

{{DATE}} becomes today's date in YYYY-MM-DD format.

A choice can aim that token at another day, so the same
Daily/{{DATE}}.md template opens yesterday when you ask it to. {{DATE+3}}
is still three days later. {{TIME}} is always the time you ran the choice.
See Which day.

{{DATE:HH:mm}} only prints the clock, so it looks like {{TIME}}. The day
is still the chosen one. Write {{DATE:YYYY-MM-DD HH:mm}} if you want both.

Add +N to move the date: {{DATE+3}} is three days from that day,
{{DATE+-3}} is three days before it.

markdown title="you write"
Daily/{{DATE}}.md
Review on {{DATE+7}}
markdown title="you get (on july 8th, 2026)"
Daily/2026-07-08.md
Review on 2026-07-15

:::note[Seeing +3 or {{DATE+3}} in your note?]
Obsidian's Daily notes and Templates core plugins fill {{date}},
{{time}}, and {{title}} themselves, and nothing else. If Open today's
daily note
or Insert template filled in the note, {{DATE}} works, but
{{DATE:YYYY-MM-DD+3}} becomes 2026-07-08+3, {{DATE+3}} stays as-is, and
|startof: turns into gibberish. Run the file from a QuickAdd
Template choice instead.
:::

Choose the date format: `{{DATE:}}` {#date-format}

Put a Moment.js format after
the colon to control how the date looks. The +N day offset works here too:
{{DATE:YYYY-MM-DD+3}}.

You write You get
{{DATE:MMMM Do, YYYY}} July 8th, 2026
{{DATE:YYYY-MM-DD_HH-mm}} 2026-07-08_09-42
{{DATE:[Week] w}} Week 28

:::tip
Literal text inside a date format goes in square brackets, like [Week] above.
Otherwise Moment.js treats every letter as a date token.
:::

Change date text casing: `|case:` {#date-case}

Add |case:<style> after a DATE, TIME, or VDATE format to transform its
rendered text. It uses the same styles as VALUE: kebab,
snake, camel, pascal, title, lower, upper, and slug.

markdown
## {{DATE:dddd, MMMM Do, yyyy.|case:lower}}
{{TIME:A|case:lower}}
{{VDATE:due,dddd, MMMM Do, YYYY|case:lower}}

On August 11, 2026 in the English locale, the first line becomes
## tuesday, august 11th, 2026.. Moment formats the date in your current
locale first, then QuickAdd applies the case transform. The transform is per
placeholder and does not change the locale or a reused VDATE value.

When combining a date snap and casing, put casing last:
{{DATE:MMMM YYYY|startof:month|case:lower}}.

Snap to the start or end of a week, month, or year {#date-snap}

Add |startof: or |endof: to move the date to the boundary of a
period before formatting. `` is one of year, quarter, month, week,
isoweek, or day (case-insensitive).

This matters when the formatted output should reflect the period rather than
the exact day. The month of a week-snapped date is the month the week starts
in, not today's calendar month.

You write (on Thursday 2023-06-01) You get
{{DATE:gggg.MM.[Wk]w|startof:week}} 2023.05.Wk22
{{DATE:YYYY-MM|startof:month}} 2023-06
{{DATE:YYYY-MM-DD|endof:month}} 2023-06-30
{{DATE:YYYY-[Q]Q|startof:quarter}} 2023-Q2
{{DATE:GGGG-[W]WW|startof:isoweek}} ISO week, Monday-anchored
  • week starts on your locale's first day of the week (matching the w/ww/gggg tokens).
  • isoweek starts on Monday (matching the W/WW/GGGG tokens).

Example: weekly notes that cross months. A weekly note named
gggg.MM.[Wk]w should file the week of June 1 under May (2023.05.Wk22),
while the heading inside the note still shows the actual day. Snap only the
file name:

markdown
File name:  {{DATE:gggg.MM.[Wk]w|startof:week}}
In the note: {{DATE:M.DD dddd}}

Snapping also works on {{VDATE}}, so one picked date can be
week-snapped in the file name and day-accurate in the body:
{{VDATE:d,gggg.MM.[Wk]w|startof:week}} and {{VDATE:d,M.DD dddd}} share the
same prompt. Combine freely with |default, |optional, and |time in any
order.

Good to know:

  • Snaps are QuickAdd-only. Obsidian's Open today's daily note and Insert template garble them; see above.
  • The +N day offset is applied before the snap, so {{DATE:YYYY-MM-DD+7|startof:week}} means "the start of next week".
  • endof: snaps to the last moment of the period (23:59:59.999), so {{DATE:YYYY-MM-DD HH:mm|endof:day}} renders ... 23:59.
  • |startof:, |endof:, and |case: are special pipe options in a date format. Any other literal | is kept as-is: {{DATE:YYYY|MM}} gives 2023|06. Put a literal |case:lower inside square brackets.
  • An unknown unit (like |startof:fortnight) shows an error listing the valid units.

The current time: `{{TIME}}` {#time}

{{TIME}} becomes the current time in HH:mm format. {{TIME:<format>}}
takes any Moment.js format, the
same as {{DATE:<format>}}.

markdown title="you write"
- {{TIME}} {{VALUE}}
Meeting {{DATE}} {{TIME:HH.mm}}
markdown title="you get"
- 14:05 Standup moved to Wednesday
Meeting 2026-07-08 14.05

Use {{TIME:HH.mm}} rather than {{TIME}} inside a file name: : is not
allowed in file names on Windows or macOS.

Unlike {{DATE}}, {{TIME}} takes no +N offset. For a time other than "now",
use {{DATE:HH:mm}} with an offset, or ask for one with
{{VDATE:<name>, <format>|time}}.

The daily note: `{{DAILY}}` {#daily}

Requires QuickAdd 2.30.0 or later.

{{DAILY}} is the path of the day's daily note, from the folder and date
format in Obsidian's Daily notes settings. Set Capture to to
{{DAILY}} and entries land in the note Open today's daily note opens,
even after you change those settings.

You write You get (Daily notes folder Journal, format YYYY-MM-DD)
{{DAILY}} Journal/2026-07-08
{{DAILY|link}} [[2026-07-08]]

When the Periodic Notes
plugin manages daily notes, {{DAILY}} uses its settings instead.

Good to know:

  • The day follows the choice's Which day, so picking yesterday captures to yesterday's daily note.
  • If the daily note doesn't exist and Create file if it doesn't exist is on, a Capture creates it from the daily notes template, filled the way that plugin fills it, not with QuickAdd's format syntax. With Daily notes, {{date}} and {{time}} are when the note is created and {{title}} is its name. With Periodic Notes, {{date}} is the note's day, and {{yesterday}} and {{tomorrow}} work too. Turn on Create file with a template to use a QuickAdd template instead.
  • |link follows your link settings. A daily note that doesn't exist yet is linked by its full path, so following the link creates it in your daily notes folder. In front matter, quote it: day: "{{DAILY|link}}".
  • If neither plugin manages daily notes, or the daily note template is missing, the run stops with an error instead of writing somewhere else.

Weekly, monthly, quarterly, and yearly notes: `{{WEEKLY}}` {#periodic-notes}

Requires QuickAdd 2.30.0 or later.

{{WEEKLY}}, {{MONTHLY}}, {{QUARTERLY}}, and {{YEARLY}} work like
{{DAILY}} for the notes the
Periodic Notes plugin
manages, with that plugin's folder, format, and template for each period.

You write (on Thursday 1 June 2023, weekly format gggg.MM.[Wk]w) You get
{{WEEKLY}} Weekly/2023.05.Wk22
{{MONTHLY}} Monthly/2023-06
[[{{QUARTERLY}}]] or {{QUARTERLY|link}} A link to 2023-Q2

Good to know:

  • A note is named from the start of its period, the way Periodic Notes names it. The week of 1 June 2023 starts on 28 May, so it files under May.
  • A missing note is created from the period's template with Periodic Notes' placeholders. In weekly notes, {{sunday:<format>}} through {{saturday:<format>}} give that day of the week. In all periods, {{date:<format>}} is the start of the period.
  • If Periodic Notes doesn't manage the period, the run stops with an error.
  • Periodic Notes 1.0 (beta) isn't supported yet; use the released version, 0.0.17.

Ask for a date: `{{VDATE:, }}` {#vdate}

{{VDATE:due,YYYY-MM-DD}} opens a date prompt and inserts your answer in the
given format. You can type natural language: today, in two weeks,
next monday. Short aliases like t (today), tm (tomorrow), and yd
(yesterday) work too, and are configurable in settings.

The name (due above) makes it a variable: enter the date once, use it in as
many places and formats as you like.

markdown title="you write"
Due: {{VDATE:due,YYYY-MM-DD}}
Week: {{VDATE:due,gggg-[W]WW}}
markdown title="you get (after answering "friday")"
Due: 2026-07-10
Week: 2026-W28

:::note[Need the day before or after the date you pick?]
A VDATE format takes no +N offset, so +-1 is printed as-is. Set the
choice's Which day to Ask each
time
instead. {{DATE}} is then the day you pick, and {{DATE+N}} counts
from it.

markdown title="you write"
Yesterday: [[{{DATE:YYYY-MM-DD-ddd+-1}}]]
Tomorrow: [[{{DATE:YYYY-MM-DD-ddd+1}}]]
markdown title="you get (after picking 2026-03-10)"
Yesterday: [[2026-03-09-Mon]]
Tomorrow: [[2026-03-11-Wed]]

:::

Reuse the date with `{{VALUE:}}` {#vdate-reuse}

Requires QuickAdd 2.29.0 or later.

{{VALUE:due}} after {{VDATE:due,<format>}} prints the same date, in that
VDATE's format. It's a short way to repeat a date you already asked for. With
several VDATEs for one name, the first one's format is used.

markdown title="you write"
- [ ] Throw and trim 📅 {{VDATE:due,YYYY-MM-DD}}
- [ ] Glaze fire 📅 {{VALUE:due}}
markdown title="you get (after answering "friday")"
- [ ] Throw and trim 📅 2026-07-10
- [ ] Glaze fire 📅 2026-07-10

A {{VALUE:due}} in a template you include with
{{TEMPLATE:path}} reuses the date the same way (QuickAdd 2.30.0
or later). An include asks its prompts first, so step by step the date prompt
opens when the include needs the date.

A date that a script set, with no VDATE for that name, prints as
YYYY-MM-DD. QuickAdd 1.14.0 to 2.28.0 printed the stored value instead, such
as @date:2026-07-10T10:00:00.000Z.

:::note
Pipes (|) can't be part of a VDATE date format; everything after the first
pipe is read as the default value and flags. For a literal separator, use
bracketed text instead: {{VDATE:due,[Due ]YYYY-MM-DD}}.
:::

Give the date prompt a default {#vdate-default}

Add |<default> after the format. If you submit the prompt empty, the default
is used. Defaults can be natural language too: {{VDATE:date,YYYY-MM-DD|today}},
{{VDATE:due,YYYY-MM-DD|next monday}}, {{VDATE:d,YYYY-MM-DD|+7 days}}. The
short aliases (t, tm, yd) also work as defaults.

A default combines with the optional flag in any order:
{{VDATE:due,YYYY-MM-DD|tomorrow|optional}} and
{{VDATE:due,YYYY-MM-DD|optional|tomorrow}} are equivalent.

Name the date prompt: `|label:` {#vdate-label}

Requires QuickAdd 2.30.0 or later.

{{VDATE:due,YYYY-MM-DD|label:When is it due?}} asks "When is it due?"
instead of "due", in the date prompt and in the
one-page input form. The name is still the
variable, so {{VALUE:due}} reuses the answer as before. With several VDATEs
for one name, the first one's label is used.

Ask for a time too: `|time` {#vdate-time}

Add |time (aliases: |datetime, |type:datetime) to put a time picker on
the date prompt - made for Date & time properties. The calendar gains an
HH:mm control, and picking a day keeps the time you set. If you omit the
date format, it defaults to YYYY-MM-DD HH:mm.

markdown
---
start: {{VDATE:start,YYYY-MM-DDTHH:mm|time}}
---

Combines with a default and optional in any order:
{{VDATE:meeting,YYYY-MM-DD HH:mm|tomorrow at 3pm|time|optional}}. Without
|time, the picker stays date-only.

Ask for input

Ask for text: `{{VALUE}}` {#value}

{{VALUE}} opens a prompt and inserts whatever you type. {{NAME}} is the
same thing under another name.

markdown title="you write (capture format)"
- [ ] {{VALUE|label:Task}}
markdown title="you get (after typing "buy milk")"
- [ ] Buy milk

If text is selected in the editor when the choice runs, the selection is used
as the value instead of prompting. For Capture choices you can turn
selection-as-value off, globally or per capture.

:::note[Paste images straight into the prompt]
Prompts whose answer lands in note content accept images. Paste (Ctrl/Cmd+V) a
screenshot or copied image, or drag an image from a file manager. QuickAdd
saves it using Obsidian's attachment settings and inserts an embedded link at
the cursor. You can mix typed text and images, and paste more than one.
Dropped images keep a sanitized original file name. If the drop is already a
vault image, QuickAdd embeds that file instead of copying it.

Clipboard text wins over an image on paste. Image files win on drop because
file managers also provide the filesystem path as text. Turn on Name pasted
images after the note title
in QuickAdd settings to name a pasted file after
the destination note when that path is known (otherwise the file stays
Clipboard image YYYY-MM-DD HH.MM.SS). Dropped files keep their sanitized
original name even when that setting is on. Prompts for file names, folders,
capture targets, and insert-after/before targets never accept images because
an embed link would break the path. Saved attachments are ordinary vault
files. Cancelling the prompt does not delete them.
:::

Good to know:

  • In macros, {{VALUE}} / {{NAME}} ask again for each template step. Use a named value like {{VALUE:sharedName}} when one answer should be reused across the whole macro.
  • In js quickadd blocks, don't put {{VALUE}} inside JavaScript string literals. Use the QuickAdd API (this.quickAddApi.inputPrompt(...)) and this.variables instead. See Inline scripts.
  • From the API, pass the value programmatically under the reserved variable name value.

Name the answer so you can reuse it: `{{VALUE:}}` {#named-value}

Give the value a name and QuickAdd asks once, then inserts the same answer at
every other place the name appears - even across the steps of a macro.

markdown title="you write (template)"
---
title: {{VALUE:title}}
---
# {{VALUE:title}}
markdown title="you get (after answering "project kickoff")"
---
title: Project kickoff
---
# Project kickoff

You can create as many named values as you need.

Offer a list to pick from: `{{VALUE:

Two or more comma-separated options turn the prompt into a searchable picker.

markdown title="you write"
status: {{VALUE:Backlog,In progress,Done}}
markdown title="you get (after picking)"
status: In progress

Need a comma inside an option? Wrap the option in double quotes - the
quotes are stripped from the inserted value:

markdown
{{VALUE:"One choice, with a comma",Second choice}}
  • Straight (") and curly (“ ”) quotes both work, so pasting from a rich-text editor is fine.
  • For a literal double quote inside a quoted option, double it: "say ""hi""" inserts say "hi".
  • Single quotes are never special: Bob's,Alice's works unchanged.
  • The same quoting works in |text: display labels and |default: values (e.g. |default:"a, b").
  • Pipes (|) inside an option are not supported. Whitespace inside quotes is trimmed.

Show friendlier labels: `|text:` {#value-text}

Decouple what the picker shows from what gets inserted:

markdown title="you write"
priority: {{VALUE:🔽,🔼,⏫|text:Low,Normal,High}}

You pick "High", QuickAdd inserts ⏫.

items and text need the same number of entries, and each label must be
unique. A comma inside an entry needs double quotes (text:"High, urgent",Low);
pipes inside an entry are not supported. With |custom, text
you type yourself is inserted as-is.

Allow answers outside the list: `|custom` {#value-custom}

{{VALUE:Red,Green,Blue|custom}} suggests Red, Green, and Blue but also lets
you type anything else, like "Purple". Useful when you have common options but
want an escape hatch.

Note: |custom can't be combined with a shorthand default;
use |default: instead.

Pick several: `|multi` {#value-multi}

|multi turns the picker into a multi-select, and the picks are written as a
YAML list. Requires an option list (2+ comma-separated values).

markdown title="you write"
---
tags: {{VALUE:work,home,urgent|multi}}
---
yaml title="you get (after picking work and urgent)"
tags:
  - work
  - urgent

Variants and combinations:

  • |multi:linklist wraps each pick as a wikilink, for list properties of links: {{VALUE:Alice,Bob,Carol|multi:linklist}} writes - "[[Alice]]" / - "[[Bob]]".
  • |multi|custom adds a text box to the picker for values not in the list.
  • Combines with |name:, |label:, |text:, |optional, and |trim. |case: is ignored (a list isn't case-transformed).

Add |format: when the output shape should be intentional instead of inferred
from where the placeholder appears:

  • |format:yaml writes a quoted YAML flow sequence. Obsidian recognizes it as
    a native list, including in capture content inserted into a template:
    topics: {{VALUE:Alpha,Beta|multi|format:yaml}} becomes
    topics: ["Alpha", "Beta"].
  • |format:markdown writes a vertical Markdown bullet list. Put the placeholder
    on its own line: {{VALUE:Alpha,Beta|multi|format:markdown}} becomes - Alpha
    followed by - Beta.
  • |format:inline always writes the compact comma-separated text form:
    Alpha,Beta.
  • |format:spaced writes the same text with a space after each comma:
    {{VALUE:option a, option b|multi|format:spaced}} becomes option a, option b.
  • |format:auto is the default and preserves the context-sensitive behavior
    described below.

Formatting is separate from the selected item representation, so it composes
with |multi:linklist, |text:, |custom, and the other multi-select options.
For example, |multi:linklist|format:yaml writes a native YAML list of
wikilinks.

Good to know:

  • The picks become a real YAML list inside front matter. In a note body they become comma-separated text.
  • In a Capture, a whole multi-select token with the default |format:auto stays a list with Write position → Property. Into a list property, a multi-select token on its own line adds one item per pick, even next to other lines. |format:markdown and |format:yaml add no dashes or brackets there, and |format:inline or |format:spaced joins the picks into one item. Capturing into a brand-new note's frontmatter also produces a list when Create file if it doesn't exist is enabled without a template. Captures into an existing note's body write comma-separated text.
  • With the one-page input form, avoid commas inside a single option (like |text:"High, urgent") on a |multi placeholder - the one-page picker can't round-trip them. The default one-prompt-at-a-time picker handles them correctly.

Reuse the pick elsewhere: `|name:` {#value-name}

|name: gives a picker a reusable name, so one pick can drive several places.
Define the options once, then reuse the answer anywhere with
{{VALUE:<name>}}:

markdown title="file name"
{{VALUE:Personal,Work,Errand|name:category}} - {{VALUE:title}}
markdown title="in the body"
tags: #{{VALUE:category}}

You pick the category once; the file name and the tag both use it.

Good to know:

  • Reuse is always {{VALUE:category}}. A bare {{category}} is not QuickAdd syntax and is left untouched (it would collide with Templater and Dataview).
  • |name combines with the other options: {{VALUE:🔽,🔼,⏫|name:priority|text:Low,Normal,High|label:Pick a priority}}.
  • Within one field, definition and reuses can appear in any order. Across fields in the default one-prompt-at-a-time flow, define the named picker in the field that is resolved first (the file name comes before the body); a reuse that runs earlier than its definition falls back to a text prompt. The one-page input form removes this caveat.
  • value and title are reserved and can't be used as names.
  • Names match case-insensitively: {{VALUE:Category}} reuses a pick named category.
  • First definition wins. If the same |name appears twice with different options in one run, the first definition's pick is reused and the second is never shown (a warning is logged to the developer console). Use distinct names for separate prompts.

Fine-tune any prompt

These options work on text prompts and pickers alike. Combine them freely:
{{VALUE:title|label:Note title|default:Untitled}}. Inside a table cell, write
the pipe as \|, as in {{FILE:People\|label:Who?}}; see
In a table cell.

In a table cell {#table-cell}

Requires QuickAdd 2.32.0 or later.

A Markdown table starts a new cell at every |, so inside a table, write the
pipe between a token's options as \|. QuickAdd reads \| inside {{...}}
as |, and the table keeps its cells:

markdown
| Client | Due |
| --- | --- |
| {{FILE:Clients\|link\|label:Client}} | {{VDATE:due,YYYY-MM-DD\|label:Due date}} |

This works for every token with options. A \| outside a token is left as it
is. Before QuickAdd 2.32.0, the \ became part of the option, so
{{FILE:Clients\|label:Client}} looked for notes in a folder named Clients\
and found none.

QuickAdd doesn't escape what it writes into the cell, so an answer that
contains a |, such as a link with display text ([[Note|Shown text]]), still
starts a new cell.

Name the prompt: `|label:` {#value-label}

{{VALUE:project|label:Which client or project?}} asks "Which client or
project?" instead of "project", in the prompt and in the
one-page input form. The name is still the
variable, so {{VALUE:project}} reuses the answer. On an option list, the
label titles the picker: {{VALUE:Red,Green,Blue|label:Pick a color}}.
Without one, the one-page form titles it by its |name: or else its options,
such as "Red / Green / Blue". A title longer than 30 characters stops at a whole
option and ends with "…" (QuickAdd 2.32.0 or later; earlier versions showed
Red,Green,Blue). An unnamed {{VALUE|label:What's the order?}} also shows its label in the
builder's preview.

Before QuickAdd 2.30.0, a named prompt kept its name as the title and showed
the label as helper text below it. If you wrote a label as an instruction,
such as |label:Comma-separated names, reword it as the question you want
asked.

Pre-fill a default: `|` {#value-default}

{{VALUE:name|Anonymous}} pre-fills the input with Anonymous - press Enter
to accept, or edit it.

Good to know:

  • Pickers without |custom ignore defaults - you have to pick one of the options.
  • If the placeholder also uses keyed options (|label:, |default:, |type:, |case:), the shorthand form is ignored; write |default:Anonymous instead. The bare |optional flag is the exception: {{VALUE:name|Anonymous|optional}} keeps the shorthand default.
  • optional is a reserved word, so a literal default of "optional" needs the keyed form: |default:optional.

The keyed form: `|default:` {#value-default-option}

Same as above, written {{VALUE:title|default:Untitled}}. Required whenever
you combine a default with other keyed options like |label:.

Ask for multiple lines: `|type:multiline` {#value-multiline}

{{VALUE:summary|type:multiline}} opens a textarea instead of a single-line
input. Works on single-value prompts only (no option lists or |custom), and
overrides the global "Use multi-line input prompt" setting for that
placeholder.

Mix single-line and multi-line in one format:

markdown
- {{VALUE:Title|label:Title}}
{{VALUE:Body|type:multiline|label:Body}}

Keyboard: submit with Ctrl/Cmd+Enter; plain Enter adds a newline.
Tab inserts a tab character (with a selection, it indents every touched
line), and Shift+Tab moves focus out of the field. See
Controlling Prompts for all prompt
shortcuts.

If |type: is present, shorthand defaults like |Some value are ignored; use
|default:.

Match an Obsidian property type: `|type:number`, `|type:slider`, `|type:checkbox`, `|type:text` {#value-property-types}

These give the prompt the right input widget for a property, and write a value
Obsidian reads as the right type:

markdown
---
rating: {{VALUE:rating|type:number|min:1|max:10}}
confidence: {{VALUE:confidence|type:slider|min:0|max:100|step:5|default:50}}
done: {{VALUE:done|type:checkbox|label:Completed?}}
id: {{VALUE:id|type:text}}
---
  • |type:number shows a numeric input and writes the value unquoted (rating: 42), so Obsidian reads a Number. Constrain it with |min:, |max:, and/or |step:.
  • |type:slider shows a slider plus numeric input. |min: and |max: are required; |step: defaults to 1. If the range is missing or invalid, QuickAdd falls back to the plain numeric input instead of guessing.
  • |type:checkbox (alias |type:boolean) shows a true / false picker for checkbox properties. The |label: becomes the picker's title, and it writes a real boolean: done: true.
  • |type:text keeps the value a string by writing it as a quoted YAML value (id: "0042"). Without it, a text property given 0042 is read as the number 42, true becomes a boolean, and values starting with # or [ are mis-parsed entirely.

Good to know:

  • Plain numbers and booleans already round-trip without |type: - count: {{VALUE:count}} answered 42 becomes a Number. The |type: options add the right widget and validation, and |type:text fixes the cases Obsidian would otherwise mis-read.
  • Dates like 2025-12-25 are always kept as text by Obsidian, so they never need |type:text.
  • |min:, |max:, and |step: are only treated as numeric options together with |type:number or |type:slider. Without those, they keep their old meaning as ordinary text.

Change the casing: `|case:` {#value-case}

Transforms the answer's casing. Styles: kebab, snake, camel, pascal,
title, lower, upper, slug.

markdown title="you write"
{{DATE:YYYY-MM-DD}}-{{VALUE:title|case:slug}}.md
markdown title="you get (after answering "my great idea!")"
2026-07-08-my-great-idea.md

Trim stray spaces: `|trim` {#value-trim}

|trim removes leading and trailing whitespace from the answer for that one
placeholder - useful in file names, links, and properties where an accidental
space from a mobile keyboard would create a different note.

markdown
Raw: {{VALUE:title}}
Link: [[{{VALUE:title|trim}}]]

Trimming is per placeholder and doesn't change the stored value, so the same
answer can be used raw in one place and trimmed in another. It composes with
other options ({{VALUE:title|trim|case:slug}}). For |multi values, each
entry is trimmed while the value stays a list. The keyed form |trim:false
turns trimming off when a shared snippet adds it.

Make a prompt skippable: `|optional` {#optional-fields}

|optional marks a prompt as fine to leave unanswered: skipping it resolves
to nothing instead of blocking the run. Works on {{VALUE}}/{{NAME}},
{{VALUE:<name>}}, option lists, {{VDATE:...}}, and {{FILE:...}}.

markdown
{{VALUE:reminder|optional}}
{{VDATE:due,YYYY-MM-DD|optional}}
{{VALUE:low,medium,high|optional}}

What optional changes:

  • Prompts gain a Skip button (and a hint line). Skipping - or submitting empty - counts as an answer: the placeholder becomes nothing, and you aren't asked again for the same variable later in the run.
  • Empty beats the default. With a default, the default is pre-filled; clearing it and submitting yields empty. (Required prompts keep the old behavior: empty falls back to the default.)
  • Optional dates accept a blank instead of failing the run. A typo like "tomorow" still errors - only blank means "leave empty".
  • Option lists show a skip shortcut in the footer (Ctrl/Cmd+Shift+Enter) instead of forcing a pick.
  • The one-page input form shows an "(optional)" badge; optional dropdowns get a "Skip (leave empty)" entry.
  • Esc still cancels the whole run - skipping is an answer, cancelling is not.

:::tip[Make decoration disappear with the answer]
Put literal text inside the date format using square brackets:

markdown
- [ ] {{VALUE}} {{VDATE:due,[📅 ]YYYY-MM-DD|optional}}

An answered date renders 📅 2026-06-14; a skipped date renders nothing at
all - the emoji vanishes with it. Works for prefixes like [Due: ]YYYY-MM-DD
too.
:::

Good to know:

  • The keyed form |optional:false turns the flag off explicitly (useful when a shared snippet adds it).
  • The flag sits happily next to a shorthand default: {{VALUE:reminder|call mom|optional}}. Because optional is reserved, a literal default of "optional" needs {{VALUE:x|default:optional}}.
  • Scripting: setting a variable to the empty string (params.variables.myVar = "") counts as "answered, empty" for every placeholder type, including {{VDATE}} - it renders empty instead of prompting. To force a prompt, leave the variable unset (or delete it / set it to undefined). The old workaround of assigning a single space still works but is no longer needed.

The note you ran QuickAdd from

These placeholders read from the active note - the one that was focused
when you triggered QuickAdd.

Inserts a link to the active note, in [[link]] format.

markdown title="you write"
Source: {{LINKCURRENT}}
markdown title="you get"
Source: [[Meeting with Alice]]

When the append-link setting is Enabled (skip if unavailable), this
placeholder becomes empty instead of erroring when no note is focused.

Like {{LINKCURRENT}}, but links to the heading your cursor is under, in
[[Note#Heading]] format - clicking the link jumps to that section instead of
the top of the file.

It picks the nearest heading at or above the cursor. With the cursor above the
first heading (or in a file without headings), it falls back to a plain
whole-file link. When a heading's text repeats in the file, it uses the
disambiguating ancestor path ([[Note#Parent#Heading]]); if even that isn't
unique, it falls back to a whole-file link rather than linking to the wrong
section. Honors the same required/optional behavior as {{LINKCURRENT}}.

The note's file name: `{{FILENAMECURRENT}}` {#filenamecurrent}

The active note's file name, without the extension: Notes from {{FILENAMECURRENT}}. Honors the same required/optional behavior as
{{LINKCURRENT}} - when optional and no note is active, it becomes empty.

In a Template File name,
{{FILENAMECURRENT}}/{{VALUE}} creates the new note under a folder named after
the active note.

The note's folder: `{{FOLDERCURRENT}}` {#foldercurrent}

The active note's folder, as a vault-relative path with no trailing slash
(Projects/Alpha). For a note at the vault root it becomes empty. Not to be
confused with {{FOLDER}}, which is the folder a new note is
being created in.

This makes per-project captures work without a macro. With Capture to set
to:

text
{{FOLDERCURRENT}}/Project Tasks.md

running the capture from any note inside Projects/Alpha targets
Projects/Alpha/Project Tasks.md, from Projects/Beta it targets
Projects/Beta/Project Tasks.md, and so on. A trailing slash
({{FOLDERCURRENT}}/) instead opens a file picker confined to that folder.

Works in capture targets, file name formats, note bodies and capture formats,
and Template choice folder paths (like {{FOLDERCURRENT}}/Subnotes).

No active note: in paths (capture targets, file names, folder paths) a
missing active note always stops the run with a clear error - it never falls
back to the vault root. In note bodies it honors the same required/optional
behavior as {{LINKCURRENT}}.

Just the folder's name: `{{FOLDERCURRENT|name}}` {#foldercurrent-name}

|name keeps only the last path segment: for a note in Projects/Acme,
{{FOLDERCURRENT}} is Projects/Acme while {{FOLDERCURRENT|name}} is
Acme. Use it when you want the folder's name in a file name or sentence
rather than a path: Tasks for {{FOLDERCURRENT|name}}.

The selected text: `{{SELECTED}}` {#selected}

Whatever text is selected in the editor, or empty when nothing is: > {{SELECTED}}.

The note being created

The new note's folder: `{{FOLDER}}` {#folder}

The folder the note is being created in, as a vault-relative path with no
trailing slash. For a note created at the vault root it becomes empty. (For
the active note's folder, use {{FOLDERCURRENT}}.)

Where it has a value:

  • Template choices - in the file name format (the folder is resolved before the name is built) and in the template body.
  • Capture - in the capture body, where it becomes the destination file's folder.
  • Apply template to a note - the target note's folder.

Where it stays empty: the capture Capture to field (that field is what
chooses the folder, so there is nothing to reference yet), the format
JavaScript API, and macro file-path commands.

Just the folder's name: `{{FOLDER|name}}` {#folder-name}

|name keeps only the last path segment: for a target folder Projects/Acme,
{{FOLDER}} is Projects/Acme while {{FOLDER|name}} is Acme. Use it to
put the folder's name into a file name: {{FOLDER|name}} - {{VALUE}}.

:::note
In a Template file name format, prefixing the full path (like
{{FOLDER}}/{{VALUE}}) is redundant - the note is already placed in the
target folder, so the leading path is stripped. Use {{FOLDER|name}} instead.
:::

The new note's title: `{{TITLE}}` {#title}

The final file name (without extension) of the note being created or captured
to. Handy as the note's top heading:

markdown
# {{TITLE}}

Pull data from your vault

Suggest values a property already has: `{{FIELD:}}` {#field}

{{FIELD:project}} scans your vault for every value the project property
has anywhere, and offers them as suggestions. If no values exist, you are
prompted to type one.

markdown title="you write"
project: {{FIELD:project}}

You get a picker listing every project name already used in your vault - no
more typos creating a second "Websiet Redesign" project.

Pick several: `|multi` {#field-multi}

{{FIELD:topic|multi}} turns the suggestions into a multi-select. Pick
several existing values, or add new ones in the picker.

markdown title="you write"
---
topics: {{FIELD:topic|multi}}
---
yaml title="you get (after picking alpha and beta)"
topics:
  - Alpha
  - Beta

Inside front matter, |multi writes a real YAML list when the placeholder is
the property's whole value. With the default |format:auto, it also stays a
list when the entire Capture format is the token and
Write position → Property is selected.
In note bodies, file names, and other text
positions it writes comma-separated text. Combines with the same filters and
defaults as single-value FIELD prompts:
{{FIELD:topic|multi|folder:Projects|tag:active|default:Inbox}}.

FIELD multi-selects support the same explicit output formats as VALUE:
|format:yaml, |format:markdown, |format:inline, |format:spaced,
and |format:auto.
For example, topics: {{FIELD:topic|multi|format:yaml}} always writes a native
YAML list, including in template-backed captures.

:::note
The one-page input form doesn't inline FIELD multi-selects yet: vault values
can contain commas, and the one-page multi input uses commas as separators.
When a format contains {{FIELD:...|multi}}, QuickAdd collects the other
one-page inputs first, then opens the regular multi-select for the FIELD
value.
:::

Label the prompt: `|label:` {#field-label}

Requires QuickAdd 2.28.0 or later.

{{FIELD:client|label:Which client?}} asks "Which client?" instead of
"Enter value for client". The label replaces that wording wherever the prompt
appears: the picker's placeholder, the title of the text prompt you get when
the property has no values yet, and the field's name in the
one-page input form.

markdown title="you write"
---
client: {{FIELD:client|label:Which client?}}
contact: {{FIELD:contact|label:Who did you talk to?}}
---

FIELD placeholders share one answer only when they match exactly, label
included. To ask twice from the same property, give each placeholder its own
label: {{FIELD:person|label:Host}} and {{FIELD:person|label:Note taker}}.
To ask once, write the same label (or none) everywhere.

Default to the active note's value: `|default-from:active` {#field-default-from-active}

{{FIELD:project|default-from:active}} pre-fills the prompt with the value
the active note already has for that property. This is metadata
inheritance: trigger a capture from a project note, and the new content
carries the project over as the default - accept it, pick another suggestion,
or type something else.

Active note:

yaml
---
project: The Great Endeavor
---

Format:

md
project: {{FIELD:project|default-from:active}}

The project prompt defaults to The Great Endeavor. The active note is
captured when the run starts, before any QuickAdd window can move focus.

Behavior:

  • The suggestion list still comes from your whole vault (honoring folder:/tag:/exclude-*/inline filters); the active note's value is promoted to the top as the default and pre-filled in the one-page form. It is promoted even if it already exists in the suggestions.
  • If no Markdown note is active, the note lacks the property, or the value is empty, you get a normal {{FIELD:...}} prompt with no default.
  • Plain string/number/boolean values are used as-is. A YAML list value applies only to |multi prompts, where each item is pre-checked in the picker; single-select prompts skip list values. Objects and null are never used.
  • The property name matches case-insensitively: {{FIELD:Project|default-from:active}} still reads a project property.
  • This differs from |default: (a literal value), and is deliberately not spelled |default:current - that would clash with "current" as a real field value.

Combine with |multi to inherit a list property:

md
topics: {{FIELD:topics|multi|default-from:active}}

Filter where suggestions come from {#field-filters}

You write Suggestions come from
{{FIELD:status|folder:projects}} Only files in projects
{{FIELD:status|folder:goals|folder:projects}} Files in either folder
{{FIELD:status|tag:work}} Only files with #work
{{FIELD:status|tag:active|tag:project}} Files with both tags
{{FIELD:status|exclude-folder:templates}} Everything except that folder
{{FIELD:status|exclude-tag:deprecated}} Everything except files with that tag
{{FIELD:status|exclude-file:example.md}} Everything except that file
{{FIELD:id|inline:true}} Also Dataview inline fields (id:: value)
{{FIELD:id|inline:true|inline-code-blocks:ad-note}} Also inline fields inside those fenced code blocks (opt-in)

Repeated folder: filters are OR (either folder). Repeated tag: filters are
AND (all tags). Exclusions remove any matching file. Filters combine freely:

markdown
{{FIELD:status|folder:goals|folder:projects|tag:work|exclude-folder:templates}}

Defaults work here too:

  • {{FIELD:status|default:To Do}} - put a default at the top of the suggestions; Enter accepts it.
  • {{FIELD:status|default:Draft|default-empty:true}} - only add the default when no values were found.
  • {{FIELD:status|default:Draft|default-always:true}} - keep the default first even when other suggestions exist.
  • {{FIELD:project|default-from:active}} - default to the active note's value (above).

:::caution[Beta]
FIELD filtering is in beta and the syntax can change - leave your thoughts
on issue #1429.
:::

Cursor position: `{{CURSOR}}` {#cursor}

In a Template or Capture body, {{CURSOR}} marks where you want to continue typing.

md
### {{DATE:DD-MMM-YYYY}}
- {{CURSOR}}

For complete setups, see
meeting notes and project updates.

QuickAdd removes the marker and places the cursor there when the target note
is already focused in an editing mode, or when Open opens and focuses it.
The marker never opens a note by itself, switches out of Reading view, or
focuses a background pane.

It works when creating a Markdown note from a Template, overwriting an existing
note, or adding the template at the top or bottom. It also works in all
Apply Template to Note modes, Capture's At cursor
and New line above/below cursor, and included {{TEMPLATE:...}} content.

The first marker in the note body sets the position. Additional markers in the
inserted content are removed. Spelling is case-insensitive. Insert and append
operations leave literal markers already in the destination note alone;
replacing the note replaces its old content.

A Template containing only markers can create an empty note. A Capture containing
only markers and whitespace does nothing. If Templater needs a new target note
before it produces an empty Capture, QuickAdd keeps that created note and skips
insertion and cursor placement.

Templater's cursor jump takes precedence. In Templates, QuickAdd finds the marker
after Templater finishes rendering, including content from tp.file.include.
QuickAdd preserves the position through frontmatter merges and links inserted
into the active note's body. Placement is skipped when a concurrent edit makes
the position unreliable. Capture also skips placement after a whole-file
Templater rewrite, a property update, or a link written through a specified
destination file.
The marker is still removed. Without a marker, cursor behavior stays unchanged.

In property Captures, Canvas and Base content, file names, folder paths, line
targets, and generic format API calls, the token is removed without moving the cursor.

The property's current value: `{{PROPERTY}}` {#property}

In a property Capture, {{PROPERTY}}
is the value the property already has on the target note. Put it where that
value should appear.

text title="new item above the existing ones"
work
{{PROPERTY}}
text title="new item below the existing ones"
{{PROPERTY}}
work
text title="keep the current text and add to it"
{{PROPERTY}} → Ready

A list becomes one item per line. Text, numbers, and checkboxes become text. A
missing or empty property becomes nothing. If an existing list item already
contains a line break, QuickAdd stops with an error. Rewrite that list from an
inline script instead.

The token works only in a property Capture. Anywhere else, QuickAdd stops with
an error.

{{PROPERTY}} is not {{FIELD:...}}. FIELD suggests values from
across your vault and may ask you to pick one. PROPERTY is the value already
on this note.

Inline scripts read the same value through
Property Capture variables.

Pick a note from a folder: `{{FILE:}}` {#file}

{{FILE:People}} opens a picker listing the Markdown files in your People
folder and inserts your pick. Where {{FIELD}} suggests the
values of a property, {{FILE}} suggests the notes themselves - made for
"metadata folders" like People/ or Research Topics/ where every note is an
option. Because the options are real files, the list always reflects what
currently exists.

The picker shows a note's file name. When the file name is an ID, the note
shows its first level-1 heading instead. An ID is an unbroken timestamp of 12
or more digits, such as 202610061432, a UUID, or a ULID. A date such as
2026-10-06 is not an ID, so a daily note shows its file name. A frontmatter
title wins for every note. The picker always inserts the actual file, so
friendly labels never change what you get. Before QuickAdd 2.32.0, a note
showed its first level-1 heading whenever it had one, whatever its file name.

You can also find a note by its aliases: the row shows the alias with the
note's name beneath it, and still inserts the note (QuickAdd 2.30.0 or later).

Output modes:

You write You get
{{FILE:People}} The file name: Tom
{{FILE:People|link}} A resolved wikilink: [[Tom]]
{{FILE:People|path}} The vault path: People/Tom.md
{{FILE:People|multi}} Several picks

|link follows your link settings (wikilink vs Markdown, shortest path). In a
capture it resolves relative to the capture target; in a template it resolves
like {{LINKCURRENT}}. Don't wrap it in [[ ]] yourself - you'd get
[[[[Tom]]]].

Example - link a research topic in a frontmatter list (quote it so the YAML
stays valid):

yaml
research-topics:
  - "{{FILE:Research Topics|link}}"

Options:

  • |optional - allow skipping the pick (becomes nothing).
  • |custom - also allow typing a value that isn't in the folder.
  • |multi - pick several files. In frontmatter property positions, including a whole-token property capture, QuickAdd writes a YAML list. In note bodies, file names, and other text positions it writes comma-separated text. Combine with |link or |path to write links or paths for every pick.
  • |type:image - pick images instead of notes. See Pick attachments.
  • |label:Pick a person - set the picker's placeholder text.
  • |name: - share one pick between placeholders. FILE placeholders are cached by their full definition: placeholders that differ (folder, filters, mode, or |label:) prompt independently, while identical ones reuse one pick. To pick two different people, give the placeholders different labels ({{FILE:People|label:Author}} and {{FILE:People|label:Reviewer}}). To reuse the same pick - say, a name in one place and a link in another - give them the same |name:. Placeholders sharing an id should target the same folder and filters; the shared pick is required if any occurrence omits |optional.
  • Filters reuse the FIELD syntax: |tag:, |exclude-folder:, |exclude-tag:, |exclude-file: (each repeatable).

Good to know:

  • The folder is the first part of the placeholder. A |folder: option is FIELD syntax and is ignored here.
  • The folder matches recursively (subfolders included). Point at a leaf folder (like {{FILE:fields/people}}) to scope tightly.
  • Repeated |tag: filters are AND filters. Exclusions remove any matching file.
  • Markdown notes only, unless you add |type:.
  • |link and |path insert characters that aren't valid in file names; in the file name field, use the default mode.
  • In a one-page input form, single and multi FILE pickers appear inline. Search matches the friendly title, file name, and full path. Selected files remain exact path-backed values internally, so commas in file names or labels are safe. A required single picker starts empty, and the form waits for a file before it submits (QuickAdd 2.30.0 or later).

FILE multi-selects support |format:yaml, |format:markdown,
|format:inline, |format:spaced, and |format:auto. The format composes with |link and
|path, so {{FILE:People|multi|link|format:yaml}} writes a native YAML list
of links without relying on the capture context.

Pick attachments: `|type:` {#file-type}

Requires QuickAdd 2.28.0 or later.

Add |type: to pick images, PDFs, and other attachments instead of notes. Put
! in front of a link to embed the file:

markdown
!{{FILE:Attachments|type:image|link}}
Type Picks
image avif, bmp, gif, jpeg, jpg, png, svg, webp
audio 3gp, flac, m4a, mp3, ogg, wav, webm
video mkv, mov, mp4, ogv, webm
pdf pdf
note md
any Every file

Any other value is a file extension, so |type:canvas picks canvases. Combine
types with commas: |type:image,pdf. The default mode inserts the file name
with its extension (photo.png). |tag: only matches notes, since attachments
have no tags.

Insert other content

Your clipboard: `{{CLIPBOARD}}` {#clipboard}

Inserts the current clipboard content: Copied: {{CLIPBOARD}}. Becomes empty
if clipboard access fails due to permissions or security restrictions.

In Capture content, if the clipboard has no text but holds a supported image,
QuickAdd saves the image using Obsidian's attachment settings and inserts an
embedded link. Text wins when both are present. You can also paste or drop an image
straight into a value prompt while typing. No placeholder needed.
The Name pasted images after the note title setting names pasted files
after the destination note when QuickAdd already knows that path.

A template file: `{{TEMPLATE:}}` {#template}

{{TEMPLATE:Templates/Meeting.md}} inserts that file's contents. Templater
syntax inside the file is supported.

In Capture choices this can be the entire capture format: keep the full
capture body in a template file and set the format to
{{TEMPLATE:Templates/Capture Format.md}}. QuickAdd inserts the file and then
runs the usual formatting passes on the result.

A macro's result: `{{MACRO:}}` {#macro}

{{MACRO:Generate summary}} runs that macro and inserts its return value.

Label the macro's prompt: `|label:` {#macro-label}

When the macro asks you to choose an export from a script,
{{MACRO:Choose project|label:Project}} shows "Project" as the placeholder -
helpful when several macro calls show similar lists.

A snippet from settings: `{{GLOBAL_VAR:}}` {#global-var}

Inserts the value of a global variable defined in
QuickAdd settings: {{GLOBAL_VAR:Meeting Header}}. Snippets can contain other
placeholders (like {{VALUE:...}} or {{VDATE:...}}), which are processed as
usual. The GLOBAL_VAR keyword is case-insensitive; the snippet name must
match exactly as you defined it.

A math formula: `{{MVALUE}}` {#mvalue}

Opens a math prompt for writing LaTeX, with a live preview. Submit with
Ctrl/Cmd+Enter: Equation: ${{MVALUE}}$.

The math prompt only suggests LaTeX commands, so # tag and [[ link search
don't work there. For multi-line plain text, use
|type:multiline instead:

markdown
- {{TIME}} {{VALUE|type:multiline}}

A random ID: `{{RANDOM:}}` {#random}

Generates a random alphanumeric string of the given length (1-100). Useful for
unique identifiers, like block references: ^{{RANDOM:6}}.


Src Content Docs Docs GlobalVariables (`docs/src/content/docs/docs/GlobalVariables.md`)


title: Global Variables slug: docs/GlobalVariables

A global variable is a named snippet you define once in QuickAdd's settings and
reuse anywhere QuickAdd fills in placeholders. Keep your list of projects, a
folder path, or a block of boilerplate in one place, then drop it into any
choice or template with {{GLOBAL_VAR:<name>}}. Edit the snippet in settings
and every choice that uses it updates at once.

Unlike a run variable, which lives only while one
choice is running, a global variable is saved with your vault and available to
every choice, every time.

For example, define a snippet named Signature:

text title="quickadd settings → global variables"
Name:  Signature
Value: Logged by QuickAdd on {{DATE:YYYY-MM-DD}}

Then use it in a capture or template:

markdown title="you write"
{{GLOBAL_VAR:Signature}}
markdown title="you get"
Logged by QuickAdd on 2026-07-08

Snippets can hold other placeholders (like the {{DATE}} above), so a global
variable can be a fixed string, a small template, or a reusable option list.

Good places to use one:

  • A single list of projects, tags, or folders reused across many choices and templates
  • Constants you'd otherwise retype: paths, emojis, boilerplate text, or a YAML block

Define a global variable {#define}

  1. Open Settings → QuickAdd → Advanced → Global variables. Before QuickAdd
    2.30.0, Global variables is a section of the main QuickAdd tab.
  2. Add a name and a value. The value is free text and supports all of
    format syntax.
  3. That's it - changes save automatically as you type.

:::tip
In the format fields of a choice's settings, such as Capture format or
File name, type {{glob to get suggestions for the variables you've
defined, then pick one to insert it. Use descriptive names,
and avoid two names that differ only by case.
:::

Use it anywhere: `{{GLOBAL_VAR:}}` {#use}

Write {{GLOBAL_VAR:<name>}} and QuickAdd replaces it with that snippet's
value. It works everywhere QuickAdd formats text:

Where Which fields
Template choice File name, folder paths, template content
Capture choice Target path, content formatting
Macros Inline formatting strings

Good to know:

  • The GLOBAL_VAR keyword is case-insensitive, so {{global_var:Signature}} also works. The name after the colon is case-sensitive: {{GLOBAL_VAR:signature}} does not find a variable named Signature.
  • A name that doesn't match any global variable becomes an empty string rather than erroring.

Put other placeholders inside a snippet {#nested-tokens}

A snippet's value can contain any QuickAdd placeholder - {{VALUE:...}},
{{VDATE:...}}, {{FIELD:...}}, {{RANDOM:n}}, and so on. When the snippet is
inserted, those placeholders run just like they would if you'd typed them
directly.

text title="quickadd settings → global variables"
Name:  MyProjects
Value: {{VALUE:Inbox,Work,Personal,Archive}}
markdown title="you write (a template path)"
Projects/{{GLOBAL_VAR:MyProjects}}/{{DATE:YYYY}}/

Running a choice that uses MyProjects shows the Inbox / Work / Personal / Archive picker, and your pick lands in the path.

Good to know:

  • A snippet can reference another global variable. QuickAdd resolves up to 5 levels of nesting, so an accidental loop stops instead of hanging.
  • Globals expand early in the formatter, so any placeholders inside a snippet are processed by the passes that follow.
  • The one-page input form looks inside your snippets too, so prompts they introduce (like {{VALUE:...}}) appear up front with the rest.

Src Content Docs Docs InlineScripts (`docs/src/content/docs/docs/InlineScripts.md`)


title: Inline scripts slug: docs/InlineScripts

An inline script is a small block of JavaScript you drop straight into a
Template or
Capture choice - or an
AI Assistant prompt template, which runs the same
format pass. When the choice runs, QuickAdd
runs your code and inserts whatever it returns, so you can compute the text
instead of typing it. Reach for one when a placeholder
isn't enough: you need to transform what you typed, look something up, or build
text with a bit of logic.

Write your first inline script {#write-your-first-inline-script}

Put a js quickadd code block anywhere in a template or capture format.
Whatever the script returns takes its place:

text
```js quickadd
const input = await this.quickAddApi.inputPrompt("✍");
return `Input given: ${input}`;
```

QuickAdd asks you for text, and inserts Input given: followed by whatever you
typed.

Good to know:

  • Label the block js quickadd, not plain js. A plain js block is inserted as an ordinary code snippet and never runs.
  • Write the code as the body of a function, not as module.exports = .... That shape is for user scripts, which run from a macro.
  • The QuickAdd API is available as this (the same API user scripts get, where it arrives as a parameter instead).
  • To insert something, return it. Strings, numbers, booleans, and arrays are
    supported.
  • The script runs while QuickAdd builds the text, before anything is written.
    In a Template choice the new note doesn't exist yet, and
    this.app.workspace.getActiveFile() is still the note you had open, so
    editing it changes that note. To fill in a property of the new note,
    return the value from the property itself.

Return values follow the same rendering contract as a typed
{{VALUE:name}}:

Return value In ordinary text As the sole value of a frontmatter property
String Inserted unchanged Inserted unchanged
Number or boolean Inserted as scalar text Written as a Number or Checkbox value
Array Joined with commas Written as a native YAML list
null or undefined Inserts nothing Leaves the property empty

Plain objects and other unsupported JavaScript values also insert nothing.
QuickAdd does not guess how to serialize them into Markdown. Assign an object to
this.variables and reference its fields explicitly, or return the exact string
you want to insert.

Set a property on the new note {#set-a-property}

A value that never changes doesn't need a script: write type: person straight
into the template. Use a script when the value has to be worked out, such as
this number that counts the notes already in People:

text
---
type: person
number: "```js quickadd return this.app.vault.getMarkdownFiles().filter(f => f.parent?.path === 'People').length + 1```"
---

Put the script where the property's value goes. Keep it on one line and wrap it
in double quotes, with single quotes inside the script, so the template's own
frontmatter stays valid. The script runs before the new note exists, so with two
notes in People the new one gets number: "3". A quoted value stays text,
even when the script returns a number.

Execution order and `{{VALUE}}` {#execution-order-and-value}

Inline scripts run before QuickAdd fills in unnamed placeholders like
{{VALUE}} and {{NAME}} in the surrounding output.

So if you write let v = "{{VALUE}}", the script sees the literal text
{{VALUE}}, not your answer. Changing v changes that literal text, not the
value QuickAdd substitutes elsewhere.

When your script needs the real input, ask for it directly through the API and
work with what you get back:

text
```js quickadd
const raw = await this.quickAddApi.inputPrompt("Text");
if (!raw) return "";

const transformed = raw.toUpperCase();
this.variables.value = transformed; // optional handoff to formatter variables

return transformed;
```

Assigning this.variables.value hands the result back to the surrounding
format, so a later {{VALUE}} picks it up.

Property Capture variables {#property-capture-variables}

In a property Capture, QuickAdd sets
these variables before your script runs. They are reserved for that run. Do not
also use {{VALUE:propertyValue}}, {{VALUE:propertyKey}}, or {{VALUE:list}}
in the same Capture.

Variable What it holds
propertyValue The current value: text, a number, a checkbox, a list of text, or undefined when the property is missing
propertyKey The property name being written
list The current list items, or [] when the value is not a list

This script adds one to a Number property:

text
```js quickadd
const n = Number(this.variables.propertyValue ?? 0);
return n + 1;
```

Return an array to rewrite the whole list. This script drops old and puts
fresh first:

text
```js quickadd
const kept = this.variables.list.filter((tag) => tag !== "old");
return ["fresh", ...kept];
```

To place the current value inside the format instead, use
{{PROPERTY}}.

This script asks for a phone number, strips out spaces and punctuation, turns
any letters into their dial-pad digits, and returns a Markdown link:

text
```js quickadd
function convertPhoneNumberToLink(linkNumber) {
	linkNumber = linkNumber.replace(/[^a-zA-Z0-9+]/g, "");
	linkNumber = linkNumber.replace(/[ABCabc]/g, "2");
	linkNumber = linkNumber.replace(/[DEFdef]/g, "3");
	linkNumber = linkNumber.replace(/[GHIghi]/g, "4");
	linkNumber = linkNumber.replace(/[JKLjkl]/g, "5");
	linkNumber = linkNumber.replace(/[MNOmno]/g, "6");
	linkNumber = linkNumber.replace(/[PQRSpqrs]/g, "7");
	linkNumber = linkNumber.replace(/[TUVtuv]/g, "8");
	linkNumber = linkNumber.replace(/[WXYZwxyz]/g, "9");
	return `tel:${linkNumber}`;
}

const raw = await this.quickAddApi.inputPrompt("Phone number");
if (!raw) return "";

return `[${raw}](${convertPhoneNumberToLink(raw)})`;
```

Type 1-800-FLOWERS and you get [1-800-FLOWERS](tel:18003569377).


Src Content Docs Docs ManualInstallation (`docs/src/content/docs/docs/ManualInstallation.md`)


title: Manual Installation slug: docs/ManualInstallation

  1. Go to Releases and open the latest release.
  2. Download the release assets. If there is a ZIP file named like quickadd-x.x.x.zip, download that. If the release lists loose files instead, download main.js, manifest.json, and styles.css.
  3. Open your Obsidian plugins folder. If you don't know where that is, go to Community Plugins inside Obsidian. There is a folder icon on the right of Installed Plugins. Click that and it opens your plugins folder.
  4. Create a folder named quickadd inside the plugins folder, or open it if QuickAdd is already installed.
  5. Extract the ZIP file into .obsidian/plugins/quickadd/, or copy the loose main.js, manifest.json, and styles.css files into .obsidian/plugins/quickadd/.
  6. Restart Obsidian. Go to Settings → Community plugins, turn off Restricted mode if it is on, and enable QuickAdd under Installed plugins.

When updating, replace only the three release files. Keep data.json and the rest of the quickadd folder to preserve your choices and settings.

Do not place main.js, manifest.json, or styles.css directly in .obsidian/plugins/. They must be inside the quickadd folder.

When finished, your vault should contain these files:

text
.obsidian/plugins/quickadd/main.js
.obsidian/plugins/quickadd/manifest.json
.obsidian/plugins/quickadd/styles.css

Src Content Docs Docs QuickAddAPI (`docs/src/content/docs/docs/QuickAddAPI.md`)


title: QuickAdd API slug: docs/QuickAddAPI

The QuickAdd API is a set of JavaScript methods you can call from
macros, user scripts, and
inline scripts. Reach for it when a workflow needs logic
a choice can't express on its own: ask the user a question, run another choice,
call an AI model, or read values already in your vault.

Module What it does
User input Ask for text, pick from a list, confirm, or collect several answers at once
Choice execution Run another choice, or apply a template to the current note
Formatting Run format syntax such as {{DATE}} or {{VALUE:name}} on a string
Utility Read and set the clipboard, read the editor's selection
Date Format today, tomorrow, yesterday, or any day offset
AI Send prompts, build tool-calling agents, get structured output
Field suggestions List the values a property already has across your vault

:::tip[Start with the overview]

If you are choosing which API surface to use, read the
API Overview first. This page is the full reference.

:::

Accessing the API

The API can be accessed in several ways:

From QuickAdd Scripts (Macros/User Scripts)

javascript
module.exports = async (params) => {
    const { quickAddApi, app, variables } = params;
    // Use quickAddApi here
};

From Other Plugins/Scripts

javascript
const quickAddApi = app.plugins.plugins.quickadd.api;
// Use the API methods

From Templater Scripts

javascript
<%*
const quickAddApi = app.plugins.plugins.quickadd.api;
const result = await quickAddApi.inputPrompt("Enter value:");
tR += result;
%>

User Input Methods

`requestInputs(inputs: Array<{ id: string; label?: string; type: "text" | "number" | "textarea" | "dropdown" | "date" | "field-suggest" | "suggester" | "slider"; placeholder?: string; defaultValue?: string; numericConfig?: { min?: number; max?: number; step?: number }; sliderConfig?: { min: number; max: number; step?: number }; options?: string[]; dateFormat?: string; description?: string; optional?: boolean; suggesterConfig?: { allowCustomInput?: boolean; caseSensitive?: boolean; multiSelect?: boolean; } }>): Promise>`

Opens a one-page modal to collect multiple inputs in one go. Values already present in variables are used and not re-asked. Returned values are also stored into variables.

Behavior:

  • Uses existing values for any ids that already exist in variables (including empty strings).
  • Prompts only for missing (undefined/null) inputs.
  • If the user closes the modal without submitting, the promise rejects with MacroAbortError("Input cancelled by user").

Field Types:

  • text: Single-line text input
  • number: Numeric input, optionally bounded by numericConfig
  • textarea: Multi-line text input
  • dropdown: Fixed dropdown menu (no search, must select from list)
  • date: Date input with natural language support (short aliases like t, tm, yd are supported and configurable in settings)
  • field-suggest: Vault field suggestions (uses {{FIELD:...}} syntax)
  • slider: Bounded numeric input with a slider and editable number field. Requires sliderConfig.min and sliderConfig.max; sliderConfig.step defaults to 1. Invalid slider configs fall back to number.
  • suggester: NEW - Searchable autocomplete with custom options (allows custom input)
    • Supports multi-select mode via suggesterConfig.multiSelect: true for comma-separated selections

requestInputs always returns text values in a Record<string, string>. For a multi-select suggester input, the selected items are stored as one comma-separated string, not as selected record objects.

Example:

javascript
const values = await quickAddApi.requestInputs([
  { id: "project", label: "Project", type: "text", defaultValue: "Inbox" },
  { id: "due", label: "Due", type: "date", dateFormat: "YYYY-MM-DD" },
  { id: "confidence", label: "Confidence", type: "slider", defaultValue: "50", sliderConfig: { min: 0, max: 100, step: 5 } },
  { id: "status", label: "Status", type: "dropdown", options: ["Todo","Doing","Done"] },
  { 
    id: "tags", 
    label: "Tags", 
    type: "suggester", 
    options: ["work", "personal", "urgent", "review"],
    placeholder: "Type to search tags...",
    suggesterConfig: { caseSensitive: false }
  }
]);

Suggester with Dynamic Options (e.g., from Dataview):

javascript
// Get dynamic options from Dataview
const dv = app.plugins.plugins.dataview?.api;
const projectNames = dv?.pages()
  .where(p => p.type === "project")
  .map(p => p.file.name)
  .array() ?? ["Inbox"];

const values = await quickAddApi.requestInputs([
  {
    id: "project",
    label: "Select Project",
    type: "suggester",
    options: projectNames,
    placeholder: "Start typing project name..."
  }
]);

Multi-Select Suggester:

javascript
// Select multiple tags, comma-separated
const values = await quickAddApi.requestInputs([
  {
    id: "tags",
    label: "Select Tags",
    type: "suggester",
    options: ["#work", "#personal", "#project", "#urgent", "#review"],
    suggesterConfig: {
      multiSelect: true,
      caseSensitive: false
    },
    placeholder: "Type or select multiple tags..."
  }
]);

// Result: values.tags = "#work, #project, #urgent"
// Split into array if needed:
const tagArray = values.tags.split(', ').filter(Boolean);

`inputPrompt(header: string, placeholder?: string, value?: string, options?: { cursorAtEnd?: boolean, imagePaste?: { sourcePath?: string } }): Promise`

Opens a prompt that asks for text input.

Parameters:

  • header: The prompt title/question
  • placeholder: (Optional) Placeholder text in the input field
  • value: (Optional) Default value
  • options.cursorAtEnd: (Optional) When true, places the caret after the default value instead of selecting it
  • options.imagePaste: (Optional) Accept pasted or dropped images. QuickAdd saves each image as a vault attachment and inserts an embed link at the caret, except a vault image included in a file drop, which is embedded in place without copying. Clipboard text wins on paste. Image files win on drop because file managers also provide the filesystem path as text. Dropped images keep a sanitized original file name. Only enable this when the value flows into note content. File name and path prompts never accept images because an embed link would corrupt the path. sourcePath is the note path the link will live in when known. Omit it to get vault-root links that resolve from anywhere.

Returns: Promise resolving to the entered string.

Cancellation: If the user cancels or presses Escape, the promise rejects with MacroAbortError("Input cancelled by user"). Letting it bubble will stop the macro automatically. Catch it only if your script wants to handle the cancellation itself.

Example:

javascript
try {
	const name = await quickAddApi.inputPrompt(
		"What's your name?",
		"Enter your full name",
		"John Doe"
	);
	console.log(`Hello, ${name}!`);
} catch (error) {
	if (error?.name === "MacroAbortError") {
		// Optional: perform cleanup before QuickAdd aborts the macro
		return;
	}
	throw error;
}

`wideInputPrompt(header: string, placeholder?: string, value?: string, options?: { cursorAtEnd?: boolean }): Promise`

Opens a wider prompt for longer text input (multi-line).

Parameters: Same as inputPrompt

Returns: Promise resolving to the entered string. Cancelling rejects with MacroAbortError (same as inputPrompt).

Keyboard: Pressing Tab inserts a tab character at the cursor (handy for nested Markdown lists) instead of moving focus; with text selected, Tab indents every line the selection touches. Shift+Tab still moves focus out of the field. See multi-line input.

Example:

javascript
const description = await quickAddApi.wideInputPrompt(
    "Project Description",
    "Enter a detailed description...",
    "This project aims to..."
);

`yesNoPrompt(header: string, text?: string): Promise`

Opens a confirmation dialog with Yes/No buttons.

Parameters:

  • header: The dialog title
  • text: (Optional) Additional explanation text

Returns: Promise resolving to true (Yes) or false (No). If the user closes the dialog without answering, the promise rejects with MacroAbortError.

Example:

javascript
const confirmed = await quickAddApi.yesNoPrompt(
    "Delete Note?",
    "This action cannot be undone."
);

if (confirmed) {
    // Proceed with deletion
}

`infoDialog(header: string, text: string[] | string): Promise`

Shows an information dialog with an OK button.

Parameters:

  • header: Dialog title
  • text: Single string or array of strings (for multiple lines)

Example:

javascript
await quickAddApi.infoDialog(
    "Operation Complete",
    [
        "Files processed: 10",
        "Errors: 0",
        "Time taken: 2.5 seconds"
    ]
);

`suggester(displayItems: string[] | Function, actualItems: any[], placeholder?: string, allowCustomInput?: boolean, options?: { renderItem?: (value: any, el: HTMLElement) => void }): Promise`

Opens a single-selection prompt with searchable options. Can optionally allow custom input not in the predefined list.

quickAddApi.suggester(...) is single-select only. It returns one selected actualItems value, or a custom string when allowCustomInput is enabled. For multiple selections, use checkboxPrompt(...) for a list of string options, or requestInputs(...) with a type: "suggester" input and suggesterConfig.multiSelect: true. Those multi-select alternatives return strings/text values rather than the selected record objects that quickAddApi.suggester(...) can return, so map the returned strings back to your records if needed.

Parameters:

  • displayItems: Array of display strings OR a map function
  • actualItems: Array of actual values to return (strings or objects)
  • placeholder: (Optional) Placeholder text shown in the suggester
  • allowCustomInput: (Optional) When true, allows users to enter custom text not in actualItems. Defaults to false
  • options.renderItem: (Optional) Custom renderer (value, el) => void to control how each suggestion row is drawn

Returns: Promise resolving to the selected value or custom input. Cancelling rejects with MacroAbortError.

Examples:

Basic usage:

javascript
const fruit = await quickAddApi.suggester(
    ["🍎 Apple", "🍌 Banana", "🍊 Orange"],
    ["apple", "banana", "orange"]
);

With placeholder text:

javascript
const fruit = await quickAddApi.suggester(
    ["🍎 Apple", "🍌 Banana", "🍊 Orange"],
    ["apple", "banana", "orange"],
    "Choose your favorite fruit"
);

With map function:

javascript
const files = app.vault.getMarkdownFiles();
const selectedFile = await quickAddApi.suggester(
    file => file.basename,  // Display just the filename
    files                   // Return the full file object
);

Complex objects:

javascript
const tasks = [
    { id: 1, title: "Task 1", priority: "high" },
    { id: 2, title: "Task 2", priority: "low" }
];

const selectedTask = await quickAddApi.suggester(
    task => `${task.title} (${task.priority})`,
    tasks,
    "Select a task to edit"
);

Allow custom input (like quick switcher):

javascript
// User can select from existing tags or type a new one
const existingTags = ["#project", "#personal", "#work"];
const selectedTag = await quickAddApi.suggester(
    existingTags,
    existingTags,
    "Select existing tag or type new one",
    true  // allowCustomInput = true
);
// Returns either an existing tag or the user's custom input

Custom rendering

You can take full control over how suggestions are rendered by providing options.renderItem. The function receives the actual item value and the suggestion row element. If the renderer throws, QuickAdd falls back to default rendering.

Custom-rendered list (no custom input):

text
/* Detailed source-code truncated for AI context efficiency. */

Allow custom input with tailored rendering:

javascript
// Highlights when value is new; stores result in variables.project
module.exports = async (params) => {
  const { quickAddApi } = params;
  const values = ['Inbox', 'Roadmap', 'Backlog'];

  const selected = await quickAddApi.suggester(
    values,
    values,
    'Type a new project or pick…',
    true, // allowCustomInput
    {
      renderItem: (value, el) => {
        const isNew = !values.includes(value);
        const row = el.createEl('div');

        const title = row.createEl('div', { text: value });
        title.setAttr('style', 'font-weight:600;');

        const hint = isNew ? 'New project (press Enter to create)' : 'Existing project';
        const sub = row.createEl('div', { text: hint });
        sub.setAttr('style', 'font-size:12px;color:var(--text-muted);');
      }
    }
  );

  if (!selected) return;
  params.variables.project = selected;
  return selected;
};

`checkboxPrompt(items: string[], selectedItems?: string[], header?: string): Promise`

Opens a checkbox prompt allowing multiple selections.

Parameters:

  • items: Array of options to display
  • selectedItems: (Optional) Array of pre-selected items
  • header: (Optional) Title shown above the list

Returns: Promise resolving to an array of selected item strings.

Example:

javascript
const features = await quickAddApi.checkboxPrompt(
    ["Dark Mode", "Auto-save", "Spell Check", "Line Numbers"],
    ["Auto-save", "Line Numbers"]  // Pre-selected
);

console.log("Enabled features:", features);

`datePrompt(header: string, options?: { placeholder?: string, defaultValue?: string, dateFormat?: string }): Promise`

Opens the date prompt: a text field that understands dates like tomorrow or next friday, with a calendar and a preview below it.

Parameters:

  • header: The prompt title
  • options.placeholder: (Optional) Placeholder text in the field
  • options.defaultValue: (Optional) Date the field starts with, such as 2026-01-05 or today
  • options.dateFormat: (Optional) Moment.js format for the returned date

Returns: Promise resolving to the chosen date in dateFormat, or as an ISO timestamp (2026-09-30T20:21:24.495Z) when you leave dateFormat out. Text that isn't a date comes back as typed, and QuickAdd shows a notice that it couldn't parse it, so check the value if your script needs a real date. Submitting an empty field resolves to the defaultValue date, returned the same way, when you set one, and to "" otherwise. Cancelling rejects with MacroAbortError.

Example:

javascript
const due = await quickAddApi.datePrompt("Due date", { dateFormat: "YYYY-MM-DD" });

Choice Execution

`executeChoice(choiceName: string, variables?: {[key: string]: any}, options?: { date?: string | Date }): Promise`

Executes another QuickAdd choice programmatically. This is a one-way trigger: it passes variables into the target choice, waits for that choice to finish, and resolves with undefined. It does not return data from the target choice to the caller. After the target choice finishes, QuickAdd clears the temporary variable map used by that API execution. If you call it from inside a Macro script, do not expect the caller's current params.variables values to still be available afterward unless you saved or restored them yourself.

For the Macro data-flow implications, see executeChoice is a trigger.

Cancellation: If the target choice is cancelled or aborted, for example because you pressed Escape at one of its prompts, the promise rejects with MacroAbortError, and an uncaught rejection stops your script. If the choice name doesn't match a choice, QuickAdd reports an error and the promise resolves.

Parameters:

  • choiceName: Name of the choice to execute
  • variables: (Optional) Variables to pass to the choice
  • options.date: (Optional) The day {{DATE}} should use. Pass a Date,
    last week, or "ask" if you want the picker.

Example:

javascript
// Execute a template choice with variables
await quickAddApi.executeChoice("Create Meeting Note", {
    meetingTitle: "Project Review",
    attendees: "John, Jane, Bob",
    date: "2024-01-15",
    value: "Main agenda content"  // Special: maps to {{VALUE}}
});

await quickAddApi.executeChoice("Weekly review", {}, { date: "lw" });

Batch processing example:

javascript
const contacts = [
    { name: "John Doe", email: "[email protected]", company: "ACME" },
    { name: "Jane Smith", email: "[email protected]", company: "Tech Corp" }
];

for (const contact of contacts) {
    await quickAddApi.executeChoice("Create Contact", {
        contactName: contact.name,
        contactEmail: contact.email,
        contactCompany: contact.company
    });
}

`applyTemplateToActiveFile(templatePath: string, options?: { mode?: "cursor" | "top" | "bottom" | "replace" }): Promise`

Applies a template to the active note without creating a new file. The template runs through the full QuickAdd format pipeline ({{TITLE}} and the unnamed {{VALUE}}/{{NAME}} resolve to the note's basename), and Templater syntax is processed. See Apply Template to Note for the full behavior, including frontmatter merging.

Parameters:

  • templatePath: Vault path to the template file
  • options.mode: (Optional) How to apply the template. Defaults to "replace" for empty notes and "bottom" otherwise.
    • "cursor": Insert at the cursor position (requires the note to be open in the active editor)
    • "top": Insert below the note's frontmatter
    • "bottom": Append to the end of the note
    • "replace": Replace the entire note content

Returns: The target file, or null if nothing was applied (e.g., no active markdown note).

Example:

javascript
// Apply a meeting template to the currently open note
await quickAddApi.applyTemplateToActiveFile("templates/meeting.md", {
    mode: "top"
});

Formatting

`format(input: string, variables?: {[key: string]: any}, shouldClearVariables?: boolean): Promise`

Runs QuickAdd format syntax on a string and resolves to the result. Tokens that need an answer, such as {{VALUE:title}} without a value, prompt for it.

Parameters:

  • input: Text with format syntax
  • variables: (Optional) Values for named tokens, such as { title: "My Document" } for {{VALUE:title}}
  • shouldClearVariables: (Optional, default true) When true, params.variables is left as format found it: the values you pass and the answers to its prompts are gone once it returns or rejects. Pass false to keep them for later steps.

Returns: Promise resolving to the formatted text. Cancelling one of its prompts rejects with MacroAbortError.

Example:

javascript
const line = await quickAddApi.format(
    "Today is {{DATE}} and the title is {{VALUE:title}}",
    { title: "My Document" }
);

Utility Module

Access via quickAddApi.utility:

`getClipboard(): Promise`

Gets the current clipboard contents.

Example:

javascript
const clipboardText = await quickAddApi.utility.getClipboard();
console.log("Clipboard contains:", clipboardText);

`setClipboard(text: string): Promise`

Sets the clipboard contents.

Example:

javascript
await quickAddApi.utility.setClipboard("Hello, World!");

`getSelection(): string`

Gets the currently selected text in the active editor. Returns an empty string if
there is no active editor or no selection.

Example:

javascript
const selection = quickAddApi.utility.getSelection();
if (selection) {
    console.log("Selected:", selection);
}

Combined example:

javascript
// Transform clipboard contents
const original = await quickAddApi.utility.getClipboard();
const transformed = original.toUpperCase();
await quickAddApi.utility.setClipboard(transformed);

Date Module

Access via quickAddApi.date:

`now(format?: string, offset?: number): string`

Gets formatted current date/time.

Parameters:

  • format: (Optional) Moment.js format string, defaults to "YYYY-MM-DD"
  • offset: (Optional) Day offset (negative for past, positive for future)

Examples:

javascript
// Current date
const today = quickAddApi.date.now();  // "2024-01-15"

// Custom format
const timestamp = quickAddApi.date.now("YYYY-MM-DD HH:mm:ss");

// With offset
const nextWeek = quickAddApi.date.now("YYYY-MM-DD", 7);
const lastMonth = quickAddApi.date.now("YYYY-MM-DD", -30);

`tomorrow(format?: string): string`

Shorthand for now(format, 1).

`yesterday(format?: string): string`

Shorthand for now(format, -1).

Example:

javascript
const yesterdayLog = `Daily Notes/${quickAddApi.date.yesterday()}.md`;
const tomorrowTask = `Tasks for ${quickAddApi.date.tomorrow("dddd, MMMM D")}`;

AI Module

Access via quickAddApi.ai:

`prompt(prompt: string, model: string | {name: string, provider?: string}, settings?: object): Promise`

Sends a prompt to an AI model and returns the response.

Parameters:

  • prompt: The prompt text
  • model: Model identifier. The model must be configured under one of the providers in Settings → QuickAdd → AI Assistant. Accepts:
    • A model name string, e.g. "gpt-4o". Resolves to the first provider that serves it.
    • A provider-qualified string, e.g. "openai/gpt-4o", where the prefix is a provider's stable ID (shown in the provider's edit form) or display name. Use this when two providers serve the same model name. If a provider literally serves a model whose id IS the whole string (OpenRouter's openai/gpt-4o, for example), that literal model wins - use the object form to override.
    • An object, e.g. {name: "gpt-4o", provider: "openai"}. With provider set, the lookup is scoped to exactly that provider and cannot be shadowed by literal slash-named models.
  • settings: (Optional) Configuration object:
    • variableName: Output variable name (default: "output")
    • shouldAssignVariables: Auto-assign to variables (default: false)
    • modelOptions: Model parameters (temperature, max_tokens, etc.)
    • showAssistantMessages: Show AI responses in UI (default: true)
    • systemPrompt: Override system prompt

Returns: Object with:

  • [variableName]: The AI response
  • [variableName]-quoted: The response in markdown quote format

Example using string:

javascript
const result = await quickAddApi.ai.prompt(
    "Summarize this text: " + noteContent,
    "gpt-4",
    {
        variableName: "summary",
        modelOptions: {
            temperature: 0.3,
            max_tokens: 150
        }
    }
);

console.log(result.summary);
// Use in template: {{VALUE:summary}}

Example using Model object:

javascript
const result = await quickAddApi.ai.prompt(
    "What is the capital of France?",
    {name: "gpt-4"},
    {
        variableName: "capital",
        shouldAssignVariables: true,
        modelOptions: {
            temperature: 0.6,
            max_tokens: 60
        }
    }
);

Note: For newer models like gpt-4o or custom provider models, add them to the provider in Settings → QuickAdd → AI Assistant. Some providers support auto-sync to automatically update available models.

`chunkedPrompt(text: string, promptTemplate: string, model: string | {name: string, provider?: string}, settings?: object): Promise`

Splits text into chunks, runs promptTemplate once per chunk, and joins the
results. Use this for inputs that are too large for a single request.

Parameters:

  • text: The full input text to process.
  • promptTemplate: The prompt run for each chunk. Reference the current chunk with {{VALUE:chunk}}.
  • model: Model identifier (bare or provider-qualified string, or {name, provider?} object), as for prompt.
  • settings: (Optional) Configuration object:
    • variableName: Output variable name (default: "output")
    • shouldAssignVariables: Auto-assign to variables (default: false)
    • modelOptions: Model parameters (temperature, max_tokens, etc.)
    • showAssistantMessages: Show AI responses in UI (default: true)
    • systemPrompt: Override system prompt
    • chunkSeparator: RegExp used to split text (default: /\n/)
    • chunkJoiner: String inserted between chunk results (default: "\n")
    • shouldMerge: Merge small adjacent chunks up to the budget (default: true)
    • maxChunkTokens: Maximum estimated tokens for each chunk's text - see Max chunk tokens below.

Behavior:

  • Chunk sizes are estimated locally (QuickAdd no longer bundles model-specific tokenizers); the configured provider remains the source of truth for exact limits.
  • A chunk larger than the budget is split near a natural boundary (paragraph, sentence, or space) so it still fits - including when shouldMerge is false.
  • If the provider still rejects a prompt because the input exceeds its context window, that chunk is split and retried automatically. Output/completion-budget and quota errors are surfaced as-is (splitting cannot fix them).
  • The prompt template is rendered through the formatter once per chunk (plus once for sizing), so side-effectful tokens such as {{MACRO:…}} run on every render - avoid them inside a chunkedPrompt template.

Returns: Object with [variableName] (joined results) and [variableName]-quoted.

javascript
const result = await quickAddApi.ai.chunkedPrompt(
    longText,
    "Summarize this section:\n{{VALUE:chunk}}",
    "gpt-4o",
    { variableName: "summary", chunkJoiner: "\n\n" }
);

Max chunk tokens {#max-chunk-tokens}

maxChunkTokens is the estimated token budget for the text inserted into
{{VALUE:chunk}} for each chunk. It is an option of this API call, not a
QuickAdd setting - there is no Max chunk tokens field anywhere in the
plugin's settings or in the Macro AI Assistant command.

The system prompt and prompt template are counted separately. Token counts are
estimated locally, and values above the selected model's estimated input budget
are capped automatically.

Give the model tools to call: `ai.agent(config)` {#tool--function-calling--aiagentconfig}

Build an agent: give the model a prompt and a set of tools (JS functions), and it
will call them in a bounded multi-step loop until it has an answer. Works across your
configured OpenAI-compatible, Anthropic, and Gemini providers.

js
const agent = quickAddApi.ai.agent({
  model: "gpt-5",
  system: "You manage an Obsidian vault. Use the tools to ground your answers.",
  tools: {
    // built-in tools, opt-in (see ai.tools.* below)
    ...quickAddApi.ai.tools.vault({ only: ["read_note", "search_notes"] }),
    // your own tool
    save_link: quickAddApi.ai.tool({
      description: "Append a URL to the reading-list note.",
      inputSchema: {
        type: "object",
        properties: { url: { type: "string" } },
        required: ["url"],
      },
      needsApproval: true, // ask before running (the model chose the args)
      execute: async ({ url }) => {
        const file = app.vault.getAbstractFileByPath("Reading list.md");
        await app.vault.append(file, `\n- ${url}`);
        return { saved: true };
      },
    }),
  },
  maxSteps: 12, // optional; default 20, hard-capped at 100
});

const { text, steps, toolCalls } = await agent.generate({
  prompt: "Summarise my notes about {{VALUE:topic}} and save any links you find.",
  assignToVariable: "summary", // optional: writes {{VALUE:summary}} for a later step
});

ai.agent(config) returns an Agent. Config:

  • model - a configured model: bare name ("gpt-4o"), provider-qualified string ("openai/gpt-4o"), or { name, provider? } object, as for ai.prompt.
  • system - system prompt (defaults to your AI Assistant default system prompt).
  • tools - an object map of tool name → tool (from ai.tool() and/or ai.tools.*).
  • toolChoice - "auto" (default) | "none" | "required" | { type: "tool", toolName }.
    Some models can't be forced to call a tool: Claude Opus 5.5 and Claude Fable 5.1 reject
    "required" and { type: "tool", toolName }, and QuickAdd's error says so. Use
    "auto" and say in the prompt when the tool applies, or pass a schema for a fixed JSON shape.
  • stopWhen - one or more stop conditions from ai.stepCountIs(n) / ai.hasToolCall(name).
  • maxSteps - step budget (default 20, hard cap 100). Sugar for stopWhen: ai.stepCountIs(n).
  • maxOutputTokens, modelOptions - passed to the provider.

agent.generate(options) runs the loop and resolves to a result:

  • text - the final assistant text.
  • object - present only when you pass a schema (structured output, below).
  • steps - the full transcript: { text, toolCalls, toolResults, finishReason }[].
  • toolCalls / toolResults - from the last step (input / output fields, AI-SDK style).
  • usage - { inputTokens, outputTokens, totalTokens }.
  • finishReason - "stop" | "max-steps" | "length" | "aborted" | "context-overflow".

Options: prompt (formatted, like ai.prompt), schema, system/toolChoice/maxOutputTokens
(per-call overrides), and assignToVariable (write text into {{VALUE:name}}).

The agent is a stateless config holder - each generate() is independent (no retained
conversation). Reuse means reusing the config; run one generate() at a time per agent.

`ai.tool(def)`

Declares a tool. def: { description, inputSchema (JSON Schema), execute, needsApproval?, readOnly?, strict? }.

  • inputSchema is a JSON-Schema subset (type/properties/required/enum/items).
    Unsupported keywords (pattern, additionalProperties, $ref, format, …) are rejected
    at registration so a provider can't silently drop a constraint.
  • execute(input, ctx) runs your code with the model-chosen input (validated against the
    schema first). Return a string (used verbatim) or any JSON-serialisable value.
  • needsApproval (boolean or (args) => boolean) asks before running. readOnly: true marks a
    tool that only reads, so it auto-runs under the default confirmation setting.

:::note[Confirmation needs an interactive Obsidian session]
A tool that asks for approval opens a modal and waits for it. For unattended automation (e.g.
driving QuickAdd from the CLI), give the agent only readOnly tools, or set Confirm AI tool
calls
to Never and gate each tool with its own needsApproval - otherwise the run blocks on a
dialog no one can answer.
:::

⚠️ Security. Tool handlers run with the same full privilege as your script (Node require,
app, the vault). The model decides which tool to call and with what arguments, possibly
influenced by note content it reads (indirect prompt injection). QuickAdd never runs model-chosen
arguments through the formatter - and neither should you: never pass a tool's input to
quickAddApi.format(), eval, a shell, or fetch without validating it. Never put secrets in a
tool's description or arguments (they are sent to the provider). Confirmation is governed by each
tool's needsApproval plus the global Confirm AI tool calls setting (default destructive only).

Ready-made tools: `ai.tools.{vault, workspace, system}(options)` {#built-in-tools--aitoolsvault-workspace-systemoptions}

Opt-in groups of ready-made tools. Each returns a tool map you spread into an agent's tools.
Options: { only, exclude, prefix, allowedRoots } (allowedRoots confines a group to the listed
folders).

Group Read-only (auto-run) Write (asks for approval)
vault read_note, list_notes, search_notes, get_property_values create_note, append_to_note, insert_under_heading
workspace get_active_note, get_selection -
system get_date -

Write tools sanitise every model-chosen path (rejecting traversal and config dirs like .obsidian/
.git, and symlinks that escape the vault), fail rather than overwrite an existing note, and are
frontmatter-aware. There are no ambient tools - nothing runs unless you spread it into tools.

allowedRoots confines both groups it applies to. For vault it bounds the paths the model may
read or write. For workspace it bounds which file's content the agent can pull in: get_active_note
returns active:null and get_selection returns an empty string whenever the currently-open file
lives outside the roots, so a note you fenced off can't be surfaced into the transcript by a
model steered through injected content. (system ignores it - get_date touches no files.) An
absent or all-blank allowedRoots is vault-wide, the default. Note that confinement scopes what
these tools expose; it is not a sandbox for an untrusted agent - your own script's require/
fetch/quickAddApi.utility.* remain ambient - so only hand a group to an agent you trust with the
folders you grant it.

Get a validated object back: `agent.generate({ prompt, schema })` {#structured-output--agentgenerate-prompt-schema-}

Pass a JSON schema to get a validated object back:

js
const { object } = await quickAddApi.ai.agent({ model: "gpt-5" }).generate({
  prompt: "Extract the title and tags from the selection.",
  schema: {
    type: "object",
    properties: { title: { type: "string" }, tags: { type: "array", items: { type: "string" } } },
    required: ["title", "tags"],
  },
});
// object => { title: "...", tags: ["...", "..."] }

object is the parsed, schema-validated result (or undefined if the model could not produce a
match after one repair attempt). Structured output works on current models - OpenAI GPT-5.x (and
GPT-4o-class), Anthropic Claude 4.x, and Gemini 3.x; it can be combined with tools. Older models
that do not support schema-constrained output (e.g. legacy OpenAI chat models) reject the request
outright with a provider error - use a current model rather than expecting a best-effort fallback.

:::note[OpenAI reasoning models (GPT-5.x, GPT-6, o-series)]
These accept only the default temperature (omit it from modelOptions). The agent sends no
temperature unless you set one, so quickAddApi.ai.agent({ model: "gpt-6-luna" }) works as-is.

Agent turns to OpenAI's own API (https://api.openai.com/v1) use the Responses API, which lets
reasoning models such as GPT-5.6 and GPT-6 call tools with reasoning on. Other OpenAI-compatible
providers use Chat Completions. If a gateway there (Azure OpenAI, OpenRouter, LiteLLM, ...) rejects
a tool turn because the model reasons by default, QuickAdd retries it once with
reasoning_effort: "none", unless you set reasoning_effort yourself. modelOptions keep their Chat Completions names either way:
QuickAdd sends reasoning_effort as reasoning.effort and max_tokens as max_output_tokens
on the Responses API, and sends maxOutputTokens as max_completion_tokens to reasoning models
on Chat Completions.
:::

`getModels(): string[]`

Returns available AI models.

Example:

javascript
const models = quickAddApi.ai.getModels();
const selectedModel = await quickAddApi.suggester(models, models);

`getMaxTokens(model: string): number`

Gets the maximum token limit for a model.

`estimateTokens(text: string): number`

Estimates the token count for text using QuickAdd's provider-agnostic estimator.
This is useful for rough prompt sizing, but the configured AI provider remains
the source of truth for exact context limits and usage.

Example:

javascript
const text = await quickAddApi.utility.getClipboard();
const tokenCount = quickAddApi.ai.estimateTokens(text);

if (tokenCount > 4000) {
    await quickAddApi.infoDialog(
        "Text Might Be Too Long",
        `The text is estimated at ${tokenCount} tokens.`
    );
}

`countTokens(text: string, model: string | {name: string}): number`

Compatibility alias for estimateTokens. The model argument (a model name or
object) is accepted for existing scripts but ignored, since QuickAdd no longer
bundles model-specific tokenizers. Prefer estimateTokens(text).

`getRequestLogs(limit?: number): Array`

Returns recent in-memory AI request logs (newest first).

What each entry includes:

  • id: unique request id (also shown in console lifecycle logs)
  • createdAt: timestamp
  • provider, endpoint, model
  • systemPrompt, prompt (the final sent text)
  • modelOptions
  • status: pending | success | error
  • durationMs
  • usage (when available)
  • errorMessage (when failed)

Notes:

  • Logs are stored in memory only (not persisted to vault files).
  • QuickAdd keeps up to 25 completed entries.
  • If more than 25 requests are still in-flight (pending), those pending entries
    are kept until they finish, then older completed entries are trimmed.

Example:

javascript
const logs = quickAddApi.ai.getRequestLogs(10);
const latest = logs[0];
console.log(latest?.id, latest?.status, latest?.model);

`getLastRequestLog(): object | null`

Returns the latest AI request log entry, or null if none exist.

`getRequestLogById(id: string): object | null`

Returns a specific AI request log entry by id.

Example:

javascript
const last = quickAddApi.ai.getLastRequestLog();
if (!last) return;

const sameRequest = quickAddApi.ai.getRequestLogById(last.id);
console.log(sameRequest?.prompt);

`clearRequestLogs(): void`

Clears all in-memory AI request logs.

Complete Example: Research Assistant

Here's a comprehensive example combining multiple API features:

text
/* Detailed source-code truncated for AI context efficiency. */

Error Handling Best Practices

A prompt has exactly two non-happy outcomes, and they are different things:

  • The user dismissed it (Escape, Cancel, closing the dialog). The promise rejects with MacroAbortError("Input cancelled by user"). Let it bubble and QuickAdd stops the run quietly - which is almost always what you want.
  • Something broke (a bug in your script, a vault error). The promise rejects with that error. Let it bubble and QuickAdd reports it once, so you get a notice and a console entry to debug from.

A prompt never resolves undefined to mean either of those. An empty string means the user submitted an empty answer, which is a real answer.

javascript
module.exports = async (params) => {
    const { quickAddApi } = params;

    // The simplest correct script: no try/catch at all. A dismissal stops the
    // macro; a real failure is reported. Only catch if you need to do something
    // in between.
    const input = await quickAddApi.inputPrompt("Enter value:");
    if (input === "") return; // submitted empty on purpose

    // Process input...
};

If your script does need to react to a cancellation, tell the two apart by the error name:

javascript
module.exports = async (params) => {
    const { quickAddApi } = params;

    try {
        const input = await quickAddApi.inputPrompt("Enter value:");
        // Process input...
    } catch (error) {
        if (error?.name === "MacroAbortError") {
            // The user backed out. Clean up and stop.
            return;
        }
        throw error; // a real failure - let QuickAdd report it
    }
};

Performance Tips

  1. Batch Operations: Use loops wisely to avoid overwhelming the system
  2. Debounce User Input: Add delays between rapid operations
  3. Check File Existence: Verify files exist before operations
  4. Validate Input: Always validate user input before processing

Field Suggestions Module

Access via quickAddApi.fieldSuggestions:

`getFieldValues(fieldName: string, options?: object): Promise`

Retrieves all unique values for a specific field across your vault.

Parameters:

  • fieldName: The name of the field to search for
  • options: (Optional) Filtering options:
    • folder: Only search in a specific folder (e.g., "daily/notes")
    • folders: Only search in any of these folders (OR filter)
    • tags: Only search in files with all of these tags (AND filter)
    • includeInline: Include Dataview inline fields (default: false)
    • includeInlineCodeBlocks: Include inline fields inside specific fenced code block types when includeInline is true (e.g., ["ad-note"])

Returns: Promise resolving to sorted array of unique field values

Examples:

Basic usage:

javascript
// Get all status values in vault
const statuses = await quickAddApi.fieldSuggestions.getFieldValues("status");
const selected = await quickAddApi.suggester(statuses, statuses);

With folder filter:

javascript
// Get project types only from projects folder
const projectTypes = await quickAddApi.fieldSuggestions.getFieldValues(
    "type",
    { folder: "projects" }
);

With multiple folder filters:

javascript
// Get relation types from either goals or projects
const relationTypes = await quickAddApi.fieldSuggestions.getFieldValues(
    "type",
    { folders: ["goals", "projects"] }
);

With tag filter:

javascript
// Get priorities from files tagged with both #work and #important
const priorities = await quickAddApi.fieldSuggestions.getFieldValues(
    "priority",
    { tags: ["work", "important"] }
);

Include inline fields:

javascript
// Get all client names including inline fields
const clients = await quickAddApi.fieldSuggestions.getFieldValues(
    "client",
    { 
        folder: "work/projects",
        includeInline: true 
    }
);

Include inline fields in specific code block types:

javascript
const ids = await quickAddApi.fieldSuggestions.getFieldValues(
    "Id",
    {
        folder: "work/projects",
        includeInline: true,
        includeInlineCodeBlocks: ["ad-note"]
    }
);

`clearCache(fieldName?: string): void`

Clears the field suggestions cache for better performance.

Parameters:

  • fieldName: (Optional) Specific field to clear, or clears all if omitted

Example:

javascript
// Clear cache for specific field after bulk updates
await quickAddApi.fieldSuggestions.clearCache("status");

// Clear entire cache
await quickAddApi.fieldSuggestions.clearCache();

Complete Field Management Example

javascript
module.exports = async (params) => {
    const { quickAddApi, app, variables } = params;
    
    // Smart task creation with field suggestions
    const taskType = await quickAddApi.suggester(
        await quickAddApi.fieldSuggestions.getFieldValues("type", {
            folder: "tasks"
        }),
        await quickAddApi.fieldSuggestions.getFieldValues("type", {
            folder: "tasks"
        })
    );
    
    const priority = await quickAddApi.suggester(
        ["🔴 High", "🟡 Medium", "🟢 Low"],
        ["high", "medium", "low"]
    );
    
    const assignee = await quickAddApi.suggester(
        await quickAddApi.fieldSuggestions.getFieldValues("assignee", {
            tags: ["person"],
            includeInline: true
        }),
        await quickAddApi.fieldSuggestions.getFieldValues("assignee", {
            tags: ["person"],
            includeInline: true
        })
    );
    
    // Create task with consistent field values
    variables.type = taskType;
    variables.priority = priority;
    variables.assignee = assignee;
    
    await quickAddApi.executeChoice("Create Task", variables);
};

See Also


Src Content Docs Docs Settings (`docs/src/content/docs/docs/Settings.md`)


title: Settings slug: docs/Settings

This page is a reference for the QuickAdd settings tab, one group at a time. Each entry says what the setting does and what changes when you turn it on. Open the tab from Obsidian's Settings → Community plugins → QuickAdd (or Settings → QuickAdd); changes save as you make them.

Choices & packages {#choices--packages}

  • Choices - build and organize your QuickAdd choices. This is the main list you add to, reorder, and configure. Click the gear on a choice's row (on a phone, ⋮ → Configure) to open its settings as a page of this window; going back or closing Settings saves them (QuickAdd 2.30.0 or later; earlier versions open a dialog). See Template Choices, Capture Choices, Macro Choices, and Multi Choices.
  • Packages - share a set of choices with someone else, or bring theirs in. Use Export package… to bundle your choices into a file, and Import package… to add someone else's. See Share QuickAdd Packages.

Input {#input}

  • Use multi-line input prompt - get a large text box for text prompts instead of a single line, so you can write several lines at once. Multi-line prompts submit with Ctrl/Cmd+Enter, and plain Enter adds a newline. See Controlling Prompts.
  • One-page input for choices - answer all of a choice's questions in one form up front, instead of one prompt after another. Works with Template, Capture, and Macro choices. Each choice can override this individually. See One-page Inputs and Controlling Prompts.

Template folders {#template-folders}

Tell QuickAdd where your templates live, so it can suggest them when you configure a choice. Click Add folder and pick a folder from the list. Once you have one, add more with the + button next to the heading (on phones, the Add folder row below the list); remove one with the × on its row. Add as many folders as you like. Leaving the list empty suggests every template file in the vault, but it also removes the New note from template launcher row, and the command of the same name will only point you back here until a folder is configured.

Notifications {#notifications}

  • Announce updates - see what changed when a new version installs, including new features, demo videos, and bug fixes. Choose Every release, Feature releases (default; new features and breaking changes, not bug-fix-only releases), or Never.
  • Show capture notifications - get a confirmation that a capture landed. When on, QuickAdd shows a notice after content is captured successfully.

AI & online {#ai--online}

  • Disable AI & online features - stop the plugin from reaching external providers like OpenAI. This blocks plugin features such as the AI Assistant from making those requests. User scripts can still run arbitrary code, including contacting external providers, so this setting does not affect them. Turn it off to use the AI Assistant.

Appearance {#appearance}

  • Show icon in sidebar - add the QuickAdd icon to the sidebar ribbon for one-click access. Requires a reload to take effect.

Advanced {#advanced}

Settings most vaults never change are on the Advanced page, the last entry in the QuickAdd tab. Settings search finds them there too. The page is new in QuickAdd 2.30.0; earlier versions show these settings on the main tab, under the same section names.

Choice picker {#choice-picker}

The choice picker is the list you see when you run QuickAdd: Run.

  • Search nested choices - find a choice even when it lives inside a folder. When on, searching also matches choices nested in folders and shows their path. A nested match can rank above a same-level one. Turn it off to search only the level you have open.
  • "New note from template" in the launcher - decide where the "create a note from a template" row sits in the picker, so you can make a note from any template without building a dedicated Template choice. Bottom (default) keeps your most-used choice in the first slot, Top makes the template row first, and Hidden removes it. The row only appears once you have a template folder configured. The New note from template command is always in the command palette, but it needs a configured folder too - without one it shows a notice and opens these settings.

Input {#advanced-input}

  • Use editor selection as default Capture value - let a Capture reuse text you already have highlighted. When on, Capture uses the current editor selection as {{VALUE}} and may skip the prompt entirely. When off, Capture always asks for {{VALUE}}. Individual Capture choices can override this.
  • Name pasted images after the note title - name clipboard images after the destination note instead of Clipboard image YYYY-MM-DD HH.MM.SS. Applies when you paste an image into a prompt whose answer lands in note content, and when Capture's {{CLIPBOARD}} falls back to an image. If the destination is not known yet, QuickAdd keeps the timestamp name. Duplicate names follow Obsidian's attachment folder setting. Off by default.
  • Persist input prompt drafts - don't lose what you typed if you close a prompt by accident. When on, a closed prompt keeps its draft and restores it when you reopen. Drafts last only for the current session.
  • Date aliases - set your own shortcodes for natural-language dates, so typing tm in a date prompt means tomorrow. Write one per line as alias = phrase, for example tm = tomorrow. Reset to defaults restores the built-in aliases.

Notifications {#advanced-notifications}

  • Show input cancellation notifications - get a notice when you dismiss a prompt without submitting. Turn it off to avoid the extra notice every time you cancel a prompt.

Properties {#properties}

  • Convert string front matter variables to typed properties (Beta) - turn text values into real Obsidian property types so front matter reads correctly. List and object values from scripts are always written as proper properties (a list value becomes a List property), so templates produce valid front matter without this toggle. Turning it on also converts string values: a comma or bullet-list string becomes a List, "42" becomes a Number, "true" becomes a Checkbox, and so on. Off by default, because the string conversion is a beta heuristic that can have edge cases. See Template Property Types (Beta).

Global variables {#global-variables}

  • Global variables - define reusable snippets once and drop them in anywhere QuickAdd formatting is supported. These are vault-scoped. See Global Variables.

Obsidian URI {#obsidian-uri}

  • Allow URI x-callback-url - let an obsidian://quickadd link report its result back to whoever launched it. Off by default, because the callback URL is set by whoever creates the obsidian:// link and a successful callback can carry the affected note's vault path. When on, an obsidian://quickadd URI may open a callback URL (x-success / x-error / x-cancel) after a Template or Capture choice finishes, sending the outcome and, on success, the note's vault path and URL to that callback. Only shortcuts: and obsidian: callback URLs are permitted. See Obsidian URI.

Choice icons {#choice-icons}

  • Automatic choice icons - QuickAdd gives each choice type a default Lucide icon: file-text for Template, pencil for Capture, terminal for Macro, and folder for Multi. These show in the QuickAdd launcher, inside Multi choice pickers, and on registered commands in the command palette and mobile editing toolbar.
  • Override a choice's icon - give a single choice its own icon. Open the choice's configuration and set Icon to any Lucide icon id (for example star); leave it empty to fall back to the choice type default. Icons take the active Obsidian theme's color; QuickAdd does not set per-choice icon colors.

Choice icons introduced in QuickAdd 2.14.0.


Src Content Docs Docs SuggesterSystem (`docs/src/content/docs/docs/SuggesterSystem.md`)


title: Suggester System slug: docs/SuggesterSystem

When a QuickAdd prompt asks you for text, you don't have to type a file name or
tag from memory. Start typing a trigger like [[ and QuickAdd shows a
searchable list of matches, exactly like Obsidian's own link and tag
autocomplete. Pick one, and QuickAdd drops it in where you were typing.

What you can search for {#special-search-modes}

Type one of these triggers anywhere in a prompt:

Type this To search
# Every tag in your vault
[[ Every file in your vault
[[# Every heading in your vault
[[#^ A specific block

:::note[Where the triggers work]
The # and [[ triggers work in QuickAdd's single-line and multi-line input
prompts, and in text and textarea fields on the
one-page input form.
Field and pick-list widgets keep their own inline suggestions. The
math prompt ({{MVALUE}}) suggests LaTeX
commands instead.
:::

Type # to search every tag in your vault, then pick one.

Type [[ to search every file in your vault. A file shows its name, or the
alias you matched, with its path beside it. Links to notes that don't exist yet
are listed too, marked Unresolved link.

Search a nearby folder: `./` and `../` {#relative-path-navigation}

Narrow a file search to a folder near the current note:

  • ./ searches the folder the current note is in.
  • ../ searches the parent folder.

Type [[# to search every heading in your vault and link straight to it.

Type [[#^ to find and link an individual block.

Keyboard shortcuts {#keyboard-shortcuts}

You can drive the suggester entirely from the keyboard:

Key Action
↑ / ↓ Move up and down through the suggestions
Enter Pick the highlighted suggestion
Escape Close the suggester without picking

The list wraps around: pressing ↓ on the last item jumps back to the first.


Src Content Docs Docs TemplatePropertyTypes (`docs/src/content/docs/docs/TemplatePropertyTypes.md`)


title: Template Property Types (Beta) slug: docs/TemplatePropertyTypes

When a QuickAdd template or capture writes front matter, this feature makes sure
each value lands as the right Obsidian property type - a real List, Number, or
Checkbox - instead of a string that only looks right. You get clean, typed
front matter without hand-formatting YAML, so scripts can hand QuickAdd normal
JavaScript values and templates stay readable.

What gets converted {#overview}

There are two layers, and only the second one is a beta toggle.

Always on, no setting required. When a script provides a list (array)
or object value for a front matter field, QuickAdd writes it as a real
Obsidian List or object property through Obsidian's own YAML serializer. This is
what makes templates like the
Movie & Series script produce
valid front matter out of the box: a returned array of links becomes a List of
links instead of broken YAML.

Behind the beta toggle. The setting below additionally converts string
values into typed properties: a comma or bullet-list string becomes a List,
"42" becomes a Number, "true" becomes a Checkbox, and so on. This is the
beta part, because it changes a value's type based on its contents. It is
disabled by default.

Here is what the string conversion does to a comma-separated author list:

Before:

yaml
authors: "John Doe, Jane Smith, Bob Wilson"  # Manual string formatting

After:

yaml
authors:
  - John Doe
  - Jane Smith  
  - Bob Wilson

:::tip[Prefer explicit format-syntax options]
When you want a specific input or type, format syntax
gives you one directly, with no beta toggle required:

Plain {{VALUE}} already round-trips numbers, booleans, and dates to the correct
type without any setting. The beta string conversion below mainly adds
comma/bullet-string to List for properties typed as a list.

Turn on string conversion (beta) {#enabling-the-string-conversion-beta}

List and object handling needs no setting. To also convert string values
into typed properties, enable the beta toggle:

  1. Open Settings → QuickAdd → Advanced (QuickAdd 2.30.0 or later; earlier versions show it on the main QuickAdd tab).
  2. Toggle "Convert string front matter variables to typed properties (Beta)".

String conversion is disabled by default for safety.

Walkthrough {#basic-usage}

1. Set structured data in a script {#1-set-structured-data-in-scripts}

Instead of manually formatting data as strings, use native JavaScript types:

javascript
// ✅ NEW: Use native data structures
QuickAdd.variables.authors = ["John Doe", "Jane Smith", "Bob Wilson"];
QuickAdd.variables.tags = ["research", "ai", "papers"];
QuickAdd.variables.metadata = {
    rating: 5,
    conference: "ICML",
    keywords: ["ML", "AI"]
};
QuickAdd.variables.published = true;
QuickAdd.variables.year = 2023;
QuickAdd.variables.notes = null;

// ❌ OLD: Manual string formatting (no longer needed)
// QuickAdd.variables.authors = "John Doe, Jane Smith, Bob Wilson";

2. Use them in a template {#2-use-in-templates}

Your template syntax stays exactly the same:

yaml
---
title: "{{VALUE:title}}"
authors: "{{VALUE:authors}}"
tags: "{{VALUE:tags}}"
metadata: "{{VALUE:metadata}}"
published: "{{VALUE:published}}"
year: "{{VALUE:year}}"
notes: "{{VALUE:notes}}"
---

# {{VALUE:title}}

Content here...

3. Get perfect property types {#3-get-perfect-property-types}

With string conversion turned on, the
result is properly formatted as Obsidian property types:

yaml
---
title: "My Research Paper"
authors:
  - John Doe
  - Jane Smith
  - Bob Wilson
tags:
  - research
  - ai
  - papers
metadata:
  rating: 5
  conference: ICML
  keywords:
    - ML
    - AI
published: true
year: 2023
notes:
---

# My Research Paper

Content here...

In Obsidian's Properties view, authors is a List, tags are tags,
published is a Checkbox, and year is a Number. notes is empty. Obsidian
has no property type for nested objects, so metadata shows as raw JSON:

With string conversion off, the lists and the metadata object come out the same
way. Because this template wraps each placeholder in quotes, published,
year, and notes stay quoted strings ("true", "2023", "").

Which JavaScript type becomes which property {#supported-data-types}

JavaScript Type Property Output Example
Array List property ["a", "b"] → - a``- b
Empty Array Empty list [] → []
Object Object property {key: "value"} → key: value
Empty Object Empty mapping {} → {}
Number Number literal 42 → 42
Boolean Boolean literal true → true
Null Null literal null → null
String String (unchanged) "text" → text

QuickAdd respects the type you set in Obsidian {#respecting-obsidian-property-types}

QuickAdd looks at the type you've assigned in Obsidian's Properties UI for
each field and formats the value accordingly:

  • tags / multi-select (multitext) / list - strings such as foo, bar or bullet items become proper arrays.
  • Scalar types (text, number, date, datetime, checkbox) stay as single values, even if the text contains commas or line breaks.
  • Unknown type - falls back to the v2.0 behaviour (it will still split obvious arrays like YAML lists or JSON arrays).

This means you can safely type natural prose like Hello, world into a
description prompt without QuickAdd turning it into a YAML list, while
sources marked as a multi-value property will still receive a properly
formatted array.


Nesting goes as deep as you need: objects inside objects, arrays inside
objects, arrays of objects (a project's
tasks: [{ name: "Research", complete: true }] becomes a list of mappings),
and null leaves all serialize the same way. The worked example below shows a
nested structure end to end.

A worked example: academic papers {#academic-papers}

One structure from a script, through a template, into typed front matter.

Script:

javascript
// From Zotero or other source
const paper = {
    title: "Attention Is All You Need",
    authors: ["Ashish Vaswani", "Noam Shazeer", "Niki Parmar"],
    year: 2017,
    venue: "NIPS",
    keywords: ["attention", "transformer", "neural networks"],
    metrics: {
        citations: 50000,
        pages: [3000, 3010]
    }
};

Object.assign(QuickAdd.variables, paper);

Template:

yaml
---
title: "{{VALUE:title}}"
authors: "{{VALUE:authors}}"
year: "{{VALUE:year}}"
venue: "{{VALUE:venue}}"
keywords: "{{VALUE:keywords}}"
metrics: "{{VALUE:metrics}}"
---

# {{VALUE:title}}

## Summary
Paper by {{VALUE:authors}} published in {{VALUE:venue}} ({{VALUE:year}}).

Output:

yaml
---
title: Attention Is All You Need
authors:
  - Ashish Vaswani
  - Noam Shazeer
  - Niki Parmar
year: 2017
venue: NIPS
keywords:
  - attention
  - transformer
  - neural networks
metrics:
  citations: 50000
  pages:
    - 3000
    - 3010
---

# Attention Is All You Need

## Summary
Paper by Ashish Vaswani,Noam Shazeer,Niki Parmar published in NIPS (2017).

Captures and fresh templates {#captures--fresh-templates}

When a capture choice creates a new file, QuickAdd analyses the just-generated
front matter instead of relying on cached metadata. The capture payload is
inserted after the closing ---, so YAML stays at the top of the note even
on the first run.

For list-style placeholders inside the front matter, QuickAdd resolves the
parent property and respects the type you set in Obsidian:

yaml
---
sources:
  - "{{VALUE:sources}}"
description: "{{VALUE:description}}"
---
Property type (in Obsidian) Behaviour
multitext, tags, list sources becomes a YAML array (- value) using your prompt input.
text, number, checkbox, date, datetime Values remain scalars; commas or line breaks no longer force list formatting.

Example output:

yaml
sources:
  - [[Episode 1]]
  - [[Episode 2]]
description: This stays a single string, even with commas.

Feedback and support {#feedback--support}

This is a beta feature. It is designed to be safe and backward-compatible,
but test it with your own templates before relying on it for important
workflows - and when reporting an issue, include the template and the variable
data that triggered it.


Src Content Docs Docs UserScripts (`docs/src/content/docs/docs/UserScripts.md`)


title: User Scripts slug: docs/UserScripts

A user script is a piece of JavaScript you write once and run from a
Macro. It can prompt for input, read and write
notes, call Obsidian's own APIs, talk to other plugins, and pass values into the
rest of the macro - the escape hatch for anything QuickAdd's built-in choices
don't cover. This page is the full reference: how a script is shaped, how to
give it settings, what the params API offers, and a shelf of copy-paste
recipes.

You don't need to be a professional developer, but you should be comfortable
reading and lightly editing JavaScript.

:::tip[New to scripting?]

Start with the Scripting Overview for the
decision path, then use this page as the detailed reference.

:::

:::note[Obsidian API reference]
Scripts lean heavily on the Obsidian API. For
anything beyond the recipes here, keep the
App,
Vault, and
Workspace
references open.
:::

Add a script to a macro {#adding-scripts-to-macros}

A user script can live in either of two places inside your vault:

  • a standalone .js file, or
  • a note (.md) containing a ```js (or ```javascript) code block.

The note form is handy on mobile, where Obsidian can't open .js files at all.
QuickAdd doesn't run scripts from files with any other extension, such as
.cjs or .txt; rename those so they end in .js. In QuickAdd 2.30.0 or
later, a macro step that points at such a file stops the macro with a notice.

:::caution[Where scripts can live]
A user script must sit inside your Obsidian vault, but not in the
.obsidian directory or in any hidden folder (one whose name starts with a
dot). Obsidian may exclude those from the vault file index QuickAdd uses for
script discovery, so a script there is intentionally ignored and won't appear
in the script picker.

✅ Valid locations:

  • /scripts/myScript.js
  • /_quickadd/scripts/myScript.js
  • /macros/utilities/helper.js
  • /my-custom-folder/script.js
  • Any folder in your vault except .obsidian or a hidden folder

❌ Invalid locations:

  • /.obsidian/plugins/quickadd/scripts/myScript.js
  • /.obsidian/scripts/myScript.js
  • /.quickadd/scripts/myScript.js (hidden folder - use _quickadd instead)
  • /.scripts/myScript.js (hidden folder - use _scripts instead)
  • Any path within the .obsidian directory
  • Any path within a folder starting with a dot (.)
    :::

In the Macro Builder, Browse opens QuickAdd's picker of discovered scripts
(both .js files and notes that contain a code block); it is not a native file
picker. If you add a script manually, type a .js script's basename - for
scripts/my-script.js, enter my-script (or its vault path, if another .js
file shares that name) - or, for a note, type its vault path
(e.g. Scripts/my-script.md). For a specific export, append a member expression
such as my-script::start (or Scripts/my-script.md::start).

Keep a script in a note, for mobile {#scripts-in-a-note-code-block}

Write your script in a ```js code block inside any note; QuickAdd runs the
**first** ```js (or ```javascript) block in the note and ignores all
prose and other code blocks. The block is a CommonJS module exactly like a .js
file - it must assign module.exports / exports.default (a top-level return
the way inline scripts work will not export anything):

markdown
# My script

Notes about what this does - ignored by QuickAdd.

```js
module.exports = async (params) => {
    // Your code here
};
```

If the note has no ```js block, QuickAdd shows a notice and skips the script.

The note only runs when a macro calls it. A ```js block inside a template or
capture format is copied into the output as text; to run code there, use an
inline script.

:::note
Only plain ```js fences are recognized - blocks nested inside callouts or
blockquotes (> ```js) and ~~~ fences are not. To embed a literal ``` line
inside the script body, open the block with four or more backticks.
:::

The basic shape of a script {#basic-structure}

Every user script must export a module with at least an entry point function:

javascript
module.exports = async (params) => {
    // Your code here
};

Or with the object syntax:

javascript
module.exports = {
    entry: start,
    settings: {
        name: "Script Name",
        author: "Your Name",
        options: {
            // Define configurable options here
        }
    }
};

async function start(params, settings) {
    // Your code here
}

Use the plain function form for a quick script, and the object form when you
want configurable settings in the QuickAdd UI.

What your script receives {#script-parameters}

The script is called with up to two arguments: params (always) and settings
(only with the object form above).

The `params` object {#params-object}

params carries the QuickAdd API and the Obsidian context:

javascript
{
    app: App,                  // Obsidian app instance - see https://docs.obsidian.md/Reference/TypeScript+API/App
    quickAddApi: QuickAddApi,   // QuickAdd API methods (documented below)
    variables: {},              // Variables object for sharing data between scripts and templates
    obsidian: obsidian,         // Obsidian module with all classes and utilities
    abort: (message) => never   // Abort macro execution with optional message
}

The app object provides access to the entire Obsidian API, including:

The `settings` object {#settings-object}

settings holds the user-configured values for your script's options. It's only
populated when your script defines a settings block, as in the object
structure above. An export picked with :: (for example my-script::start)
gets the values too: QuickAdd uses the nearest settings block on the export
or its parents, up to module.exports.

Let users configure your script {#configurable-options}

User scripts can define configurable options that users set through the QuickAdd
UI. This makes your scripts flexible and reusable - a folder path, an API key,
or an on/off toggle becomes a form field instead of a line you have to edit.

The option types {#option-types}

Text: `type: "text"` {#text-input}

For regular string values, paths, names, etc.

javascript
options: {
    "Project": {
        type: "text",              // or "input"
        defaultValue: "",
        placeholder: "Project name",
        description: "Default project" // Optional: help text
    }
}

Secret: `type: "secret"` {#secret-input}

For API keys, access tokens, and other sensitive values. Secrets are stored in Obsidian's SecretStorage and QuickAdd stores only a reference in data.json.

javascript
options: {
    "API Key": {
        type: "secret",
        id: "api-key", // Optional stable storage id
        placeholder: "Paste API key",
        description: "Your API key"
    }
}

type: "text" / type: "input" with secret: true is still supported for older scripts and is treated as a secret setting.

Secret values are local to the Obsidian app profile. They are not included in QuickAdd package exports and are not synced through the plugin's data.json; users must enter them on each device where the script runs.

The optional id controls the SecretStorage reference used for the setting. If omitted, QuickAdd uses the option name. Set id when you want to rename the visible setting label later without creating a new saved secret.

Toggle: `type: "toggle"` {#togglecheckbox}

For boolean on/off settings.

javascript
options: {
    "Enable Feature": {
        type: "toggle",            // or "checkbox"
        defaultValue: false,
        description: "Enable this feature"
    }
}

For choosing from predefined options.

javascript
options: {
    "Output Format": {
        type: "dropdown",          // or "select"
        defaultValue: "markdown",
        options: ["markdown", "plain", "html"],
        description: "Choose output format"
    }
}

Format: `type: "format"` {#format-input}

For template strings with QuickAdd format syntax support.

javascript
options: {
    "File Name Template": {
        type: "format",
        defaultValue: "{{DATE}} - {{VALUE:title}}",
        placeholder: "Enter template",
        description: "Template for file names"
    }
}

Complete example {#complete-example}

Here's a comprehensive example showing all option types, and reading them back
inside start:

text
/* Detailed source-code truncated for AI context efficiency. */

Prompt, format, and run other choices {#using-the-quickadd-api}

User scripts have full access to the QuickAdd API through params.quickAddApi. For complete API documentation, see the QuickAdd API Reference. The sections below cover the calls you'll reach for most.

Ask the user for input {#user-input}

javascript
// Text input
const text = await quickAddApi.inputPrompt("Enter text:");

// Wide text input (multi-line)
const longText = await quickAddApi.wideInputPrompt("Enter description:");

// Yes/No confirmation
const confirmed = await quickAddApi.yesNoPrompt("Are you sure?");

// Suggester (dropdown selection)
const choice = await quickAddApi.suggester(
    ["Option 1", "Option 2", "Option 3"],
    ["value1", "value2", "value3"]
);

// Checkbox selection
const selected = await quickAddApi.checkboxPrompt(
    ["Item 1", "Item 2", "Item 3"],
    ["Item 1"]  // Pre-selected items
);

checkboxPrompt opens a searchable list where the user can tick several items.
Here a weekly review script offers five areas with Work and Home preselected,
and the user has also ticked Side projects:

Share values with later steps: variables {#variables}

Set variables that can be used in subsequent template operations:

:::tip[Sharing functions and constants]
Variables pass data between steps. To reuse the same functions or
constants
across scripts, put them in a shared module - see
Sharing Code Between Scripts.
:::

javascript
// Set variables
params.variables.myVariable = "value";
params.variables.timestamp = new Date().toISOString();
params.variables.results = arrayOfResults;

// Variables are accessible in templates as {{VALUE:myVariable}}

Run format syntax yourself {#formatting}

Format strings with QuickAdd syntax:

javascript
const formatted = await quickAddApi.format(
    "Today is {{DATE}} and the title is {{VALUE:title}}",
    { title: "My Document" }
);

By default, format leaves params.variables as it found them. The variables you pass
and the answers to its prompts are gone once it returns, and also when it
rejects because the user cancelled a prompt.

Run another choice {#execute-other-choices}

Trigger other QuickAdd choices programmatically:

javascript
await quickAddApi.executeChoice("My Other Choice", {
    customVariable: "value"
});

Reach into other plugins {#accessing-other-plugins}

A script can talk to other Obsidian plugins through app.plugins.plugins. Check
that the plugin is there before using it:

javascript
module.exports = async (params) => {
    const { app } = params;

    // Access Templater
    const templater = app.plugins.plugins["templater-obsidian"];
    if (templater) {
        // Use Templater API
    }

    // Access MetaEdit
    const metaedit = app.plugins.plugins["metaedit"];
    if (metaedit) {
        const { update } = metaedit.api;
        await update("property", "value", "path/to/file.md");
    }
};

The plugin must be installed and enabled in the vault, so check for it and fail
with a clear message when it's missing.

Handle errors and stop a macro {#error-handling-and-macro-control}

Stop a macro on purpose: `abort()` {#aborting-macro-execution}

You can intentionally stop a macro using params.abort(). This is useful for validation or conditional execution:

javascript
module.exports = async (params) => {
    const { abort, quickAddApi } = params;
    
    // Get user input
    const projectName = await quickAddApi.inputPrompt("Project name:");
    
    // Validate input
    if (!projectName || projectName.length < 3) {
        abort("Project name must be at least 3 characters");
        // Execution stops here - remaining macro commands won't run
    }
    
    // Continue with valid input
    console.log(`Creating project: ${projectName}`);
};

When to use params.abort():

  • Input validation failures
  • Missing required configuration
  • You want to provide a custom message after catching a MacroAbortError
  • Prerequisites not met (e.g., required plugin not installed)

Prompt cancellations already throw MacroAbortError and halt macros automatically, so only call abort() in those scenarios if you need to surface a custom message or you're stopping for a non-prompt reason.

What happens when you call abort():

  • Macro execution stops immediately
  • QuickAdd shows your message in a notice: "Macro execution aborted: [your message]". A message containing "cancelled by user" counts as a cancellation, so it gets a notice only when Show input cancellation notifications (under Settings → QuickAdd → Advanced, or the main QuickAdd tab before QuickAdd 2.30.0) is on
  • Remaining commands in the macro are skipped
  • It isn't reported as an error

QuickAdd API methods that can be cancelled:

  • inputPrompt()
  • wideInputPrompt()
  • yesNoPrompt()
  • suggester()
  • checkboxPrompt()
  • datePrompt()
  • requestInputs()

Each of these now rejects with MacroAbortError("Input cancelled by user") when the user presses Escape or closes the dialog. If you do nothing, the macro will automatically stop (matching user expectations). If you want to handle cancellation in your script, wrap the call in try/catch and intercept the error before it reaches the macro engine.

javascript
try {
    const name = await quickAddApi.inputPrompt("Your name:");
} catch (error) {
    if (error?.name === "MacroAbortError") {
        // Optional custom handling (e.g., cleanup) before the macro aborts
        return;
    }
    throw error; // real errors should still bubble up
}

Important: Because cancellations now throw, you should only call abort() yourself when you want to provide a custom message or stop execution for a non-prompt reason.

javascript
module.exports = async (params) => {
    const { quickAddApi, abort } = params;
    
    let name;
    try {
        name = await quickAddApi.inputPrompt("Your name:");
    } catch (error) {
        if (error?.name === "MacroAbortError") {
            abort("Name is required");
            return;
        }
        throw error;
    }
    
    console.log(`Processing: ${name}`);
};

Catch unexpected errors {#script-error-handling}

Always include proper error handling for unexpected errors:

javascript
async function start(params, settings) {
    try {
        // Your code here
        const result = await riskyOperation();
        
        if (!result) {
            new Notice("Operation failed", 5000);
            throw new Error("Failed to complete operation");
        }
        
    } catch (error) {
        console.error("Script error:", error);
        new Notice(`Error: ${error.message}`, 5000);
        throw error;  // Re-throw to stop macro execution
    }
}

Error behavior:

  • Unhandled errors in scripts automatically stop the macro
  • Original error stack traces are preserved for debugging
  • Errors are logged to the console for troubleshooting

Best practices {#best-practices}

  1. Validate Settings: Always check that required settings are configured before using them
  2. User Feedback: Use new Notice() to provide feedback to users
  3. Error Messages: Provide clear, actionable error messages
  4. Default Values: Always provide sensible default values for options
  5. Documentation: Add descriptions to all options to help users understand their purpose
  6. Variable Names: Use clear, descriptive names for variables you set
  7. Async/Await: Always use async/await for asynchronous operations
  8. Console Logging: Use console.log() for debugging, but remove or minimize in production

Share code between scripts {#sharing-code-between-scripts}

Need the same helper functions or constants in several scripts? Put them in one
shared .js module and require() it from each script. User scripts run as
CommonJS modules and receive a require function (backed by Obsidian's
Electron/Node require), so you can load any module on disk by its absolute
path.

:::caution[Desktop only]
require() uses Obsidian's Electron/Node runtime, which exists only in the
desktop app. On mobile there is no require, so this pattern does not work
there - see Cross-platform alternatives.
:::

:::caution[require() runs code immediately]
require() executes the target file with full Node/Electron access (filesystem,
network, and more) the moment it loads - before any export is called. Only
require modules you wrote or trust, exactly as you would any code you run on
your machine.
:::

Create the shared module {#create-the-shared-module}

Save a normal .js file in your vault. (It must be a real .js file - require
loads from disk and cannot read a script written in a ```js note code block.)
Export your helpers with module.exports:

javascript
// scripts/shared-utils.js
module.exports = {
    formatNumber: (n) => new Intl.NumberFormat("en-US").format(n),
    CURRENCY: "USD",
};

The module must already exist on disk when the requiring script runs.

Require it from your scripts {#require-it-from-your-scripts}

Build the absolute path at runtime from the vault's base path, then require
the module. Don't paste a literal absolute path - the vault lives at a different
location on every machine and account, so a hardcoded path breaks the moment the
vault is synced or opened elsewhere. Relative paths don't work either (the script
has no __dirname), so always resolve against getBasePath():

javascript
// scripts/my-macro-script.js
module.exports = async (params) => {
    const { app, obsidian } = params;

    // require() is desktop-only - fail clearly on mobile.
    if (!(app.vault.adapter instanceof obsidian.FileSystemAdapter)) {
        throw new Error("This script shares helpers via require(), which only works in the desktop app.");
    }

    const path = require("path");
    // getBasePath() is the supported method; older scripts may use the
    // equivalent app.vault.adapter.basePath.
    const utils = require(path.join(app.vault.adapter.getBasePath(), "scripts", "shared-utils.js"));

    new obsidian.Notice(`${utils.formatNumber(1234567)} ${utils.CURRENCY}`); // "1,234,567 USD"
};

:::note[Reload to pick up edits]
A require'd module is cached the first time it loads. After you edit
shared-utils.js the change is not picked up until you reload - run
Reload app without saving from the Command palette. (A regular user script is
re-read on every run, but a module it requires is not.)

Advanced: bust the cache from a script without reloading. Use window.require -
the require your script receives is a thin wrapper and has no .cache/.resolve:

javascript
module.exports = async (params) => {
    const { app, obsidian } = params;
    if (!(app.vault.adapter instanceof obsidian.FileSystemAdapter)) return;

    const fullPath = window.require("path").join(app.vault.adapter.getBasePath(), "scripts", "shared-utils.js");
    delete window.require.cache[window.require.resolve(fullPath)];
    const utils = window.require(fullPath); // reflects your latest edits to this file
    // Only refreshes shared-utils.js itself, not other modules it requires.
};

:::

Cross-platform alternatives {#cross-platform-alternatives}

Because require is desktop-only, on mobile (or for a portable vault) share code
another way:

  • Inline the helper in each script, or
  • Chain scripts in a macro and pass data (not functions) through
    params.variables between steps.

Advanced patterns {#advanced-patterns}

Offer several actions from one script {#multiple-entry-points}

Export an object with multiple functions that users can choose from. The object
can also carry plain values (a default or shared constant) alongside the
functions:

javascript
module.exports = {
    "Create Note": createNote,
    "Update Note": updateNote,
    "Delete Note": deleteNote
};

async function createNote(params) {
    // Implementation
}

async function updateNote(params) {
    // Implementation
}

async function deleteNote(params) {
    // Implementation
}

Return a value {#returning-values}

Scripts can return values that become available as macro output:

javascript
async function start(params, settings) {
    const result = await processData();
    
    // Return value becomes available as macro output
    return result;
}

Work with files {#working-with-files}

javascript
async function start(params, settings) {
    const { app, obsidian } = params;
    
    // Get all markdown files
    const files = app.vault.getMarkdownFiles();
    
    // Get a specific file
    const file = app.vault.getAbstractFileByPath("path/to/file.md");
    if (file instanceof obsidian.TFile) {
        // Read file content
        const content = await app.vault.read(file);
        
        // Get file metadata
        const metadata = app.metadataCache.getFileCache(file);
        const frontmatter = metadata?.frontmatter;
        const links = metadata?.links || [];
        const tags = metadata?.tags || [];
        
        // Process content
        const modified = content.replace(/old/g, "new");
        
        // Save changes
        await app.vault.modify(file, modified);
    }
    
    // Create new file
    const newFile = await app.vault.create(
        "folder/subfolder/new-note.md", 
        "# Title\n\nContent here"
    );
    
    // Rename file
    await app.fileManager.renameFile(
        newFile, 
        "folder/subfolder/renamed-note.md"
    );
    
    // Delete file
    await app.vault.delete(newFile);
    
    // Copy file
    await app.vault.copy(
        file, 
        "path/to/copy.md"
    );
    
    // Get folder
    const folder = app.vault.getAbstractFileByPath("folder/subfolder");
    if (folder instanceof obsidian.TFolder) {
        // List folder contents
        const children = folder.children;
        
        // Create folder if it doesn't exist
        const path = "new/folder/structure";
        if (!app.vault.getAbstractFileByPath(path)) {
            await app.vault.createFolder(path);
        }
    }
}

Work with the active file {#working-with-the-active-file}

javascript
async function start(params, settings) {
    const { app, obsidian } = params;
    
    // Get active file
    const activeFile = app.workspace.getActiveFile();
    if (!activeFile) {
        new obsidian.Notice("No active file");
        return;
    }
    
    // Get active editor
    const activeView = app.workspace.getActiveViewOfType(obsidian.MarkdownView);
    if (activeView) {
        const editor = activeView.editor;
        
        // Get selected text
        const selection = editor.getSelection();
        
        // Get current line
        const cursor = editor.getCursor();
        const line = editor.getLine(cursor.line);
        
        // Replace selection
        editor.replaceSelection("New text");
        
        // Insert at cursor
        editor.replaceRange(
            "Inserted text", 
            cursor
        );
        
        // Get entire document
        const fullText = editor.getValue();
        
        // Replace entire document
        editor.setValue("Completely new content");
    }
}

Work with metadata and frontmatter {#working-with-metadata-and-frontmatter}

javascript
async function start(params, settings) {
    const { app, obsidian } = params;
    
    const file = app.workspace.getActiveFile();
    if (!file) return;
    
    // Get frontmatter
    const cache = app.metadataCache.getFileCache(file);
    const frontmatter = cache?.frontmatter || {};
    
    // Update frontmatter
    await app.fileManager.processFrontMatter(file, (fm) => {
        fm.tags = fm.tags || [];
        fm.tags.push("processed");
        fm.date = new Date().toISOString();
        fm.status = "completed";
        delete fm.oldField;  // Remove a field
    });
    
    // Get all files with specific frontmatter
    const filesWithTag = app.vault.getMarkdownFiles().filter(f => {
        const meta = app.metadataCache.getFileCache(f);
        return meta?.frontmatter?.tags?.includes("important");
    });
    
    // Get backlinks
    const backlinks = app.metadataCache.getBacklinksForFile(file);
    
    // Get outgoing links
    const links = cache?.links || [];
    const embeds = cache?.embeds || [];
}

Open and navigate files {#opening-and-navigating-files}

javascript
async function start(params, settings) {
    const { app, obsidian } = params;
    
    // Open file in active pane
    const file = app.vault.getAbstractFileByPath("path/to/note.md");
    if (file instanceof obsidian.TFile) {
        await app.workspace.getLeaf().openFile(file);
    }
    
    // Open in new pane
    await app.workspace.getLeaf('split').openFile(file);
    
    // Open in new tab
    await app.workspace.getLeaf('tab').openFile(file);
    
    // Open in new window
    await app.workspace.getLeaf('window').openFile(file);
    
    // Navigate to specific heading
    await app.workspace.openLinkText(
        "note#heading", 
        "",  // source path
        true  // new leaf
    );
    
    // Create and open a daily note
    const { createDailyNote } = app.plugins.plugins["daily-notes"].instance;
    const dailyNote = await createDailyNote(moment());
    await app.workspace.getLeaf().openFile(dailyNote);
}

Recipes {#common-patterns--recipes}

Process every note in a folder {#processing-multiple-notes}

javascript
async function processAllNotesInFolder(params, settings) {
    const { app, obsidian, quickAddApi } = params;
    
    const folderPath = settings["Folder Path"] || "Notes";
    const folder = app.vault.getAbstractFileByPath(folderPath);
    
    if (!(folder instanceof obsidian.TFolder)) {
        throw new Error(`Folder not found: ${folderPath}`);
    }
    
    let processed = 0;
    const errors = [];
    
    // Process each markdown file in folder
    for (const file of folder.children) {
        if (file instanceof obsidian.TFile && file.extension === "md") {
            try {
                const content = await app.vault.read(file);
                
                // Your processing logic here
                const modified = content + "\n\n---\nProcessed: " + new Date().toISOString();
                
                await app.vault.modify(file, modified);
                processed++;
                
            } catch (error) {
                errors.push(`${file.path}: ${error.message}`);
            }
        }
    }
    
    // Report results
    new obsidian.Notice(`Processed ${processed} files`);
    if (errors.length > 0) {
        await quickAddApi.infoDialog("Errors", errors);
    }
    
    return { processed, errors };
}

Create a note from a template {#creating-notes-from-templates}

javascript
async function createNoteFromTemplate(params, settings) {
    const { app, quickAddApi, variables } = params;
    
    // Get template
    const templatePath = settings["Template Path"];
    const templateFile = app.vault.getAbstractFileByPath(templatePath);
    
    if (!templateFile) {
        throw new Error(`Template not found: ${templatePath}`);
    }
    
    // Read template content
    const template = await app.vault.read(templateFile);
    
    // Get user input
    const title = await quickAddApi.inputPrompt("Note title:");
    const tags = await quickAddApi.inputPrompt("Tags (comma-separated):");
    
    // Format the template
    const formatted = await quickAddApi.format(template, {
        title: title,
        tags: tags.split(",").map(t => `#${t.trim()}`).join(" "),
        date: new Date().toISOString()
    });
    
    // Create the note
    const fileName = `${title.replace(/[^\w\s]/gi, '')}.md`;
    const filePath = `${settings["Output Folder"]}/${fileName}`;
    
    await app.vault.create(filePath, formatted);
    
    // Open the new note
    const newFile = app.vault.getAbstractFileByPath(filePath);
    await app.workspace.getLeaf().openFile(newFile);
    
    return filePath;
}

Add, remove, or replace a tag in bulk {#bulk-tag-operations}

javascript
async function bulkTagOperations(params, settings) {
    const { app, quickAddApi, obsidian } = params;
    
    const operation = await quickAddApi.suggester(
        ["Add tag", "Remove tag", "Replace tag"],
        ["add", "remove", "replace"]
    );
    
    const tag = await quickAddApi.inputPrompt("Tag name (without #):");
    let newTag;
    
    if (operation === "replace") {
        newTag = await quickAddApi.inputPrompt("Replace with tag:");
    }
    
    const files = app.vault.getMarkdownFiles();
    let modified = 0;
    
    for (const file of files) {
        await app.fileManager.processFrontMatter(file, (fm) => {
            fm.tags = fm.tags || [];
            
            if (operation === "add" && !fm.tags.includes(tag)) {
                fm.tags.push(tag);
                modified++;
            } else if (operation === "remove") {
                const index = fm.tags.indexOf(tag);
                if (index > -1) {
                    fm.tags.splice(index, 1);
                    modified++;
                }
            } else if (operation === "replace") {
                const index = fm.tags.indexOf(tag);
                if (index > -1) {
                    fm.tags[index] = newTag;
                    modified++;
                }
            }
        });
    }
    
    new obsidian.Notice(`Modified ${modified} files`);
    return modified;
}

Search and replace across the vault {#search-and-replace-across-vault}

javascript
async function searchAndReplace(params, settings) {
    const { app, quickAddApi, obsidian } = params;
    
    const searchTerm = await quickAddApi.inputPrompt("Search for:");
    const replaceTerm = await quickAddApi.inputPrompt("Replace with:");
    
    const confirm = await quickAddApi.yesNoPrompt(
        "Confirm",
        `Replace all occurrences of "${searchTerm}" with "${replaceTerm}"?`
    );
    
    if (!confirm) return;
    
    const files = app.vault.getMarkdownFiles();
    const results = [];
    
    for (const file of files) {
        const content = await app.vault.read(file);
        
        if (content.includes(searchTerm)) {
            const newContent = content.replaceAll(searchTerm, replaceTerm);
            await app.vault.modify(file, newContent);
            
            const count = (content.match(new RegExp(searchTerm, 'g')) || []).length;
            results.push(`${file.path}: ${count} replacements`);
        }
    }
    
    if (results.length > 0) {
        await quickAddApi.infoDialog("Replacements Made", results);
    } else {
        new obsidian.Notice("No matches found");
    }
    
    return results;
}

Enhance the daily note {#daily-note-automation}

javascript
async function enhanceDailyNote(params, settings) {
    const { app, obsidian } = params;
    
    // Get or create today's daily note
    const { moment } = window;
    const dailyNotes = app.plugins.plugins["daily-notes"];
    
    if (!dailyNotes) {
        throw new Error("Daily Notes plugin not enabled");
    }
    
    const { createDailyNote, getDailyNote } = dailyNotes.instance;
    const date = moment();
    
    let dailyNote = getDailyNote(date, dailyNotes.instance.options);
    if (!dailyNote) {
        dailyNote = await createDailyNote(date);
    }
    
    // Add content to daily note
    const content = await app.vault.read(dailyNote);
    
    // Add weather (example - would need actual API)
    const weather = "☀️ Sunny, 22°C";
    
    // Add tasks from yesterday
    const yesterday = moment().subtract(1, 'day');
    const yesterdayNote = getDailyNote(yesterday, dailyNotes.instance.options);
    
    let unfinishedTasks = "";
    if (yesterdayNote) {
        const yesterdayContent = await app.vault.read(yesterdayNote);
        const taskRegex = /- \[ \] .+/g;
        const tasks = yesterdayContent.match(taskRegex);
        if (tasks) {
            unfinishedTasks = "\n## Carried Over Tasks\n" + tasks.join("\n");
        }
    }
    
    const enhanced = content + `
## Weather
${weather}

${unfinishedTasks}

## Notes
- 

## Gratitude
- 
`;
    
    await app.vault.modify(dailyNote, enhanced);
    await app.workspace.getLeaf().openFile(dailyNote);
    
    return dailyNote.path;
}

Debugging tips {#debugging-tips}

  1. Use Console Logging Strategically

    javascript
    console.log("Script started", { settings, params });
    console.group("Processing files");
    files.forEach(f => console.log(f.path));
    console.groupEnd();
  2. Developer Console Access

    • Windows/Linux: Ctrl + Shift + I
    • Mac: Cmd + Option + I
    • Filter console by typing "QuickAdd" to see only relevant logs
  3. Error Boundaries

    javascript
    try {
        // Risky operation
        await app.vault.modify(file, content);
    } catch (error) {
        console.error("Failed to modify file:", error);
        new Notice(`Error: ${error.message}`);
        // Continue or throw depending on severity
    }
  4. Performance Monitoring

    javascript
    console.time("Processing files");
    // ... your code
    console.timeEnd("Processing files");
  5. Debugging State

    javascript
    // Use debugger statement to pause execution
    debugger;  // Execution will pause here if DevTools is open
    
    // Inspect variables at specific points
    console.table(variables);  // Great for objects/arrays

Example scripts {#example-scripts}

For complete working examples, see:

Additional resources {#additional-resources}

Obsidian API documentation {#obsidian-api-documentation}

QuickAdd resources {#quickadd-resources}

Troubleshooting {#troubleshooting}

Script not loading:

  • If QuickAdd says it "could not find" the script, the file was moved, renamed, or deleted, and the macro stops at that step (QuickAdd 2.30.0 or later; earlier versions show two errors and run the rest of the macro). Update the script's path in the macro
  • Check the file path in your macro configuration
  • Ensure the script exports a valid module
  • Check console for syntax errors

Settings not appearing:

  • Verify the settings object structure
  • Ensure option names are unique
  • Restart Obsidian after making changes to the settings structure

Variables not available in templates:

  • Make sure you're setting them on params.variables
  • Read them with {{VALUE:name}}, not {{name}}
  • Check that the script completes successfully
  • See Variables and data flow for how script variables move into later Template and Capture steps

API methods returning undefined:

  • Ensure you're using await with async methods
  • Check that QuickAdd plugin is enabled
  • Verify you're accessing the API correctly through params.quickAddApi

Src Content Docs Docs VariablesDataFlow (`docs/src/content/docs/docs/VariablesDataFlow.md`)


title: Variables and data flow description: How run variables move between prompts, scripts, and Template, Capture, and AI steps within a single QuickAdd run slug: docs/VariablesDataFlow

When a choice runs, QuickAdd keeps one scratchpad of named values for that run.
Prompts, scripts, and AI Assistant commands write values onto it; placeholders
like {{VALUE:project}} read them back. In a Macro, every step shares the same
scratchpad in order, so an answer you give in one step is available to every
step after it.

This page is about those run variables - the temporary values that exist
only while a Template, Capture, or Macro run is executing. For saved, reusable
snippets that persist across runs, see
Global Variables instead. The exact placeholder
grammar lives in Format Syntax; this page is about how
values move between steps.

At a glance, here is what writes a run variable and what reads it back:

Written by Read by
A prompt, such as {{VALUE:project}} or {{VDATE:due,YYYY-MM-DD}} {{VALUE:project}} in a later field or step
A user script (params.variables.slug = "...") {{VALUE:slug}}, or params.variables.slug in another script
An AI Assistant command's Output variable name {{VALUE:output}}
A script, the CLI, or the API supplying a value up front any placeholder that would otherwise prompt

Write once, use everywhere {#write-values-once}

Give a value a name when the same answer should appear in more than one
place. QuickAdd asks for it once and reuses your answer everywhere the name
appears:

markdown
File name:         Projects/{{VALUE:project}}
Template body:     # {{VALUE:project}}
Capture format:    Logged work for {{VALUE:project}}

If project isn't set yet, QuickAdd asks once and stores your answer under
project. Every later {{VALUE:project}} in the same run reads that stored
answer instead of asking again. This works across a Template file name, the
Template body, Capture formats, and later Macro steps.

If a script, the CLI, or the QuickAdd API supplies project before the
placeholder is formatted, the placeholder uses that value and never asks. A key
that is absent or set to undefined counts as missing; an empty string counts
as a deliberate empty answer.

When the prompt should be a picker, define the options once with a name, then
reuse the name on its own:

markdown
{{VALUE:work,home,urgent|name:category}}
{{VALUE:category}}

See {{VALUE:<options>|name:<name>}} for
option lists, display labels, first-definition behavior, and reserved names.

:::note
Leading and trailing spaces around the name are trimmed, so {{VALUE: project }}
reads the same variable as {{VALUE:project}}. Prefer the no-space form anyway

  • it's easier to scan, and safer in shared vaults that might run an older
    QuickAdd.

Trimming introduced in QuickAdd 2.17.2.
:::

The unnamed answer: `{{VALUE}}` {#the-special-value-input}

The unnamed {{VALUE}} reads a special variable called value. It's meant for
one-off input, or for a trigger that already supplied some content:

markdown
{{VALUE}}

This is not the same as {{VALUE:project}}. If an answer needs to survive
across several Macro steps, copy it into a named key and read that instead:

javascript
module.exports = async (params) => {
    if (params.variables.value !== undefined) {
        params.variables.project = params.variables.value;
    }
};

Later steps can then use:

markdown
{{VALUE:project}}

Sharing values with a script {#scripts-share-the-map}

A user script in a Macro receives params.variables - the same scratchpad the
placeholders read from. Write to it, and later steps see your values:

javascript
module.exports = async (params) => {
    const title = await params.quickAddApi.inputPrompt("Title");

    params.variables.title = title;
    params.variables.slug = title.toLowerCase().replace(/\s+/g, "-");
};

Later Macro steps can read them:

markdown
# {{VALUE:title}}
Slug: {{VALUE:slug}}

Two things trip people up here:

  • A plain JavaScript variable isn't enough. Only values written onto params.variables are visible to later steps. A const title = "Inbox" stays private to that one script; params.variables.title = title is what makes it available.
  • Order matters. A script must run before the Template, Capture, or script step that reads its variables.

:::note
Some older examples name the first parameter QuickAdd instead of params:

javascript
module.exports = async (QuickAdd) => {
    QuickAdd.variables.title = "Inbox";
};

That works because QuickAdd is just the local parameter name - it's the same
object as params. New scripts should use params to match the rest of the
docs.
:::

Inline scripts run first {#inline-scripts}

Inline scripts run before the ordinary placeholders around them, so an
inline script can't read a value the surrounding output is still about to
prompt for. This does not do what it looks like:

javascript
const value = "{{VALUE:project}}";

While the inline script runs, that's just literal text - the placeholder hasn't
been filled in yet. If the script needs input-aware logic, prompt inside the
script, or read a variable an earlier step already set:

javascript
const project = this.variables.project;
return project ? `Project: ${project}` : "";

Reading an AI Assistant's output {#ai-assistant-outputs}

An AI Assistant Macro command writes its response to the variable named in its
Output variable name setting. The default name is output, so later steps
can read:

markdown
{{VALUE:output}}

or, in a script:

javascript
module.exports = async (params) => {
    console.log(params.variables.output);
};

QuickAdd also writes a quote-block version under <name>-quoted. With the
default output name that's:

markdown
{{VALUE:output-quoted}}

If you renamed the output variable to description, read {{VALUE:description}}
and {{VALUE:description-quoted}} instead. Both are available to later steps in
the same Macro run.

`executeChoice` is a trigger, not a function call {#executechoice-is-a-trigger}

The Macro Builder's Choice command and the API method
quickAddApi.executeChoice look similar but behave differently.

A Choice command added inside a Macro runs as part of that Macro's sequence
and shares the Macro's scratchpad with the steps after it.

quickAddApi.executeChoice(choiceName, variables) is a one-way trigger. It
passes the variables you give it into the target choice, waits for that choice
to finish, and resolves with undefined - it does not hand the target
choice's output back to the caller. After the target finishes, QuickAdd clears
the temporary variables that execution used. So if you call it from a Macro
script, don't expect your current params.variables to still be around
afterward unless you saved and restored them yourself.

Use it to trigger another choice with input:

javascript
module.exports = async (params) => {
    await params.quickAddApi.executeChoice("Create project note", {
        project: params.variables.project,
    });
};

Don't expect a return value back from it:

javascript
module.exports = async (params) => {
    const result = await params.quickAddApi.executeChoice("Pick project");
    params.variables.project = result; // result is undefined
};

When later steps need a value, keep the workflow inside one Macro sequence, set
params.variables directly, or call a shared JavaScript helper that returns the
value to your script.

Troubleshooting {#troubleshooting}

The same prompt appears more than once. Use the same named placeholder
everywhere, such as {{VALUE:project}}. A bare {{VALUE}} and a named
{{VALUE:project}} are different inputs.

A script value is missing in a later Template or Capture. Set it on
params.variables, not only in a local JavaScript variable, and make sure the
script step runs before the step that reads it.

A placeholder stayed as literal text. The syntax is {{VALUE:name}}. A bare
{{name}} is not a QuickAdd placeholder.

An AI result is missing. Check the AI Assistant command's Output variable
name
, then read that exact variable in a later Macro step.

executeChoice returned undefined. That's expected. Use executeChoice
to trigger another choice with input variables, not to fetch a return value from
it.


Src Content Docs Docs Advanced CLI (`docs/src/content/docs/docs/Advanced/CLI.md`)


title: QuickAdd CLI description: Run, list, and check QuickAdd choices from Obsidian's native CLI, pass variables non-interactively, and create notes from templates slug: docs/Advanced/CLI

QuickAdd hooks into Obsidian's own command-line interface, so you can run a
choice from a terminal, a shell script, or a scheduled job - no link-building or
extra plugins. Point the obsidian command at a vault and a choice, and it
runs.

bash
obsidian vault=dev quickadd choice="Daily log"

QuickAdd registers these CLI handlers automatically on any Obsidian version that
supports plugin CLI commands.
The Raycast extension is built on these
commands, so you can run choices from Raycast without typing any.

What you need {#requirements}

  • Obsidian 1.12.2 or newer (the plugin CLI handler API arrived in 1.12.2).
  • QuickAdd enabled in the target vault.

The commands {#commands}

Run a choice: `quickadd` / `quickadd:run` {#quickadd--quickaddrun}

Run a QuickAdd choice from the CLI, by name or by id:

bash
obsidian vault=dev quickadd choice="Daily log"
obsidian vault=dev quickadd:run id="choice-id"
obsidian vault=dev quickadd:run choice="Weekly review" date=lw

date= is the day for {{DATE}}. Pass a real day (2026-08-21,
last friday, lw) and the choice's Which day setting is skipped. Pass
ask to open the date picker instead.

current= names the run's current note (QuickAdd 2.32.0 or later). By default
that is the active tab, which a run started from outside Obsidian cannot see.
Pass a vault path (.md may be omitted) and every part of the run that reads
the current note uses that note instead: Capture to active file, Link to
captured file
(Link to created file in a Template), {{LINKCURRENT}}, {{LINKSECTION}}, {{FILENAMECURRENT}},
{{FOLDERCURRENT}}, {{SELECTED}}, and a Template's Same folder as current
file
. Pass none to run without a current note.

bash
obsidian vault=dev quickadd:run choice="Add to today" value-value="Call Sam" current="Daily/2026-10-07"
obsidian vault=dev quickadd:run choice="Inbox" value-value="Idea" current=none
  • A path with no note returns {"ok":false,"error":"No note at '…'."} before anything runs.
  • When the named note is not open in the active editor, QuickAdd writes through
    the vault: a capture set to At cursor, New line above cursor, or New
    line below cursor
    goes to the top of the note, a link goes on
    a new last line (or into its frontmatter property), {{LINKSECTION}} links to
    the note without a heading, and {{SELECTED}} is empty.
  • With none, a choice that requires the current note fails with an error
    that says the run was started with current=none. A Template set to Same
    folder as current file
    uses the vault root instead. An optional link or
    token is left empty.
  • Macro scripts that call app.workspace.getActiveFile() themselves are not
    affected; current= only changes what QuickAdd resolves. An Obsidian
    Command
    step that runs a QuickAdd choice's command uses the named note too.

current= is also accepted by quickadd:run-template, quickadd:check, and
quickadd:interactive.

List your choices: `quickadd:list` {#quickaddlist}

List every QuickAdd choice (including nested choices inside multis):

bash
obsidian vault=dev quickadd:list
obsidian vault=dev quickadd:list type=Capture
obsidian vault=dev quickadd:list commands

Each Template and Capture carries a writes object that says what it writes,
read from its settings (QuickAdd 2.30.0 or later). You (or an agent) can tell
choices apart without opening them:

json
{"name":"Daily log","type":"Capture","currentNote":"none","writes":{"target":"Daily/{{DATE:YYYY-MM-DD}}.md","position":"after","line":"## Log","format":"- {{TIME}} {{VALUE}}\n","createWithTemplate":"Templates/Daily.md"}}
{"name":"Meeting note","type":"Template","currentNote":"optional","writes":{"template":"Templates/Meeting.md","folder":"Meetings","fileName":"{{DATE}} {{VALUE:topic}}"}}

Every choice also carries currentNote (QuickAdd 2.32.0 or later), which says
whether the choice uses the current note, so a caller knows when to pass
current=: required when the run fails, or lands somewhere else, without one
(Capture to active file, a link set to Enabled (strict), a {{LINKCURRENT}}-style
token, Same folder as current file, or no folder setting while Obsidian
creates new notes in the current file's folder), optional when something is left empty without
one (a link set to Enabled (skip if unavailable) and the tokens it makes optional,
{{SELECTED}}, a {{FIELD:…|default-from:active}} default), and none otherwise. The tokens
are read from every text the run formats, including the template a Capture
creates its target with and the files a template includes. Macros and Multis
are always none. The same key is in the choice summary that
quickadd:check, quickadd:run, and quickadd:interactive return.

Capture key Meaning
target The Capture to value, or <active file>
position top, bottom, after, before, cursor, newLineAbove, newLineBelow, or property, matching Write position
line The line for after or before
property The property a property capture writes
format The text written: the Capture format, or {{VALUE}} when it is empty (before QuickAdd 2.30.0: when its toggle is off)
task true when the capture is written as a task
eachLine true when One entry per line is on: each line of {{VALUE}} becomes its own entry (QuickAdd 2.30.0 or later)
createWithTemplate The template for a target file that doesn't exist yet
Template key Meaning
template The template file
folder The folder the note goes in; <default> is Obsidian's default location for new notes
fileName The File name, or {{VALUE}} when it is empty (before QuickAdd 2.30.0: when the File name format toggle is off)

Formats are shown unexpanded. <ask> means QuickAdd asks for that part when the
choice runs. Macros and Multis have no writes.

See what a choice still needs: `quickadd:check` {#quickaddcheck}

Check which inputs are still missing before a non-interactive run:

bash
obsidian vault=dev quickadd:check choice="Daily log"

List what Obsidian's own [[ link or # tag suggester offers, so another app can complete links and tags the way the editor does:

bash
obsidian vault=dev quickadd:suggest kind=links
obsidian vault=dev quickadd:suggest kind=links source="Daily/2026-10-04.md"
obsidian vault=dev quickadd:suggest kind=tags
  • kind=links returns one item per linkable file and per alias, newest first, with files in Obsidian's Excluded files list last. text is what goes inside [[...]] (Plan, Projects/Meeting when two notes share a name, Plan|Big Plan for an alias), path is the file, and alias is set on alias items. Pass source=<note path> when you know which note the link goes into: under the Relative path to file link format the text depends on it; without source the text is computed from the vault root.
  • kind=tags returns {"tag":"work","count":3} items, most used first, without the #.

Create a note from a template: `quickadd:run-template` {#quickaddrun-template}

Create a new note from a template file, with no dedicated Template choice
required. This is the scriptable form of the New note from template
command.

bash
obsidian vault=dev quickadd:run-template \
  path="Templates/Meeting.md" \
  value-value="2026-06-14 Standup"
  • path= is the template file (vault-relative). A leading slash is allowed and a missing .md extension is added, matching how Template choices resolve paths. If no file resolves there, the command returns {"ok":false} up front.
  • The new note's name comes from {{VALUE}} - pass it as value-value=.... A non-interactive run with an empty or missing name returns missingFlags instead of creating an unnamed note. The note is created in Obsidian's "Default location for new notes".
  • The picker (interactive command) only lists templates inside your configured template folder(s); path= here is explicit, so any vault file resolves.
  • Like quickadd:run, name collisions on the target note still prompt (the file-exists choice is not a pre-collected input). Under quickadd:interactive that prompt is forwarded to you like any other.

Save a clipboard image: `quickadd:save-clipboard-image` {#quickaddsave-clipboard-image}

Save a 1x1 PNG through the same path QuickAdd uses when you paste an image into a prompt or when {{CLIPBOARD}} falls back to an image. Useful for checking attachment naming without driving a modal.

bash
obsidian vault=dev quickadd:save-clipboard-image \
  sourcePath="Meetings/Meeting notes.md" \
  nameAfterNoteTitle=true
  • sourcePath= is the note the attachment belongs to (the capture destination). Empty uses vault-root attachment placement and the timestamp name even when title-naming is on.
  • nameAfterNoteTitle= overrides the Name pasted images after the note title setting for this save. Omit it to use the setting.

Preview and import a package: `quickadd:package-preview` / `quickadd:package-import` {#quickaddpackage-import}

Review a package as JSON, or install it with the
import modal's default decisions:

bash
obsidian vault=dev quickadd:package-preview path=path/to/package.quickadd.json
obsidian vault=dev quickadd:package-import path=path/to/package.quickadd.json acknowledge=true

A package that runs code is refused until you pass acknowledge=true. See
Preview a package from the command line
for the other options. quickadd:package-import needs QuickAdd 2.28.0 or later.

Check an AI provider: `quickadd:ai-test-connection` {#quickaddai-test-connection}

Check that an AI provider accepts its
linked key. This is the provider page's Test connection button:

bash
obsidian vault=dev quickadd:ai-test-connection provider=openai
  • provider= is the provider's ID, such as openai, or its name. The ID must
    match exactly; the name can be in any case.

  • A working key returns "ok":true and the number of models the provider
    lists:

    json
    {"command":"quickadd:ai-test-connection","provider":"openai","ok":true,"modelCount":64,"apiKeyLinked":true}

    A rejected key returns "ok":false with the provider's error.

  • An unknown or missing provider= returns "ok":false and lists the
    provider IDs you can use.

  • The output never contains the key. This command needs QuickAdd 2.28.0 or
    later.

Let an AI agent write through your choices {#agents}

Coding agents such as Claude Code and Codex can edit your vault's files
directly, but then they copy your conventions by hand and sometimes miss one.
QuickAdd ships an agent skill
that tells an agent to list your choices, check their inputs, and run them
with verify instead. It can also create a new choice for you by importing a
package.

Install it into your vault:

bash
npx skills add chhoumann/quickadd

Or copy `skills/quickadd/SKI