mirror of
https://github.com/simstudioai/sim.git
synced 2026-03-15 03:00:33 -04:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecd3536a72 | ||
|
|
635179d696 | ||
|
|
f88926a6a8 | ||
|
|
690b47a0bf | ||
|
|
158d5236bc | ||
|
|
1ba1bc8edb | ||
|
|
53fd92a30a |
@@ -710,6 +710,155 @@ export function PerplexityIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function ObsidianIcon(props: SVGProps<SVGSVGElement>) {
|
||||
const id = useId()
|
||||
const bl = `${id}-bl`
|
||||
const tr = `${id}-tr`
|
||||
const tl = `${id}-tl`
|
||||
const br = `${id}-br`
|
||||
const te = `${id}-te`
|
||||
const le = `${id}-le`
|
||||
const be = `${id}-be`
|
||||
const me = `${id}-me`
|
||||
const clip = `${id}-clip`
|
||||
return (
|
||||
<svg {...props} viewBox='0 0 512 512' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<radialGradient
|
||||
id={bl}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(-59 -225 150 -39 161.4 470)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.4' />
|
||||
<stop offset='1' stopOpacity='.1' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={tr}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(50 -379 280 37 360 374.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.6' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.1' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={tl}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(69 -319 218 47 175.4 307)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.8' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.4' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={br}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(-96 -163 187 -111 335.3 512.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.3' />
|
||||
<stop offset='1' stopOpacity='.3' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={te}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(-36 166 -112 -24 310 128.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='0' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.2' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={le}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(88 89 -190 187 111 220.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.2' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.4' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={be}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(9 130 -276 20 215 284)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.2' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.3' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={me}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(-198 -104 327 -623 400 399.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.2' />
|
||||
<stop offset='.5' stopColor='#fff' stopOpacity='.2' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.3' />
|
||||
</radialGradient>
|
||||
<clipPath id={clip}>
|
||||
<path d='M.2.2h512v512H.2z' />
|
||||
</clipPath>
|
||||
<g clipPath={`url(#${clip})`}>
|
||||
<path
|
||||
d='M382.3 475.6c-3.1 23.4-26 41.6-48.7 35.3-32.4-8.9-69.9-22.8-103.6-25.4l-51.7-4a34 34 0 0 1-22-10.2l-89-91.7a34 34 0 0 1-6.7-37.7s55-121 57.1-127.3c2-6.3 9.6-61.2 14-90.6 1.2-7.9 5-15 11-20.3L248 8.9a34.1 34.1 0 0 1 49.6 4.3L386 125.6a37 37 0 0 1 7.6 22.4c0 21.3 1.8 65 13.6 93.2 11.5 27.3 32.5 57 43.5 71.5a17.3 17.3 0 0 1 1.3 19.2 1494 1494 0 0 1-44.8 70.6c-15 22.3-21.9 49.9-25 73.1z'
|
||||
fill='#6c31e3'
|
||||
/>
|
||||
<path
|
||||
d='M165.9 478.3c41.4-84 40.2-144.2 22.6-187-16.2-39.6-46.3-64.5-70-80-.6 2.3-1.3 4.4-2.2 6.5L60.6 342a34 34 0 0 0 6.6 37.7l89.1 91.7a34 34 0 0 0 9.6 7z'
|
||||
fill={`url(#${bl})`}
|
||||
/>
|
||||
<path
|
||||
d='M278.4 307.8c11.2 1.2 22.2 3.6 32.8 7.6 34 12.7 65 41.2 90.5 96.3 1.8-3.1 3.6-6.2 5.6-9.2a1536 1536 0 0 0 44.8-70.6 17 17 0 0 0-1.3-19.2c-11-14.6-32-44.2-43.5-71.5-11.8-28.2-13.5-72-13.6-93.2 0-8.1-2.6-16-7.6-22.4L297.6 13.2a34 34 0 0 0-1.5-1.7 96 96 0 0 1 2 54 198.3 198.3 0 0 1-17.6 41.3l-7.2 14.2a171 171 0 0 0-19.4 71c-1.2 29.4 4.8 66.4 24.5 115.8z'
|
||||
fill={`url(#${tr})`}
|
||||
/>
|
||||
<path
|
||||
d='M278.4 307.8c-19.7-49.4-25.8-86.4-24.5-115.9a171 171 0 0 1 19.4-71c2.3-4.8 4.8-9.5 7.2-14.1 7.1-13.9 14-27 17.6-41.4a96 96 0 0 0-2-54A34.1 34.1 0 0 0 248 9l-105.4 94.8a34.1 34.1 0 0 0-10.9 20.3l-12.8 85-.5 2.3c23.8 15.5 54 40.4 70.1 80a147 147 0 0 1 7.8 24.8c28-6.8 55.7-11 82.1-8.3z'
|
||||
fill={`url(#${tl})`}
|
||||
/>
|
||||
<path
|
||||
d='M333.6 511c22.7 6.2 45.6-12 48.7-35.4a187 187 0 0 1 19.4-63.9c-25.6-55-56.5-83.6-90.4-96.3-36-13.4-75.2-9-115 .7 8.9 40.4 3.6 93.3-30.4 162.2 4 1.8 8.1 3 12.5 3.3 0 0 24.4 2 53.6 4.1 29 2 72.4 17.1 101.6 25.2z'
|
||||
fill={`url(#${br})`}
|
||||
/>
|
||||
<g clipRule='evenodd' fillRule='evenodd'>
|
||||
<path
|
||||
d='M254.1 190c-1.3 29.2 2.4 62.8 22.1 112.1l-6.2-.5c-17.7-51.5-21.5-78-20.2-107.6a174.7 174.7 0 0 1 20.4-72c2.4-4.9 8-14.1 10.5-18.8 7.1-13.7 11.9-21 16-33.6 5.7-17.5 4.5-25.9 3.8-34.1 4.6 29.9-12.7 56-25.7 82.4a177.1 177.1 0 0 0-20.7 72z'
|
||||
fill={`url(#${te})`}
|
||||
/>
|
||||
<path
|
||||
d='M194.3 293.4c2.4 5.4 4.6 9.8 6 16.5L195 311c-2.1-7.8-3.8-13.4-6.8-20-17.8-42-46.3-63.6-69.7-79.5 28.2 15.2 57.2 39 75.7 81.9z'
|
||||
fill={`url(#${le})`}
|
||||
/>
|
||||
<path
|
||||
d='M200.6 315.1c9.8 46-1.2 104.2-33.6 160.9 27.1-56.2 40.2-110.1 29.3-160z'
|
||||
fill={`url(#${be})`}
|
||||
/>
|
||||
<path
|
||||
d='M312.5 311c53.1 19.9 73.6 63.6 88.9 100-19-38.1-45.2-80.3-90.8-96-34.8-11.8-64.1-10.4-114.3 1l-1.1-5c53.2-12.1 81-13.5 117.3 0z'
|
||||
fill={`url(#${me})`}
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function NotionIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' width='1em' height='1em' {...props}>
|
||||
@@ -1806,6 +1955,14 @@ export function Mem0Icon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function EvernoteIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='#7fce2c'>
|
||||
<path d='M29.343 16.818c.1 1.695-.08 3.368-.305 5.045-.225 1.712-.508 3.416-.964 5.084-.3 1.067-.673 2.1-1.202 3.074-.65 1.192-1.635 1.87-2.992 1.924l-3.832.036c-.636-.017-1.278-.146-1.9-.297-1.192-.3-1.862-1.1-2.06-2.3-.186-1.08-.173-2.187.04-3.264.252-1.23 1-1.96 2.234-2.103.817-.1 1.65-.077 2.476-.1.205-.007.275.098.203.287-.196.53-.236 1.07-.098 1.623.053.207-.023.307-.26.305a7.77 7.77 0 0 0-1.123.053c-.636.086-.96.47-.96 1.112 0 .205.026.416.066.622.103.507.45.78.944.837 1.123.127 2.247.138 3.37-.05.675-.114 1.08-.54 1.16-1.208.152-1.3.155-2.587-.228-3.845-.33-1.092-1.006-1.565-2.134-1.7l-3.36-.54c-1.06-.193-1.7-.887-1.92-1.9-.13-.572-.14-1.17-.214-1.757-.013-.106-.074-.208-.1-.3-.04.1-.106.212-.117.326-.066.68-.053 1.373-.185 2.04-.16.8-.404 1.566-.67 2.33-.185.535-.616.837-1.205.8a37.76 37.76 0 0 1-7.123-1.353l-.64-.207c-.927-.26-1.487-.903-1.74-1.787l-1-3.853-.74-4.3c-.115-.755-.2-1.523-.083-2.293.154-1.112.914-1.903 2.04-1.964l3.558-.062c.127 0 .254.003.373-.026a1.23 1.23 0 0 0 1.01-1.255l-.05-3.036c-.048-1.576.8-2.38 2.156-2.622a10.58 10.58 0 0 1 4.91.26c.933.275 1.467.923 1.715 1.83.058.22.146.3.37.287l2.582.01 3.333.37c.686.095 1.364.25 2.032.42 1.165.298 1.793 1.112 1.962 2.256l.357 3.355.3 5.577.01 2.277zm-4.534-1.155c-.02-.666-.07-1.267-.444-1.784a1.66 1.66 0 0 0-2.469-.15c-.364.4-.494.88-.564 1.4-.008.034.106.126.16.126l.8-.053c.768.007 1.523.113 2.25.393.066.026.136.04.265.077zM8.787 1.154a3.82 3.82 0 0 0-.278 1.592l.05 2.934c.005.357-.075.45-.433.45L5.1 6.156c-.583 0-1.143.1-1.554.278l5.2-5.332c.02.013.04.033.06.053z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ElevenLabsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
ElasticsearchIcon,
|
||||
ElevenLabsIcon,
|
||||
EnrichSoIcon,
|
||||
EvernoteIcon,
|
||||
ExaAIIcon,
|
||||
EyeIcon,
|
||||
FirecrawlIcon,
|
||||
@@ -103,6 +104,7 @@ import {
|
||||
MySQLIcon,
|
||||
Neo4jIcon,
|
||||
NotionIcon,
|
||||
ObsidianIcon,
|
||||
OnePasswordIcon,
|
||||
OpenAIIcon,
|
||||
OutlookIcon,
|
||||
@@ -202,6 +204,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
elasticsearch: ElasticsearchIcon,
|
||||
elevenlabs: ElevenLabsIcon,
|
||||
enrich: EnrichSoIcon,
|
||||
evernote: EvernoteIcon,
|
||||
exa: ExaAIIcon,
|
||||
file_v3: DocumentIcon,
|
||||
firecrawl: FirecrawlIcon,
|
||||
@@ -265,6 +268,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
mysql: MySQLIcon,
|
||||
neo4j: Neo4jIcon,
|
||||
notion_v2: NotionIcon,
|
||||
obsidian: ObsidianIcon,
|
||||
onedrive: MicrosoftOneDriveIcon,
|
||||
onepassword: OnePasswordIcon,
|
||||
openai: OpenAIIcon,
|
||||
|
||||
267
apps/docs/content/docs/en/tools/evernote.mdx
Normal file
267
apps/docs/content/docs/en/tools/evernote.mdx
Normal file
@@ -0,0 +1,267 @@
|
||||
---
|
||||
title: Evernote
|
||||
description: Manage notes, notebooks, and tags in Evernote
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="evernote"
|
||||
color="#E0E0E0"
|
||||
/>
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate with Evernote to manage notes, notebooks, and tags. Create, read, update, copy, search, and delete notes. Create and list notebooks and tags.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `evernote_copy_note`
|
||||
|
||||
Copy a note to another notebook in Evernote
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `noteGuid` | string | Yes | GUID of the note to copy |
|
||||
| `toNotebookGuid` | string | Yes | GUID of the destination notebook |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `note` | object | The copied note metadata |
|
||||
| ↳ `guid` | string | New note GUID |
|
||||
| ↳ `title` | string | Note title |
|
||||
| ↳ `notebookGuid` | string | GUID of the destination notebook |
|
||||
| ↳ `created` | number | Creation timestamp in milliseconds |
|
||||
| ↳ `updated` | number | Last updated timestamp in milliseconds |
|
||||
|
||||
### `evernote_create_note`
|
||||
|
||||
Create a new note in Evernote
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `title` | string | Yes | Title of the note |
|
||||
| `content` | string | Yes | Content of the note \(plain text or ENML\) |
|
||||
| `notebookGuid` | string | No | GUID of the notebook to create the note in \(defaults to default notebook\) |
|
||||
| `tagNames` | string | No | Comma-separated list of tag names to apply |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `note` | object | The created note |
|
||||
| ↳ `guid` | string | Unique identifier of the note |
|
||||
| ↳ `title` | string | Title of the note |
|
||||
| ↳ `content` | string | ENML content of the note |
|
||||
| ↳ `notebookGuid` | string | GUID of the containing notebook |
|
||||
| ↳ `tagNames` | array | Tag names applied to the note |
|
||||
| ↳ `created` | number | Creation timestamp in milliseconds |
|
||||
| ↳ `updated` | number | Last updated timestamp in milliseconds |
|
||||
|
||||
### `evernote_create_notebook`
|
||||
|
||||
Create a new notebook in Evernote
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `name` | string | Yes | Name for the new notebook |
|
||||
| `stack` | string | No | Stack name to group the notebook under |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `notebook` | object | The created notebook |
|
||||
| ↳ `guid` | string | Notebook GUID |
|
||||
| ↳ `name` | string | Notebook name |
|
||||
| ↳ `defaultNotebook` | boolean | Whether this is the default notebook |
|
||||
| ↳ `serviceCreated` | number | Creation timestamp in milliseconds |
|
||||
| ↳ `serviceUpdated` | number | Last updated timestamp in milliseconds |
|
||||
| ↳ `stack` | string | Notebook stack name |
|
||||
|
||||
### `evernote_create_tag`
|
||||
|
||||
Create a new tag in Evernote
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `name` | string | Yes | Name for the new tag |
|
||||
| `parentGuid` | string | No | GUID of the parent tag for hierarchy |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tag` | object | The created tag |
|
||||
| ↳ `guid` | string | Tag GUID |
|
||||
| ↳ `name` | string | Tag name |
|
||||
| ↳ `parentGuid` | string | Parent tag GUID |
|
||||
| ↳ `updateSequenceNum` | number | Update sequence number |
|
||||
|
||||
### `evernote_delete_note`
|
||||
|
||||
Move a note to the trash in Evernote
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `noteGuid` | string | Yes | GUID of the note to delete |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the note was successfully deleted |
|
||||
| `noteGuid` | string | GUID of the deleted note |
|
||||
|
||||
### `evernote_get_note`
|
||||
|
||||
Retrieve a note from Evernote by its GUID
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `noteGuid` | string | Yes | GUID of the note to retrieve |
|
||||
| `withContent` | boolean | No | Whether to include note content \(default: true\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `note` | object | The retrieved note |
|
||||
| ↳ `guid` | string | Unique identifier of the note |
|
||||
| ↳ `title` | string | Title of the note |
|
||||
| ↳ `content` | string | ENML content of the note |
|
||||
| ↳ `contentLength` | number | Length of the note content |
|
||||
| ↳ `notebookGuid` | string | GUID of the containing notebook |
|
||||
| ↳ `tagGuids` | array | GUIDs of tags on the note |
|
||||
| ↳ `tagNames` | array | Names of tags on the note |
|
||||
| ↳ `created` | number | Creation timestamp in milliseconds |
|
||||
| ↳ `updated` | number | Last updated timestamp in milliseconds |
|
||||
| ↳ `active` | boolean | Whether the note is active \(not in trash\) |
|
||||
|
||||
### `evernote_get_notebook`
|
||||
|
||||
Retrieve a notebook from Evernote by its GUID
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `notebookGuid` | string | Yes | GUID of the notebook to retrieve |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `notebook` | object | The retrieved notebook |
|
||||
| ↳ `guid` | string | Notebook GUID |
|
||||
| ↳ `name` | string | Notebook name |
|
||||
| ↳ `defaultNotebook` | boolean | Whether this is the default notebook |
|
||||
| ↳ `serviceCreated` | number | Creation timestamp in milliseconds |
|
||||
| ↳ `serviceUpdated` | number | Last updated timestamp in milliseconds |
|
||||
| ↳ `stack` | string | Notebook stack name |
|
||||
|
||||
### `evernote_list_notebooks`
|
||||
|
||||
List all notebooks in an Evernote account
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `notebooks` | array | List of notebooks |
|
||||
|
||||
### `evernote_list_tags`
|
||||
|
||||
List all tags in an Evernote account
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `tags` | array | List of tags |
|
||||
|
||||
### `evernote_search_notes`
|
||||
|
||||
Search for notes in Evernote using the Evernote search grammar
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `query` | string | Yes | Search query using Evernote search grammar \(e.g., "tag:work intitle:meeting"\) |
|
||||
| `notebookGuid` | string | No | Restrict search to a specific notebook by GUID |
|
||||
| `offset` | number | No | Starting index for results \(default: 0\) |
|
||||
| `maxNotes` | number | No | Maximum number of notes to return \(default: 25\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `totalNotes` | number | Total number of matching notes |
|
||||
| `notes` | array | List of matching note metadata |
|
||||
|
||||
### `evernote_update_note`
|
||||
|
||||
Update an existing note in Evernote
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Evernote developer token |
|
||||
| `noteGuid` | string | Yes | GUID of the note to update |
|
||||
| `title` | string | No | New title for the note |
|
||||
| `content` | string | No | New content for the note \(plain text or ENML\) |
|
||||
| `notebookGuid` | string | No | GUID of the notebook to move the note to |
|
||||
| `tagNames` | string | No | Comma-separated list of tag names \(replaces existing tags\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `note` | object | The updated note |
|
||||
| ↳ `guid` | string | Unique identifier of the note |
|
||||
| ↳ `title` | string | Title of the note |
|
||||
| ↳ `content` | string | ENML content of the note |
|
||||
| ↳ `notebookGuid` | string | GUID of the containing notebook |
|
||||
| ↳ `tagNames` | array | Tag names on the note |
|
||||
| ↳ `created` | number | Creation timestamp in milliseconds |
|
||||
| ↳ `updated` | number | Last updated timestamp in milliseconds |
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"elasticsearch",
|
||||
"elevenlabs",
|
||||
"enrich",
|
||||
"evernote",
|
||||
"exa",
|
||||
"file",
|
||||
"firecrawl",
|
||||
@@ -98,6 +99,7 @@
|
||||
"mysql",
|
||||
"neo4j",
|
||||
"notion",
|
||||
"obsidian",
|
||||
"onedrive",
|
||||
"onepassword",
|
||||
"openai",
|
||||
|
||||
323
apps/docs/content/docs/en/tools/obsidian.mdx
Normal file
323
apps/docs/content/docs/en/tools/obsidian.mdx
Normal file
@@ -0,0 +1,323 @@
|
||||
---
|
||||
title: Obsidian
|
||||
description: Interact with your Obsidian vault via the Local REST API
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="obsidian"
|
||||
color="#0F0F0F"
|
||||
/>
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Read, create, update, search, and delete notes in your Obsidian vault. Manage periodic notes, execute commands, and patch content at specific locations. Requires the Obsidian Local REST API plugin.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `obsidian_append_active`
|
||||
|
||||
Append content to the currently active file in Obsidian
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `content` | string | Yes | Markdown content to append to the active file |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `appended` | boolean | Whether content was successfully appended |
|
||||
|
||||
### `obsidian_append_note`
|
||||
|
||||
Append content to an existing note in your Obsidian vault
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `filename` | string | Yes | Path to the note relative to vault root \(e.g. "folder/note.md"\) |
|
||||
| `content` | string | Yes | Markdown content to append to the note |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `filename` | string | Path of the note |
|
||||
| `appended` | boolean | Whether content was successfully appended |
|
||||
|
||||
### `obsidian_append_periodic_note`
|
||||
|
||||
Append content to the current periodic note (daily, weekly, monthly, quarterly, or yearly). Creates the note if it does not exist.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `period` | string | Yes | Period type: daily, weekly, monthly, quarterly, or yearly |
|
||||
| `content` | string | Yes | Markdown content to append to the periodic note |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `period` | string | Period type of the note |
|
||||
| `appended` | boolean | Whether content was successfully appended |
|
||||
|
||||
### `obsidian_create_note`
|
||||
|
||||
Create or replace a note in your Obsidian vault
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `filename` | string | Yes | Path for the note relative to vault root \(e.g. "folder/note.md"\) |
|
||||
| `content` | string | Yes | Markdown content for the note |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `filename` | string | Path of the created note |
|
||||
| `created` | boolean | Whether the note was successfully created |
|
||||
|
||||
### `obsidian_delete_note`
|
||||
|
||||
Delete a note from your Obsidian vault
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `filename` | string | Yes | Path to the note to delete relative to vault root |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `filename` | string | Path of the deleted note |
|
||||
| `deleted` | boolean | Whether the note was successfully deleted |
|
||||
|
||||
### `obsidian_execute_command`
|
||||
|
||||
Execute a command in Obsidian (e.g. open daily note, toggle sidebar)
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `commandId` | string | Yes | ID of the command to execute \(use List Commands operation to discover available commands\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `commandId` | string | ID of the executed command |
|
||||
| `executed` | boolean | Whether the command was successfully executed |
|
||||
|
||||
### `obsidian_get_active`
|
||||
|
||||
Retrieve the content of the currently active file in Obsidian
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `content` | string | Markdown content of the active file |
|
||||
| `filename` | string | Path to the active file |
|
||||
|
||||
### `obsidian_get_note`
|
||||
|
||||
Retrieve the content of a note from your Obsidian vault
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `filename` | string | Yes | Path to the note relative to vault root \(e.g. "folder/note.md"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `content` | string | Markdown content of the note |
|
||||
| `filename` | string | Path to the note |
|
||||
|
||||
### `obsidian_get_periodic_note`
|
||||
|
||||
Retrieve the current periodic note (daily, weekly, monthly, quarterly, or yearly)
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `period` | string | Yes | Period type: daily, weekly, monthly, quarterly, or yearly |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `content` | string | Markdown content of the periodic note |
|
||||
| `period` | string | Period type of the note |
|
||||
|
||||
### `obsidian_list_commands`
|
||||
|
||||
List all available commands in Obsidian
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `commands` | json | List of available commands with IDs and names |
|
||||
| ↳ `id` | string | Command identifier |
|
||||
| ↳ `name` | string | Human-readable command name |
|
||||
|
||||
### `obsidian_list_files`
|
||||
|
||||
List files and directories in your Obsidian vault
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `path` | string | No | Directory path relative to vault root. Leave empty to list root. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `files` | json | List of files and directories |
|
||||
| ↳ `path` | string | File or directory path |
|
||||
| ↳ `type` | string | Whether the entry is a file or directory |
|
||||
|
||||
### `obsidian_open_file`
|
||||
|
||||
Open a file in the Obsidian UI (creates the file if it does not exist)
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `filename` | string | Yes | Path to the file relative to vault root |
|
||||
| `newLeaf` | boolean | No | Whether to open the file in a new leaf/tab |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `filename` | string | Path of the opened file |
|
||||
| `opened` | boolean | Whether the file was successfully opened |
|
||||
|
||||
### `obsidian_patch_active`
|
||||
|
||||
Insert or replace content at a specific heading, block reference, or frontmatter field in the active file
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `content` | string | Yes | Content to insert at the target location |
|
||||
| `operation` | string | Yes | How to insert content: append, prepend, or replace |
|
||||
| `targetType` | string | Yes | Type of target: heading, block, or frontmatter |
|
||||
| `target` | string | Yes | Target identifier \(heading text, block reference ID, or frontmatter field name\) |
|
||||
| `targetDelimiter` | string | No | Delimiter for nested headings \(default: "::"\) |
|
||||
| `trimTargetWhitespace` | boolean | No | Whether to trim whitespace from target before matching \(default: false\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `patched` | boolean | Whether the active file was successfully patched |
|
||||
|
||||
### `obsidian_patch_note`
|
||||
|
||||
Insert or replace content at a specific heading, block reference, or frontmatter field in a note
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `filename` | string | Yes | Path to the note relative to vault root \(e.g. "folder/note.md"\) |
|
||||
| `content` | string | Yes | Content to insert at the target location |
|
||||
| `operation` | string | Yes | How to insert content: append, prepend, or replace |
|
||||
| `targetType` | string | Yes | Type of target: heading, block, or frontmatter |
|
||||
| `target` | string | Yes | Target identifier \(heading text, block reference ID, or frontmatter field name\) |
|
||||
| `targetDelimiter` | string | No | Delimiter for nested headings \(default: "::"\) |
|
||||
| `trimTargetWhitespace` | boolean | No | Whether to trim whitespace from target before matching \(default: false\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `filename` | string | Path of the patched note |
|
||||
| `patched` | boolean | Whether the note was successfully patched |
|
||||
|
||||
### `obsidian_search`
|
||||
|
||||
Search for text across notes in your Obsidian vault
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | API key from Obsidian Local REST API plugin settings |
|
||||
| `baseUrl` | string | Yes | Base URL for the Obsidian Local REST API |
|
||||
| `query` | string | Yes | Text to search for across vault notes |
|
||||
| `contextLength` | number | No | Number of characters of context around each match \(default: 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `results` | json | Search results with filenames, scores, and matching contexts |
|
||||
| ↳ `filename` | string | Path to the matching note |
|
||||
| ↳ `score` | number | Relevance score |
|
||||
| ↳ `matches` | json | Matching text contexts |
|
||||
| ↳ `context` | string | Text surrounding the match |
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
|
||||
import { SSE_HEADERS } from '@/lib/core/utils/sse'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { markExecutionCancelled } from '@/lib/execution/cancellation'
|
||||
import { decrementSSEConnections, incrementSSEConnections } from '@/lib/monitoring/sse-connections'
|
||||
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
|
||||
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
|
||||
import {
|
||||
@@ -631,11 +630,9 @@ async function handleMessageStream(
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
let messageStreamDecremented = false
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
incrementSSEConnections('a2a-message')
|
||||
const sendEvent = (event: string, data: unknown) => {
|
||||
try {
|
||||
const jsonRpcResponse = {
|
||||
@@ -845,19 +842,10 @@ async function handleMessageStream(
|
||||
})
|
||||
} finally {
|
||||
await releaseLock(lockKey, lockValue)
|
||||
if (!messageStreamDecremented) {
|
||||
messageStreamDecremented = true
|
||||
decrementSSEConnections('a2a-message')
|
||||
}
|
||||
controller.close()
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
if (!messageStreamDecremented) {
|
||||
messageStreamDecremented = true
|
||||
decrementSSEConnections('a2a-message')
|
||||
}
|
||||
},
|
||||
cancel() {},
|
||||
})
|
||||
|
||||
return new NextResponse(stream, {
|
||||
@@ -1042,22 +1030,16 @@ async function handleTaskResubscribe(
|
||||
{ once: true }
|
||||
)
|
||||
|
||||
let sseDecremented = false
|
||||
const cleanup = () => {
|
||||
isCancelled = true
|
||||
if (pollTimeoutId) {
|
||||
clearTimeout(pollTimeoutId)
|
||||
pollTimeoutId = null
|
||||
}
|
||||
if (!sseDecremented) {
|
||||
sseDecremented = true
|
||||
decrementSSEConnections('a2a-resubscribe')
|
||||
}
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
incrementSSEConnections('a2a-resubscribe')
|
||||
const sendEvent = (event: string, data: unknown): boolean => {
|
||||
if (isCancelled || abortSignal.aborted) return false
|
||||
try {
|
||||
|
||||
@@ -14,7 +14,6 @@ import { getSession } from '@/lib/auth'
|
||||
import { SSE_HEADERS } from '@/lib/core/utils/sse'
|
||||
import { mcpConnectionManager } from '@/lib/mcp/connection-manager'
|
||||
import { mcpPubSub } from '@/lib/mcp/pubsub'
|
||||
import { decrementSSEConnections, incrementSSEConnections } from '@/lib/monitoring/sse-connections'
|
||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||
|
||||
const logger = createLogger('McpEventsSSE')
|
||||
@@ -50,14 +49,11 @@ export async function GET(request: NextRequest) {
|
||||
for (const unsub of unsubscribers) {
|
||||
unsub()
|
||||
}
|
||||
decrementSSEConnections('mcp-events')
|
||||
logger.info(`SSE connection closed for workspace ${workspaceId}`)
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
incrementSSEConnections('mcp-events')
|
||||
|
||||
const send = (eventName: string, data: Record<string, unknown>) => {
|
||||
if (cleaned) return
|
||||
try {
|
||||
|
||||
38
apps/sim/app/api/tools/evernote/copy-note/route.ts
Normal file
38
apps/sim/app/api/tools/evernote/copy-note/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { copyNote } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteCopyNoteAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, noteGuid, toNotebookGuid } = body
|
||||
|
||||
if (!apiKey || !noteGuid || !toNotebookGuid) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey, noteGuid, and toNotebookGuid are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const note = await copyNote(apiKey, noteGuid, toNotebookGuid)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { note },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to copy note', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
51
apps/sim/app/api/tools/evernote/create-note/route.ts
Normal file
51
apps/sim/app/api/tools/evernote/create-note/route.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { createNote } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteCreateNoteAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, title, content, notebookGuid, tagNames } = body
|
||||
|
||||
if (!apiKey || !title || !content) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey, title, and content are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const parsedTags = tagNames
|
||||
? (() => {
|
||||
const tags =
|
||||
typeof tagNames === 'string'
|
||||
? tagNames
|
||||
.split(',')
|
||||
.map((t: string) => t.trim())
|
||||
.filter(Boolean)
|
||||
: tagNames
|
||||
return tags.length > 0 ? tags : undefined
|
||||
})()
|
||||
: undefined
|
||||
|
||||
const note = await createNote(apiKey, title, content, notebookGuid || undefined, parsedTags)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { note },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to create note', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
38
apps/sim/app/api/tools/evernote/create-notebook/route.ts
Normal file
38
apps/sim/app/api/tools/evernote/create-notebook/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { createNotebook } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteCreateNotebookAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, name, stack } = body
|
||||
|
||||
if (!apiKey || !name) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey and name are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const notebook = await createNotebook(apiKey, name, stack || undefined)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { notebook },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to create notebook', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
38
apps/sim/app/api/tools/evernote/create-tag/route.ts
Normal file
38
apps/sim/app/api/tools/evernote/create-tag/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { createTag } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteCreateTagAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, name, parentGuid } = body
|
||||
|
||||
if (!apiKey || !name) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey and name are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const tag = await createTag(apiKey, name, parentGuid || undefined)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { tag },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to create tag', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
41
apps/sim/app/api/tools/evernote/delete-note/route.ts
Normal file
41
apps/sim/app/api/tools/evernote/delete-note/route.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { deleteNote } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteDeleteNoteAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, noteGuid } = body
|
||||
|
||||
if (!apiKey || !noteGuid) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey and noteGuid are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
await deleteNote(apiKey, noteGuid)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
success: true,
|
||||
noteGuid,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to delete note', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
38
apps/sim/app/api/tools/evernote/get-note/route.ts
Normal file
38
apps/sim/app/api/tools/evernote/get-note/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { getNote } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteGetNoteAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, noteGuid, withContent = true } = body
|
||||
|
||||
if (!apiKey || !noteGuid) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey and noteGuid are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const note = await getNote(apiKey, noteGuid, withContent)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { note },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to get note', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
38
apps/sim/app/api/tools/evernote/get-notebook/route.ts
Normal file
38
apps/sim/app/api/tools/evernote/get-notebook/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { getNotebook } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteGetNotebookAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, notebookGuid } = body
|
||||
|
||||
if (!apiKey || !notebookGuid) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey and notebookGuid are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const notebook = await getNotebook(apiKey, notebookGuid)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { notebook },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to get notebook', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
799
apps/sim/app/api/tools/evernote/lib/client.ts
Normal file
799
apps/sim/app/api/tools/evernote/lib/client.ts
Normal file
@@ -0,0 +1,799 @@
|
||||
/**
|
||||
* Evernote API client using Thrift binary protocol over HTTP.
|
||||
* Implements only the NoteStore methods needed for the integration.
|
||||
*/
|
||||
|
||||
import {
|
||||
ThriftReader,
|
||||
ThriftWriter,
|
||||
TYPE_BOOL,
|
||||
TYPE_I32,
|
||||
TYPE_I64,
|
||||
TYPE_LIST,
|
||||
TYPE_STRING,
|
||||
TYPE_STRUCT,
|
||||
} from './thrift'
|
||||
|
||||
export interface EvernoteNotebook {
|
||||
guid: string
|
||||
name: string
|
||||
defaultNotebook: boolean
|
||||
serviceCreated: number | null
|
||||
serviceUpdated: number | null
|
||||
stack: string | null
|
||||
}
|
||||
|
||||
export interface EvernoteNote {
|
||||
guid: string
|
||||
title: string
|
||||
content: string | null
|
||||
contentLength: number | null
|
||||
created: number | null
|
||||
updated: number | null
|
||||
deleted: number | null
|
||||
active: boolean
|
||||
notebookGuid: string | null
|
||||
tagGuids: string[]
|
||||
tagNames: string[]
|
||||
}
|
||||
|
||||
export interface EvernoteNoteMetadata {
|
||||
guid: string
|
||||
title: string | null
|
||||
contentLength: number | null
|
||||
created: number | null
|
||||
updated: number | null
|
||||
notebookGuid: string | null
|
||||
tagGuids: string[]
|
||||
}
|
||||
|
||||
export interface EvernoteTag {
|
||||
guid: string
|
||||
name: string
|
||||
parentGuid: string | null
|
||||
updateSequenceNum: number | null
|
||||
}
|
||||
|
||||
export interface EvernoteSearchResult {
|
||||
startIndex: number
|
||||
totalNotes: number
|
||||
notes: EvernoteNoteMetadata[]
|
||||
}
|
||||
|
||||
/** Extract shard ID from an Evernote developer token */
|
||||
function extractShardId(token: string): string {
|
||||
const match = token.match(/S=s(\d+)/)
|
||||
if (!match) {
|
||||
throw new Error('Invalid Evernote token format: cannot extract shard ID')
|
||||
}
|
||||
return `s${match[1]}`
|
||||
}
|
||||
|
||||
/** Get the NoteStore URL for the given token */
|
||||
function getNoteStoreUrl(token: string): string {
|
||||
const shardId = extractShardId(token)
|
||||
const host = token.includes(':Sandbox') ? 'sandbox.evernote.com' : 'www.evernote.com'
|
||||
return `https://${host}/shard/${shardId}/notestore`
|
||||
}
|
||||
|
||||
/** Make a Thrift RPC call to the NoteStore */
|
||||
async function callNoteStore(token: string, writer: ThriftWriter): Promise<ThriftReader> {
|
||||
const url = getNoteStoreUrl(token)
|
||||
const body = writer.toBuffer()
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-thrift',
|
||||
Accept: 'application/x-thrift',
|
||||
},
|
||||
body: new Uint8Array(body),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Evernote API HTTP error: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer()
|
||||
const reader = new ThriftReader(arrayBuffer)
|
||||
const msg = reader.readMessageBegin()
|
||||
|
||||
if (reader.isException(msg.type)) {
|
||||
const ex = reader.readException()
|
||||
throw new Error(`Evernote API error: ${ex.message}`)
|
||||
}
|
||||
|
||||
return reader
|
||||
}
|
||||
|
||||
/** Check for Evernote-specific exceptions in the response struct. Returns true if handled. */
|
||||
function checkEvernoteException(reader: ThriftReader, fieldId: number, fieldType: number): boolean {
|
||||
if (fieldId === 1 && fieldType === TYPE_STRUCT) {
|
||||
let message = ''
|
||||
let errorCode = 0
|
||||
reader.readStruct((r, fid, ftype) => {
|
||||
if (fid === 1 && ftype === TYPE_I32) {
|
||||
errorCode = r.readI32()
|
||||
} else if (fid === 2 && ftype === TYPE_STRING) {
|
||||
message = r.readString()
|
||||
} else {
|
||||
r.skip(ftype)
|
||||
}
|
||||
})
|
||||
throw new Error(`Evernote error (${errorCode}): ${message}`)
|
||||
}
|
||||
if (fieldId === 2 && fieldType === TYPE_STRUCT) {
|
||||
let message = ''
|
||||
let errorCode = 0
|
||||
reader.readStruct((r, fid, ftype) => {
|
||||
if (fid === 1 && ftype === TYPE_I32) {
|
||||
errorCode = r.readI32()
|
||||
} else if (fid === 2 && ftype === TYPE_STRING) {
|
||||
message = r.readString()
|
||||
} else {
|
||||
r.skip(ftype)
|
||||
}
|
||||
})
|
||||
throw new Error(`Evernote system error (${errorCode}): ${message}`)
|
||||
}
|
||||
if (fieldId === 3 && fieldType === TYPE_STRUCT) {
|
||||
let identifier = ''
|
||||
let key = ''
|
||||
reader.readStruct((r, fid, ftype) => {
|
||||
if (fid === 1 && ftype === TYPE_STRING) {
|
||||
identifier = r.readString()
|
||||
} else if (fid === 2 && ftype === TYPE_STRING) {
|
||||
key = r.readString()
|
||||
} else {
|
||||
r.skip(ftype)
|
||||
}
|
||||
})
|
||||
throw new Error(`Evernote not found: ${identifier}${key ? ` (${key})` : ''}`)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function readNotebook(reader: ThriftReader): EvernoteNotebook {
|
||||
const notebook: EvernoteNotebook = {
|
||||
guid: '',
|
||||
name: '',
|
||||
defaultNotebook: false,
|
||||
serviceCreated: null,
|
||||
serviceUpdated: null,
|
||||
stack: null,
|
||||
}
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
switch (fieldId) {
|
||||
case 1:
|
||||
if (fieldType === TYPE_STRING) notebook.guid = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 2:
|
||||
if (fieldType === TYPE_STRING) notebook.name = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 4:
|
||||
if (fieldType === TYPE_BOOL) notebook.defaultNotebook = r.readBool()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 5:
|
||||
if (fieldType === TYPE_I64) notebook.serviceCreated = Number(r.readI64())
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 6:
|
||||
if (fieldType === TYPE_I64) notebook.serviceUpdated = Number(r.readI64())
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 9:
|
||||
if (fieldType === TYPE_STRING) notebook.stack = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
default:
|
||||
r.skip(fieldType)
|
||||
}
|
||||
})
|
||||
|
||||
return notebook
|
||||
}
|
||||
|
||||
function readNote(reader: ThriftReader): EvernoteNote {
|
||||
const note: EvernoteNote = {
|
||||
guid: '',
|
||||
title: '',
|
||||
content: null,
|
||||
contentLength: null,
|
||||
created: null,
|
||||
updated: null,
|
||||
deleted: null,
|
||||
active: true,
|
||||
notebookGuid: null,
|
||||
tagGuids: [],
|
||||
tagNames: [],
|
||||
}
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
switch (fieldId) {
|
||||
case 1:
|
||||
if (fieldType === TYPE_STRING) note.guid = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 2:
|
||||
if (fieldType === TYPE_STRING) note.title = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 3:
|
||||
if (fieldType === TYPE_STRING) note.content = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 5:
|
||||
if (fieldType === TYPE_I32) note.contentLength = r.readI32()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 6:
|
||||
if (fieldType === TYPE_I64) note.created = Number(r.readI64())
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 7:
|
||||
if (fieldType === TYPE_I64) note.updated = Number(r.readI64())
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 8:
|
||||
if (fieldType === TYPE_I64) note.deleted = Number(r.readI64())
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 9:
|
||||
if (fieldType === TYPE_BOOL) note.active = r.readBool()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 11:
|
||||
if (fieldType === TYPE_STRING) note.notebookGuid = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 12:
|
||||
if (fieldType === TYPE_LIST) {
|
||||
const { size } = r.readListBegin()
|
||||
for (let i = 0; i < size; i++) {
|
||||
note.tagGuids.push(r.readString())
|
||||
}
|
||||
} else {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
break
|
||||
case 15:
|
||||
if (fieldType === TYPE_LIST) {
|
||||
const { size } = r.readListBegin()
|
||||
for (let i = 0; i < size; i++) {
|
||||
note.tagNames.push(r.readString())
|
||||
}
|
||||
} else {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
break
|
||||
default:
|
||||
r.skip(fieldType)
|
||||
}
|
||||
})
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
function readTag(reader: ThriftReader): EvernoteTag {
|
||||
const tag: EvernoteTag = {
|
||||
guid: '',
|
||||
name: '',
|
||||
parentGuid: null,
|
||||
updateSequenceNum: null,
|
||||
}
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
switch (fieldId) {
|
||||
case 1:
|
||||
if (fieldType === TYPE_STRING) tag.guid = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 2:
|
||||
if (fieldType === TYPE_STRING) tag.name = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 3:
|
||||
if (fieldType === TYPE_STRING) tag.parentGuid = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 4:
|
||||
if (fieldType === TYPE_I32) tag.updateSequenceNum = r.readI32()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
default:
|
||||
r.skip(fieldType)
|
||||
}
|
||||
})
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
function readNoteMetadata(reader: ThriftReader): EvernoteNoteMetadata {
|
||||
const meta: EvernoteNoteMetadata = {
|
||||
guid: '',
|
||||
title: null,
|
||||
contentLength: null,
|
||||
created: null,
|
||||
updated: null,
|
||||
notebookGuid: null,
|
||||
tagGuids: [],
|
||||
}
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
switch (fieldId) {
|
||||
case 1:
|
||||
if (fieldType === TYPE_STRING) meta.guid = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 2:
|
||||
if (fieldType === TYPE_STRING) meta.title = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 5:
|
||||
if (fieldType === TYPE_I32) meta.contentLength = r.readI32()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 6:
|
||||
if (fieldType === TYPE_I64) meta.created = Number(r.readI64())
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 7:
|
||||
if (fieldType === TYPE_I64) meta.updated = Number(r.readI64())
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 11:
|
||||
if (fieldType === TYPE_STRING) meta.notebookGuid = r.readString()
|
||||
else r.skip(fieldType)
|
||||
break
|
||||
case 12:
|
||||
if (fieldType === TYPE_LIST) {
|
||||
const { size } = r.readListBegin()
|
||||
for (let i = 0; i < size; i++) {
|
||||
meta.tagGuids.push(r.readString())
|
||||
}
|
||||
} else {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
break
|
||||
default:
|
||||
r.skip(fieldType)
|
||||
}
|
||||
})
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
export async function listNotebooks(token: string): Promise<EvernoteNotebook[]> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('listNotebooks', 0)
|
||||
writer.writeStringField(1, token)
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
const notebooks: EvernoteNotebook[] = []
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_LIST) {
|
||||
const { size } = r.readListBegin()
|
||||
for (let i = 0; i < size; i++) {
|
||||
notebooks.push(readNotebook(r))
|
||||
}
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return notebooks
|
||||
}
|
||||
|
||||
export async function getNote(
|
||||
token: string,
|
||||
guid: string,
|
||||
withContent = true
|
||||
): Promise<EvernoteNote> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('getNote', 0)
|
||||
writer.writeStringField(1, token)
|
||||
writer.writeStringField(2, guid)
|
||||
writer.writeBoolField(3, withContent)
|
||||
writer.writeBoolField(4, false)
|
||||
writer.writeBoolField(5, false)
|
||||
writer.writeBoolField(6, false)
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
let note: EvernoteNote | null = null
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_STRUCT) {
|
||||
note = readNote(r)
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!note) {
|
||||
throw new Error('No note returned from Evernote API')
|
||||
}
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
/** Wrap content in ENML if it's not already */
|
||||
function wrapInEnml(content: string): string {
|
||||
if (content.includes('<!DOCTYPE en-note')) {
|
||||
return content
|
||||
}
|
||||
const escaped = content
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\n/g, '<br/>')
|
||||
return `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note>${escaped}</en-note>`
|
||||
}
|
||||
|
||||
export async function createNote(
|
||||
token: string,
|
||||
title: string,
|
||||
content: string,
|
||||
notebookGuid?: string,
|
||||
tagNames?: string[]
|
||||
): Promise<EvernoteNote> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('createNote', 0)
|
||||
writer.writeStringField(1, token)
|
||||
|
||||
writer.writeFieldBegin(TYPE_STRUCT, 2)
|
||||
writer.writeStringField(2, title)
|
||||
writer.writeStringField(3, wrapInEnml(content))
|
||||
if (notebookGuid) {
|
||||
writer.writeStringField(11, notebookGuid)
|
||||
}
|
||||
if (tagNames && tagNames.length > 0) {
|
||||
writer.writeStringListField(15, tagNames)
|
||||
}
|
||||
writer.writeFieldStop()
|
||||
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
let note: EvernoteNote | null = null
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_STRUCT) {
|
||||
note = readNote(r)
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!note) {
|
||||
throw new Error('No note returned from Evernote API')
|
||||
}
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
export async function updateNote(
|
||||
token: string,
|
||||
guid: string,
|
||||
title?: string,
|
||||
content?: string,
|
||||
notebookGuid?: string,
|
||||
tagNames?: string[]
|
||||
): Promise<EvernoteNote> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('updateNote', 0)
|
||||
writer.writeStringField(1, token)
|
||||
|
||||
writer.writeFieldBegin(TYPE_STRUCT, 2)
|
||||
writer.writeStringField(1, guid)
|
||||
if (title !== undefined) {
|
||||
writer.writeStringField(2, title)
|
||||
}
|
||||
if (content !== undefined) {
|
||||
writer.writeStringField(3, wrapInEnml(content))
|
||||
}
|
||||
if (notebookGuid !== undefined) {
|
||||
writer.writeStringField(11, notebookGuid)
|
||||
}
|
||||
if (tagNames !== undefined) {
|
||||
writer.writeStringListField(15, tagNames)
|
||||
}
|
||||
writer.writeFieldStop()
|
||||
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
let note: EvernoteNote | null = null
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_STRUCT) {
|
||||
note = readNote(r)
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!note) {
|
||||
throw new Error('No note returned from Evernote API')
|
||||
}
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
export async function deleteNote(token: string, guid: string): Promise<number> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('deleteNote', 0)
|
||||
writer.writeStringField(1, token)
|
||||
writer.writeStringField(2, guid)
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
let usn = 0
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_I32) {
|
||||
usn = r.readI32()
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return usn
|
||||
}
|
||||
|
||||
export async function searchNotes(
|
||||
token: string,
|
||||
query: string,
|
||||
notebookGuid?: string,
|
||||
offset = 0,
|
||||
maxNotes = 25
|
||||
): Promise<EvernoteSearchResult> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('findNotesMetadata', 0)
|
||||
writer.writeStringField(1, token)
|
||||
|
||||
// NoteFilter (field 2)
|
||||
writer.writeFieldBegin(TYPE_STRUCT, 2)
|
||||
if (query) {
|
||||
writer.writeStringField(3, query)
|
||||
}
|
||||
if (notebookGuid) {
|
||||
writer.writeStringField(4, notebookGuid)
|
||||
}
|
||||
writer.writeFieldStop()
|
||||
|
||||
// offset (field 3)
|
||||
writer.writeI32Field(3, offset)
|
||||
// maxNotes (field 4)
|
||||
writer.writeI32Field(4, maxNotes)
|
||||
|
||||
// NotesMetadataResultSpec (field 5)
|
||||
writer.writeFieldBegin(TYPE_STRUCT, 5)
|
||||
writer.writeBoolField(2, true) // includeTitle
|
||||
writer.writeBoolField(5, true) // includeContentLength
|
||||
writer.writeBoolField(6, true) // includeCreated
|
||||
writer.writeBoolField(7, true) // includeUpdated
|
||||
writer.writeBoolField(11, true) // includeNotebookGuid
|
||||
writer.writeBoolField(12, true) // includeTagGuids
|
||||
writer.writeFieldStop()
|
||||
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
const result: EvernoteSearchResult = {
|
||||
startIndex: 0,
|
||||
totalNotes: 0,
|
||||
notes: [],
|
||||
}
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_STRUCT) {
|
||||
r.readStruct((r2, fid2, ftype2) => {
|
||||
switch (fid2) {
|
||||
case 1:
|
||||
if (ftype2 === TYPE_I32) result.startIndex = r2.readI32()
|
||||
else r2.skip(ftype2)
|
||||
break
|
||||
case 2:
|
||||
if (ftype2 === TYPE_I32) result.totalNotes = r2.readI32()
|
||||
else r2.skip(ftype2)
|
||||
break
|
||||
case 3:
|
||||
if (ftype2 === TYPE_LIST) {
|
||||
const { size } = r2.readListBegin()
|
||||
for (let i = 0; i < size; i++) {
|
||||
result.notes.push(readNoteMetadata(r2))
|
||||
}
|
||||
} else {
|
||||
r2.skip(ftype2)
|
||||
}
|
||||
break
|
||||
default:
|
||||
r2.skip(ftype2)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function getNotebook(token: string, guid: string): Promise<EvernoteNotebook> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('getNotebook', 0)
|
||||
writer.writeStringField(1, token)
|
||||
writer.writeStringField(2, guid)
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
let notebook: EvernoteNotebook | null = null
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_STRUCT) {
|
||||
notebook = readNotebook(r)
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!notebook) {
|
||||
throw new Error('No notebook returned from Evernote API')
|
||||
}
|
||||
|
||||
return notebook
|
||||
}
|
||||
|
||||
export async function createNotebook(
|
||||
token: string,
|
||||
name: string,
|
||||
stack?: string
|
||||
): Promise<EvernoteNotebook> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('createNotebook', 0)
|
||||
writer.writeStringField(1, token)
|
||||
|
||||
writer.writeFieldBegin(TYPE_STRUCT, 2)
|
||||
writer.writeStringField(2, name)
|
||||
if (stack) {
|
||||
writer.writeStringField(9, stack)
|
||||
}
|
||||
writer.writeFieldStop()
|
||||
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
let notebook: EvernoteNotebook | null = null
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_STRUCT) {
|
||||
notebook = readNotebook(r)
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!notebook) {
|
||||
throw new Error('No notebook returned from Evernote API')
|
||||
}
|
||||
|
||||
return notebook
|
||||
}
|
||||
|
||||
export async function listTags(token: string): Promise<EvernoteTag[]> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('listTags', 0)
|
||||
writer.writeStringField(1, token)
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
const tags: EvernoteTag[] = []
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_LIST) {
|
||||
const { size } = r.readListBegin()
|
||||
for (let i = 0; i < size; i++) {
|
||||
tags.push(readTag(r))
|
||||
}
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
export async function createTag(
|
||||
token: string,
|
||||
name: string,
|
||||
parentGuid?: string
|
||||
): Promise<EvernoteTag> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('createTag', 0)
|
||||
writer.writeStringField(1, token)
|
||||
|
||||
writer.writeFieldBegin(TYPE_STRUCT, 2)
|
||||
writer.writeStringField(2, name)
|
||||
if (parentGuid) {
|
||||
writer.writeStringField(3, parentGuid)
|
||||
}
|
||||
writer.writeFieldStop()
|
||||
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
let tag: EvernoteTag | null = null
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_STRUCT) {
|
||||
tag = readTag(r)
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!tag) {
|
||||
throw new Error('No tag returned from Evernote API')
|
||||
}
|
||||
|
||||
return tag
|
||||
}
|
||||
|
||||
export async function copyNote(
|
||||
token: string,
|
||||
noteGuid: string,
|
||||
toNotebookGuid: string
|
||||
): Promise<EvernoteNote> {
|
||||
const writer = new ThriftWriter()
|
||||
writer.writeMessageBegin('copyNote', 0)
|
||||
writer.writeStringField(1, token)
|
||||
writer.writeStringField(2, noteGuid)
|
||||
writer.writeStringField(3, toNotebookGuid)
|
||||
writer.writeFieldStop()
|
||||
|
||||
const reader = await callNoteStore(token, writer)
|
||||
let note: EvernoteNote | null = null
|
||||
|
||||
reader.readStruct((r, fieldId, fieldType) => {
|
||||
if (fieldId === 0 && fieldType === TYPE_STRUCT) {
|
||||
note = readNote(r)
|
||||
} else {
|
||||
if (!checkEvernoteException(r, fieldId, fieldType)) {
|
||||
r.skip(fieldType)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!note) {
|
||||
throw new Error('No note returned from Evernote API')
|
||||
}
|
||||
|
||||
return note
|
||||
}
|
||||
255
apps/sim/app/api/tools/evernote/lib/thrift.ts
Normal file
255
apps/sim/app/api/tools/evernote/lib/thrift.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Minimal Thrift binary protocol encoder/decoder for Evernote API.
|
||||
* Supports only the types needed for NoteStore operations.
|
||||
*/
|
||||
|
||||
const THRIFT_VERSION_1 = 0x80010000
|
||||
const MESSAGE_CALL = 1
|
||||
const MESSAGE_EXCEPTION = 3
|
||||
|
||||
const TYPE_STOP = 0
|
||||
const TYPE_BOOL = 2
|
||||
const TYPE_I32 = 8
|
||||
const TYPE_I64 = 10
|
||||
const TYPE_STRING = 11
|
||||
const TYPE_STRUCT = 12
|
||||
const TYPE_LIST = 15
|
||||
|
||||
export class ThriftWriter {
|
||||
private buffer: number[] = []
|
||||
|
||||
writeMessageBegin(name: string, seqId: number): void {
|
||||
this.writeI32(THRIFT_VERSION_1 | MESSAGE_CALL)
|
||||
this.writeString(name)
|
||||
this.writeI32(seqId)
|
||||
}
|
||||
|
||||
writeFieldBegin(type: number, id: number): void {
|
||||
this.buffer.push(type)
|
||||
this.writeI16(id)
|
||||
}
|
||||
|
||||
writeFieldStop(): void {
|
||||
this.buffer.push(TYPE_STOP)
|
||||
}
|
||||
|
||||
writeString(value: string): void {
|
||||
const encoded = new TextEncoder().encode(value)
|
||||
this.writeI32(encoded.length)
|
||||
for (const byte of encoded) {
|
||||
this.buffer.push(byte)
|
||||
}
|
||||
}
|
||||
|
||||
writeBool(value: boolean): void {
|
||||
this.buffer.push(value ? 1 : 0)
|
||||
}
|
||||
|
||||
writeI16(value: number): void {
|
||||
this.buffer.push((value >> 8) & 0xff)
|
||||
this.buffer.push(value & 0xff)
|
||||
}
|
||||
|
||||
writeI32(value: number): void {
|
||||
this.buffer.push((value >> 24) & 0xff)
|
||||
this.buffer.push((value >> 16) & 0xff)
|
||||
this.buffer.push((value >> 8) & 0xff)
|
||||
this.buffer.push(value & 0xff)
|
||||
}
|
||||
|
||||
writeI64(value: bigint): void {
|
||||
const buf = new ArrayBuffer(8)
|
||||
const view = new DataView(buf)
|
||||
view.setBigInt64(0, value, false)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
this.buffer.push(view.getUint8(i))
|
||||
}
|
||||
}
|
||||
|
||||
writeStringField(id: number, value: string): void {
|
||||
this.writeFieldBegin(TYPE_STRING, id)
|
||||
this.writeString(value)
|
||||
}
|
||||
|
||||
writeBoolField(id: number, value: boolean): void {
|
||||
this.writeFieldBegin(TYPE_BOOL, id)
|
||||
this.writeBool(value)
|
||||
}
|
||||
|
||||
writeI32Field(id: number, value: number): void {
|
||||
this.writeFieldBegin(TYPE_I32, id)
|
||||
this.writeI32(value)
|
||||
}
|
||||
|
||||
writeStringListField(id: number, values: string[]): void {
|
||||
this.writeFieldBegin(TYPE_LIST, id)
|
||||
this.buffer.push(TYPE_STRING)
|
||||
this.writeI32(values.length)
|
||||
for (const v of values) {
|
||||
this.writeString(v)
|
||||
}
|
||||
}
|
||||
|
||||
toBuffer(): Buffer {
|
||||
return Buffer.from(this.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
export class ThriftReader {
|
||||
private view: DataView
|
||||
private pos = 0
|
||||
|
||||
constructor(buffer: ArrayBuffer) {
|
||||
this.view = new DataView(buffer)
|
||||
}
|
||||
|
||||
readMessageBegin(): { name: string; type: number; seqId: number } {
|
||||
const versionAndType = this.readI32()
|
||||
const version = versionAndType & 0xffff0000
|
||||
if (version !== (THRIFT_VERSION_1 | 0)) {
|
||||
throw new Error(`Unsupported Thrift version: 0x${version.toString(16)}`)
|
||||
}
|
||||
const type = versionAndType & 0x000000ff
|
||||
const name = this.readString()
|
||||
const seqId = this.readI32()
|
||||
return { name, type, seqId }
|
||||
}
|
||||
|
||||
readFieldBegin(): { type: number; id: number } {
|
||||
const type = this.view.getUint8(this.pos++)
|
||||
if (type === TYPE_STOP) {
|
||||
return { type: TYPE_STOP, id: 0 }
|
||||
}
|
||||
const id = this.view.getInt16(this.pos, false)
|
||||
this.pos += 2
|
||||
return { type, id }
|
||||
}
|
||||
|
||||
readString(): string {
|
||||
const length = this.readI32()
|
||||
const bytes = new Uint8Array(this.view.buffer, this.pos, length)
|
||||
this.pos += length
|
||||
return new TextDecoder().decode(bytes)
|
||||
}
|
||||
|
||||
readBool(): boolean {
|
||||
return this.view.getUint8(this.pos++) !== 0
|
||||
}
|
||||
|
||||
readI32(): number {
|
||||
const value = this.view.getInt32(this.pos, false)
|
||||
this.pos += 4
|
||||
return value
|
||||
}
|
||||
|
||||
readI64(): bigint {
|
||||
const value = this.view.getBigInt64(this.pos, false)
|
||||
this.pos += 8
|
||||
return value
|
||||
}
|
||||
|
||||
readBinary(): Uint8Array {
|
||||
const length = this.readI32()
|
||||
const bytes = new Uint8Array(this.view.buffer, this.pos, length)
|
||||
this.pos += length
|
||||
return bytes
|
||||
}
|
||||
|
||||
readListBegin(): { elementType: number; size: number } {
|
||||
const elementType = this.view.getUint8(this.pos++)
|
||||
const size = this.readI32()
|
||||
return { elementType, size }
|
||||
}
|
||||
|
||||
/** Skip a value of the given Thrift type */
|
||||
skip(type: number): void {
|
||||
switch (type) {
|
||||
case TYPE_BOOL:
|
||||
this.pos += 1
|
||||
break
|
||||
case 6: // I16
|
||||
this.pos += 2
|
||||
break
|
||||
case 3: // BYTE
|
||||
this.pos += 1
|
||||
break
|
||||
case TYPE_I32:
|
||||
this.pos += 4
|
||||
break
|
||||
case TYPE_I64:
|
||||
case 4: // DOUBLE
|
||||
this.pos += 8
|
||||
break
|
||||
case TYPE_STRING: {
|
||||
const len = this.readI32()
|
||||
this.pos += len
|
||||
break
|
||||
}
|
||||
case TYPE_STRUCT:
|
||||
this.skipStruct()
|
||||
break
|
||||
case TYPE_LIST:
|
||||
case 14: {
|
||||
// SET
|
||||
const { elementType, size } = this.readListBegin()
|
||||
for (let i = 0; i < size; i++) {
|
||||
this.skip(elementType)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 13: {
|
||||
// MAP
|
||||
const keyType = this.view.getUint8(this.pos++)
|
||||
const valueType = this.view.getUint8(this.pos++)
|
||||
const count = this.readI32()
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.skip(keyType)
|
||||
this.skip(valueType)
|
||||
}
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error(`Cannot skip unknown Thrift type: ${type}`)
|
||||
}
|
||||
}
|
||||
|
||||
private skipStruct(): void {
|
||||
for (;;) {
|
||||
const { type } = this.readFieldBegin()
|
||||
if (type === TYPE_STOP) break
|
||||
this.skip(type)
|
||||
}
|
||||
}
|
||||
|
||||
/** Read struct fields, calling the handler for each field */
|
||||
readStruct<T>(handler: (reader: ThriftReader, fieldId: number, fieldType: number) => void): void {
|
||||
for (;;) {
|
||||
const { type, id } = this.readFieldBegin()
|
||||
if (type === TYPE_STOP) break
|
||||
handler(this, id, type)
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if this is an exception response */
|
||||
isException(messageType: number): boolean {
|
||||
return messageType === MESSAGE_EXCEPTION
|
||||
}
|
||||
|
||||
/** Read a Thrift application exception */
|
||||
readException(): { message: string; type: number } {
|
||||
let message = ''
|
||||
let type = 0
|
||||
this.readStruct((reader, fieldId, fieldType) => {
|
||||
if (fieldId === 1 && fieldType === TYPE_STRING) {
|
||||
message = reader.readString()
|
||||
} else if (fieldId === 2 && fieldType === TYPE_I32) {
|
||||
type = reader.readI32()
|
||||
} else {
|
||||
reader.skip(fieldType)
|
||||
}
|
||||
})
|
||||
return { message, type }
|
||||
}
|
||||
}
|
||||
|
||||
export { TYPE_BOOL, TYPE_I32, TYPE_I64, TYPE_LIST, TYPE_STOP, TYPE_STRING, TYPE_STRUCT }
|
||||
35
apps/sim/app/api/tools/evernote/list-notebooks/route.ts
Normal file
35
apps/sim/app/api/tools/evernote/list-notebooks/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { listNotebooks } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteListNotebooksAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey } = body
|
||||
|
||||
if (!apiKey) {
|
||||
return NextResponse.json({ success: false, error: 'apiKey is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const notebooks = await listNotebooks(apiKey)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { notebooks },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to list notebooks', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
35
apps/sim/app/api/tools/evernote/list-tags/route.ts
Normal file
35
apps/sim/app/api/tools/evernote/list-tags/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { listTags } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteListTagsAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey } = body
|
||||
|
||||
if (!apiKey) {
|
||||
return NextResponse.json({ success: false, error: 'apiKey is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const tags = await listTags(apiKey)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { tags },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to list tags', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
49
apps/sim/app/api/tools/evernote/search-notes/route.ts
Normal file
49
apps/sim/app/api/tools/evernote/search-notes/route.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { searchNotes } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteSearchNotesAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, query, notebookGuid, offset = 0, maxNotes = 25 } = body
|
||||
|
||||
if (!apiKey || !query) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey and query are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const clampedMaxNotes = Math.min(Math.max(Number(maxNotes) || 25, 1), 250)
|
||||
|
||||
const result = await searchNotes(
|
||||
apiKey,
|
||||
query,
|
||||
notebookGuid || undefined,
|
||||
Number(offset),
|
||||
clampedMaxNotes
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
totalNotes: result.totalNotes,
|
||||
notes: result.notes,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to search notes', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
58
apps/sim/app/api/tools/evernote/update-note/route.ts
Normal file
58
apps/sim/app/api/tools/evernote/update-note/route.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { updateNote } from '@/app/api/tools/evernote/lib/client'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('EvernoteUpdateNoteAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { apiKey, noteGuid, title, content, notebookGuid, tagNames } = body
|
||||
|
||||
if (!apiKey || !noteGuid) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'apiKey and noteGuid are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const parsedTags = tagNames
|
||||
? (() => {
|
||||
const tags =
|
||||
typeof tagNames === 'string'
|
||||
? tagNames
|
||||
.split(',')
|
||||
.map((t: string) => t.trim())
|
||||
.filter(Boolean)
|
||||
: tagNames
|
||||
return tags.length > 0 ? tags : undefined
|
||||
})()
|
||||
: undefined
|
||||
|
||||
const note = await updateNote(
|
||||
apiKey,
|
||||
noteGuid,
|
||||
title || undefined,
|
||||
content || undefined,
|
||||
notebookGuid || undefined,
|
||||
parsedTags
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: { note },
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
logger.error('Failed to update note', { error: message })
|
||||
return NextResponse.json({ success: false, error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { getCostMultiplier, isBillingEnabled } from '@/lib/core/config/feature-flags'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { decrementSSEConnections, incrementSSEConnections } from '@/lib/monitoring/sse-connections'
|
||||
import { enrichTableSchema } from '@/lib/table/llm/wand'
|
||||
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
|
||||
import { extractResponseText, parseResponsesUsage } from '@/providers/openai/utils'
|
||||
@@ -331,14 +330,10 @@ export async function POST(req: NextRequest) {
|
||||
const encoder = new TextEncoder()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
let wandStreamClosed = false
|
||||
const readable = new ReadableStream({
|
||||
async start(controller) {
|
||||
incrementSSEConnections('wand')
|
||||
const reader = response.body?.getReader()
|
||||
if (!reader) {
|
||||
wandStreamClosed = true
|
||||
decrementSSEConnections('wand')
|
||||
controller.close()
|
||||
return
|
||||
}
|
||||
@@ -483,18 +478,9 @@ export async function POST(req: NextRequest) {
|
||||
controller.close()
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
if (!wandStreamClosed) {
|
||||
wandStreamClosed = true
|
||||
decrementSSEConnections('wand')
|
||||
}
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
if (!wandStreamClosed) {
|
||||
wandStreamClosed = true
|
||||
decrementSSEConnections('wand')
|
||||
}
|
||||
},
|
||||
cancel() {},
|
||||
})
|
||||
|
||||
return new Response(readable, {
|
||||
|
||||
@@ -22,7 +22,6 @@ import { createExecutionEventWriter, setExecutionMeta } from '@/lib/execution/ev
|
||||
import { processInputFileFields } from '@/lib/execution/files'
|
||||
import { preprocessExecution } from '@/lib/execution/preprocessing'
|
||||
import { LoggingSession } from '@/lib/logs/execution/logging-session'
|
||||
import { decrementSSEConnections, incrementSSEConnections } from '@/lib/monitoring/sse-connections'
|
||||
import {
|
||||
cleanupExecutionBase64Cache,
|
||||
hydrateUserFilesWithBase64,
|
||||
@@ -764,7 +763,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
const encoder = new TextEncoder()
|
||||
const timeoutController = createTimeoutAbortController(preprocessResult.executionTimeout?.sync)
|
||||
let isStreamClosed = false
|
||||
let sseDecremented = false
|
||||
|
||||
const eventWriter = createExecutionEventWriter(executionId)
|
||||
setExecutionMeta(executionId, {
|
||||
@@ -775,7 +773,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
async start(controller) {
|
||||
incrementSSEConnections('workflow-execute')
|
||||
let finalMetaStatus: 'complete' | 'error' | 'cancelled' | null = null
|
||||
|
||||
const sendEvent = (event: ExecutionEvent) => {
|
||||
@@ -1159,10 +1156,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
if (executionId) {
|
||||
await cleanupExecutionBase64Cache(executionId)
|
||||
}
|
||||
if (!sseDecremented) {
|
||||
sseDecremented = true
|
||||
decrementSSEConnections('workflow-execute')
|
||||
}
|
||||
if (!isStreamClosed) {
|
||||
try {
|
||||
controller.enqueue(encoder.encode('data: [DONE]\n\n'))
|
||||
@@ -1174,10 +1167,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
cancel() {
|
||||
isStreamClosed = true
|
||||
logger.info(`[${requestId}] Client disconnected from SSE stream`)
|
||||
if (!sseDecremented) {
|
||||
sseDecremented = true
|
||||
decrementSSEConnections('workflow-execute')
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
getExecutionMeta,
|
||||
readExecutionEvents,
|
||||
} from '@/lib/execution/event-buffer'
|
||||
import { decrementSSEConnections, incrementSSEConnections } from '@/lib/monitoring/sse-connections'
|
||||
import { formatSSEEvent } from '@/lib/workflows/executor/execution-events'
|
||||
import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils'
|
||||
|
||||
@@ -74,10 +73,8 @@ export async function GET(
|
||||
|
||||
let closed = false
|
||||
|
||||
let sseDecremented = false
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
async start(controller) {
|
||||
incrementSSEConnections('execution-stream-reconnect')
|
||||
let lastEventId = fromEventId
|
||||
const pollDeadline = Date.now() + MAX_POLL_DURATION_MS
|
||||
|
||||
@@ -145,20 +142,11 @@ export async function GET(
|
||||
controller.close()
|
||||
} catch {}
|
||||
}
|
||||
} finally {
|
||||
if (!sseDecremented) {
|
||||
sseDecremented = true
|
||||
decrementSSEConnections('execution-stream-reconnect')
|
||||
}
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
closed = true
|
||||
logger.info('Client disconnected from reconnection stream', { executionId })
|
||||
if (!sseDecremented) {
|
||||
sseDecremented = true
|
||||
decrementSSEConnections('execution-stream-reconnect')
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
308
apps/sim/blocks/blocks/evernote.ts
Normal file
308
apps/sim/blocks/blocks/evernote.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
import { EvernoteIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
|
||||
export const EvernoteBlock: BlockConfig = {
|
||||
type: 'evernote',
|
||||
name: 'Evernote',
|
||||
description: 'Manage notes, notebooks, and tags in Evernote',
|
||||
longDescription:
|
||||
'Integrate with Evernote to manage notes, notebooks, and tags. Create, read, update, copy, search, and delete notes. Create and list notebooks and tags.',
|
||||
docsLink: 'https://docs.sim.ai/tools/evernote',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: EvernoteIcon,
|
||||
authMode: AuthMode.ApiKey,
|
||||
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Create Note', id: 'create_note' },
|
||||
{ label: 'Get Note', id: 'get_note' },
|
||||
{ label: 'Update Note', id: 'update_note' },
|
||||
{ label: 'Delete Note', id: 'delete_note' },
|
||||
{ label: 'Copy Note', id: 'copy_note' },
|
||||
{ label: 'Search Notes', id: 'search_notes' },
|
||||
{ label: 'Get Notebook', id: 'get_notebook' },
|
||||
{ label: 'Create Notebook', id: 'create_notebook' },
|
||||
{ label: 'List Notebooks', id: 'list_notebooks' },
|
||||
{ label: 'Create Tag', id: 'create_tag' },
|
||||
{ label: 'List Tags', id: 'list_tags' },
|
||||
],
|
||||
value: () => 'create_note',
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'Developer Token',
|
||||
type: 'short-input',
|
||||
password: true,
|
||||
placeholder: 'Enter your Evernote developer token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'title',
|
||||
title: 'Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'Note title',
|
||||
condition: { field: 'operation', value: 'create_note' },
|
||||
required: { field: 'operation', value: 'create_note' },
|
||||
},
|
||||
{
|
||||
id: 'content',
|
||||
title: 'Content',
|
||||
type: 'long-input',
|
||||
placeholder: 'Note content (plain text or ENML)',
|
||||
condition: { field: 'operation', value: 'create_note' },
|
||||
required: { field: 'operation', value: 'create_note' },
|
||||
},
|
||||
{
|
||||
id: 'noteGuid',
|
||||
title: 'Note GUID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter the note GUID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_note', 'update_note', 'delete_note', 'copy_note'],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['get_note', 'update_note', 'delete_note', 'copy_note'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'updateTitle',
|
||||
title: 'New Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'New title (leave empty to keep current)',
|
||||
condition: { field: 'operation', value: 'update_note' },
|
||||
},
|
||||
{
|
||||
id: 'updateContent',
|
||||
title: 'New Content',
|
||||
type: 'long-input',
|
||||
placeholder: 'New content (leave empty to keep current)',
|
||||
condition: { field: 'operation', value: 'update_note' },
|
||||
},
|
||||
{
|
||||
id: 'toNotebookGuid',
|
||||
title: 'Destination Notebook GUID',
|
||||
type: 'short-input',
|
||||
placeholder: 'GUID of the destination notebook',
|
||||
condition: { field: 'operation', value: 'copy_note' },
|
||||
required: { field: 'operation', value: 'copy_note' },
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
title: 'Search Query',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., "tag:work intitle:meeting"',
|
||||
condition: { field: 'operation', value: 'search_notes' },
|
||||
required: { field: 'operation', value: 'search_notes' },
|
||||
},
|
||||
{
|
||||
id: 'notebookGuid',
|
||||
title: 'Notebook GUID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Notebook GUID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_note', 'update_note', 'search_notes', 'get_notebook'],
|
||||
},
|
||||
required: { field: 'operation', value: 'get_notebook' },
|
||||
},
|
||||
{
|
||||
id: 'notebookName',
|
||||
title: 'Notebook Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Name for the new notebook',
|
||||
condition: { field: 'operation', value: 'create_notebook' },
|
||||
required: { field: 'operation', value: 'create_notebook' },
|
||||
},
|
||||
{
|
||||
id: 'stack',
|
||||
title: 'Stack',
|
||||
type: 'short-input',
|
||||
placeholder: 'Stack name (optional)',
|
||||
condition: { field: 'operation', value: 'create_notebook' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'tagName',
|
||||
title: 'Tag Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Name for the new tag',
|
||||
condition: { field: 'operation', value: 'create_tag' },
|
||||
required: { field: 'operation', value: 'create_tag' },
|
||||
},
|
||||
{
|
||||
id: 'parentGuid',
|
||||
title: 'Parent Tag GUID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Parent tag GUID (optional)',
|
||||
condition: { field: 'operation', value: 'create_tag' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'tagNames',
|
||||
title: 'Tags',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated tags (e.g., "work, meeting, urgent")',
|
||||
condition: { field: 'operation', value: ['create_note', 'update_note'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'maxNotes',
|
||||
title: 'Max Results',
|
||||
type: 'short-input',
|
||||
placeholder: '25',
|
||||
condition: { field: 'operation', value: 'search_notes' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'offset',
|
||||
title: 'Offset',
|
||||
type: 'short-input',
|
||||
placeholder: '0',
|
||||
condition: { field: 'operation', value: 'search_notes' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'withContent',
|
||||
title: 'Include Content',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Yes', id: 'true' },
|
||||
{ label: 'No', id: 'false' },
|
||||
],
|
||||
value: () => 'true',
|
||||
condition: { field: 'operation', value: 'get_note' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
],
|
||||
|
||||
tools: {
|
||||
access: [
|
||||
'evernote_copy_note',
|
||||
'evernote_create_note',
|
||||
'evernote_create_notebook',
|
||||
'evernote_create_tag',
|
||||
'evernote_delete_note',
|
||||
'evernote_get_note',
|
||||
'evernote_get_notebook',
|
||||
'evernote_list_notebooks',
|
||||
'evernote_list_tags',
|
||||
'evernote_search_notes',
|
||||
'evernote_update_note',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => `evernote_${params.operation}`,
|
||||
params: (params) => {
|
||||
const { operation, apiKey, ...rest } = params
|
||||
|
||||
switch (operation) {
|
||||
case 'create_note':
|
||||
return {
|
||||
apiKey,
|
||||
title: rest.title,
|
||||
content: rest.content,
|
||||
notebookGuid: rest.notebookGuid || undefined,
|
||||
tagNames: rest.tagNames || undefined,
|
||||
}
|
||||
case 'get_note':
|
||||
return {
|
||||
apiKey,
|
||||
noteGuid: rest.noteGuid,
|
||||
withContent: rest.withContent !== 'false',
|
||||
}
|
||||
case 'update_note':
|
||||
return {
|
||||
apiKey,
|
||||
noteGuid: rest.noteGuid,
|
||||
title: rest.updateTitle || undefined,
|
||||
content: rest.updateContent || undefined,
|
||||
notebookGuid: rest.notebookGuid || undefined,
|
||||
tagNames: rest.tagNames || undefined,
|
||||
}
|
||||
case 'delete_note':
|
||||
return {
|
||||
apiKey,
|
||||
noteGuid: rest.noteGuid,
|
||||
}
|
||||
case 'copy_note':
|
||||
return {
|
||||
apiKey,
|
||||
noteGuid: rest.noteGuid,
|
||||
toNotebookGuid: rest.toNotebookGuid,
|
||||
}
|
||||
case 'search_notes':
|
||||
return {
|
||||
apiKey,
|
||||
query: rest.query,
|
||||
notebookGuid: rest.notebookGuid || undefined,
|
||||
offset: rest.offset ? Number(rest.offset) : 0,
|
||||
maxNotes: rest.maxNotes ? Number(rest.maxNotes) : 25,
|
||||
}
|
||||
case 'get_notebook':
|
||||
return {
|
||||
apiKey,
|
||||
notebookGuid: rest.notebookGuid,
|
||||
}
|
||||
case 'create_notebook':
|
||||
return {
|
||||
apiKey,
|
||||
name: rest.notebookName,
|
||||
stack: rest.stack || undefined,
|
||||
}
|
||||
case 'list_notebooks':
|
||||
return { apiKey }
|
||||
case 'create_tag':
|
||||
return {
|
||||
apiKey,
|
||||
name: rest.tagName,
|
||||
parentGuid: rest.parentGuid || undefined,
|
||||
}
|
||||
case 'list_tags':
|
||||
return { apiKey }
|
||||
default:
|
||||
return { apiKey }
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
inputs: {
|
||||
apiKey: { type: 'string', description: 'Evernote developer token' },
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
title: { type: 'string', description: 'Note title' },
|
||||
content: { type: 'string', description: 'Note content' },
|
||||
noteGuid: { type: 'string', description: 'Note GUID' },
|
||||
updateTitle: { type: 'string', description: 'New note title' },
|
||||
updateContent: { type: 'string', description: 'New note content' },
|
||||
toNotebookGuid: { type: 'string', description: 'Destination notebook GUID' },
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
notebookGuid: { type: 'string', description: 'Notebook GUID' },
|
||||
notebookName: { type: 'string', description: 'Notebook name' },
|
||||
stack: { type: 'string', description: 'Notebook stack name' },
|
||||
tagName: { type: 'string', description: 'Tag name' },
|
||||
parentGuid: { type: 'string', description: 'Parent tag GUID' },
|
||||
tagNames: { type: 'string', description: 'Comma-separated tag names' },
|
||||
maxNotes: { type: 'string', description: 'Maximum number of results' },
|
||||
offset: { type: 'string', description: 'Starting index for results' },
|
||||
withContent: { type: 'string', description: 'Whether to include note content' },
|
||||
},
|
||||
|
||||
outputs: {
|
||||
note: { type: 'json', description: 'Note data' },
|
||||
notebook: { type: 'json', description: 'Notebook data' },
|
||||
notebooks: { type: 'json', description: 'List of notebooks' },
|
||||
tag: { type: 'json', description: 'Tag data' },
|
||||
tags: { type: 'json', description: 'List of tags' },
|
||||
totalNotes: { type: 'number', description: 'Total number of matching notes' },
|
||||
notes: { type: 'json', description: 'List of note metadata' },
|
||||
success: { type: 'boolean', description: 'Whether the operation succeeded' },
|
||||
noteGuid: { type: 'string', description: 'GUID of the affected note' },
|
||||
},
|
||||
}
|
||||
270
apps/sim/blocks/blocks/obsidian.ts
Normal file
270
apps/sim/blocks/blocks/obsidian.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { ObsidianIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
|
||||
export const ObsidianBlock: BlockConfig = {
|
||||
type: 'obsidian',
|
||||
name: 'Obsidian',
|
||||
description: 'Interact with your Obsidian vault via the Local REST API',
|
||||
longDescription:
|
||||
'Read, create, update, search, and delete notes in your Obsidian vault. Manage periodic notes, execute commands, and patch content at specific locations. Requires the Obsidian Local REST API plugin.',
|
||||
docsLink: 'https://docs.sim.ai/tools/obsidian',
|
||||
category: 'tools',
|
||||
bgColor: '#0F0F0F',
|
||||
icon: ObsidianIcon,
|
||||
authMode: AuthMode.ApiKey,
|
||||
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'List Files', id: 'list_files' },
|
||||
{ label: 'Get Note', id: 'get_note' },
|
||||
{ label: 'Create Note', id: 'create_note' },
|
||||
{ label: 'Append to Note', id: 'append_note' },
|
||||
{ label: 'Patch Note', id: 'patch_note' },
|
||||
{ label: 'Delete Note', id: 'delete_note' },
|
||||
{ label: 'Search', id: 'search' },
|
||||
{ label: 'Get Active File', id: 'get_active' },
|
||||
{ label: 'Append to Active File', id: 'append_active' },
|
||||
{ label: 'Patch Active File', id: 'patch_active' },
|
||||
{ label: 'Open File', id: 'open_file' },
|
||||
{ label: 'List Commands', id: 'list_commands' },
|
||||
{ label: 'Execute Command', id: 'execute_command' },
|
||||
{ label: 'Get Periodic Note', id: 'get_periodic_note' },
|
||||
{ label: 'Append to Periodic Note', id: 'append_periodic_note' },
|
||||
],
|
||||
value: () => 'get_note',
|
||||
},
|
||||
{
|
||||
id: 'baseUrl',
|
||||
title: 'Base URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'https://127.0.0.1:27124',
|
||||
value: () => 'https://127.0.0.1:27124',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'API Key',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter your Obsidian Local REST API key',
|
||||
password: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'path',
|
||||
title: 'Directory Path',
|
||||
type: 'short-input',
|
||||
placeholder: 'Leave empty for vault root (e.g. "Projects/notes")',
|
||||
condition: { field: 'operation', value: 'list_files' },
|
||||
},
|
||||
{
|
||||
id: 'filename',
|
||||
title: 'Note Path',
|
||||
type: 'short-input',
|
||||
placeholder: 'folder/note.md',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['get_note', 'create_note', 'append_note', 'patch_note', 'delete_note', 'open_file'],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['get_note', 'create_note', 'append_note', 'patch_note', 'delete_note', 'open_file'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'content',
|
||||
title: 'Content',
|
||||
type: 'long-input',
|
||||
placeholder: 'Markdown content',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'create_note',
|
||||
'append_note',
|
||||
'patch_note',
|
||||
'append_active',
|
||||
'patch_active',
|
||||
'append_periodic_note',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'create_note',
|
||||
'append_note',
|
||||
'patch_note',
|
||||
'append_active',
|
||||
'patch_active',
|
||||
'append_periodic_note',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'patchOperation',
|
||||
title: 'Patch Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Append', id: 'append' },
|
||||
{ label: 'Prepend', id: 'prepend' },
|
||||
{ label: 'Replace', id: 'replace' },
|
||||
],
|
||||
value: () => 'append',
|
||||
condition: { field: 'operation', value: ['patch_note', 'patch_active'] },
|
||||
required: { field: 'operation', value: ['patch_note', 'patch_active'] },
|
||||
},
|
||||
{
|
||||
id: 'targetType',
|
||||
title: 'Target Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Heading', id: 'heading' },
|
||||
{ label: 'Block Reference', id: 'block' },
|
||||
{ label: 'Frontmatter', id: 'frontmatter' },
|
||||
],
|
||||
value: () => 'heading',
|
||||
condition: { field: 'operation', value: ['patch_note', 'patch_active'] },
|
||||
required: { field: 'operation', value: ['patch_note', 'patch_active'] },
|
||||
},
|
||||
{
|
||||
id: 'target',
|
||||
title: 'Target',
|
||||
type: 'short-input',
|
||||
placeholder: 'Heading text, block ID, or frontmatter field',
|
||||
condition: { field: 'operation', value: ['patch_note', 'patch_active'] },
|
||||
required: { field: 'operation', value: ['patch_note', 'patch_active'] },
|
||||
},
|
||||
{
|
||||
id: 'targetDelimiter',
|
||||
title: 'Target Delimiter',
|
||||
type: 'short-input',
|
||||
placeholder: ':: (default)',
|
||||
condition: { field: 'operation', value: ['patch_note', 'patch_active'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'trimTargetWhitespace',
|
||||
title: 'Trim Target Whitespace',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: ['patch_note', 'patch_active'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
title: 'Search Query',
|
||||
type: 'short-input',
|
||||
placeholder: 'Text to search for',
|
||||
condition: { field: 'operation', value: 'search' },
|
||||
required: { field: 'operation', value: 'search' },
|
||||
},
|
||||
{
|
||||
id: 'contextLength',
|
||||
title: 'Context Length',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
condition: { field: 'operation', value: 'search' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'commandId',
|
||||
title: 'Command ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g. daily-notes:open-today',
|
||||
condition: { field: 'operation', value: 'execute_command' },
|
||||
required: { field: 'operation', value: 'execute_command' },
|
||||
},
|
||||
{
|
||||
id: 'newLeaf',
|
||||
title: 'Open in New Tab',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'open_file' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'period',
|
||||
title: 'Period',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Daily', id: 'daily' },
|
||||
{ label: 'Weekly', id: 'weekly' },
|
||||
{ label: 'Monthly', id: 'monthly' },
|
||||
{ label: 'Quarterly', id: 'quarterly' },
|
||||
{ label: 'Yearly', id: 'yearly' },
|
||||
],
|
||||
value: () => 'daily',
|
||||
condition: { field: 'operation', value: ['get_periodic_note', 'append_periodic_note'] },
|
||||
required: { field: 'operation', value: ['get_periodic_note', 'append_periodic_note'] },
|
||||
},
|
||||
],
|
||||
|
||||
tools: {
|
||||
access: [
|
||||
'obsidian_append_active',
|
||||
'obsidian_append_note',
|
||||
'obsidian_append_periodic_note',
|
||||
'obsidian_create_note',
|
||||
'obsidian_delete_note',
|
||||
'obsidian_execute_command',
|
||||
'obsidian_get_active',
|
||||
'obsidian_get_note',
|
||||
'obsidian_get_periodic_note',
|
||||
'obsidian_list_commands',
|
||||
'obsidian_list_files',
|
||||
'obsidian_open_file',
|
||||
'obsidian_patch_active',
|
||||
'obsidian_patch_note',
|
||||
'obsidian_search',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => `obsidian_${params.operation}`,
|
||||
params: (params) => {
|
||||
const result: Record<string, unknown> = {}
|
||||
if (params.contextLength) {
|
||||
result.contextLength = Number(params.contextLength)
|
||||
}
|
||||
if (params.patchOperation) {
|
||||
result.operation = params.patchOperation
|
||||
}
|
||||
return result
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
baseUrl: { type: 'string', description: 'Base URL for the Obsidian Local REST API' },
|
||||
apiKey: { type: 'string', description: 'API key for authentication' },
|
||||
filename: { type: 'string', description: 'Path to the note relative to vault root' },
|
||||
content: { type: 'string', description: 'Markdown content for the note' },
|
||||
path: { type: 'string', description: 'Directory path to list' },
|
||||
query: { type: 'string', description: 'Text to search for' },
|
||||
contextLength: { type: 'number', description: 'Characters of context around matches' },
|
||||
commandId: { type: 'string', description: 'ID of the command to execute' },
|
||||
patchOperation: { type: 'string', description: 'Patch operation: append, prepend, or replace' },
|
||||
targetType: { type: 'string', description: 'Target type: heading, block, or frontmatter' },
|
||||
target: { type: 'string', description: 'Target identifier for patch operations' },
|
||||
targetDelimiter: { type: 'string', description: 'Delimiter for nested headings' },
|
||||
trimTargetWhitespace: { type: 'boolean', description: 'Trim whitespace from target' },
|
||||
newLeaf: { type: 'boolean', description: 'Open file in new tab' },
|
||||
period: { type: 'string', description: 'Periodic note period type' },
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Markdown content of the note' },
|
||||
filename: { type: 'string', description: 'Path to the note' },
|
||||
files: { type: 'json', description: 'List of files and directories (path, type)' },
|
||||
results: { type: 'json', description: 'Search results (filename, score, matches)' },
|
||||
commands: { type: 'json', description: 'List of available commands (id, name)' },
|
||||
created: { type: 'boolean', description: 'Whether the note was created' },
|
||||
appended: { type: 'boolean', description: 'Whether content was appended' },
|
||||
patched: { type: 'boolean', description: 'Whether content was patched' },
|
||||
deleted: { type: 'boolean', description: 'Whether the note was deleted' },
|
||||
executed: { type: 'boolean', description: 'Whether the command was executed' },
|
||||
opened: { type: 'boolean', description: 'Whether the file was opened' },
|
||||
commandId: { type: 'string', description: 'ID of the executed command' },
|
||||
period: { type: 'string', description: 'Period type of the periodic note' },
|
||||
},
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import { ElasticsearchBlock } from '@/blocks/blocks/elasticsearch'
|
||||
import { ElevenLabsBlock } from '@/blocks/blocks/elevenlabs'
|
||||
import { EnrichBlock } from '@/blocks/blocks/enrich'
|
||||
import { EvaluatorBlock } from '@/blocks/blocks/evaluator'
|
||||
import { EvernoteBlock } from '@/blocks/blocks/evernote'
|
||||
import { ExaBlock } from '@/blocks/blocks/exa'
|
||||
import { FileBlock, FileV2Block, FileV3Block } from '@/blocks/blocks/file'
|
||||
import { FirecrawlBlock } from '@/blocks/blocks/firecrawl'
|
||||
@@ -113,6 +114,7 @@ import { MySQLBlock } from '@/blocks/blocks/mysql'
|
||||
import { Neo4jBlock } from '@/blocks/blocks/neo4j'
|
||||
import { NoteBlock } from '@/blocks/blocks/note'
|
||||
import { NotionBlock, NotionV2Block } from '@/blocks/blocks/notion'
|
||||
import { ObsidianBlock } from '@/blocks/blocks/obsidian'
|
||||
import { OneDriveBlock } from '@/blocks/blocks/onedrive'
|
||||
import { OnePasswordBlock } from '@/blocks/blocks/onepassword'
|
||||
import { OpenAIBlock } from '@/blocks/blocks/openai'
|
||||
@@ -234,6 +236,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
elasticsearch: ElasticsearchBlock,
|
||||
elevenlabs: ElevenLabsBlock,
|
||||
enrich: EnrichBlock,
|
||||
evernote: EvernoteBlock,
|
||||
evaluator: EvaluatorBlock,
|
||||
exa: ExaBlock,
|
||||
file: FileBlock,
|
||||
@@ -320,6 +323,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
note: NoteBlock,
|
||||
notion: NotionBlock,
|
||||
notion_v2: NotionV2Block,
|
||||
obsidian: ObsidianBlock,
|
||||
onepassword: OnePasswordBlock,
|
||||
onedrive: OneDriveBlock,
|
||||
openai: OpenAIBlock,
|
||||
|
||||
@@ -710,6 +710,155 @@ export function PerplexityIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function ObsidianIcon(props: SVGProps<SVGSVGElement>) {
|
||||
const id = useId()
|
||||
const bl = `${id}-bl`
|
||||
const tr = `${id}-tr`
|
||||
const tl = `${id}-tl`
|
||||
const br = `${id}-br`
|
||||
const te = `${id}-te`
|
||||
const le = `${id}-le`
|
||||
const be = `${id}-be`
|
||||
const me = `${id}-me`
|
||||
const clip = `${id}-clip`
|
||||
return (
|
||||
<svg {...props} viewBox='0 0 512 512' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<radialGradient
|
||||
id={bl}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(-59 -225 150 -39 161.4 470)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.4' />
|
||||
<stop offset='1' stopOpacity='.1' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={tr}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(50 -379 280 37 360 374.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.6' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.1' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={tl}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(69 -319 218 47 175.4 307)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.8' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.4' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={br}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(-96 -163 187 -111 335.3 512.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.3' />
|
||||
<stop offset='1' stopOpacity='.3' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={te}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(-36 166 -112 -24 310 128.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='0' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.2' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={le}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(88 89 -190 187 111 220.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.2' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.4' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={be}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(9 130 -276 20 215 284)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.2' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.3' />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id={me}
|
||||
cx='0'
|
||||
cy='0'
|
||||
gradientTransform='matrix(-198 -104 327 -623 400 399.2)'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
r='1'
|
||||
>
|
||||
<stop offset='0' stopColor='#fff' stopOpacity='.2' />
|
||||
<stop offset='.5' stopColor='#fff' stopOpacity='.2' />
|
||||
<stop offset='1' stopColor='#fff' stopOpacity='.3' />
|
||||
</radialGradient>
|
||||
<clipPath id={clip}>
|
||||
<path d='M.2.2h512v512H.2z' />
|
||||
</clipPath>
|
||||
<g clipPath={`url(#${clip})`}>
|
||||
<path
|
||||
d='M382.3 475.6c-3.1 23.4-26 41.6-48.7 35.3-32.4-8.9-69.9-22.8-103.6-25.4l-51.7-4a34 34 0 0 1-22-10.2l-89-91.7a34 34 0 0 1-6.7-37.7s55-121 57.1-127.3c2-6.3 9.6-61.2 14-90.6 1.2-7.9 5-15 11-20.3L248 8.9a34.1 34.1 0 0 1 49.6 4.3L386 125.6a37 37 0 0 1 7.6 22.4c0 21.3 1.8 65 13.6 93.2 11.5 27.3 32.5 57 43.5 71.5a17.3 17.3 0 0 1 1.3 19.2 1494 1494 0 0 1-44.8 70.6c-15 22.3-21.9 49.9-25 73.1z'
|
||||
fill='#6c31e3'
|
||||
/>
|
||||
<path
|
||||
d='M165.9 478.3c41.4-84 40.2-144.2 22.6-187-16.2-39.6-46.3-64.5-70-80-.6 2.3-1.3 4.4-2.2 6.5L60.6 342a34 34 0 0 0 6.6 37.7l89.1 91.7a34 34 0 0 0 9.6 7z'
|
||||
fill={`url(#${bl})`}
|
||||
/>
|
||||
<path
|
||||
d='M278.4 307.8c11.2 1.2 22.2 3.6 32.8 7.6 34 12.7 65 41.2 90.5 96.3 1.8-3.1 3.6-6.2 5.6-9.2a1536 1536 0 0 0 44.8-70.6 17 17 0 0 0-1.3-19.2c-11-14.6-32-44.2-43.5-71.5-11.8-28.2-13.5-72-13.6-93.2 0-8.1-2.6-16-7.6-22.4L297.6 13.2a34 34 0 0 0-1.5-1.7 96 96 0 0 1 2 54 198.3 198.3 0 0 1-17.6 41.3l-7.2 14.2a171 171 0 0 0-19.4 71c-1.2 29.4 4.8 66.4 24.5 115.8z'
|
||||
fill={`url(#${tr})`}
|
||||
/>
|
||||
<path
|
||||
d='M278.4 307.8c-19.7-49.4-25.8-86.4-24.5-115.9a171 171 0 0 1 19.4-71c2.3-4.8 4.8-9.5 7.2-14.1 7.1-13.9 14-27 17.6-41.4a96 96 0 0 0-2-54A34.1 34.1 0 0 0 248 9l-105.4 94.8a34.1 34.1 0 0 0-10.9 20.3l-12.8 85-.5 2.3c23.8 15.5 54 40.4 70.1 80a147 147 0 0 1 7.8 24.8c28-6.8 55.7-11 82.1-8.3z'
|
||||
fill={`url(#${tl})`}
|
||||
/>
|
||||
<path
|
||||
d='M333.6 511c22.7 6.2 45.6-12 48.7-35.4a187 187 0 0 1 19.4-63.9c-25.6-55-56.5-83.6-90.4-96.3-36-13.4-75.2-9-115 .7 8.9 40.4 3.6 93.3-30.4 162.2 4 1.8 8.1 3 12.5 3.3 0 0 24.4 2 53.6 4.1 29 2 72.4 17.1 101.6 25.2z'
|
||||
fill={`url(#${br})`}
|
||||
/>
|
||||
<g clipRule='evenodd' fillRule='evenodd'>
|
||||
<path
|
||||
d='M254.1 190c-1.3 29.2 2.4 62.8 22.1 112.1l-6.2-.5c-17.7-51.5-21.5-78-20.2-107.6a174.7 174.7 0 0 1 20.4-72c2.4-4.9 8-14.1 10.5-18.8 7.1-13.7 11.9-21 16-33.6 5.7-17.5 4.5-25.9 3.8-34.1 4.6 29.9-12.7 56-25.7 82.4a177.1 177.1 0 0 0-20.7 72z'
|
||||
fill={`url(#${te})`}
|
||||
/>
|
||||
<path
|
||||
d='M194.3 293.4c2.4 5.4 4.6 9.8 6 16.5L195 311c-2.1-7.8-3.8-13.4-6.8-20-17.8-42-46.3-63.6-69.7-79.5 28.2 15.2 57.2 39 75.7 81.9z'
|
||||
fill={`url(#${le})`}
|
||||
/>
|
||||
<path
|
||||
d='M200.6 315.1c9.8 46-1.2 104.2-33.6 160.9 27.1-56.2 40.2-110.1 29.3-160z'
|
||||
fill={`url(#${be})`}
|
||||
/>
|
||||
<path
|
||||
d='M312.5 311c53.1 19.9 73.6 63.6 88.9 100-19-38.1-45.2-80.3-90.8-96-34.8-11.8-64.1-10.4-114.3 1l-1.1-5c53.2-12.1 81-13.5 117.3 0z'
|
||||
fill={`url(#${me})`}
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function NotionIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' width='1em' height='1em' {...props}>
|
||||
@@ -1806,6 +1955,14 @@ export function Mem0Icon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function EvernoteIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='#7fce2c'>
|
||||
<path d='M29.343 16.818c.1 1.695-.08 3.368-.305 5.045-.225 1.712-.508 3.416-.964 5.084-.3 1.067-.673 2.1-1.202 3.074-.65 1.192-1.635 1.87-2.992 1.924l-3.832.036c-.636-.017-1.278-.146-1.9-.297-1.192-.3-1.862-1.1-2.06-2.3-.186-1.08-.173-2.187.04-3.264.252-1.23 1-1.96 2.234-2.103.817-.1 1.65-.077 2.476-.1.205-.007.275.098.203.287-.196.53-.236 1.07-.098 1.623.053.207-.023.307-.26.305a7.77 7.77 0 0 0-1.123.053c-.636.086-.96.47-.96 1.112 0 .205.026.416.066.622.103.507.45.78.944.837 1.123.127 2.247.138 3.37-.05.675-.114 1.08-.54 1.16-1.208.152-1.3.155-2.587-.228-3.845-.33-1.092-1.006-1.565-2.134-1.7l-3.36-.54c-1.06-.193-1.7-.887-1.92-1.9-.13-.572-.14-1.17-.214-1.757-.013-.106-.074-.208-.1-.3-.04.1-.106.212-.117.326-.066.68-.053 1.373-.185 2.04-.16.8-.404 1.566-.67 2.33-.185.535-.616.837-1.205.8a37.76 37.76 0 0 1-7.123-1.353l-.64-.207c-.927-.26-1.487-.903-1.74-1.787l-1-3.853-.74-4.3c-.115-.755-.2-1.523-.083-2.293.154-1.112.914-1.903 2.04-1.964l3.558-.062c.127 0 .254.003.373-.026a1.23 1.23 0 0 0 1.01-1.255l-.05-3.036c-.048-1.576.8-2.38 2.156-2.622a10.58 10.58 0 0 1 4.91.26c.933.275 1.467.923 1.715 1.83.058.22.146.3.37.287l2.582.01 3.333.37c.686.095 1.364.25 2.032.42 1.165.298 1.793 1.112 1.962 2.256l.357 3.355.3 5.577.01 2.277zm-4.534-1.155c-.02-.666-.07-1.267-.444-1.784a1.66 1.66 0 0 0-2.469-.15c-.364.4-.494.88-.564 1.4-.008.034.106.126.16.126l.8-.053c.768.007 1.523.113 2.25.393.066.026.136.04.265.077zM8.787 1.154a3.82 3.82 0 0 0-.278 1.592l.05 2.934c.005.357-.075.45-.433.45L5.1 6.156c-.583 0-1.143.1-1.554.278l5.2-5.332c.02.013.04.033.06.053z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ElevenLabsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
/**
|
||||
* Periodic memory telemetry for diagnosing heap growth in production.
|
||||
* Logs process.memoryUsage(), V8 heap stats, and active SSE connection
|
||||
* counts every 60s, enabling correlation between connection leaks and
|
||||
* memory spikes.
|
||||
* Periodic memory telemetry for monitoring heap growth in production.
|
||||
* Logs process.memoryUsage() and V8 heap stats every 60s.
|
||||
*/
|
||||
|
||||
import v8 from 'node:v8'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import {
|
||||
getActiveSSEConnectionCount,
|
||||
getActiveSSEConnectionsByRoute,
|
||||
} from '@/lib/monitoring/sse-connections'
|
||||
|
||||
const logger = createLogger('MemoryTelemetry', { logLevel: 'INFO' })
|
||||
|
||||
@@ -23,16 +17,6 @@ export function startMemoryTelemetry(intervalMs = 60_000) {
|
||||
started = true
|
||||
|
||||
const timer = setInterval(() => {
|
||||
// Trigger opportunistic (non-blocking) garbage collection if running on Bun.
|
||||
// This signals JSC GC + mimalloc page purge without blocking the event loop,
|
||||
// helping reclaim RSS that mimalloc otherwise retains under sustained load.
|
||||
const bunGlobal = (globalThis as Record<string, unknown>).Bun as
|
||||
| { gc?: (force: boolean) => void }
|
||||
| undefined
|
||||
if (typeof bunGlobal?.gc === 'function') {
|
||||
bunGlobal.gc(false)
|
||||
}
|
||||
|
||||
const mem = process.memoryUsage()
|
||||
const heap = v8.getHeapStatistics()
|
||||
|
||||
@@ -49,8 +33,6 @@ export function startMemoryTelemetry(intervalMs = 60_000) {
|
||||
? process.getActiveResourcesInfo().length
|
||||
: -1,
|
||||
uptimeMin: Math.round(process.uptime() / 60),
|
||||
activeSSEConnections: getActiveSSEConnectionCount(),
|
||||
sseByRoute: getActiveSSEConnectionsByRoute(),
|
||||
})
|
||||
}, intervalMs)
|
||||
timer.unref()
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Tracks active SSE connections by route for memory leak diagnostics.
|
||||
* Logged alongside periodic memory telemetry to correlate connection
|
||||
* counts with heap growth.
|
||||
*/
|
||||
|
||||
const connections = new Map<string, number>()
|
||||
|
||||
export function incrementSSEConnections(route: string) {
|
||||
connections.set(route, (connections.get(route) ?? 0) + 1)
|
||||
}
|
||||
|
||||
export function decrementSSEConnections(route: string) {
|
||||
const count = (connections.get(route) ?? 0) - 1
|
||||
if (count <= 0) connections.delete(route)
|
||||
else connections.set(route, count)
|
||||
}
|
||||
|
||||
export function getActiveSSEConnectionCount(): number {
|
||||
let total = 0
|
||||
for (const count of connections.values()) total += count
|
||||
return total
|
||||
}
|
||||
|
||||
export function getActiveSSEConnectionsByRoute(): Record<string, number> {
|
||||
return Object.fromEntries(connections)
|
||||
}
|
||||
@@ -1166,6 +1166,12 @@ export async function queueWebhookExecution(
|
||||
})
|
||||
}
|
||||
|
||||
// Slack requires an empty 200 for interactive payloads (view_submission, block_actions, etc.)
|
||||
// A JSON body like {"message":"..."} is not a recognized response format and causes modal errors
|
||||
if (foundWebhook.provider === 'slack') {
|
||||
return new NextResponse(null, { status: 200 })
|
||||
}
|
||||
|
||||
// Twilio Voice requires TwiML XML response
|
||||
if (foundWebhook.provider === 'twilio_voice') {
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
@@ -1211,6 +1217,12 @@ export async function queueWebhookExecution(
|
||||
)
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'slack') {
|
||||
// Return empty 200 to avoid Slack showing an error dialog to the user,
|
||||
// even though processing failed. The error is already logged above.
|
||||
return new NextResponse(null, { status: 200 })
|
||||
}
|
||||
|
||||
if (foundWebhook.provider === 'twilio_voice') {
|
||||
const errorTwiml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Response>
|
||||
|
||||
78
apps/sim/tools/evernote/copy_note.ts
Normal file
78
apps/sim/tools/evernote/copy_note.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteCopyNoteParams, EvernoteCopyNoteResponse } from './types'
|
||||
|
||||
export const evernoteCopyNoteTool: ToolConfig<EvernoteCopyNoteParams, EvernoteCopyNoteResponse> = {
|
||||
id: 'evernote_copy_note',
|
||||
name: 'Evernote Copy Note',
|
||||
description: 'Copy a note to another notebook in Evernote',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
noteGuid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the note to copy',
|
||||
},
|
||||
toNotebookGuid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the destination notebook',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/copy-note',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
noteGuid: params.noteGuid,
|
||||
toNotebookGuid: params.toNotebookGuid,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to copy note')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { note: data.output.note },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
note: {
|
||||
type: 'object',
|
||||
description: 'The copied note metadata',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'New note GUID' },
|
||||
title: { type: 'string', description: 'Note title' },
|
||||
notebookGuid: {
|
||||
type: 'string',
|
||||
description: 'GUID of the destination notebook',
|
||||
optional: true,
|
||||
},
|
||||
created: {
|
||||
type: 'number',
|
||||
description: 'Creation timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'number',
|
||||
description: 'Last updated timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
101
apps/sim/tools/evernote/create_note.ts
Normal file
101
apps/sim/tools/evernote/create_note.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteCreateNoteParams, EvernoteCreateNoteResponse } from './types'
|
||||
|
||||
export const evernoteCreateNoteTool: ToolConfig<
|
||||
EvernoteCreateNoteParams,
|
||||
EvernoteCreateNoteResponse
|
||||
> = {
|
||||
id: 'evernote_create_note',
|
||||
name: 'Evernote Create Note',
|
||||
description: 'Create a new note in Evernote',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Title of the note',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Content of the note (plain text or ENML)',
|
||||
},
|
||||
notebookGuid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the notebook to create the note in (defaults to default notebook)',
|
||||
},
|
||||
tagNames: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of tag names to apply',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/create-note',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
title: params.title,
|
||||
content: params.content,
|
||||
notebookGuid: params.notebookGuid || null,
|
||||
tagNames: params.tagNames || null,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to create note')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { note: data.output.note },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
note: {
|
||||
type: 'object',
|
||||
description: 'The created note',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Unique identifier of the note' },
|
||||
title: { type: 'string', description: 'Title of the note' },
|
||||
content: { type: 'string', description: 'ENML content of the note', optional: true },
|
||||
notebookGuid: {
|
||||
type: 'string',
|
||||
description: 'GUID of the containing notebook',
|
||||
optional: true,
|
||||
},
|
||||
tagNames: {
|
||||
type: 'array',
|
||||
description: 'Tag names applied to the note',
|
||||
optional: true,
|
||||
},
|
||||
created: {
|
||||
type: 'number',
|
||||
description: 'Creation timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'number',
|
||||
description: 'Last updated timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
78
apps/sim/tools/evernote/create_notebook.ts
Normal file
78
apps/sim/tools/evernote/create_notebook.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteCreateNotebookParams, EvernoteCreateNotebookResponse } from './types'
|
||||
|
||||
export const evernoteCreateNotebookTool: ToolConfig<
|
||||
EvernoteCreateNotebookParams,
|
||||
EvernoteCreateNotebookResponse
|
||||
> = {
|
||||
id: 'evernote_create_notebook',
|
||||
name: 'Evernote Create Notebook',
|
||||
description: 'Create a new notebook in Evernote',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name for the new notebook',
|
||||
},
|
||||
stack: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Stack name to group the notebook under',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/create-notebook',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
name: params.name,
|
||||
stack: params.stack || null,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to create notebook')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { notebook: data.output.notebook },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
notebook: {
|
||||
type: 'object',
|
||||
description: 'The created notebook',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Notebook GUID' },
|
||||
name: { type: 'string', description: 'Notebook name' },
|
||||
defaultNotebook: { type: 'boolean', description: 'Whether this is the default notebook' },
|
||||
serviceCreated: {
|
||||
type: 'number',
|
||||
description: 'Creation timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
serviceUpdated: {
|
||||
type: 'number',
|
||||
description: 'Last updated timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
stack: { type: 'string', description: 'Notebook stack name', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
70
apps/sim/tools/evernote/create_tag.ts
Normal file
70
apps/sim/tools/evernote/create_tag.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteCreateTagParams, EvernoteCreateTagResponse } from './types'
|
||||
|
||||
export const evernoteCreateTagTool: ToolConfig<EvernoteCreateTagParams, EvernoteCreateTagResponse> =
|
||||
{
|
||||
id: 'evernote_create_tag',
|
||||
name: 'Evernote Create Tag',
|
||||
description: 'Create a new tag in Evernote',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name for the new tag',
|
||||
},
|
||||
parentGuid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the parent tag for hierarchy',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/create-tag',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
name: params.name,
|
||||
parentGuid: params.parentGuid || null,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to create tag')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { tag: data.output.tag },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tag: {
|
||||
type: 'object',
|
||||
description: 'The created tag',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Tag GUID' },
|
||||
name: { type: 'string', description: 'Tag name' },
|
||||
parentGuid: { type: 'string', description: 'Parent tag GUID', optional: true },
|
||||
updateSequenceNum: {
|
||||
type: 'number',
|
||||
description: 'Update sequence number',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
62
apps/sim/tools/evernote/delete_note.ts
Normal file
62
apps/sim/tools/evernote/delete_note.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteDeleteNoteParams, EvernoteDeleteNoteResponse } from './types'
|
||||
|
||||
export const evernoteDeleteNoteTool: ToolConfig<
|
||||
EvernoteDeleteNoteParams,
|
||||
EvernoteDeleteNoteResponse
|
||||
> = {
|
||||
id: 'evernote_delete_note',
|
||||
name: 'Evernote Delete Note',
|
||||
description: 'Move a note to the trash in Evernote',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
noteGuid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the note to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/delete-note',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
noteGuid: params.noteGuid,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to delete note')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
success: true,
|
||||
noteGuid: data.output.noteGuid,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the note was successfully deleted',
|
||||
},
|
||||
noteGuid: {
|
||||
type: 'string',
|
||||
description: 'GUID of the deleted note',
|
||||
},
|
||||
},
|
||||
}
|
||||
87
apps/sim/tools/evernote/get_note.ts
Normal file
87
apps/sim/tools/evernote/get_note.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteGetNoteParams, EvernoteGetNoteResponse } from './types'
|
||||
|
||||
export const evernoteGetNoteTool: ToolConfig<EvernoteGetNoteParams, EvernoteGetNoteResponse> = {
|
||||
id: 'evernote_get_note',
|
||||
name: 'Evernote Get Note',
|
||||
description: 'Retrieve a note from Evernote by its GUID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
noteGuid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the note to retrieve',
|
||||
},
|
||||
withContent: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to include note content (default: true)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/get-note',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
noteGuid: params.noteGuid,
|
||||
withContent: params.withContent ?? true,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to get note')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { note: data.output.note },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
note: {
|
||||
type: 'object',
|
||||
description: 'The retrieved note',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Unique identifier of the note' },
|
||||
title: { type: 'string', description: 'Title of the note' },
|
||||
content: { type: 'string', description: 'ENML content of the note', optional: true },
|
||||
contentLength: {
|
||||
type: 'number',
|
||||
description: 'Length of the note content',
|
||||
optional: true,
|
||||
},
|
||||
notebookGuid: {
|
||||
type: 'string',
|
||||
description: 'GUID of the containing notebook',
|
||||
optional: true,
|
||||
},
|
||||
tagGuids: { type: 'array', description: 'GUIDs of tags on the note', optional: true },
|
||||
tagNames: { type: 'array', description: 'Names of tags on the note', optional: true },
|
||||
created: {
|
||||
type: 'number',
|
||||
description: 'Creation timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'number',
|
||||
description: 'Last updated timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
active: { type: 'boolean', description: 'Whether the note is active (not in trash)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
71
apps/sim/tools/evernote/get_notebook.ts
Normal file
71
apps/sim/tools/evernote/get_notebook.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteGetNotebookParams, EvernoteGetNotebookResponse } from './types'
|
||||
|
||||
export const evernoteGetNotebookTool: ToolConfig<
|
||||
EvernoteGetNotebookParams,
|
||||
EvernoteGetNotebookResponse
|
||||
> = {
|
||||
id: 'evernote_get_notebook',
|
||||
name: 'Evernote Get Notebook',
|
||||
description: 'Retrieve a notebook from Evernote by its GUID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
notebookGuid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the notebook to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/get-notebook',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
notebookGuid: params.notebookGuid,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to get notebook')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { notebook: data.output.notebook },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
notebook: {
|
||||
type: 'object',
|
||||
description: 'The retrieved notebook',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Notebook GUID' },
|
||||
name: { type: 'string', description: 'Notebook name' },
|
||||
defaultNotebook: { type: 'boolean', description: 'Whether this is the default notebook' },
|
||||
serviceCreated: {
|
||||
type: 'number',
|
||||
description: 'Creation timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
serviceUpdated: {
|
||||
type: 'number',
|
||||
description: 'Last updated timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
stack: { type: 'string', description: 'Notebook stack name', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
12
apps/sim/tools/evernote/index.ts
Normal file
12
apps/sim/tools/evernote/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export { evernoteCopyNoteTool } from './copy_note'
|
||||
export { evernoteCreateNoteTool } from './create_note'
|
||||
export { evernoteCreateNotebookTool } from './create_notebook'
|
||||
export { evernoteCreateTagTool } from './create_tag'
|
||||
export { evernoteDeleteNoteTool } from './delete_note'
|
||||
export { evernoteGetNoteTool } from './get_note'
|
||||
export { evernoteGetNotebookTool } from './get_notebook'
|
||||
export { evernoteListNotebooksTool } from './list_notebooks'
|
||||
export { evernoteListTagsTool } from './list_tags'
|
||||
export { evernoteSearchNotesTool } from './search_notes'
|
||||
export * from './types'
|
||||
export { evernoteUpdateNoteTool } from './update_note'
|
||||
64
apps/sim/tools/evernote/list_notebooks.ts
Normal file
64
apps/sim/tools/evernote/list_notebooks.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteListNotebooksParams, EvernoteListNotebooksResponse } from './types'
|
||||
|
||||
export const evernoteListNotebooksTool: ToolConfig<
|
||||
EvernoteListNotebooksParams,
|
||||
EvernoteListNotebooksResponse
|
||||
> = {
|
||||
id: 'evernote_list_notebooks',
|
||||
name: 'Evernote List Notebooks',
|
||||
description: 'List all notebooks in an Evernote account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/list-notebooks',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to list notebooks')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { notebooks: data.output.notebooks },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
notebooks: {
|
||||
type: 'array',
|
||||
description: 'List of notebooks',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Notebook GUID' },
|
||||
name: { type: 'string', description: 'Notebook name' },
|
||||
defaultNotebook: { type: 'boolean', description: 'Whether this is the default notebook' },
|
||||
serviceCreated: {
|
||||
type: 'number',
|
||||
description: 'Creation timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
serviceUpdated: {
|
||||
type: 'number',
|
||||
description: 'Last updated timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
stack: { type: 'string', description: 'Notebook stack name', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
55
apps/sim/tools/evernote/list_tags.ts
Normal file
55
apps/sim/tools/evernote/list_tags.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteListTagsParams, EvernoteListTagsResponse } from './types'
|
||||
|
||||
export const evernoteListTagsTool: ToolConfig<EvernoteListTagsParams, EvernoteListTagsResponse> = {
|
||||
id: 'evernote_list_tags',
|
||||
name: 'Evernote List Tags',
|
||||
description: 'List all tags in an Evernote account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/list-tags',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to list tags')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { tags: data.output.tags },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'List of tags',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Tag GUID' },
|
||||
name: { type: 'string', description: 'Tag name' },
|
||||
parentGuid: { type: 'string', description: 'Parent tag GUID', optional: true },
|
||||
updateSequenceNum: {
|
||||
type: 'number',
|
||||
description: 'Update sequence number',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
92
apps/sim/tools/evernote/search_notes.ts
Normal file
92
apps/sim/tools/evernote/search_notes.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteSearchNotesParams, EvernoteSearchNotesResponse } from './types'
|
||||
|
||||
export const evernoteSearchNotesTool: ToolConfig<
|
||||
EvernoteSearchNotesParams,
|
||||
EvernoteSearchNotesResponse
|
||||
> = {
|
||||
id: 'evernote_search_notes',
|
||||
name: 'Evernote Search Notes',
|
||||
description: 'Search for notes in Evernote using the Evernote search grammar',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search query using Evernote search grammar (e.g., "tag:work intitle:meeting")',
|
||||
},
|
||||
notebookGuid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Restrict search to a specific notebook by GUID',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Starting index for results (default: 0)',
|
||||
},
|
||||
maxNotes: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of notes to return (default: 25)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/search-notes',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
query: params.query,
|
||||
notebookGuid: params.notebookGuid || null,
|
||||
offset: params.offset ?? 0,
|
||||
maxNotes: params.maxNotes ?? 25,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to search notes')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
totalNotes: data.output.totalNotes,
|
||||
notes: data.output.notes,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
totalNotes: {
|
||||
type: 'number',
|
||||
description: 'Total number of matching notes',
|
||||
},
|
||||
notes: {
|
||||
type: 'array',
|
||||
description: 'List of matching note metadata',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Note GUID' },
|
||||
title: { type: 'string', description: 'Note title', optional: true },
|
||||
contentLength: { type: 'number', description: 'Content length in bytes', optional: true },
|
||||
created: { type: 'number', description: 'Creation timestamp', optional: true },
|
||||
updated: { type: 'number', description: 'Last updated timestamp', optional: true },
|
||||
notebookGuid: { type: 'string', description: 'Containing notebook GUID', optional: true },
|
||||
tagGuids: { type: 'array', description: 'Tag GUIDs', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
166
apps/sim/tools/evernote/types.ts
Normal file
166
apps/sim/tools/evernote/types.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface EvernoteBaseParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface EvernoteCreateNoteParams extends EvernoteBaseParams {
|
||||
title: string
|
||||
content: string
|
||||
notebookGuid?: string
|
||||
tagNames?: string
|
||||
}
|
||||
|
||||
export interface EvernoteGetNoteParams extends EvernoteBaseParams {
|
||||
noteGuid: string
|
||||
withContent?: boolean
|
||||
}
|
||||
|
||||
export interface EvernoteUpdateNoteParams extends EvernoteBaseParams {
|
||||
noteGuid: string
|
||||
title?: string
|
||||
content?: string
|
||||
notebookGuid?: string
|
||||
tagNames?: string
|
||||
}
|
||||
|
||||
export interface EvernoteDeleteNoteParams extends EvernoteBaseParams {
|
||||
noteGuid: string
|
||||
}
|
||||
|
||||
export interface EvernoteSearchNotesParams extends EvernoteBaseParams {
|
||||
query: string
|
||||
notebookGuid?: string
|
||||
offset?: number
|
||||
maxNotes?: number
|
||||
}
|
||||
|
||||
export interface EvernoteListNotebooksParams extends EvernoteBaseParams {}
|
||||
|
||||
export interface EvernoteGetNotebookParams extends EvernoteBaseParams {
|
||||
notebookGuid: string
|
||||
}
|
||||
|
||||
export interface EvernoteCreateNotebookParams extends EvernoteBaseParams {
|
||||
name: string
|
||||
stack?: string
|
||||
}
|
||||
|
||||
export interface EvernoteListTagsParams extends EvernoteBaseParams {}
|
||||
|
||||
export interface EvernoteCreateTagParams extends EvernoteBaseParams {
|
||||
name: string
|
||||
parentGuid?: string
|
||||
}
|
||||
|
||||
export interface EvernoteCopyNoteParams extends EvernoteBaseParams {
|
||||
noteGuid: string
|
||||
toNotebookGuid: string
|
||||
}
|
||||
|
||||
export interface EvernoteNoteOutput {
|
||||
guid: string
|
||||
title: string
|
||||
content: string | null
|
||||
contentLength: number | null
|
||||
created: number | null
|
||||
updated: number | null
|
||||
active: boolean
|
||||
notebookGuid: string | null
|
||||
tagGuids: string[]
|
||||
tagNames: string[]
|
||||
}
|
||||
|
||||
export interface EvernoteNotebookOutput {
|
||||
guid: string
|
||||
name: string
|
||||
defaultNotebook: boolean
|
||||
serviceCreated: number | null
|
||||
serviceUpdated: number | null
|
||||
stack: string | null
|
||||
}
|
||||
|
||||
export interface EvernoteNoteMetadataOutput {
|
||||
guid: string
|
||||
title: string | null
|
||||
contentLength: number | null
|
||||
created: number | null
|
||||
updated: number | null
|
||||
notebookGuid: string | null
|
||||
tagGuids: string[]
|
||||
}
|
||||
|
||||
export interface EvernoteTagOutput {
|
||||
guid: string
|
||||
name: string
|
||||
parentGuid: string | null
|
||||
updateSequenceNum: number | null
|
||||
}
|
||||
|
||||
export interface EvernoteCreateNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
note: EvernoteNoteOutput
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteGetNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
note: EvernoteNoteOutput
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteUpdateNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
note: EvernoteNoteOutput
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteDeleteNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
success: boolean
|
||||
noteGuid: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteSearchNotesResponse extends ToolResponse {
|
||||
output: {
|
||||
totalNotes: number
|
||||
notes: EvernoteNoteMetadataOutput[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteListNotebooksResponse extends ToolResponse {
|
||||
output: {
|
||||
notebooks: EvernoteNotebookOutput[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteGetNotebookResponse extends ToolResponse {
|
||||
output: {
|
||||
notebook: EvernoteNotebookOutput
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteCreateNotebookResponse extends ToolResponse {
|
||||
output: {
|
||||
notebook: EvernoteNotebookOutput
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteListTagsResponse extends ToolResponse {
|
||||
output: {
|
||||
tags: EvernoteTagOutput[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteCreateTagResponse extends ToolResponse {
|
||||
output: {
|
||||
tag: EvernoteTagOutput
|
||||
}
|
||||
}
|
||||
|
||||
export interface EvernoteCopyNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
note: EvernoteNoteOutput
|
||||
}
|
||||
}
|
||||
104
apps/sim/tools/evernote/update_note.ts
Normal file
104
apps/sim/tools/evernote/update_note.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { EvernoteUpdateNoteParams, EvernoteUpdateNoteResponse } from './types'
|
||||
|
||||
export const evernoteUpdateNoteTool: ToolConfig<
|
||||
EvernoteUpdateNoteParams,
|
||||
EvernoteUpdateNoteResponse
|
||||
> = {
|
||||
id: 'evernote_update_note',
|
||||
name: 'Evernote Update Note',
|
||||
description: 'Update an existing note in Evernote',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Evernote developer token',
|
||||
},
|
||||
noteGuid: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the note to update',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New title for the note',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New content for the note (plain text or ENML)',
|
||||
},
|
||||
notebookGuid: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GUID of the notebook to move the note to',
|
||||
},
|
||||
tagNames: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of tag names (replaces existing tags)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/evernote/update-note',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
apiKey: params.apiKey,
|
||||
noteGuid: params.noteGuid,
|
||||
title: params.title || null,
|
||||
content: params.content || null,
|
||||
notebookGuid: params.notebookGuid || null,
|
||||
tagNames: params.tagNames || null,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to update note')
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: { note: data.output.note },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
note: {
|
||||
type: 'object',
|
||||
description: 'The updated note',
|
||||
properties: {
|
||||
guid: { type: 'string', description: 'Unique identifier of the note' },
|
||||
title: { type: 'string', description: 'Title of the note' },
|
||||
content: { type: 'string', description: 'ENML content of the note', optional: true },
|
||||
notebookGuid: {
|
||||
type: 'string',
|
||||
description: 'GUID of the containing notebook',
|
||||
optional: true,
|
||||
},
|
||||
tagNames: { type: 'array', description: 'Tag names on the note', optional: true },
|
||||
created: {
|
||||
type: 'number',
|
||||
description: 'Creation timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'number',
|
||||
description: 'Last updated timestamp in milliseconds',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
66
apps/sim/tools/obsidian/append_active.ts
Normal file
66
apps/sim/tools/obsidian/append_active.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianAppendActiveParams, ObsidianAppendActiveResponse } from './types'
|
||||
|
||||
export const appendActiveTool: ToolConfig<
|
||||
ObsidianAppendActiveParams,
|
||||
ObsidianAppendActiveResponse
|
||||
> = {
|
||||
id: 'obsidian_append_active',
|
||||
name: 'Obsidian Append to Active File',
|
||||
description: 'Append content to the currently active file in Obsidian',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Markdown content to append to the active file',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/active/`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'text/markdown',
|
||||
}),
|
||||
body: (params) => params.content,
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to append to active file: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
appended: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
appended: {
|
||||
type: 'boolean',
|
||||
description: 'Whether content was successfully appended',
|
||||
},
|
||||
},
|
||||
}
|
||||
74
apps/sim/tools/obsidian/append_note.ts
Normal file
74
apps/sim/tools/obsidian/append_note.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianAppendNoteParams, ObsidianAppendNoteResponse } from './types'
|
||||
|
||||
export const appendNoteTool: ToolConfig<ObsidianAppendNoteParams, ObsidianAppendNoteResponse> = {
|
||||
id: 'obsidian_append_note',
|
||||
name: 'Obsidian Append to Note',
|
||||
description: 'Append content to an existing note in your Obsidian vault',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path to the note relative to vault root (e.g. "folder/note.md")',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Markdown content to append to the note',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'text/markdown',
|
||||
}),
|
||||
body: (params) => params.content,
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to append to note: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
filename: params?.filename ?? '',
|
||||
appended: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
filename: {
|
||||
type: 'string',
|
||||
description: 'Path of the note',
|
||||
},
|
||||
appended: {
|
||||
type: 'boolean',
|
||||
description: 'Whether content was successfully appended',
|
||||
},
|
||||
},
|
||||
}
|
||||
78
apps/sim/tools/obsidian/append_periodic_note.ts
Normal file
78
apps/sim/tools/obsidian/append_periodic_note.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianAppendPeriodicNoteParams, ObsidianAppendPeriodicNoteResponse } from './types'
|
||||
|
||||
export const appendPeriodicNoteTool: ToolConfig<
|
||||
ObsidianAppendPeriodicNoteParams,
|
||||
ObsidianAppendPeriodicNoteResponse
|
||||
> = {
|
||||
id: 'obsidian_append_periodic_note',
|
||||
name: 'Obsidian Append to Periodic Note',
|
||||
description:
|
||||
'Append content to the current periodic note (daily, weekly, monthly, quarterly, or yearly). Creates the note if it does not exist.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
period: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Period type: daily, weekly, monthly, quarterly, or yearly',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Markdown content to append to the periodic note',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/periodic/${encodeURIComponent(params.period)}/`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'text/markdown',
|
||||
}),
|
||||
body: (params) => params.content,
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to append to periodic note: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
period: params?.period ?? '',
|
||||
appended: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
period: {
|
||||
type: 'string',
|
||||
description: 'Period type of the note',
|
||||
},
|
||||
appended: {
|
||||
type: 'boolean',
|
||||
description: 'Whether content was successfully appended',
|
||||
},
|
||||
},
|
||||
}
|
||||
74
apps/sim/tools/obsidian/create_note.ts
Normal file
74
apps/sim/tools/obsidian/create_note.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianCreateNoteParams, ObsidianCreateNoteResponse } from './types'
|
||||
|
||||
export const createNoteTool: ToolConfig<ObsidianCreateNoteParams, ObsidianCreateNoteResponse> = {
|
||||
id: 'obsidian_create_note',
|
||||
name: 'Obsidian Create Note',
|
||||
description: 'Create or replace a note in your Obsidian vault',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path for the note relative to vault root (e.g. "folder/note.md")',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Markdown content for the note',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'text/markdown',
|
||||
}),
|
||||
body: (params) => params.content,
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to create note: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
filename: params?.filename ?? '',
|
||||
created: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
filename: {
|
||||
type: 'string',
|
||||
description: 'Path of the created note',
|
||||
},
|
||||
created: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the note was successfully created',
|
||||
},
|
||||
},
|
||||
}
|
||||
66
apps/sim/tools/obsidian/delete_note.ts
Normal file
66
apps/sim/tools/obsidian/delete_note.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianDeleteNoteParams, ObsidianDeleteNoteResponse } from './types'
|
||||
|
||||
export const deleteNoteTool: ToolConfig<ObsidianDeleteNoteParams, ObsidianDeleteNoteResponse> = {
|
||||
id: 'obsidian_delete_note',
|
||||
name: 'Obsidian Delete Note',
|
||||
description: 'Delete a note from your Obsidian vault',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path to the note to delete relative to vault root',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to delete note: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
filename: params?.filename ?? '',
|
||||
deleted: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
filename: {
|
||||
type: 'string',
|
||||
description: 'Path of the deleted note',
|
||||
},
|
||||
deleted: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the note was successfully deleted',
|
||||
},
|
||||
},
|
||||
}
|
||||
70
apps/sim/tools/obsidian/execute_command.ts
Normal file
70
apps/sim/tools/obsidian/execute_command.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianExecuteCommandParams, ObsidianExecuteCommandResponse } from './types'
|
||||
|
||||
export const executeCommandTool: ToolConfig<
|
||||
ObsidianExecuteCommandParams,
|
||||
ObsidianExecuteCommandResponse
|
||||
> = {
|
||||
id: 'obsidian_execute_command',
|
||||
name: 'Obsidian Execute Command',
|
||||
description: 'Execute a command in Obsidian (e.g. open daily note, toggle sidebar)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
commandId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'ID of the command to execute (use List Commands operation to discover available commands)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/commands/${encodeURIComponent(params.commandId.trim())}/`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to execute command: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
commandId: params?.commandId ?? '',
|
||||
executed: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
commandId: {
|
||||
type: 'string',
|
||||
description: 'ID of the executed command',
|
||||
},
|
||||
executed: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the command was successfully executed',
|
||||
},
|
||||
},
|
||||
}
|
||||
59
apps/sim/tools/obsidian/get_active.ts
Normal file
59
apps/sim/tools/obsidian/get_active.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianGetActiveParams, ObsidianGetActiveResponse } from './types'
|
||||
|
||||
export const getActiveTool: ToolConfig<ObsidianGetActiveParams, ObsidianGetActiveResponse> = {
|
||||
id: 'obsidian_get_active',
|
||||
name: 'Obsidian Get Active File',
|
||||
description: 'Retrieve the content of the currently active file in Obsidian',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/active/`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
Accept: 'application/vnd.olrapi.note+json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: data.content ?? '',
|
||||
filename: data.path ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Markdown content of the active file',
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
description: 'Path to the active file',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
68
apps/sim/tools/obsidian/get_note.ts
Normal file
68
apps/sim/tools/obsidian/get_note.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianGetNoteParams, ObsidianGetNoteResponse } from './types'
|
||||
|
||||
export const getNoteTool: ToolConfig<ObsidianGetNoteParams, ObsidianGetNoteResponse> = {
|
||||
id: 'obsidian_get_note',
|
||||
name: 'Obsidian Get Note',
|
||||
description: 'Retrieve the content of a note from your Obsidian vault',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path to the note relative to vault root (e.g. "folder/note.md")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
Accept: 'text/markdown',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to get note: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
const content = await response.text()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
filename: params?.filename ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Markdown content of the note',
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
description: 'Path to the note',
|
||||
},
|
||||
},
|
||||
}
|
||||
67
apps/sim/tools/obsidian/get_periodic_note.ts
Normal file
67
apps/sim/tools/obsidian/get_periodic_note.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianGetPeriodicNoteParams, ObsidianGetPeriodicNoteResponse } from './types'
|
||||
|
||||
export const getPeriodicNoteTool: ToolConfig<
|
||||
ObsidianGetPeriodicNoteParams,
|
||||
ObsidianGetPeriodicNoteResponse
|
||||
> = {
|
||||
id: 'obsidian_get_periodic_note',
|
||||
name: 'Obsidian Get Periodic Note',
|
||||
description: 'Retrieve the current periodic note (daily, weekly, monthly, quarterly, or yearly)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
period: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Period type: daily, weekly, monthly, quarterly, or yearly',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/periodic/${encodeURIComponent(params.period)}/`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
Accept: 'text/markdown',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const content = await response.text()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
period: params?.period ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Markdown content of the periodic note',
|
||||
},
|
||||
period: {
|
||||
type: 'string',
|
||||
description: 'Period type of the note',
|
||||
},
|
||||
},
|
||||
}
|
||||
16
apps/sim/tools/obsidian/index.ts
Normal file
16
apps/sim/tools/obsidian/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export { appendActiveTool as obsidianAppendActiveTool } from './append_active'
|
||||
export { appendNoteTool as obsidianAppendNoteTool } from './append_note'
|
||||
export { appendPeriodicNoteTool as obsidianAppendPeriodicNoteTool } from './append_periodic_note'
|
||||
export { createNoteTool as obsidianCreateNoteTool } from './create_note'
|
||||
export { deleteNoteTool as obsidianDeleteNoteTool } from './delete_note'
|
||||
export { executeCommandTool as obsidianExecuteCommandTool } from './execute_command'
|
||||
export { getActiveTool as obsidianGetActiveTool } from './get_active'
|
||||
export { getNoteTool as obsidianGetNoteTool } from './get_note'
|
||||
export { getPeriodicNoteTool as obsidianGetPeriodicNoteTool } from './get_periodic_note'
|
||||
export { listCommandsTool as obsidianListCommandsTool } from './list_commands'
|
||||
export { listFilesTool as obsidianListFilesTool } from './list_files'
|
||||
export { openFileTool as obsidianOpenFileTool } from './open_file'
|
||||
export { patchActiveTool as obsidianPatchActiveTool } from './patch_active'
|
||||
export { patchNoteTool as obsidianPatchNoteTool } from './patch_note'
|
||||
export { searchTool as obsidianSearchTool } from './search'
|
||||
export * from './types'
|
||||
68
apps/sim/tools/obsidian/list_commands.ts
Normal file
68
apps/sim/tools/obsidian/list_commands.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianListCommandsParams, ObsidianListCommandsResponse } from './types'
|
||||
|
||||
export const listCommandsTool: ToolConfig<
|
||||
ObsidianListCommandsParams,
|
||||
ObsidianListCommandsResponse
|
||||
> = {
|
||||
id: 'obsidian_list_commands',
|
||||
name: 'Obsidian List Commands',
|
||||
description: 'List all available commands in Obsidian',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/commands/`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to list commands: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
commands:
|
||||
data.commands?.map((cmd: { id: string; name: string }) => ({
|
||||
id: cmd.id ?? '',
|
||||
name: cmd.name ?? '',
|
||||
})) ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
commands: {
|
||||
type: 'json',
|
||||
description: 'List of available commands with IDs and names',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Command identifier' },
|
||||
name: { type: 'string', description: 'Human-readable command name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
76
apps/sim/tools/obsidian/list_files.ts
Normal file
76
apps/sim/tools/obsidian/list_files.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianListFilesParams, ObsidianListFilesResponse } from './types'
|
||||
|
||||
export const listFilesTool: ToolConfig<ObsidianListFilesParams, ObsidianListFilesResponse> = {
|
||||
id: 'obsidian_list_files',
|
||||
name: 'Obsidian List Files',
|
||||
description: 'List files and directories in your Obsidian vault',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Directory path relative to vault root. Leave empty to list root.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
const path = params.path
|
||||
? `/${params.path.trim().split('/').map(encodeURIComponent).join('/')}/`
|
||||
: '/'
|
||||
return `${base}/vault${path}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to list files: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
files:
|
||||
data.files?.map((f: string | { path: string; type: string }) => {
|
||||
if (typeof f === 'string') {
|
||||
return { path: f, type: f.endsWith('/') ? 'directory' : 'file' }
|
||||
}
|
||||
return { path: f.path ?? '', type: f.type ?? 'file' }
|
||||
}) ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
files: {
|
||||
type: 'json',
|
||||
description: 'List of files and directories',
|
||||
properties: {
|
||||
path: { type: 'string', description: 'File or directory path' },
|
||||
type: { type: 'string', description: 'Whether the entry is a file or directory' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
73
apps/sim/tools/obsidian/open_file.ts
Normal file
73
apps/sim/tools/obsidian/open_file.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianOpenFileParams, ObsidianOpenFileResponse } from './types'
|
||||
|
||||
export const openFileTool: ToolConfig<ObsidianOpenFileParams, ObsidianOpenFileResponse> = {
|
||||
id: 'obsidian_open_file',
|
||||
name: 'Obsidian Open File',
|
||||
description: 'Open a file in the Obsidian UI (creates the file if it does not exist)',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path to the file relative to vault root',
|
||||
},
|
||||
newLeaf: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to open the file in a new leaf/tab',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
const leafParam = params.newLeaf ? '?newLeaf=true' : ''
|
||||
return `${base}/open/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}${leafParam}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to open file: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
filename: params?.filename ?? '',
|
||||
opened: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
filename: {
|
||||
type: 'string',
|
||||
description: 'Path of the opened file',
|
||||
},
|
||||
opened: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the file was successfully opened',
|
||||
},
|
||||
},
|
||||
}
|
||||
107
apps/sim/tools/obsidian/patch_active.ts
Normal file
107
apps/sim/tools/obsidian/patch_active.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianPatchActiveParams, ObsidianPatchActiveResponse } from './types'
|
||||
|
||||
export const patchActiveTool: ToolConfig<ObsidianPatchActiveParams, ObsidianPatchActiveResponse> = {
|
||||
id: 'obsidian_patch_active',
|
||||
name: 'Obsidian Patch Active File',
|
||||
description:
|
||||
'Insert or replace content at a specific heading, block reference, or frontmatter field in the active file',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Content to insert at the target location',
|
||||
},
|
||||
operation: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'How to insert content: append, prepend, or replace',
|
||||
},
|
||||
targetType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Type of target: heading, block, or frontmatter',
|
||||
},
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Target identifier (heading text, block reference ID, or frontmatter field name)',
|
||||
},
|
||||
targetDelimiter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Delimiter for nested headings (default: "::")',
|
||||
},
|
||||
trimTargetWhitespace: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to trim whitespace from target before matching (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/active/`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'text/markdown',
|
||||
Operation: params.operation,
|
||||
'Target-Type': params.targetType,
|
||||
Target: encodeURIComponent(params.target),
|
||||
}
|
||||
if (params.targetDelimiter) {
|
||||
headers['Target-Delimiter'] = params.targetDelimiter
|
||||
}
|
||||
if (params.trimTargetWhitespace) {
|
||||
headers['Trim-Target-Whitespace'] = 'true'
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => params.content,
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to patch active file: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
patched: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
patched: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the active file was successfully patched',
|
||||
},
|
||||
},
|
||||
}
|
||||
118
apps/sim/tools/obsidian/patch_note.ts
Normal file
118
apps/sim/tools/obsidian/patch_note.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianPatchNoteParams, ObsidianPatchNoteResponse } from './types'
|
||||
|
||||
export const patchNoteTool: ToolConfig<ObsidianPatchNoteParams, ObsidianPatchNoteResponse> = {
|
||||
id: 'obsidian_patch_note',
|
||||
name: 'Obsidian Patch Note',
|
||||
description:
|
||||
'Insert or replace content at a specific heading, block reference, or frontmatter field in a note',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
filename: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path to the note relative to vault root (e.g. "folder/note.md")',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Content to insert at the target location',
|
||||
},
|
||||
operation: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'How to insert content: append, prepend, or replace',
|
||||
},
|
||||
targetType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Type of target: heading, block, or frontmatter',
|
||||
},
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Target identifier (heading text, block reference ID, or frontmatter field name)',
|
||||
},
|
||||
targetDelimiter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Delimiter for nested headings (default: "::")',
|
||||
},
|
||||
trimTargetWhitespace: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to trim whitespace from target before matching (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
return `${base}/vault/${params.filename.trim().split('/').map(encodeURIComponent).join('/')}`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'text/markdown',
|
||||
Operation: params.operation,
|
||||
'Target-Type': params.targetType,
|
||||
Target: encodeURIComponent(params.target),
|
||||
}
|
||||
if (params.targetDelimiter) {
|
||||
headers['Target-Delimiter'] = params.targetDelimiter
|
||||
}
|
||||
if (params.trimTargetWhitespace) {
|
||||
headers['Trim-Target-Whitespace'] = 'true'
|
||||
}
|
||||
return headers
|
||||
},
|
||||
body: (params) => params.content,
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Failed to patch note: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
filename: params?.filename ?? '',
|
||||
patched: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
filename: {
|
||||
type: 'string',
|
||||
description: 'Path of the patched note',
|
||||
},
|
||||
patched: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the note was successfully patched',
|
||||
},
|
||||
},
|
||||
}
|
||||
95
apps/sim/tools/obsidian/search.ts
Normal file
95
apps/sim/tools/obsidian/search.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { ObsidianSearchParams, ObsidianSearchResponse } from './types'
|
||||
|
||||
export const searchTool: ToolConfig<ObsidianSearchParams, ObsidianSearchResponse> = {
|
||||
id: 'obsidian_search',
|
||||
name: 'Obsidian Search',
|
||||
description: 'Search for text across notes in your Obsidian vault',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'API key from Obsidian Local REST API plugin settings',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Base URL for the Obsidian Local REST API',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Text to search for across vault notes',
|
||||
},
|
||||
contextLength: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of characters of context around each match (default: 100)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const base = params.baseUrl.replace(/\/$/, '')
|
||||
const contextParam = params.contextLength ? `&contextLength=${params.contextLength}` : ''
|
||||
return `${base}/search/simple/?query=${encodeURIComponent(params.query)}${contextParam}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: 'Unknown error' }))
|
||||
throw new Error(`Search failed: ${error.message ?? response.statusText}`)
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results:
|
||||
data?.map(
|
||||
(item: {
|
||||
filename: string
|
||||
score: number
|
||||
matches: Array<{ match: { start: number; end: number }; context: string }>
|
||||
}) => ({
|
||||
filename: item.filename ?? '',
|
||||
score: item.score ?? 0,
|
||||
matches:
|
||||
item.matches?.map((m: { context: string }) => ({
|
||||
context: m.context ?? '',
|
||||
})) ?? [],
|
||||
})
|
||||
) ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'json',
|
||||
description: 'Search results with filenames, scores, and matching contexts',
|
||||
properties: {
|
||||
filename: { type: 'string', description: 'Path to the matching note' },
|
||||
score: { type: 'number', description: 'Relevance score' },
|
||||
matches: {
|
||||
type: 'json',
|
||||
description: 'Matching text contexts',
|
||||
properties: {
|
||||
context: { type: 'string', description: 'Text surrounding the match' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
190
apps/sim/tools/obsidian/types.ts
Normal file
190
apps/sim/tools/obsidian/types.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface ObsidianBaseParams {
|
||||
apiKey: string
|
||||
baseUrl: string
|
||||
}
|
||||
|
||||
export interface ObsidianListFilesParams extends ObsidianBaseParams {
|
||||
path?: string
|
||||
}
|
||||
|
||||
export interface ObsidianListFilesResponse extends ToolResponse {
|
||||
output: {
|
||||
files: Array<{
|
||||
path: string
|
||||
type: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianGetNoteParams extends ObsidianBaseParams {
|
||||
filename: string
|
||||
}
|
||||
|
||||
export interface ObsidianGetNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
filename: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianCreateNoteParams extends ObsidianBaseParams {
|
||||
filename: string
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface ObsidianCreateNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
filename: string
|
||||
created: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianAppendNoteParams extends ObsidianBaseParams {
|
||||
filename: string
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface ObsidianAppendNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
filename: string
|
||||
appended: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianPatchNoteParams extends ObsidianBaseParams {
|
||||
filename: string
|
||||
content: string
|
||||
operation: string
|
||||
targetType: string
|
||||
target: string
|
||||
targetDelimiter?: string
|
||||
trimTargetWhitespace?: boolean
|
||||
}
|
||||
|
||||
export interface ObsidianPatchNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
filename: string
|
||||
patched: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianDeleteNoteParams extends ObsidianBaseParams {
|
||||
filename: string
|
||||
}
|
||||
|
||||
export interface ObsidianDeleteNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
filename: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianSearchParams extends ObsidianBaseParams {
|
||||
query: string
|
||||
contextLength?: number
|
||||
}
|
||||
|
||||
export interface ObsidianSearchResponse extends ToolResponse {
|
||||
output: {
|
||||
results: Array<{
|
||||
filename: string
|
||||
score: number
|
||||
matches: Array<{
|
||||
context: string
|
||||
}>
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianGetActiveParams extends ObsidianBaseParams {}
|
||||
|
||||
export interface ObsidianGetActiveResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
filename: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianAppendActiveParams extends ObsidianBaseParams {
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface ObsidianAppendActiveResponse extends ToolResponse {
|
||||
output: {
|
||||
appended: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianPatchActiveParams extends ObsidianBaseParams {
|
||||
content: string
|
||||
operation: string
|
||||
targetType: string
|
||||
target: string
|
||||
targetDelimiter?: string
|
||||
trimTargetWhitespace?: boolean
|
||||
}
|
||||
|
||||
export interface ObsidianPatchActiveResponse extends ToolResponse {
|
||||
output: {
|
||||
patched: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianListCommandsParams extends ObsidianBaseParams {}
|
||||
|
||||
export interface ObsidianListCommandsResponse extends ToolResponse {
|
||||
output: {
|
||||
commands: Array<{
|
||||
id: string
|
||||
name: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianExecuteCommandParams extends ObsidianBaseParams {
|
||||
commandId: string
|
||||
}
|
||||
|
||||
export interface ObsidianExecuteCommandResponse extends ToolResponse {
|
||||
output: {
|
||||
commandId: string
|
||||
executed: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianOpenFileParams extends ObsidianBaseParams {
|
||||
filename: string
|
||||
newLeaf?: boolean
|
||||
}
|
||||
|
||||
export interface ObsidianOpenFileResponse extends ToolResponse {
|
||||
output: {
|
||||
filename: string
|
||||
opened: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianGetPeriodicNoteParams extends ObsidianBaseParams {
|
||||
period: string
|
||||
}
|
||||
|
||||
export interface ObsidianGetPeriodicNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
period: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObsidianAppendPeriodicNoteParams extends ObsidianBaseParams {
|
||||
period: string
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface ObsidianAppendPeriodicNoteResponse extends ToolResponse {
|
||||
output: {
|
||||
period: string
|
||||
appended: boolean
|
||||
}
|
||||
}
|
||||
@@ -426,6 +426,19 @@ import {
|
||||
enrichSearchSimilarCompaniesTool,
|
||||
enrichVerifyEmailTool,
|
||||
} from '@/tools/enrich'
|
||||
import {
|
||||
evernoteCopyNoteTool,
|
||||
evernoteCreateNotebookTool,
|
||||
evernoteCreateNoteTool,
|
||||
evernoteCreateTagTool,
|
||||
evernoteDeleteNoteTool,
|
||||
evernoteGetNotebookTool,
|
||||
evernoteGetNoteTool,
|
||||
evernoteListNotebooksTool,
|
||||
evernoteListTagsTool,
|
||||
evernoteSearchNotesTool,
|
||||
evernoteUpdateNoteTool,
|
||||
} from '@/tools/evernote'
|
||||
import {
|
||||
exaAnswerTool,
|
||||
exaFindSimilarLinksTool,
|
||||
@@ -1450,6 +1463,23 @@ import {
|
||||
notionWriteTool,
|
||||
notionWriteV2Tool,
|
||||
} from '@/tools/notion'
|
||||
import {
|
||||
obsidianAppendActiveTool,
|
||||
obsidianAppendNoteTool,
|
||||
obsidianAppendPeriodicNoteTool,
|
||||
obsidianCreateNoteTool,
|
||||
obsidianDeleteNoteTool,
|
||||
obsidianExecuteCommandTool,
|
||||
obsidianGetActiveTool,
|
||||
obsidianGetNoteTool,
|
||||
obsidianGetPeriodicNoteTool,
|
||||
obsidianListCommandsTool,
|
||||
obsidianListFilesTool,
|
||||
obsidianOpenFileTool,
|
||||
obsidianPatchActiveTool,
|
||||
obsidianPatchNoteTool,
|
||||
obsidianSearchTool,
|
||||
} from '@/tools/obsidian'
|
||||
import {
|
||||
onedriveCreateFolderTool,
|
||||
onedriveDeleteTool,
|
||||
@@ -2739,6 +2769,21 @@ export const tools: Record<string, ToolConfig> = {
|
||||
notion_create_database_v2: notionCreateDatabaseV2Tool,
|
||||
notion_update_page_v2: notionUpdatePageV2Tool,
|
||||
notion_add_database_row_v2: notionAddDatabaseRowTool,
|
||||
obsidian_append_active: obsidianAppendActiveTool,
|
||||
obsidian_append_note: obsidianAppendNoteTool,
|
||||
obsidian_append_periodic_note: obsidianAppendPeriodicNoteTool,
|
||||
obsidian_create_note: obsidianCreateNoteTool,
|
||||
obsidian_delete_note: obsidianDeleteNoteTool,
|
||||
obsidian_execute_command: obsidianExecuteCommandTool,
|
||||
obsidian_get_active: obsidianGetActiveTool,
|
||||
obsidian_get_note: obsidianGetNoteTool,
|
||||
obsidian_get_periodic_note: obsidianGetPeriodicNoteTool,
|
||||
obsidian_list_commands: obsidianListCommandsTool,
|
||||
obsidian_list_files: obsidianListFilesTool,
|
||||
obsidian_open_file: obsidianOpenFileTool,
|
||||
obsidian_patch_active: obsidianPatchActiveTool,
|
||||
obsidian_patch_note: obsidianPatchNoteTool,
|
||||
obsidian_search: obsidianSearchTool,
|
||||
onepassword_list_vaults: onepasswordListVaultsTool,
|
||||
onepassword_get_vault: onepasswordGetVaultTool,
|
||||
onepassword_list_items: onepasswordListItemsTool,
|
||||
@@ -3122,6 +3167,17 @@ export const tools: Record<string, ToolConfig> = {
|
||||
elasticsearch_list_indices: elasticsearchListIndicesTool,
|
||||
elasticsearch_cluster_health: elasticsearchClusterHealthTool,
|
||||
elasticsearch_cluster_stats: elasticsearchClusterStatsTool,
|
||||
evernote_copy_note: evernoteCopyNoteTool,
|
||||
evernote_create_note: evernoteCreateNoteTool,
|
||||
evernote_create_notebook: evernoteCreateNotebookTool,
|
||||
evernote_create_tag: evernoteCreateTagTool,
|
||||
evernote_delete_note: evernoteDeleteNoteTool,
|
||||
evernote_get_note: evernoteGetNoteTool,
|
||||
evernote_get_notebook: evernoteGetNotebookTool,
|
||||
evernote_list_notebooks: evernoteListNotebooksTool,
|
||||
evernote_list_tags: evernoteListTagsTool,
|
||||
evernote_search_notes: evernoteSearchNotesTool,
|
||||
evernote_update_note: evernoteUpdateNoteTool,
|
||||
enrich_check_credits: enrichCheckCreditsTool,
|
||||
enrich_company_funding: enrichCompanyFundingTool,
|
||||
enrich_company_lookup: enrichCompanyLookupTool,
|
||||
|
||||
Reference in New Issue
Block a user