# Technical Documentation: chhoumann/quickadd

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/chhoumann/quickadd)

### Overview (`README.md`)

<p align="center"><img src="assets/quickadd-banner.png" alt="QuickAdd for Obsidian" width="520"></p>

<p align="center"><b>One hotkey to log a line, create a note, or run a whole workflow.</b></p>

<p align="center"><a href="https://community.obsidian.md/plugins/quickadd">Install</a>&nbsp;· <a href="https://quickadd.obsidian.guide/docs/">Documentation</a>&nbsp;· <a href="https://quickadd.obsidian.guide/docs/Examples/">Examples</a>&nbsp;· <a href="https://github.com/chhoumann/quickadd/discussions">Discussions</a>&nbsp;· <a href="https://github.com/chhoumann/quickadd/releases">Changelog</a></p>

<p align="center"><img src="assets/readme-hero.gif" alt="Running QuickAdd: one hotkey adds a timestamped line to today's daily note, then creates a meeting note from a template" width="880"></p>

## What you can build

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

- **[Template](https://quickadd.obsidian.guide/docs/Choices/TemplateChoice/)** 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](https://quickadd.obsidian.guide/docs/Choices/CaptureChoice/)** adds text to a note without opening it. For example, a timestamped line under the Log heading of today's daily note.
- **[Macro](https://quickadd.obsidian.guide/docs/Choices/MacroChoice/)** 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](https://quickadd.obsidian.guide/docs/Choices/MultiChoice/)** 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](https://quickadd.obsidian.guide/docs/FormatSyntax/) for all of them.

QuickAdd also has:

- **Forms**: collect every input on [one page](https://quickadd.obsidian.guide/docs/Advanced/onePageInputs/), with dropdowns, date pickers, and sliders.
- **Scripts**: run your own JavaScript as [user scripts](https://quickadd.obsidian.guide/docs/UserScripts/), and call the [QuickAdd API](https://quickadd.obsidian.guide/docs/QuickAddAPI/) from scripts, Templater, or other plugins.
- **Triggers**: run choices from [`obsidian://quickadd` links](https://quickadd.obsidian.guide/docs/Advanced/ObsidianUri/), Apple Shortcuts, [launchers and schedulers](https://quickadd.obsidian.guide/docs/Advanced/TriggerQuickAddFromOutsideObsidian/), or the [Obsidian CLI](https://quickadd.obsidian.guide/docs/Advanced/CLI/).
- **AI**: send prompts to OpenAI, Anthropic, Gemini, or any OpenAI-compatible provider, including local models, with the [AI Assistant](https://quickadd.obsidian.guide/docs/AIAssistant/). AI features stay off until you [turn them on](https://quickadd.obsidian.guide/docs/Settings/#ai--online).
- **Packages**: export your choices as a [package](https://quickadd.obsidian.guide/docs/Choices/Packages/) and import them into another vault.

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](https://quickadd.obsidian.guide/docs/#first-workflow) guide.
3. In **Settings → QuickAdd**, click the ⚡ next to a choice to add it to the command palette (on mobile, tap ⋮&nbsp;→&nbsp;**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](https://quickadd.obsidian.guide/docs/ComingFromTemplater/) shows the QuickAdd way to do each Templater job.

## Learn more

- [Examples](https://quickadd.obsidian.guide/docs/Examples/): complete workflows for daily notes, inboxes, meetings, books, movies, and more.
- [Scripting guide](https://quickadd.obsidian.guide/docs/Advanced/ScriptingGuide/): writing your own scripts when the built-in steps aren't enough.
- [FAQ](https://quickadd.obsidian.guide/docs/FAQ/): answers to common questions.
- [llms.txt](https://quickadd.obsidian.guide/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](https://github.com/chhoumann/quickadd/discussions).
- Report bugs and request features in [Issues](https://github.com/chhoumann/quickadd/issues).
- Report security problems privately, as described in the [security policy](SECURITY.md).

## Contributing

Contributions are welcome. Read [CONTRIBUTING.md](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](https://buymeacoffee.com/chhoumann).

## License

[MIT](LICENSE)

---

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

# QuickAdd docs

The docs site (https://quickadd.obsidian.guide), built with [Astro Starlight](https://starlight.astro.build/).

## 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`).
- `<page-url>.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`](https://github.com/chhoumann/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/svelte-check@4.7.6.patch`
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 <name>" 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.

```
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: <id>` in its frontmatter. The
`PackageCard` component then renders the "Get this workflow" card from the
manifest, and `/packages/<id>.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-<id>` or `qa-pkg-<id>-<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/<id>/package.json`, paste the `choices` and
   `rootChoiceIds`, and rename every choice id to the `qa-pkg-<id>` 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: <id>` 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
description: "Get started with QuickAdd: install it, pick between Template, Capture, Macro, and Multi choices, and build your first workflow"
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](#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](/docs/ManualInstallation/).

## 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](/docs/Choices/TemplateChoice/) |
| Append text to a journal, log, task list, or existing file | Capture choice | [Capture Choices](/docs/Choices/CaptureChoice/) |
| Run one or more Obsidian commands, scripts, or choices | Macro choice | [Macro Choices](/docs/Choices/MacroChoice/) |
| Group choices into a nested menu | Multi choice | [Multi Choices](/docs/Choices/MultiChoice/) |
| Share configured workflows across vaults | Package | [Share QuickAdd Packages](/docs/Choices/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](/docs/FormatSyntax/):
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](/docs/SuggesterSystem/) 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](/docs/Examples/) to pick a complete workflow by choice
type, difficulty, prerequisites, and outcome.

Good first examples:

- [Capture: Add entries to your daily note](/docs/Examples/Capture_ToDailyNote/)
- [Template: Add an Inbox Item](/docs/Examples/Template_AddAnInboxItem/)
- [Macro: Book Finder](/docs/Examples/Macro_BookFinder/)
- [Capture: Canvas Capture](/docs/Examples/Capture_CanvasCapture/)

### I want to automate with scripts

Start with the [scripting overview](/docs/Advanced/ScriptingGuide/), then move to
[User Scripts](/docs/UserScripts/) and the [QuickAdd API reference](/docs/QuickAddAPI/)
when you need exact method details.

### I want to call QuickAdd from outside Obsidian

Use [Obsidian URI](/docs/Advanced/ObsidianUri/) for URI-triggered workflows, or the
[QuickAdd CLI](/docs/Advanced/CLI/) for shell scripts and external automation.

---

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

---
title: API Overview
description: Reference for the QuickAdd scripting API - where it is available and the method families for input, choices, dates, AI, and fields
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](/docs/QuickAddAPI/).

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](/docs/Choices/TemplateChoice/) or
[Capture Choices](/docs/Choices/CaptureChoice/) - 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](/docs/QuickAddAPI/#user-input-methods) |
| Run another choice from a script | Choice execution | [Choice Execution](/docs/QuickAddAPI/#choice-execution) |
| Read selected text or clipboard content | Utility module | [Utility Module](/docs/QuickAddAPI/#utility-module) |
| Format dates | Date module | [Date Module](/docs/QuickAddAPI/#date-module) |
| Call configured AI providers | AI module | [AI Module](/docs/QuickAddAPI/#ai-module) |
| Read field suggestions from the vault | Field suggestions module | [Field Suggestions Module](/docs/QuickAddAPI/#field-suggestions-module) |

## Recommended path

1. Read the [scripting overview](/docs/Advanced/ScriptingGuide/) if you have not written a
   QuickAdd script before.
2. Copy a working pattern from the [examples overview](/docs/Examples/).
3. Use the [QuickAdd API reference](/docs/QuickAddAPI/) 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
description: Browse ready-made QuickAdd workflows for captures, templates, and macros, with setup difficulty, prerequisites, and what each creates
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](/docs/Choices/Packages/#install-an-example)
for the full walkthrough.

| Workflow | Choice type | Setup | Prerequisites | What it creates |
| --- | --- | --- | --- | --- |
| [Capture to Your Daily Note](/docs/Examples/Capture_ToDailyNote/) | Capture | Beginner | Daily note path | Timestamped entries, tasks, quotes, callouts, and table rows |
| [Capture to Inbox with GPS](/docs/Examples/Macro_CaptureInboxGps/) | Macro | Intermediate | Obsidian mobile 1.11+ for GPS | A timestamped inbox line with coordinates |
| [Brain dump](/docs/Examples/Macro_BrainDump/) | 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](/docs/Examples/Capture_AddTaskToKanbanBoard/) | Capture | Beginner | Obsidian Kanban plugin | A task in a board section |
| [Fetch Tasks from Todoist](/docs/Examples/Capture_FetchTasksFromTodoist/) | Capture and Macro | Intermediate | Todoist API token | Imported Todoist tasks |
| [Canvas Capture](/docs/Examples/Capture_CanvasCapture/) | Capture | Intermediate | An Obsidian Canvas file | Text added to a selected or targeted card |
| [Add an Inbox Item](/docs/Examples/Template_AddAnInboxItem/) | Template | Beginner | Inbox folder or note | A new inbox note |
| [Meeting notes and project updates](/docs/Examples/Template_MeetingNotes/) | Template | Beginner | QuickAdd 2.27.0 or later | Dated notes and updates, ready to type in |
| [Create an MOC Note with a Link Dashboard](/docs/Examples/Template_CreateMOCNoteWithLinkDashboard/) | Template | Intermediate | Base template file | A note with an embedded Base dashboard |
| [Automatic Book Notes from Readwise](/docs/Examples/Template_AutomaticBookNotesFromReadwise/) | Template and Macro | Advanced | Readwise account and access token | Book notes with highlights |
| [Book Finder](/docs/Examples/Macro_BookFinder/) | Macro | Intermediate | Book lookup script | A populated book note |
| [Movie and Series Script](/docs/Examples/Macro_MovieAndSeriesScript/) | Macro | Intermediate | OMDb API key | Media notes with metadata |
| [Move Notes with a Tag](/docs/Examples/Macro_MoveNotesWithATagToAFolder/) | Macro | Intermediate | Tagged notes | Notes moved into a target folder |
| [Zettelizer](/docs/Examples/Macro_Zettelizer/) | Macro | Intermediate | Headings in an existing note | New notes split from headings |
| [Toggl Manager](/docs/Examples/Macro_TogglManager/) | Macro | Advanced | Toggl Track account and integration plugin | Preset time entries |

## Pick by goal

### Capture information faster

Start with [Capture to Your Daily Note](/docs/Examples/Capture_ToDailyNote/) for daily-note
captures. Use [Capture to Inbox with GPS](/docs/Examples/Macro_CaptureInboxGps/) when the
line should also store device coordinates. Use [Brain dump](/docs/Examples/Macro_BrainDump/)
to type several entries in a row without reopening the Capture. Move to
[Canvas Capture](/docs/Examples/Capture_CanvasCapture/) when your target is a Canvas card
instead of a Markdown note.

### Create structured notes

Start with [Add an Inbox Item](/docs/Examples/Template_AddAnInboxItem/) for a small template.
Try [meeting notes and project updates](/docs/Examples/Template_MeetingNotes/)
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](/docs/Examples/Template_CreateMOCNoteWithLinkDashboard/)
when you want a generated note to include a live Base dashboard.

### Run scripted workflows

Start with [Book Finder](/docs/Examples/Macro_BookFinder/) 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": "pnpm@10.32.1",
  "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}}`](/docs/FormatSyntax/#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."
		]
	}
}

---

### Packages Moc Link Dashboard Package.Json (`docs/packages/moc-link-dashboard/package.json`)

{
	"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."
		]
	}
}

---

### Packages Moc Related Notes Package.Json (`docs/packages/moc-related-notes/package.json`)

{
	"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\n```base\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](https://www.omdbapi.com/apikey.aspx))"
		],
		"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](https://readwise.io/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](https://track.toggl.com/profile)",
			"the [Toggl Track integration](https://github.com/mcndt/obsidian-toggl-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](https://track.toggl.com/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="
		}
	]
}

---

### Public Packages Moc Link Dashboard.Quickadd.Json (`docs/public/packages/moc-link-dashboard.quickadd.json`)

{
	"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"
		}
	]
}

---

### Public Packages Moc Related Notes.Quickadd.Json (`docs/public/packages/moc-related-notes.quickadd.json`)

{
	"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\n```base\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
- [ ]

---

### Packages Moc Link Dashboard Files MOC Link Dashboard (`docs/packages/moc-link-dashboard/files/MOC Link Dashboard.md`)

---
tags:
  - moc
---

# {{VALUE:moc_title}}

## Link Dashboard

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`)

---
category: "{{VALUE:typeLink}}"
director: {{VALUE:directorLink}}
genre: {{VALUE:genreLinks}}
cast: {{VALUE:actorLinks}}
year: "{{VALUE:Year}}"
imdbId: "{{VALUE:imdbID}}"
imdb: "[IMDb]({{VALUE:imdbUrl}})"
ratingImdb: "{{VALUE:imdbRating}}"
rating: 
cover: "{{VALUE:Poster}}"
---



{{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
description: Configure AI providers, send prompt templates from macros or scripts, and use structured output, tool calling, and token budgets
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](#providers-and-local-models).
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](/docs/FormatSyntax/), 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](/docs/InlineScripts/) (`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](/docs/Choices/Packages/) - 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](#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](#advanced-sampling-settings).

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

QuickAdd resolves [Format Syntax](/docs/FormatSyntax/) 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](/docs/Advanced/CLI/#quickaddai-test-connection) 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](#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()`](/docs/QuickAddAPI/#max-chunk-tokens) 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](/docs/QuickAddAPI/#ai-module).

## 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](/docs/UserScripts/).

### 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
description: Apply a template to an existing note with insert, append, or replace modes, merging frontmatter and optionally moving the note to match
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](/docs/Choices/TemplateChoice/) 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](#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](/docs/FormatSyntax/) pipeline. Templater syntax is also processed
when the Templater plugin is enabled.

Put [`{{CURSOR}}`](/docs/FormatSyntax/#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](/docs/Examples/Template_MeetingNotes/#add-an-update-to-an-existing-project)
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](/docs/Choices/TemplateChoice/) 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](/docs/QuickAddAPI/) 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
description: Migrate Templater workflows to QuickAdd - templates, prompts, dates, daily notes, folder workflows, cursor placement, and scripts, done the QuickAdd-native way.
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](#common-migration-snags)).

New to QuickAdd entirely? Start with [Getting Started](/docs/) 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](/docs/Settings/#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](/docs/Choices/TemplateChoice/) 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}}`](/docs/FormatSyntax/#title) |
| Today's date, any format | `tp.date.now` | [`{{DATE:YYYY-MM-DD}}`](/docs/FormatSyntax/#date-format), offsets like `{{DATE+7}}` |
| Ask for a date in plain language | `tp.system.prompt` | [`{{VDATE:due,YYYY-MM-DD}}`](/docs/FormatSyntax/#vdate) - natural language built in |
| Prompt for text | `tp.system.prompt` | [`{{VALUE:name}}`](/docs/FormatSyntax/#named-value) |
| Pick from a list | `tp.system.suggester` | [`{{VALUE:Option A,Option B}}`](/docs/FormatSyntax/#named-value), [`{{FIELD:...}}`](/docs/FormatSyntax/#field), [`{{FILE:...}}`](/docs/FormatSyntax/#file) |
| Include one template in another | `tp.file.include` | [`{{TEMPLATE:Templates/Partial.md}}`](/docs/FormatSyntax/#template) |
| Apply a template to an existing note | the insert template command | [Apply template to active note](/docs/ApplyTemplateToNote/) |
| Insert the clipboard | `tp.system.clipboard` | [`{{CLIPBOARD}}`](/docs/FormatSyntax/#clipboard) |
| Insert the selected text | `tp.selection` | [`{{SELECTED}}`](/docs/FormatSyntax/#selected) |
| Reuse a property from the note you're in | `tp.frontmatter` | [`{{FIELD:project\|default-from:active}}`](/docs/FormatSyntax/#field-default-from-active); to re-render a value you prompted for, just [repeat `{{VALUE:name}}`](#prompt-once-reuse-everywhere) |
| Link back to the note you came from | `tp.file.path` workarounds | [`{{LINKCURRENT}}`](/docs/FormatSyntax/#linkcurrent) (a link), [`{{FILENAMECURRENT}}`](/docs/FormatSyntax/#filenamecurrent) (raw name, for embeds like ` (link to the heading you're in) |
| Run JavaScript | `tp.user` | [Inline scripts](/docs/InlineScripts/), [user scripts in macros](/docs/Choices/MacroChoice/), [`{{MACRO:...}}`](/docs/FormatSyntax/#macro) |
| Folder templates | folder templates | No automatic equivalent - see [Templates chosen by folder](#templates-chosen-by-folder) |
| Cursor marker | `tp.file.cursor` | `{{CURSOR}}` in Template or Capture bodies - see [Where the cursor lands](#where-the-cursor-lands) |

## Create new notes from templates

A [Template choice](/docs/Choices/TemplateChoice/) 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}}`](/docs/FormatSyntax/#template).
- **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](/docs/ApplyTemplateToNote/).

A [Capture choice](/docs/Choices/CaptureChoice/) 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}}`](/docs/FormatSyntax/#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](/docs/Examples/Capture_ToDailyNote/); [Capture choices](/docs/Choices/CaptureChoice/) 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](/docs/FormatSyntax/), 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](https://momentjs.com/docs/#/displaying/format/), and offsets travel in days: `{{DATE+7}}`, `{{DATE:gggg-[W]WW+7}}`. Snap to period boundaries with [`|startof:` / `|endof:`](/docs/FormatSyntax/#date-snap), 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}}`](/docs/FormatSyntax/#vdate) 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](#run-scripts) 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](#create-new-notes-from-templates).

That sharing spans [Macro choice](/docs/Choices/MacroChoice/) 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](/docs/Advanced/ScriptingGuide/).

To gather every prompt on a single form up front instead of one dialog at a time, enable [one-page input](/docs/Advanced/onePageInputs/) - and see [Controlling Prompts](/docs/ControllingPrompts/) for everything about prompt order, labels, defaults, and skipping.

## Where the cursor lands

Use [`{{CURSOR}}`](/docs/FormatSyntax/#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](/docs/InlineScripts/)**: 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](/docs/Choices/MacroChoice/)**: a `.js` file in your vault, run as a step of a Macro choice. Scripts receive `app`, the [QuickAdd API](/docs/QuickAddAPI/) (`inputPrompt`, `suggester`, `executeChoice`, and more), the shared `variables` map, and the `obsidian` module - see the [scripting guide](/docs/Advanced/ScriptingGuide/).

`{{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](#the-quick-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](#todays-daily-note)).

---

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

---
title: Controlling Prompts
description: Everything you control about the questions QuickAdd asks - which ones appear, in what order, how they look, how to skip them, and which keys submit or cancel.
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](/docs/FormatSyntax/). For the one-page form in depth, see [One-page Inputs](/docs/Advanced/onePageInputs/).

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

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

- [`{{VALUE}}` / `{{VALUE:name}}`](/docs/FormatSyntax/#value) ask for text, or show a pick list when you give them options.
- [`{{VDATE:name,format}}`](/docs/FormatSyntax/#vdate) asks for a date, and understands natural language like `tomorrow`.
- [`{{FIELD:name}}`](/docs/FormatSyntax/#field) suggests values a property already has in your vault.
- [`{{FILE:folder}}`](/docs/FormatSyntax/#file) 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](/docs/QuickAddAPI/), or the [CLI](/docs/Advanced/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:`](/docs/FormatSyntax/#value-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](/docs/Choices/CaptureChoice/#use-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](#one-form-instead-of-many-prompts): 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:`](/docs/FormatSyntax/#value-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:`](/docs/FormatSyntax/#value-label) | `{{VALUE:attendees\|label:Who attended?}}` |
| A pre-filled default | [`\|default:`](/docs/FormatSyntax/#value-default-option) | `{{VALUE:status\|default:open}}` |
| A large, multi-line text box | [`\|type:multiline`](/docs/FormatSyntax/#value-multiline) | `{{VALUE:notes\|type:multiline}}` |
| A number, slider, or checkbox | [`\|type:number` and friends](/docs/FormatSyntax/#value-property-types) | `{{VALUE:rating\|type:slider\|min:0\|max:10}}` |
| A pick list | [comma-separated options](/docs/FormatSyntax/#named-value) | `{{VALUE:low,medium,high}}` |
| A pick list that also accepts custom text | [`\|custom`](/docs/FormatSyntax/#value-custom) | `{{VALUE:home,work\|custom}}` |
| Several selections at once | [`\|multi`](/docs/FormatSyntax/#value-multi) | `{{VALUE:a,b,c\|multi}}` |

The full reference for every flag lives in [Format Syntax](/docs/FormatSyntax/).

Good to know:

- `|label:` works on `{{VALUE}}` placeholders, [`{{VDATE:...}}`](/docs/FormatSyntax/#vdate-label) dates, [`{{FIELD:...}}`](/docs/FormatSyntax/#field-label) suggestions, and [`{{FILE:...}}`](/docs/FormatSyntax/#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`](/docs/FormatSyntax/#optional-fields) 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}}`](/docs/FormatSyntax/#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](/docs/Settings/#advanced-notifications) (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](/docs/QuickAddAPI/), 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](/docs/SuggesterSystem/) 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](/docs/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}}`](/docs/FormatSyntax/#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](/docs/Advanced/onePageInputs/).

## 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](https://obsidian.md/help/snippets):

| 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`](/docs/QuickAddAPI/). Both are covered in [One-page Inputs](/docs/Advanced/onePageInputs/#user-scripts-declare-inputs-optional).

---

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

---
title: FAQ
description: How to sync your QuickAdd choices, macros, and scripts between devices, and why API keys stay local to each one
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](https://obsidian.md/help/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](/docs/UserScripts/) 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](/docs/Choices/Packages/) 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
description: "Placeholders like {{DATE}} and {{VALUE}} that QuickAdd replaces with real values: dates, your answers, links, clipboard content, and more"
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}}`](#value) | Ask for text |
| [`{{VALUE:title}}`](#named-value) | Ask for text once, reuse the answer anywhere as `title` |
| [`{{VALUE:Red,Green,Blue}}`](#value-suggest) | Pick from a list |
| [`{{VDATE:due,YYYY-MM-DD}}`](#vdate) | Ask for a date ("tomorrow" works) |
| [`{{FIELD:project}}`](#field) | Suggest values that property already has in your vault |
| [`{{PROPERTY}}`](#property) | Insert the property's current value (property Captures only) |
| [`{{CURSOR}}`](#cursor) | Place the cursor inside a Template or Capture |
| [`{{FILE:People}}`](#file) | Pick a note from a folder |
| [`{{MVALUE}}`](#mvalue) | Write a math formula (LaTeX) |

**Dates**

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

**The note you ran QuickAdd from**

| Placeholder | What you get |
| --- | --- |
| [`{{LINKCURRENT}}`](#linkcurrent) | A link to it: `[[That note]]` |
| [`{{LINKSECTION}}`](#linksection) | A link to the section your cursor is in |
| [`{{FILENAMECURRENT}}`](#filenamecurrent) | Its file name |
| [`{{FOLDERCURRENT}}`](#foldercurrent) | Its folder |
| [`{{SELECTED}}`](#selected) | The text you had selected |

**The note being created**

| Placeholder | What you get |
| --- | --- |
| [`{{TITLE}}`](#title) | The new note's file name |
| [`{{FOLDER}}`](#folder) | The folder the new note lands in |

**Other content**

| Placeholder | What it inserts |
| --- | --- |
| [`{{CLIPBOARD}}`](#clipboard) | Whatever you copied last |
| [`{{TEMPLATE:Templates/Meeting.md}}`](#template) | The contents of a template file |
| [`{{MACRO:My Macro}}`](#macro) | Whatever a macro returns |
| [`{{GLOBAL_VAR:Header}}`](#global-var) | A snippet you defined in settings |
| [`{{RANDOM:6}}`](#random) | 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](/docs/Choices/TemplateChoice/#date-origin).

`{{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
```

<a id="obsidian-date-placeholders"></a>

:::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](/docs/Choices/TemplateChoice/) instead.
:::

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

Put a [Moment.js format](https://momentjs.com/docs/#/displaying/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`](#value-case): `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:<unit>` or `|endof:<unit>` to move the date to the boundary of a
period before formatting. `<unit>` 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}}`](#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](#obsidian-date-placeholders).
- 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](https://momentjs.com/docs/#/displaying/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}}`](#vdate).

### 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](https://github.com/liamcain/obsidian-periodic-notes)
plugin manages daily notes, `{{DAILY}}` uses its settings instead.

Good to know:

- The day follows the choice's [Which day](/docs/Choices/TemplateChoice/#date-origin), 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}}`](#daily) for the notes the
[Periodic Notes](https://github.com/liamcain/obsidian-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:<name>, <format>}}` {#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](/docs/Choices/TemplateChoice/#date-origin) 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:<name>}}` {#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}}`](#template) 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](#optional-fields) 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](/docs/Advanced/onePageInputs/). 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](#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](/docs/InlineScripts/#execution-order-and-value).
- **From the API**, pass the value programmatically under the reserved variable name `value`.

### Name the answer so you can reuse it: `{{VALUE:<name>}}` {#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:<option>,<option>}}` {#value-suggest}

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`](#value-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](#value-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**](/docs/Choices/CaptureChoice/#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](/docs/Advanced/onePageInputs/), 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](/docs/Advanced/onePageInputs/) 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](#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](/docs/Advanced/onePageInputs/). 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: `|<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`](#value-default-option) 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.

<a id="mixed-mode-example"></a>

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](/docs/ControllingPrompts/#submit-keys) 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.

### A link to the note: `{{LINKCURRENT}}` {#linkcurrent}

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.

### A link to the current section: `{{LINKSECTION}}` {#linksection}

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](/docs/Choices/TemplateChoice/#optional),
`{{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}}`](#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}}`.

<a id="foldercurrentname---just-the-folder-name"></a>

#### 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}}`](#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.

<a id="foldername--just-the-folder-name"></a>

#### 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 name>}}` {#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**](/docs/Choices/CaptureChoice/#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](/docs/Advanced/onePageInputs/).

```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](#field-default-from-active)).

:::caution[Beta]
FIELD filtering is in beta and the syntax can change - leave your thoughts
[on issue #1429](https://github.com/chhoumann/quickadd/issues/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](/docs/Examples/Template_MeetingNotes/).

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](/docs/ApplyTemplateToNote/) 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](/docs/Choices/CaptureChoice/#property), `{{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). `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](/docs/InlineScripts/#property-capture-variables).

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

`{{FILE:People}}` opens a picker listing the Markdown files in your `People`
folder and inserts your pick. Where [`{{FIELD}}`](#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](/docs/Choices/CaptureChoice/#property), 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](#file-type).
- `|label:Pick a person` - set the picker's placeholder text.
- `|name:<id>` - 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](#value) 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:<path>}}` {#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 name>}}` {#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:<name>}}` {#global-var}

Inserts the value of a [global variable](/docs/GlobalVariables/) 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`](#value-multiline) instead:

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

### A random ID: `{{RANDOM:<length>}}` {#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
description: "Define reusable, vault-wide text snippets once in settings and drop them into any choice or template with the {{GLOBAL_VAR:name}} placeholder"
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](/docs/VariablesDataFlow/), 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](/docs/FormatSyntax/).
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:<name>}}` {#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](/docs/Advanced/onePageInputs/) 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
description: Run JavaScript inside Template and Capture choices with js quickadd code blocks, returning text or typed values to be inserted
slug: docs/InlineScripts
---

An inline script is a small block of JavaScript you drop straight into a
[Template](/docs/Choices/TemplateChoice/) or
[Capture](/docs/Choices/CaptureChoice/) choice - or an
[AI Assistant prompt template](/docs/AIAssistant/#setup), 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](/docs/FormatSyntax/)
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:

````
```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](/docs/UserScripts/), which run from a macro.
- The [QuickAdd API](/docs/QuickAddAPI/) 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](#set-a-property).

Return values follow the same rendering contract as a typed
[`{{VALUE:name}}`](/docs/FormatSyntax/#named-value):

| 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`:

````
---
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:

````
```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](/docs/Choices/CaptureChoice/#property), 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:

````
```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:

````
```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}}`](/docs/FormatSyntax/#property).

### Example: convert phone text to a `tel:` link {#example-convert-phone-text-to-a-tel-link}

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:

````
```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
description: Install QuickAdd manually from GitHub release files into your vault's plugins folder when the plugin browser is unavailable
slug: docs/ManualInstallation
---

1. Go to [Releases](https://github.com/chhoumann/quickadd/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
description: "Full reference for the QuickAdd API: input prompts, suggesters, file operations, AI prompts and agents, and choice execution"
slug: docs/QuickAddAPI
---

The QuickAdd API is a set of JavaScript methods you can call from
[macros](/docs/Choices/MacroChoice/), [user scripts](/docs/UserScripts/), and
[inline scripts](/docs/InlineScripts/). 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](#user-input-methods) | Ask for text, pick from a list, confirm, or collect several answers at once |
| [Choice execution](#choice-execution) | Run another choice, or apply a template to the current note |
| [Formatting](#formatting) | Run format syntax such as `{{DATE}}` or `{{VALUE:name}}` on a string |
| [Utility](#utility-module) | Read and set the clipboard, read the editor's selection |
| [Date](#date-module) | Format today, tomorrow, yesterday, or any day offset |
| [AI](#ai-module) | Send prompts, build tool-calling agents, get structured output |
| [Field suggestions](#field-suggestions-module) | 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](/docs/Advanced/APIOverview/) 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<Record<string, string>>`
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<string>`
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<string>`
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](/docs/FormatSyntax/#value-multiline).

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

### `yesNoPrompt(header: string, text?: string): Promise<boolean>`
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<void>`
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<any>`
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):
```
/* 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<string[]>`
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<string>`
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<void>`
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](/docs/VariablesDataFlow/#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: "john@example.com", company: "ACME" },
    { name: "Jane Smith", email: "jane@example.com", 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<TFile | null>`
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](/docs/ApplyTemplateToNote/) 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<string>`
Runs QuickAdd [format syntax](/docs/FormatSyntax/) 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<string>`
Gets the current clipboard contents.

**Example:**
```javascript
const clipboardText = await quickAddApi.utility.getClipboard();
console.log("Clipboard contains:", clipboardText);
```

### `setClipboard(text: string): Promise<void>`
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<object>`
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<object>`
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](#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<object>`
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:

```
/* 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<string[]>`
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

- [Macro Choices](/docs/Choices/MacroChoice/) - Using scripts in macros
- [Inline Scripts](/docs/InlineScripts/) - Using scripts in templates
- [Format Syntax](/docs/FormatSyntax/) - Template variables
- [Examples](/docs/Examples/Macro_BookFinder/) - Practical implementations

---

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

---
title: Settings
description: What every QuickAdd setting does and the effect of turning it on, from choices and input behavior to templates, AI, notifications, and choice icons
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](/docs/Choices/TemplateChoice/), [Capture Choices](/docs/Choices/CaptureChoice/), [Macro Choices](/docs/Choices/MacroChoice/), and [Multi Choices](/docs/Choices/MultiChoice/).
- **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](/docs/Choices/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](/docs/ControllingPrompts/#submit-keys).
- **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](/docs/Advanced/onePageInputs/#per-choice-override). See [One-page Inputs](/docs/Advanced/onePageInputs/) and [Controlling Prompts](/docs/ControllingPrompts/).

## 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](/docs/AIAssistant/).

## 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](#template-folders) 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)](/docs/TemplatePropertyTypes/).

### 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](/docs/GlobalVariables/).

### 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](/docs/Advanced/ObsidianUri/).

## Choice icons {#choice-icons}

- **Automatic choice icons** - QuickAdd gives each choice type a default [Lucide](https://lucide.dev) 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
description: "Type # for tags or [[ for files, headings, and blocks right inside QuickAdd prompts, with keyboard navigation to pick fast"
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 |
| --- | --- |
| [`#`](#tag-search) | Every tag in your vault |
| [`[[`](#file-search) | Every file in your vault |
| [`[[#`](#heading-search) | Every heading in your vault |
| [`[[#^`](#block-reference-search) | 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](/docs/ControllingPrompts/#one-form-instead-of-many-prompts).
Field and pick-list widgets keep their own inline suggestions. The
[math prompt](/docs/FormatSyntax/#mvalue) (`{{MVALUE}}`) suggests LaTeX
commands instead.
:::

### Search your tags: `#` {#tag-search}

Type `#` to search every tag in your vault, then pick one.

### Search your files: `[[` {#file-search}

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.

### Jump to a heading: `[[#` {#heading-search}

Type `[[#` to search every heading in your vault and link straight to it.

### Link a specific block: `[[#^` {#block-reference-search}

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)
description: Write proper Obsidian property types from template frontmatter variables, with an optional beta string-to-type conversion
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](/docs/Examples/Macro_MovieAndSeriesScript/) 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](/docs/FormatSyntax/)
gives you one directly, with no beta toggle required:

- [`{{VALUE:x|type:number}}` / `|type:slider` / `|type:checkbox` / `|type:text`](/docs/FormatSyntax/#value-property-types) - pick the right input widget; `|type:slider|min:0|max:100` is useful for bounded Number properties, and `|type:text` keeps a number-looking value (e.g. `0042`) a string.
- [`{{VALUE:a,b,c|multi}}`](/docs/FormatSyntax/#value-multi) - a multi-select picker that writes a real List (also `|multi:linklist` for link lists).
- [`{{VDATE:when,fmt|time}}`](/docs/FormatSyntax/#vdate-time) - a date **and time** picker for `Date & time` properties.
:::

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](#enabling-the-string-conversion-beta) 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`<br />`- 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.

<a id="complex-nested-structures"></a>
<a id="project-management"></a>

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 id="real-world-examples"></a>

## 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
description: "Write custom JavaScript for macros: script structure, configurable options, the params API, and file and metadata recipes"
slug: docs/UserScripts
---

A user script is a piece of JavaScript you write once and run from a
[Macro](/docs/Choices/MacroChoice/). 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](/docs/Advanced/ScriptingGuide/) for the
decision path, then use this page as the detailed reference.

:::

:::note[Obsidian API reference]
Scripts lean heavily on the [Obsidian API](https://docs.obsidian.md/Home). For
anything beyond the recipes here, keep the
[App](https://docs.obsidian.md/Reference/TypeScript+API/App),
[Vault](https://docs.obsidian.md/Reference/TypeScript+API/Vault), and
[Workspace](https://docs.obsidian.md/Reference/TypeScript+API/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](/docs/InlineScripts/) 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](/docs/InlineScripts/).

:::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](#configurable-options) 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:
- `app.vault` - File and folder operations ([Vault API](https://docs.obsidian.md/Reference/TypeScript+API/Vault))
- `app.workspace` - Window and pane management ([Workspace API](https://docs.obsidian.md/Reference/TypeScript+API/Workspace))
- `app.metadataCache` - File metadata and links ([MetadataCache API](https://docs.obsidian.md/Reference/TypeScript+API/MetadataCache))
- `app.fileManager` - File operations and renaming ([FileManager API](https://docs.obsidian.md/Reference/TypeScript+API/FileManager))

### 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"
    }
}
```

#### Dropdown: `type: "dropdown"` {#dropdownselect}

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`:

```
/* 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](/docs/QuickAddAPI/) through `params.quickAddApi`. For complete API documentation, see the [QuickAdd API Reference](/docs/QuickAddAPI/). 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](#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**](/docs/Settings/#advanced-notifications), 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](#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 `require`s 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:
- <a href="/scripts/userScriptExample.js" download>Complete Example with All Options</a> - Demonstrates all option types and patterns
- <a href="/scripts/movies.js" download>Movie Script</a> - Fetches movie data from OMDb API
- <a href="/scripts/citationsManager.js" download>Citations Manager</a> - Integrates with Obsidian Citations plugin
- [Book Finder](/docs/Examples/Macro_BookFinder/) - Searches for book information
- [Migrate Dataview Properties](/docs/Examples/Macro_MigrateDataviewProperties/) - Migrates inline dataview properties to YAML frontmatter with configurable settings

## Additional resources {#additional-resources}

### Obsidian API documentation {#obsidian-api-documentation}
- [Official Obsidian API Reference](https://docs.obsidian.md/Reference/TypeScript+API)
- [Plugin Development Guide](https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin)
- [Vault Module](https://docs.obsidian.md/Reference/TypeScript+API/Vault) - File operations
- [MetadataCache](https://docs.obsidian.md/Reference/TypeScript+API/MetadataCache) - File metadata and links
- [Workspace](https://docs.obsidian.md/Reference/TypeScript+API/Workspace) - Panes and leaves
- [Editor](https://docs.obsidian.md/Reference/TypeScript+API/Editor) - Text editing operations

### QuickAdd resources {#quickadd-resources}
- [QuickAdd API Reference](/docs/QuickAddAPI/)
- [Format Syntax Guide](/docs/FormatSyntax/)
- [Macro Choice Documentation](/docs/Choices/MacroChoice/)
- [Inline Scripts](/docs/InlineScripts/)

## 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](/docs/VariablesDataFlow/) 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](/docs/GlobalVariables/) instead. The exact placeholder
grammar lives in [Format Syntax](/docs/FormatSyntax/); 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>}}`](/docs/FormatSyntax/#value-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](/docs/Advanced/RaycastExtension/) 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**](/docs/Choices/CaptureChoice/#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"
```

### Suggest links and tags: `quickadd:suggest` {#quickaddsuggest}

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](/docs/Choices/Packages/) 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](/docs/Choices/Packages/#preview-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](/docs/AIAssistant/#add-a-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](https://github.com/chhoumann/quickadd/tree/master/skills/quickadd)
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/SKILL.md` into your agent's skills folder, such as
`.claude/skills/quickadd/` in your vault.

The skill needs QuickAdd 2.30.0 or later: it picks a choice by the `writes`
object that [`quickadd:list`](#quickaddlist) shows.

## Pass variables to a choice {#passing-variables}

QuickAdd's CLI accepts variables three ways:

1. `value-<name>=...` (the same form the URI uses)
2. extra `key=value` args
3. `vars=<json-object>` for structured values

```bash
obsidian vault=dev quickadd \
  choice="Daily log" \
  value-project="QuickAdd" \
  mood="focused"

obsidian vault=dev quickadd \
  choice="Daily log" \
  vars='{"project":"QuickAdd","sprint":42}'
```

Values are passed through exactly as provided. If a choice should ignore an
accidental leading or trailing space for a specific placeholder, use `|trim` in
that format string, for example `{{VALUE:project|trim}}`.

For [property captures](/docs/Choices/CaptureChoice/#property), `vars` preserves
native numbers, checkboxes, and lists when the Capture format is one whole token:

```bash
obsidian vault=dev quickadd choice="Add project tags" \
  vars='{"tags":["Research, writing","work"]}' verify=true
```

`verify=true` includes the engine's [confirmed outcome](#verified-and-effect),
so automation can distinguish a changed property from an unchanged capture.

For Template choices, passing `value` supplies the new note's name. It does not
select an existing note from the discovery picker. The generated path follows
the choice's file-exists behavior. To use **When selecting an existing note**,
run interactively and select the offered note in the discovery prompt.

### Names the CLI reserves {#reserved-flag-names}

The bare `key=value` form (pattern 2) ignores names that a command already uses
as flags or selectors: `choice`, `id`, `vars`, `ui`, `verify`, `date`,
`current` (on `quickadd` / `quickadd:run`), `fields` (on `quickadd:check`), and
`path` (on `quickadd:run-template`). If a choice has a variable named after one of these
(for example `{{VALUE:verify}}`), pass it with the `value-` prefix or via
`vars` instead - neither is ever treated as a flag:

```bash
obsidian vault=dev quickadd choice="My choice" value-verify="a value"
obsidian vault=dev quickadd choice="My choice" vars='{"verify":"a value"}'
```

## What happens when inputs are missing {#non-interactive-behavior}

By default, `quickadd` and `quickadd:run` are non-interactive. If QuickAdd finds
missing inputs, it returns a JSON payload with `missing` fields and
`missingFlags` suggestions instead of opening prompts.

Pass a returned `missingFlags` entry back exactly as shown. Some generated flags
fill internal runtime selections, such as a preselected capture target file.

Add `ui` to allow interactive prompts:

```bash
obsidian vault=dev quickadd choice="Daily log" ui
```

In a [scheduled job](/docs/Advanced/TriggerQuickAddFromOutsideObsidian/#run-quickadd-on-a-schedule),
only add `ui` when the job runs while you are logged in and able to answer the
prompts.

## Knowing whether anything actually landed {#verified-and-effect}

`ok:true` means the choice ran without aborting. It does **not** mean your vault
changed. Two more keys answer the questions an automation actually asks:

| Key | Question it answers | Values |
| --- | --- | --- |
| `verified` | Did QuickAdd confirm what the engine did? | `true` on the outcome path (`verify` on a Template/Capture choice), `false` when it could not look |
| `effect` | What did the run do to the vault? | `created`, `changed`, `unchanged`, `unknown` |

```bash
obsidian vault=dev quickadd:run choice="Inbox" value-value="  " verify=true
# -> {"ok":true,"choice":{…},"file":"Inbox.md","verified":true,"effect":"unchanged","durationMs":6}
```

That run is working exactly as designed: the capture's payload was empty, so
QuickAdd deliberately left `Inbox.md` alone rather than writing a blank line, and
said so in a notice. A Template set to **Keep existing file** when the file
already exists reports the same `effect`. If you are counting captures, writing an
idempotency marker, or deciding whether to retry, key off `effect`, not `ok`.

`effect` is present on every **success** payload (`ok:true`), and `unknown` is stated
rather than omitted - a missing key reads as `false` in both `jq` and JavaScript, which
would turn "QuickAdd did not look" into "nothing happened". A failed or cancelled run
carries `error` instead and no `effect`, because there is no outcome to describe.
`verified:false` still means only *"not confirmed - go look"*; it never means
*"confirmed that nothing changed"*.

The `obsidian://quickadd` [x-callback](/docs/Advanced/TriggerQuickAddFromOutsideObsidian/)
success callback carries the same `effect` value.

## Answer run-time prompts from outside: `quickadd:interactive` {#interactive-runs-quickaddinteractive}

Some choices prompt at *run time* for inputs that can't be gathered up front -
a macro's `quickAddApi.suggester` over data it just fetched, an `inputPrompt`,
`yesNoPrompt`, `checkboxPrompt`, and so on. `quickadd:interactive` runs a choice
and **forwards those prompts to you over a local HTTP bridge**, so an external
front end (Raycast, a script) can render them and send back answers, instead of
the prompts opening in Obsidian.

```bash
obsidian vault=dev quickadd:interactive choice="Import from Readwise"
# -> {"ok":true,"host":"127.0.0.1","port":51789,"sessionId":"…","token":"…","capabilities":["abort","outcome-effect"]}
```

The command returns connection details immediately and runs the choice in the
background. Attach to the session and drive it:

- `GET  http://127.0.0.1:<port>/poll?session=<id>&token=<token>` - long-polls for the next event: `{"kind":"prompt","requestId":…,"prompt":{…}}`, `{"kind":"done","result":…}`, `{"kind":"error","error":…}`, or a periodic `{"kind":"idle"}` keepalive (just poll again).
- `POST http://127.0.0.1:<port>/reply?session=<id>&token=<token>` with body `{"requestId":…,"value":…}` to answer, or `{"requestId":…,"cancelled":true}` to cancel (which ends the run - except on an `info` panel, see below).
- `POST http://127.0.0.1:<port>/abort?session=<id>&token=<token>` - end the run. Answers `{"ok":true,"interrupted":<n>}`, where `n` is how many pending prompts it rejected; `409` if the run had already finished (benign - poll for the terminal event); `404` for an unknown session or token, or for any method other than `POST`.

Prompts a Template or Capture run opens itself - the "file already exists" chooser,
the folder picker, the note-discovery picker, the heading picker, the capture-target
picker - are forwarded like any other. (The AI assistant's tool-confirmation dialog is
the one that is not: run such a choice at the desktop, or set tool confirmation to
"never".)

They arrive as `suggester` prompts, and because the engine controls the
list, a reply that is not one of the offered `value` tokens is refused rather than
acted on (unless the prompt sets `allowCustomInput`, as the folder and discovery
pickers do so you can create something new).

Prompt `type`s and the `value` you reply with: `suggester`/`input`/`date` →
string, `confirm` → boolean, `checkbox` → string array, `info` →
acknowledgement, `form` → an object mapping each field's `id` to its value.
Ordinary and date fields use strings (dates use the `@date:ISO` format), while
multi-select fields use string arrays. Use the array form for multi-selects so
values containing commas remain unambiguous.

A `form` field that picks notes (a Capture target, a `{{FILE:...}}` field) has
`"type":"suggester"` like any other pick list, plus `"picker":"file"` (QuickAdd
2.31.0 and later); other fields omit `picker`. QuickAdd's own form starts a note
picker empty unless the field has a `defaultValue`, and does not submit while a
required single-note picker (not `optional`, not `multiSelect`) has no pick.
Other pick lists have no such rule.

The run's outcome arrives as the
`done`/`error` poll event: `done` carries the same `verified` and `effect` keys
described under [Knowing whether anything actually landed](#verified-and-effect).

### Cancelling, and ending a run

`{"cancelled":true}` is how you say *the user dismissed this prompt*. It ends the
run exactly as pressing Escape on the in-app dialog does.

`info` is the exception, because the in-app dialog is: `GenericInfoDialog` resolves
on every close path and has no way to abort anything, so the same choice run in
Obsidian continues past the panel. Escape is the only gesture an info panel affords,
so cancelling one just closes it and the run carries on - matching the app.

To end a run deliberately, `POST /abort`. It rejects whatever the run is blocked on
and makes its next prompt fail too, so the run unwinds and delivers its **real**
outcome - usually `{"kind":"error","error":"Input cancelled by user"}`, but `done` if
it had nothing left to interrupt and simply finished. `/abort` never fabricates a
terminal event; keep polling until one arrives. The `interrupted` count tells you
whether it stopped anything.

:::caution[What `/abort` cannot reach]
`/abort` interrupts prompts that were routed **to you**. A run that is mid-work
between prompts keeps going, so `"interrupted":0` means nothing was waiting on you and
the run may still finish and commit its side effects. Keep polling for the terminal
event either way.
:::

### When a reply is rejected

`/reply` answers `400` and leaves the prompt **pending** when it cannot honour
what you sent, so you can correct the reply and POST again. Two cases:

- `cancelled` is present but is not a boolean (`"true"`, `1`, `"no"`). QuickAdd
  will neither abort on a flag it does not recognise nor quietly answer the prompt
  on the user's behalf, so it asks you to fix the flag. Use the literal `true`;
  `false` and omitting it both mean "this is a real answer".
- a `confirm` reply whose `value` is not `true`/`false`. The user never answered,
  and QuickAdd will not invent a "No" for them.

Every other prompt type accepts whatever you send, including an empty answer:
`""` and `[]` are things a user genuinely submits in the app (the Skip buttons,
optional fields), so they must stay legal here too.

A `409` from `/reply` means nothing was waiting on that `requestId`.

Good to know:

- **Desktop only.** The bridge binds to `127.0.0.1`, is gated by the per-session `token`, rejects browser (`Origin`/`Referer`) and non-loopback `Host` requests, and the server is ephemeral - it starts on the first session and stops when the last one ends.
- **Concurrency.** Each run gets its own `sessionId` + `token`; many can run at once without interfering.
- If no client attaches within ~30s the run is aborted so a prompt can't hang forever.

---

--- METRICS ---
- Files Extracted: 74
- Estimated Token Budget: ~127639 tokens
- Canonical Reference: https://codewiki.google/github.com/chhoumann/quickadd
