Updating openapi and creating periodic tools

This commit is contained in:
Jevin Maltais
2025-03-22 07:59:36 -04:00
parent e6ef6a1e6d
commit 4c9cea7f30
4 changed files with 749 additions and 15 deletions

View File

@@ -149,7 +149,7 @@ paths:
- "Active File"
patch:
description: |
Inserts content into the currently-open note relative to a heading within that note.
Inserts content into the currently-open note relative to a heading, block refeerence, or frontmatter field within that document.
Allows you to modify the content relative to a heading, block reference, or frontmatter field in your document.
@@ -275,7 +275,7 @@ paths:
You may find using a `Content-Type` of `application/json` to be
particularly useful in the case of frontmatter since frontmatter
fields' values are JSON data, and the API can be smarter about
interpreting your `prepend` or `append` requests if you specify
interpreting yoru `prepend` or `append` requests if you specify
your data as JSON (particularly when appending, for example,
list items).
parameters:
@@ -362,7 +362,7 @@ paths:
description: |
Your path references a directory instead of a file; this request method is valid only for updating files.
summary: |
Insert content into the currently open note in Obsidian relative to a heading within that document.
Insert content into the currently open note in Obsidian relative to a heading, block reference, or frontmatter field within that document.
tags:
- "Active File"
post:
@@ -403,6 +403,7 @@ paths:
tags:
- "Active File"
put:
parameters: []
requestBody:
content:
"*/*":
@@ -593,7 +594,7 @@ paths:
- "Periodic Notes"
patch:
description: |
Inserts content into an existing note relative to a heading within your note.
Inserts content into a periodic note relative to a heading, block refeerence, or frontmatter field within that document.
Allows you to modify the content relative to a heading, block reference, or frontmatter field in your document.
@@ -719,7 +720,7 @@ paths:
You may find using a `Content-Type` of `application/json` to be
particularly useful in the case of frontmatter since frontmatter
fields' values are JSON data, and the API can be smarter about
interpreting your `prepend` or `append` requests if you specify
interpreting yoru `prepend` or `append` requests if you specify
your data as JSON (particularly when appending, for example,
list items).
parameters:
@@ -819,7 +820,7 @@ paths:
description: |
Your path references a directory instead of a file; this request method is valid only for updating files.
summary: |
Insert content into a periodic note relative to a heading within that document.
Insert content into a periodic note in Obsidian relative to a heading, block reference, or frontmatter field within that document.
tags:
- "Periodic Notes"
post:
@@ -920,6 +921,498 @@ paths:
Update the content of a periodic note.
tags:
- "Periodic Notes"
"/periodic/{year}/{month}/{day}/{period}/":
delete:
description: |
Deletes the periodic note for the specified period.
parameters:
- description: "The year of the date for which you would like to grab a periodic note."
in: "path"
name: "year"
required: true
schema:
type: "number"
- description: "The month (1-12) of the date for which you would like to grab a periodic note."
in: "path"
name: "month"
required: true
schema:
type: "number"
- description: "The day (1-31) of the date for which you would like to grab a periodic note."
in: "path"
name: "day"
required: true
schema:
type: "number"
- description: "The name of the period for which you would like to grab the current note."
in: "path"
name: "period"
required: true
schema:
default: "daily"
enum:
- "daily"
- "weekly"
- "monthly"
- "quarterly"
- "yearly"
type: "string"
responses:
"204":
description: "Success"
"404":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: "File does not exist."
"405":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: |
Your path references a directory instead of a file; this request method is valid only for updating files.
summary: |
Delete a periodic note.
tags:
- "Periodic Notes"
get:
parameters:
- description: "The year of the date for which you would like to grab a periodic note."
in: "path"
name: "year"
required: true
schema:
type: "number"
- description: "The month (1-12) of the date for which you would like to grab a periodic note."
in: "path"
name: "month"
required: true
schema:
type: "number"
- description: "The day (1-31) of the date for which you would like to grab a periodic note."
in: "path"
name: "day"
required: true
schema:
type: "number"
- description: "The name of the period for which you would like to grab the current note."
in: "path"
name: "period"
required: true
schema:
default: "daily"
enum:
- "daily"
- "weekly"
- "monthly"
- "quarterly"
- "yearly"
type: "string"
responses:
"200":
content:
"application/vnd.olrapi.note+json":
schema:
"$ref": "#/components/schemas/NoteJson"
text/markdown:
schema:
example: |
# This is my document
something else here
type: "string"
description: "Success"
"404":
description: "File does not exist"
summary: |
Get current periodic note for the specified period.
tags:
- "Periodic Notes"
patch:
description: |
Inserts content into a periodic note relative to a heading, block refeerence, or frontmatter field within that document.
Allows you to modify the content relative to a heading, block reference, or frontmatter field in your document.
Note that this API was changed in Version 3.0 of this extension and the earlier PATCH API is now deprecated. Requests made using the previous version of this API will continue to work until Version 4.0 is released. See https://github.com/coddingtonbear/obsidian-local-rest-api/wiki/Changes-to-PATCH-requests-between-versions-2.0-and-3.0 for more details and migration instructions.
# Examples
All of the below examples assume you have a document that looks like
this:
```markdown
---
alpha: 1
beta: test
delta:
zeta: 1
yotta: 1
gamma:
- one
- two
---
# Heading 1
This is the content for heading one
Also references some [[#^484ef2]]
## Subheading 1:1
Content for Subheading 1:1
### Subsubheading 1:1:1
### Subsubheading 1:1:2
Testing how block references work for a table.[[#^2c7cfa]]
Some content for Subsubheading 1:1:2
More random text.
^2d9b4a
## Subheading 1:2
Content for Subheading 1:2.
some content with a block reference ^484ef2
## Subheading 1:3
| City | Population |
| ------------ | ---------- |
| Seattle, WA | 8 |
| Portland, OR | 4 |
^2c7cfa
```
## Append Content Below a Heading
If you wanted to append the content "Hello" below "Subheading 1:1:1" under "Heading 1",
you could send a request with the following headers:
- `Operation`: `append`
- `Target-Type`: `heading`
- `Target`: `Heading 1::Subheading 1:1:1`
- with the request body: `Hello`
The above would work just fine for `prepend` or `replace`, too, of course,
but with different results.
## Append Content to a Block Reference
If you wanted to append the content "Hello" below the block referenced by
"2d9b4a" above ("More random text."), you could send the following headers:
- `Operation`: `append`
- `Target-Type`: `block`
- `Target`: `2d9b4a`
- with the request body: `Hello`
The above would work just fine for `prepend` or `replace`, too, of course,
but with different results.
## Add a Row to a Table Referenced by a Block Reference
If you wanted to add a new city ("Chicago, IL") and population ("16") pair to the table above
referenced by the block reference `2c7cfa`, you could send the following
headers:
- `Operation`: `append`
- `TargetType`: `block`
- `Target`: `2c7cfa`
- `Content-Type`: `application/json`
- with the request body: `[["Chicago, IL", "16"]]`
The use of a `Content-Type` of `application/json` allows the API
to infer that member of your array represents rows and columns of your
to append to the referenced table. You can of course just use a
`Content-Type` of `text/markdown`, but in such a case you'll have to
format your table row manually instead of letting the library figure
it out for you.
You also have the option of using `prepend` (in which case, your new
row would be the first -- right below the table heading) or `replace` (in which
case all rows except the table heading would be replaced by the new row(s)
you supplied).
## Setting a Frontmatter Field
If you wanted to set the frontmatter field `alpha` to `2`, you could
send the following headers:
- `Operation`: `replace`
- `TargetType`: `frontmatter`
- `Target`: `beep`
- with the request body `2`
If you're setting a frontmatter field that might not already exist
you may want to use the `Create-Target-If-Missing` header so the
new frontmatter field is created and set to your specified value
if it doesn't already exist.
You may find using a `Content-Type` of `application/json` to be
particularly useful in the case of frontmatter since frontmatter
fields' values are JSON data, and the API can be smarter about
interpreting yoru `prepend` or `append` requests if you specify
your data as JSON (particularly when appending, for example,
list items).
parameters:
- description: "Patch operation to perform"
in: "header"
name: "Operation"
required: true
schema:
enum:
- "append"
- "prepend"
- "replace"
type: "string"
- description: "Type of target to patch"
in: "header"
name: "Target-Type"
required: true
schema:
enum:
- "heading"
- "block"
- "frontmatter"
type: "string"
- description: "Delimiter to use for nested targets (i.e. Headings)"
in: "header"
name: "Target-Delimiter"
required: false
schema:
default: "::"
type: "string"
- description: |
Target to patch; this value can be URL-Encoded and *must*
be URL-Encoded if it includes non-ASCII characters.
in: "header"
name: "Target"
required: true
schema:
type: "string"
- description: "Trim whitespace from Target before applying patch?"
in: "header"
name: "Trim-Target-Whitespace"
required: false
schema:
default: "false"
enum:
- "true"
- "false"
type: "string"
- description: "The year of the date for which you would like to grab a periodic note."
in: "path"
name: "year"
required: true
schema:
type: "number"
- description: "The month (1-12) of the date for which you would like to grab a periodic note."
in: "path"
name: "month"
required: true
schema:
type: "number"
- description: "The day (1-31) of the date for which you would like to grab a periodic note."
in: "path"
name: "day"
required: true
schema:
type: "number"
- description: "The name of the period for which you would like to grab the current note."
in: "path"
name: "period"
required: true
schema:
default: "daily"
enum:
- "daily"
- "weekly"
- "monthly"
- "quarterly"
- "yearly"
type: "string"
requestBody:
content:
application/json:
schema:
example: "['one', 'two']"
type: "string"
text/markdown:
schema:
example: |
# This is my document
something else here
type: "string"
description: "Content you would like to insert."
required: true
responses:
"200":
description: "Success"
"400":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: "Bad Request; see response message for details."
"404":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: "Does not exist"
"405":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: |
Your path references a directory instead of a file; this request method is valid only for updating files.
summary: |
Insert content into a periodic note in Obsidian relative to a heading, block reference, or frontmatter field within that document.
tags:
- "Periodic Notes"
post:
description: |
Appends content to the periodic note for the specified period. This will create the relevant periodic note if necessary.
parameters:
- description: "The year of the date for which you would like to grab a periodic note."
in: "path"
name: "year"
required: true
schema:
type: "number"
- description: "The month (1-12) of the date for which you would like to grab a periodic note."
in: "path"
name: "month"
required: true
schema:
type: "number"
- description: "The day (1-31) of the date for which you would like to grab a periodic note."
in: "path"
name: "day"
required: true
schema:
type: "number"
- description: "The name of the period for which you would like to grab the current note."
in: "path"
name: "period"
required: true
schema:
default: "daily"
enum:
- "daily"
- "weekly"
- "monthly"
- "quarterly"
- "yearly"
type: "string"
requestBody:
content:
text/markdown:
schema:
example: |
# This is my document
something else here
type: "string"
description: "Content you would like to append."
required: true
responses:
"204":
description: "Success"
"400":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: "Bad Request"
"405":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: |
Your path references a directory instead of a file; this request method is valid only for updating files.
summary: |
Append content to a periodic note.
tags:
- "Periodic Notes"
put:
parameters:
- description: "The year of the date for which you would like to grab a periodic note."
in: "path"
name: "year"
required: true
schema:
type: "number"
- description: "The month (1-12) of the date for which you would like to grab a periodic note."
in: "path"
name: "month"
required: true
schema:
type: "number"
- description: "The day (1-31) of the date for which you would like to grab a periodic note."
in: "path"
name: "day"
required: true
schema:
type: "number"
- description: "The name of the period for which you would like to grab the current note."
in: "path"
name: "period"
required: true
schema:
default: "daily"
enum:
- "daily"
- "weekly"
- "monthly"
- "quarterly"
- "yearly"
type: "string"
requestBody:
content:
"*/*":
schema:
type: "string"
text/markdown:
schema:
example: |
# This is my document
something else here
type: "string"
description: "Content of the file you would like to upload."
required: true
responses:
"204":
description: "Success"
"400":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: |
Incoming file could not be processed. Make sure you have specified a reasonable file name, and make sure you have set a reasonable 'Content-Type' header; if you are uploading a note, 'text/markdown' is likely the right choice.
"405":
content:
application/json:
schema:
"$ref": "#/components/schemas/Error"
description: |
Your path references a directory instead of a file; this request method is valid only for updating files.
summary: |
Update the content of a periodic note.
tags:
- "Periodic Notes"
/search/:
post:
description: |
@@ -1195,7 +1688,7 @@ paths:
- "Vault Files"
patch:
description: |
Inserts content into an existing note relative to a heading within your note.
Inserts content into an existing note relative to a heading, block refeerence, or frontmatter field within that document.
Allows you to modify the content relative to a heading, block reference, or frontmatter field in your document.
@@ -1321,7 +1814,7 @@ paths:
You may find using a `Content-Type` of `application/json` to be
particularly useful in the case of frontmatter since frontmatter
fields' values are JSON data, and the API can be smarter about
interpreting your `prepend` or `append` requests if you specify
interpreting yoru `prepend` or `append` requests if you specify
your data as JSON (particularly when appending, for example,
list items).
parameters:
@@ -1416,14 +1909,14 @@ paths:
description: |
Your path references a directory instead of a file; this request method is valid only for updating files.
summary: |
Insert content into an existing note relative to a heading within that document.
Insert content into an existing note in Obsidian relative to a heading, block reference, or frontmatter field within that document.
tags:
- "Vault Files"
post:
description: |
Appends content to the end of an existing note. If the specified file does not yet exist, it will be created as an empty file.
If you would like to insert text relative to a particular heading instead of appending to the end of the file, see 'patch'.
If you would like to insert text relative to a particular heading, block reference, or frontmatter field instead of appending to the end of the file, see 'patch'.
parameters:
- description: |
Path to the relevant file (relative to your vault root).

View File

@@ -152,4 +152,88 @@ class Obsidian():
response.raise_for_status()
return response.json()
return self._safe_call(call_fn)
return self._safe_call(call_fn)
def get_periodic_note(self, period: str) -> Any:
"""Get current periodic note for the specified period.
Args:
period: The period type (daily, weekly, monthly, quarterly, yearly)
Returns:
Content of the periodic note
"""
url = f"{self.get_base_url()}/periodic/{period}/"
def call_fn():
response = requests.get(url, headers=self._get_headers(), verify=self.verify_ssl, timeout=self.timeout)
response.raise_for_status()
return response.text
return self._safe_call(call_fn)
def get_recent_periodic_notes(self, period: str, limit: int = 5, include_content: bool = False) -> Any:
"""Get most recent periodic notes for the specified period type.
Args:
period: The period type (daily, weekly, monthly, quarterly, yearly)
limit: Maximum number of notes to return (default: 5)
include_content: Whether to include note content (default: False)
Returns:
List of recent periodic notes
"""
url = f"{self.get_base_url()}/periodic/{period}/recent"
params = {
"limit": limit,
"includeContent": include_content
}
def call_fn():
response = requests.get(
url,
headers=self._get_headers(),
params=params,
verify=self.verify_ssl,
timeout=self.timeout
)
response.raise_for_status()
return response.json()
return self._safe_call(call_fn)
def get_recent_changes(self, limit: int = 10, days: int = None, extensions: list = None) -> Any:
"""Get recently modified files in the vault.
Args:
limit: Maximum number of files to return (default: 10)
days: Only include files modified within this many days (optional)
extensions: Only include files with these extensions (optional)
Returns:
List of recently modified files with metadata
"""
url = f"{self.get_base_url()}/vault/recent"
params = {"limit": limit}
if days is not None:
params["days"] = days
if extensions is not None:
params["extensions"] = ",".join(extensions)
def call_fn():
response = requests.get(
url,
headers=self._get_headers(),
params=params,
verify=self.verify_ssl,
timeout=self.timeout
)
response.raise_for_status()
return response.json()
return self._safe_call(call_fn)

View File

@@ -49,6 +49,9 @@ add_tool_handler(tools.PatchContentToolHandler())
add_tool_handler(tools.AppendContentToolHandler())
add_tool_handler(tools.ComplexSearchToolHandler())
add_tool_handler(tools.BatchGetFileContentsToolHandler())
add_tool_handler(tools.PeriodicNotesToolHandler())
add_tool_handler(tools.RecentPeriodicNotesToolHandler())
add_tool_handler(tools.RecentChangesToolHandler())
@app.list_tools()
async def list_tools() -> list[Tool]:
@@ -86,6 +89,3 @@ async def main():
write_stream,
app.create_initialization_options()
)

View File

@@ -362,4 +362,161 @@ class BatchGetFileContentsToolHandler(ToolHandler):
type="text",
text=content
)
]
]
class PeriodicNotesToolHandler(ToolHandler):
def __init__(self):
super().__init__("obsidian_get_periodic_note")
def get_tool_description(self):
return Tool(
name=self.name,
description="Get current periodic note for the specified period.",
inputSchema={
"type": "object",
"properties": {
"period": {
"type": "string",
"description": "The period type (daily, weekly, monthly, quarterly, yearly)",
"enum": ["daily", "weekly", "monthly", "quarterly", "yearly"]
}
},
"required": ["period"]
}
)
def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
if "period" not in args:
raise RuntimeError("period argument missing in arguments")
period = args["period"]
valid_periods = ["daily", "weekly", "monthly", "quarterly", "yearly"]
if period not in valid_periods:
raise RuntimeError(f"Invalid period: {period}. Must be one of: {', '.join(valid_periods)}")
api = obsidian.Obsidian(api_key=api_key)
content = api.get_periodic_note(period)
return [
TextContent(
type="text",
text=content
)
]
class RecentPeriodicNotesToolHandler(ToolHandler):
def __init__(self):
super().__init__("obsidian_get_recent_periodic_notes")
def get_tool_description(self):
return Tool(
name=self.name,
description="Get most recent periodic notes for the specified period type.",
inputSchema={
"type": "object",
"properties": {
"period": {
"type": "string",
"description": "The period type (daily, weekly, monthly, quarterly, yearly)",
"enum": ["daily", "weekly", "monthly", "quarterly", "yearly"]
},
"limit": {
"type": "integer",
"description": "Maximum number of notes to return (default: 5)",
"default": 5,
"minimum": 1,
"maximum": 50
},
"include_content": {
"type": "boolean",
"description": "Whether to include note content (default: false)",
"default": False
}
},
"required": ["period"]
}
)
def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
if "period" not in args:
raise RuntimeError("period argument missing in arguments")
period = args["period"]
valid_periods = ["daily", "weekly", "monthly", "quarterly", "yearly"]
if period not in valid_periods:
raise RuntimeError(f"Invalid period: {period}. Must be one of: {', '.join(valid_periods)}")
limit = args.get("limit", 5)
if not isinstance(limit, int) or limit < 1:
raise RuntimeError(f"Invalid limit: {limit}. Must be a positive integer")
include_content = args.get("include_content", False)
if not isinstance(include_content, bool):
raise RuntimeError(f"Invalid include_content: {include_content}. Must be a boolean")
api = obsidian.Obsidian(api_key=api_key)
results = api.get_recent_periodic_notes(period, limit, include_content)
return [
TextContent(
type="text",
text=json.dumps(results, indent=2)
)
]
class RecentChangesToolHandler(ToolHandler):
def __init__(self):
super().__init__("obsidian_get_recent_changes")
def get_tool_description(self):
return Tool(
name=self.name,
description="Get recently modified files in the vault.",
inputSchema={
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of files to return (default: 10)",
"default": 10,
"minimum": 1,
"maximum": 100
},
"days": {
"type": "integer",
"description": "Only include files modified within this many days (optional)",
"minimum": 1
},
"extensions": {
"type": "array",
"items": {
"type": "string"
},
"description": "Only include files with these extensions (optional)"
}
}
}
)
def run_tool(self, args: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
limit = args.get("limit", 10)
if not isinstance(limit, int) or limit < 1:
raise RuntimeError(f"Invalid limit: {limit}. Must be a positive integer")
days = args.get("days")
if days is not None and (not isinstance(days, int) or days < 1):
raise RuntimeError(f"Invalid days: {days}. Must be a positive integer")
extensions = args.get("extensions")
if extensions is not None and not isinstance(extensions, list):
raise RuntimeError(f"Invalid extensions: {extensions}. Must be an array of strings")
api = obsidian.Obsidian(api_key=api_key)
results = api.get_recent_changes(limit, days, extensions)
return [
TextContent(
type="text",
text=json.dumps(results, indent=2)
)
]