aboutsummaryrefslogtreecommitdiffstats
path: root/.agents/skills/obsidian-bases
diff options
context:
space:
mode:
Diffstat (limited to '.agents/skills/obsidian-bases')
-rw-r--r--.agents/skills/obsidian-bases/SKILL.md497
-rw-r--r--.agents/skills/obsidian-bases/references/FUNCTIONS_REFERENCE.md173
2 files changed, 670 insertions, 0 deletions
diff --git a/.agents/skills/obsidian-bases/SKILL.md b/.agents/skills/obsidian-bases/SKILL.md
new file mode 100644
index 0000000..7e84aa4
--- /dev/null
+++ b/.agents/skills/obsidian-bases/SKILL.md
@@ -0,0 +1,497 @@
+---
+name: obsidian-bases
+description: Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
+---
+
+# Obsidian Bases Skill
+
+## Workflow
+
+1. **Create the file**: Create a `.base` file in the vault with valid YAML content
+2. **Define scope**: Add `filters` to select which notes appear (by tag, folder, property, or date)
+3. **Add formulas** (optional): Define computed properties in the `formulas` section
+4. **Configure views**: Add one or more views (`table`, `cards`, `list`, or `map`) with `order` specifying which properties to display
+5. **Validate**: Verify the file is valid YAML with no syntax errors. Check that all referenced properties and formulas exist. Common issues: unquoted strings containing special YAML characters, mismatched quotes in formula expressions, referencing `formula.X` without defining `X` in `formulas`
+6. **Test in Obsidian**: Open the `.base` file in Obsidian to confirm the view renders correctly. If it shows a YAML error, check quoting rules below
+
+## Schema
+
+Base files use the `.base` extension and contain valid YAML.
+
+```yaml
+# Global filters apply to ALL views in the base
+filters:
+ # Can be a single filter string
+ # OR a recursive filter object with and/or/not
+ and: []
+ or: []
+ not: []
+
+# Define formula properties that can be used across all views
+formulas:
+ formula_name: 'expression'
+
+# Configure display names and settings for properties
+properties:
+ property_name:
+ displayName: "Display Name"
+ formula.formula_name:
+ displayName: "Formula Display Name"
+ file.ext:
+ displayName: "Extension"
+
+# Define custom summary formulas
+summaries:
+ custom_summary_name: 'values.mean().round(3)'
+
+# Define one or more views
+views:
+ - type: table | cards | list | map
+ name: "View Name"
+ limit: 10 # Optional: limit results
+ groupBy: # Optional: group results
+ property: property_name
+ direction: ASC | DESC
+ filters: # View-specific filters
+ and: []
+ order: # Properties to display in order
+ - file.name
+ - property_name
+ - formula.formula_name
+ summaries: # Map properties to summary formulas
+ property_name: Average
+```
+
+## Filter Syntax
+
+Filters narrow down results. They can be applied globally or per-view.
+
+### Filter Structure
+
+```yaml
+# Single filter
+filters: 'status == "done"'
+
+# AND - all conditions must be true
+filters:
+ and:
+ - 'status == "done"'
+ - 'priority > 3'
+
+# OR - any condition can be true
+filters:
+ or:
+ - 'file.hasTag("book")'
+ - 'file.hasTag("article")'
+
+# NOT - exclude matching items
+filters:
+ not:
+ - 'file.hasTag("archived")'
+
+# Nested filters
+filters:
+ or:
+ - file.hasTag("tag")
+ - and:
+ - file.hasTag("book")
+ - file.hasLink("Textbook")
+ - not:
+ - file.hasTag("book")
+ - file.inFolder("Required Reading")
+```
+
+### Filter Operators
+
+| Operator | Description |
+|----------|-------------|
+| `==` | equals |
+| `!=` | not equal |
+| `>` | greater than |
+| `<` | less than |
+| `>=` | greater than or equal |
+| `<=` | less than or equal |
+| `&&` | logical and |
+| `\|\|` | logical or |
+| <code>!</code> | logical not |
+
+## Properties
+
+### Three Types of Properties
+
+1. **Note properties** - From frontmatter: `note.author` or just `author`
+2. **File properties** - File metadata: `file.name`, `file.mtime`, etc.
+3. **Formula properties** - Computed values: `formula.my_formula`
+
+### File Properties Reference
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `file.name` | String | File name |
+| `file.basename` | String | File name without extension |
+| `file.path` | String | Full path to file |
+| `file.folder` | String | Parent folder path |
+| `file.ext` | String | File extension |
+| `file.size` | Number | File size in bytes |
+| `file.ctime` | Date | Created time |
+| `file.mtime` | Date | Modified time |
+| `file.tags` | List | All tags in file |
+| `file.links` | List | Internal links in file |
+| `file.backlinks` | List | Files linking to this file |
+| `file.embeds` | List | Embeds in the note |
+| `file.properties` | Object | All frontmatter properties |
+
+### The `this` Keyword
+
+- In main content area: refers to the base file itself
+- When embedded: refers to the embedding file
+- In sidebar: refers to the active file in main content
+
+## Formula Syntax
+
+Formulas compute values from properties. Defined in the `formulas` section.
+
+```yaml
+formulas:
+ # Simple arithmetic
+ total: "price * quantity"
+
+ # Conditional logic
+ status_icon: 'if(done, "✅", "⏳")'
+
+ # String formatting
+ formatted_price: 'if(price, price.toFixed(2) + " dollars")'
+
+ # Date formatting
+ created: 'file.ctime.format("YYYY-MM-DD")'
+
+ # Calculate days since created (use .days for Duration)
+ days_old: '(now() - file.ctime).days'
+
+ # Calculate days until due date
+ days_until_due: 'if(due_date, (date(due_date) - today()).days, "")'
+```
+
+## Key Functions
+
+Most commonly used functions. For the complete reference of all types (Date, String, Number, List, File, Link, Object, RegExp), see [FUNCTIONS_REFERENCE.md](references/FUNCTIONS_REFERENCE.md).
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `date()` | `date(string): date` | Parse string to date (`YYYY-MM-DD HH:mm:ss`) |
+| `now()` | `now(): date` | Current date and time |
+| `today()` | `today(): date` | Current date (time = 00:00:00) |
+| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
+| `duration()` | `duration(string): duration` | Parse duration string |
+| `file()` | `file(path): file` | Get file object |
+| `link()` | `link(path, display?): Link` | Create a link |
+
+### Duration Type
+
+When subtracting two dates, the result is a **Duration** type (not a number).
+
+**Duration Fields:** `duration.days`, `duration.hours`, `duration.minutes`, `duration.seconds`, `duration.milliseconds`
+
+**IMPORTANT:** Duration does NOT support `.round()`, `.floor()`, `.ceil()` directly. Access a numeric field first (like `.days`), then apply number functions.
+
+```yaml
+# CORRECT: Calculate days between dates
+"(date(due_date) - today()).days" # Returns number of days
+"(now() - file.ctime).days" # Days since created
+"(date(due_date) - today()).days.round(0)" # Rounded days
+
+# WRONG - will cause error:
+# "((date(due) - today()) / 86400000).round(0)" # Duration doesn't support division then round
+```
+
+### Date Arithmetic
+
+```yaml
+# Duration units: y/year/years, M/month/months, d/day/days,
+# w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds
+"now() + \"1 day\"" # Tomorrow
+"today() + \"7d\"" # A week from today
+"now() - file.ctime" # Returns Duration
+"(now() - file.ctime).days" # Get days as number
+```
+
+## View Types
+
+### Table View
+
+```yaml
+views:
+ - type: table
+ name: "My Table"
+ order:
+ - file.name
+ - status
+ - due_date
+ summaries:
+ price: Sum
+ count: Average
+```
+
+### Cards View
+
+```yaml
+views:
+ - type: cards
+ name: "Gallery"
+ order:
+ - file.name
+ - cover_image
+ - description
+```
+
+### List View
+
+```yaml
+views:
+ - type: list
+ name: "Simple List"
+ order:
+ - file.name
+ - status
+```
+
+### Map View
+
+Requires latitude/longitude properties and the Maps community plugin.
+
+```yaml
+views:
+ - type: map
+ name: "Locations"
+ # Map-specific settings for lat/lng properties
+```
+
+## Default Summary Formulas
+
+| Name | Input Type | Description |
+|------|------------|-------------|
+| `Average` | Number | Mathematical mean |
+| `Min` | Number | Smallest number |
+| `Max` | Number | Largest number |
+| `Sum` | Number | Sum of all numbers |
+| `Range` | Number | Max - Min |
+| `Median` | Number | Mathematical median |
+| `Stddev` | Number | Standard deviation |
+| `Earliest` | Date | Earliest date |
+| `Latest` | Date | Latest date |
+| `Range` | Date | Latest - Earliest |
+| `Checked` | Boolean | Count of true values |
+| `Unchecked` | Boolean | Count of false values |
+| `Empty` | Any | Count of empty values |
+| `Filled` | Any | Count of non-empty values |
+| `Unique` | Any | Count of unique values |
+
+## Complete Examples
+
+### Task Tracker Base
+
+```yaml
+filters:
+ and:
+ - file.hasTag("task")
+ - 'file.ext == "md"'
+
+formulas:
+ days_until_due: 'if(due, (date(due) - today()).days, "")'
+ is_overdue: 'if(due, date(due) < today() && status != "done", false)'
+ priority_label: 'if(priority == 1, "🔴 High", if(priority == 2, "🟡 Medium", "🟢 Low"))'
+
+properties:
+ status:
+ displayName: Status
+ formula.days_until_due:
+ displayName: "Days Until Due"
+ formula.priority_label:
+ displayName: Priority
+
+views:
+ - type: table
+ name: "Active Tasks"
+ filters:
+ and:
+ - 'status != "done"'
+ order:
+ - file.name
+ - status
+ - formula.priority_label
+ - due
+ - formula.days_until_due
+ groupBy:
+ property: status
+ direction: ASC
+ summaries:
+ formula.days_until_due: Average
+
+ - type: table
+ name: "Completed"
+ filters:
+ and:
+ - 'status == "done"'
+ order:
+ - file.name
+ - completed_date
+```
+
+### Reading List Base
+
+```yaml
+filters:
+ or:
+ - file.hasTag("book")
+ - file.hasTag("article")
+
+formulas:
+ reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
+ status_icon: 'if(status == "reading", "📖", if(status == "done", "✅", "📚"))'
+ year_read: 'if(finished_date, date(finished_date).year, "")'
+
+properties:
+ author:
+ displayName: Author
+ formula.status_icon:
+ displayName: ""
+ formula.reading_time:
+ displayName: "Est. Time"
+
+views:
+ - type: cards
+ name: "Library"
+ order:
+ - cover
+ - file.name
+ - author
+ - formula.status_icon
+ filters:
+ not:
+ - 'status == "dropped"'
+
+ - type: table
+ name: "Reading List"
+ filters:
+ and:
+ - 'status == "to-read"'
+ order:
+ - file.name
+ - author
+ - pages
+ - formula.reading_time
+```
+
+### Daily Notes Index
+
+```yaml
+filters:
+ and:
+ - file.inFolder("Daily Notes")
+ - '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)'
+
+formulas:
+ word_estimate: '(file.size / 5).round(0)'
+ day_of_week: 'date(file.basename).format("dddd")'
+
+properties:
+ formula.day_of_week:
+ displayName: "Day"
+ formula.word_estimate:
+ displayName: "~Words"
+
+views:
+ - type: table
+ name: "Recent Notes"
+ limit: 30
+ order:
+ - file.name
+ - formula.day_of_week
+ - formula.word_estimate
+ - file.mtime
+```
+
+## Embedding Bases
+
+Embed in Markdown files:
+
+```markdown
+![[MyBase.base]]
+
+<!-- Specific view -->
+![[MyBase.base#View Name]]
+```
+
+## YAML Quoting Rules
+
+- Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
+- Use double quotes for simple strings: `"My View Name"`
+- Escape nested quotes properly in complex expressions
+
+## Troubleshooting
+
+### YAML Syntax Errors
+
+**Unquoted special characters**: Strings containing `:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` `` must be quoted.
+
+```yaml
+# WRONG - colon in unquoted string
+displayName: Status: Active
+
+# CORRECT
+displayName: "Status: Active"
+```
+
+**Mismatched quotes in formulas**: When a formula contains double quotes, wrap the entire formula in single quotes.
+
+```yaml
+# WRONG - double quotes inside double quotes
+formulas:
+ label: "if(done, "Yes", "No")"
+
+# CORRECT - single quotes wrapping double quotes
+formulas:
+ label: 'if(done, "Yes", "No")'
+```
+
+### Common Formula Errors
+
+**Duration math without field access**: Subtracting dates returns a Duration, not a number. Always access `.days`, `.hours`, etc.
+
+```yaml
+# WRONG - Duration is not a number
+"(now() - file.ctime).round(0)"
+
+# CORRECT - access .days first, then round
+"(now() - file.ctime).days.round(0)"
+```
+
+**Missing null checks**: Properties may not exist on all notes. Use `if()` to guard.
+
+```yaml
+# WRONG - crashes if due_date is empty
+"(date(due_date) - today()).days"
+
+# CORRECT - guard with if()
+'if(due_date, (date(due_date) - today()).days, "")'
+```
+
+**Referencing undefined formulas**: Ensure every `formula.X` in `order` or `properties` has a matching entry in `formulas`.
+
+```yaml
+# This will fail silently if 'total' is not defined in formulas
+order:
+ - formula.total
+
+# Fix: define it
+formulas:
+ total: "price * quantity"
+```
+
+## References
+
+- [Bases Syntax](https://help.obsidian.md/bases/syntax)
+- [Functions](https://help.obsidian.md/bases/functions)
+- [Views](https://help.obsidian.md/bases/views)
+- [Formulas](https://help.obsidian.md/formulas)
+- [Complete Functions Reference](references/FUNCTIONS_REFERENCE.md)
diff --git a/.agents/skills/obsidian-bases/references/FUNCTIONS_REFERENCE.md b/.agents/skills/obsidian-bases/references/FUNCTIONS_REFERENCE.md
new file mode 100644
index 0000000..047888d
--- /dev/null
+++ b/.agents/skills/obsidian-bases/references/FUNCTIONS_REFERENCE.md
@@ -0,0 +1,173 @@
+# Functions Reference
+
+## Global Functions
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `date()` | `date(string): date` | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
+| `duration()` | `duration(string): duration` | Parse duration string |
+| `now()` | `now(): date` | Current date and time |
+| `today()` | `today(): date` | Current date (time = 00:00:00) |
+| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
+| `min()` | `min(n1, n2, ...): number` | Smallest number |
+| `max()` | `max(n1, n2, ...): number` | Largest number |
+| `number()` | `number(any): number` | Convert to number |
+| `link()` | `link(path, display?): Link` | Create a link |
+| `list()` | `list(element): List` | Wrap in list if not already |
+| `file()` | `file(path): file` | Get file object |
+| `image()` | `image(path): image` | Create image for rendering |
+| `icon()` | `icon(name): icon` | Lucide icon by name |
+| `html()` | `html(string): html` | Render as HTML |
+| `escapeHTML()` | `escapeHTML(string): string` | Escape HTML characters |
+
+## Any Type Functions
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `isTruthy()` | `any.isTruthy(): boolean` | Coerce to boolean |
+| `isType()` | `any.isType(type): boolean` | Check type |
+| `toString()` | `any.toString(): string` | Convert to string |
+
+## Date Functions & Fields
+
+**Fields:** `date.year`, `date.month`, `date.day`, `date.hour`, `date.minute`, `date.second`, `date.millisecond`
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `date()` | `date.date(): date` | Remove time portion |
+| `format()` | `date.format(string): string` | Format with Moment.js pattern |
+| `time()` | `date.time(): string` | Get time as string |
+| `relative()` | `date.relative(): string` | Human-readable relative time |
+| `isEmpty()` | `date.isEmpty(): boolean` | Always false for dates |
+
+## Duration Type
+
+When subtracting two dates, the result is a **Duration** type (not a number). Duration has its own properties and methods.
+
+**Duration Fields:**
+| Field | Type | Description |
+|-------|------|-------------|
+| `duration.days` | Number | Total days in duration |
+| `duration.hours` | Number | Total hours in duration |
+| `duration.minutes` | Number | Total minutes in duration |
+| `duration.seconds` | Number | Total seconds in duration |
+| `duration.milliseconds` | Number | Total milliseconds in duration |
+
+**IMPORTANT:** Duration does NOT support `.round()`, `.floor()`, `.ceil()` directly. You must access a numeric field first (like `.days`), then apply number functions.
+
+```yaml
+# CORRECT: Calculate days between dates
+"(date(due_date) - today()).days" # Returns number of days
+"(now() - file.ctime).days" # Days since created
+
+# CORRECT: Round the numeric result if needed
+"(date(due_date) - today()).days.round(0)" # Rounded days
+"(now() - file.ctime).hours.round(0)" # Rounded hours
+
+# WRONG - will cause error:
+# "((date(due) - today()) / 86400000).round(0)" # Duration doesn't support division then round
+```
+
+## Date Arithmetic
+
+```yaml
+# Duration units: y/year/years, M/month/months, d/day/days,
+# w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds
+
+# Add/subtract durations
+"date + \"1M\"" # Add 1 month
+"date - \"2h\"" # Subtract 2 hours
+"now() + \"1 day\"" # Tomorrow
+"today() + \"7d\"" # A week from today
+
+# Subtract dates returns Duration type
+"now() - file.ctime" # Returns Duration
+"(now() - file.ctime).days" # Get days as number
+"(now() - file.ctime).hours" # Get hours as number
+
+# Complex duration arithmetic
+"now() + (duration('1d') * 2)"
+```
+
+## String Functions
+
+**Field:** `string.length`
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `contains()` | `string.contains(value): boolean` | Check substring |
+| `containsAll()` | `string.containsAll(...values): boolean` | All substrings present |
+| `containsAny()` | `string.containsAny(...values): boolean` | Any substring present |
+| `startsWith()` | `string.startsWith(query): boolean` | Starts with query |
+| `endsWith()` | `string.endsWith(query): boolean` | Ends with query |
+| `isEmpty()` | `string.isEmpty(): boolean` | Empty or not present |
+| `lower()` | `string.lower(): string` | To lowercase |
+| `title()` | `string.title(): string` | To Title Case |
+| `trim()` | `string.trim(): string` | Remove whitespace |
+| `replace()` | `string.replace(pattern, replacement): string` | Replace pattern |
+| `repeat()` | `string.repeat(count): string` | Repeat string |
+| `reverse()` | `string.reverse(): string` | Reverse string |
+| `slice()` | `string.slice(start, end?): string` | Substring |
+| `split()` | `string.split(separator, n?): list` | Split to list |
+
+## Number Functions
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `abs()` | `number.abs(): number` | Absolute value |
+| `ceil()` | `number.ceil(): number` | Round up |
+| `floor()` | `number.floor(): number` | Round down |
+| `round()` | `number.round(digits?): number` | Round to digits |
+| `toFixed()` | `number.toFixed(precision): string` | Fixed-point notation |
+| `isEmpty()` | `number.isEmpty(): boolean` | Not present |
+
+## List Functions
+
+**Field:** `list.length`
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `contains()` | `list.contains(value): boolean` | Element exists |
+| `containsAll()` | `list.containsAll(...values): boolean` | All elements exist |
+| `containsAny()` | `list.containsAny(...values): boolean` | Any element exists |
+| `filter()` | `list.filter(expression): list` | Filter by condition (uses `value`, `index`) |
+| `map()` | `list.map(expression): list` | Transform elements (uses `value`, `index`) |
+| `reduce()` | `list.reduce(expression, initial): any` | Reduce to single value (uses `value`, `index`, `acc`) |
+| `flat()` | `list.flat(): list` | Flatten nested lists |
+| `join()` | `list.join(separator): string` | Join to string |
+| `reverse()` | `list.reverse(): list` | Reverse order |
+| `slice()` | `list.slice(start, end?): list` | Sublist |
+| `sort()` | `list.sort(): list` | Sort ascending |
+| `unique()` | `list.unique(): list` | Remove duplicates |
+| `isEmpty()` | `list.isEmpty(): boolean` | No elements |
+
+## File Functions
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `asLink()` | `file.asLink(display?): Link` | Convert to link |
+| `hasLink()` | `file.hasLink(otherFile): boolean` | Has link to file |
+| `hasTag()` | `file.hasTag(...tags): boolean` | Has any of the tags |
+| `hasProperty()` | `file.hasProperty(name): boolean` | Has property |
+| `inFolder()` | `file.inFolder(folder): boolean` | In folder or subfolder |
+
+## Link Functions
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `asFile()` | `link.asFile(): file` | Get file object |
+| `linksTo()` | `link.linksTo(file): boolean` | Links to file |
+
+## Object Functions
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `isEmpty()` | `object.isEmpty(): boolean` | No properties |
+| `keys()` | `object.keys(): list` | List of keys |
+| `values()` | `object.values(): list` | List of values |
+
+## Regular Expression Functions
+
+| Function | Signature | Description |
+|----------|-----------|-------------|
+| `matches()` | `regexp.matches(string): boolean` | Test if matches |