mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
feat(evernote): add Evernote integration with 11 tools (#3456)
* feat(evernote): add Evernote integration with 11 tools * fix(evernote): fix signed integer mismatch in Thrift version check * fix(evernote): fix exception field mapping and add sandbox support * fix(evernote): address PR review feedback * fix(evernote): clamp maxNotes to Evernote's 250 limit Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1955,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,
|
||||
@@ -203,6 +204,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
elasticsearch: ElasticsearchIcon,
|
||||
elevenlabs: ElevenLabsIcon,
|
||||
enrich: EnrichSoIcon,
|
||||
evernote: EvernoteIcon,
|
||||
exa: ExaAIIcon,
|
||||
file_v3: DocumentIcon,
|
||||
firecrawl: FirecrawlIcon,
|
||||
|
||||
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",
|
||||
|
||||
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 })
|
||||
}
|
||||
}
|
||||
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' },
|
||||
},
|
||||
}
|
||||
@@ -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'
|
||||
@@ -235,6 +236,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
elasticsearch: ElasticsearchBlock,
|
||||
elevenlabs: ElevenLabsBlock,
|
||||
enrich: EnrichBlock,
|
||||
evernote: EvernoteBlock,
|
||||
evaluator: EvaluatorBlock,
|
||||
exa: ExaBlock,
|
||||
file: FileBlock,
|
||||
|
||||
@@ -1955,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
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
@@ -3154,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