feat(tools): added zoom, elasticsearch, dropbox, kalshi, polymarket, datadog, ahrefs, gitlab, shopify, ssh, wordpress (#2175)

* feat(tools): added zoom, elasticsearch, dropbox, box, datadog, ahrefs, gitlab, shopify, ssh, wordpress

* added polymarket & kalshi, fixed ssh

* fix search modal bg instead of bgColor, added polymarket and kalshi new endpoints

* split up grafana

* update docs script & docs

* added more zoom endpoints

* remove unused box creds

* finished wordpress, shopify, kalshi

* cleanup

* revert envvar dropdown changes

* updated grafana endpoints
This commit is contained in:
Waleed
2025-12-03 20:09:27 -08:00
committed by GitHub
parent 08a11935af
commit 5630e133fd
272 changed files with 42658 additions and 64 deletions

View File

@@ -473,6 +473,30 @@ export function GithubIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function GitLabIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<path
d='M22.65 14.39L12 22.13 1.35 14.39a.84.84 0 0 1-.3-.94l1.22-3.78 2.44-7.51A.42.42 0 0 1 4.82 2a.43.43 0 0 1 .58 0 .42.42 0 0 1 .11.18l2.44 7.49h8.1l2.44-7.51A.42.42 0 0 1 18.6 2a.43.43 0 0 1 .58 0 .42.42 0 0 1 .11.18l2.44 7.51L23 13.45a.84.84 0 0 1-.35.94z'
fill='#FC6D26'
/>
<path d='M12 22.13L16.05 9.67H7.95L12 22.13z' fill='#E24329' />
<path d='M12 22.13L7.95 9.67H1.69L12 22.13z' fill='#FC6D26' />
<path d='M1.69 9.67L.47 13.45a.84.84 0 0 0 .3.94L12 22.13 1.69 9.67z' fill='#FCA326' />
<path
d='M1.69 9.67H7.95L5.51 2.16A.42.42 0 0 0 4.93 2a.42.42 0 0 0-.11.18L1.69 9.67z'
fill='#E24329'
/>
<path d='M12 22.13L16.05 9.67H22.31L12 22.13z' fill='#FC6D26' />
<path d='M22.31 9.67L23.53 13.45a.84.84 0 0 1-.3.94L12 22.13 22.31 9.67z' fill='#FCA326' />
<path
d='M22.31 9.67H16.05l2.44-7.51A.42.42 0 0 1 19.07 2a.42.42 0 0 1 .11.18l3.13 7.49z'
fill='#E24329'
/>
</svg>
)
}
export function SerperIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox='0 0 654 600' xmlns='http://www.w3.org/2000/svg' {...props}>
@@ -649,6 +673,37 @@ export function GmailIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function GrafanaIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='800px'
height='800px'
viewBox='0 0 16 16'
xmlns='http://www.w3.org/2000/svg'
fill='none'
>
<path
fill='url(#grafana-color-16__paint0_linear_2372_364)'
d='M13.985 7.175a4.408 4.408 0 00-.138-.802 5.035 5.035 0 00-1.054-1.998 2.96 2.96 0 00-.366-.393c.198-.787-.245-1.468-.245-1.468-.764-.046-1.237.227-1.42.363-.031-.015-.062-.03-.092-.03-.122-.046-.26-.106-.397-.137-.138-.045-.275-.075-.413-.12-.137-.031-.29-.061-.443-.092-.03 0-.046 0-.076-.015C9.005 1.44 8.058 1 8.058 1 7.004 1.666 6.79 2.604 6.79 2.604s0 .015-.016.06l-.183.046c-.076.03-.168.06-.244.076-.077.03-.168.06-.245.09-.153.076-.32.152-.473.228-.153.09-.306.181-.443.272-.016-.015-.03-.015-.03-.015-1.467-.545-2.766.136-2.766.136-.122 1.544.58 2.528.733 2.71-.03.09-.06.196-.091.287a8.104 8.104 0 00-.245 1.09c0 .06-.015.106-.015.166C1.397 8.386 1 9.748 1 9.748c1.13 1.287 2.46 1.377 2.46 1.377.167.303.366.575.58.848.092.106.183.212.29.318a3.014 3.014 0 00.061 2.149c1.268.045 2.093-.545 2.261-.681.122.045.26.076.382.106.382.106.78.151 1.176.181h.49c.595.848 1.634.954 1.634.954.748-.772.779-1.544.779-1.71v-.015-.03-.03c.153-.107.305-.228.443-.35a5.37 5.37 0 00.779-.892c.015-.03.046-.06.061-.09.84.045 1.436-.515 1.436-.515-.138-.863-.642-1.287-.749-1.378l-.015-.015h-.015s-.015 0-.015-.015c0-.045.015-.106.015-.151 0-.091.015-.182.015-.288V9.4v-.166-.076-.152l-.015-.075c-.015-.091-.03-.197-.061-.288a3.506 3.506 0 00-.428-1.044 3.856 3.856 0 00-.718-.848 3.784 3.784 0 00-.901-.575 3.347 3.347 0 00-.993-.272c-.168-.015-.336-.03-.504-.03H9.37 9.204c-.092.015-.169.015-.26.03-.336.06-.642.181-.932.348-.275.166-.52.363-.718.605a2.579 2.579 0 00-.459.757 2.63 2.63 0 00-.183.817v.393c.015.137.046.273.077.394.076.258.183.485.336.666.137.197.32.348.504.485.183.12.382.212.58.272.199.06.382.076.565.076h.244c.031 0 .047 0 .062-.015.015 0 .046-.015.061-.015.046-.016.076-.016.122-.03l.23-.092a.869.869 0 00.198-.12c.015-.016.03-.03.046-.03a.129.129 0 00.015-.198c-.046-.06-.122-.075-.183-.03-.015.015-.03.015-.046.03-.046.03-.107.046-.168.06l-.183.046c-.03 0-.061.015-.092.015H8.73a1.519 1.519 0 01-.825-.378 1.452 1.452 0 01-.306-.378 1.655 1.655 0 01-.168-.485c-.015-.09-.015-.166-.015-.257v-.106-.03c0-.046.015-.091.015-.136.061-.364.26-.727.55-1 .077-.075.153-.136.23-.181.076-.06.167-.106.259-.151.092-.046.183-.076.29-.106a.993.993 0 01.306-.046h.321c.107.015.229.03.336.046.214.045.427.12.626.242.397.212.733.56.947.969.107.211.183.423.214.65.015.06.015.121.015.167v.363c0 .06-.015.121-.015.182 0 .06-.015.12-.03.181l-.046.182c-.03.121-.077.242-.123.363a3.183 3.183 0 01-.366.666 3.002 3.002 0 01-1.91 1.18c-.122.016-.26.03-.382.046h-.198c-.061 0-.138 0-.199-.015a3.637 3.637 0 01-.81-.151 4.068 4.068 0 01-.748-.303 4.098 4.098 0 01-1.696-1.695 4.398 4.398 0 01-.29-.742c-.076-.257-.107-.514-.137-.772v-.302-.091c0-.136.015-.258.03-.394s.046-.272.061-.393c.03-.137.061-.258.092-.394a5.33 5.33 0 01.275-.741c.214-.47.504-.893.855-1.226.092-.091.184-.167.275-.243.092-.075.184-.136.29-.211a5.39 5.39 0 01.306-.182c.046-.03.107-.045.153-.076a.26.26 0 01.076-.03.26.26 0 01.077-.03c.107-.046.229-.091.336-.121.03-.015.06-.015.091-.03.03-.016.061-.016.092-.03.061-.016.122-.031.168-.046.03-.015.061-.015.092-.015.03 0 .06-.016.091-.016.03 0 .061-.015.092-.015l.046-.015h.046c.03 0 .06-.015.091-.015.03 0 .061-.015.107-.015.03 0 .077-.015.107-.015h.764c.23.015.443.03.657.075.428.076.84.212 1.207.394.366.182.702.393.977.636l.046.045.046.045c.03.03.061.061.107.091l.092.091.091.09c.123.122.23.258.336.394.199.258.367.515.49.772.014.015.014.03.03.046.015.015.015.03.015.045l.046.09.046.092.045.09c.046.122.092.228.123.333.06.167.107.318.137.455.015.045.061.09.122.075a.104.104 0 00.107-.106c.092-.227.092-.393.077-.575z'
/>
<defs>
<linearGradient
id='grafana-color-16__paint0_linear_2372_364'
x1='7.502'
x2='7.502'
y1='18.142'
y2='5.356'
gradientUnits='userSpaceOnUse'
>
<stop stop-color='#FFF200' />
<stop offset='1' stop-color='#F15A29' />
</linearGradient>
</defs>
</svg>
)
}
export function GoogleDriveIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -1259,7 +1314,7 @@ export function ConfluenceIcon(props: SVGProps<SVGSVGElement>) {
export function TwilioIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'>
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='-8 -8 272 272'>
<path
fill='currentColor'
d='M128 0c70.656 0 128 57.344 128 128s-57.344 128-128 128S0 198.656 0 128 57.344 0 128 0zm0 33.792c-52.224 0-94.208 41.984-94.208 94.208S75.776 222.208 128 222.208s94.208-41.984 94.208-94.208S180.224 33.792 128 33.792zm31.744 99.328c14.704 0 26.624 11.92 26.624 26.624 0 14.704-11.92 26.624-26.624 26.624-14.704 0-26.624-11.92-26.624-26.624 0-14.704 11.92-26.624 26.624-26.624zm-63.488 0c14.704 0 26.624 11.92 26.624 26.624 0 14.704-11.92 26.624-26.624 26.624-14.704 0-26.624-11.92-26.624-26.624 0-14.704 11.92-26.624 26.624-26.624zm63.488-63.488c14.704 0 26.624 11.92 26.624 26.624 0 14.704-11.92 26.624-26.624 26.624-14.704 0-26.624-11.92-26.624-26.624 0-14.704 11.92-26.624 26.624-26.624zm-63.488 0c14.704 0 26.624 11.92 26.624 26.624 0 14.704-11.92 26.624-26.624 26.624-14.704 0-26.624-11.92-26.624-26.624 0-14.704 11.92-26.624 26.624-26.624z'
@@ -1666,21 +1721,13 @@ export function ElevenLabsIcon(props: SVGProps<SVGSVGElement>) {
export function LinkupIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 24 24'
width='24'
height='24'
fill='none'
>
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'>
<g transform='translate(12, 12) scale(1.3) translate(-12, -12)'>
<path
d='M20.2 14.1c-.4-.3-1.6-.4-2.9-.2.5-1.4 1.3-3.9.1-5-.6-.5-1.5-.7-2.6-.5-.3 0-.6.1-1 .2-1.1-1.6-2.4-2.5-3.8-2.5-1.6 0-3.1 1-4.1 2.9-1.2 2.1-1.9 5.1-1.9 8.8v.03l.4.3c3-.9 7.5-2.3 10.7-2.9 0 .9.1 1.9.1 2.8v.03l.4.3c.1 0 5.4-1.7 5.3-3.3 0-.2-.1-.5-.3-.7zM19.9 14.7c.03.4-1.7 1.4-4 2.3.5-.7 1-1.6 1.3-2.5 1.4-.1 2.4-.1 2.7.2zM16.4 14.6c-.3.7-.7 1.4-1.2 2-.02-.6-.1-1.2-.2-1.8.4-.1.9-.1 1.4-.2zM16.5 9.4c.8.7.9 2.4.1 5.1-.5.1-1 .1-1.5.2-.3-2-.9-3.8-1.7-5.3.3-.1.6-.2.8-.2.9-.1 1.7.05 2.3.2zM9.5 6.8c1.2 0 2.3.7 3.2 2.1-2.8 1.1-5.9 3.4-8.4 7.8.2-5.1 1.9-9.9 5.2-9.9zM4.7 17c3.4-4.9 6.4-6.8 8.4-7.8.7 1.3 1.2 2.9 1.5 4.8-3.2.6-7.3 1.8-9.9 3z'
fill='currentColor'
stroke='currentColor'
strokeWidth='0.5'
strokeLinejoin='round'
fill='#000000'
/>
</g>
</svg>
)
}
@@ -3694,6 +3741,22 @@ export function ZendeskIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function ZoomIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
width='800px'
height='800px'
viewBox='0 0 32 32'
version='1.1'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M19.283 17.4c-0.367 0.374-0.879 0.606-1.444 0.606-1.117 0-2.023-0.906-2.023-2.023s0.906-2.023 2.023-2.023c0.929 0 1.712 0.626 1.949 1.479l0.003 0.014c0.045 0.159 0.071 0.341 0.071 0.53 0 0.552-0.221 1.052-0.579 1.417l0-0zM15.471 13.586c-0.648 0.615-1.052 1.483-1.052 2.446 0 1.861 1.509 3.37 3.37 3.37s3.37-1.509 3.37-3.37c0-1.54-1.033-2.838-2.444-3.241l-0.024-0.006c-0.27-0.078-0.581-0.123-0.902-0.123-0.899 0-1.716 0.352-2.32 0.925l0.002-0.001zM28.296 12.601c-0.802 0.001-1.522 0.352-2.016 0.909l-0.002 0.003c-0.496-0.562-1.219-0.915-2.023-0.915-0.563 0-1.086 0.173-1.519 0.468l0.009-0.006c-0.316-0.278-0.73-0.451-1.184-0.462l-0.002-0v6.742l0.337-0.016c0.544-0.014 0.981-0.451 0.995-0.993l0-0.001 0.016-0.337v-2.361l0.017-0.337c0-0.001 0-0.002 0-0.003 0-0.245 0.061-0.477 0.169-0.679l-0.004 0.008c0.238-0.405 0.671-0.672 1.166-0.672s0.928 0.267 1.162 0.664l0.003 0.006c0.103 0.196 0.164 0.428 0.165 0.675v0l0.017 0.339v2.361l0.016 0.336c0.022 0.54 0.454 0.972 0.991 0.995l0.002 0 0.337 0.016v-3.708l0.015-0.337c0-0.001 0-0.002 0-0.003 0-0.247 0.062-0.48 0.171-0.683l-0.004 0.008c0.238-0.403 0.67-0.669 1.165-0.669 0.496 0 0.929 0.268 1.164 0.666l0.003 0.006c0.102 0.195 0.162 0.427 0.162 0.673 0 0.001 0 0.001 0 0.002v-0l0.019 0.337v2.361l0.016 0.336c0.020 0.541 0.454 0.975 0.993 0.995l0.002 0 0.337 0.016v-4.045c-0.001-1.488-1.208-2.694-2.697-2.694-0.001 0-0.002 0-0.003 0h0zM12.206 17.4c-0.37 0.393-0.894 0.638-1.475 0.638-1.117 0-2.023-0.906-2.023-2.023s0.906-2.023 2.023-2.023c0.924 0 1.703 0.619 1.945 1.465l0.004 0.014c0.047 0.163 0.075 0.351 0.075 0.544 0 0.536-0.209 1.024-0.549 1.386l0.001-0.001zM10.78 12.6h-0.005c-1.86 0.001-3.367 1.509-3.367 3.368s1.508 3.368 3.368 3.368 3.368-1.508 3.368-3.368c0-1.86-1.507-3.367-3.366-3.368h-0zM6.734 18.008l-0.337-0.015h-3.035l4.044-4.045-0.016-0.337c-0.013-0.544-0.451-0.982-0.994-0.995l-0.001-0-0.337-0.016h-5.052l0.018 0.337c0.026 0.538 0.455 0.967 0.99 0.995l0.002 0 0.337 0.016h3.037l-4.049 4.045 0.017 0.336c0.019 0.541 0.453 0.975 0.992 0.995l0.002 0 0.337 0.016h5.056l-0.018-0.337c-0.024-0.539-0.455-0.969-0.991-0.993l-0.002-0z' />
</svg>
)
}
export function PylonIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -3801,6 +3864,30 @@ export function SmtpIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function SshIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
<rect x='2' y='4' width='20' height='16' rx='2' />
<path d='M6 8h.01' />
<path d='M10 8h.01' />
<path d='M14 8h8' />
<path d='M6 12l3 3-3 3' />
<path d='M12 18h6' />
</svg>
)
}
export function ApifyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -3887,3 +3974,181 @@ export function DynamoDBIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function WordpressIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 25.925 25.925'>
<g fill='currentColor'>
<path d='M1.843,12.962c0,4.401,2.557,8.205,6.267,10.008L2.805,8.437C2.189,9.819,1.843,11.35,1.843,12.962z M20.469,12.4c0-1.374-0.493-2.326-0.917-3.066c-0.563-0.917-1.092-1.691-1.092-2.608c0-1.021,0.775-1.973,1.867-1.973c0.049,0,0.096,0.006,0.145,0.008c-1.979-1.813-4.615-2.919-7.509-2.919c-3.885,0-7.303,1.993-9.291,5.013c0.261,0.008,0.507,0.013,0.716,0.013c1.163,0,2.963-0.142,2.963-0.142c0.599-0.035,0.67,0.846,0.071,0.917c0,0-0.603,0.07-1.272,0.105l4.049,12.045l2.434-7.298l-1.732-4.747c-0.599-0.035-1.167-0.105-1.167-0.105c-0.6-0.036-0.529-0.953,0.07-0.917c0,0,1.836,0.142,2.928,0.142c1.163,0,2.964-0.142,2.964-0.142c0.6-0.035,0.67,0.846,0.071,0.917c0,0-0.604,0.07-1.272,0.105l4.018,11.953l1.11-3.706C20.187,14.55,20.469,13.353,20.469,12.4z M13.158,13.935l-3.337,9.694c0.997,0.293,2.05,0.453,3.142,0.453c1.294,0,2.537-0.224,3.693-0.63c-0.029-0.048-0.057-0.099-0.08-0.153L13.158,13.935z M22.72,7.627c0.049,0.354,0.075,0.734,0.075,1.144c0,1.128-0.212,2.396-0.846,3.982l-3.396,9.82c3.306-1.928,5.529-5.509,5.529-9.611C24.082,11.028,23.588,9.21,22.72,7.627z' />
<path d='M0,12.962c0,7.147,5.815,12.963,12.962,12.963c7.149,0,12.963-5.816,12.963-12.963S20.111,0,12.963,0S0,5.814,0,12.962z M0.594,12.962c0-6.819,5.548-12.368,12.368-12.368s12.369,5.549,12.369,12.368S19.782,25.33,12.963,25.33S0.594,19.781,0.594,12.962z' />
</g>
</svg>
)
}
export function AhrefsIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1065 1300'>
<path
fillRule='evenodd'
fill='#ff8800'
d='m111.3 0.7h953.4v1264.4h-256.9v-137.8h-21.2c-127 129.9-230.4 172.4-410.4 172.4-227.8 0-376.1-127.3-376.1-339.5v-58.3c0-288.9 121.8-392.4 511-421.5l241-18.6v-180.2h-640.8zm640.8 707.7l-193.4 18.6c-203.9 18.6-248.8 47.8-251.5 169.7-2.7 87.5 42.4 127.2 148.3 127.2 98 0 217.1-53 296.6-132.5z'
/>
</svg>
)
}
export function ShopifyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 48 48'
width='48px'
height='48px'
>
<path
fill='#7cb342'
d='M37.216,11.78c-0.023-0.211-0.211-0.305-0.351-0.305s-3.21-0.234-3.21-0.234s-2.132-2.132-2.39-2.343 c-0.234-0.234-0.68-0.164-0.867-0.117c-0.023,0-0.469,0.141-1.195,0.375c-0.726-2.086-1.968-3.984-4.194-3.984h-0.211 C24.187,4.375,23.391,4,22.735,4c-5.155,0-7.639,6.444-8.412,9.725c-2.015,0.633-3.445,1.054-3.609,1.125 c-1.125,0.351-1.148,0.375-1.289,1.429c-0.117,0.797-3.046,23.456-3.046,23.456L29.179,44l12.373-2.671 C41.575,41.282,37.24,11.991,37.216,11.78z M27.937,9.483c-0.562,0.164-1.242,0.375-1.921,0.609V9.671 c0-1.265-0.164-2.296-0.469-3.117C26.718,6.695,27.445,7.984,27.937,9.483L27.937,9.483z M24.117,6.812 c0.305,0.797,0.516,1.922,0.516,3.468v0.234c-1.265,0.398-2.601,0.797-3.984,1.242C21.422,8.804,22.899,7.351,24.117,6.812 L24.117,6.812z M22.617,5.359c0.234,0,0.469,0.094,0.656,0.234c-1.664,0.773-3.421,2.718-4.148,6.655 c-1.101,0.351-2.156,0.656-3.163,0.984C16.806,10.233,18.915,5.359,22.617,5.359z'
/>
<path
fill='#558b2f'
d='M36.865,11.428c-0.141,0-3.21-0.234-3.21-0.234s-2.132-2.132-2.39-2.343 C31.17,8.757,31.053,8.71,30.96,8.71L29.249,44l12.373-2.671c0,0-4.335-29.338-4.359-29.549 C37.169,11.569,37.005,11.475,36.865,11.428z'
/>
<path
fill='#fff'
d='M24.792,18.593l-1.475,4.449c0,0-1.337-0.715-2.927-0.715c-2.374,0-2.489,1.498-2.489,1.867 c0,2.028,5.301,2.812,5.301,7.583c0,3.757-2.374,6.177-5.578,6.177c-3.872,0-5.808-2.397-5.808-2.397l1.037-3.411 c0,0,2.028,1.752,3.734,1.752c1.129,0,1.59-0.876,1.59-1.521c0-2.651-4.333-2.766-4.333-7.145c0-3.665,2.628-7.214,7.952-7.214 C23.777,17.994,24.792,18.593,24.792,18.593z'
/>
</svg>
)
}
export function BoxCompanyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 41 22'>
<path
d='M39.7 19.2c.5.7.4 1.6-.2 2.1-.7.5-1.7.4-2.2-.2l-3.5-4.5-3.4 4.4c-.5.7-1.5.7-2.2.2-.7-.5-.8-1.4-.3-2.1l4-5.2-4-5.2c-.5-.7-.3-1.7.3-2.2.7-.5 1.7-.3 2.2.3l3.4 4.5L37.3 7c.5-.7 1.4-.8 2.2-.3.7.5.7 1.5.2 2.2L35.8 14l3.9 5.2zm-18.2-.6c-2.6 0-4.7-2-4.7-4.6 0-2.5 2.1-4.6 4.7-4.6s4.7 2.1 4.7 4.6c-.1 2.6-2.2 4.6-4.7 4.6zm-13.8 0c-2.6 0-4.7-2-4.7-4.6 0-2.5 2.1-4.6 4.7-4.6s4.7 2.1 4.7 4.6c0 2.6-2.1 4.6-4.7 4.6zM21.5 6.4c-2.9 0-5.5 1.6-6.8 4-1.3-2.4-3.9-4-6.9-4-1.8 0-3.4.6-4.7 1.5V1.5C3.1.7 2.4 0 1.6 0 .7 0 0 .7 0 1.5v12.6c.1 4.2 3.5 7.5 7.7 7.5 3 0 5.6-1.7 6.9-4.1 1.3 2.4 3.9 4.1 6.8 4.1 4.3 0 7.8-3.4 7.8-7.7.1-4.1-3.4-7.5-7.7-7.5z'
fill='currentColor'
/>
</svg>
)
}
export function DropboxIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 43 40'>
<path
d='m12.5 0l-12.5 8.1 8.7 7 12.5-7.8-8.7-7.3zm-12.5 21.9l12.5 8.2 8.7-7.3-12.5-7.7-8.7 6.8zm21.2 0.9l8.8 7.3 12.4-8.1-8.6-6.9-12.6 7.7zm21.2-14.7l-12.4-8.1-8.8 7.3 12.6 7.8 8.6-7zm-21.1 16.3l-8.8 7.3-3.7-2.5v2.8l12.5 7.5 12.5-7.5v-2.8l-3.8 2.5-8.7-7.3z'
fill='currentColor'
/>
</svg>
)
}
export function ElasticsearchIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
<path
fill='#343741'
d='M4 64c0 5.535.777 10.879 2.098 16H84c8.836 0 16-7.164 16-16s-7.164-16-16-16H6.098A63.738 63.738 0 0 0 4 64'
/>
<path
fill='#fec514'
d='M111.695 30.648A61.485 61.485 0 0 0 117.922 24C106.188 9.379 88.199 0 68 0 42.715 0 20.957 14.71 10.574 36H98.04a20.123 20.123 0 0 0 13.652-5.352'
/>
<path
fill='#00bfb3'
d='M98.04 92H10.577C20.961 113.29 42.715 128 68 128c20.2 0 38.188-9.383 49.922-24a61.1 61.1 0 0 0-6.227-6.648A20.133 20.133 0 0 0 98.04 92'
/>
</svg>
)
}
export function GitlabIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 380 380'>
<path
fill='#e24329'
d='M265.26416,174.37243l-.2134-.55822-21.19899-55.30908c-.4236-1.08359-1.18542-1.99642-2.17699-2.62689-.98837-.63373-2.14749-.93253-3.32305-.87014-1.1689.06239-2.29195.48925-3.20809,1.21821-.90957.73554-1.56629,1.73047-1.87493,2.85346l-14.31327,43.80662h-57.90965l-14.31327-43.80662c-.30864-1.12299-.96536-2.11791-1.87493-2.85346-.91614-.72895-2.03911-1.15582-3.20809-1.21821-1.17548-.06239-2.33468.23641-3.32297.87014-.99166.63047-1.75348,1.5433-2.17707,2.62689l-21.19891,55.31237-.21348.55493c-6.28158,16.38521-.92929,34.90803,13.05891,45.48782.02621.01641.04922.03611.07552.05582l.18719.14119,32.29094,24.17392,15.97151,12.09024,9.71951,7.34871c2.34117,1.77316,5.57877,1.77316,7.92002,0l9.71943-7.34871,15.96822-12.09024,32.48142-24.31511c.02958-.02299.05588-.04269.08538-.06568,13.97834-10.57977,19.32735-29.09604,13.04905-45.47796Z'
/>
<path
fill='#fc6d26'
d='M265.26416,174.37243l-.2134-.55822c-10.5174,2.16062-20.20405,6.6099-28.49844,12.81593-.1346.0985-25.20497,19.05805-46.55171,35.19699,15.84998,11.98517,29.6477,22.40405,29.6477,22.40405l32.48142-24.31511c.02958-.02299.05588-.04269.08538-.06568,13.97834-10.57977,19.32735-29.09604,13.04905-45.47796Z'
/>
<path
fill='#fca326'
d='M160.34962,244.23117l15.97151,12.09024,9.71951,7.34871c2.34117,1.77316,5.57877,1.77316,7.92002,0l9.71943-7.34871,15.96822-12.09024s-13.79772-10.41888-29.6477-22.40405c-15.85327,11.98517-29.65099,22.40405-29.65099,22.40405Z'
/>
<path
fill='#fc6d26'
d='M143.44561,186.63014c-8.29111-6.20274-17.97446-10.65531-28.49507-12.81264l-.21348.55493c-6.28158,16.38521-.92929,34.90803,13.05891,45.48782.02621.01641.04922.03611.07552.05582l.18719.14119,32.29094,24.17392s13.79772-10.41888,29.65099-22.40405c-21.34673-16.13894-46.42031-35.09848-46.55499-35.19699Z'
/>
</svg>
)
}
export function SSHIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 48 48'
width='48px'
height='48px'
>
<path
fill='#7cb342'
d='M37.216,11.78c-0.023-0.211-0.211-0.305-0.351-0.305s-3.21-0.234-3.21-0.234s-2.132-2.132-2.39-2.343 c-0.234-0.234-0.68-0.164-0.867-0.117c-0.023,0-0.469,0.141-1.195,0.375c-0.726-2.086-1.968-3.984-4.194-3.984h-0.211 C24.187,4.375,23.391,4,22.735,4c-5.155,0-7.639,6.444-8.412,9.725c-2.015,0.633-3.445,1.054-3.609,1.125 c-1.125,0.351-1.148,0.375-1.289,1.429c-0.117,0.797-3.046,23.456-3.046,23.456L29.179,44l12.373-2.671 C41.575,41.282,37.24,11.991,37.216,11.78z M27.937,9.483c-0.562,0.164-1.242,0.375-1.921,0.609V9.671 c0-1.265-0.164-2.296-0.469-3.117C26.718,6.695,27.445,7.984,27.937,9.483L27.937,9.483z M24.117,6.812 c0.305,0.797,0.516,1.922,0.516,3.468v0.234c-1.265,0.398-2.601,0.797-3.984,1.242C21.422,8.804,22.899,7.351,24.117,6.812 L24.117,6.812z M22.617,5.359c0.234,0,0.469,0.094,0.656,0.234c-1.664,0.773-3.421,2.718-4.148,6.655 c-1.101,0.351-2.156,0.656-3.163,0.984C16.806,10.233,18.915,5.359,22.617,5.359z'
/>
<path
fill='#558b2f'
d='M36.865,11.428c-0.141,0-3.21-0.234-3.21-0.234s-2.132-2.132-2.39-2.343 C31.17,8.757,31.053,8.71,30.96,8.71L29.249,44l12.373-2.671c0,0-4.335-29.338-4.359-29.549 C37.169,11.569,37.005,11.475,36.865,11.428z'
/>
<path
fill='#fff'
d='M24.792,18.593l-1.475,4.449c0,0-1.337-0.715-2.927-0.715c-2.374,0-2.489,1.498-2.489,1.867 c0,2.028,5.301,2.812,5.301,7.583c0,3.757-2.374,6.177-5.578,6.177c-3.872,0-5.808-2.397-5.808-2.397l1.037-3.411 c0,0,2.028,1.752,3.734,1.752c1.129,0,1.59-0.876,1.59-1.521c0-2.651-4.333-2.766-4.333-7.145c0-3.665,2.628-7.214,7.952-7.214 C23.777,17.994,24.792,18.593,24.792,18.593z'
/>
</svg>
)
}
export function DatadogIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'>
<g fill='currentColor'>
<path d='M57.705 33.717l1.575 20.1L31.4 58.82 29 38.72l3.983-.556c.648.278 1.112.37 1.853.556 1.204.278 2.594.648 4.63-.463.463-.278 1.482-1.112 1.853-1.667zm-43.073 23.9c-.463-.74-1.297-1.575-2.594-2.594-1.853-1.482-1.204-4.076-.093-5.65 1.4-2.686 8.707-6.206 8.337-10.56-.185-1.575-.37-3.613-1.853-5.095-.093.556 0 1.112 0 1.112s-.648-.74-.926-1.853c-.278-.37-.556-.556-.834-1.112-.185.648-.185 1.297-.185 1.297s-.463-1.204-.556-2.13c-.278.463-.37 1.297-.37 1.297s-.648-1.853-.463-2.87c-.278-.834-1.204-2.594-.926-6.484 1.575 1.112 5.095.834 6.484-1.204.463-.648.74-2.5-.185-6.114-.648-2.316-2.223-5.743-2.87-7.04l-.093.093c.37 1.02 1.02 3.242 1.297 4.354.74 3.242 1.02 4.354.648 5.836-.278 1.297-1.02 2.13-2.964 3.057-1.853.926-4.446-1.4-4.54-1.482-1.853-1.482-3.242-3.9-3.427-5.002-.185-1.297.74-2.038 1.204-3.057-.648.185-1.4.556-1.4.556s.834-.926 1.945-1.667a9.43 9.43 0 0 0 1.204-.834H10.28s1.112-.648 2.316-1.02H10.93l4.446-1.945c1.4-.556 2.686-.37 3.427.648 1.02 1.4 2.038 2.13 4.168 2.686 1.297-.556 1.76-.926 3.427-1.4 1.482-1.667 2.686-1.853 2.686-1.853s-.834.74-.926 1.575c.834-.648 1.76-1.204 1.76-1.204s-.37.463-.648 1.112l.093.093c1.02-.556 2.13-1.02 2.13-1.02s-.37.37-.74.926c.74 0 2.223 0 2.78.093 3.427.093 4.168-3.613 5.465-4.168 1.667-.556 2.408-.926 5.187 1.853 2.408 2.408 4.26 6.577 3.335 7.503-.74.74-2.316-.278-4.076-2.408-.926-1.112-1.575-2.5-1.945-4.168-.185-1.4-1.204-2.223-1.204-2.223s.648 1.4.648 2.594c0 .648.093 3.057 1.112 4.446-.093.185-.185 1.02-.278 1.112-1.204-1.482-3.9-2.5-4.26-2.87 1.482 1.204 4.817 3.9 6.114 6.484 1.204 2.5.463 4.724 1.112 5.28.185.185 2.594 3.15 3.057 4.724.834 2.686.093 5.465-1.02 7.132l-2.964.463a3.92 3.92 0 0 1-1.112-.371c.185-.37.648-1.297.648-1.482l-.185-.278c-.926 1.297-2.5 2.594-3.798 3.335-1.667.926-3.613.834-4.9.37-3.613-1.112-7.04-3.52-7.78-4.168 0 0 0 .463.093.648a28.07 28.07 0 0 0 5.002 4.168l-4.26.463 2.038 15.747c-.926.093-1.02.185-2.038.37-.834-3.057-2.5-5.002-4.354-6.206-1.575-1.02-3.798-1.297-5.928-.834l-.093.185c1.482-.185 3.242.093 5.002 1.204s3.15 3.983 3.705 5.65c.648 2.223 1.112 4.54-.648 7.04-1.297 1.76-5.002 2.78-7.966.648.834 1.297 1.853 2.316 3.335 2.5 2.13.278 4.168-.093 5.65-1.482 1.204-1.204 1.853-3.798 1.667-6.577l1.945-.278.648 4.9L62.06 56.7l-2.78-25.195-1.575.278L54.556 0 1.942 6.114l6.484 52.428z' />
<path d='M39.665 30.06c1.4 1.02 2.594 1.667 3.798 1.575.74-.093 1.482-1.297 1.945-2.408.37-.74.37-1.575-.185-1.853-.278-.093-1.4-.093-2.223 0-1.575.185-3.15.74-3.52 1.02-.556.37-.278 1.297.185 1.667m.37-10.84v.093l.093.185c.37.74.74 1.482 1.482 1.853.185 0 .37-.093.556-.093.648 0 1.02.093 1.297.185v-.556c-.093-.926.185-2.594-1.667-3.52-.74-.37-1.667-.185-2.038.185h.185c.463.185.185.37.093.556-.093.37-.185.463 0 1.112' />
<path d='M31.698 19.407c.463-.37-2.13-.926-4.168.37-1.482 1.02-1.482 3.15-.093 4.354a1.27 1.27 0 0 1 .37.278c.37-.185.926-.37 1.4-.556.926-.278 1.667-.463 2.316-.556.278-.37.648-.926.556-1.945-.093-1.4-1.204-1.112-.37-1.945m15.098 22.5l-4.446 7.4-5.187-1.575-4.54 6.947.185 2.223 24.825-4.54-1.482-15.47-4.076 8.522z' />
</g>
</svg>
)
}
export function KalshiIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 78 20' fill='currentColor' xmlns='http://www.w3.org/2000/svg'>
<path d='M40.1043 0H36.0332V19.9986H40.1043V0Z' />
<path d='M0.416887 0.0221237H4.73849V8.99348L12.818 0.0221237H18.0582L10.6468 8.24586L18.5384 20H13.3608L7.59868 11.5686L4.73849 14.7459V20H0.416887V0.0221237Z' />
<path
fillRule='evenodd'
clipRule='evenodd'
d='M34.4675 19.8117H32.4007C30.5426 19.8117 29.624 19.0017 29.6658 17.4027C29.1229 18.2334 28.4549 18.8771 27.6824 19.3132C26.8891 19.7494 25.9496 19.9778 24.8222 19.9778C23.1729 19.9778 21.8368 19.604 20.8138 18.8564C19.8117 18.088 19.3106 17.0289 19.3106 15.6582C19.3106 14.1007 19.8952 12.8962 21.0434 12.0656C22.2126 11.2141 23.9036 10.778 26.1166 10.778H29.0603V10.0719C29.0603 9.40737 28.8098 8.8882 28.3087 8.49362C27.8077 8.09905 27.1396 7.89138 26.2836 7.89138C25.532 7.89138 24.9266 8.05752 24.4464 8.36902C23.9662 8.70129 23.674 9.1374 23.5905 9.67734H19.6446C19.7699 8.18212 20.4589 7.01916 21.6697 6.18848C22.8806 5.3578 24.4882 4.92169 26.4924 4.92169C28.5801 4.92169 30.2086 5.37857 31.3359 6.29232C32.4842 7.20607 33.0688 8.53516 33.0688 10.2588V15.4298C33.0688 15.7828 33.1523 16.0321 33.2984 16.1774C33.4445 16.302 33.6951 16.3851 34.0291 16.3851H34.4675V19.8117ZM26.0749 13.4569C25.2398 13.4569 24.5717 13.6231 24.0915 13.9761C23.6322 14.3084 23.4026 14.7653 23.4026 15.3675C23.4026 15.8867 23.5905 16.2813 23.9871 16.5928C24.3838 16.9043 24.9266 17.0496 25.5947 17.0496C26.6594 17.0496 27.4945 16.7589 28.1 16.1567C28.7054 15.5544 29.0394 14.7445 29.0603 13.7269V13.4569H26.0749Z'
/>
<path d='M45.5115 14.9314C45.5741 15.5752 45.8873 16.0944 46.4718 16.5097C47.0564 16.9043 47.7871 17.112 48.6848 17.112C49.5408 17.112 50.2297 16.9874 50.7308 16.7174C51.2318 16.4266 51.4824 16.0321 51.4824 15.5129C51.4824 15.1391 51.3571 14.8483 51.1275 14.6614C50.8978 14.4745 50.5638 14.3292 50.1462 14.2669C49.7287 14.163 49.0397 14.0592 48.0794 13.9554C46.7641 13.7892 45.6785 13.5608 44.8225 13.2908C43.9665 13.0208 43.2567 12.6055 42.7557 12.024C42.2337 11.4426 41.9832 10.6949 41.9832 9.73966C41.9832 8.78438 42.2337 7.9537 42.7557 7.22685C43.2985 6.47924 44.0501 5.91853 45.0104 5.50319C45.9708 5.10861 47.0773 4.90094 48.3299 4.90094C50.355 4.92171 51.9625 5.35782 53.1943 6.1885C54.4469 7.01918 55.115 8.18213 55.2194 9.67736H51.3571C51.2945 9.11665 51.0022 8.68054 50.4594 8.3275C49.9374 7.97446 49.2694 7.78756 48.4343 7.78756C47.6618 7.78756 47.0355 7.93293 46.5553 8.22367C46.096 8.5144 45.8664 8.88821 45.8664 9.36585C45.8664 9.71889 45.9916 9.9681 46.2422 10.1342C46.4927 10.3004 46.8267 10.425 47.2234 10.508C47.6201 10.5911 48.309 10.6742 49.2485 10.7572C51.2527 10.9857 52.7768 11.4218 53.8206 12.0448C54.9062 12.647 55.4282 13.7062 55.4282 15.2222C55.4282 16.1774 55.1359 17.0081 54.5722 17.735C54.0085 18.4618 53.2361 19.0225 52.2131 19.4171C51.211 19.7909 50.0418 19.9986 48.7266 19.9986C46.6806 19.9986 44.9895 19.5417 43.716 18.6487C42.4216 17.735 41.7535 16.4889 41.67 14.9314H45.5115Z' />
<path d='M69.7503 6.72852C68.623 5.6694 67.2033 5.12946 65.4496 5.12946C63.6333 5.12946 62.1719 5.794 61.0654 7.12309V0H56.9943V19.9986H61.0654V12.4602C61.0654 11.1934 61.3368 10.2174 61.9213 9.5113C62.5059 8.80522 63.3201 8.45218 64.364 8.45218C65.3661 8.45218 66.1177 8.78445 66.6187 9.42823C67.1198 10.0512 67.3703 10.965 67.3703 12.1902V19.9986H71.4414V12.0241C71.4414 9.55283 70.8777 7.78763 69.7503 6.72852Z' />
<path d='M73.0068 5.29551H77.0779V19.9778H73.0068V5.29551Z' />
<path d='M76.473 0.581477C76.0972 0.20767 75.617 0 75.0324 0C74.4688 0 73.9677 0.20767 73.571 0.581477C73.1952 0.955283 72.9865 1.41216 72.9865 1.97287C72.9865 2.53358 73.1952 3.01122 73.571 3.38503C73.9677 3.75883 74.4688 3.9665 75.0324 3.9665C75.5961 3.9665 76.0972 3.7796 76.473 3.38503C76.8488 2.99045 77.0575 2.53358 77.0575 1.97287C77.0575 1.41216 76.8488 0.934516 76.473 0.581477Z' />
</svg>
)
}
export function PolymarketIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='51 209 123 155'>
<path
fill='currentColor'
d='M173.2,363.2L51.1,328.3v-83.7l122.1-34.9V363.2z M161.4,296.2l-89.8,25.6l89.8,25.6L161.4,296.2z M62.9,260.8v51.3l89.8-25.6L62.9,260.8z M161.4,225.3L71.6,251l89.8,25.6L161.4,225.3z'
/>
</svg>
)
}

View File

@@ -4,6 +4,7 @@
import type { ComponentType, SVGProps } from 'react'
import {
AhrefsIcon,
AirtableIcon,
ApifyIcon,
ApolloIcon,
@@ -14,14 +15,18 @@ import {
CalendlyIcon,
ClayIcon,
ConfluenceIcon,
DatadogIcon,
DiscordIcon,
DocumentIcon,
DropboxIcon,
DynamoDBIcon,
ElasticsearchIcon,
ElevenLabsIcon,
ExaAIIcon,
EyeIcon,
FirecrawlIcon,
GithubIcon,
GitLabIcon,
GmailIcon,
GoogleCalendarIcon,
GoogleDocsIcon,
@@ -30,6 +35,7 @@ import {
GoogleIcon,
GoogleSheetsIcon,
GoogleVaultIcon,
GrafanaIcon,
HubspotIcon,
HuggingFaceIcon,
HunterIOIcon,
@@ -38,6 +44,7 @@ import {
IntercomIcon,
JinaAIIcon,
JiraIcon,
KalshiIcon,
LinearIcon,
LinkedInIcon,
LinkupIcon,
@@ -61,6 +68,7 @@ import {
PerplexityIcon,
PineconeIcon,
PipedriveIcon,
PolymarketIcon,
PostgresIcon,
PosthogIcon,
PylonIcon,
@@ -74,8 +82,10 @@ import {
SendgridIcon,
SentryIcon,
SerperIcon,
ShopifyIcon,
SlackIcon,
SmtpIcon,
SshIcon,
STTIcon,
StagehandIcon,
StripeIcon,
@@ -92,19 +102,23 @@ import {
WebflowIcon,
WhatsAppIcon,
WikipediaIcon,
WordpressIcon,
xIcon,
YouTubeIcon,
ZendeskIcon,
ZepIcon,
ZoomIcon,
} from '@/components/icons'
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
export const blockTypeToIconMap: Record<string, IconComponent> = {
zoom: ZoomIcon,
zep: ZepIcon,
zendesk: ZendeskIcon,
youtube: YouTubeIcon,
x: xIcon,
wordpress: WordpressIcon,
wikipedia: WikipediaIcon,
whatsapp: WhatsAppIcon,
webflow: WebflowIcon,
@@ -125,8 +139,10 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
stripe: StripeIcon,
stagehand_agent: StagehandIcon,
stagehand: StagehandIcon,
ssh: SshIcon,
smtp: SmtpIcon,
slack: SlackIcon,
shopify: ShopifyIcon,
sharepoint: MicrosoftSharepointIcon,
serper: SerperIcon,
sentry: SentryIcon,
@@ -141,6 +157,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
pylon: PylonIcon,
posthog: PosthogIcon,
postgresql: PostgresIcon,
polymarket: PolymarketIcon,
pipedrive: PipedriveIcon,
pinecone: PineconeIcon,
perplexity: PerplexityIcon,
@@ -164,6 +181,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
linkedin: LinkedInIcon,
linear: LinearIcon,
knowledge: PackageSearchIcon,
kalshi: KalshiIcon,
jira: JiraIcon,
jina: JinaAIIcon,
intercom: IntercomIcon,
@@ -172,6 +190,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
hunter: HunterIOIcon,
huggingface: HuggingFaceIcon,
hubspot: HubspotIcon,
grafana: GrafanaIcon,
google_vault: GoogleVaultIcon,
google_sheets: GoogleSheetsIcon,
google_forms: GoogleFormsIcon,
@@ -180,13 +199,17 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_calendar: GoogleCalendarIcon,
google_search: GoogleIcon,
gmail: GmailIcon,
gitlab: GitLabIcon,
github: GithubIcon,
firecrawl: FirecrawlIcon,
file: DocumentIcon,
exa: ExaAIIcon,
elevenlabs: ElevenLabsIcon,
elasticsearch: ElasticsearchIcon,
dynamodb: DynamoDBIcon,
dropbox: DropboxIcon,
discord: DiscordIcon,
datadog: DatadogIcon,
confluence: ConfluenceIcon,
clay: ClayIcon,
calendly: CalendlyIcon,
@@ -196,4 +219,5 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
apollo: ApolloIcon,
apify: ApifyIcon,
airtable: AirtableIcon,
ahrefs: AhrefsIcon,
}

View File

@@ -0,0 +1,204 @@
---
title: Ahrefs
description: SEO analysis with Ahrefs
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="ahrefs"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Ahrefs](https://ahrefs.com/) is a leading SEO toolset for analyzing websites, tracking rankings, monitoring backlinks, and researching keywords. It provides detailed insights into your own website as well as your competitors, helping you make data-driven decisions to improve your search visibility.
With the Ahrefs integration in Sim, you can:
- **Analyze Domain Rating & Authority**: Instantly check the Domain Rating (DR) and Ahrefs Rank of any website to gauge its authority.
- **Fetch Backlinks**: Retrieve a list of backlinks pointing to a site or specific URL, with details like anchor text, referring page DR, and more.
- **Get Backlink Statistics**: Access metrics on backlink types (dofollow, nofollow, text, image, redirect, etc.) for a domain or URL.
- **Explore Organic Keywords** *(planned)*: View keywords a domain ranks for and their positions in Google search results.
- **Discover Top Pages** *(planned)*: Identify the highest-performing pages by organic traffic and links.
These tools let your agents automate SEO research, monitor competitors, and generate reports—all as part of your workflow automations. To use the Ahrefs integration, youll need an Ahrefs Enterprise subscription with API access.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Ahrefs SEO tools into your workflow. Analyze domain ratings, backlinks, organic keywords, top pages, and more. Requires an Ahrefs Enterprise plan with API access.
## Tools
### `ahrefs_domain_rating`
Get the Domain Rating (DR) and Ahrefs Rank for a target domain. Domain Rating shows the strength of a website
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `target` | string | Yes | The target domain to analyze \(e.g., example.com\) |
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
| `apiKey` | string | Yes | Ahrefs API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `domainRating` | number | Domain Rating score \(0-100\) |
| `ahrefsRank` | number | Ahrefs Rank - global ranking based on backlink profile strength |
### `ahrefs_backlinks`
Get a list of backlinks pointing to a target domain or URL. Returns details about each backlink including source URL, anchor text, and domain rating.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `target` | string | Yes | The target domain or URL to analyze |
| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains\), exact \(exact URL match\) |
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
| `limit` | number | No | Maximum number of results to return \(default: 100\) |
| `offset` | number | No | Number of results to skip for pagination |
| `apiKey` | string | Yes | Ahrefs API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `backlinks` | array | List of backlinks pointing to the target |
### `ahrefs_backlinks_stats`
Get backlink statistics for a target domain or URL. Returns totals for different backlink types including dofollow, nofollow, text, image, and redirect links.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `target` | string | Yes | The target domain or URL to analyze |
| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains\), exact \(exact URL match\) |
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
| `apiKey` | string | Yes | Ahrefs API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `stats` | object | Backlink statistics summary |
### `ahrefs_referring_domains`
Get a list of domains that link to a target domain or URL. Returns unique referring domains with their domain rating, backlink counts, and discovery dates.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `target` | string | Yes | The target domain or URL to analyze |
| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains\), exact \(exact URL match\) |
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
| `limit` | number | No | Maximum number of results to return \(default: 100\) |
| `offset` | number | No | Number of results to skip for pagination |
| `apiKey` | string | Yes | Ahrefs API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `referringDomains` | array | List of domains linking to the target |
### `ahrefs_organic_keywords`
Get organic keywords that a target domain or URL ranks for in Google search results. Returns keyword details including search volume, ranking position, and estimated traffic.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `target` | string | Yes | The target domain or URL to analyze |
| `country` | string | No | Country code for search results \(e.g., us, gb, de\). Default: us |
| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains\), exact \(exact URL match\) |
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
| `limit` | number | No | Maximum number of results to return \(default: 100\) |
| `offset` | number | No | Number of results to skip for pagination |
| `apiKey` | string | Yes | Ahrefs API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `keywords` | array | List of organic keywords the target ranks for |
### `ahrefs_top_pages`
Get the top pages of a target domain sorted by organic traffic. Returns page URLs with their traffic, keyword counts, and estimated traffic value.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `target` | string | Yes | The target domain to analyze |
| `country` | string | No | Country code for traffic data \(e.g., us, gb, de\). Default: us |
| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains\) |
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
| `limit` | number | No | Maximum number of results to return \(default: 100\) |
| `offset` | number | No | Number of results to skip for pagination |
| `apiKey` | string | Yes | Ahrefs API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pages` | array | List of top pages by organic traffic |
### `ahrefs_keyword_overview`
Get detailed metrics for a keyword including search volume, keyword difficulty, CPC, clicks, and traffic potential.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `keyword` | string | Yes | The keyword to analyze |
| `country` | string | No | Country code for keyword data \(e.g., us, gb, de\). Default: us |
| `apiKey` | string | Yes | Ahrefs API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `overview` | object | Keyword metrics overview |
### `ahrefs_broken_backlinks`
Get a list of broken backlinks pointing to a target domain or URL. Useful for identifying link reclamation opportunities.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `target` | string | Yes | The target domain or URL to analyze |
| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains\), exact \(exact URL match\) |
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
| `limit` | number | No | Maximum number of results to return \(default: 100\) |
| `offset` | number | No | Number of results to skip for pagination |
| `apiKey` | string | Yes | Ahrefs API Key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `brokenBacklinks` | array | List of broken backlinks |
## Notes
- Category: `tools`
- Type: `ahrefs`

View File

@@ -0,0 +1,307 @@
---
title: Datadog
description: Monitor infrastructure, applications, and logs with Datadog
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="datadog"
color="#632CA6"
/>
{/* MANUAL-CONTENT-START:intro */}
[Datadog](https://datadoghq.com/) is a comprehensive monitoring and analytics platform for infrastructure, applications, logs, and more. It enables organizations to gain real-time visibility into the health and performance of systems, detect anomalies, and automate incident response.
With Datadog, you can:
- **Monitor metrics**: Collect, visualize, and analyze metrics from servers, cloud services, and custom applications.
- **Query time series data**: Run advanced queries on performance metrics for trend analysis and reporting.
- **Manage monitors and events**: Set up monitors to detect issues, trigger alerts, and create events for observability.
- **Handle downtimes**: Schedule and programmatically manage planned downtimes to suppress alerts during maintenance.
- **Analyze logs and traces** *(with additional setup in Datadog)*: Centralize and inspect logs or distributed traces for deeper troubleshooting.
Sims Datadog integration lets your agents automate these operations and interact with your Datadog account programmatically. Use it to submit custom metrics, query timeseries data, manage monitors, create events, and streamline your monitoring workflows directly within Sim automations.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Datadog monitoring into workflows. Submit metrics, manage monitors, query logs, create events, handle downtimes, and more.
## Tools
### `datadog_submit_metrics`
Submit custom metrics to Datadog. Use for tracking application performance, business metrics, or custom monitoring data.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `series` | string | Yes | JSON array of metric series to submit. Each series should include metric name, type \(gauge/rate/count\), points \(timestamp/value pairs\), and optional tags. |
| `apiKey` | string | Yes | Datadog API key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the metrics were submitted successfully |
| `errors` | array | Any errors that occurred during submission |
### `datadog_query_timeseries`
Query metric timeseries data from Datadog. Use for analyzing trends, creating reports, or retrieving metric values.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | Datadog metrics query \(e.g., "avg:system.cpu.user\{*\}"\) |
| `from` | number | Yes | Start time as Unix timestamp in seconds |
| `to` | number | Yes | End time as Unix timestamp in seconds |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `series` | array | Array of timeseries data with metric name, tags, and data points |
| `status` | string | Query status |
### `datadog_create_event`
Post an event to the Datadog event stream. Use for deployment notifications, alerts, or any significant occurrences.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `title` | string | Yes | Event title |
| `text` | string | Yes | Event body/description. Supports markdown. |
| `alertType` | string | No | Alert type: error, warning, info, success, user_update, recommendation, or snapshot |
| `priority` | string | No | Event priority: normal or low |
| `host` | string | No | Host name to associate with this event |
| `tags` | string | No | Comma-separated list of tags \(e.g., "env:production,service:api"\) |
| `aggregationKey` | string | No | Key to aggregate events together |
| `sourceTypeName` | string | No | Source type name for the event |
| `dateHappened` | number | No | Unix timestamp when the event occurred \(defaults to now\) |
| `apiKey` | string | Yes | Datadog API key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `event` | object | The created event details |
### `datadog_create_monitor`
Create a new monitor/alert in Datadog. Monitors can track metrics, service checks, events, and more.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Monitor name |
| `type` | string | Yes | Monitor type: metric alert, service check, event alert, process alert, log alert, query alert, composite, synthetics alert, slo alert |
| `query` | string | Yes | Monitor query \(e.g., "avg\(last_5m\):avg:system.cpu.idle\{*\} &lt; 20"\) |
| `message` | string | No | Message to include with notifications. Can include @-mentions and markdown. |
| `tags` | string | No | Comma-separated list of tags |
| `priority` | number | No | Monitor priority \(1-5, where 1 is highest\) |
| `options` | string | No | JSON string of monitor options \(thresholds, notify_no_data, renotify_interval, etc.\) |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `monitor` | object | The created monitor details |
### `datadog_get_monitor`
Retrieve details of a specific monitor by ID.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `monitorId` | string | Yes | The ID of the monitor to retrieve |
| `groupStates` | string | No | Comma-separated group states to include: alert, warn, no data, ok |
| `withDowntimes` | boolean | No | Include downtime data with the monitor |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `monitor` | object | The monitor details |
### `datadog_list_monitors`
List all monitors in Datadog with optional filtering by name, tags, or state.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupStates` | string | No | Comma-separated group states to filter by: alert, warn, no data, ok |
| `name` | string | No | Filter monitors by name \(partial match\) |
| `tags` | string | No | Comma-separated list of tags to filter by |
| `monitorTags` | string | No | Comma-separated list of monitor tags to filter by |
| `withDowntimes` | boolean | No | Include downtime data with monitors |
| `page` | number | No | Page number for pagination \(0-indexed\) |
| `pageSize` | number | No | Number of monitors per page \(max 1000\) |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `monitors` | array | List of monitors |
### `datadog_mute_monitor`
Mute a monitor to temporarily suppress notifications.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `monitorId` | string | Yes | The ID of the monitor to mute |
| `scope` | string | No | Scope to mute \(e.g., "host:myhost"\). If not specified, mutes all scopes. |
| `end` | number | No | Unix timestamp when the mute should end. If not specified, mutes indefinitely. |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the monitor was successfully muted |
### `datadog_query_logs`
Search and retrieve logs from Datadog. Use for troubleshooting, analysis, or monitoring.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | Log search query \(e.g., "service:web-app status:error"\) |
| `from` | string | Yes | Start time in ISO-8601 format or relative \(e.g., "now-1h"\) |
| `to` | string | Yes | End time in ISO-8601 format or relative \(e.g., "now"\) |
| `limit` | number | No | Maximum number of logs to return \(default: 50, max: 1000\) |
| `sort` | string | No | Sort order: timestamp \(oldest first\) or -timestamp \(newest first\) |
| `indexes` | string | No | Comma-separated list of log indexes to search |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `logs` | array | List of log entries |
### `datadog_send_logs`
Send log entries to Datadog for centralized logging and analysis.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `logs` | string | Yes | JSON array of log entries. Each entry should have message and optionally ddsource, ddtags, hostname, service. |
| `apiKey` | string | Yes | Datadog API key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the logs were sent successfully |
### `datadog_create_downtime`
Schedule a downtime to suppress monitor notifications during maintenance windows.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `scope` | string | Yes | Scope to apply downtime to \(e.g., "host:myhost", "env:production", or "*" for all\) |
| `message` | string | No | Message to display during downtime |
| `start` | number | No | Unix timestamp for downtime start \(defaults to now\) |
| `end` | number | No | Unix timestamp for downtime end |
| `timezone` | string | No | Timezone for the downtime \(e.g., "America/New_York"\) |
| `monitorId` | string | No | Specific monitor ID to mute |
| `monitorTags` | string | No | Comma-separated monitor tags to match \(e.g., "team:backend,priority:high"\) |
| `muteFirstRecoveryNotification` | boolean | No | Mute the first recovery notification |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `downtime` | object | The created downtime details |
### `datadog_list_downtimes`
List all scheduled downtimes in Datadog.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `currentOnly` | boolean | No | Only return currently active downtimes |
| `monitorId` | string | No | Filter by monitor ID |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `downtimes` | array | List of downtimes |
### `datadog_cancel_downtime`
Cancel a scheduled downtime.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `downtimeId` | string | Yes | The ID of the downtime to cancel |
| `apiKey` | string | Yes | Datadog API key |
| `applicationKey` | string | Yes | Datadog Application key |
| `site` | string | No | Datadog site/region \(default: datadoghq.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the downtime was successfully canceled |
## Notes
- Category: `tools`
- Type: `datadog`

View File

@@ -0,0 +1,224 @@
---
title: Dropbox
description: Upload, download, share, and manage files in Dropbox
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="dropbox"
color="#0061FF"
/>
{/* MANUAL-CONTENT-START:intro */}
[Dropbox](https://dropbox.com/) is a popular cloud storage and collaboration platform that enables individuals and teams to securely store, access, and share files from anywhere. Dropbox is designed for easy file management, syncing, and powerful collaboration, whether you're working solo or with a group.
With Dropbox in Sim, you can:
- **Upload and download files**: Seamlessly upload any file to your Dropbox or retrieve content on demand
- **List folder contents**: Browse the files and folders within any Dropbox directory
- **Create new folders**: Organize your files by programmatically creating new folders in your Dropbox
- **Search files and folders**: Locate documents, images, or other items by name or content
- **Generate shared links**: Quickly create shareable public or private links for files and folders
- **Manage files**: Move, delete, or rename files and folders as part of automated workflows
These capabilities allow your Sim agents to automate Dropbox operations directly within your workflows — from backing up important files to distributing content and maintaining organized folders. Use Dropbox as both a source and destination for files, enabling seamless cloud storage management as part of your business processes.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Dropbox into your workflow for file management, sharing, and collaboration. Upload files, download content, create folders, manage shared links, and more.
## Tools
### `dropbox_upload`
Upload a file to Dropbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `path` | string | Yes | The path in Dropbox where the file should be saved \(e.g., /folder/document.pdf\) |
| `fileContent` | string | Yes | The base64 encoded content of the file to upload |
| `fileName` | string | No | Optional filename \(used if path is a folder\) |
| `mode` | string | No | Write mode: add \(default\) or overwrite |
| `autorename` | boolean | No | If true, rename the file if there is a conflict |
| `mute` | boolean | No | If true, don't notify the user about this upload |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `file` | object | The uploaded file metadata |
### `dropbox_download`
Download a file from Dropbox and get a temporary link
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `path` | string | Yes | The path of the file to download \(e.g., /folder/document.pdf\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `file` | object | The file metadata |
### `dropbox_list_folder`
List the contents of a folder in Dropbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `path` | string | Yes | The path of the folder to list \(use "" for root\) |
| `recursive` | boolean | No | If true, list contents recursively |
| `includeDeleted` | boolean | No | If true, include deleted files/folders |
| `includeMediaInfo` | boolean | No | If true, include media info for photos/videos |
| `limit` | number | No | Maximum number of results to return \(default: 500\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `entries` | array | List of files and folders in the directory |
### `dropbox_create_folder`
Create a new folder in Dropbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `path` | string | Yes | The path where the folder should be created \(e.g., /new-folder\) |
| `autorename` | boolean | No | If true, rename the folder if there is a conflict |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `folder` | object | The created folder metadata |
### `dropbox_delete`
Delete a file or folder in Dropbox (moves to trash)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `path` | string | Yes | The path of the file or folder to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `metadata` | object | Metadata of the deleted item |
### `dropbox_copy`
Copy a file or folder in Dropbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `fromPath` | string | Yes | The source path of the file or folder to copy |
| `toPath` | string | Yes | The destination path for the copied file or folder |
| `autorename` | boolean | No | If true, rename the file if there is a conflict at destination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `metadata` | object | Metadata of the copied item |
### `dropbox_move`
Move or rename a file or folder in Dropbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `fromPath` | string | Yes | The source path of the file or folder to move |
| `toPath` | string | Yes | The destination path for the moved file or folder |
| `autorename` | boolean | No | If true, rename the file if there is a conflict at destination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `metadata` | object | Metadata of the moved item |
### `dropbox_get_metadata`
Get metadata for a file or folder in Dropbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `path` | string | Yes | The path of the file or folder to get metadata for |
| `includeMediaInfo` | boolean | No | If true, include media info for photos/videos |
| `includeDeleted` | boolean | No | If true, include deleted files in results |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `metadata` | object | Metadata for the file or folder |
### `dropbox_create_shared_link`
Create a shareable link for a file or folder in Dropbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `path` | string | Yes | The path of the file or folder to share |
| `requestedVisibility` | string | No | Visibility: public, team_only, or password |
| `linkPassword` | string | No | Password for the shared link \(only if visibility is password\) |
| `expires` | string | No | Expiration date in ISO 8601 format \(e.g., 2025-12-31T23:59:59Z\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `sharedLink` | object | The created shared link |
### `dropbox_search`
Search for files and folders in Dropbox
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | The search query |
| `path` | string | No | Limit search to a specific folder path |
| `fileExtensions` | string | No | Comma-separated list of file extensions to filter by \(e.g., pdf,xlsx\) |
| `maxResults` | number | No | Maximum number of results to return \(default: 100\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `matches` | array | Search results |
## Notes
- Category: `tools`
- Type: `dropbox`

View File

@@ -0,0 +1,370 @@
---
title: Elasticsearch
description: Search, index, and manage data in Elasticsearch
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="elasticsearch"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Elasticsearch](https://www.elastic.co/elasticsearch/) is a powerful distributed search and analytics engine that enables you to index, search, and analyze large volumes of data in real time. Its widely used for powering search features, log and event data analytics, observability, and more.
With Elasticsearch in Sim, you gain programmatic access to core Elasticsearch capabilities, including:
- **Search documents**: Perform advanced searches on structured or unstructured text using Query DSL, with support for sorting, pagination, and field selection.
- **Index documents**: Add new documents or update existing ones in any Elasticsearch index for immediate retrieval and analysis.
- **Get, update, or delete documents**: Retrieve, modify, or remove specific documents by ID.
- **Bulk operations**: Execute multiple indexing or update actions in a single request for high-throughput data processing.
- **Manage indexes**: Create, delete, or get details about indexes as part of your workflow automation.
- **Cluster monitoring**: Check the health and stats of your Elasticsearch deployment.
Sim's Elasticsearch tools work with both self-hosted and Elastic Cloud environments. Integrate Elasticsearch into your agent workflows to automate data ingestion, search across vast datasets, run reporting, or build custom search-powered applications all without manual intervention.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Elasticsearch into workflows for powerful search, indexing, and data management. Supports document CRUD operations, advanced search queries, bulk operations, index management, and cluster monitoring. Works with both self-hosted and Elastic Cloud deployments.
## Tools
### `elasticsearch_search`
Search documents in Elasticsearch using Query DSL. Returns matching documents with scores and metadata.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Index name to search |
| `query` | string | No | Query DSL as JSON string |
| `from` | number | No | Starting offset for pagination \(default: 0\) |
| `size` | number | No | Number of results to return \(default: 10\) |
| `sort` | string | No | Sort specification as JSON string |
| `sourceIncludes` | string | No | Comma-separated list of fields to include in _source |
| `sourceExcludes` | string | No | Comma-separated list of fields to exclude from _source |
| `trackTotalHits` | boolean | No | Track accurate total hit count \(default: true\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `took` | number | Time in milliseconds the search took |
| `timed_out` | boolean | Whether the search timed out |
| `hits` | object | Search results with total count and matching documents |
| `aggregations` | json | Aggregation results if any |
### `elasticsearch_index_document`
Index (create or update) a document in Elasticsearch.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Target index name |
| `documentId` | string | No | Document ID \(auto-generated if not provided\) |
| `document` | string | Yes | Document body as JSON string |
| `refresh` | string | No | Refresh policy: true, false, or wait_for |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `_index` | string | Index where the document was stored |
| `_id` | string | Document ID |
| `_version` | number | Document version |
| `result` | string | Operation result \(created or updated\) |
### `elasticsearch_get_document`
Retrieve a document by ID from Elasticsearch.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Index name |
| `documentId` | string | Yes | Document ID to retrieve |
| `sourceIncludes` | string | No | Comma-separated list of fields to include |
| `sourceExcludes` | string | No | Comma-separated list of fields to exclude |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `_index` | string | Index name |
| `_id` | string | Document ID |
| `_version` | number | Document version |
| `found` | boolean | Whether the document was found |
| `_source` | json | Document content |
### `elasticsearch_update_document`
Partially update a document in Elasticsearch using doc merge.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Index name |
| `documentId` | string | Yes | Document ID to update |
| `document` | string | Yes | Partial document to merge as JSON string |
| `retryOnConflict` | number | No | Number of retries on version conflict |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `_index` | string | Index name |
| `_id` | string | Document ID |
| `_version` | number | New document version |
| `result` | string | Operation result \(updated or noop\) |
### `elasticsearch_delete_document`
Delete a document from Elasticsearch by ID.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Index name |
| `documentId` | string | Yes | Document ID to delete |
| `refresh` | string | No | Refresh policy: true, false, or wait_for |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `_index` | string | Index name |
| `_id` | string | Document ID |
| `_version` | number | Document version |
| `result` | string | Operation result \(deleted or not_found\) |
### `elasticsearch_bulk`
Perform multiple index, create, delete, or update operations in a single request for high performance.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | No | Default index for operations that do not specify one |
| `operations` | string | Yes | Bulk operations as NDJSON string \(newline-delimited JSON\) |
| `refresh` | string | No | Refresh policy: true, false, or wait_for |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `took` | number | Time in milliseconds the bulk operation took |
| `errors` | boolean | Whether any operation had an error |
| `items` | array | Results for each operation |
### `elasticsearch_count`
Count documents matching a query in Elasticsearch.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Index name to count documents in |
| `query` | string | No | Optional query to filter documents \(JSON string\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `count` | number | Number of documents matching the query |
| `_shards` | object | Shard statistics |
### `elasticsearch_create_index`
Create a new index with optional settings and mappings.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Index name to create |
| `settings` | string | No | Index settings as JSON string |
| `mappings` | string | No | Index mappings as JSON string |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `acknowledged` | boolean | Whether the request was acknowledged |
| `shards_acknowledged` | boolean | Whether the shards were acknowledged |
| `index` | string | Created index name |
### `elasticsearch_delete_index`
Delete an index and all its documents. This operation is irreversible.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Index name to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `acknowledged` | boolean | Whether the deletion was acknowledged |
### `elasticsearch_get_index`
Retrieve index information including settings, mappings, and aliases.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `index` | string | Yes | Index name to retrieve info for |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `index` | json | Index information including aliases, mappings, and settings |
### `elasticsearch_cluster_health`
Get the health status of the Elasticsearch cluster.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
| `waitForStatus` | string | No | Wait until cluster reaches this status: green, yellow, or red |
| `timeout` | string | No | Timeout for the wait operation \(e.g., 30s, 1m\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `cluster_name` | string | Name of the cluster |
| `status` | string | Cluster health status: green, yellow, or red |
| `number_of_nodes` | number | Total number of nodes in the cluster |
| `number_of_data_nodes` | number | Number of data nodes |
| `active_shards` | number | Number of active shards |
| `unassigned_shards` | number | Number of unassigned shards |
### `elasticsearch_cluster_stats`
Get comprehensive statistics about the Elasticsearch cluster.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `deploymentType` | string | Yes | Deployment type: self_hosted or cloud |
| `host` | string | No | Elasticsearch host URL \(for self-hosted\) |
| `cloudId` | string | No | Elastic Cloud ID \(for cloud deployments\) |
| `authMethod` | string | Yes | Authentication method: api_key or basic_auth |
| `apiKey` | string | No | Elasticsearch API key |
| `username` | string | No | Username for basic auth |
| `password` | string | No | Password for basic auth |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `cluster_name` | string | Name of the cluster |
| `status` | string | Cluster health status |
| `nodes` | object | Node statistics including count and versions |
| `indices` | object | Index statistics including document count and store size |
## Notes
- Category: `tools`
- Type: `elasticsearch`

View File

@@ -0,0 +1,434 @@
---
title: GitLab
description: Interact with GitLab projects, issues, merge requests, and pipelines
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="gitlab"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[GitLab](https://gitlab.com/) is a comprehensive DevOps platform that allows teams to manage, collaborate on, and automate their software development lifecycle. With GitLab, you can effortlessly handle source code management, CI/CD, reviews, and collaboration in a single application.
With GitLab in Sim, you can:
- **Manage projects and repositories**: List and retrieve your GitLab projects, access details, and organize your repositories
- **Work with issues**: List, create, and comment on issues to track work and collaborate effectively
- **Handle merge requests**: Review, create, and manage merge requests for code changes and peer reviews
- **Automate CI/CD pipelines**: Trigger, monitor, and interact with GitLab pipelines as part of your automation flows
- **Collaborate with comments**: Add comments to issues or merge requests for efficient communication within your team
Using Sims GitLab integration, your agents can programmatically interact with your GitLab projects. Automate project management, issue tracking, code reviews, and pipeline operations seamlessly in your workflows, optimizing your software development process and enhancing collaboration across your team.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate GitLab into the workflow. Can manage projects, issues, merge requests, pipelines, and add comments. Supports all core GitLab DevOps operations.
## Tools
### `gitlab_list_projects`
List GitLab projects accessible to the authenticated user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `owned` | boolean | No | Limit to projects owned by the current user |
| `membership` | boolean | No | Limit to projects the current user is a member of |
| `search` | string | No | Search projects by name |
| `visibility` | string | No | Filter by visibility \(public, internal, private\) |
| `orderBy` | string | No | Order by field \(id, name, path, created_at, updated_at, last_activity_at\) |
| `sort` | string | No | Sort direction \(asc, desc\) |
| `perPage` | number | No | Number of results per page \(default 20, max 100\) |
| `page` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projects` | array | List of GitLab projects |
| `total` | number | Total number of projects |
### `gitlab_get_project`
Get details of a specific GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path \(e.g., "namespace/project"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `project` | object | The GitLab project details |
### `gitlab_list_issues`
List issues in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `state` | string | No | Filter by state \(opened, closed, all\) |
| `labels` | string | No | Comma-separated list of label names |
| `assigneeId` | number | No | Filter by assignee user ID |
| `milestoneTitle` | string | No | Filter by milestone title |
| `search` | string | No | Search issues by title and description |
| `orderBy` | string | No | Order by field \(created_at, updated_at\) |
| `sort` | string | No | Sort direction \(asc, desc\) |
| `perPage` | number | No | Number of results per page \(default 20, max 100\) |
| `page` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `issues` | array | List of GitLab issues |
| `total` | number | Total number of issues |
### `gitlab_get_issue`
Get details of a specific GitLab issue
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `issueIid` | number | Yes | Issue number within the project \(the # shown in GitLab UI\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `issue` | object | The GitLab issue details |
### `gitlab_create_issue`
Create a new issue in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `title` | string | Yes | Issue title |
| `description` | string | No | Issue description \(Markdown supported\) |
| `labels` | string | No | Comma-separated list of label names |
| `assigneeIds` | array | No | Array of user IDs to assign |
| `milestoneId` | number | No | Milestone ID to assign |
| `dueDate` | string | No | Due date in YYYY-MM-DD format |
| `confidential` | boolean | No | Whether the issue is confidential |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `issue` | object | The created GitLab issue |
### `gitlab_update_issue`
Update an existing issue in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `issueIid` | number | Yes | Issue internal ID \(IID\) |
| `title` | string | No | New issue title |
| `description` | string | No | New issue description \(Markdown supported\) |
| `stateEvent` | string | No | State event \(close or reopen\) |
| `labels` | string | No | Comma-separated list of label names |
| `assigneeIds` | array | No | Array of user IDs to assign |
| `milestoneId` | number | No | Milestone ID to assign |
| `dueDate` | string | No | Due date in YYYY-MM-DD format |
| `confidential` | boolean | No | Whether the issue is confidential |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `issue` | object | The updated GitLab issue |
### `gitlab_delete_issue`
Delete an issue from a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `issueIid` | number | Yes | Issue internal ID \(IID\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the issue was deleted successfully |
### `gitlab_create_issue_note`
Add a comment to a GitLab issue
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `issueIid` | number | Yes | Issue internal ID \(IID\) |
| `body` | string | Yes | Comment body \(Markdown supported\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `note` | object | The created comment |
### `gitlab_list_merge_requests`
List merge requests in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `state` | string | No | Filter by state \(opened, closed, merged, all\) |
| `labels` | string | No | Comma-separated list of label names |
| `sourceBranch` | string | No | Filter by source branch |
| `targetBranch` | string | No | Filter by target branch |
| `orderBy` | string | No | Order by field \(created_at, updated_at\) |
| `sort` | string | No | Sort direction \(asc, desc\) |
| `perPage` | number | No | Number of results per page \(default 20, max 100\) |
| `page` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `mergeRequests` | array | List of GitLab merge requests |
| `total` | number | Total number of merge requests |
### `gitlab_get_merge_request`
Get details of a specific GitLab merge request
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `mergeRequest` | object | The GitLab merge request details |
### `gitlab_create_merge_request`
Create a new merge request in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `sourceBranch` | string | Yes | Source branch name |
| `targetBranch` | string | Yes | Target branch name |
| `title` | string | Yes | Merge request title |
| `description` | string | No | Merge request description \(Markdown supported\) |
| `labels` | string | No | Comma-separated list of label names |
| `assigneeIds` | array | No | Array of user IDs to assign |
| `milestoneId` | number | No | Milestone ID to assign |
| `removeSourceBranch` | boolean | No | Delete source branch after merge |
| `squash` | boolean | No | Squash commits on merge |
| `draft` | boolean | No | Mark as draft \(work in progress\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `mergeRequest` | object | The created GitLab merge request |
### `gitlab_update_merge_request`
Update an existing merge request in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) |
| `title` | string | No | New merge request title |
| `description` | string | No | New merge request description |
| `stateEvent` | string | No | State event \(close or reopen\) |
| `labels` | string | No | Comma-separated list of label names |
| `assigneeIds` | array | No | Array of user IDs to assign |
| `milestoneId` | number | No | Milestone ID to assign |
| `targetBranch` | string | No | New target branch |
| `removeSourceBranch` | boolean | No | Delete source branch after merge |
| `squash` | boolean | No | Squash commits on merge |
| `draft` | boolean | No | Mark as draft \(work in progress\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `mergeRequest` | object | The updated GitLab merge request |
### `gitlab_merge_merge_request`
Merge a merge request in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) |
| `mergeCommitMessage` | string | No | Custom merge commit message |
| `squashCommitMessage` | string | No | Custom squash commit message |
| `squash` | boolean | No | Squash commits before merging |
| `shouldRemoveSourceBranch` | boolean | No | Delete source branch after merge |
| `mergeWhenPipelineSucceeds` | boolean | No | Merge when pipeline succeeds |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `mergeRequest` | object | The merged GitLab merge request |
### `gitlab_create_merge_request_note`
Add a comment to a GitLab merge request
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) |
| `body` | string | Yes | Comment body \(Markdown supported\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `note` | object | The created comment |
### `gitlab_list_pipelines`
List pipelines in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `ref` | string | No | Filter by ref \(branch or tag\) |
| `status` | string | No | Filter by status \(created, waiting_for_resource, preparing, pending, running, success, failed, canceled, skipped, manual, scheduled\) |
| `orderBy` | string | No | Order by field \(id, status, ref, updated_at, user_id\) |
| `sort` | string | No | Sort direction \(asc, desc\) |
| `perPage` | number | No | Number of results per page \(default 20, max 100\) |
| `page` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pipelines` | array | List of GitLab pipelines |
| `total` | number | Total number of pipelines |
### `gitlab_get_pipeline`
Get details of a specific GitLab pipeline
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `pipelineId` | number | Yes | Pipeline ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pipeline` | object | The GitLab pipeline details |
### `gitlab_create_pipeline`
Trigger a new pipeline in a GitLab project
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `ref` | string | Yes | Branch or tag to run the pipeline on |
| `variables` | array | No | Array of variables for the pipeline \(each with key, value, and optional variable_type\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pipeline` | object | The created GitLab pipeline |
### `gitlab_retry_pipeline`
Retry a failed GitLab pipeline
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `pipelineId` | number | Yes | Pipeline ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pipeline` | object | The retried GitLab pipeline |
### `gitlab_cancel_pipeline`
Cancel a running GitLab pipeline
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID or URL-encoded path |
| `pipelineId` | number | Yes | Pipeline ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pipeline` | object | The cancelled GitLab pipeline |
## Notes
- Category: `tools`
- Type: `gitlab`

View File

@@ -0,0 +1,499 @@
---
title: Grafana
description: Interact with Grafana dashboards, alerts, and annotations
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="grafana"
color="#E0E0E0"
/>
{/* MANUAL-CONTENT-START:intro */}
[Grafana](https://grafana.com/) is a leading open-source platform for monitoring, observability, and visualization. It allows users to query, visualize, alert on, and analyze data from a variety of sources, making it an essential tool for infrastructure and application monitoring.
With Grafana, you can:
- **Visualize data**: Build and customize dashboards to display metrics, logs, and traces in real time
- **Monitor health and status**: Check the health of your Grafana instance and connected data sources
- **Manage alerts and annotations**: Set up alert rules, manage notifications, and annotate dashboards with important events
- **Organize content**: Organize dashboards and data sources into folders for better access management
In Sim, the Grafana integration empowers your agents to interact directly with your Grafana instance via API, enabling actions such as:
- Checking the Grafana server, database, and data source health status
- Retrieving, listing, and managing dashboards, alert rules, annotations, data sources, and folders
- Automating the monitoring of your infrastructure by integrating Grafana data and alerts into your workflow automations
These capabilities enable Sim agents to monitor systems, proactively respond to alerts, and help ensure the reliability and visibility of your services — all as part of your automated workflows.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Grafana into workflows. Manage dashboards, alerts, annotations, data sources, folders, and monitor health status.
## Tools
### `grafana_get_dashboard`
Get a dashboard by its UID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `dashboardUid` | string | Yes | The UID of the dashboard to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `dashboard` | json | The full dashboard JSON object |
| `meta` | json | Dashboard metadata \(version, permissions, etc.\) |
### `grafana_list_dashboards`
Search and list all dashboards
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `query` | string | No | Search query to filter dashboards by title |
| `tag` | string | No | Filter by tag \(comma-separated for multiple tags\) |
| `folderIds` | string | No | Filter by folder IDs \(comma-separated\) |
| `starred` | boolean | No | Only return starred dashboards |
| `limit` | number | No | Maximum number of dashboards to return |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `dashboards` | array | List of dashboard search results |
### `grafana_create_dashboard`
Create a new dashboard
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `title` | string | Yes | The title of the new dashboard |
| `folderUid` | string | No | The UID of the folder to create the dashboard in |
| `tags` | string | No | Comma-separated list of tags |
| `timezone` | string | No | Dashboard timezone \(e.g., browser, utc\) |
| `refresh` | string | No | Auto-refresh interval \(e.g., 5s, 1m, 5m\) |
| `panels` | string | No | JSON array of panel configurations |
| `overwrite` | boolean | No | Overwrite existing dashboard with same title |
| `message` | string | No | Commit message for the dashboard version |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | number | The numeric ID of the created dashboard |
| `uid` | string | The UID of the created dashboard |
| `url` | string | The URL path to the dashboard |
| `status` | string | Status of the operation \(success\) |
| `version` | number | The version number of the dashboard |
| `slug` | string | URL-friendly slug of the dashboard |
### `grafana_update_dashboard`
Update an existing dashboard. Fetches the current dashboard and merges your changes.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `dashboardUid` | string | Yes | The UID of the dashboard to update |
| `title` | string | No | New title for the dashboard |
| `folderUid` | string | No | New folder UID to move the dashboard to |
| `tags` | string | No | Comma-separated list of new tags |
| `timezone` | string | No | Dashboard timezone \(e.g., browser, utc\) |
| `refresh` | string | No | Auto-refresh interval \(e.g., 5s, 1m, 5m\) |
| `panels` | string | No | JSON array of panel configurations |
| `overwrite` | boolean | No | Overwrite even if there is a version conflict |
| `message` | string | No | Commit message for this version |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | number | The numeric ID of the updated dashboard |
| `uid` | string | The UID of the updated dashboard |
| `url` | string | The URL path to the dashboard |
| `status` | string | Status of the operation \(success\) |
| `version` | number | The new version number of the dashboard |
| `slug` | string | URL-friendly slug of the dashboard |
### `grafana_delete_dashboard`
Delete a dashboard by its UID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `dashboardUid` | string | Yes | The UID of the dashboard to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `title` | string | The title of the deleted dashboard |
| `message` | string | Confirmation message |
| `id` | number | The ID of the deleted dashboard |
### `grafana_list_alert_rules`
List all alert rules in the Grafana instance
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `rules` | array | List of alert rules |
### `grafana_get_alert_rule`
Get a specific alert rule by its UID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `alertRuleUid` | string | Yes | The UID of the alert rule to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `uid` | string | Alert rule UID |
| `title` | string | Alert rule title |
| `condition` | string | Alert condition |
| `data` | json | Alert rule query data |
| `folderUID` | string | Parent folder UID |
| `ruleGroup` | string | Rule group name |
| `noDataState` | string | State when no data is returned |
| `execErrState` | string | State on execution error |
| `annotations` | json | Alert annotations |
| `labels` | json | Alert labels |
### `grafana_create_alert_rule`
Create a new alert rule
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `title` | string | Yes | The title of the alert rule |
| `folderUid` | string | Yes | The UID of the folder to create the alert in |
| `ruleGroup` | string | Yes | The name of the rule group |
| `condition` | string | Yes | The refId of the query or expression to use as the alert condition |
| `data` | string | Yes | JSON array of query/expression data objects |
| `forDuration` | string | No | Duration to wait before firing \(e.g., 5m, 1h\) |
| `noDataState` | string | No | State when no data is returned \(NoData, Alerting, OK\) |
| `execErrState` | string | No | State on execution error \(Alerting, OK\) |
| `annotations` | string | No | JSON object of annotations |
| `labels` | string | No | JSON object of labels |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `uid` | string | The UID of the created alert rule |
| `title` | string | Alert rule title |
| `folderUID` | string | Parent folder UID |
| `ruleGroup` | string | Rule group name |
### `grafana_update_alert_rule`
Update an existing alert rule. Fetches the current rule and merges your changes.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `alertRuleUid` | string | Yes | The UID of the alert rule to update |
| `title` | string | No | New title for the alert rule |
| `folderUid` | string | No | New folder UID to move the alert to |
| `ruleGroup` | string | No | New rule group name |
| `condition` | string | No | New condition refId |
| `data` | string | No | New JSON array of query/expression data objects |
| `forDuration` | string | No | Duration to wait before firing \(e.g., 5m, 1h\) |
| `noDataState` | string | No | State when no data is returned \(NoData, Alerting, OK\) |
| `execErrState` | string | No | State on execution error \(Alerting, OK\) |
| `annotations` | string | No | JSON object of annotations |
| `labels` | string | No | JSON object of labels |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `uid` | string | The UID of the updated alert rule |
| `title` | string | Alert rule title |
| `folderUID` | string | Parent folder UID |
| `ruleGroup` | string | Rule group name |
### `grafana_delete_alert_rule`
Delete an alert rule by its UID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `alertRuleUid` | string | Yes | The UID of the alert rule to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Confirmation message |
### `grafana_list_contact_points`
List all alert notification contact points
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `contactPoints` | array | List of contact points |
### `grafana_create_annotation`
Create an annotation on a dashboard or as a global annotation
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `text` | string | Yes | The text content of the annotation |
| `tags` | string | No | Comma-separated list of tags |
| `dashboardUid` | string | No | UID of the dashboard to add the annotation to \(optional for global annotations\) |
| `panelId` | number | No | ID of the panel to add the annotation to |
| `time` | number | No | Start time in epoch milliseconds \(defaults to now\) |
| `timeEnd` | number | No | End time in epoch milliseconds \(for range annotations\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | number | The ID of the created annotation |
| `message` | string | Confirmation message |
### `grafana_list_annotations`
Query annotations by time range, dashboard, or tags
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `from` | number | No | Start time in epoch milliseconds |
| `to` | number | No | End time in epoch milliseconds |
| `dashboardUid` | string | No | Filter by dashboard UID |
| `panelId` | number | No | Filter by panel ID |
| `tags` | string | No | Comma-separated list of tags to filter by |
| `type` | string | No | Filter by type \(alert or annotation\) |
| `limit` | number | No | Maximum number of annotations to return |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `annotations` | array | List of annotations |
### `grafana_update_annotation`
Update an existing annotation
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `annotationId` | number | Yes | The ID of the annotation to update |
| `text` | string | Yes | New text content for the annotation |
| `tags` | string | No | Comma-separated list of new tags |
| `time` | number | No | New start time in epoch milliseconds |
| `timeEnd` | number | No | New end time in epoch milliseconds |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | number | The ID of the updated annotation |
| `message` | string | Confirmation message |
### `grafana_delete_annotation`
Delete an annotation by its ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `annotationId` | number | Yes | The ID of the annotation to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Confirmation message |
### `grafana_list_data_sources`
List all data sources configured in Grafana
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `dataSources` | array | List of data sources |
### `grafana_get_data_source`
Get a data source by its ID or UID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `dataSourceId` | string | Yes | The ID or UID of the data source to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | number | Data source ID |
| `uid` | string | Data source UID |
| `name` | string | Data source name |
| `type` | string | Data source type |
| `url` | string | Data source connection URL |
| `database` | string | Database name \(if applicable\) |
| `isDefault` | boolean | Whether this is the default data source |
| `jsonData` | json | Additional data source configuration |
### `grafana_list_folders`
List all folders in Grafana
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `limit` | number | No | Maximum number of folders to return |
| `page` | number | No | Page number for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `folders` | array | List of folders |
### `grafana_create_folder`
Create a new folder in Grafana
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Grafana Service Account Token |
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
| `organizationId` | string | No | Organization ID for multi-org Grafana instances |
| `title` | string | Yes | The title of the new folder |
| `uid` | string | No | Optional UID for the folder \(auto-generated if not provided\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `id` | number | The numeric ID of the created folder |
| `uid` | string | The UID of the created folder |
| `title` | string | The title of the created folder |
| `url` | string | The URL path to the folder |
## Notes
- Category: `tools`
- Type: `grafana`

View File

@@ -0,0 +1,300 @@
---
title: Kalshi
description: Access prediction markets data from Kalshi
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="kalshi"
color="#09C285"
/>
{/* MANUAL-CONTENT-START:intro */}
[Kalshi](https://kalshi.com) is a federally regulated exchange where users can trade directly on the outcomes of future events—prediction markets. Kalshis robust API and Sim integration enable agents and workflows to programmatically access all aspects of the platform, supporting everything from research and analytics to automated trading and monitoring.
With Kalshis integration in Sim, you can:
- **Market & Event Data:** Search, filter, and retrieve real-time and historical data for markets and events; fetch granular details on market status, series, event groupings, and more.
- **Account & Balance Management:** Access account balances, available funds, and monitor real-time open positions.
- **Order & Trade Management:** Place new orders, cancel existing ones, view open orders, retrieve a live orderbook, and access complete trade histories.
- **Execution Analysis:** Fetch recent trades, historical fills, and candlestick data for backtesting or market structure research.
- **Monitoring:** Check exchange-wide or series-level status, receive real-time updates about market changes or trading halts, and automate responses.
- **Automation Ready:** Build end-to-end automated agents and dashboards that consume, analyze, and trade on real-world event probabilities.
By using these unified tools and endpoints, you can seamlessly incorporate Kalshis prediction markets, live trading capabilities, and deep event data into your AI-powered applications, dashboards, and workflows—enabling sophisticated, automated decision-making tied to real-world outcomes.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, and exchange status.
## Tools
### `kalshi_get_markets`
Retrieve a list of prediction markets from Kalshi with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `status` | string | No | Filter by status \(unopened, open, closed, settled\) |
| `seriesTicker` | string | No | Filter by series ticker |
| `eventTicker` | string | No | Filter by event ticker |
| `limit` | string | No | Number of results \(1-1000, default: 100\) |
| `cursor` | string | No | Pagination cursor for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Markets data and metadata |
### `kalshi_get_market`
Retrieve details of a specific prediction market by ticker
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `ticker` | string | Yes | The market ticker \(e.g., "KXBTC-24DEC31"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Market data and metadata |
### `kalshi_get_events`
Retrieve a list of events from Kalshi with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `status` | string | No | Filter by status \(open, closed, settled\) |
| `seriesTicker` | string | No | Filter by series ticker |
| `withNestedMarkets` | string | No | Include nested markets in response \(true/false\) |
| `limit` | string | No | Number of results \(1-200, default: 200\) |
| `cursor` | string | No | Pagination cursor for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Events data and metadata |
### `kalshi_get_event`
Retrieve details of a specific event by ticker
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `eventTicker` | string | Yes | The event ticker |
| `withNestedMarkets` | string | No | Include nested markets in response \(true/false\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Event data and metadata |
### `kalshi_get_balance`
Retrieve your account balance and portfolio value from Kalshi
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `keyId` | string | Yes | Your Kalshi API Key ID |
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Balance data and metadata |
### `kalshi_get_positions`
Retrieve your open positions from Kalshi
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `keyId` | string | Yes | Your Kalshi API Key ID |
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
| `ticker` | string | No | Filter by market ticker |
| `eventTicker` | string | No | Filter by event ticker \(max 10 comma-separated\) |
| `settlementStatus` | string | No | Filter by settlement status \(all, unsettled, settled\). Default: unsettled |
| `limit` | string | No | Number of results \(1-1000, default: 100\) |
| `cursor` | string | No | Pagination cursor for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Positions data and metadata |
### `kalshi_get_orders`
Retrieve your orders from Kalshi with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `keyId` | string | Yes | Your Kalshi API Key ID |
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
| `ticker` | string | No | Filter by market ticker |
| `eventTicker` | string | No | Filter by event ticker \(max 10 comma-separated\) |
| `status` | string | No | Filter by status \(resting, canceled, executed\) |
| `limit` | string | No | Number of results \(1-200, default: 100\) |
| `cursor` | string | No | Pagination cursor for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Orders data and metadata |
### `kalshi_get_orderbook`
Retrieve the orderbook (bids and asks) for a specific market
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `ticker` | string | Yes | Market ticker \(e.g., KXBTC-24DEC31\) |
| `depth` | number | No | Number of price levels to return per side |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Orderbook data and metadata |
### `kalshi_get_trades`
Retrieve recent trades across all markets or for a specific market
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `ticker` | string | No | Filter by market ticker |
| `minTs` | number | No | Minimum timestamp \(Unix milliseconds\) |
| `maxTs` | number | No | Maximum timestamp \(Unix milliseconds\) |
| `limit` | string | No | Number of results \(1-1000, default: 100\) |
| `cursor` | string | No | Pagination cursor for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Trades data and metadata |
### `kalshi_get_candlesticks`
Retrieve OHLC candlestick data for a specific market
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `seriesTicker` | string | Yes | Series ticker |
| `ticker` | string | Yes | Market ticker \(e.g., KXBTC-24DEC31\) |
| `startTs` | number | No | Start timestamp \(Unix milliseconds\) |
| `endTs` | number | No | End timestamp \(Unix milliseconds\) |
| `periodInterval` | number | No | Period interval: 1 \(1min\), 60 \(1hour\), or 1440 \(1day\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Candlestick data and metadata |
### `kalshi_get_fills`
Retrieve your portfolio
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `keyId` | string | Yes | Your Kalshi API Key ID |
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
| `ticker` | string | No | Filter by market ticker |
| `orderId` | string | No | Filter by order ID |
| `minTs` | number | No | Minimum timestamp \(Unix milliseconds\) |
| `maxTs` | number | No | Maximum timestamp \(Unix milliseconds\) |
| `limit` | string | No | Number of results \(1-1000, default: 100\) |
| `cursor` | string | No | Pagination cursor for next page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Fills data and metadata |
### `kalshi_get_series_by_ticker`
Retrieve details of a specific market series by ticker
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `seriesTicker` | string | Yes | Series ticker |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Series data and metadata |
### `kalshi_get_exchange_status`
Retrieve the current status of the Kalshi exchange (trading and exchange activity)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Exchange status data and metadata |
## Notes
- Category: `tools`
- Type: `kalshi`

View File

@@ -1,6 +1,7 @@
{
"pages": [
"index",
"ahrefs",
"airtable",
"apify",
"apollo",
@@ -10,13 +11,17 @@
"calendly",
"clay",
"confluence",
"datadog",
"discord",
"dropbox",
"dynamodb",
"elasticsearch",
"elevenlabs",
"exa",
"file",
"firecrawl",
"github",
"gitlab",
"gmail",
"google_calendar",
"google_docs",
@@ -25,6 +30,7 @@
"google_search",
"google_sheets",
"google_vault",
"grafana",
"hubspot",
"huggingface",
"hunter",
@@ -33,6 +39,7 @@
"intercom",
"jina",
"jira",
"kalshi",
"knowledge",
"linear",
"linkedin",
@@ -56,6 +63,7 @@
"perplexity",
"pinecone",
"pipedrive",
"polymarket",
"postgresql",
"posthog",
"pylon",
@@ -70,8 +78,10 @@
"sentry",
"serper",
"sharepoint",
"shopify",
"slack",
"smtp",
"ssh",
"stagehand",
"stagehand_agent",
"stripe",
@@ -92,9 +102,11 @@
"webflow",
"whatsapp",
"wikipedia",
"wordpress",
"x",
"youtube",
"zendesk",
"zep"
"zep",
"zoom"
]
}

View File

@@ -0,0 +1,357 @@
---
title: Polymarket
description: Access prediction markets data from Polymarket
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="polymarket"
color="#4C82FB"
/>
{/* MANUAL-CONTENT-START:intro */}
[Polymarket](https://polymarket.com) is a decentralized prediction markets platform where users can trade on the outcome of future events using blockchain technology. Polymarket provides a comprehensive API, enabling developers and agents to access live market data, event listings, price information, and orderbook statistics to power data-driven workflows and AI automations.
With Polymarkets API and Sim integration, you can enable agents to programmatically retrieve prediction market information, explore open markets and associated events, analyze historical price data, and access orderbooks and market midpoints. This creates new possibilities for research, automated analysis, and developing intelligent agents that react to real-time event probabilities derived from market prices.
Key features of the Polymarket integration include:
- **Market Listing & Filtering:** List all current or historical prediction markets, filter by tag, sort, and paginate through results.
- **Market Detail:** Retrieve details for a single market by market ID or slug, including its outcomes and status.
- **Event Listings:** Access lists of Polymarket events and detailed event information.
- **Orderbook & Price Data:** Analyze the orderbook, get the latest market prices, view the midpoint, or obtain historical price information for any market.
- **Automation Ready:** Build agents or tools that react programmatically to market developments, changing odds, or specific event outcomes.
By using these documented API endpoints, you can seamlessly integrate Polymarkets rich on-chain prediction market data into your own AI workflows, dashboards, research tools, and trading automations.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Polymarket prediction markets into the workflow. Can get markets, market, events, event, tags, series, orderbook, price, midpoint, price history, last trade price, spread, tick size, positions, trades, and search.
## Tools
### `polymarket_get_markets`
Retrieve a list of prediction markets from Polymarket with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `closed` | string | No | Filter by closed status \(true/false\). Use false for active markets only. |
| `order` | string | No | Sort field \(e.g., id, volume, liquidity\) |
| `ascending` | string | No | Sort direction \(true for ascending, false for descending\) |
| `tagId` | string | No | Filter by tag ID |
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
| `offset` | string | No | Pagination offset \(skip this many results\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Markets data and metadata |
### `polymarket_get_market`
Retrieve details of a specific prediction market by ID or slug
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `marketId` | string | No | The market ID. Required if slug is not provided. |
| `slug` | string | No | The market slug \(e.g., "will-trump-win"\). Required if marketId is not provided. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Market data and metadata |
### `polymarket_get_events`
Retrieve a list of events from Polymarket with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `closed` | string | No | Filter by closed status \(true/false\). Use false for active events only. |
| `order` | string | No | Sort field \(e.g., id, volume\) |
| `ascending` | string | No | Sort direction \(true for ascending, false for descending\) |
| `tagId` | string | No | Filter by tag ID |
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
| `offset` | string | No | Pagination offset \(skip this many results\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Events data and metadata |
### `polymarket_get_event`
Retrieve details of a specific event by ID or slug
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `eventId` | string | No | The event ID. Required if slug is not provided. |
| `slug` | string | No | The event slug \(e.g., "2024-presidential-election"\). Required if eventId is not provided. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Event data and metadata |
### `polymarket_get_tags`
Retrieve available tags for filtering markets from Polymarket
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
| `offset` | string | No | Pagination offset \(skip this many results\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Tags data and metadata |
### `polymarket_search`
Search for markets, events, and profiles on Polymarket
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | Search query term |
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
| `offset` | string | No | Pagination offset \(skip this many results\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Search results and metadata |
### `polymarket_get_series`
Retrieve series (related market groups) from Polymarket
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
| `offset` | string | No | Pagination offset \(skip this many results\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Series data and metadata |
### `polymarket_get_series_by_id`
Retrieve a specific series (related market group) by ID from Polymarket
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `seriesId` | string | Yes | The series ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Series data and metadata |
### `polymarket_get_orderbook`
Retrieve the order book summary for a specific token
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tokenId` | string | Yes | The CLOB token ID \(from market clobTokenIds\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Orderbook data and metadata |
### `polymarket_get_price`
Retrieve the market price for a specific token and side
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tokenId` | string | Yes | The CLOB token ID \(from market clobTokenIds\) |
| `side` | string | Yes | Order side: buy or sell |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Price data and metadata |
### `polymarket_get_midpoint`
Retrieve the midpoint price for a specific token
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tokenId` | string | Yes | The CLOB token ID \(from market clobTokenIds\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Midpoint price data and metadata |
### `polymarket_get_price_history`
Retrieve historical price data for a specific market token
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tokenId` | string | Yes | The CLOB token ID \(from market clobTokenIds\) |
| `interval` | string | No | Duration ending at current time \(1m, 1h, 6h, 1d, 1w, max\). Mutually exclusive with startTs/endTs. |
| `fidelity` | number | No | Data resolution in minutes \(e.g., 60 for hourly\) |
| `startTs` | number | No | Start timestamp \(Unix seconds UTC\) |
| `endTs` | number | No | End timestamp \(Unix seconds UTC\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Price history data and metadata |
### `polymarket_get_last_trade_price`
Retrieve the last trade price for a specific token
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tokenId` | string | Yes | The CLOB token ID \(from market clobTokenIds\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Last trade price and metadata |
### `polymarket_get_spread`
Retrieve the bid-ask spread for a specific token
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tokenId` | string | Yes | The CLOB token ID \(from market clobTokenIds\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Spread data and metadata |
### `polymarket_get_tick_size`
Retrieve the minimum tick size for a specific token
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `tokenId` | string | Yes | The CLOB token ID \(from market clobTokenIds\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Tick size and metadata |
### `polymarket_get_positions`
Retrieve user positions from Polymarket
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `user` | string | Yes | User wallet address |
| `market` | string | No | Optional market ID to filter positions |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Positions data and metadata |
### `polymarket_get_trades`
Retrieve trade history from Polymarket
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `user` | string | No | User wallet address to filter trades |
| `market` | string | No | Market ID to filter trades |
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
| `offset` | string | No | Pagination offset \(skip this many results\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | object | Trades data and metadata |
## Notes
- Category: `tools`
- Type: `polymarket`

View File

@@ -89,6 +89,13 @@ Create a new issue with specified properties
| `bodyHtml` | string | Yes | Issue body in HTML format |
| `accountId` | string | No | Account ID to associate with issue |
| `assigneeId` | string | No | User ID to assign issue to |
| `teamId` | string | No | Team ID to assign issue to |
| `requesterId` | string | No | Requester user ID \(alternative to requester_email\) |
| `requesterEmail` | string | No | Requester email address \(alternative to requester_id\) |
| `priority` | string | No | Issue priority |
| `tags` | string | No | Comma-separated tag IDs |
| `customFields` | string | No | Custom fields as JSON object |
| `attachmentUrls` | string | No | Comma-separated attachment URLs |
#### Output
@@ -130,6 +137,9 @@ Update an existing issue
| `teamId` | string | No | Team ID to assign issue to |
| `tags` | string | No | Comma-separated tag IDs |
| `customFields` | string | No | Custom fields as JSON object |
| `customerPortalVisible` | boolean | No | Whether issue is visible in customer portal |
| `requesterId` | string | No | Requester user ID |
| `accountId` | string | No | Account ID to associate with issue |
#### Output

View File

@@ -0,0 +1,449 @@
---
title: Shopify
description: Manage products, orders, customers, and inventory in your Shopify store
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="shopify"
color="#FFFFFF"
/>
{/* MANUAL-CONTENT-START:intro */}
[Shopify](https://www.shopify.com/) is a leading e-commerce platform designed to help merchants build, run, and grow their online stores. Shopify makes it easy to manage every aspect of your store, from products and inventory to orders and customers.
With Shopify in Sim, your agents can:
- **Create and manage products**: Add new products, update product details, and remove products from your store.
- **List and retrieve orders**: Get information about customer orders, including filtering and order management.
- **Manage customers**: Access and update customer details, or add new customers to your store.
- **Adjust inventory levels**: Programmatically change product stock levels to keep your inventory accurate.
Use Sim's Shopify integration to automate common store management workflows—such as syncing inventory, fulfilling orders, or managing listings—directly from your automations. Empower your agents to access, update, and organize all your store data using simple, programmatic tools.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Shopify into your workflow. Manage products, orders, customers, and inventory. Create, read, update, and delete products. List and manage orders. Handle customer data and adjust inventory levels.
## Tools
### `shopify_create_product`
Create a new product in your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `title` | string | Yes | Product title |
| `descriptionHtml` | string | No | Product description \(HTML\) |
| `vendor` | string | No | Product vendor/brand |
| `productType` | string | No | Product type/category |
| `tags` | array | No | Product tags |
| `status` | string | No | Product status \(ACTIVE, DRAFT, ARCHIVED\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `product` | object | The created product |
### `shopify_get_product`
Get a single product by ID from your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `productId` | string | Yes | Product ID \(gid://shopify/Product/123456789\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `product` | object | The product details |
### `shopify_list_products`
List products from your Shopify store with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `first` | number | No | Number of products to return \(default: 50, max: 250\) |
| `query` | string | No | Search query to filter products \(e.g., "title:shirt" or "vendor:Nike" or "status:active"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `products` | array | List of products |
| `pageInfo` | object | Pagination information |
### `shopify_update_product`
Update an existing product in your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `productId` | string | Yes | Product ID to update \(gid://shopify/Product/123456789\) |
| `title` | string | No | New product title |
| `descriptionHtml` | string | No | New product description \(HTML\) |
| `vendor` | string | No | New product vendor/brand |
| `productType` | string | No | New product type/category |
| `tags` | array | No | New product tags |
| `status` | string | No | New product status \(ACTIVE, DRAFT, ARCHIVED\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `product` | object | The updated product |
### `shopify_delete_product`
Delete a product from your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `productId` | string | Yes | Product ID to delete \(gid://shopify/Product/123456789\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deletedId` | string | The ID of the deleted product |
### `shopify_get_order`
Get a single order by ID from your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `orderId` | string | Yes | Order ID \(gid://shopify/Order/123456789\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `order` | object | The order details |
### `shopify_list_orders`
List orders from your Shopify store with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `first` | number | No | Number of orders to return \(default: 50, max: 250\) |
| `status` | string | No | Filter by order status \(open, closed, cancelled, any\) |
| `query` | string | No | Search query to filter orders \(e.g., "financial_status:paid" or "fulfillment_status:unfulfilled" or "email:customer@example.com"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `orders` | array | List of orders |
| `pageInfo` | object | Pagination information |
### `shopify_update_order`
Update an existing order in your Shopify store (note, tags, email)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `orderId` | string | Yes | Order ID to update \(gid://shopify/Order/123456789\) |
| `note` | string | No | New order note |
| `tags` | array | No | New order tags |
| `email` | string | No | New customer email for the order |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `order` | object | The updated order |
### `shopify_cancel_order`
Cancel an order in your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `orderId` | string | Yes | Order ID to cancel \(gid://shopify/Order/123456789\) |
| `reason` | string | Yes | Cancellation reason \(CUSTOMER, DECLINED, FRAUD, INVENTORY, STAFF, OTHER\) |
| `notifyCustomer` | boolean | No | Whether to notify the customer about the cancellation |
| `refund` | boolean | No | Whether to refund the order |
| `restock` | boolean | No | Whether to restock the inventory |
| `staffNote` | string | No | A note about the cancellation for staff reference |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `order` | object | The cancellation result |
### `shopify_create_customer`
Create a new customer in your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `email` | string | No | Customer email address |
| `firstName` | string | No | Customer first name |
| `lastName` | string | No | Customer last name |
| `phone` | string | No | Customer phone number |
| `note` | string | No | Note about the customer |
| `tags` | array | No | Customer tags |
| `addresses` | array | No | Customer addresses |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customer` | object | The created customer |
### `shopify_get_customer`
Get a single customer by ID from your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `customerId` | string | Yes | Customer ID \(gid://shopify/Customer/123456789\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customer` | object | The customer details |
### `shopify_list_customers`
List customers from your Shopify store with optional filtering
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `first` | number | No | Number of customers to return \(default: 50, max: 250\) |
| `query` | string | No | Search query to filter customers \(e.g., "first_name:John" or "last_name:Smith" or "email:*@gmail.com" or "tag:vip"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customers` | array | List of customers |
| `pageInfo` | object | Pagination information |
### `shopify_update_customer`
Update an existing customer in your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `customerId` | string | Yes | Customer ID to update \(gid://shopify/Customer/123456789\) |
| `email` | string | No | New customer email address |
| `firstName` | string | No | New customer first name |
| `lastName` | string | No | New customer last name |
| `phone` | string | No | New customer phone number |
| `note` | string | No | New note about the customer |
| `tags` | array | No | New customer tags |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customer` | object | The updated customer |
### `shopify_delete_customer`
Delete a customer from your Shopify store
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `customerId` | string | Yes | Customer ID to delete \(gid://shopify/Customer/123456789\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deletedId` | string | The ID of the deleted customer |
### `shopify_list_inventory_items`
List inventory items from your Shopify store. Use this to find inventory item IDs by SKU.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `first` | number | No | Number of inventory items to return \(default: 50, max: 250\) |
| `query` | string | No | Search query to filter inventory items \(e.g., "sku:ABC123"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `inventoryItems` | array | List of inventory items with their IDs, SKUs, and stock levels |
| `pageInfo` | object | Pagination information |
### `shopify_get_inventory_level`
Get inventory level for a product variant at a specific location
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `inventoryItemId` | string | Yes | Inventory item ID \(gid://shopify/InventoryItem/123456789\) |
| `locationId` | string | No | Location ID to filter by \(optional\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `inventoryLevel` | object | The inventory level details |
### `shopify_adjust_inventory`
Adjust inventory quantity for a product variant at a specific location
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `inventoryItemId` | string | Yes | Inventory item ID \(gid://shopify/InventoryItem/123456789\) |
| `locationId` | string | Yes | Location ID \(gid://shopify/Location/123456789\) |
| `delta` | number | Yes | Amount to adjust \(positive to increase, negative to decrease\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `inventoryLevel` | object | The inventory adjustment result |
### `shopify_list_locations`
List inventory locations from your Shopify store. Use this to find location IDs needed for inventory operations.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `first` | number | No | Number of locations to return \(default: 50, max: 250\) |
| `includeInactive` | boolean | No | Whether to include deactivated locations \(default: false\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `locations` | array | List of locations with their IDs, names, and addresses |
| `pageInfo` | object | Pagination information |
### `shopify_create_fulfillment`
Create a fulfillment to mark order items as shipped. Requires a fulfillment order ID (get this from the order details).
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `fulfillmentOrderId` | string | Yes | The fulfillment order ID \(e.g., gid://shopify/FulfillmentOrder/123456789\) |
| `trackingNumber` | string | No | Tracking number for the shipment |
| `trackingCompany` | string | No | Shipping carrier name \(e.g., UPS, FedEx, USPS, DHL\) |
| `trackingUrl` | string | No | URL to track the shipment |
| `notifyCustomer` | boolean | No | Whether to send a shipping confirmation email to the customer \(default: true\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `fulfillment` | object | The created fulfillment with tracking info and fulfilled items |
### `shopify_list_collections`
List product collections from your Shopify store. Filter by title, type (custom/smart), or handle.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `first` | number | No | Number of collections to return \(default: 50, max: 250\) |
| `query` | string | No | Search query to filter collections \(e.g., "title:Summer" or "collection_type:smart"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `collections` | array | List of collections with their IDs, titles, and product counts |
| `pageInfo` | object | Pagination information |
### `shopify_get_collection`
Get a specific collection by ID, including its products. Use this to retrieve products within a collection.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `shopDomain` | string | Yes | Your Shopify store domain \(e.g., mystore.myshopify.com\) |
| `collectionId` | string | Yes | The collection ID \(e.g., gid://shopify/Collection/123456789\) |
| `productsFirst` | number | No | Number of products to return from this collection \(default: 50, max: 250\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `collection` | object | The collection details including its products |
## Notes
- Category: `tools`
- Type: `shopify`

View File

@@ -0,0 +1,399 @@
---
title: SSH
description: Connect to remote servers via SSH
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="ssh"
color="#000000"
/>
{/* MANUAL-CONTENT-START:intro */}
[SSH (Secure Shell)](https://en.wikipedia.org/wiki/Secure_Shell) is a widely-used protocol for securely connecting to remote servers, allowing you to execute commands, transfer files, and manage systems over encrypted channels.
With SSH support in Sim, your agents can:
- **Execute remote commands**: Run shell commands on any SSH-accessible server
- **Upload and run scripts**: Easily transfer and execute multi-line scripts for advanced automation
- **Transfer files securely**: Upload and download files as part of your workflows (coming soon or via command)
- **Automate server management**: Perform updates, maintenance, monitoring, deployments, and configuration tasks programmatically
- **Use flexible authentication**: Connect with password or private key authentication, including support for encrypted keys
The following Sim SSH tools enable your agents to interact with servers as part of larger automations:
- `ssh_execute_command`: Run any single shell command remotely and capture output, status, and errors.
- `ssh_execute_script`: Upload and execute a full multi-line script on the remote system.
- (Additional tools coming soon, such as file transfer.)
By integrating SSH into your agent workflows, you can automate secure access, remote operations, and server orchestration—streamlining DevOps, IT automation, and custom remote management, all from within Sim.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Execute commands, transfer files, and manage remote servers via SSH. Supports password and private key authentication for secure server access.
## Tools
### `ssh_execute_command`
Execute a shell command on a remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `command` | string | Yes | Shell command to execute on the remote server |
| `workingDirectory` | string | No | Working directory for command execution |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `stdout` | string | Standard output from command |
| `stderr` | string | Standard error output |
| `exitCode` | number | Command exit code |
| `success` | boolean | Whether command succeeded \(exit code 0\) |
| `message` | string | Operation status message |
### `ssh_execute_script`
Upload and execute a multi-line script on a remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `script` | string | Yes | Script content to execute \(bash, python, etc.\) |
| `interpreter` | string | No | Script interpreter \(default: /bin/bash\) |
| `workingDirectory` | string | No | Working directory for script execution |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `stdout` | string | Standard output from script |
| `stderr` | string | Standard error output |
| `exitCode` | number | Script exit code |
| `success` | boolean | Whether script succeeded \(exit code 0\) |
| `scriptPath` | string | Temporary path where script was uploaded |
| `message` | string | Operation status message |
### `ssh_check_command_exists`
Check if a command/program exists on the remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `commandName` | string | Yes | Command name to check \(e.g., docker, git, python3\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `commandExists` | boolean | Whether the command exists |
| `commandPath` | string | Full path to the command \(if found\) |
| `version` | string | Command version output \(if applicable\) |
| `message` | string | Operation status message |
### `ssh_upload_file`
Upload a file to a remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `fileContent` | string | Yes | File content to upload \(base64 encoded for binary files\) |
| `fileName` | string | Yes | Name of the file being uploaded |
| `remotePath` | string | Yes | Destination path on the remote server |
| `permissions` | string | No | File permissions \(e.g., 0644\) |
| `overwrite` | boolean | No | Whether to overwrite existing files \(default: true\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `uploaded` | boolean | Whether the file was uploaded successfully |
| `remotePath` | string | Final path on the remote server |
| `size` | number | File size in bytes |
| `message` | string | Operation status message |
### `ssh_download_file`
Download a file from a remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `remotePath` | string | Yes | Path of the file on the remote server |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `downloaded` | boolean | Whether the file was downloaded successfully |
| `fileContent` | string | File content \(base64 encoded for binary files\) |
| `fileName` | string | Name of the downloaded file |
| `remotePath` | string | Source path on the remote server |
| `size` | number | File size in bytes |
| `message` | string | Operation status message |
### `ssh_list_directory`
List files and directories in a remote directory
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `path` | string | Yes | Remote directory path to list |
| `detailed` | boolean | No | Include file details \(size, permissions, modified date\) |
| `recursive` | boolean | No | List subdirectories recursively \(default: false\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `entries` | array | Array of file and directory entries |
### `ssh_check_file_exists`
Check if a file or directory exists on the remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `path` | string | Yes | Remote file or directory path to check |
| `type` | string | No | Expected type: file, directory, or any \(default: any\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `exists` | boolean | Whether the path exists |
| `type` | string | Type of path \(file, directory, symlink, not_found\) |
| `size` | number | File size if it is a file |
| `permissions` | string | File permissions \(e.g., 0755\) |
| `modified` | string | Last modified timestamp |
| `message` | string | Operation status message |
### `ssh_create_directory`
Create a directory on the remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `path` | string | Yes | Directory path to create |
| `recursive` | boolean | No | Create parent directories if they do not exist \(default: true\) |
| `permissions` | string | No | Directory permissions \(default: 0755\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `created` | boolean | Whether the directory was created successfully |
| `remotePath` | string | Created directory path |
| `alreadyExists` | boolean | Whether the directory already existed |
| `message` | string | Operation status message |
### `ssh_delete_file`
Delete a file or directory from the remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `path` | string | Yes | Path to delete |
| `recursive` | boolean | No | Recursively delete directories \(default: false\) |
| `force` | boolean | No | Force deletion without confirmation \(default: false\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the path was deleted successfully |
| `remotePath` | string | Deleted path |
| `message` | string | Operation status message |
### `ssh_move_rename`
Move or rename a file or directory on the remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `sourcePath` | string | Yes | Current path of the file or directory |
| `destinationPath` | string | Yes | New path for the file or directory |
| `overwrite` | boolean | No | Overwrite destination if it exists \(default: false\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `moved` | boolean | Whether the operation was successful |
| `sourcePath` | string | Original path |
| `destinationPath` | string | New path |
| `message` | string | Operation status message |
### `ssh_get_system_info`
Retrieve system information from the remote SSH server
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `hostname` | string | Server hostname |
| `os` | string | Operating system \(e.g., Linux, Darwin\) |
| `architecture` | string | CPU architecture \(e.g., x64, arm64\) |
| `uptime` | number | System uptime in seconds |
| `memory` | json | Memory information \(total, free, used\) |
| `diskSpace` | json | Disk space information \(total, free, used\) |
| `message` | string | Operation status message |
### `ssh_read_file_content`
Read the contents of a remote file
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `path` | string | Yes | Remote file path to read |
| `encoding` | string | No | File encoding \(default: utf-8\) |
| `maxSize` | number | No | Maximum file size to read in MB \(default: 10\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | File content as string |
| `size` | number | File size in bytes |
| `lines` | number | Number of lines in file |
| `remotePath` | string | Remote file path |
| `message` | string | Operation status message |
### `ssh_write_file_content`
Write or append content to a remote file
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `host` | string | Yes | SSH server hostname or IP address |
| `port` | number | Yes | SSH server port \(default: 22\) |
| `username` | string | Yes | SSH username |
| `password` | string | No | Password for authentication \(if not using private key\) |
| `privateKey` | string | No | Private key for authentication \(OpenSSH format\) |
| `passphrase` | string | No | Passphrase for encrypted private key |
| `path` | string | Yes | Remote file path to write to |
| `content` | string | Yes | Content to write to the file |
| `mode` | string | No | Write mode: overwrite, append, or create \(default: overwrite\) |
| `permissions` | string | No | File permissions \(e.g., 0644\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `written` | boolean | Whether the file was written successfully |
| `remotePath` | string | File path |
| `size` | number | Final file size in bytes |
| `message` | string | Operation status message |
## Notes
- Category: `tools`
- Type: `ssh`

View File

@@ -0,0 +1,571 @@
---
title: WordPress
description: Manage WordPress content
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="wordpress"
color="#21759B"
/>
{/* MANUAL-CONTENT-START:intro */}
[WordPress](https://wordpress.org/) is the worlds leading open-source content management system, making it easy to publish and manage websites, blogs, and all types of online content. With WordPress, you can create and update posts or pages, organize your content with categories and tags, manage media files, moderate comments, and handle user accounts—allowing you to run everything from personal blogs to complex business sites.
Sims integration with WordPress lets your agents automate essential website tasks. You can programmatically create new blog posts with specific titles, content, categories, tags, and featured images. Updating existing posts—such as changing their content, title, or publishing status—is straightforward. You can also publish or save content as drafts, manage static pages, work with media uploads, oversee comments, and assign content to relevant organizational taxonomies.
By connecting WordPress to your automations, Sim empowers your agents to streamline content publishing, editorial workflows, and everyday site management—helping you keep your website fresh, organized, and secure without manual effort.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate with WordPress to create, update, and manage posts, pages, media, comments, categories, tags, and users. Supports WordPress.com sites via OAuth and self-hosted WordPress sites using Application Passwords authentication.
## Tools
### `wordpress_create_post`
Create a new blog post in WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `title` | string | Yes | Post title |
| `content` | string | No | Post content \(HTML or plain text\) |
| `status` | string | No | Post status: publish, draft, pending, private, or future |
| `excerpt` | string | No | Post excerpt |
| `categories` | string | No | Comma-separated category IDs |
| `tags` | string | No | Comma-separated tag IDs |
| `featuredMedia` | number | No | Featured image media ID |
| `slug` | string | No | URL slug for the post |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `post` | object | The created post |
### `wordpress_update_post`
Update an existing blog post in WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `postId` | number | Yes | The ID of the post to update |
| `title` | string | No | Post title |
| `content` | string | No | Post content \(HTML or plain text\) |
| `status` | string | No | Post status: publish, draft, pending, private, or future |
| `excerpt` | string | No | Post excerpt |
| `categories` | string | No | Comma-separated category IDs |
| `tags` | string | No | Comma-separated tag IDs |
| `featuredMedia` | number | No | Featured image media ID |
| `slug` | string | No | URL slug for the post |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `post` | object | The updated post |
### `wordpress_delete_post`
Delete a blog post from WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `postId` | number | Yes | The ID of the post to delete |
| `force` | boolean | No | Bypass trash and force delete permanently |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the post was deleted |
| `post` | object | The deleted post |
### `wordpress_get_post`
Get a single blog post from WordPress.com by ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `postId` | number | Yes | The ID of the post to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `post` | object | The retrieved post |
### `wordpress_list_posts`
List blog posts from WordPress.com with optional filters
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `perPage` | number | No | Number of posts per page \(default: 10, max: 100\) |
| `page` | number | No | Page number for pagination |
| `status` | string | No | Post status filter: publish, draft, pending, private |
| `author` | number | No | Filter by author ID |
| `categories` | string | No | Comma-separated category IDs to filter by |
| `tags` | string | No | Comma-separated tag IDs to filter by |
| `search` | string | No | Search term to filter posts |
| `orderBy` | string | No | Order by field: date, id, title, slug, modified |
| `order` | string | No | Order direction: asc or desc |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `posts` | array | List of posts |
### `wordpress_create_page`
Create a new page in WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `title` | string | Yes | Page title |
| `content` | string | No | Page content \(HTML or plain text\) |
| `status` | string | No | Page status: publish, draft, pending, private |
| `excerpt` | string | No | Page excerpt |
| `parent` | number | No | Parent page ID for hierarchical pages |
| `menuOrder` | number | No | Order in page menu |
| `featuredMedia` | number | No | Featured image media ID |
| `slug` | string | No | URL slug for the page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `page` | object | The created page |
### `wordpress_update_page`
Update an existing page in WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `pageId` | number | Yes | The ID of the page to update |
| `title` | string | No | Page title |
| `content` | string | No | Page content \(HTML or plain text\) |
| `status` | string | No | Page status: publish, draft, pending, private |
| `excerpt` | string | No | Page excerpt |
| `parent` | number | No | Parent page ID for hierarchical pages |
| `menuOrder` | number | No | Order in page menu |
| `featuredMedia` | number | No | Featured image media ID |
| `slug` | string | No | URL slug for the page |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `page` | object | The updated page |
### `wordpress_delete_page`
Delete a page from WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `pageId` | number | Yes | The ID of the page to delete |
| `force` | boolean | No | Bypass trash and force delete permanently |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the page was deleted |
| `page` | object | The deleted page |
### `wordpress_get_page`
Get a single page from WordPress.com by ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `pageId` | number | Yes | The ID of the page to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `page` | object | The retrieved page |
### `wordpress_list_pages`
List pages from WordPress.com with optional filters
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `perPage` | number | No | Number of pages per request \(default: 10, max: 100\) |
| `page` | number | No | Page number for pagination |
| `status` | string | No | Page status filter: publish, draft, pending, private |
| `parent` | number | No | Filter by parent page ID |
| `search` | string | No | Search term to filter pages |
| `orderBy` | string | No | Order by field: date, id, title, slug, modified, menu_order |
| `order` | string | No | Order direction: asc or desc |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pages` | array | List of pages |
### `wordpress_upload_media`
Upload a media file (image, video, document) to WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `file` | string | Yes | Base64 encoded file data or URL to fetch file from |
| `filename` | string | Yes | Filename with extension \(e.g., image.jpg\) |
| `title` | string | No | Media title |
| `caption` | string | No | Media caption |
| `altText` | string | No | Alternative text for accessibility |
| `description` | string | No | Media description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `media` | object | The uploaded media item |
### `wordpress_get_media`
Get a single media item from WordPress.com by ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `mediaId` | number | Yes | The ID of the media item to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `media` | object | The retrieved media item |
### `wordpress_list_media`
List media items from the WordPress.com media library
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `perPage` | number | No | Number of media items per request \(default: 10, max: 100\) |
| `page` | number | No | Page number for pagination |
| `search` | string | No | Search term to filter media |
| `mediaType` | string | No | Filter by media type: image, video, audio, application |
| `mimeType` | string | No | Filter by specific MIME type \(e.g., image/jpeg\) |
| `orderBy` | string | No | Order by field: date, id, title, slug |
| `order` | string | No | Order direction: asc or desc |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `media` | array | List of media items |
### `wordpress_delete_media`
Delete a media item from WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `mediaId` | number | Yes | The ID of the media item to delete |
| `force` | boolean | No | Force delete \(media has no trash, so deletion is permanent\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the media was deleted |
| `media` | object | The deleted media item |
### `wordpress_create_comment`
Create a new comment on a WordPress.com post
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `postId` | number | Yes | The ID of the post to comment on |
| `content` | string | Yes | Comment content |
| `parent` | number | No | Parent comment ID for replies |
| `authorName` | string | No | Comment author display name |
| `authorEmail` | string | No | Comment author email |
| `authorUrl` | string | No | Comment author URL |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `comment` | object | The created comment |
### `wordpress_list_comments`
List comments from WordPress.com with optional filters
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `perPage` | number | No | Number of comments per request \(default: 10, max: 100\) |
| `page` | number | No | Page number for pagination |
| `postId` | number | No | Filter by post ID |
| `status` | string | No | Filter by comment status: approved, hold, spam, trash |
| `search` | string | No | Search term to filter comments |
| `orderBy` | string | No | Order by field: date, id, parent |
| `order` | string | No | Order direction: asc or desc |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `comments` | array | List of comments |
### `wordpress_update_comment`
Update a comment in WordPress.com (content or status)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `commentId` | number | Yes | The ID of the comment to update |
| `content` | string | No | Updated comment content |
| `status` | string | No | Comment status: approved, hold, spam, trash |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `comment` | object | The updated comment |
### `wordpress_delete_comment`
Delete a comment from WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `commentId` | number | Yes | The ID of the comment to delete |
| `force` | boolean | No | Bypass trash and force delete permanently |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `deleted` | boolean | Whether the comment was deleted |
| `comment` | object | The deleted comment |
### `wordpress_create_category`
Create a new category in WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `name` | string | Yes | Category name |
| `description` | string | No | Category description |
| `parent` | number | No | Parent category ID for hierarchical categories |
| `slug` | string | No | URL slug for the category |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `category` | object | The created category |
### `wordpress_list_categories`
List categories from WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `perPage` | number | No | Number of categories per request \(default: 10, max: 100\) |
| `page` | number | No | Page number for pagination |
| `search` | string | No | Search term to filter categories |
| `order` | string | No | Order direction: asc or desc |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `categories` | array | List of categories |
### `wordpress_create_tag`
Create a new tag in WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `name` | string | Yes | Tag name |
| `description` | string | No | Tag description |
| `slug` | string | No | URL slug for the tag |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tag` | object | The created tag |
### `wordpress_list_tags`
List tags from WordPress.com
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `perPage` | number | No | Number of tags per request \(default: 10, max: 100\) |
| `page` | number | No | Page number for pagination |
| `search` | string | No | Search term to filter tags |
| `order` | string | No | Order direction: asc or desc |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tags` | array | List of tags |
### `wordpress_get_current_user`
Get information about the currently authenticated WordPress.com user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `user` | object | The current user |
### `wordpress_list_users`
List users from WordPress.com (requires admin privileges)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `perPage` | number | No | Number of users per request \(default: 10, max: 100\) |
| `page` | number | No | Page number for pagination |
| `search` | string | No | Search term to filter users |
| `roles` | string | No | Comma-separated role names to filter by |
| `order` | string | No | Order direction: asc or desc |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `users` | array | List of users |
### `wordpress_get_user`
Get a specific user from WordPress.com by ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `userId` | number | Yes | The ID of the user to retrieve |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `user` | object | The retrieved user |
### `wordpress_search_content`
Search across all content types in WordPress.com (posts, pages, media)
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | WordPress.com site ID or domain \(e.g., 12345678 or mysite.wordpress.com\) |
| `query` | string | Yes | Search query |
| `perPage` | number | No | Number of results per request \(default: 10, max: 100\) |
| `page` | number | No | Page number for pagination |
| `type` | string | No | Filter by content type: post, page, attachment |
| `subtype` | string | No | Filter by post type slug \(e.g., post, page\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `results` | array | Search results |
## Notes
- Category: `tools`
- Type: `wordpress`

View File

@@ -117,6 +117,9 @@ Create a new ticket in Zendesk with support for custom fields
| `type` | string | No | Type \(problem, incident, question, task\) |
| `tags` | string | No | Comma-separated tags |
| `assigneeId` | string | No | Assignee user ID |
| `groupId` | string | No | Group ID |
| `requesterId` | string | No | Requester user ID |
| `customFields` | string | No | Custom fields as JSON object \(e.g., \{"field_id": "value"\}\) |
#### Output

View File

@@ -0,0 +1,255 @@
---
title: Zoom
description: Create and manage Zoom meetings and recordings
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="zoom"
color="#2D8CFF"
/>
{/* MANUAL-CONTENT-START:intro */}
[Zoom](https://zoom.us/) is a leading cloud-based communications platform for video meetings, webinars, and online collaboration. It allows users and organizations to easily schedule, host, and manage meetings, providing tools for screen sharing, chat, recordings, and more.
With Zoom, you can:
- **Schedule and manage meetings**: Create instant or scheduled meetings, including recurring events
- **Configure meeting options**: Set meeting passwords, enable waiting rooms, and control participant video/audio
- **Send invitations and share details**: Retrieve meeting invitations and information for easy sharing
- **Get and update meeting data**: Access meeting details, modify existing meetings, and manage settings programmatically
In Sim, the Zoom integration empowers your agents to automate scheduling and meeting management. Use tool actions to:
- Programmatically create new meetings with custom settings
- List all meetings for a specific user (or yourself)
- Retrieve details or invitations for any meeting
- Update or delete existing meetings directly from your automations
These capabilities let you streamline remote collaboration, automate recurring video sessions, and manage your organization's Zoom environment all as part of your workflows.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrate Zoom into workflows. Create, list, update, and delete Zoom meetings. Get meeting details, invitations, recordings, and participants. Manage cloud recordings programmatically.
## Tools
### `zoom_create_meeting`
Create a new Zoom meeting
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The user ID or email address. Use "me" for the authenticated user. |
| `topic` | string | Yes | Meeting topic |
| `type` | number | No | Meeting type: 1=instant, 2=scheduled, 3=recurring no fixed time, 8=recurring fixed time |
| `startTime` | string | No | Meeting start time in ISO 8601 format \(e.g., 2025-06-03T10:00:00Z\) |
| `duration` | number | No | Meeting duration in minutes |
| `timezone` | string | No | Timezone for the meeting \(e.g., America/Los_Angeles\) |
| `password` | string | No | Meeting password |
| `agenda` | string | No | Meeting agenda |
| `hostVideo` | boolean | No | Start with host video on |
| `participantVideo` | boolean | No | Start with participant video on |
| `joinBeforeHost` | boolean | No | Allow participants to join before host |
| `muteUponEntry` | boolean | No | Mute participants upon entry |
| `waitingRoom` | boolean | No | Enable waiting room |
| `autoRecording` | string | No | Auto recording setting: local, cloud, or none |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `meeting` | object | The created meeting with all its properties |
### `zoom_list_meetings`
List all meetings for a Zoom user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The user ID or email address. Use "me" for the authenticated user. |
| `type` | string | No | Meeting type filter: scheduled, live, upcoming, upcoming_meetings, or previous_meetings |
| `pageSize` | number | No | Number of records per page \(max 300\) |
| `nextPageToken` | string | No | Token for pagination to get next page of results |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `meetings` | array | List of meetings |
| `pageInfo` | object | Pagination information |
### `zoom_get_meeting`
Get details of a specific Zoom meeting
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `meetingId` | string | Yes | The meeting ID |
| `occurrenceId` | string | No | Occurrence ID for recurring meetings |
| `showPreviousOccurrences` | boolean | No | Show previous occurrences for recurring meetings |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `meeting` | object | The meeting details |
### `zoom_update_meeting`
Update an existing Zoom meeting
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `meetingId` | string | Yes | The meeting ID to update |
| `topic` | string | No | Meeting topic |
| `type` | number | No | Meeting type: 1=instant, 2=scheduled, 3=recurring no fixed time, 8=recurring fixed time |
| `startTime` | string | No | Meeting start time in ISO 8601 format \(e.g., 2025-06-03T10:00:00Z\) |
| `duration` | number | No | Meeting duration in minutes |
| `timezone` | string | No | Timezone for the meeting \(e.g., America/Los_Angeles\) |
| `password` | string | No | Meeting password |
| `agenda` | string | No | Meeting agenda |
| `hostVideo` | boolean | No | Start with host video on |
| `participantVideo` | boolean | No | Start with participant video on |
| `joinBeforeHost` | boolean | No | Allow participants to join before host |
| `muteUponEntry` | boolean | No | Mute participants upon entry |
| `waitingRoom` | boolean | No | Enable waiting room |
| `autoRecording` | string | No | Auto recording setting: local, cloud, or none |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the meeting was updated successfully |
### `zoom_delete_meeting`
Delete or cancel a Zoom meeting
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `meetingId` | string | Yes | The meeting ID to delete |
| `occurrenceId` | string | No | Occurrence ID for deleting a specific occurrence of a recurring meeting |
| `scheduleForReminder` | boolean | No | Send cancellation reminder email to registrants |
| `cancelMeetingReminder` | boolean | No | Send cancellation email to registrants and alternative hosts |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the meeting was deleted successfully |
### `zoom_get_meeting_invitation`
Get the meeting invitation text for a Zoom meeting
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `meetingId` | string | Yes | The meeting ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `invitation` | string | The meeting invitation text |
### `zoom_list_recordings`
List all cloud recordings for a Zoom user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `userId` | string | Yes | The user ID or email address. Use "me" for the authenticated user. |
| `from` | string | No | Start date in yyyy-mm-dd format \(within last 6 months\) |
| `to` | string | No | End date in yyyy-mm-dd format |
| `pageSize` | number | No | Number of records per page \(max 300\) |
| `nextPageToken` | string | No | Token for pagination to get next page of results |
| `trash` | boolean | No | Set to true to list recordings from trash |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `recordings` | array | List of recordings |
| `pageInfo` | object | Pagination information |
### `zoom_get_meeting_recordings`
Get all recordings for a specific Zoom meeting
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `meetingId` | string | Yes | The meeting ID or meeting UUID |
| `includeFolderItems` | boolean | No | Include items within a folder |
| `ttl` | number | No | Time to live for download URLs in seconds \(max 604800\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `recording` | object | The meeting recording with all files |
### `zoom_delete_recording`
Delete cloud recordings for a Zoom meeting
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `meetingId` | string | Yes | The meeting ID or meeting UUID |
| `recordingId` | string | No | Specific recording file ID to delete. If not provided, deletes all recordings. |
| `action` | string | No | Delete action: "trash" \(move to trash\) or "delete" \(permanently delete\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the recording was deleted successfully |
### `zoom_list_past_participants`
List participants from a past Zoom meeting
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `meetingId` | string | Yes | The past meeting ID or UUID |
| `pageSize` | number | No | Number of records per page \(max 300\) |
| `nextPageToken` | string | No | Token for pagination to get next page of results |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `participants` | array | List of meeting participants |
| `pageInfo` | object | Pagination information |
## Notes
- Category: `tools`
- Type: `zoom`

View File

@@ -0,0 +1,167 @@
import crypto from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { env } from '@/lib/core/config/env'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('ShopifyCallback')
export const dynamic = 'force-dynamic'
const SHOP_DOMAIN_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9-]*\.myshopify\.com$/
/**
* Validates the HMAC signature from Shopify to ensure the request is authentic
* @see https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/offline-access-tokens
*/
function validateHmac(searchParams: URLSearchParams, clientSecret: string): boolean {
const hmac = searchParams.get('hmac')
if (!hmac) {
return false
}
const params: Record<string, string> = {}
searchParams.forEach((value, key) => {
if (key !== 'hmac') {
params[key] = value
}
})
const message = Object.keys(params)
.sort()
.map((key) => `${key}=${params[key]}`)
.join('&')
const generatedHmac = crypto.createHmac('sha256', clientSecret).update(message).digest('hex')
try {
return crypto.timingSafeEqual(Buffer.from(hmac, 'hex'), Buffer.from(generatedHmac, 'hex'))
} catch {
return false
}
}
export async function GET(request: NextRequest) {
const baseUrl = getBaseUrl()
try {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.redirect(`${baseUrl}/workspace?error=unauthorized`)
}
const { searchParams } = request.nextUrl
const code = searchParams.get('code')
const state = searchParams.get('state')
const shop = searchParams.get('shop')
const storedState = request.cookies.get('shopify_oauth_state')?.value
const storedShop = request.cookies.get('shopify_shop_domain')?.value
const clientId = env.SHOPIFY_CLIENT_ID
const clientSecret = env.SHOPIFY_CLIENT_SECRET
if (!clientId || !clientSecret) {
logger.error('Shopify credentials not configured')
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_config_error`)
}
if (!validateHmac(searchParams, clientSecret)) {
logger.error('HMAC validation failed in Shopify OAuth callback')
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_hmac_invalid`)
}
if (!state || state !== storedState) {
logger.error('State mismatch in Shopify OAuth callback')
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_state_mismatch`)
}
if (!code) {
logger.error('No code received from Shopify')
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_no_code`)
}
const shopDomain = shop || storedShop
if (!shopDomain) {
logger.error('No shop domain available')
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_no_shop`)
}
if (!SHOP_DOMAIN_REGEX.test(shopDomain)) {
logger.error('Invalid shop domain format:', { shopDomain })
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_invalid_shop`)
}
const tokenResponse = await fetch(`https://${shopDomain}/admin/oauth/access_token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
client_id: clientId,
client_secret: clientSecret,
code: code,
}),
})
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text()
logger.error('Failed to exchange code for token:', {
status: tokenResponse.status,
body: errorText,
})
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_token_error`)
}
const tokenData = await tokenResponse.json()
const accessToken = tokenData.access_token
const scope = tokenData.scope
logger.info('Shopify token exchange successful:', {
hasAccessToken: !!accessToken,
scope: scope,
})
if (!accessToken) {
logger.error('No access token in response')
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_no_token`)
}
const storeUrl = new URL(`${baseUrl}/api/auth/oauth2/shopify/store`)
const response = NextResponse.redirect(storeUrl)
response.cookies.set('shopify_pending_token', accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60,
path: '/',
})
response.cookies.set('shopify_pending_shop', shopDomain, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60,
path: '/',
})
response.cookies.set('shopify_pending_scope', scope || '', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60,
path: '/',
})
response.cookies.delete('shopify_oauth_state')
response.cookies.delete('shopify_shop_domain')
return response
} catch (error) {
logger.error('Error in Shopify OAuth callback:', error)
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_callback_error`)
}
}

View File

@@ -0,0 +1,96 @@
import { db } from '@sim/db'
import { account } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('ShopifyStore')
export const dynamic = 'force-dynamic'
export async function GET(request: NextRequest) {
const baseUrl = getBaseUrl()
try {
const session = await getSession()
if (!session?.user?.id) {
logger.warn('Unauthorized attempt to store Shopify token')
return NextResponse.redirect(`${baseUrl}/workspace?error=unauthorized`)
}
const accessToken = request.cookies.get('shopify_pending_token')?.value
const shopDomain = request.cookies.get('shopify_pending_shop')?.value
const scope = request.cookies.get('shopify_pending_scope')?.value
if (!accessToken || !shopDomain) {
logger.error('Missing token or shop domain in cookies')
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_missing_data`)
}
const shopResponse = await fetch(`https://${shopDomain}/admin/api/2024-10/shop.json`, {
headers: {
'X-Shopify-Access-Token': accessToken,
'Content-Type': 'application/json',
},
})
if (!shopResponse.ok) {
const errorText = await shopResponse.text()
logger.error('Invalid Shopify token', {
status: shopResponse.status,
error: errorText,
})
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_invalid_token`)
}
const shopData = await shopResponse.json()
const shopInfo = shopData.shop
const existing = await db.query.account.findFirst({
where: and(eq(account.userId, session.user.id), eq(account.providerId, 'shopify')),
})
const now = new Date()
const accountData = {
accessToken: accessToken,
accountId: shopInfo.id?.toString() || shopDomain,
scope: scope || '',
updatedAt: now,
idToken: shopDomain,
}
if (existing) {
await db.update(account).set(accountData).where(eq(account.id, existing.id))
logger.info('Updated existing Shopify account', { accountId: existing.id })
} else {
await db.insert(account).values({
id: `shopify_${session.user.id}_${Date.now()}`,
userId: session.user.id,
providerId: 'shopify',
...accountData,
createdAt: now,
})
logger.info('Created new Shopify account for user', { userId: session.user.id })
}
const returnUrl = request.cookies.get('shopify_return_url')?.value
const redirectUrl = returnUrl || `${baseUrl}/workspace`
const finalUrl = new URL(redirectUrl)
finalUrl.searchParams.set('shopify_connected', 'true')
const response = NextResponse.redirect(finalUrl.toString())
response.cookies.delete('shopify_pending_token')
response.cookies.delete('shopify_pending_shop')
response.cookies.delete('shopify_pending_scope')
response.cookies.delete('shopify_return_url')
return response
} catch (error) {
logger.error('Error storing Shopify token:', error)
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_store_error`)
}
}

View File

@@ -0,0 +1,215 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { env } from '@/lib/core/config/env'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('ShopifyAuthorize')
export const dynamic = 'force-dynamic'
const SHOPIFY_SCOPES = [
'write_products',
'write_orders',
'write_customers',
'write_inventory',
'read_locations',
'write_merchant_managed_fulfillment_orders',
].join(',')
export async function GET(request: NextRequest) {
try {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const clientId = env.SHOPIFY_CLIENT_ID
if (!clientId) {
logger.error('SHOPIFY_CLIENT_ID not configured')
return NextResponse.json({ error: 'Shopify client ID not configured' }, { status: 500 })
}
const shopDomain = request.nextUrl.searchParams.get('shop')
const returnUrl = request.nextUrl.searchParams.get('returnUrl')
if (!shopDomain) {
const returnUrlParam = returnUrl ? encodeURIComponent(returnUrl) : ''
return new NextResponse(
`<!DOCTYPE html>
<html>
<head>
<title>Connect Shopify Store</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background: linear-gradient(135deg, #96BF48 0%, #5C8A23 100%);
}
.container {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
text-align: center;
max-width: 400px;
width: 90%;
}
h2 {
color: #111827;
margin: 0 0 0.5rem 0;
}
p {
color: #6b7280;
margin: 0 0 1.5rem 0;
}
input {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 1rem;
margin-bottom: 1rem;
box-sizing: border-box;
}
input:focus {
outline: none;
border-color: #96BF48;
box-shadow: 0 0 0 3px rgba(150, 191, 72, 0.2);
}
button {
width: 100%;
padding: 0.75rem;
background: #96BF48;
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
font-weight: 500;
}
button:hover {
background: #7FA93D;
}
.help {
font-size: 0.875rem;
color: #9ca3af;
margin-top: 1rem;
}
</style>
</head>
<body>
<div class="container">
<h2>Connect Your Shopify Store</h2>
<p>Enter your Shopify store domain to continue</p>
<form onsubmit="handleSubmit(event)">
<input
type="text"
id="shop"
placeholder="mystore.myshopify.com"
required
pattern="[a-zA-Z0-9-]+\\.myshopify\\.com"
/>
<button type="submit">Connect Store</button>
</form>
<p class="help">Your store domain looks like: yourstore.myshopify.com</p>
</div>
<script>
const returnUrl = '${returnUrlParam}';
function handleSubmit(e) {
e.preventDefault();
let shop = document.getElementById('shop').value.trim().toLowerCase();
// Clean up the shop domain
shop = shop.replace('https://', '').replace('http://', '');
if (!shop.endsWith('.myshopify.com')) {
shop = shop.replace('.myshopify.com', '') + '.myshopify.com';
}
let url = window.location.pathname + '?shop=' + encodeURIComponent(shop);
if (returnUrl) {
url += '&returnUrl=' + returnUrl;
}
window.location.href = url;
}
</script>
</body>
</html>`,
{
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-store, no-cache, must-revalidate',
},
}
)
}
let cleanShop = shopDomain.toLowerCase().trim()
cleanShop = cleanShop.replace('https://', '').replace('http://', '')
if (!cleanShop.endsWith('.myshopify.com')) {
cleanShop = `${cleanShop.replace('.myshopify.com', '')}.myshopify.com`
}
const baseUrl = getBaseUrl()
const redirectUri = `${baseUrl}/api/auth/oauth2/callback/shopify`
const state = crypto.randomUUID()
const oauthUrl =
`https://${cleanShop}/admin/oauth/authorize?` +
new URLSearchParams({
client_id: clientId,
scope: SHOPIFY_SCOPES,
redirect_uri: redirectUri,
state: state,
}).toString()
logger.info('Initiating Shopify OAuth:', {
shop: cleanShop,
requestedScopes: SHOPIFY_SCOPES,
redirectUri,
returnUrl: returnUrl || 'not specified',
})
const response = NextResponse.redirect(oauthUrl)
response.cookies.set('shopify_oauth_state', state, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 10,
path: '/',
})
response.cookies.set('shopify_shop_domain', cleanShop, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 10,
path: '/',
})
if (returnUrl) {
response.cookies.set('shopify_return_url', returnUrl, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 10,
path: '/',
})
}
return response
} catch (error) {
logger.error('Error initiating Shopify authorization:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}

View File

@@ -0,0 +1,104 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHCheckCommandExistsAPI')
const CheckCommandExistsSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
commandName: z.string().min(1, 'Command name is required'),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = CheckCommandExistsSchema.parse(body)
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(
`[${requestId}] Checking if command '${params.commandName}' exists on ${params.host}:${params.port}`
)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const escapedCommand = escapeShellArg(params.commandName)
const result = await executeSSHCommand(
client,
`command -v '${escapedCommand}' 2>/dev/null || which '${escapedCommand}' 2>/dev/null`
)
const exists = result.exitCode === 0 && result.stdout.trim().length > 0
const path = exists ? result.stdout.trim() : undefined
let version: string | undefined
if (exists) {
try {
const versionResult = await executeSSHCommand(
client,
`'${escapedCommand}' --version 2>&1 | head -1 || '${escapedCommand}' -v 2>&1 | head -1`
)
if (versionResult.exitCode === 0 && versionResult.stdout.trim()) {
version = versionResult.stdout.trim()
}
} catch {
// Version check failed, that's okay
}
}
logger.info(
`[${requestId}] Command '${params.commandName}' ${exists ? 'exists' : 'does not exist'}`
)
return NextResponse.json({
exists,
path,
version,
message: exists
? `Command '${params.commandName}' found at ${path}`
: `Command '${params.commandName}' not found`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH check command exists failed:`, error)
return NextResponse.json(
{ error: `SSH check command exists failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,134 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper, Stats } from 'ssh2'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import {
createSSHConnection,
getFileType,
parsePermissions,
sanitizePath,
} from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHCheckFileExistsAPI')
const CheckFileExistsSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
path: z.string().min(1, 'Path is required'),
type: z.enum(['file', 'directory', 'any']).default('any'),
})
function getSFTP(client: Client): Promise<SFTPWrapper> {
return new Promise((resolve, reject) => {
client.sftp((err, sftp) => {
if (err) {
reject(err)
} else {
resolve(sftp)
}
})
})
}
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = CheckFileExistsSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(
`[${requestId}] Checking if path exists: ${params.path} on ${params.host}:${params.port}`
)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const sftp = await getSFTP(client)
const filePath = sanitizePath(params.path)
const stats = await new Promise<Stats | null>((resolve) => {
sftp.stat(filePath, (err, stats) => {
if (err) {
resolve(null)
} else {
resolve(stats)
}
})
})
if (!stats) {
logger.info(`[${requestId}] Path does not exist: ${filePath}`)
return NextResponse.json({
exists: false,
type: 'not_found',
message: `Path does not exist: ${filePath}`,
})
}
const fileType = getFileType(stats)
// Check if the type matches the expected type
if (params.type !== 'any' && fileType !== params.type) {
logger.info(`[${requestId}] Path exists but is not a ${params.type}: ${filePath}`)
return NextResponse.json({
exists: false,
type: fileType,
size: stats.size,
permissions: parsePermissions(stats.mode),
modified: new Date((stats.mtime || 0) * 1000).toISOString(),
message: `Path exists but is a ${fileType}, not a ${params.type}`,
})
}
logger.info(`[${requestId}] Path exists: ${filePath} (${fileType})`)
return NextResponse.json({
exists: true,
type: fileType,
size: stats.size,
permissions: parsePermissions(stats.mode),
modified: new Date((stats.mtime || 0) * 1000).toISOString(),
message: `Path exists: ${filePath} (${fileType})`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH check file exists failed:`, error)
return NextResponse.json(
{ error: `SSH check file exists failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,110 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import {
createSSHConnection,
escapeShellArg,
executeSSHCommand,
sanitizePath,
} from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHCreateDirectoryAPI')
const CreateDirectorySchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
path: z.string().min(1, 'Path is required'),
recursive: z.boolean().default(true),
permissions: z.string().default('0755'),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = CreateDirectorySchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(`[${requestId}] Creating directory ${params.path} on ${params.host}:${params.port}`)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const dirPath = sanitizePath(params.path)
const escapedPath = escapeShellArg(dirPath)
// Check if directory already exists
const checkResult = await executeSSHCommand(
client,
`test -d '${escapedPath}' && echo "exists"`
)
const alreadyExists = checkResult.stdout.trim() === 'exists'
if (alreadyExists) {
logger.info(`[${requestId}] Directory already exists: ${dirPath}`)
return NextResponse.json({
created: false,
path: dirPath,
alreadyExists: true,
message: `Directory already exists: ${dirPath}`,
})
}
// Create directory
const mkdirFlag = params.recursive ? '-p' : ''
const command = `mkdir ${mkdirFlag} -m ${params.permissions} '${escapedPath}'`
const result = await executeSSHCommand(client, command)
if (result.exitCode !== 0) {
throw new Error(result.stderr || 'Failed to create directory')
}
logger.info(`[${requestId}] Directory created successfully: ${dirPath}`)
return NextResponse.json({
created: true,
path: dirPath,
alreadyExists: false,
message: `Directory created successfully: ${dirPath}`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH create directory failed:`, error)
return NextResponse.json(
{ error: `SSH create directory failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,103 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import {
createSSHConnection,
escapeShellArg,
executeSSHCommand,
sanitizePath,
} from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHDeleteFileAPI')
const DeleteFileSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
path: z.string().min(1, 'Path is required'),
recursive: z.boolean().default(false),
force: z.boolean().default(false),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = DeleteFileSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(`[${requestId}] Deleting ${params.path} on ${params.host}:${params.port}`)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const filePath = sanitizePath(params.path)
const escapedPath = escapeShellArg(filePath)
// Check if path exists
const checkResult = await executeSSHCommand(
client,
`test -e '${escapedPath}' && echo "exists"`
)
if (checkResult.stdout.trim() !== 'exists') {
return NextResponse.json({ error: `Path does not exist: ${filePath}` }, { status: 404 })
}
// Build delete command
let command: string
if (params.recursive) {
command = params.force ? `rm -rf '${escapedPath}'` : `rm -r '${escapedPath}'`
} else {
command = params.force ? `rm -f '${escapedPath}'` : `rm '${escapedPath}'`
}
const result = await executeSSHCommand(client, command)
if (result.exitCode !== 0) {
throw new Error(result.stderr || 'Failed to delete path')
}
logger.info(`[${requestId}] Path deleted successfully: ${filePath}`)
return NextResponse.json({
deleted: true,
path: filePath,
message: `Successfully deleted: ${filePath}`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH delete file failed:`, error)
return NextResponse.json({ error: `SSH delete file failed: ${errorMessage}` }, { status: 500 })
}
}

View File

@@ -0,0 +1,127 @@
import { randomUUID } from 'crypto'
import path from 'path'
import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper } from 'ssh2'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHDownloadFileAPI')
const DownloadFileSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
remotePath: z.string().min(1, 'Remote path is required'),
})
function getSFTP(client: Client): Promise<SFTPWrapper> {
return new Promise((resolve, reject) => {
client.sftp((err, sftp) => {
if (err) {
reject(err)
} else {
resolve(sftp)
}
})
})
}
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = DownloadFileSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(
`[${requestId}] Downloading file from ${params.host}:${params.port}${params.remotePath}`
)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const sftp = await getSFTP(client)
const remotePath = sanitizePath(params.remotePath)
// Check if file exists
const stats = await new Promise<{ size: number }>((resolve, reject) => {
sftp.stat(remotePath, (err, stats) => {
if (err) {
reject(new Error(`File not found: ${remotePath}`))
} else {
resolve(stats)
}
})
})
// Read file content
const content = await new Promise<Buffer>((resolve, reject) => {
const chunks: Buffer[] = []
const readStream = sftp.createReadStream(remotePath)
readStream.on('data', (chunk: Buffer) => {
chunks.push(chunk)
})
readStream.on('end', () => {
resolve(Buffer.concat(chunks))
})
readStream.on('error', reject)
})
const fileName = path.basename(remotePath)
// Encode content as base64 for binary safety
const base64Content = content.toString('base64')
logger.info(`[${requestId}] File downloaded successfully from ${remotePath}`)
return NextResponse.json({
downloaded: true,
content: base64Content,
fileName: fileName,
remotePath: remotePath,
size: stats.size,
message: `File downloaded successfully from ${remotePath}`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH file download failed:`, error)
return NextResponse.json(
{ error: `SSH file download failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,84 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createSSHConnection, executeSSHCommand, sanitizeCommand } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHExecuteCommandAPI')
const ExecuteCommandSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
command: z.string().min(1, 'Command is required'),
workingDirectory: z.string().nullish(),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = ExecuteCommandSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(`[${requestId}] Executing SSH command on ${params.host}:${params.port}`)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
// Build command with optional working directory
let command = sanitizeCommand(params.command)
if (params.workingDirectory) {
command = `cd "${params.workingDirectory}" && ${command}`
}
const result = await executeSSHCommand(client, command)
logger.info(`[${requestId}] Command executed successfully with exit code ${result.exitCode}`)
return NextResponse.json({
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode,
success: result.exitCode === 0,
message: `Command executed with exit code ${result.exitCode}`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH command execution failed:`, error)
return NextResponse.json(
{ error: `SSH command execution failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,104 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHExecuteScriptAPI')
const ExecuteScriptSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
script: z.string().min(1, 'Script content is required'),
interpreter: z.string().default('/bin/bash'),
workingDirectory: z.string().nullish(),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = ExecuteScriptSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(`[${requestId}] Executing SSH script on ${params.host}:${params.port}`)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
// Create a temporary script file, execute it, and clean up
const scriptPath = `/tmp/sim_script_${requestId}.sh`
const escapedScriptPath = escapeShellArg(scriptPath)
const escapedInterpreter = escapeShellArg(params.interpreter)
// Build the command to create, execute, and clean up the script
// Note: heredoc with quoted delimiter ('SIMEOF') prevents variable expansion
let command = `cat > '${escapedScriptPath}' << 'SIMEOF'
${params.script}
SIMEOF
chmod +x '${escapedScriptPath}'`
if (params.workingDirectory) {
const escapedWorkDir = escapeShellArg(params.workingDirectory)
command += `
cd '${escapedWorkDir}'`
}
command += `
'${escapedInterpreter}' '${escapedScriptPath}'
exit_code=$?
rm -f '${escapedScriptPath}'
exit $exit_code`
const result = await executeSSHCommand(client, command)
logger.info(`[${requestId}] Script executed successfully with exit code ${result.exitCode}`)
return NextResponse.json({
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode,
success: result.exitCode === 0,
scriptPath: scriptPath,
message: `Script executed with exit code ${result.exitCode}`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH script execution failed:`, error)
return NextResponse.json(
{ error: `SSH script execution failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,125 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createSSHConnection, executeSSHCommand } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHGetSystemInfoAPI')
const GetSystemInfoSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = GetSystemInfoSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(`[${requestId}] Getting system info from ${params.host}:${params.port}`)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
// Get hostname
const hostnameResult = await executeSSHCommand(client, 'hostname')
const hostname = hostnameResult.stdout.trim()
// Get OS info
const osResult = await executeSSHCommand(client, 'uname -s')
const os = osResult.stdout.trim()
// Get architecture
const archResult = await executeSSHCommand(client, 'uname -m')
const architecture = archResult.stdout.trim()
// Get uptime in seconds
const uptimeResult = await executeSSHCommand(
client,
"cat /proc/uptime 2>/dev/null | awk '{print int($1)}' || sysctl -n kern.boottime 2>/dev/null | awk '{print int(($(date +%s)) - $4)}'"
)
const uptime = Number.parseInt(uptimeResult.stdout.trim()) || 0
// Get memory info
const memoryResult = await executeSSHCommand(
client,
"free -b 2>/dev/null | awk '/Mem:/ {print $2, $7, $3}' || vm_stat 2>/dev/null | awk '/Pages free|Pages active|Pages speculative|Pages wired|page size/ {gsub(/[^0-9]/, \"\"); print}'"
)
const memParts = memoryResult.stdout.trim().split(/\s+/)
let memory = { total: 0, free: 0, used: 0 }
if (memParts.length >= 3) {
memory = {
total: Number.parseInt(memParts[0]) || 0,
free: Number.parseInt(memParts[1]) || 0,
used: Number.parseInt(memParts[2]) || 0,
}
}
// Get disk space
const diskResult = await executeSSHCommand(
client,
"df -B1 / 2>/dev/null | awk 'NR==2 {print $2, $4, $3}' || df -k / 2>/dev/null | awk 'NR==2 {print $2*1024, $4*1024, $3*1024}'"
)
const diskParts = diskResult.stdout.trim().split(/\s+/)
let diskSpace = { total: 0, free: 0, used: 0 }
if (diskParts.length >= 3) {
diskSpace = {
total: Number.parseInt(diskParts[0]) || 0,
free: Number.parseInt(diskParts[1]) || 0,
used: Number.parseInt(diskParts[2]) || 0,
}
}
logger.info(`[${requestId}] System info retrieved successfully`)
return NextResponse.json({
hostname,
os,
architecture,
uptime,
memory,
diskSpace,
message: `System info retrieved for ${hostname}`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH get system info failed:`, error)
return NextResponse.json(
{ error: `SSH get system info failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,132 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import type { Client, FileEntry, SFTPWrapper } from 'ssh2'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import {
createSSHConnection,
getFileType,
parsePermissions,
sanitizePath,
} from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHListDirectoryAPI')
const ListDirectorySchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
path: z.string().min(1, 'Path is required'),
detailed: z.boolean().default(true),
recursive: z.boolean().default(false),
})
function getSFTP(client: Client): Promise<SFTPWrapper> {
return new Promise((resolve, reject) => {
client.sftp((err, sftp) => {
if (err) {
reject(err)
} else {
resolve(sftp)
}
})
})
}
interface FileInfo {
name: string
type: 'file' | 'directory' | 'symlink' | 'other'
size: number
permissions: string
modified: string
}
async function listDir(sftp: SFTPWrapper, dirPath: string): Promise<FileEntry[]> {
return new Promise((resolve, reject) => {
sftp.readdir(dirPath, (err, list) => {
if (err) {
reject(err)
} else {
resolve(list)
}
})
})
}
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = ListDirectorySchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(`[${requestId}] Listing directory ${params.path} on ${params.host}:${params.port}`)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const sftp = await getSFTP(client)
const dirPath = sanitizePath(params.path)
const list = await listDir(sftp, dirPath)
const entries: FileInfo[] = list.map((entry) => ({
name: entry.filename,
type: getFileType(entry.attrs),
size: entry.attrs.size,
permissions: parsePermissions(entry.attrs.mode),
modified: new Date((entry.attrs.mtime || 0) * 1000).toISOString(),
}))
const totalFiles = entries.filter((e) => e.type === 'file').length
const totalDirectories = entries.filter((e) => e.type === 'directory').length
logger.info(
`[${requestId}] Directory listed successfully: ${totalFiles} files, ${totalDirectories} directories`
)
return NextResponse.json({
entries,
totalFiles,
totalDirectories,
message: `Found ${totalFiles} files and ${totalDirectories} directories`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH list directory failed:`, error)
return NextResponse.json(
{ error: `SSH list directory failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,117 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import {
createSSHConnection,
escapeShellArg,
executeSSHCommand,
sanitizePath,
} from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHMoveRenameAPI')
const MoveRenameSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
sourcePath: z.string().min(1, 'Source path is required'),
destinationPath: z.string().min(1, 'Destination path is required'),
overwrite: z.boolean().default(false),
})
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = MoveRenameSchema.parse(body)
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(
`[${requestId}] Moving ${params.sourcePath} to ${params.destinationPath} on ${params.host}:${params.port}`
)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const sourcePath = sanitizePath(params.sourcePath)
const destPath = sanitizePath(params.destinationPath)
const escapedSource = escapeShellArg(sourcePath)
const escapedDest = escapeShellArg(destPath)
const sourceCheck = await executeSSHCommand(
client,
`test -e '${escapedSource}' && echo "exists"`
)
if (sourceCheck.stdout.trim() !== 'exists') {
return NextResponse.json(
{ error: `Source path does not exist: ${sourcePath}` },
{ status: 404 }
)
}
if (!params.overwrite) {
const destCheck = await executeSSHCommand(
client,
`test -e '${escapedDest}' && echo "exists"`
)
if (destCheck.stdout.trim() === 'exists') {
return NextResponse.json(
{ error: `Destination already exists and overwrite is disabled: ${destPath}` },
{ status: 409 }
)
}
}
const command = params.overwrite
? `mv -f '${escapedSource}' '${escapedDest}'`
: `mv '${escapedSource}' '${escapedDest}'`
const result = await executeSSHCommand(client, command)
if (result.exitCode !== 0) {
throw new Error(result.stderr || 'Failed to move/rename')
}
logger.info(`[${requestId}] Successfully moved ${sourcePath} to ${destPath}`)
return NextResponse.json({
success: true,
sourcePath,
destinationPath: destPath,
message: `Successfully moved ${sourcePath} to ${destPath}`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH move/rename failed:`, error)
return NextResponse.json({ error: `SSH move/rename failed: ${errorMessage}` }, { status: 500 })
}
}

View File

@@ -0,0 +1,132 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper } from 'ssh2'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHReadFileContentAPI')
const ReadFileContentSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
path: z.string().min(1, 'Path is required'),
encoding: z.string().default('utf-8'),
maxSize: z.coerce.number().default(10), // MB
})
function getSFTP(client: Client): Promise<SFTPWrapper> {
return new Promise((resolve, reject) => {
client.sftp((err, sftp) => {
if (err) {
reject(err)
} else {
resolve(sftp)
}
})
})
}
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = ReadFileContentSchema.parse(body)
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(
`[${requestId}] Reading file content from ${params.path} on ${params.host}:${params.port}`
)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const sftp = await getSFTP(client)
const filePath = sanitizePath(params.path)
const maxBytes = params.maxSize * 1024 * 1024 // Convert MB to bytes
const stats = await new Promise<{ size: number }>((resolve, reject) => {
sftp.stat(filePath, (err, stats) => {
if (err) {
reject(new Error(`File not found: ${filePath}`))
} else {
resolve(stats)
}
})
})
if (stats.size > maxBytes) {
return NextResponse.json(
{ error: `File size (${stats.size} bytes) exceeds maximum allowed (${maxBytes} bytes)` },
{ status: 400 }
)
}
const content = await new Promise<string>((resolve, reject) => {
const chunks: Buffer[] = []
const readStream = sftp.createReadStream(filePath)
readStream.on('data', (chunk: Buffer) => {
chunks.push(chunk)
})
readStream.on('end', () => {
const buffer = Buffer.concat(chunks)
resolve(buffer.toString(params.encoding as BufferEncoding))
})
readStream.on('error', reject)
})
const lines = content.split('\n').length
logger.info(
`[${requestId}] File content read successfully: ${stats.size} bytes, ${lines} lines`
)
return NextResponse.json({
content,
size: stats.size,
lines,
path: filePath,
message: `File read successfully: ${stats.size} bytes, ${lines} lines`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH read file content failed:`, error)
return NextResponse.json(
{ error: `SSH read file content failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,129 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper } from 'ssh2'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHUploadFileAPI')
const UploadFileSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
fileContent: z.string().min(1, 'File content is required'),
fileName: z.string().min(1, 'File name is required'),
remotePath: z.string().min(1, 'Remote path is required'),
permissions: z.string().nullish(),
overwrite: z.boolean().default(true),
})
function getSFTP(client: Client): Promise<SFTPWrapper> {
return new Promise((resolve, reject) => {
client.sftp((err, sftp) => {
if (err) {
reject(err)
} else {
resolve(sftp)
}
})
})
}
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = UploadFileSchema.parse(body)
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(
`[${requestId}] Uploading file to ${params.host}:${params.port}${params.remotePath}`
)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const sftp = await getSFTP(client)
const remotePath = sanitizePath(params.remotePath)
if (!params.overwrite) {
const exists = await new Promise<boolean>((resolve) => {
sftp.stat(remotePath, (err) => {
resolve(!err)
})
})
if (exists) {
return NextResponse.json(
{ error: 'File already exists and overwrite is disabled' },
{ status: 409 }
)
}
}
let content: Buffer
try {
content = Buffer.from(params.fileContent, 'base64')
const reEncoded = content.toString('base64')
if (reEncoded !== params.fileContent) {
content = Buffer.from(params.fileContent, 'utf-8')
}
} catch {
content = Buffer.from(params.fileContent, 'utf-8')
}
await new Promise<void>((resolve, reject) => {
const writeStream = sftp.createWriteStream(remotePath, {
mode: params.permissions ? Number.parseInt(params.permissions, 8) : 0o644,
})
writeStream.on('error', reject)
writeStream.on('close', () => resolve())
writeStream.end(content)
})
logger.info(`[${requestId}] File uploaded successfully to ${remotePath}`)
return NextResponse.json({
uploaded: true,
remotePath: remotePath,
size: content.length,
message: `File uploaded successfully to ${remotePath}`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH file upload failed:`, error)
return NextResponse.json({ error: `SSH file upload failed: ${errorMessage}` }, { status: 500 })
}
}

View File

@@ -0,0 +1,272 @@
import { type Attributes, Client, type ConnectConfig } from 'ssh2'
// File type constants from POSIX
const S_IFMT = 0o170000 // bit mask for the file type bit field
const S_IFDIR = 0o040000 // directory
const S_IFREG = 0o100000 // regular file
const S_IFLNK = 0o120000 // symbolic link
export interface SSHConnectionConfig {
host: string
port: number
username: string
password?: string | null
privateKey?: string | null
passphrase?: string | null
timeout?: number
keepaliveInterval?: number
readyTimeout?: number
}
export interface SSHCommandResult {
stdout: string
stderr: string
exitCode: number
}
/**
* Format SSH error with helpful troubleshooting context
*/
function formatSSHError(err: Error, config: { host: string; port: number }): Error {
const errorMessage = err.message.toLowerCase()
const host = config.host
const port = config.port
// Connection refused - server not running or wrong port
if (errorMessage.includes('econnrefused') || errorMessage.includes('connection refused')) {
return new Error(
`Connection refused to ${host}:${port}. ` +
`Please verify: (1) SSH server is running on the target machine, ` +
`(2) Port ${port} is correct (default SSH port is 22), ` +
`(3) Firewall allows connections to port ${port}.`
)
}
// Connection reset - server closed connection unexpectedly
if (errorMessage.includes('econnreset') || errorMessage.includes('connection reset')) {
return new Error(
`Connection reset by ${host}:${port}. ` +
`This usually means: (1) Wrong port number (SSH default is 22), ` +
`(2) Server rejected the connection, ` +
`(3) Network/firewall interrupted the connection. ` +
`Verify your SSH server configuration and port number.`
)
}
// Timeout - server unreachable or slow
if (errorMessage.includes('etimedout') || errorMessage.includes('timeout')) {
return new Error(
`Connection timed out to ${host}:${port}. ` +
`Please verify: (1) Host "${host}" is reachable, ` +
`(2) No firewall is blocking the connection, ` +
`(3) The SSH server is responding.`
)
}
// DNS/hostname resolution
if (errorMessage.includes('enotfound') || errorMessage.includes('getaddrinfo')) {
return new Error(
`Could not resolve hostname "${host}". ` +
`Please verify the hostname or IP address is correct.`
)
}
// Authentication failure
if (errorMessage.includes('authentication') || errorMessage.includes('auth')) {
return new Error(
`Authentication failed for user on ${host}:${port}. ` +
`Please verify: (1) Username is correct, ` +
`(2) Password or private key is valid, ` +
`(3) User has SSH access on the server.`
)
}
// Private key format issues
if (
errorMessage.includes('key') &&
(errorMessage.includes('parse') || errorMessage.includes('invalid'))
) {
return new Error(
`Invalid private key format. ` +
`Please ensure you're using a valid OpenSSH private key. ` +
`The key should start with "-----BEGIN" and end with "-----END".`
)
}
// Host key verification (first connection)
if (errorMessage.includes('host key') || errorMessage.includes('hostkey')) {
return new Error(
`Host key verification issue for ${host}. ` +
`This may be the first connection to this server or the server's key has changed.`
)
}
// Return original error with context if no specific match
return new Error(`SSH connection to ${host}:${port} failed: ${err.message}`)
}
/**
* Create an SSH connection using the provided configuration
*
* Uses ssh2 library defaults which align with OpenSSH standards:
* - readyTimeout: 20000ms (20 seconds)
* - keepaliveInterval: 0 (disabled, same as OpenSSH ServerAliveInterval)
* - keepaliveCountMax: 3 (same as OpenSSH ServerAliveCountMax)
*/
export function createSSHConnection(config: SSHConnectionConfig): Promise<Client> {
return new Promise((resolve, reject) => {
const client = new Client()
const port = config.port || 22
const host = config.host
if (!host || host.trim() === '') {
reject(new Error('Host is required. Please provide a valid hostname or IP address.'))
return
}
const hasPassword = config.password && config.password.trim() !== ''
const hasPrivateKey = config.privateKey && config.privateKey.trim() !== ''
if (!hasPassword && !hasPrivateKey) {
reject(new Error('Authentication required. Please provide either a password or private key.'))
return
}
const connectConfig: ConnectConfig = {
host: host.trim(),
port,
username: config.username,
}
if (config.readyTimeout !== undefined) {
connectConfig.readyTimeout = config.readyTimeout
}
if (config.keepaliveInterval !== undefined) {
connectConfig.keepaliveInterval = config.keepaliveInterval
}
if (hasPrivateKey) {
connectConfig.privateKey = config.privateKey!
if (config.passphrase && config.passphrase.trim() !== '') {
connectConfig.passphrase = config.passphrase
}
} else if (hasPassword) {
connectConfig.password = config.password!
}
client.on('ready', () => {
resolve(client)
})
client.on('error', (err) => {
reject(formatSSHError(err, { host, port }))
})
try {
client.connect(connectConfig)
} catch (err) {
reject(formatSSHError(err instanceof Error ? err : new Error(String(err)), { host, port }))
}
})
}
/**
* Execute a command on the SSH connection
*/
export function executeSSHCommand(client: Client, command: string): Promise<SSHCommandResult> {
return new Promise((resolve, reject) => {
client.exec(command, (err, stream) => {
if (err) {
reject(err)
return
}
let stdout = ''
let stderr = ''
stream.on('close', (code: number) => {
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
exitCode: code ?? 0,
})
})
stream.on('data', (data: Buffer) => {
stdout += data.toString()
})
stream.stderr.on('data', (data: Buffer) => {
stderr += data.toString()
})
})
})
}
/**
* Sanitize command input to prevent command injection
*/
export function sanitizeCommand(command: string): string {
return command.trim()
}
/**
* Sanitize file path - removes null bytes and trims whitespace
*/
export function sanitizePath(path: string): string {
let sanitized = path.replace(/\0/g, '')
sanitized = sanitized.trim()
return sanitized
}
/**
* Escape a string for safe use in single-quoted shell arguments
* This is standard practice for shell command construction.
* e.g., "/tmp/test'file" becomes "/tmp/test'\''file"
*
* The pattern 'foo'\''bar' works because:
* - First ' ends the current single-quoted string
* - \' inserts a literal single quote (escaped outside quotes)
* - Next ' starts a new single-quoted string
*/
export function escapeShellArg(arg: string): string {
return arg.replace(/'/g, "'\\''")
}
/**
* Validate that authentication credentials are provided
*/
export function validateAuth(params: { password?: string; privateKey?: string }): {
isValid: boolean
error?: string
} {
if (!params.password && !params.privateKey) {
return {
isValid: false,
error: 'Either password or privateKey must be provided for authentication',
}
}
return { isValid: true }
}
/**
* Parse file permissions from octal string
*/
export function parsePermissions(mode: number): string {
return `0${(mode & 0o777).toString(8)}`
}
/**
* Get file type from attributes mode bits
*/
export function getFileType(attrs: Attributes): 'file' | 'directory' | 'symlink' | 'other' {
const mode = attrs.mode
const fileType = mode & S_IFMT
if (fileType === S_IFDIR) return 'directory'
if (fileType === S_IFREG) return 'file'
if (fileType === S_IFLNK) return 'symlink'
return 'other'
}

View File

@@ -0,0 +1,152 @@
import { randomUUID } from 'crypto'
import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper } from 'ssh2'
import { z } from 'zod'
import { createLogger } from '@/lib/logs/console/logger'
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHWriteFileContentAPI')
const WriteFileContentSchema = z.object({
host: z.string().min(1, 'Host is required'),
port: z.coerce.number().int().positive().default(22),
username: z.string().min(1, 'Username is required'),
password: z.string().nullish(),
privateKey: z.string().nullish(),
passphrase: z.string().nullish(),
path: z.string().min(1, 'Path is required'),
content: z.string(),
mode: z.enum(['overwrite', 'append', 'create']).default('overwrite'),
permissions: z.string().nullish(),
})
function getSFTP(client: Client): Promise<SFTPWrapper> {
return new Promise((resolve, reject) => {
client.sftp((err, sftp) => {
if (err) {
reject(err)
} else {
resolve(sftp)
}
})
})
}
export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8)
try {
const body = await request.json()
const params = WriteFileContentSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) {
return NextResponse.json(
{ error: 'Either password or privateKey must be provided' },
{ status: 400 }
)
}
logger.info(
`[${requestId}] Writing file content to ${params.path} on ${params.host}:${params.port} (mode: ${params.mode})`
)
const client = await createSSHConnection({
host: params.host,
port: params.port,
username: params.username,
password: params.password,
privateKey: params.privateKey,
passphrase: params.passphrase,
})
try {
const sftp = await getSFTP(client)
const filePath = sanitizePath(params.path)
// Check if file exists for 'create' mode
if (params.mode === 'create') {
const exists = await new Promise<boolean>((resolve) => {
sftp.stat(filePath, (err) => {
resolve(!err)
})
})
if (exists) {
return NextResponse.json(
{ error: `File already exists and mode is 'create': ${filePath}` },
{ status: 409 }
)
}
}
// Handle append mode by reading existing content first
let finalContent = params.content
if (params.mode === 'append') {
const existingContent = await new Promise<string>((resolve) => {
const chunks: Buffer[] = []
const readStream = sftp.createReadStream(filePath)
readStream.on('data', (chunk: Buffer) => {
chunks.push(chunk)
})
readStream.on('end', () => {
resolve(Buffer.concat(chunks).toString('utf-8'))
})
readStream.on('error', () => {
resolve('')
})
})
finalContent = existingContent + params.content
}
// Write file
const fileMode = params.permissions ? Number.parseInt(params.permissions, 8) : 0o644
await new Promise<void>((resolve, reject) => {
const writeStream = sftp.createWriteStream(filePath, { mode: fileMode })
writeStream.on('error', reject)
writeStream.on('close', () => resolve())
writeStream.end(Buffer.from(finalContent, 'utf-8'))
})
// Get final file size
const stats = await new Promise<{ size: number }>((resolve, reject) => {
sftp.stat(filePath, (err, stats) => {
if (err) reject(err)
else resolve(stats)
})
})
logger.info(`[${requestId}] File written successfully: ${stats.size} bytes`)
return NextResponse.json({
written: true,
path: filePath,
size: stats.size,
message: `File written successfully: ${stats.size} bytes`,
})
} finally {
client.end()
}
} catch (error) {
if (error instanceof z.ZodError) {
logger.warn(`[${requestId}] Invalid request data`, { errors: error.errors })
return NextResponse.json(
{ error: 'Invalid request data', details: error.errors },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
logger.error(`[${requestId}] SSH write file content failed:`, error)
return NextResponse.json(
{ error: `SSH write file content failed: ${errorMessage}` },
{ status: 500 }
)
}
}

View File

@@ -232,7 +232,7 @@ function TemplateCardInner({
key={index}
className='flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-[4px]'
style={{
backgroundColor: blockConfig.bgColor || 'gray',
background: blockConfig.bgColor || 'gray',
marginLeft: index > 0 ? '-4px' : '0',
}}
>
@@ -257,7 +257,7 @@ function TemplateCardInner({
key={index}
className='flex h-[18px] w-[18px] flex-shrink-0 items-center justify-center rounded-[4px]'
style={{
backgroundColor: blockConfig.bgColor || 'gray',
background: blockConfig.bgColor || 'gray',
marginLeft: index > 0 ? '-4px' : '0',
}}
>

View File

@@ -232,6 +232,38 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'webhooks:read': 'Read your Pipedrive webhooks',
'webhooks:full': 'Full access to manage your Pipedrive webhooks',
w_member_social: 'Access your LinkedIn profile',
// Box scopes
root_readwrite: 'Read and write all files and folders in your Box account',
root_readonly: 'Read all files and folders in your Box account',
// Shopify scopes (write_* implicitly includes read access)
write_products: 'Read and manage your Shopify products',
write_orders: 'Read and manage your Shopify orders',
write_customers: 'Read and manage your Shopify customers',
write_inventory: 'Read and manage your Shopify inventory levels',
read_locations: 'View your store locations',
write_merchant_managed_fulfillment_orders: 'Create fulfillments for orders',
// Zoom scopes
'user:read:user': 'View your Zoom profile information',
'meeting:write:meeting': 'Create Zoom meetings',
'meeting:read:meeting': 'View Zoom meeting details',
'meeting:read:list_meetings': 'List your Zoom meetings',
'meeting:update:meeting': 'Update Zoom meetings',
'meeting:delete:meeting': 'Delete Zoom meetings',
'meeting:read:invitation': 'View Zoom meeting invitations',
'meeting:read:list_past_participants': 'View past meeting participants',
'cloud_recording:read:list_user_recordings': 'List your Zoom cloud recordings',
'cloud_recording:read:list_recording_files': 'View recording files',
'cloud_recording:delete:recording_file': 'Delete cloud recordings',
// Dropbox scopes
'account_info.read': 'View your Dropbox account information',
'files.metadata.read': 'View file and folder names, sizes, and dates',
'files.metadata.write': 'Modify file and folder metadata',
'files.content.read': 'Download and read your Dropbox files',
'files.content.write': 'Upload, copy, move, and delete files in your Dropbox',
'sharing.read': 'View your shared files and folders',
'sharing.write': 'Share files and folders with others',
// WordPress.com scopes
global: 'Full access to manage your WordPress.com sites, posts, pages, media, and settings',
}
function getScopeDescription(scope: string): string {
@@ -289,6 +321,13 @@ export function OAuthRequiredModal({
return
}
if (providerId === 'shopify') {
// Pass the current URL so we can redirect back after OAuth
const returnUrl = encodeURIComponent(window.location.href)
window.location.href = `/api/auth/shopify/authorize?returnUrl=${returnUrl}`
return
}
await client.oauth2.link({
providerId,
callbackURL: window.location.href,

View File

@@ -353,6 +353,7 @@ export function Editor() {
blockId={currentBlockId}
config={subBlock}
isPreview={false}
subBlockValues={subBlockState}
disabled={!userPermissions.canEdit}
fieldDiffStatus={undefined}
allowExpandInPreview={false}

View File

@@ -564,9 +564,7 @@ export function SearchModal({
<div
className='relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
style={{
backgroundColor: showColoredIcon
? item.bgColor
: 'transparent',
background: showColoredIcon ? item.bgColor : 'transparent',
}}
>
<Icon

View File

@@ -0,0 +1,458 @@
import { AhrefsIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { AhrefsResponse } from '@/tools/ahrefs/types'
export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
type: 'ahrefs',
name: 'Ahrefs',
description: 'SEO analysis with Ahrefs',
authMode: AuthMode.ApiKey,
longDescription:
'Integrate Ahrefs SEO tools into your workflow. Analyze domain ratings, backlinks, organic keywords, top pages, and more. Requires an Ahrefs Enterprise plan with API access.',
docsLink: 'https://docs.ahrefs.com/docs/api/reference/introduction',
category: 'tools',
bgColor: '#E0E0E0',
icon: AhrefsIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Domain Rating', id: 'ahrefs_domain_rating' },
{ label: 'Backlinks', id: 'ahrefs_backlinks' },
{ label: 'Backlinks Stats', id: 'ahrefs_backlinks_stats' },
{ label: 'Referring Domains', id: 'ahrefs_referring_domains' },
{ label: 'Organic Keywords', id: 'ahrefs_organic_keywords' },
{ label: 'Top Pages', id: 'ahrefs_top_pages' },
{ label: 'Keyword Overview', id: 'ahrefs_keyword_overview' },
{ label: 'Broken Backlinks', id: 'ahrefs_broken_backlinks' },
],
value: () => 'ahrefs_domain_rating',
},
// Domain Rating operation inputs
{
id: 'target',
title: 'Target Domain',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_domain_rating' },
required: true,
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_domain_rating' },
},
// Backlinks operation inputs
{
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: 'example.com or https://example.com/page',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
required: true,
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
},
// Backlinks Stats operation inputs
{
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_backlinks_stats' },
required: true,
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_backlinks_stats' },
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_backlinks_stats' },
},
// Referring Domains operation inputs
{
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
required: true,
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
},
// Organic Keywords operation inputs
{
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: [
{ label: 'United States', id: 'us' },
{ label: 'United Kingdom', id: 'gb' },
{ label: 'Germany', id: 'de' },
{ label: 'France', id: 'fr' },
{ label: 'Spain', id: 'es' },
{ label: 'Italy', id: 'it' },
{ label: 'Canada', id: 'ca' },
{ label: 'Australia', id: 'au' },
{ label: 'Japan', id: 'jp' },
{ label: 'Brazil', id: 'br' },
{ label: 'India', id: 'in' },
{ label: 'Netherlands', id: 'nl' },
{ label: 'Poland', id: 'pl' },
{ label: 'Russia', id: 'ru' },
{ label: 'Mexico', id: 'mx' },
],
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
},
// Top Pages operation inputs
{
id: 'target',
title: 'Target Domain',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: [
{ label: 'United States', id: 'us' },
{ label: 'United Kingdom', id: 'gb' },
{ label: 'Germany', id: 'de' },
{ label: 'France', id: 'fr' },
{ label: 'Spain', id: 'es' },
{ label: 'Italy', id: 'it' },
{ label: 'Canada', id: 'ca' },
{ label: 'Australia', id: 'au' },
{ label: 'Japan', id: 'jp' },
{ label: 'Brazil', id: 'br' },
{ label: 'India', id: 'in' },
{ label: 'Netherlands', id: 'nl' },
{ label: 'Poland', id: 'pl' },
{ label: 'Russia', id: 'ru' },
{ label: 'Mexico', id: 'mx' },
],
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
},
// Keyword Overview operation inputs
{
id: 'keyword',
title: 'Keyword',
type: 'short-input',
placeholder: 'Enter keyword to analyze',
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: [
{ label: 'United States', id: 'us' },
{ label: 'United Kingdom', id: 'gb' },
{ label: 'Germany', id: 'de' },
{ label: 'France', id: 'fr' },
{ label: 'Spain', id: 'es' },
{ label: 'Italy', id: 'it' },
{ label: 'Canada', id: 'ca' },
{ label: 'Australia', id: 'au' },
{ label: 'Japan', id: 'jp' },
{ label: 'Brazil', id: 'br' },
{ label: 'India', id: 'in' },
{ label: 'Netherlands', id: 'nl' },
{ label: 'Poland', id: 'pl' },
{ label: 'Russia', id: 'ru' },
{ label: 'Mexico', id: 'mx' },
],
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
},
// Broken Backlinks operation inputs
{
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
required: true,
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
},
// API Key (common to all operations)
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter your Ahrefs API key',
password: true,
required: true,
},
],
tools: {
access: [
'ahrefs_domain_rating',
'ahrefs_backlinks',
'ahrefs_backlinks_stats',
'ahrefs_referring_domains',
'ahrefs_organic_keywords',
'ahrefs_top_pages',
'ahrefs_keyword_overview',
'ahrefs_broken_backlinks',
],
config: {
tool: (params) => {
// Convert numeric string inputs to numbers
if (params.limit) {
params.limit = Number(params.limit)
}
if (params.offset) {
params.offset = Number(params.offset)
}
switch (params.operation) {
case 'ahrefs_domain_rating':
return 'ahrefs_domain_rating'
case 'ahrefs_backlinks':
return 'ahrefs_backlinks'
case 'ahrefs_backlinks_stats':
return 'ahrefs_backlinks_stats'
case 'ahrefs_referring_domains':
return 'ahrefs_referring_domains'
case 'ahrefs_organic_keywords':
return 'ahrefs_organic_keywords'
case 'ahrefs_top_pages':
return 'ahrefs_top_pages'
case 'ahrefs_keyword_overview':
return 'ahrefs_keyword_overview'
case 'ahrefs_broken_backlinks':
return 'ahrefs_broken_backlinks'
default:
return 'ahrefs_domain_rating'
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
apiKey: { type: 'string', description: 'Ahrefs API key' },
target: { type: 'string', description: 'Target domain or URL to analyze' },
keyword: { type: 'string', description: 'Keyword to analyze' },
mode: { type: 'string', description: 'Analysis mode (domain, prefix, subdomains, exact)' },
country: { type: 'string', description: 'Country code for geo-specific data' },
date: { type: 'string', description: 'Date for historical data in YYYY-MM-DD format' },
limit: { type: 'number', description: 'Maximum number of results to return' },
offset: { type: 'number', description: 'Number of results to skip for pagination' },
},
outputs: {
// Domain Rating output
domainRating: { type: 'number', description: 'Domain Rating score (0-100)' },
ahrefsRank: { type: 'number', description: 'Ahrefs Rank (global ranking)' },
// Backlinks output
backlinks: { type: 'json', description: 'List of backlinks' },
// Backlinks Stats output
stats: { type: 'json', description: 'Backlink statistics' },
// Referring Domains output
referringDomains: { type: 'json', description: 'List of referring domains' },
// Organic Keywords output
keywords: { type: 'json', description: 'List of organic keywords' },
// Top Pages output
pages: { type: 'json', description: 'List of top pages' },
// Keyword Overview output
overview: { type: 'json', description: 'Keyword metrics overview' },
// Broken Backlinks output
brokenBacklinks: { type: 'json', description: 'List of broken backlinks' },
},
}

View File

@@ -0,0 +1,599 @@
import { DatadogIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { DatadogResponse } from '@/tools/datadog/types'
export const DatadogBlock: BlockConfig<DatadogResponse> = {
type: 'datadog',
name: 'Datadog',
description: 'Monitor infrastructure, applications, and logs with Datadog',
authMode: AuthMode.ApiKey,
longDescription:
'Integrate Datadog monitoring into workflows. Submit metrics, manage monitors, query logs, create events, handle downtimes, and more.',
docsLink: 'https://docs.sim.ai/tools/datadog',
category: 'tools',
bgColor: '#632CA6',
icon: DatadogIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Submit Metrics', id: 'datadog_submit_metrics' },
{ label: 'Query Timeseries', id: 'datadog_query_timeseries' },
{ label: 'Create Event', id: 'datadog_create_event' },
{ label: 'Create Monitor', id: 'datadog_create_monitor' },
{ label: 'Get Monitor', id: 'datadog_get_monitor' },
{ label: 'List Monitors', id: 'datadog_list_monitors' },
{ label: 'Mute Monitor', id: 'datadog_mute_monitor' },
{ label: 'Query Logs', id: 'datadog_query_logs' },
{ label: 'Send Logs', id: 'datadog_send_logs' },
{ label: 'Create Downtime', id: 'datadog_create_downtime' },
{ label: 'List Downtimes', id: 'datadog_list_downtimes' },
{ label: 'Cancel Downtime', id: 'datadog_cancel_downtime' },
],
value: () => 'datadog_submit_metrics',
},
// ========================
// Submit Metrics inputs
// ========================
{
id: 'series',
title: 'Metrics Data (JSON)',
type: 'code',
placeholder: `[
{
"metric": "custom.app.response_time",
"type": "gauge",
"points": [{"timestamp": ${Math.floor(Date.now() / 1000)}, "value": 0.85}],
"tags": ["env:production", "service:api"]
}
]`,
condition: { field: 'operation', value: 'datadog_submit_metrics' },
required: true,
},
// ========================
// Query Timeseries inputs
// ========================
{
id: 'query',
title: 'Query',
type: 'long-input',
placeholder: 'avg:system.cpu.user{*}',
condition: { field: 'operation', value: 'datadog_query_timeseries' },
required: true,
},
{
id: 'from',
title: 'From (Unix Timestamp)',
type: 'short-input',
placeholder: 'e.g., 1701360000',
condition: { field: 'operation', value: 'datadog_query_timeseries' },
required: true,
},
{
id: 'to',
title: 'To (Unix Timestamp)',
type: 'short-input',
placeholder: 'e.g., 1701446400',
condition: { field: 'operation', value: 'datadog_query_timeseries' },
required: true,
},
// ========================
// Create Event inputs
// ========================
{
id: 'title',
title: 'Event Title',
type: 'short-input',
placeholder: 'Deployment completed',
condition: { field: 'operation', value: 'datadog_create_event' },
required: true,
},
{
id: 'text',
title: 'Event Text',
type: 'long-input',
placeholder: 'Describe the event...',
condition: { field: 'operation', value: 'datadog_create_event' },
required: true,
},
{
id: 'alertType',
title: 'Alert Type',
type: 'dropdown',
options: [
{ label: 'Info', id: 'info' },
{ label: 'Success', id: 'success' },
{ label: 'Warning', id: 'warning' },
{ label: 'Error', id: 'error' },
],
value: () => 'info',
condition: { field: 'operation', value: 'datadog_create_event' },
},
{
id: 'priority',
title: 'Priority',
type: 'dropdown',
options: [
{ label: 'Normal', id: 'normal' },
{ label: 'Low', id: 'low' },
],
value: () => 'normal',
condition: { field: 'operation', value: 'datadog_create_event' },
},
{
id: 'tags',
title: 'Tags',
type: 'short-input',
placeholder: 'env:production, service:api',
condition: { field: 'operation', value: 'datadog_create_event' },
},
// ========================
// Create Monitor inputs
// ========================
{
id: 'name',
title: 'Monitor Name',
type: 'short-input',
placeholder: 'High CPU Usage Alert',
condition: { field: 'operation', value: 'datadog_create_monitor' },
required: true,
},
{
id: 'type',
title: 'Monitor Type',
type: 'dropdown',
options: [
{ label: 'Metric Alert', id: 'metric alert' },
{ label: 'Service Check', id: 'service check' },
{ label: 'Event Alert', id: 'event alert' },
{ label: 'Log Alert', id: 'log alert' },
{ label: 'Query Alert', id: 'query alert' },
{ label: 'Composite', id: 'composite' },
{ label: 'SLO Alert', id: 'slo alert' },
],
value: () => 'metric alert',
condition: { field: 'operation', value: 'datadog_create_monitor' },
required: true,
},
{
id: 'monitorQuery',
title: 'Monitor Query',
type: 'long-input',
placeholder: 'avg(last_5m):avg:system.cpu.idle{*} < 20',
condition: { field: 'operation', value: 'datadog_create_monitor' },
required: true,
},
{
id: 'message',
title: 'Notification Message',
type: 'long-input',
placeholder: 'Alert! CPU usage is high. @slack-alerts',
condition: { field: 'operation', value: 'datadog_create_monitor' },
},
{
id: 'monitorTags',
title: 'Tags',
type: 'short-input',
placeholder: 'team:backend, priority:high',
condition: { field: 'operation', value: 'datadog_create_monitor' },
},
{
id: 'monitorPriority',
title: 'Priority (1-5)',
type: 'short-input',
placeholder: '3',
condition: { field: 'operation', value: 'datadog_create_monitor' },
},
{
id: 'options',
title: 'Options (JSON)',
type: 'code',
placeholder: '{"notify_no_data": true, "thresholds": {"critical": 90}}',
condition: { field: 'operation', value: 'datadog_create_monitor' },
},
// ========================
// Get Monitor inputs
// ========================
{
id: 'monitorId',
title: 'Monitor ID',
type: 'short-input',
placeholder: '12345678',
condition: { field: 'operation', value: 'datadog_get_monitor' },
required: true,
},
// ========================
// List Monitors inputs
// ========================
{
id: 'listMonitorName',
title: 'Filter by Name',
type: 'short-input',
placeholder: 'CPU',
condition: { field: 'operation', value: 'datadog_list_monitors' },
},
{
id: 'listMonitorTags',
title: 'Filter by Tags',
type: 'short-input',
placeholder: 'env:production',
condition: { field: 'operation', value: 'datadog_list_monitors' },
},
// ========================
// Mute Monitor inputs
// ========================
{
id: 'muteMonitorId',
title: 'Monitor ID',
type: 'short-input',
placeholder: '12345678',
condition: { field: 'operation', value: 'datadog_mute_monitor' },
required: true,
},
{
id: 'scope',
title: 'Scope',
type: 'short-input',
placeholder: 'host:myhost (optional)',
condition: { field: 'operation', value: 'datadog_mute_monitor' },
},
{
id: 'end',
title: 'End Time (Unix Timestamp)',
type: 'short-input',
placeholder: 'Leave empty for indefinite',
condition: { field: 'operation', value: 'datadog_mute_monitor' },
},
// ========================
// Query Logs inputs
// ========================
{
id: 'logQuery',
title: 'Search Query',
type: 'long-input',
placeholder: 'service:web-app status:error',
condition: { field: 'operation', value: 'datadog_query_logs' },
required: true,
},
{
id: 'logFrom',
title: 'From',
type: 'short-input',
placeholder: 'now-1h',
condition: { field: 'operation', value: 'datadog_query_logs' },
required: true,
},
{
id: 'logTo',
title: 'To',
type: 'short-input',
placeholder: 'now',
condition: { field: 'operation', value: 'datadog_query_logs' },
required: true,
},
{
id: 'logLimit',
title: 'Limit',
type: 'short-input',
placeholder: '50',
condition: { field: 'operation', value: 'datadog_query_logs' },
},
// ========================
// Send Logs inputs
// ========================
{
id: 'logs',
title: 'Logs (JSON)',
type: 'code',
placeholder: `[
{
"message": "Application started successfully",
"service": "my-app",
"ddsource": "custom",
"ddtags": "env:production"
}
]`,
condition: { field: 'operation', value: 'datadog_send_logs' },
required: true,
},
// ========================
// Create Downtime inputs
// ========================
{
id: 'downtimeScope',
title: 'Scope',
type: 'short-input',
placeholder: 'host:myhost or env:production or *',
condition: { field: 'operation', value: 'datadog_create_downtime' },
required: true,
},
{
id: 'downtimeMessage',
title: 'Message',
type: 'long-input',
placeholder: 'Scheduled maintenance',
condition: { field: 'operation', value: 'datadog_create_downtime' },
},
{
id: 'downtimeStart',
title: 'Start Time (Unix Timestamp)',
type: 'short-input',
placeholder: 'Leave empty for now',
condition: { field: 'operation', value: 'datadog_create_downtime' },
},
{
id: 'downtimeEnd',
title: 'End Time (Unix Timestamp)',
type: 'short-input',
placeholder: 'e.g., 1701450000',
condition: { field: 'operation', value: 'datadog_create_downtime' },
},
{
id: 'downtimeMonitorId',
title: 'Monitor ID (optional)',
type: 'short-input',
placeholder: '12345678',
condition: { field: 'operation', value: 'datadog_create_downtime' },
},
// ========================
// List Downtimes inputs
// ========================
{
id: 'currentOnly',
title: 'Current Only',
type: 'switch',
condition: { field: 'operation', value: 'datadog_list_downtimes' },
},
// ========================
// Cancel Downtime inputs
// ========================
{
id: 'downtimeId',
title: 'Downtime ID',
type: 'short-input',
placeholder: 'abc123',
condition: { field: 'operation', value: 'datadog_cancel_downtime' },
required: true,
},
// ========================
// Authentication (common)
// ========================
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter your Datadog API key',
password: true,
required: true,
},
// Application Key - REQUIRED only for read/manage operations (not needed for submit_metrics, create_event, send_logs)
{
id: 'applicationKey',
title: 'Application Key',
type: 'short-input',
placeholder: 'Enter your Datadog application key',
password: true,
condition: {
field: 'operation',
value: [
'datadog_query_timeseries',
'datadog_create_monitor',
'datadog_get_monitor',
'datadog_list_monitors',
'datadog_mute_monitor',
'datadog_query_logs',
'datadog_create_downtime',
'datadog_list_downtimes',
'datadog_cancel_downtime',
],
},
required: true,
},
{
id: 'site',
title: 'Datadog Site',
type: 'dropdown',
options: [
{ label: 'US1 (datadoghq.com)', id: 'datadoghq.com' },
{ label: 'US3 (us3.datadoghq.com)', id: 'us3.datadoghq.com' },
{ label: 'US5 (us5.datadoghq.com)', id: 'us5.datadoghq.com' },
{ label: 'EU (datadoghq.eu)', id: 'datadoghq.eu' },
{ label: 'AP1 (ap1.datadoghq.com)', id: 'ap1.datadoghq.com' },
{ label: 'US1-FED (ddog-gov.com)', id: 'ddog-gov.com' },
],
value: () => 'datadoghq.com',
},
],
tools: {
access: [
'datadog_submit_metrics',
'datadog_query_timeseries',
'datadog_create_event',
'datadog_create_monitor',
'datadog_get_monitor',
'datadog_list_monitors',
'datadog_mute_monitor',
'datadog_query_logs',
'datadog_send_logs',
'datadog_create_downtime',
'datadog_list_downtimes',
'datadog_cancel_downtime',
],
config: {
tool: (params) => params.operation,
params: (params) => {
// Base params that are always needed
const baseParams: Record<string, any> = {
apiKey: params.apiKey,
applicationKey: params.applicationKey,
site: params.site,
}
// Only include params relevant to each operation
switch (params.operation) {
case 'datadog_submit_metrics':
return { ...baseParams, series: params.series }
case 'datadog_query_timeseries':
return {
...baseParams,
query: params.query,
from: params.from ? Number(params.from) : undefined,
to: params.to ? Number(params.to) : undefined,
}
case 'datadog_create_event':
return {
...baseParams,
title: params.title,
text: params.text,
alertType: params.alertType,
priority: params.priority,
tags: params.tags,
}
case 'datadog_create_monitor':
return {
...baseParams,
name: params.name,
type: params.type,
query: params.monitorQuery,
message: params.message,
tags: params.monitorTags,
priority: params.monitorPriority ? Number(params.monitorPriority) : undefined,
options: params.options,
}
case 'datadog_get_monitor':
return { ...baseParams, monitorId: params.monitorId }
case 'datadog_list_monitors':
return {
...baseParams,
name: params.listMonitorName || undefined,
tags: params.listMonitorTags || undefined,
}
case 'datadog_mute_monitor':
return {
...baseParams,
monitorId: params.muteMonitorId,
scope: params.scope,
end: params.end ? Number(params.end) : undefined,
}
case 'datadog_query_logs':
return {
...baseParams,
query: params.logQuery,
from: params.logFrom,
to: params.logTo,
limit: params.logLimit ? Number(params.logLimit) : undefined,
}
case 'datadog_send_logs':
return { ...baseParams, logs: params.logs }
case 'datadog_create_downtime':
return {
...baseParams,
scope: params.downtimeScope,
message: params.downtimeMessage,
start: params.downtimeStart ? Number(params.downtimeStart) : undefined,
end: params.downtimeEnd ? Number(params.downtimeEnd) : undefined,
monitorId: params.downtimeMonitorId,
}
case 'datadog_list_downtimes':
return { ...baseParams, currentOnly: params.currentOnly }
case 'datadog_cancel_downtime':
return { ...baseParams, downtimeId: params.downtimeId }
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
apiKey: { type: 'string', description: 'Datadog API key' },
applicationKey: { type: 'string', description: 'Datadog Application key' },
site: { type: 'string', description: 'Datadog site/region' },
// Metrics
series: { type: 'json', description: 'Metrics data to submit' },
query: { type: 'string', description: 'Query string' },
from: { type: 'number', description: 'Start time (Unix timestamp)' },
to: { type: 'number', description: 'End time (Unix timestamp)' },
// Events
title: { type: 'string', description: 'Event title' },
text: { type: 'string', description: 'Event text/body' },
alertType: { type: 'string', description: 'Alert type' },
priority: { type: 'string', description: 'Priority level' },
tags: { type: 'string', description: 'Comma-separated tags' },
// Monitors
name: { type: 'string', description: 'Monitor name' },
type: { type: 'string', description: 'Monitor type' },
monitorQuery: { type: 'string', description: 'Monitor query' },
message: { type: 'string', description: 'Notification message' },
monitorTags: { type: 'string', description: 'Monitor tags' },
monitorPriority: { type: 'number', description: 'Monitor priority (1-5)' },
options: { type: 'json', description: 'Monitor options' },
monitorId: { type: 'string', description: 'Monitor ID' },
muteMonitorId: { type: 'string', description: 'Monitor ID to mute' },
scope: { type: 'string', description: 'Scope for muting' },
end: { type: 'number', description: 'End time for mute' },
// Logs
logQuery: { type: 'string', description: 'Log search query' },
logFrom: { type: 'string', description: 'Log start time' },
logTo: { type: 'string', description: 'Log end time' },
logLimit: { type: 'number', description: 'Max logs to return' },
logs: { type: 'json', description: 'Logs to send' },
// Downtimes
downtimeScope: { type: 'string', description: 'Downtime scope' },
downtimeMessage: { type: 'string', description: 'Downtime message' },
downtimeStart: { type: 'number', description: 'Downtime start time' },
downtimeEnd: { type: 'number', description: 'Downtime end time' },
downtimeMonitorId: { type: 'string', description: 'Monitor ID for downtime' },
currentOnly: { type: 'boolean', description: 'Filter to current downtimes' },
downtimeId: { type: 'string', description: 'Downtime ID to cancel' },
listMonitorName: { type: 'string', description: 'Filter monitors by name' },
listMonitorTags: { type: 'string', description: 'Filter monitors by tags' },
},
outputs: {
success: { type: 'boolean', description: 'Whether the operation succeeded' },
// Metrics
series: { type: 'json', description: 'Timeseries data' },
status: { type: 'string', description: 'Query status' },
// Events
event: { type: 'json', description: 'Event data' },
events: { type: 'json', description: 'List of events' },
// Monitors
monitor: { type: 'json', description: 'Monitor data' },
monitors: { type: 'json', description: 'List of monitors' },
// Logs
logs: { type: 'json', description: 'Log entries' },
nextLogId: { type: 'string', description: 'Pagination cursor for logs' },
// Downtimes
downtime: { type: 'json', description: 'Downtime data' },
downtimes: { type: 'json', description: 'List of downtimes' },
},
}

View File

@@ -0,0 +1,368 @@
import { DropboxIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { DropboxResponse } from '@/tools/dropbox/types'
export const DropboxBlock: BlockConfig<DropboxResponse> = {
type: 'dropbox',
name: 'Dropbox',
description: 'Upload, download, share, and manage files in Dropbox',
authMode: AuthMode.OAuth,
longDescription:
'Integrate Dropbox into your workflow for file management, sharing, and collaboration. Upload files, download content, create folders, manage shared links, and more.',
docsLink: 'https://docs.sim.ai/tools/dropbox',
category: 'tools',
icon: DropboxIcon,
bgColor: '#0061FF',
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Upload File', id: 'dropbox_upload' },
{ label: 'Download File', id: 'dropbox_download' },
{ label: 'List Folder', id: 'dropbox_list_folder' },
{ label: 'Create Folder', id: 'dropbox_create_folder' },
{ label: 'Delete File/Folder', id: 'dropbox_delete' },
{ label: 'Copy File/Folder', id: 'dropbox_copy' },
{ label: 'Move File/Folder', id: 'dropbox_move' },
{ label: 'Get Metadata', id: 'dropbox_get_metadata' },
{ label: 'Create Shared Link', id: 'dropbox_create_shared_link' },
{ label: 'Search Files', id: 'dropbox_search' },
],
value: () => 'dropbox_upload',
},
{
id: 'credential',
title: 'Dropbox Account',
type: 'oauth-input',
serviceId: 'dropbox',
requiredScopes: [
'account_info.read',
'files.metadata.read',
'files.metadata.write',
'files.content.read',
'files.content.write',
'sharing.read',
'sharing.write',
],
placeholder: 'Select Dropbox account',
required: true,
},
// Upload operation inputs
{
id: 'path',
title: 'Destination Path',
type: 'short-input',
placeholder: '/folder/document.pdf',
condition: { field: 'operation', value: 'dropbox_upload' },
required: true,
},
{
id: 'fileContent',
title: 'File Content',
type: 'long-input',
placeholder: 'Base64 encoded file content or file reference',
condition: { field: 'operation', value: 'dropbox_upload' },
required: true,
},
{
id: 'mode',
title: 'Write Mode',
type: 'dropdown',
options: [
{ label: 'Add (create new)', id: 'add' },
{ label: 'Overwrite (replace existing)', id: 'overwrite' },
],
value: () => 'add',
condition: { field: 'operation', value: 'dropbox_upload' },
},
{
id: 'autorename',
title: 'Auto-rename on Conflict',
type: 'switch',
condition: { field: 'operation', value: 'dropbox_upload' },
},
// Download operation inputs
{
id: 'path',
title: 'File Path',
type: 'short-input',
placeholder: '/folder/document.pdf',
condition: { field: 'operation', value: 'dropbox_download' },
required: true,
},
// List folder operation inputs
{
id: 'path',
title: 'Folder Path',
type: 'short-input',
placeholder: '/ (root) or /folder',
condition: { field: 'operation', value: 'dropbox_list_folder' },
required: true,
},
{
id: 'recursive',
title: 'List Recursively',
type: 'switch',
condition: { field: 'operation', value: 'dropbox_list_folder' },
},
{
id: 'limit',
title: 'Maximum Results',
type: 'short-input',
placeholder: '500',
condition: { field: 'operation', value: 'dropbox_list_folder' },
},
// Create folder operation inputs
{
id: 'path',
title: 'Folder Path',
type: 'short-input',
placeholder: '/new-folder',
condition: { field: 'operation', value: 'dropbox_create_folder' },
required: true,
},
{
id: 'autorename',
title: 'Auto-rename on Conflict',
type: 'switch',
condition: { field: 'operation', value: 'dropbox_create_folder' },
},
// Delete operation inputs
{
id: 'path',
title: 'Path to Delete',
type: 'short-input',
placeholder: '/folder/file.txt',
condition: { field: 'operation', value: 'dropbox_delete' },
required: true,
},
// Copy operation inputs
{
id: 'fromPath',
title: 'Source Path',
type: 'short-input',
placeholder: '/source/document.pdf',
condition: { field: 'operation', value: 'dropbox_copy' },
required: true,
},
{
id: 'toPath',
title: 'Destination Path',
type: 'short-input',
placeholder: '/destination/document.pdf',
condition: { field: 'operation', value: 'dropbox_copy' },
required: true,
},
{
id: 'autorename',
title: 'Auto-rename on Conflict',
type: 'switch',
condition: { field: 'operation', value: 'dropbox_copy' },
},
// Move operation inputs
{
id: 'fromPath',
title: 'Source Path',
type: 'short-input',
placeholder: '/old-location/document.pdf',
condition: { field: 'operation', value: 'dropbox_move' },
required: true,
},
{
id: 'toPath',
title: 'Destination Path',
type: 'short-input',
placeholder: '/new-location/document.pdf',
condition: { field: 'operation', value: 'dropbox_move' },
required: true,
},
{
id: 'autorename',
title: 'Auto-rename on Conflict',
type: 'switch',
condition: { field: 'operation', value: 'dropbox_move' },
},
// Get metadata operation inputs
{
id: 'path',
title: 'File/Folder Path',
type: 'short-input',
placeholder: '/folder/document.pdf',
condition: { field: 'operation', value: 'dropbox_get_metadata' },
required: true,
},
{
id: 'includeMediaInfo',
title: 'Include Media Info',
type: 'switch',
condition: { field: 'operation', value: 'dropbox_get_metadata' },
},
// Create shared link operation inputs
{
id: 'path',
title: 'File/Folder Path',
type: 'short-input',
placeholder: '/folder/document.pdf',
condition: { field: 'operation', value: 'dropbox_create_shared_link' },
required: true,
},
{
id: 'requestedVisibility',
title: 'Visibility',
type: 'dropdown',
options: [
{ label: 'Public (anyone with link)', id: 'public' },
{ label: 'Team Only', id: 'team_only' },
{ label: 'Password Protected', id: 'password' },
],
value: () => 'public',
condition: { field: 'operation', value: 'dropbox_create_shared_link' },
},
{
id: 'linkPassword',
title: 'Link Password',
type: 'short-input',
placeholder: 'Enter password for the link',
password: true,
condition: { field: 'operation', value: 'dropbox_create_shared_link' },
},
{
id: 'expires',
title: 'Expiration Date',
type: 'short-input',
placeholder: '2025-12-31T23:59:59Z',
condition: { field: 'operation', value: 'dropbox_create_shared_link' },
},
// Search operation inputs
{
id: 'query',
title: 'Search Query',
type: 'short-input',
placeholder: 'Enter search term...',
condition: { field: 'operation', value: 'dropbox_search' },
required: true,
},
{
id: 'path',
title: 'Search in Folder',
type: 'short-input',
placeholder: '/ (search all) or /folder',
condition: { field: 'operation', value: 'dropbox_search' },
},
{
id: 'fileExtensions',
title: 'File Extensions',
type: 'short-input',
placeholder: 'pdf,xlsx,docx (comma-separated)',
condition: { field: 'operation', value: 'dropbox_search' },
},
{
id: 'maxResults',
title: 'Maximum Results',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'dropbox_search' },
},
],
tools: {
access: [
'dropbox_upload',
'dropbox_download',
'dropbox_list_folder',
'dropbox_create_folder',
'dropbox_delete',
'dropbox_copy',
'dropbox_move',
'dropbox_get_metadata',
'dropbox_create_shared_link',
'dropbox_search',
],
config: {
tool: (params) => {
// Convert numeric params
if (params.limit) {
params.limit = Number(params.limit)
}
if (params.maxResults) {
params.maxResults = Number(params.maxResults)
}
switch (params.operation) {
case 'dropbox_upload':
return 'dropbox_upload'
case 'dropbox_download':
return 'dropbox_download'
case 'dropbox_list_folder':
return 'dropbox_list_folder'
case 'dropbox_create_folder':
return 'dropbox_create_folder'
case 'dropbox_delete':
return 'dropbox_delete'
case 'dropbox_copy':
return 'dropbox_copy'
case 'dropbox_move':
return 'dropbox_move'
case 'dropbox_get_metadata':
return 'dropbox_get_metadata'
case 'dropbox_create_shared_link':
return 'dropbox_create_shared_link'
case 'dropbox_search':
return 'dropbox_search'
default:
return 'dropbox_upload'
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Dropbox OAuth credential' },
// Common inputs
path: { type: 'string', description: 'Path in Dropbox' },
autorename: { type: 'boolean', description: 'Auto-rename on conflict' },
// Upload inputs
fileContent: { type: 'string', description: 'Base64 encoded file content' },
fileName: { type: 'string', description: 'Optional filename' },
mode: { type: 'string', description: 'Write mode: add or overwrite' },
mute: { type: 'boolean', description: 'Mute notifications' },
// List folder inputs
recursive: { type: 'boolean', description: 'List recursively' },
includeDeleted: { type: 'boolean', description: 'Include deleted files' },
includeMediaInfo: { type: 'boolean', description: 'Include media info' },
limit: { type: 'number', description: 'Maximum results' },
// Copy/Move inputs
fromPath: { type: 'string', description: 'Source path' },
toPath: { type: 'string', description: 'Destination path' },
// Shared link inputs
requestedVisibility: { type: 'string', description: 'Link visibility' },
linkPassword: { type: 'string', description: 'Password for the link' },
expires: { type: 'string', description: 'Expiration date (ISO 8601)' },
// Search inputs
query: { type: 'string', description: 'Search query' },
fileExtensions: { type: 'string', description: 'File extensions filter' },
maxResults: { type: 'number', description: 'Maximum search results' },
},
outputs: {
// Upload/Download outputs
file: { type: 'json', description: 'File metadata' },
content: { type: 'string', description: 'File content (base64)' },
temporaryLink: { type: 'string', description: 'Temporary download link' },
// List folder outputs
entries: { type: 'json', description: 'List of files and folders' },
cursor: { type: 'string', description: 'Pagination cursor' },
hasMore: { type: 'boolean', description: 'Whether more results exist' },
// Create folder output
folder: { type: 'json', description: 'Created folder metadata' },
// Delete output
deleted: { type: 'boolean', description: 'Whether deletion was successful' },
// Copy/Move/Get metadata output
metadata: { type: 'json', description: 'Item metadata' },
// Shared link output
sharedLink: { type: 'json', description: 'Shared link details' },
// Search outputs
matches: { type: 'json', description: 'Search results' },
},
}

View File

@@ -0,0 +1,435 @@
import { ElasticsearchIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { ElasticsearchResponse } from '@/tools/elasticsearch/types'
export const ElasticsearchBlock: BlockConfig<ElasticsearchResponse> = {
type: 'elasticsearch',
name: 'Elasticsearch',
description: 'Search, index, and manage data in Elasticsearch',
authMode: AuthMode.ApiKey,
longDescription:
'Integrate Elasticsearch into workflows for powerful search, indexing, and data management. Supports document CRUD operations, advanced search queries, bulk operations, index management, and cluster monitoring. Works with both self-hosted and Elastic Cloud deployments.',
docsLink: 'https://docs.sim.ai/tools/elasticsearch',
category: 'tools',
bgColor: '#E0E0E0',
icon: ElasticsearchIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Document Operations
{ label: 'Search', id: 'elasticsearch_search' },
{ label: 'Index Document', id: 'elasticsearch_index_document' },
{ label: 'Get Document', id: 'elasticsearch_get_document' },
{ label: 'Update Document', id: 'elasticsearch_update_document' },
{ label: 'Delete Document', id: 'elasticsearch_delete_document' },
{ label: 'Bulk Operations', id: 'elasticsearch_bulk' },
{ label: 'Count Documents', id: 'elasticsearch_count' },
// Index Management
{ label: 'Create Index', id: 'elasticsearch_create_index' },
{ label: 'Delete Index', id: 'elasticsearch_delete_index' },
{ label: 'Get Index Info', id: 'elasticsearch_get_index' },
// Cluster Operations
{ label: 'Cluster Health', id: 'elasticsearch_cluster_health' },
{ label: 'Cluster Stats', id: 'elasticsearch_cluster_stats' },
],
value: () => 'elasticsearch_search',
},
// Deployment type
{
id: 'deploymentType',
title: 'Deployment Type',
type: 'dropdown',
options: [
{ label: 'Self-Hosted', id: 'self_hosted' },
{ label: 'Elastic Cloud', id: 'cloud' },
],
value: () => 'self_hosted',
},
// Self-hosted host
{
id: 'host',
title: 'Elasticsearch Host',
type: 'short-input',
placeholder: 'https://localhost:9200',
required: true,
condition: { field: 'deploymentType', value: 'self_hosted' },
},
// Cloud ID
{
id: 'cloudId',
title: 'Cloud ID',
type: 'short-input',
placeholder: 'deployment-name:base64-encoded-data',
required: true,
condition: { field: 'deploymentType', value: 'cloud' },
},
// Authentication method
{
id: 'authMethod',
title: 'Authentication Method',
type: 'dropdown',
options: [
{ label: 'API Key', id: 'api_key' },
{ label: 'Basic Auth', id: 'basic_auth' },
],
value: () => 'api_key',
},
// API Key
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter encoded API key',
password: true,
required: true,
condition: { field: 'authMethod', value: 'api_key' },
},
// Username
{
id: 'username',
title: 'Username',
type: 'short-input',
placeholder: 'Enter username',
required: true,
condition: { field: 'authMethod', value: 'basic_auth' },
},
// Password
{
id: 'password',
title: 'Password',
type: 'short-input',
placeholder: 'Enter password',
password: true,
required: true,
condition: { field: 'authMethod', value: 'basic_auth' },
},
// Index name - for most operations
{
id: 'index',
title: 'Index Name',
type: 'short-input',
placeholder: 'my-index',
required: true,
condition: {
field: 'operation',
value: [
'elasticsearch_search',
'elasticsearch_index_document',
'elasticsearch_get_document',
'elasticsearch_update_document',
'elasticsearch_delete_document',
'elasticsearch_bulk',
'elasticsearch_count',
'elasticsearch_create_index',
'elasticsearch_delete_index',
'elasticsearch_get_index',
],
},
},
// Document ID - for get/update/delete
{
id: 'documentId',
title: 'Document ID',
type: 'short-input',
placeholder: 'unique-document-id',
required: true,
condition: {
field: 'operation',
value: [
'elasticsearch_get_document',
'elasticsearch_update_document',
'elasticsearch_delete_document',
],
},
},
// Optional Document ID - for index document
{
id: 'documentId',
title: 'Document ID',
type: 'short-input',
placeholder: 'Leave empty for auto-generated ID',
condition: { field: 'operation', value: 'elasticsearch_index_document' },
},
// Document body - for index
{
id: 'document',
title: 'Document',
type: 'code',
placeholder: '{ "field": "value", "another_field": 123 }',
required: true,
condition: { field: 'operation', value: 'elasticsearch_index_document' },
},
// Document body - for update (partial)
{
id: 'document',
title: 'Partial Document',
type: 'code',
placeholder: '{ "field_to_update": "new_value" }',
required: true,
condition: { field: 'operation', value: 'elasticsearch_update_document' },
},
// Search query
{
id: 'query',
title: 'Search Query',
type: 'code',
placeholder: '{ "match": { "field": "search term" } }',
condition: { field: 'operation', value: 'elasticsearch_search' },
},
// Count query
{
id: 'query',
title: 'Query',
type: 'code',
placeholder: '{ "match": { "field": "value" } }',
condition: { field: 'operation', value: 'elasticsearch_count' },
},
// Search size
{
id: 'size',
title: 'Number of Results',
type: 'short-input',
placeholder: '10',
condition: { field: 'operation', value: 'elasticsearch_search' },
},
// Search from (offset)
{
id: 'from',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'elasticsearch_search' },
},
// Sort
{
id: 'sort',
title: 'Sort',
type: 'code',
placeholder: '[{ "field": { "order": "asc" } }]',
condition: { field: 'operation', value: 'elasticsearch_search' },
},
// Source includes
{
id: 'sourceIncludes',
title: 'Fields to Include',
type: 'short-input',
placeholder: 'field1, field2 (comma-separated)',
condition: {
field: 'operation',
value: ['elasticsearch_search', 'elasticsearch_get_document'],
},
},
// Source excludes
{
id: 'sourceExcludes',
title: 'Fields to Exclude',
type: 'short-input',
placeholder: 'field1, field2 (comma-separated)',
condition: {
field: 'operation',
value: ['elasticsearch_search', 'elasticsearch_get_document'],
},
},
// Bulk operations
{
id: 'operations',
title: 'Bulk Operations',
type: 'code',
placeholder:
'{ "index": { "_index": "my-index", "_id": "1" } }\n{ "field": "value" }\n{ "delete": { "_index": "my-index", "_id": "2" } }',
required: true,
condition: { field: 'operation', value: 'elasticsearch_bulk' },
},
// Index settings
{
id: 'settings',
title: 'Index Settings',
type: 'code',
placeholder: '{ "number_of_shards": 1, "number_of_replicas": 1 }',
condition: { field: 'operation', value: 'elasticsearch_create_index' },
},
// Index mappings
{
id: 'mappings',
title: 'Index Mappings',
type: 'code',
placeholder: '{ "properties": { "field": { "type": "text" } } }',
condition: { field: 'operation', value: 'elasticsearch_create_index' },
},
// Refresh option
{
id: 'refresh',
title: 'Refresh',
type: 'dropdown',
options: [
{ label: 'Default', id: '' },
{ label: 'Wait For', id: 'wait_for' },
{ label: 'Immediate', id: 'true' },
{ label: 'None', id: 'false' },
],
value: () => '',
condition: {
field: 'operation',
value: [
'elasticsearch_index_document',
'elasticsearch_delete_document',
'elasticsearch_bulk',
],
},
},
// Cluster health wait for status
{
id: 'waitForStatus',
title: 'Wait for Status',
type: 'dropdown',
options: [
{ label: 'None', id: '' },
{ label: 'Green', id: 'green' },
{ label: 'Yellow', id: 'yellow' },
{ label: 'Red', id: 'red' },
],
value: () => '',
condition: { field: 'operation', value: 'elasticsearch_cluster_health' },
},
// Cluster health timeout
{
id: 'timeout',
title: 'Timeout (seconds)',
type: 'short-input',
placeholder: '30',
condition: { field: 'operation', value: 'elasticsearch_cluster_health' },
},
// Retry on conflict
{
id: 'retryOnConflict',
title: 'Retry on Conflict',
type: 'short-input',
placeholder: '3',
condition: { field: 'operation', value: 'elasticsearch_update_document' },
},
],
tools: {
access: [
'elasticsearch_search',
'elasticsearch_index_document',
'elasticsearch_get_document',
'elasticsearch_update_document',
'elasticsearch_delete_document',
'elasticsearch_bulk',
'elasticsearch_count',
'elasticsearch_create_index',
'elasticsearch_delete_index',
'elasticsearch_get_index',
'elasticsearch_cluster_health',
'elasticsearch_cluster_stats',
],
config: {
tool: (params) => {
// Convert numeric strings to numbers
if (params.size) {
params.size = Number(params.size)
}
if (params.from) {
params.from = Number(params.from)
}
if (params.retryOnConflict) {
params.retryOnConflict = Number(params.retryOnConflict)
}
// Append 's' to timeout for Elasticsearch time format
if (params.timeout && !params.timeout.endsWith('s')) {
params.timeout = `${params.timeout}s`
}
// Return the operation as the tool ID
return params.operation || 'elasticsearch_search'
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
deploymentType: { type: 'string', description: 'self_hosted or cloud' },
host: { type: 'string', description: 'Elasticsearch host URL' },
cloudId: { type: 'string', description: 'Elastic Cloud ID' },
authMethod: { type: 'string', description: 'api_key or basic_auth' },
apiKey: { type: 'string', description: 'API key for authentication' },
username: { type: 'string', description: 'Username for basic auth' },
password: { type: 'string', description: 'Password for basic auth' },
index: { type: 'string', description: 'Index name' },
documentId: { type: 'string', description: 'Document ID' },
document: { type: 'string', description: 'Document body as JSON' },
query: { type: 'string', description: 'Search query as JSON' },
size: { type: 'number', description: 'Number of results' },
from: { type: 'number', description: 'Starting offset' },
sort: { type: 'string', description: 'Sort specification as JSON' },
sourceIncludes: { type: 'string', description: 'Fields to include' },
sourceExcludes: { type: 'string', description: 'Fields to exclude' },
operations: { type: 'string', description: 'Bulk operations as NDJSON' },
settings: { type: 'string', description: 'Index settings as JSON' },
mappings: { type: 'string', description: 'Index mappings as JSON' },
refresh: { type: 'string', description: 'Refresh policy' },
waitForStatus: { type: 'string', description: 'Wait for cluster status' },
timeout: { type: 'string', description: 'Timeout for wait operations' },
retryOnConflict: { type: 'number', description: 'Retry attempts on conflict' },
},
outputs: {
// Search outputs
hits: { type: 'json', description: 'Search results' },
took: { type: 'number', description: 'Time taken in milliseconds' },
timed_out: { type: 'boolean', description: 'Whether the operation timed out' },
aggregations: { type: 'json', description: 'Aggregation results' },
// Document outputs
_index: { type: 'string', description: 'Index name' },
_id: { type: 'string', description: 'Document ID' },
_version: { type: 'number', description: 'Document version' },
_source: { type: 'json', description: 'Document content' },
result: { type: 'string', description: 'Operation result' },
found: { type: 'boolean', description: 'Whether document was found' },
// Bulk outputs
errors: { type: 'boolean', description: 'Whether any errors occurred' },
items: { type: 'json', description: 'Bulk operation results' },
// Count outputs
count: { type: 'number', description: 'Document count' },
// Index outputs
acknowledged: { type: 'boolean', description: 'Whether operation was acknowledged' },
// Cluster outputs
cluster_name: { type: 'string', description: 'Cluster name' },
status: { type: 'string', description: 'Cluster health status' },
number_of_nodes: { type: 'number', description: 'Number of nodes' },
indices: { type: 'json', description: 'Index statistics' },
nodes: { type: 'json', description: 'Node statistics' },
},
}

View File

@@ -0,0 +1,697 @@
import { GitLabIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { GitLabResponse } from '@/tools/gitlab/types'
export const GitLabBlock: BlockConfig<GitLabResponse> = {
type: 'gitlab',
name: 'GitLab',
description: 'Interact with GitLab projects, issues, merge requests, and pipelines',
authMode: AuthMode.ApiKey,
triggerAllowed: false,
longDescription:
'Integrate GitLab into the workflow. Can manage projects, issues, merge requests, pipelines, and add comments. Supports all core GitLab DevOps operations.',
docsLink: 'https://docs.sim.ai/tools/gitlab',
category: 'tools',
icon: GitLabIcon,
bgColor: '#E0E0E0',
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Project Operations
{ label: 'List Projects', id: 'gitlab_list_projects' },
{ label: 'Get Project', id: 'gitlab_get_project' },
// Issue Operations
{ label: 'List Issues', id: 'gitlab_list_issues' },
{ label: 'Get Issue', id: 'gitlab_get_issue' },
{ label: 'Create Issue', id: 'gitlab_create_issue' },
{ label: 'Update Issue', id: 'gitlab_update_issue' },
{ label: 'Delete Issue', id: 'gitlab_delete_issue' },
{ label: 'Add Issue Comment', id: 'gitlab_create_issue_note' },
// Merge Request Operations
{ label: 'List Merge Requests', id: 'gitlab_list_merge_requests' },
{ label: 'Get Merge Request', id: 'gitlab_get_merge_request' },
{ label: 'Create Merge Request', id: 'gitlab_create_merge_request' },
{ label: 'Update Merge Request', id: 'gitlab_update_merge_request' },
{ label: 'Merge Merge Request', id: 'gitlab_merge_merge_request' },
{ label: 'Add MR Comment', id: 'gitlab_create_merge_request_note' },
// Pipeline Operations
{ label: 'List Pipelines', id: 'gitlab_list_pipelines' },
{ label: 'Get Pipeline', id: 'gitlab_get_pipeline' },
{ label: 'Create Pipeline', id: 'gitlab_create_pipeline' },
{ label: 'Retry Pipeline', id: 'gitlab_retry_pipeline' },
{ label: 'Cancel Pipeline', id: 'gitlab_cancel_pipeline' },
],
value: () => 'gitlab_list_projects',
},
{
id: 'accessToken',
title: 'Personal Access Token',
type: 'short-input',
placeholder: 'Enter your GitLab Personal Access Token',
password: true,
required: true,
},
// Project ID (required for most operations)
{
id: 'projectId',
title: 'Project ID',
type: 'short-input',
placeholder: 'Enter project ID or path (e.g., username/project)',
required: true,
condition: {
field: 'operation',
value: [
'gitlab_get_project',
'gitlab_list_issues',
'gitlab_get_issue',
'gitlab_create_issue',
'gitlab_update_issue',
'gitlab_delete_issue',
'gitlab_create_issue_note',
'gitlab_list_merge_requests',
'gitlab_get_merge_request',
'gitlab_create_merge_request',
'gitlab_update_merge_request',
'gitlab_merge_merge_request',
'gitlab_create_merge_request_note',
'gitlab_list_pipelines',
'gitlab_get_pipeline',
'gitlab_create_pipeline',
'gitlab_retry_pipeline',
'gitlab_cancel_pipeline',
],
},
},
// Issue Number (IID) - the # shown in GitLab UI
{
id: 'issueIid',
title: 'Issue Number',
type: 'short-input',
placeholder: 'Enter issue number (e.g., 1 for issue #1)',
required: true,
condition: {
field: 'operation',
value: [
'gitlab_get_issue',
'gitlab_update_issue',
'gitlab_delete_issue',
'gitlab_create_issue_note',
],
},
},
// Merge Request Number (IID) - the ! number shown in GitLab UI
{
id: 'mergeRequestIid',
title: 'MR Number',
type: 'short-input',
placeholder: 'Enter MR number (e.g., 1 for !1)',
required: true,
condition: {
field: 'operation',
value: [
'gitlab_get_merge_request',
'gitlab_update_merge_request',
'gitlab_merge_merge_request',
'gitlab_create_merge_request_note',
],
},
},
// Pipeline ID
{
id: 'pipelineId',
title: 'Pipeline ID',
type: 'short-input',
placeholder: 'Enter pipeline ID',
required: true,
condition: {
field: 'operation',
value: ['gitlab_get_pipeline', 'gitlab_retry_pipeline', 'gitlab_cancel_pipeline'],
},
},
// Title (for issue/MR creation)
{
id: 'title',
title: 'Title',
type: 'short-input',
placeholder: 'Enter title',
required: true,
condition: {
field: 'operation',
value: ['gitlab_create_issue', 'gitlab_create_merge_request'],
},
},
// Description
{
id: 'description',
title: 'Description',
type: 'long-input',
placeholder: 'Enter description (Markdown supported)',
condition: {
field: 'operation',
value: [
'gitlab_create_issue',
'gitlab_update_issue',
'gitlab_create_merge_request',
'gitlab_update_merge_request',
],
},
},
// Comment body
{
id: 'body',
title: 'Comment',
type: 'long-input',
placeholder: 'Enter comment text (Markdown supported)',
required: true,
condition: {
field: 'operation',
value: ['gitlab_create_issue_note', 'gitlab_create_merge_request_note'],
},
},
// Source branch (for MR creation)
{
id: 'sourceBranch',
title: 'Source Branch',
type: 'short-input',
placeholder: 'Enter source branch name',
required: true,
condition: {
field: 'operation',
value: ['gitlab_create_merge_request'],
},
},
// Target branch (for MR creation)
{
id: 'targetBranch',
title: 'Target Branch',
type: 'short-input',
placeholder: 'Enter target branch name (e.g., main)',
required: true,
condition: {
field: 'operation',
value: ['gitlab_create_merge_request'],
},
},
// Ref (for pipeline creation)
{
id: 'ref',
title: 'Branch/Tag',
type: 'short-input',
placeholder: 'Enter branch or tag name',
required: true,
condition: {
field: 'operation',
value: ['gitlab_create_pipeline'],
},
},
// Labels
{
id: 'labels',
title: 'Labels',
type: 'short-input',
placeholder: 'Enter labels (comma-separated)',
condition: {
field: 'operation',
value: [
'gitlab_create_issue',
'gitlab_update_issue',
'gitlab_list_issues',
'gitlab_create_merge_request',
'gitlab_update_merge_request',
'gitlab_list_merge_requests',
],
},
},
// Assignee IDs
{
id: 'assigneeIds',
title: 'Assignee IDs',
type: 'short-input',
placeholder: 'Enter assignee user IDs (comma-separated)',
condition: {
field: 'operation',
value: [
'gitlab_create_issue',
'gitlab_update_issue',
'gitlab_create_merge_request',
'gitlab_update_merge_request',
],
},
},
// Milestone ID
{
id: 'milestoneId',
title: 'Milestone ID',
type: 'short-input',
placeholder: 'Enter milestone ID',
condition: {
field: 'operation',
value: ['gitlab_create_issue', 'gitlab_update_issue'],
},
},
// State filter for issues
{
id: 'issueState',
title: 'State',
type: 'dropdown',
options: [
{ label: 'All', id: 'all' },
{ label: 'Open', id: 'opened' },
{ label: 'Closed', id: 'closed' },
],
value: () => 'all',
condition: {
field: 'operation',
value: ['gitlab_list_issues'],
},
},
// State filter for merge requests
{
id: 'mrState',
title: 'State',
type: 'dropdown',
options: [
{ label: 'All', id: 'all' },
{ label: 'Open', id: 'opened' },
{ label: 'Closed', id: 'closed' },
{ label: 'Merged', id: 'merged' },
],
value: () => 'all',
condition: {
field: 'operation',
value: ['gitlab_list_merge_requests'],
},
},
// State event (for updates)
{
id: 'stateEvent',
title: 'State Event',
type: 'dropdown',
options: [
{ label: 'No Change', id: '' },
{ label: 'Close', id: 'close' },
{ label: 'Reopen', id: 'reopen' },
],
value: () => '',
condition: {
field: 'operation',
value: ['gitlab_update_issue', 'gitlab_update_merge_request'],
},
},
// Pipeline status filter
{
id: 'pipelineStatus',
title: 'Pipeline Status',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Running', id: 'running' },
{ label: 'Pending', id: 'pending' },
{ label: 'Success', id: 'success' },
{ label: 'Failed', id: 'failed' },
{ label: 'Canceled', id: 'canceled' },
{ label: 'Skipped', id: 'skipped' },
],
value: () => '',
condition: {
field: 'operation',
value: ['gitlab_list_pipelines'],
},
},
// Remove source branch after merge
{
id: 'removeSourceBranch',
title: 'Remove Source Branch',
type: 'switch',
condition: {
field: 'operation',
value: ['gitlab_create_merge_request', 'gitlab_merge_merge_request'],
},
},
// Squash commits
{
id: 'squash',
title: 'Squash Commits',
type: 'switch',
condition: {
field: 'operation',
value: ['gitlab_merge_merge_request'],
},
},
// Merge commit message
{
id: 'mergeCommitMessage',
title: 'Merge Commit Message',
type: 'long-input',
placeholder: 'Enter custom merge commit message (optional)',
condition: {
field: 'operation',
value: ['gitlab_merge_merge_request'],
},
},
// Per page (pagination)
{
id: 'perPage',
title: 'Results Per Page',
type: 'short-input',
placeholder: 'Number of results per page (default: 20, max: 100)',
condition: {
field: 'operation',
value: [
'gitlab_list_projects',
'gitlab_list_issues',
'gitlab_list_merge_requests',
'gitlab_list_pipelines',
],
},
},
// Page number
{
id: 'page',
title: 'Page Number',
type: 'short-input',
placeholder: 'Page number (default: 1)',
condition: {
field: 'operation',
value: [
'gitlab_list_projects',
'gitlab_list_issues',
'gitlab_list_merge_requests',
'gitlab_list_pipelines',
],
},
},
],
tools: {
access: [
'gitlab_list_projects',
'gitlab_get_project',
'gitlab_list_issues',
'gitlab_get_issue',
'gitlab_create_issue',
'gitlab_update_issue',
'gitlab_delete_issue',
'gitlab_create_issue_note',
'gitlab_list_merge_requests',
'gitlab_get_merge_request',
'gitlab_create_merge_request',
'gitlab_update_merge_request',
'gitlab_merge_merge_request',
'gitlab_create_merge_request_note',
'gitlab_list_pipelines',
'gitlab_get_pipeline',
'gitlab_create_pipeline',
'gitlab_retry_pipeline',
'gitlab_cancel_pipeline',
],
config: {
tool: (params) => {
return params.operation || 'gitlab_list_projects'
},
params: (params) => {
const baseParams: Record<string, any> = {
accessToken: params.accessToken,
}
switch (params.operation) {
case 'gitlab_list_projects':
return {
...baseParams,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
}
case 'gitlab_get_project':
if (!params.projectId?.trim()) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
}
case 'gitlab_list_issues':
if (!params.projectId?.trim()) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
state: params.issueState !== 'all' ? params.issueState : undefined,
labels: params.labels?.trim() || undefined,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
}
case 'gitlab_get_issue':
if (!params.projectId?.trim() || !params.issueIid) {
throw new Error('Project ID and Issue Number are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
issueIid: Number(params.issueIid),
}
case 'gitlab_create_issue':
if (!params.projectId?.trim() || !params.title?.trim()) {
throw new Error('Project ID and title are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
title: params.title.trim(),
description: params.description?.trim() || undefined,
labels: params.labels?.trim() || undefined,
assigneeIds: params.assigneeIds
? params.assigneeIds.split(',').map((id: string) => Number(id.trim()))
: undefined,
milestoneId: params.milestoneId ? Number(params.milestoneId) : undefined,
}
case 'gitlab_update_issue':
if (!params.projectId?.trim() || !params.issueIid) {
throw new Error('Project ID and Issue IID are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
issueIid: Number(params.issueIid),
title: params.title?.trim() || undefined,
description: params.description?.trim() || undefined,
labels: params.labels?.trim() || undefined,
assigneeIds: params.assigneeIds
? params.assigneeIds.split(',').map((id: string) => Number(id.trim()))
: undefined,
stateEvent: params.stateEvent || undefined,
}
case 'gitlab_delete_issue':
if (!params.projectId?.trim() || !params.issueIid) {
throw new Error('Project ID and Issue IID are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
issueIid: Number(params.issueIid),
}
case 'gitlab_create_issue_note':
if (!params.projectId?.trim() || !params.issueIid || !params.body?.trim()) {
throw new Error('Project ID, Issue IID, and comment body are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
issueIid: Number(params.issueIid),
body: params.body.trim(),
}
case 'gitlab_list_merge_requests':
if (!params.projectId?.trim()) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
state: params.mrState !== 'all' ? params.mrState : undefined,
labels: params.labels?.trim() || undefined,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
}
case 'gitlab_get_merge_request':
if (!params.projectId?.trim() || !params.mergeRequestIid) {
throw new Error('Project ID and Merge Request IID are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
mergeRequestIid: Number(params.mergeRequestIid),
}
case 'gitlab_create_merge_request':
if (
!params.projectId?.trim() ||
!params.title?.trim() ||
!params.sourceBranch?.trim() ||
!params.targetBranch?.trim()
) {
throw new Error('Project ID, title, source branch, and target branch are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
title: params.title.trim(),
sourceBranch: params.sourceBranch.trim(),
targetBranch: params.targetBranch.trim(),
description: params.description?.trim() || undefined,
labels: params.labels?.trim() || undefined,
assigneeIds: params.assigneeIds
? params.assigneeIds.split(',').map((id: string) => Number(id.trim()))
: undefined,
removeSourceBranch: params.removeSourceBranch || undefined,
}
case 'gitlab_update_merge_request':
if (!params.projectId?.trim() || !params.mergeRequestIid) {
throw new Error('Project ID and Merge Request IID are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
mergeRequestIid: Number(params.mergeRequestIid),
title: params.title?.trim() || undefined,
description: params.description?.trim() || undefined,
labels: params.labels?.trim() || undefined,
assigneeIds: params.assigneeIds
? params.assigneeIds.split(',').map((id: string) => Number(id.trim()))
: undefined,
stateEvent: params.stateEvent || undefined,
}
case 'gitlab_merge_merge_request':
if (!params.projectId?.trim() || !params.mergeRequestIid) {
throw new Error('Project ID and Merge Request IID are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
mergeRequestIid: Number(params.mergeRequestIid),
mergeCommitMessage: params.mergeCommitMessage?.trim() || undefined,
squash: params.squash || undefined,
shouldRemoveSourceBranch: params.removeSourceBranch || undefined,
}
case 'gitlab_create_merge_request_note':
if (!params.projectId?.trim() || !params.mergeRequestIid || !params.body?.trim()) {
throw new Error('Project ID, Merge Request IID, and comment body are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
mergeRequestIid: Number(params.mergeRequestIid),
body: params.body.trim(),
}
case 'gitlab_list_pipelines':
if (!params.projectId?.trim()) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
status: params.pipelineStatus || undefined,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
}
case 'gitlab_get_pipeline':
if (!params.projectId?.trim() || !params.pipelineId) {
throw new Error('Project ID and Pipeline ID are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
pipelineId: Number(params.pipelineId),
}
case 'gitlab_create_pipeline':
if (!params.projectId?.trim() || !params.ref?.trim()) {
throw new Error('Project ID and branch/tag ref are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
ref: params.ref.trim(),
}
case 'gitlab_retry_pipeline':
case 'gitlab_cancel_pipeline':
if (!params.projectId?.trim() || !params.pipelineId) {
throw new Error('Project ID and Pipeline ID are required.')
}
return {
...baseParams,
projectId: params.projectId.trim(),
pipelineId: Number(params.pipelineId),
}
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'GitLab access token' },
projectId: { type: 'string', description: 'Project ID or URL-encoded path' },
issueIid: { type: 'number', description: 'Issue internal ID' },
mergeRequestIid: { type: 'number', description: 'Merge request internal ID' },
pipelineId: { type: 'number', description: 'Pipeline ID' },
title: { type: 'string', description: 'Title for issue or merge request' },
description: { type: 'string', description: 'Description (Markdown supported)' },
body: { type: 'string', description: 'Comment body' },
sourceBranch: { type: 'string', description: 'Source branch for merge request' },
targetBranch: { type: 'string', description: 'Target branch for merge request' },
ref: { type: 'string', description: 'Branch or tag reference for pipeline' },
labels: { type: 'string', description: 'Labels (comma-separated)' },
assigneeIds: { type: 'string', description: 'Assignee user IDs (comma-separated)' },
milestoneId: { type: 'number', description: 'Milestone ID' },
issueState: { type: 'string', description: 'Issue state filter (opened, closed, all)' },
mrState: {
type: 'string',
description: 'Merge request state filter (opened, closed, merged, all)',
},
stateEvent: { type: 'string', description: 'State event (close, reopen)' },
pipelineStatus: { type: 'string', description: 'Pipeline status filter' },
removeSourceBranch: { type: 'boolean', description: 'Remove source branch after merge' },
squash: { type: 'boolean', description: 'Squash commits on merge' },
mergeCommitMessage: { type: 'string', description: 'Custom merge commit message' },
perPage: { type: 'number', description: 'Results per page' },
page: { type: 'number', description: 'Page number' },
},
outputs: {
// Project outputs
projects: { type: 'json', description: 'List of projects' },
project: { type: 'json', description: 'Project details' },
// Issue outputs
issues: { type: 'json', description: 'List of issues' },
issue: { type: 'json', description: 'Issue details' },
// Merge request outputs
mergeRequests: { type: 'json', description: 'List of merge requests' },
mergeRequest: { type: 'json', description: 'Merge request details' },
// Pipeline outputs
pipelines: { type: 'json', description: 'List of pipelines' },
pipeline: { type: 'json', description: 'Pipeline details' },
// Note outputs
note: { type: 'json', description: 'Comment/note details' },
// Success indicator
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,503 @@
import { GrafanaIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { GrafanaResponse } from '@/tools/grafana/types'
export const GrafanaBlock: BlockConfig<GrafanaResponse> = {
type: 'grafana',
name: 'Grafana',
description: 'Interact with Grafana dashboards, alerts, and annotations',
authMode: AuthMode.ApiKey,
longDescription:
'Integrate Grafana into workflows. Manage dashboards, alerts, annotations, data sources, folders, and monitor health status.',
docsLink: 'https://docs.sim.ai/tools/grafana',
category: 'tools',
bgColor: '#E0E0E0',
icon: GrafanaIcon,
subBlocks: [
// Operation dropdown
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Dashboards
{ label: 'List Dashboards', id: 'grafana_list_dashboards' },
{ label: 'Get Dashboard', id: 'grafana_get_dashboard' },
{ label: 'Create Dashboard', id: 'grafana_create_dashboard' },
{ label: 'Update Dashboard', id: 'grafana_update_dashboard' },
{ label: 'Delete Dashboard', id: 'grafana_delete_dashboard' },
// Alerts
{ label: 'List Alert Rules', id: 'grafana_list_alert_rules' },
{ label: 'Get Alert Rule', id: 'grafana_get_alert_rule' },
{ label: 'Create Alert Rule', id: 'grafana_create_alert_rule' },
{ label: 'Update Alert Rule', id: 'grafana_update_alert_rule' },
{ label: 'Delete Alert Rule', id: 'grafana_delete_alert_rule' },
{ label: 'List Contact Points', id: 'grafana_list_contact_points' },
// Annotations
{ label: 'Create Annotation', id: 'grafana_create_annotation' },
{ label: 'List Annotations', id: 'grafana_list_annotations' },
{ label: 'Update Annotation', id: 'grafana_update_annotation' },
{ label: 'Delete Annotation', id: 'grafana_delete_annotation' },
// Data Sources
{ label: 'List Data Sources', id: 'grafana_list_data_sources' },
{ label: 'Get Data Source', id: 'grafana_get_data_source' },
// Folders
{ label: 'List Folders', id: 'grafana_list_folders' },
{ label: 'Create Folder', id: 'grafana_create_folder' },
],
value: () => 'grafana_list_dashboards',
},
// Base Configuration (common to all operations)
{
id: 'baseUrl',
title: 'Grafana URL',
type: 'short-input',
placeholder: 'https://your-grafana.com',
required: true,
},
{
id: 'apiKey',
title: 'Service Account Token',
type: 'short-input',
placeholder: 'glsa_...',
password: true,
required: true,
},
{
id: 'organizationId',
title: 'Organization ID',
type: 'short-input',
placeholder: 'Optional - for multi-org instances',
},
// Data Source operations
{
id: 'dataSourceId',
title: 'Data Source ID',
type: 'short-input',
placeholder: 'Enter data source ID or UID',
required: true,
condition: {
field: 'operation',
value: 'grafana_get_data_source',
},
},
// Dashboard operations
{
id: 'dashboardUid',
title: 'Dashboard UID',
type: 'short-input',
placeholder: 'Enter dashboard UID',
required: true,
condition: {
field: 'operation',
value: ['grafana_get_dashboard', 'grafana_update_dashboard', 'grafana_delete_dashboard'],
},
},
{
id: 'query',
title: 'Search Query',
type: 'short-input',
placeholder: 'Filter dashboards by title',
condition: { field: 'operation', value: 'grafana_list_dashboards' },
},
{
id: 'tag',
title: 'Filter by Tag',
type: 'short-input',
placeholder: 'tag1, tag2 (comma-separated)',
condition: { field: 'operation', value: 'grafana_list_dashboards' },
},
// Create/Update Dashboard
{
id: 'title',
title: 'Dashboard Title',
type: 'short-input',
placeholder: 'Enter dashboard title',
required: true,
condition: { field: 'operation', value: 'grafana_create_dashboard' },
},
{
id: 'folderUid',
title: 'Folder UID',
type: 'short-input',
placeholder: 'Optional - folder to create dashboard in',
condition: {
field: 'operation',
value: [
'grafana_create_dashboard',
'grafana_update_dashboard',
'grafana_create_alert_rule',
],
},
},
{
id: 'tags',
title: 'Tags',
type: 'short-input',
placeholder: 'tag1, tag2 (comma-separated)',
condition: {
field: 'operation',
value: ['grafana_create_dashboard', 'grafana_update_dashboard'],
},
},
{
id: 'panels',
title: 'Panels (JSON)',
type: 'long-input',
placeholder: 'JSON array of panel configurations',
condition: {
field: 'operation',
value: ['grafana_create_dashboard', 'grafana_update_dashboard'],
},
},
{
id: 'message',
title: 'Commit Message',
type: 'short-input',
placeholder: 'Optional version message',
condition: {
field: 'operation',
value: ['grafana_create_dashboard', 'grafana_update_dashboard'],
},
},
// Alert Rule operations
{
id: 'alertRuleUid',
title: 'Alert Rule UID',
type: 'short-input',
placeholder: 'Enter alert rule UID',
required: true,
condition: {
field: 'operation',
value: ['grafana_get_alert_rule', 'grafana_update_alert_rule', 'grafana_delete_alert_rule'],
},
},
{
id: 'alertTitle',
title: 'Alert Title',
type: 'short-input',
placeholder: 'Enter alert rule name',
condition: {
field: 'operation',
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
},
},
{
id: 'folderUid',
title: 'Folder UID',
type: 'short-input',
placeholder: 'Folder UID for the alert rule',
condition: {
field: 'operation',
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
},
},
{
id: 'ruleGroup',
title: 'Rule Group',
type: 'short-input',
placeholder: 'Enter rule group name',
condition: {
field: 'operation',
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
},
},
{
id: 'condition',
title: 'Condition',
type: 'short-input',
placeholder: 'Condition refId (e.g., A)',
condition: {
field: 'operation',
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
},
},
{
id: 'data',
title: 'Query Data (JSON)',
type: 'long-input',
placeholder: 'JSON array of query/expression data objects',
condition: {
field: 'operation',
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
},
},
{
id: 'forDuration',
title: 'For Duration',
type: 'short-input',
placeholder: '5m (e.g., 5m, 1h)',
condition: {
field: 'operation',
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
},
},
{
id: 'noDataState',
title: 'No Data State',
type: 'dropdown',
options: [
{ label: 'No Data', id: 'NoData' },
{ label: 'Alerting', id: 'Alerting' },
{ label: 'OK', id: 'OK' },
],
value: () => 'NoData',
condition: {
field: 'operation',
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
},
},
{
id: 'execErrState',
title: 'Error State',
type: 'dropdown',
options: [
{ label: 'Alerting', id: 'Alerting' },
{ label: 'OK', id: 'OK' },
],
value: () => 'Alerting',
condition: {
field: 'operation',
value: ['grafana_create_alert_rule', 'grafana_update_alert_rule'],
},
},
// Annotation operations
{
id: 'text',
title: 'Annotation Text',
type: 'long-input',
placeholder: 'Enter annotation text...',
required: true,
condition: {
field: 'operation',
value: ['grafana_create_annotation', 'grafana_update_annotation'],
},
},
{
id: 'annotationTags',
title: 'Tags',
type: 'short-input',
placeholder: 'tag1, tag2 (comma-separated)',
condition: {
field: 'operation',
value: [
'grafana_create_annotation',
'grafana_update_annotation',
'grafana_list_annotations',
],
},
},
{
id: 'annotationDashboardUid',
title: 'Dashboard UID',
type: 'short-input',
placeholder: 'Optional - attach to specific dashboard',
condition: {
field: 'operation',
value: ['grafana_create_annotation', 'grafana_list_annotations'],
},
},
{
id: 'panelId',
title: 'Panel ID',
type: 'short-input',
placeholder: 'Optional - attach to specific panel',
condition: {
field: 'operation',
value: ['grafana_create_annotation', 'grafana_list_annotations'],
},
},
{
id: 'time',
title: 'Time (epoch ms)',
type: 'short-input',
placeholder: 'Optional - defaults to now',
condition: {
field: 'operation',
value: ['grafana_create_annotation', 'grafana_update_annotation'],
},
},
{
id: 'timeEnd',
title: 'End Time (epoch ms)',
type: 'short-input',
placeholder: 'Optional - for range annotations',
condition: {
field: 'operation',
value: ['grafana_create_annotation', 'grafana_update_annotation'],
},
},
{
id: 'annotationId',
title: 'Annotation ID',
type: 'short-input',
placeholder: 'Enter annotation ID',
required: true,
condition: {
field: 'operation',
value: ['grafana_update_annotation', 'grafana_delete_annotation'],
},
},
{
id: 'from',
title: 'From Time (epoch ms)',
type: 'short-input',
placeholder: 'Filter from time',
condition: { field: 'operation', value: 'grafana_list_annotations' },
},
{
id: 'to',
title: 'To Time (epoch ms)',
type: 'short-input',
placeholder: 'Filter to time',
condition: { field: 'operation', value: 'grafana_list_annotations' },
},
// Folder operations
{
id: 'folderTitle',
title: 'Folder Title',
type: 'short-input',
placeholder: 'Enter folder title',
required: true,
condition: { field: 'operation', value: 'grafana_create_folder' },
},
{
id: 'folderUidNew',
title: 'Folder UID',
type: 'short-input',
placeholder: 'Optional - auto-generated if not provided',
condition: { field: 'operation', value: 'grafana_create_folder' },
},
],
tools: {
access: [
'grafana_get_dashboard',
'grafana_list_dashboards',
'grafana_create_dashboard',
'grafana_update_dashboard',
'grafana_delete_dashboard',
'grafana_list_alert_rules',
'grafana_get_alert_rule',
'grafana_create_alert_rule',
'grafana_update_alert_rule',
'grafana_delete_alert_rule',
'grafana_list_contact_points',
'grafana_create_annotation',
'grafana_list_annotations',
'grafana_update_annotation',
'grafana_delete_annotation',
'grafana_list_data_sources',
'grafana_get_data_source',
'grafana_list_folders',
'grafana_create_folder',
],
config: {
tool: (params) => {
// Convert numeric string fields to numbers
if (params.panelId) {
params.panelId = Number(params.panelId)
}
if (params.annotationId) {
params.annotationId = Number(params.annotationId)
}
if (params.time) {
params.time = Number(params.time)
}
if (params.timeEnd) {
params.timeEnd = Number(params.timeEnd)
}
if (params.from) {
params.from = Number(params.from)
}
if (params.to) {
params.to = Number(params.to)
}
// Map subblock fields to tool parameter names
if (params.alertTitle) {
params.title = params.alertTitle
}
if (params.folderTitle) {
params.title = params.folderTitle
}
if (params.folderUidNew) {
params.uid = params.folderUidNew
}
if (params.annotationTags) {
params.tags = params.annotationTags
}
if (params.annotationDashboardUid) {
params.dashboardUid = params.annotationDashboardUid
}
return params.operation
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
baseUrl: { type: 'string', description: 'Grafana instance URL' },
apiKey: { type: 'string', description: 'Service Account Token' },
organizationId: { type: 'string', description: 'Organization ID (optional)' },
// Dashboard inputs
dashboardUid: { type: 'string', description: 'Dashboard UID' },
title: { type: 'string', description: 'Dashboard or folder title' },
folderUid: { type: 'string', description: 'Folder UID' },
tags: { type: 'string', description: 'Comma-separated tags' },
panels: { type: 'string', description: 'JSON array of panels' },
message: { type: 'string', description: 'Commit message' },
query: { type: 'string', description: 'Search query' },
tag: { type: 'string', description: 'Filter by tag' },
// Alert inputs
alertRuleUid: { type: 'string', description: 'Alert rule UID' },
alertTitle: { type: 'string', description: 'Alert rule title' },
ruleGroup: { type: 'string', description: 'Rule group name' },
condition: { type: 'string', description: 'Alert condition refId' },
data: { type: 'string', description: 'Query data JSON' },
forDuration: { type: 'string', description: 'Duration before firing' },
noDataState: { type: 'string', description: 'State on no data' },
execErrState: { type: 'string', description: 'State on error' },
// Annotation inputs
text: { type: 'string', description: 'Annotation text' },
annotationId: { type: 'number', description: 'Annotation ID' },
panelId: { type: 'number', description: 'Panel ID' },
time: { type: 'number', description: 'Start time (epoch ms)' },
timeEnd: { type: 'number', description: 'End time (epoch ms)' },
from: { type: 'number', description: 'Filter from time' },
to: { type: 'number', description: 'Filter to time' },
// Data source inputs
dataSourceId: { type: 'string', description: 'Data source ID or UID' },
},
outputs: {
// Health outputs
version: { type: 'string', description: 'Grafana version' },
database: { type: 'string', description: 'Database health status' },
status: { type: 'string', description: 'Health status' },
// Dashboard outputs
dashboard: { type: 'json', description: 'Dashboard JSON' },
meta: { type: 'json', description: 'Dashboard metadata' },
dashboards: { type: 'json', description: 'List of dashboards' },
uid: { type: 'string', description: 'Created/updated UID' },
url: { type: 'string', description: 'Dashboard URL' },
// Alert outputs
rules: { type: 'json', description: 'Alert rules list' },
contactPoints: { type: 'json', description: 'Contact points list' },
// Annotation outputs
annotations: { type: 'json', description: 'Annotations list' },
id: { type: 'number', description: 'Annotation ID' },
// Data source outputs
dataSources: { type: 'json', description: 'Data sources list' },
// Folder outputs
folders: { type: 'json', description: 'Folders list' },
// Common
message: { type: 'string', description: 'Status message' },
},
}

View File

@@ -0,0 +1,399 @@
import { KalshiIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
export const KalshiBlock: BlockConfig = {
type: 'kalshi',
name: 'Kalshi',
description: 'Access prediction markets data from Kalshi',
longDescription:
'Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, and exchange status.',
docsLink: 'https://docs.sim.ai/tools/kalshi',
authMode: AuthMode.ApiKey,
category: 'tools',
bgColor: '#09C285',
icon: KalshiIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Get Markets', id: 'get_markets' },
{ label: 'Get Market', id: 'get_market' },
{ label: 'Get Events', id: 'get_events' },
{ label: 'Get Event', id: 'get_event' },
{ label: 'Get Balance', id: 'get_balance' },
{ label: 'Get Positions', id: 'get_positions' },
{ label: 'Get Orders', id: 'get_orders' },
{ label: 'Get Orderbook', id: 'get_orderbook' },
{ label: 'Get Trades', id: 'get_trades' },
{ label: 'Get Candlesticks', id: 'get_candlesticks' },
{ label: 'Get Fills', id: 'get_fills' },
{ label: 'Get Series by Ticker', id: 'get_series_by_ticker' },
{ label: 'Get Exchange Status', id: 'get_exchange_status' },
],
value: () => 'get_markets',
},
// Auth fields (for authenticated operations)
{
id: 'keyId',
title: 'API Key ID',
type: 'short-input',
placeholder: 'Your Kalshi API Key ID',
condition: {
field: 'operation',
value: ['get_balance', 'get_positions', 'get_orders', 'get_fills'],
},
required: true,
},
{
id: 'privateKey',
title: 'Private Key',
type: 'long-input',
password: true,
placeholder: 'Your RSA Private Key (PEM format)',
condition: {
field: 'operation',
value: ['get_balance', 'get_positions', 'get_orders', 'get_fills'],
},
required: true,
},
// Get Markets fields
{
id: 'status',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Unopened', id: 'unopened' },
{ label: 'Open', id: 'open' },
{ label: 'Closed', id: 'closed' },
{ label: 'Settled', id: 'settled' },
],
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
},
{
id: 'seriesTicker',
title: 'Series Ticker',
type: 'short-input',
placeholder: 'Filter by series ticker',
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
},
{
id: 'eventTicker',
title: 'Event Ticker',
type: 'short-input',
placeholder: 'Event ticker',
required: {
field: 'operation',
value: ['get_event'],
},
condition: {
field: 'operation',
value: ['get_markets', 'get_event', 'get_positions', 'get_orders'],
},
},
// Get Market fields - ticker is REQUIRED for get_market (path param)
{
id: 'ticker',
title: 'Market Ticker',
type: 'short-input',
placeholder: 'Market ticker (e.g., KXBTC-24DEC31)',
required: true,
condition: { field: 'operation', value: ['get_market', 'get_orderbook'] },
},
// Ticker filter for get_orders and get_positions - OPTIONAL
{
id: 'tickerFilter',
title: 'Market Ticker',
type: 'short-input',
placeholder: 'Filter by market ticker (optional)',
condition: { field: 'operation', value: ['get_orders', 'get_positions'] },
},
// Nested markets option
{
id: 'withNestedMarkets',
title: 'Include Markets',
type: 'dropdown',
options: [
{ label: 'No', id: '' },
{ label: 'Yes', id: 'true' },
],
condition: { field: 'operation', value: ['get_events', 'get_event'] },
},
// Get Positions fields
{
id: 'settlementStatus',
title: 'Settlement Status',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Unsettled', id: 'unsettled' },
{ label: 'Settled', id: 'settled' },
],
condition: { field: 'operation', value: ['get_positions'] },
},
// Get Orders fields
{
id: 'orderStatus',
title: 'Order Status',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Resting', id: 'resting' },
{ label: 'Canceled', id: 'canceled' },
{ label: 'Executed', id: 'executed' },
],
condition: { field: 'operation', value: ['get_orders'] },
},
// Get Orderbook fields
{
id: 'depth',
title: 'Depth',
type: 'short-input',
placeholder: 'Number of price levels per side',
condition: { field: 'operation', value: ['get_orderbook'] },
},
// Get Trades fields
{
id: 'tickerTrades',
title: 'Market Ticker',
type: 'short-input',
placeholder: 'Filter by market ticker (optional)',
condition: { field: 'operation', value: ['get_trades'] },
},
{
id: 'minTs',
title: 'Min Timestamp',
type: 'short-input',
placeholder: 'Minimum timestamp (Unix milliseconds)',
condition: { field: 'operation', value: ['get_trades', 'get_fills'] },
},
{
id: 'maxTs',
title: 'Max Timestamp',
type: 'short-input',
placeholder: 'Maximum timestamp (Unix milliseconds)',
condition: { field: 'operation', value: ['get_trades', 'get_fills'] },
},
// Get Candlesticks fields
{
id: 'seriesTickerCandlesticks',
title: 'Series Ticker',
type: 'short-input',
placeholder: 'Series ticker',
required: true,
condition: { field: 'operation', value: ['get_candlesticks'] },
},
{
id: 'tickerCandlesticks',
title: 'Market Ticker',
type: 'short-input',
placeholder: 'Market ticker (e.g., KXBTC-24DEC31)',
required: true,
condition: { field: 'operation', value: ['get_candlesticks'] },
},
{
id: 'startTs',
title: 'Start Timestamp',
type: 'short-input',
placeholder: 'Start timestamp (Unix milliseconds)',
condition: { field: 'operation', value: ['get_candlesticks'] },
},
{
id: 'endTs',
title: 'End Timestamp',
type: 'short-input',
placeholder: 'End timestamp (Unix milliseconds)',
condition: { field: 'operation', value: ['get_candlesticks'] },
},
{
id: 'periodInterval',
title: 'Period Interval',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: '1 minute', id: '1' },
{ label: '1 hour', id: '60' },
{ label: '1 day', id: '1440' },
],
condition: { field: 'operation', value: ['get_candlesticks'] },
},
// Get Fills fields
{
id: 'tickerFills',
title: 'Market Ticker',
type: 'short-input',
placeholder: 'Filter by market ticker (optional)',
condition: { field: 'operation', value: ['get_fills'] },
},
{
id: 'orderId',
title: 'Order ID',
type: 'short-input',
placeholder: 'Filter by order ID (optional)',
condition: { field: 'operation', value: ['get_fills'] },
},
// Get Series by Ticker fields
{
id: 'seriesTickerGet',
title: 'Series Ticker',
type: 'short-input',
placeholder: 'Series ticker',
required: true,
condition: { field: 'operation', value: ['get_series_by_ticker'] },
},
// Pagination fields
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: 'Number of results (1-1000, default: 100)',
condition: {
field: 'operation',
value: [
'get_markets',
'get_events',
'get_positions',
'get_orders',
'get_trades',
'get_fills',
],
},
},
{
id: 'cursor',
title: 'Cursor',
type: 'short-input',
placeholder: 'Pagination cursor',
condition: {
field: 'operation',
value: [
'get_markets',
'get_events',
'get_positions',
'get_orders',
'get_trades',
'get_fills',
],
},
},
],
tools: {
access: [
'kalshi_get_markets',
'kalshi_get_market',
'kalshi_get_events',
'kalshi_get_event',
'kalshi_get_balance',
'kalshi_get_positions',
'kalshi_get_orders',
'kalshi_get_orderbook',
'kalshi_get_trades',
'kalshi_get_candlesticks',
'kalshi_get_fills',
'kalshi_get_series_by_ticker',
'kalshi_get_exchange_status',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'get_markets':
return 'kalshi_get_markets'
case 'get_market':
return 'kalshi_get_market'
case 'get_events':
return 'kalshi_get_events'
case 'get_event':
return 'kalshi_get_event'
case 'get_balance':
return 'kalshi_get_balance'
case 'get_positions':
return 'kalshi_get_positions'
case 'get_orders':
return 'kalshi_get_orders'
case 'get_orderbook':
return 'kalshi_get_orderbook'
case 'get_trades':
return 'kalshi_get_trades'
case 'get_candlesticks':
return 'kalshi_get_candlesticks'
case 'get_fills':
return 'kalshi_get_fills'
case 'get_series_by_ticker':
return 'kalshi_get_series_by_ticker'
case 'get_exchange_status':
return 'kalshi_get_exchange_status'
default:
return 'kalshi_get_markets'
}
},
params: (params) => {
const {
operation,
orderStatus,
tickerFilter,
tickerTrades,
tickerFills,
tickerCandlesticks,
seriesTickerCandlesticks,
seriesTickerGet,
...rest
} = params
const cleanParams: Record<string, any> = {}
// Map orderStatus to status for get_orders
if (operation === 'get_orders' && orderStatus) {
cleanParams.status = orderStatus
}
// Map tickerFilter to ticker for get_orders and get_positions
if ((operation === 'get_orders' || operation === 'get_positions') && tickerFilter) {
cleanParams.ticker = tickerFilter
}
// Map tickerTrades to ticker for get_trades
if (operation === 'get_trades' && tickerTrades) {
cleanParams.ticker = tickerTrades
}
// Map tickerFills to ticker for get_fills
if (operation === 'get_fills' && tickerFills) {
cleanParams.ticker = tickerFills
}
// Map fields for get_candlesticks
if (operation === 'get_candlesticks') {
if (seriesTickerCandlesticks) cleanParams.seriesTicker = seriesTickerCandlesticks
if (tickerCandlesticks) cleanParams.ticker = tickerCandlesticks
}
// Map seriesTickerGet to seriesTicker for get_series_by_ticker
if (operation === 'get_series_by_ticker' && seriesTickerGet) {
cleanParams.seriesTicker = seriesTickerGet
}
Object.entries(rest).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
cleanParams[key] = value
}
})
return cleanParams
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
keyId: { type: 'string', description: 'Kalshi API Key ID' },
privateKey: { type: 'string', description: 'RSA Private Key (PEM format)' },
ticker: { type: 'string', description: 'Market ticker' },
eventTicker: { type: 'string', description: 'Event ticker' },
status: { type: 'string', description: 'Filter by status' },
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: { type: 'json', description: 'Operation result data' },
},
}

View File

@@ -0,0 +1,355 @@
import { PolymarketIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
export const PolymarketBlock: BlockConfig = {
type: 'polymarket',
name: 'Polymarket',
description: 'Access prediction markets data from Polymarket',
longDescription:
'Integrate Polymarket prediction markets into the workflow. Can get markets, market, events, event, tags, series, orderbook, price, midpoint, price history, last trade price, spread, tick size, positions, trades, and search.',
docsLink: 'https://docs.sim.ai/tools/polymarket',
category: 'tools',
bgColor: '#4C82FB',
icon: PolymarketIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Get Markets', id: 'get_markets' },
{ label: 'Get Market', id: 'get_market' },
{ label: 'Get Events', id: 'get_events' },
{ label: 'Get Event', id: 'get_event' },
{ label: 'Get Tags', id: 'get_tags' },
{ label: 'Search', id: 'search' },
{ label: 'Get Series', id: 'get_series' },
{ label: 'Get Series by ID', id: 'get_series_by_id' },
{ label: 'Get Orderbook', id: 'get_orderbook' },
{ label: 'Get Price', id: 'get_price' },
{ label: 'Get Midpoint', id: 'get_midpoint' },
{ label: 'Get Price History', id: 'get_price_history' },
{ label: 'Get Last Trade Price', id: 'get_last_trade_price' },
{ label: 'Get Spread', id: 'get_spread' },
{ label: 'Get Tick Size', id: 'get_tick_size' },
{ label: 'Get Positions', id: 'get_positions' },
{ label: 'Get Trades', id: 'get_trades' },
],
value: () => 'get_markets',
},
// Get Market fields - marketId or slug (one is required)
{
id: 'marketId',
title: 'Market ID',
type: 'short-input',
placeholder: 'Market ID (required if no slug)',
condition: { field: 'operation', value: ['get_market'] },
},
{
id: 'marketSlug',
title: 'Market Slug',
type: 'short-input',
placeholder: 'Market slug (required if no ID)',
condition: { field: 'operation', value: ['get_market'] },
},
// Get Event fields - eventId or slug (one is required)
{
id: 'eventId',
title: 'Event ID',
type: 'short-input',
placeholder: 'Event ID (required if no slug)',
condition: { field: 'operation', value: ['get_event'] },
},
{
id: 'eventSlug',
title: 'Event Slug',
type: 'short-input',
placeholder: 'Event slug (required if no ID)',
condition: { field: 'operation', value: ['get_event'] },
},
// Series ID for get_series_by_id
{
id: 'seriesId',
title: 'Series ID',
type: 'short-input',
placeholder: 'Series ID',
required: true,
condition: { field: 'operation', value: ['get_series_by_id'] },
},
// Search query
{
id: 'query',
title: 'Search Query',
type: 'short-input',
placeholder: 'Search term',
required: true,
condition: { field: 'operation', value: ['search'] },
},
// User wallet address for Data API operations
{
id: 'user',
title: 'User Wallet Address',
type: 'short-input',
placeholder: 'Wallet address',
required: true,
condition: { field: 'operation', value: ['get_positions'] },
},
{
id: 'user',
title: 'User Wallet Address',
type: 'short-input',
placeholder: 'Wallet address (optional filter)',
condition: { field: 'operation', value: ['get_trades'] },
},
// Market filter for positions and trades
{
id: 'market',
title: 'Market ID',
type: 'short-input',
placeholder: 'Market ID (optional filter)',
condition: { field: 'operation', value: ['get_positions', 'get_trades'] },
},
// Token ID for CLOB operations
{
id: 'tokenId',
title: 'Token ID',
type: 'short-input',
placeholder: 'CLOB Token ID from market',
required: true,
condition: {
field: 'operation',
value: [
'get_orderbook',
'get_price',
'get_midpoint',
'get_price_history',
'get_last_trade_price',
'get_spread',
'get_tick_size',
],
},
},
// Side for price query
{
id: 'side',
title: 'Side',
type: 'dropdown',
options: [
{ label: 'Buy', id: 'buy' },
{ label: 'Sell', id: 'sell' },
],
condition: { field: 'operation', value: ['get_price'] },
required: true,
},
// Price history specific fields
{
id: 'interval',
title: 'Interval',
type: 'dropdown',
options: [
{ label: 'None (use timestamps)', id: '' },
{ label: '1 Minute', id: '1m' },
{ label: '1 Hour', id: '1h' },
{ label: '6 Hours', id: '6h' },
{ label: '1 Day', id: '1d' },
{ label: '1 Week', id: '1w' },
{ label: 'Max', id: 'max' },
],
condition: { field: 'operation', value: ['get_price_history'] },
},
{
id: 'fidelity',
title: 'Fidelity (minutes)',
type: 'short-input',
placeholder: 'Data resolution in minutes (e.g., 60)',
condition: { field: 'operation', value: ['get_price_history'] },
},
{
id: 'startTs',
title: 'Start Timestamp',
type: 'short-input',
placeholder: 'Unix timestamp UTC (if no interval)',
condition: { field: 'operation', value: ['get_price_history'] },
},
{
id: 'endTs',
title: 'End Timestamp',
type: 'short-input',
placeholder: 'Unix timestamp UTC (if no interval)',
condition: { field: 'operation', value: ['get_price_history'] },
},
// Filters for list operations
{
id: 'closed',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Active Only', id: 'false' },
{ label: 'Closed Only', id: 'true' },
],
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
},
{
id: 'order',
title: 'Sort By',
type: 'short-input',
placeholder: 'Sort field (e.g., id, volume, liquidity)',
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
},
{
id: 'ascending',
title: 'Sort Order',
type: 'dropdown',
options: [
{ label: 'Descending (newest first)', id: 'false' },
{ label: 'Ascending (oldest first)', id: 'true' },
],
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
},
{
id: 'tagId',
title: 'Tag ID',
type: 'short-input',
placeholder: 'Filter by tag ID',
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
},
// Pagination fields
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: 'Number of results (recommended: 25-50)',
condition: {
field: 'operation',
value: ['get_markets', 'get_events', 'get_tags', 'search', 'get_series', 'get_trades'],
},
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: 'Pagination offset',
condition: {
field: 'operation',
value: ['get_markets', 'get_events', 'get_tags', 'search', 'get_series', 'get_trades'],
},
},
],
tools: {
access: [
'polymarket_get_markets',
'polymarket_get_market',
'polymarket_get_events',
'polymarket_get_event',
'polymarket_get_tags',
'polymarket_search',
'polymarket_get_series',
'polymarket_get_series_by_id',
'polymarket_get_orderbook',
'polymarket_get_price',
'polymarket_get_midpoint',
'polymarket_get_price_history',
'polymarket_get_last_trade_price',
'polymarket_get_spread',
'polymarket_get_tick_size',
'polymarket_get_positions',
'polymarket_get_trades',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'get_markets':
return 'polymarket_get_markets'
case 'get_market':
return 'polymarket_get_market'
case 'get_events':
return 'polymarket_get_events'
case 'get_event':
return 'polymarket_get_event'
case 'get_tags':
return 'polymarket_get_tags'
case 'search':
return 'polymarket_search'
case 'get_series':
return 'polymarket_get_series'
case 'get_series_by_id':
return 'polymarket_get_series_by_id'
case 'get_orderbook':
return 'polymarket_get_orderbook'
case 'get_price':
return 'polymarket_get_price'
case 'get_midpoint':
return 'polymarket_get_midpoint'
case 'get_price_history':
return 'polymarket_get_price_history'
case 'get_last_trade_price':
return 'polymarket_get_last_trade_price'
case 'get_spread':
return 'polymarket_get_spread'
case 'get_tick_size':
return 'polymarket_get_tick_size'
case 'get_positions':
return 'polymarket_get_positions'
case 'get_trades':
return 'polymarket_get_trades'
default:
return 'polymarket_get_markets'
}
},
params: (params) => {
const { operation, marketSlug, eventSlug, ...rest } = params
const cleanParams: Record<string, any> = {}
// Map marketSlug to slug for get_market
if (operation === 'get_market' && marketSlug) {
cleanParams.slug = marketSlug
}
// Map eventSlug to slug for get_event
if (operation === 'get_event' && eventSlug) {
cleanParams.slug = eventSlug
}
// Convert numeric fields from string to number for get_price_history
if (operation === 'get_price_history') {
if (rest.fidelity) cleanParams.fidelity = Number(rest.fidelity)
if (rest.startTs) cleanParams.startTs = Number(rest.startTs)
if (rest.endTs) cleanParams.endTs = Number(rest.endTs)
rest.fidelity = undefined
rest.startTs = undefined
rest.endTs = undefined
}
Object.entries(rest).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
cleanParams[key] = value
}
})
return cleanParams
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
marketId: { type: 'string', description: 'Market ID' },
marketSlug: { type: 'string', description: 'Market slug' },
eventId: { type: 'string', description: 'Event ID' },
eventSlug: { type: 'string', description: 'Event slug' },
seriesId: { type: 'string', description: 'Series ID' },
query: { type: 'string', description: 'Search query' },
user: { type: 'string', description: 'User wallet address' },
market: { type: 'string', description: 'Market ID filter' },
tokenId: { type: 'string', description: 'CLOB Token ID' },
side: { type: 'string', description: 'Order side (buy/sell)' },
interval: { type: 'string', description: 'Price history interval' },
fidelity: { type: 'number', description: 'Data resolution in minutes' },
startTs: { type: 'number', description: 'Start timestamp (Unix)' },
endTs: { type: 'number', description: 'End timestamp (Unix)' },
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
output: { type: 'json', description: 'Operation result data' },
},
}

View File

@@ -0,0 +1,845 @@
import { ShopifyIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
interface ShopifyResponse {
success: boolean
error?: string
output: Record<string, unknown>
}
export const ShopifyBlock: BlockConfig<ShopifyResponse> = {
type: 'shopify',
name: 'Shopify',
description: 'Manage products, orders, customers, and inventory in your Shopify store',
authMode: AuthMode.OAuth,
longDescription:
'Integrate Shopify into your workflow. Manage products, orders, customers, and inventory. Create, read, update, and delete products. List and manage orders. Handle customer data and adjust inventory levels.',
docsLink: 'https://docs.sim.ai/tools/shopify',
category: 'tools',
icon: ShopifyIcon,
bgColor: '#FFFFFF',
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Product Operations
{ label: 'Create Product', id: 'shopify_create_product' },
{ label: 'Get Product', id: 'shopify_get_product' },
{ label: 'List Products', id: 'shopify_list_products' },
{ label: 'Update Product', id: 'shopify_update_product' },
{ label: 'Delete Product', id: 'shopify_delete_product' },
// Order Operations
{ label: 'Get Order', id: 'shopify_get_order' },
{ label: 'List Orders', id: 'shopify_list_orders' },
{ label: 'Update Order', id: 'shopify_update_order' },
{ label: 'Cancel Order', id: 'shopify_cancel_order' },
// Customer Operations
{ label: 'Create Customer', id: 'shopify_create_customer' },
{ label: 'Get Customer', id: 'shopify_get_customer' },
{ label: 'List Customers', id: 'shopify_list_customers' },
{ label: 'Update Customer', id: 'shopify_update_customer' },
{ label: 'Delete Customer', id: 'shopify_delete_customer' },
// Inventory Operations
{ label: 'List Inventory Items', id: 'shopify_list_inventory_items' },
{ label: 'Get Inventory Level', id: 'shopify_get_inventory_level' },
{ label: 'Adjust Inventory', id: 'shopify_adjust_inventory' },
// Location Operations
{ label: 'List Locations', id: 'shopify_list_locations' },
// Fulfillment Operations
{ label: 'Create Fulfillment', id: 'shopify_create_fulfillment' },
// Collection Operations
{ label: 'List Collections', id: 'shopify_list_collections' },
{ label: 'Get Collection', id: 'shopify_get_collection' },
],
value: () => 'shopify_list_products',
},
{
id: 'credential',
title: 'Shopify Account',
type: 'oauth-input',
serviceId: 'shopify',
requiredScopes: [
'write_products',
'write_orders',
'write_customers',
'write_inventory',
'read_locations',
'write_merchant_managed_fulfillment_orders',
],
placeholder: 'Select Shopify account',
required: true,
},
{
id: 'shopDomain',
title: 'Shop Domain',
type: 'short-input',
placeholder: 'Auto-detected from OAuth or enter manually',
hidden: true, // Auto-detected from OAuth credential's idToken field
},
// Product ID (for get/update/delete operations)
{
id: 'productId',
title: 'Product ID',
type: 'short-input',
placeholder: 'gid://shopify/Product/123456789',
required: true,
condition: {
field: 'operation',
value: ['shopify_get_product', 'shopify_update_product', 'shopify_delete_product'],
},
},
// Product Title (for create/update)
{
id: 'title',
title: 'Product Title',
type: 'short-input',
placeholder: 'Enter product title',
required: {
field: 'operation',
value: ['shopify_create_product'],
},
condition: {
field: 'operation',
value: ['shopify_create_product', 'shopify_update_product'],
},
},
// Product Description
{
id: 'descriptionHtml',
title: 'Description (HTML)',
type: 'long-input',
placeholder: 'Enter product description',
condition: {
field: 'operation',
value: ['shopify_create_product', 'shopify_update_product'],
},
},
// Product Type
{
id: 'productType',
title: 'Product Type',
type: 'short-input',
placeholder: 'e.g., Shoes, Electronics',
condition: {
field: 'operation',
value: ['shopify_create_product', 'shopify_update_product'],
},
},
// Vendor
{
id: 'vendor',
title: 'Vendor',
type: 'short-input',
placeholder: 'Enter vendor name',
condition: {
field: 'operation',
value: ['shopify_create_product', 'shopify_update_product'],
},
},
// Tags
{
id: 'tags',
title: 'Tags',
type: 'short-input',
placeholder: 'tag1, tag2, tag3 (comma-separated)',
condition: {
field: 'operation',
value: ['shopify_create_product', 'shopify_update_product'],
},
},
// Status
{
id: 'status',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'Active', id: 'ACTIVE' },
{ label: 'Draft', id: 'DRAFT' },
{ label: 'Archived', id: 'ARCHIVED' },
],
value: () => 'ACTIVE',
condition: {
field: 'operation',
value: ['shopify_create_product', 'shopify_update_product'],
},
},
// Query for listing products
{
id: 'productQuery',
title: 'Search Query',
type: 'short-input',
placeholder: 'Filter products (optional)',
condition: {
field: 'operation',
value: ['shopify_list_products'],
},
},
// Query for listing customers
{
id: 'customerQuery',
title: 'Search Query',
type: 'short-input',
placeholder: 'e.g., first_name:John OR email:*@gmail.com',
condition: {
field: 'operation',
value: ['shopify_list_customers'],
},
},
// Query for listing inventory items
{
id: 'inventoryQuery',
title: 'Search Query',
type: 'short-input',
placeholder: 'e.g., sku:ABC123',
condition: {
field: 'operation',
value: ['shopify_list_inventory_items'],
},
},
// Order ID
{
id: 'orderId',
title: 'Order ID',
type: 'short-input',
placeholder: 'gid://shopify/Order/123456789',
required: true,
condition: {
field: 'operation',
value: ['shopify_get_order', 'shopify_update_order', 'shopify_cancel_order'],
},
},
// Order Status (for listing)
{
id: 'orderStatus',
title: 'Order Status',
type: 'dropdown',
options: [
{ label: 'Any', id: 'any' },
{ label: 'Open', id: 'open' },
{ label: 'Closed', id: 'closed' },
{ label: 'Cancelled', id: 'cancelled' },
],
value: () => 'any',
condition: {
field: 'operation',
value: ['shopify_list_orders'],
},
},
// Order Note (for update)
{
id: 'orderNote',
title: 'Order Note',
type: 'long-input',
placeholder: 'Enter order note',
condition: {
field: 'operation',
value: ['shopify_update_order'],
},
},
// Order Email (for update)
{
id: 'orderEmail',
title: 'Customer Email',
type: 'short-input',
placeholder: 'customer@example.com',
condition: {
field: 'operation',
value: ['shopify_update_order'],
},
},
// Order Tags (for update)
{
id: 'orderTags',
title: 'Order Tags',
type: 'short-input',
placeholder: 'tag1, tag2, tag3 (comma-separated)',
condition: {
field: 'operation',
value: ['shopify_update_order'],
},
},
// Cancel Order Reason
{
id: 'cancelReason',
title: 'Cancel Reason',
type: 'dropdown',
options: [
{ label: 'Customer Request', id: 'CUSTOMER' },
{ label: 'Declined Payment', id: 'DECLINED' },
{ label: 'Fraud', id: 'FRAUD' },
{ label: 'Inventory Issue', id: 'INVENTORY' },
{ label: 'Other', id: 'OTHER' },
],
value: () => 'OTHER',
required: true,
condition: {
field: 'operation',
value: ['shopify_cancel_order'],
},
},
// Staff Note (for cancel order)
{
id: 'staffNote',
title: 'Staff Note',
type: 'long-input',
placeholder: 'Internal note about this cancellation',
condition: {
field: 'operation',
value: ['shopify_cancel_order'],
},
},
// Customer ID
{
id: 'customerId',
title: 'Customer ID',
type: 'short-input',
placeholder: 'gid://shopify/Customer/123456789',
required: true,
condition: {
field: 'operation',
value: ['shopify_get_customer', 'shopify_update_customer', 'shopify_delete_customer'],
},
},
// Customer Email (at least one of email/phone/firstName/lastName required for create)
{
id: 'customerEmail',
title: 'Email',
type: 'short-input',
placeholder: 'customer@example.com',
condition: {
field: 'operation',
value: ['shopify_create_customer', 'shopify_update_customer'],
},
},
// Customer First Name
{
id: 'firstName',
title: 'First Name',
type: 'short-input',
placeholder: 'Enter first name',
condition: {
field: 'operation',
value: ['shopify_create_customer', 'shopify_update_customer'],
},
},
// Customer Last Name
{
id: 'lastName',
title: 'Last Name',
type: 'short-input',
placeholder: 'Enter last name',
condition: {
field: 'operation',
value: ['shopify_create_customer', 'shopify_update_customer'],
},
},
// Customer Phone
{
id: 'phone',
title: 'Phone',
type: 'short-input',
placeholder: '+1234567890',
condition: {
field: 'operation',
value: ['shopify_create_customer', 'shopify_update_customer'],
},
},
// Customer Note
{
id: 'customerNote',
title: 'Customer Note',
type: 'long-input',
placeholder: 'Enter note about customer',
condition: {
field: 'operation',
value: ['shopify_create_customer', 'shopify_update_customer'],
},
},
// Customer Tags
{
id: 'customerTags',
title: 'Customer Tags',
type: 'short-input',
placeholder: 'vip, wholesale (comma-separated)',
condition: {
field: 'operation',
value: ['shopify_create_customer', 'shopify_update_customer'],
},
},
// Accepts Marketing
{
id: 'acceptsMarketing',
title: 'Accepts Marketing',
type: 'switch',
condition: {
field: 'operation',
value: ['shopify_create_customer', 'shopify_update_customer'],
},
},
// Inventory Item ID
{
id: 'inventoryItemId',
title: 'Inventory Item ID',
type: 'short-input',
placeholder: 'gid://shopify/InventoryItem/123456789',
required: true,
condition: {
field: 'operation',
value: ['shopify_get_inventory_level', 'shopify_adjust_inventory'],
},
},
// Location ID
{
id: 'locationId',
title: 'Location ID',
type: 'short-input',
placeholder: 'gid://shopify/Location/123456789',
required: {
field: 'operation',
value: 'shopify_adjust_inventory',
},
condition: {
field: 'operation',
value: ['shopify_get_inventory_level', 'shopify_adjust_inventory'],
},
},
// Delta (for inventory adjustment)
{
id: 'delta',
title: 'Quantity Change',
type: 'short-input',
placeholder: 'Positive to add, negative to subtract',
required: true,
condition: {
field: 'operation',
value: ['shopify_adjust_inventory'],
},
},
// Fulfillment Order ID
{
id: 'fulfillmentOrderId',
title: 'Fulfillment Order ID',
type: 'short-input',
placeholder: 'gid://shopify/FulfillmentOrder/123456789',
required: true,
condition: {
field: 'operation',
value: ['shopify_create_fulfillment'],
},
},
// Tracking Number
{
id: 'trackingNumber',
title: 'Tracking Number',
type: 'short-input',
placeholder: 'Enter tracking number',
condition: {
field: 'operation',
value: ['shopify_create_fulfillment'],
},
},
// Tracking Company
{
id: 'trackingCompany',
title: 'Shipping Carrier',
type: 'short-input',
placeholder: 'e.g., UPS, FedEx, USPS, DHL',
condition: {
field: 'operation',
value: ['shopify_create_fulfillment'],
},
},
// Tracking URL
{
id: 'trackingUrl',
title: 'Tracking URL',
type: 'short-input',
placeholder: 'https://...',
condition: {
field: 'operation',
value: ['shopify_create_fulfillment'],
},
},
// Notify Customer (for fulfillment)
{
id: 'notifyCustomer',
title: 'Notify Customer',
type: 'switch',
condition: {
field: 'operation',
value: ['shopify_create_fulfillment'],
},
},
// Collection ID
{
id: 'collectionId',
title: 'Collection ID',
type: 'short-input',
placeholder: 'gid://shopify/Collection/123456789',
required: true,
condition: {
field: 'operation',
value: ['shopify_get_collection'],
},
},
// Collection Query
{
id: 'collectionQuery',
title: 'Search Query',
type: 'short-input',
placeholder: 'e.g., title:Summer OR collection_type:smart',
condition: {
field: 'operation',
value: ['shopify_list_collections'],
},
},
],
tools: {
access: [
'shopify_create_product',
'shopify_get_product',
'shopify_list_products',
'shopify_update_product',
'shopify_delete_product',
'shopify_get_order',
'shopify_list_orders',
'shopify_update_order',
'shopify_cancel_order',
'shopify_create_customer',
'shopify_get_customer',
'shopify_list_customers',
'shopify_update_customer',
'shopify_delete_customer',
'shopify_list_inventory_items',
'shopify_get_inventory_level',
'shopify_adjust_inventory',
'shopify_list_locations',
'shopify_create_fulfillment',
'shopify_list_collections',
'shopify_get_collection',
],
config: {
tool: (params) => {
return params.operation || 'shopify_list_products'
},
params: (params) => {
const baseParams: Record<string, unknown> = {
credential: params.credential,
shopDomain: params.shopDomain?.trim(),
}
switch (params.operation) {
// Product Operations
case 'shopify_create_product':
if (!params.title?.trim()) {
throw new Error('Product title is required.')
}
return {
...baseParams,
title: params.title.trim(),
descriptionHtml: params.descriptionHtml?.trim(),
productType: params.productType?.trim(),
vendor: params.vendor?.trim(),
tags: params.tags
?.split(',')
.map((t: string) => t.trim())
.filter(Boolean),
status: params.status,
}
case 'shopify_get_product':
if (!params.productId?.trim()) {
throw new Error('Product ID is required.')
}
return {
...baseParams,
productId: params.productId.trim(),
}
case 'shopify_list_products':
return {
...baseParams,
query: params.productQuery?.trim(),
}
case 'shopify_update_product':
if (!params.productId?.trim()) {
throw new Error('Product ID is required.')
}
return {
...baseParams,
productId: params.productId.trim(),
title: params.title?.trim(),
descriptionHtml: params.descriptionHtml?.trim(),
productType: params.productType?.trim(),
vendor: params.vendor?.trim(),
tags: params.tags
?.split(',')
.map((t: string) => t.trim())
.filter(Boolean),
status: params.status,
}
case 'shopify_delete_product':
if (!params.productId?.trim()) {
throw new Error('Product ID is required.')
}
return {
...baseParams,
productId: params.productId.trim(),
}
// Order Operations
case 'shopify_get_order':
if (!params.orderId?.trim()) {
throw new Error('Order ID is required.')
}
return {
...baseParams,
orderId: params.orderId.trim(),
}
case 'shopify_list_orders':
return {
...baseParams,
status: params.orderStatus !== 'any' ? params.orderStatus : undefined,
}
case 'shopify_update_order':
if (!params.orderId?.trim()) {
throw new Error('Order ID is required.')
}
return {
...baseParams,
orderId: params.orderId.trim(),
note: params.orderNote?.trim(),
email: params.orderEmail?.trim(),
tags: params.orderTags
?.split(',')
.map((t: string) => t.trim())
.filter(Boolean),
}
case 'shopify_cancel_order':
if (!params.orderId?.trim()) {
throw new Error('Order ID is required.')
}
if (!params.cancelReason) {
throw new Error('Cancel reason is required.')
}
return {
...baseParams,
orderId: params.orderId.trim(),
reason: params.cancelReason,
staffNote: params.staffNote?.trim(),
}
// Customer Operations
case 'shopify_create_customer':
// At least one of email/phone/firstName/lastName required (validated in tool)
return {
...baseParams,
email: params.customerEmail?.trim(),
firstName: params.firstName?.trim(),
lastName: params.lastName?.trim(),
phone: params.phone?.trim(),
note: params.customerNote?.trim(),
tags: params.customerTags
?.split(',')
.map((t: string) => t.trim())
.filter(Boolean),
acceptsMarketing: params.acceptsMarketing,
}
case 'shopify_get_customer':
if (!params.customerId?.trim()) {
throw new Error('Customer ID is required.')
}
return {
...baseParams,
customerId: params.customerId.trim(),
}
case 'shopify_list_customers':
return {
...baseParams,
query: params.customerQuery?.trim(),
}
case 'shopify_update_customer':
if (!params.customerId?.trim()) {
throw new Error('Customer ID is required.')
}
return {
...baseParams,
customerId: params.customerId.trim(),
email: params.customerEmail?.trim(),
firstName: params.firstName?.trim(),
lastName: params.lastName?.trim(),
phone: params.phone?.trim(),
note: params.customerNote?.trim(),
tags: params.customerTags
?.split(',')
.map((t: string) => t.trim())
.filter(Boolean),
}
case 'shopify_delete_customer':
if (!params.customerId?.trim()) {
throw new Error('Customer ID is required.')
}
return {
...baseParams,
customerId: params.customerId.trim(),
}
// Inventory Operations
case 'shopify_list_inventory_items':
return {
...baseParams,
query: params.inventoryQuery?.trim(),
}
case 'shopify_get_inventory_level':
if (!params.inventoryItemId?.trim()) {
throw new Error('Inventory Item ID is required.')
}
return {
...baseParams,
inventoryItemId: params.inventoryItemId.trim(),
locationId: params.locationId?.trim(),
}
case 'shopify_adjust_inventory':
if (!params.inventoryItemId?.trim()) {
throw new Error('Inventory Item ID is required.')
}
if (!params.locationId?.trim()) {
throw new Error('Location ID is required.')
}
if (params.delta === undefined || params.delta === '') {
throw new Error('Quantity change (delta) is required.')
}
return {
...baseParams,
inventoryItemId: params.inventoryItemId.trim(),
locationId: params.locationId.trim(),
delta: Number(params.delta),
}
// Location Operations
case 'shopify_list_locations':
return {
...baseParams,
}
// Fulfillment Operations
case 'shopify_create_fulfillment':
if (!params.fulfillmentOrderId?.trim()) {
throw new Error('Fulfillment Order ID is required.')
}
return {
...baseParams,
fulfillmentOrderId: params.fulfillmentOrderId.trim(),
trackingNumber: params.trackingNumber?.trim(),
trackingCompany: params.trackingCompany?.trim(),
trackingUrl: params.trackingUrl?.trim(),
notifyCustomer: params.notifyCustomer,
}
// Collection Operations
case 'shopify_list_collections':
return {
...baseParams,
query: params.collectionQuery?.trim(),
}
case 'shopify_get_collection':
if (!params.collectionId?.trim()) {
throw new Error('Collection ID is required.')
}
return {
...baseParams,
collectionId: params.collectionId.trim(),
}
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Shopify access token' },
shopDomain: { type: 'string', description: 'Shopify store domain' },
// Product inputs
productId: { type: 'string', description: 'Product ID' },
title: { type: 'string', description: 'Product title' },
descriptionHtml: { type: 'string', description: 'Product description (HTML)' },
productType: { type: 'string', description: 'Product type' },
vendor: { type: 'string', description: 'Product vendor' },
tags: { type: 'string', description: 'Tags (comma-separated)' },
status: { type: 'string', description: 'Product status' },
query: { type: 'string', description: 'Search query' },
// Order inputs
orderId: { type: 'string', description: 'Order ID' },
orderStatus: { type: 'string', description: 'Order status filter' },
orderNote: { type: 'string', description: 'Order note' },
orderEmail: { type: 'string', description: 'Order customer email' },
orderTags: { type: 'string', description: 'Order tags' },
cancelReason: { type: 'string', description: 'Order cancellation reason' },
staffNote: { type: 'string', description: 'Staff note for order cancellation' },
// Customer inputs
customerId: { type: 'string', description: 'Customer ID' },
customerEmail: { type: 'string', description: 'Customer email' },
firstName: { type: 'string', description: 'Customer first name' },
lastName: { type: 'string', description: 'Customer last name' },
phone: { type: 'string', description: 'Customer phone' },
customerNote: { type: 'string', description: 'Customer note' },
customerTags: { type: 'string', description: 'Customer tags' },
acceptsMarketing: { type: 'boolean', description: 'Accepts marketing' },
// Inventory inputs
inventoryQuery: { type: 'string', description: 'Inventory search query' },
inventoryItemId: { type: 'string', description: 'Inventory item ID' },
locationId: { type: 'string', description: 'Location ID' },
delta: { type: 'number', description: 'Quantity change' },
// Fulfillment inputs
fulfillmentOrderId: { type: 'string', description: 'Fulfillment order ID' },
trackingNumber: { type: 'string', description: 'Shipment tracking number' },
trackingCompany: { type: 'string', description: 'Shipping carrier name' },
trackingUrl: { type: 'string', description: 'Tracking URL' },
notifyCustomer: { type: 'boolean', description: 'Send shipping notification email' },
// Collection inputs
collectionId: { type: 'string', description: 'Collection ID' },
collectionQuery: { type: 'string', description: 'Collection search query' },
},
outputs: {
// Product outputs
product: { type: 'json', description: 'Product data' },
products: { type: 'json', description: 'Products list' },
// Order outputs
order: { type: 'json', description: 'Order data' },
orders: { type: 'json', description: 'Orders list' },
// Customer outputs
customer: { type: 'json', description: 'Customer data' },
customers: { type: 'json', description: 'Customers list' },
// Inventory outputs
inventoryItems: { type: 'json', description: 'Inventory items list' },
inventoryLevel: { type: 'json', description: 'Inventory level data' },
// Location outputs
locations: { type: 'json', description: 'Locations list' },
// Fulfillment outputs
fulfillment: { type: 'json', description: 'Fulfillment data' },
// Collection outputs
collection: { type: 'json', description: 'Collection data with products' },
collections: { type: 'json', description: 'Collections list' },
// Delete outputs
deletedId: { type: 'string', description: 'ID of deleted resource' },
// Success indicator
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -0,0 +1,518 @@
import { SshIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { SSHResponse } from '@/tools/ssh/types'
export const SSHBlock: BlockConfig<SSHResponse> = {
type: 'ssh',
name: 'SSH',
description: 'Connect to remote servers via SSH',
authMode: AuthMode.ApiKey,
longDescription:
'Execute commands, transfer files, and manage remote servers via SSH. Supports password and private key authentication for secure server access.',
docsLink: 'https://docs.sim.ai/tools/ssh',
category: 'tools',
bgColor: '#000000',
icon: SshIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Execute Command', id: 'ssh_execute_command' },
{ label: 'Execute Script', id: 'ssh_execute_script' },
{ label: 'Check Command Exists', id: 'ssh_check_command_exists' },
{ label: 'Upload File', id: 'ssh_upload_file' },
{ label: 'Download File', id: 'ssh_download_file' },
{ label: 'List Directory', id: 'ssh_list_directory' },
{ label: 'Check File/Directory Exists', id: 'ssh_check_file_exists' },
{ label: 'Create Directory', id: 'ssh_create_directory' },
{ label: 'Delete File/Directory', id: 'ssh_delete_file' },
{ label: 'Move/Rename', id: 'ssh_move_rename' },
{ label: 'Get System Info', id: 'ssh_get_system_info' },
{ label: 'Read File Content', id: 'ssh_read_file_content' },
{ label: 'Write File Content', id: 'ssh_write_file_content' },
],
value: () => 'ssh_execute_command',
},
// Connection parameters
{
id: 'host',
title: 'Host',
type: 'short-input',
placeholder: 'example.com or 192.168.1.100',
required: true,
},
{
id: 'port',
title: 'Port',
type: 'short-input',
placeholder: '22',
value: () => '22',
},
{
id: 'username',
title: 'Username',
type: 'short-input',
placeholder: 'ubuntu, root, or deploy',
required: true,
},
// Authentication method selector
{
id: 'authMethod',
title: 'Authentication Method',
type: 'dropdown',
options: [
{ label: 'Password', id: 'password' },
{ label: 'Private Key', id: 'privateKey' },
],
value: () => 'password',
},
// Password authentication
{
id: 'password',
title: 'Password',
type: 'short-input',
password: true,
placeholder: 'Your SSH password',
condition: { field: 'authMethod', value: 'password' },
},
// Private key authentication
{
id: 'privateKey',
title: 'Private Key',
type: 'code',
placeholder: '-----BEGIN OPENSSH PRIVATE KEY-----\n...',
condition: { field: 'authMethod', value: 'privateKey' },
},
{
id: 'passphrase',
title: 'Passphrase',
type: 'short-input',
password: true,
placeholder: 'Passphrase for encrypted key (optional)',
condition: { field: 'authMethod', value: 'privateKey' },
},
// EXECUTE COMMAND
{
id: 'command',
title: 'Command',
type: 'code',
placeholder: 'ls -la /var/www',
required: true,
condition: { field: 'operation', value: 'ssh_execute_command' },
},
{
id: 'workingDirectory',
title: 'Working Directory',
type: 'short-input',
placeholder: '/var/www/html (optional)',
condition: { field: 'operation', value: 'ssh_execute_command' },
},
// EXECUTE SCRIPT
{
id: 'script',
title: 'Script Content',
type: 'code',
placeholder: '#!/bin/bash\necho "Hello World"',
required: true,
condition: { field: 'operation', value: 'ssh_execute_script' },
},
{
id: 'interpreter',
title: 'Interpreter',
type: 'short-input',
placeholder: '/bin/bash',
condition: { field: 'operation', value: 'ssh_execute_script' },
},
{
id: 'scriptWorkingDirectory',
title: 'Working Directory',
type: 'short-input',
placeholder: '/var/www/html (optional)',
condition: { field: 'operation', value: 'ssh_execute_script' },
},
// CHECK COMMAND EXISTS
{
id: 'commandName',
title: 'Command Name',
type: 'short-input',
placeholder: 'docker, git, python3',
required: true,
condition: { field: 'operation', value: 'ssh_check_command_exists' },
},
// UPLOAD FILE
{
id: 'fileContent',
title: 'File Content',
type: 'code',
placeholder: 'Content to upload...',
required: true,
condition: { field: 'operation', value: 'ssh_upload_file' },
},
{
id: 'fileName',
title: 'File Name',
type: 'short-input',
placeholder: 'config.json',
required: true,
condition: { field: 'operation', value: 'ssh_upload_file' },
},
{
id: 'remotePath',
title: 'Remote Path',
type: 'short-input',
placeholder: '/var/www/html/config.json',
required: true,
condition: { field: 'operation', value: 'ssh_upload_file' },
},
{
id: 'permissions',
title: 'Permissions',
type: 'short-input',
placeholder: '0644',
condition: { field: 'operation', value: 'ssh_upload_file' },
},
// DOWNLOAD FILE
{
id: 'downloadRemotePath',
title: 'Remote File Path',
type: 'short-input',
placeholder: '/var/log/app.log',
required: true,
condition: { field: 'operation', value: 'ssh_download_file' },
},
// LIST DIRECTORY
{
id: 'listPath',
title: 'Directory Path',
type: 'short-input',
placeholder: '/var/www',
required: true,
condition: { field: 'operation', value: 'ssh_list_directory' },
},
{
id: 'detailed',
title: 'Show Details',
type: 'switch',
condition: { field: 'operation', value: 'ssh_list_directory' },
},
// CHECK FILE EXISTS
{
id: 'checkPath',
title: 'Path to Check',
type: 'short-input',
placeholder: '/etc/nginx/nginx.conf',
required: true,
condition: { field: 'operation', value: 'ssh_check_file_exists' },
},
{
id: 'checkType',
title: 'Expected Type',
type: 'dropdown',
options: [
{ label: 'Any', id: 'any' },
{ label: 'File', id: 'file' },
{ label: 'Directory', id: 'directory' },
],
value: () => 'any',
condition: { field: 'operation', value: 'ssh_check_file_exists' },
},
// CREATE DIRECTORY
{
id: 'createPath',
title: 'Directory Path',
type: 'short-input',
placeholder: '/var/www/new-site',
required: true,
condition: { field: 'operation', value: 'ssh_create_directory' },
},
{
id: 'recursive',
title: 'Create Parent Directories',
type: 'switch',
defaultValue: true,
condition: { field: 'operation', value: 'ssh_create_directory' },
},
// DELETE FILE
{
id: 'deletePath',
title: 'Path to Delete',
type: 'short-input',
placeholder: '/tmp/old-file.txt',
required: true,
condition: { field: 'operation', value: 'ssh_delete_file' },
},
{
id: 'deleteRecursive',
title: 'Recursive Delete',
type: 'switch',
condition: { field: 'operation', value: 'ssh_delete_file' },
},
{
id: 'force',
title: 'Force Delete',
type: 'switch',
condition: { field: 'operation', value: 'ssh_delete_file' },
},
// MOVE/RENAME
{
id: 'sourcePath',
title: 'Source Path',
type: 'short-input',
placeholder: '/var/www/old-name',
required: true,
condition: { field: 'operation', value: 'ssh_move_rename' },
},
{
id: 'destinationPath',
title: 'Destination Path',
type: 'short-input',
placeholder: '/var/www/new-name',
required: true,
condition: { field: 'operation', value: 'ssh_move_rename' },
},
{
id: 'overwrite',
title: 'Overwrite if Exists',
type: 'switch',
condition: { field: 'operation', value: 'ssh_move_rename' },
},
// READ FILE CONTENT
{
id: 'readPath',
title: 'File Path',
type: 'short-input',
placeholder: '/var/log/app.log',
required: true,
condition: { field: 'operation', value: 'ssh_read_file_content' },
},
{
id: 'encoding',
title: 'Encoding',
type: 'short-input',
placeholder: 'utf-8',
condition: { field: 'operation', value: 'ssh_read_file_content' },
},
{
id: 'maxSize',
title: 'Max Size (MB)',
type: 'short-input',
placeholder: '10',
condition: { field: 'operation', value: 'ssh_read_file_content' },
},
// WRITE FILE CONTENT
{
id: 'writePath',
title: 'File Path',
type: 'short-input',
placeholder: '/etc/config.json',
required: true,
condition: { field: 'operation', value: 'ssh_write_file_content' },
},
{
id: 'content',
title: 'File Content',
type: 'code',
placeholder: 'Content to write...',
required: true,
condition: { field: 'operation', value: 'ssh_write_file_content' },
},
{
id: 'writeMode',
title: 'Write Mode',
type: 'dropdown',
options: [
{ label: 'Overwrite', id: 'overwrite' },
{ label: 'Append', id: 'append' },
{ label: 'Create (fail if exists)', id: 'create' },
],
value: () => 'overwrite',
condition: { field: 'operation', value: 'ssh_write_file_content' },
},
{
id: 'writePermissions',
title: 'Permissions',
type: 'short-input',
placeholder: '0644',
condition: { field: 'operation', value: 'ssh_write_file_content' },
},
],
tools: {
access: [
'ssh_execute_command',
'ssh_execute_script',
'ssh_check_command_exists',
'ssh_upload_file',
'ssh_download_file',
'ssh_list_directory',
'ssh_check_file_exists',
'ssh_create_directory',
'ssh_delete_file',
'ssh_move_rename',
'ssh_get_system_info',
'ssh_read_file_content',
'ssh_write_file_content',
],
config: {
tool: (params) => {
return params.operation || 'ssh_execute_command'
},
params: (params) => {
// Build connection config
const connectionConfig: Record<string, unknown> = {
host: params.host,
port:
typeof params.port === 'string' ? Number.parseInt(params.port, 10) : params.port || 22,
username: params.username,
}
// Add authentication based on method
if (params.authMethod === 'privateKey') {
connectionConfig.privateKey = params.privateKey
if (params.passphrase) {
connectionConfig.passphrase = params.passphrase
}
} else {
connectionConfig.password = params.password
}
// Build operation-specific parameters based on the selected operation
const operation = params.operation || 'ssh_execute_command'
switch (operation) {
case 'ssh_execute_command':
return {
...connectionConfig,
command: params.command,
workingDirectory: params.workingDirectory,
}
case 'ssh_execute_script':
return {
...connectionConfig,
script: params.script,
interpreter: params.interpreter || '/bin/bash',
workingDirectory: params.scriptWorkingDirectory,
}
case 'ssh_check_command_exists':
return {
...connectionConfig,
commandName: params.commandName,
}
case 'ssh_upload_file':
return {
...connectionConfig,
fileContent: params.fileContent,
fileName: params.fileName,
remotePath: params.remotePath,
permissions: params.permissions,
}
case 'ssh_download_file':
return {
...connectionConfig,
remotePath: params.downloadRemotePath,
}
case 'ssh_list_directory':
return {
...connectionConfig,
path: params.listPath,
detailed: params.detailed,
}
case 'ssh_check_file_exists':
return {
...connectionConfig,
path: params.checkPath,
type: params.checkType || 'any',
}
case 'ssh_create_directory':
return {
...connectionConfig,
path: params.createPath,
recursive: params.recursive !== false,
}
case 'ssh_delete_file':
return {
...connectionConfig,
path: params.deletePath,
recursive: params.deleteRecursive,
force: params.force,
}
case 'ssh_move_rename':
return {
...connectionConfig,
sourcePath: params.sourcePath,
destinationPath: params.destinationPath,
overwrite: params.overwrite,
}
case 'ssh_get_system_info':
return connectionConfig
case 'ssh_read_file_content':
return {
...connectionConfig,
path: params.readPath,
encoding: params.encoding || 'utf-8',
maxSize: params.maxSize ? Number.parseInt(params.maxSize, 10) : 10,
}
case 'ssh_write_file_content':
return {
...connectionConfig,
path: params.writePath,
content: params.content,
mode: params.writeMode || 'overwrite',
permissions: params.writePermissions,
}
default:
return connectionConfig
}
},
},
},
inputs: {
operation: { type: 'string', description: 'SSH operation to perform' },
host: { type: 'string', description: 'SSH server hostname' },
port: { type: 'number', description: 'SSH server port' },
username: { type: 'string', description: 'SSH username' },
authMethod: { type: 'string', description: 'Authentication method' },
password: { type: 'string', description: 'Password for authentication' },
privateKey: { type: 'string', description: 'Private key for authentication' },
passphrase: { type: 'string', description: 'Passphrase for encrypted key' },
command: { type: 'string', description: 'Command to execute' },
script: { type: 'string', description: 'Script content to execute' },
commandName: { type: 'string', description: 'Command name to check' },
fileContent: { type: 'string', description: 'File content to upload' },
fileName: { type: 'string', description: 'Name of the file' },
remotePath: { type: 'string', description: 'Remote file/directory path' },
content: { type: 'string', description: 'File content' },
},
outputs: {
stdout: { type: 'string', description: 'Command standard output' },
stderr: { type: 'string', description: 'Command standard error' },
exitCode: { type: 'number', description: 'Command exit code' },
success: { type: 'boolean', description: 'Operation success status' },
fileContent: { type: 'string', description: 'Downloaded/read file content' },
entries: { type: 'json', description: 'Directory entries' },
exists: { type: 'boolean', description: 'File/directory existence' },
content: { type: 'string', description: 'File content' },
hostname: { type: 'string', description: 'Server hostname' },
os: { type: 'string', description: 'Operating system' },
message: { type: 'string', description: 'Operation status message' },
},
}

View File

@@ -0,0 +1,958 @@
import { WordpressIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { WordPressResponse } from '@/tools/wordpress/types'
export const WordPressBlock: BlockConfig<WordPressResponse> = {
type: 'wordpress',
name: 'WordPress',
description: 'Manage WordPress content',
authMode: AuthMode.OAuth,
longDescription:
'Integrate with WordPress to create, update, and manage posts, pages, media, comments, categories, tags, and users. Supports WordPress.com sites via OAuth and self-hosted WordPress sites using Application Passwords authentication.',
docsLink: 'https://docs.sim.ai/tools/wordpress',
category: 'tools',
bgColor: '#21759B',
icon: WordpressIcon,
subBlocks: [
// Operation Selection
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Posts
{ label: 'Create Post', id: 'wordpress_create_post' },
{ label: 'Update Post', id: 'wordpress_update_post' },
{ label: 'Delete Post', id: 'wordpress_delete_post' },
{ label: 'Get Post', id: 'wordpress_get_post' },
{ label: 'List Posts', id: 'wordpress_list_posts' },
// Pages
{ label: 'Create Page', id: 'wordpress_create_page' },
{ label: 'Update Page', id: 'wordpress_update_page' },
{ label: 'Delete Page', id: 'wordpress_delete_page' },
{ label: 'Get Page', id: 'wordpress_get_page' },
{ label: 'List Pages', id: 'wordpress_list_pages' },
// Media
{ label: 'Upload Media', id: 'wordpress_upload_media' },
{ label: 'Get Media', id: 'wordpress_get_media' },
{ label: 'List Media', id: 'wordpress_list_media' },
{ label: 'Delete Media', id: 'wordpress_delete_media' },
// Comments
{ label: 'Create Comment', id: 'wordpress_create_comment' },
{ label: 'List Comments', id: 'wordpress_list_comments' },
{ label: 'Update Comment', id: 'wordpress_update_comment' },
{ label: 'Delete Comment', id: 'wordpress_delete_comment' },
// Categories
{ label: 'Create Category', id: 'wordpress_create_category' },
{ label: 'List Categories', id: 'wordpress_list_categories' },
// Tags
{ label: 'Create Tag', id: 'wordpress_create_tag' },
{ label: 'List Tags', id: 'wordpress_list_tags' },
// Users
{ label: 'Get Current User', id: 'wordpress_get_current_user' },
{ label: 'List Users', id: 'wordpress_list_users' },
{ label: 'Get User', id: 'wordpress_get_user' },
// Search
{ label: 'Search Content', id: 'wordpress_search_content' },
],
value: () => 'wordpress_create_post',
},
// Credential selector for OAuth
{
id: 'credential',
title: 'WordPress Account',
type: 'oauth-input',
serviceId: 'wordpress',
requiredScopes: ['global'],
placeholder: 'Select WordPress account',
required: true,
},
// Site ID for WordPress.com (required for OAuth)
{
id: 'siteId',
title: 'Site ID or Domain',
type: 'short-input',
placeholder: 'e.g., 12345678 or yoursite.wordpress.com',
description: 'Your WordPress.com site ID or domain. Find it in Settings → General.',
required: true,
},
// Post Operations - Post ID
{
id: 'postId',
title: 'Post ID',
type: 'short-input',
placeholder: 'Enter post ID',
condition: {
field: 'operation',
value: ['wordpress_update_post', 'wordpress_delete_post', 'wordpress_get_post'],
},
required: {
field: 'operation',
value: ['wordpress_update_post', 'wordpress_delete_post', 'wordpress_get_post'],
},
},
// Post/Page Title
{
id: 'title',
title: 'Title',
type: 'short-input',
placeholder: 'Post or page title',
condition: {
field: 'operation',
value: [
'wordpress_create_post',
'wordpress_update_post',
'wordpress_create_page',
'wordpress_update_page',
],
},
required: {
field: 'operation',
value: ['wordpress_create_post', 'wordpress_create_page'],
},
},
// Post/Page Content
{
id: 'content',
title: 'Content',
type: 'long-input',
placeholder: 'Post or page content (HTML or plain text)',
condition: {
field: 'operation',
value: [
'wordpress_create_post',
'wordpress_update_post',
'wordpress_create_page',
'wordpress_update_page',
],
},
},
// Post/Page Status
{
id: 'status',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'Publish', id: 'publish' },
{ label: 'Draft', id: 'draft' },
{ label: 'Pending', id: 'pending' },
{ label: 'Private', id: 'private' },
],
value: () => 'publish',
condition: {
field: 'operation',
value: [
'wordpress_create_post',
'wordpress_update_post',
'wordpress_create_page',
'wordpress_update_page',
],
},
},
// Excerpt (for posts and pages)
{
id: 'excerpt',
title: 'Excerpt',
type: 'long-input',
placeholder: 'Post or page excerpt',
condition: {
field: 'operation',
value: [
'wordpress_create_post',
'wordpress_update_post',
'wordpress_create_page',
'wordpress_update_page',
],
},
},
// Slug (for posts and pages)
{
id: 'slug',
title: 'Slug',
type: 'short-input',
placeholder: 'URL slug (optional)',
condition: {
field: 'operation',
value: [
'wordpress_create_post',
'wordpress_update_post',
'wordpress_create_page',
'wordpress_update_page',
],
},
},
// Categories (for posts only)
{
id: 'categories',
title: 'Categories',
type: 'short-input',
placeholder: 'Comma-separated category IDs',
condition: {
field: 'operation',
value: ['wordpress_create_post', 'wordpress_update_post'],
},
},
// Tags (for posts only)
{
id: 'tags',
title: 'Tags',
type: 'short-input',
placeholder: 'Comma-separated tag IDs',
condition: {
field: 'operation',
value: ['wordpress_create_post', 'wordpress_update_post'],
},
},
// Featured Media ID
{
id: 'featuredMedia',
title: 'Featured Image ID',
type: 'short-input',
placeholder: 'Media ID for featured image',
condition: {
field: 'operation',
value: [
'wordpress_create_post',
'wordpress_update_post',
'wordpress_create_page',
'wordpress_update_page',
],
},
},
// Page-specific: Page ID
{
id: 'pageId',
title: 'Page ID',
type: 'short-input',
placeholder: 'Enter page ID',
condition: {
field: 'operation',
value: ['wordpress_update_page', 'wordpress_delete_page', 'wordpress_get_page'],
},
required: {
field: 'operation',
value: ['wordpress_update_page', 'wordpress_delete_page', 'wordpress_get_page'],
},
},
// Page-specific: Parent Page
{
id: 'parent',
title: 'Parent Page ID',
type: 'short-input',
placeholder: 'Parent page ID (for hierarchy)',
condition: {
field: 'operation',
value: ['wordpress_create_page', 'wordpress_update_page'],
},
},
// Page-specific: Menu Order
{
id: 'menuOrder',
title: 'Menu Order',
type: 'short-input',
placeholder: 'Order in menu (number)',
condition: {
field: 'operation',
value: ['wordpress_create_page', 'wordpress_update_page'],
},
},
// Media Operations
{
id: 'file',
title: 'File',
type: 'short-input',
placeholder: 'Base64 encoded file data or file URL',
condition: { field: 'operation', value: 'wordpress_upload_media' },
required: { field: 'operation', value: 'wordpress_upload_media' },
},
{
id: 'filename',
title: 'Filename',
type: 'short-input',
placeholder: 'image.jpg',
condition: { field: 'operation', value: 'wordpress_upload_media' },
required: { field: 'operation', value: 'wordpress_upload_media' },
},
{
id: 'mediaTitle',
title: 'Media Title',
type: 'short-input',
placeholder: 'Title for the media',
condition: { field: 'operation', value: 'wordpress_upload_media' },
},
{
id: 'caption',
title: 'Caption',
type: 'short-input',
placeholder: 'Media caption',
condition: { field: 'operation', value: 'wordpress_upload_media' },
},
{
id: 'altText',
title: 'Alt Text',
type: 'short-input',
placeholder: 'Alternative text for accessibility',
condition: { field: 'operation', value: 'wordpress_upload_media' },
},
{
id: 'mediaId',
title: 'Media ID',
type: 'short-input',
placeholder: 'Enter media ID',
condition: {
field: 'operation',
value: ['wordpress_get_media', 'wordpress_delete_media'],
},
required: {
field: 'operation',
value: ['wordpress_get_media', 'wordpress_delete_media'],
},
},
{
id: 'mediaType',
title: 'Media Type',
type: 'dropdown',
options: [
{ label: 'All Types', id: '' },
{ label: 'Image', id: 'image' },
{ label: 'Video', id: 'video' },
{ label: 'Audio', id: 'audio' },
{ label: 'Application', id: 'application' },
],
value: () => '',
condition: { field: 'operation', value: 'wordpress_list_media' },
},
// Comment Operations
{
id: 'commentPostId',
title: 'Post ID',
type: 'short-input',
placeholder: 'Post ID to comment on',
condition: { field: 'operation', value: 'wordpress_create_comment' },
required: { field: 'operation', value: 'wordpress_create_comment' },
},
{
id: 'commentContent',
title: 'Comment Content',
type: 'long-input',
placeholder: 'Comment text',
condition: {
field: 'operation',
value: ['wordpress_create_comment', 'wordpress_update_comment'],
},
required: { field: 'operation', value: 'wordpress_create_comment' },
},
{
id: 'commentId',
title: 'Comment ID',
type: 'short-input',
placeholder: 'Enter comment ID',
condition: {
field: 'operation',
value: ['wordpress_update_comment', 'wordpress_delete_comment'],
},
required: {
field: 'operation',
value: ['wordpress_update_comment', 'wordpress_delete_comment'],
},
},
{
id: 'commentStatus',
title: 'Comment Status',
type: 'dropdown',
options: [
{ label: 'Approved', id: 'approved' },
{ label: 'Hold', id: 'hold' },
{ label: 'Spam', id: 'spam' },
{ label: 'Trash', id: 'trash' },
],
value: () => 'approved',
condition: { field: 'operation', value: 'wordpress_update_comment' },
},
// Category Operations
{
id: 'categoryName',
title: 'Category Name',
type: 'short-input',
placeholder: 'Category name',
condition: { field: 'operation', value: 'wordpress_create_category' },
required: { field: 'operation', value: 'wordpress_create_category' },
},
{
id: 'categoryDescription',
title: 'Description',
type: 'long-input',
placeholder: 'Category description',
condition: { field: 'operation', value: 'wordpress_create_category' },
},
{
id: 'categoryParent',
title: 'Parent Category ID',
type: 'short-input',
placeholder: 'Parent category ID',
condition: { field: 'operation', value: 'wordpress_create_category' },
},
{
id: 'categorySlug',
title: 'Category Slug',
type: 'short-input',
placeholder: 'URL slug (optional)',
condition: { field: 'operation', value: 'wordpress_create_category' },
},
// Tag Operations
{
id: 'tagName',
title: 'Tag Name',
type: 'short-input',
placeholder: 'Tag name',
condition: { field: 'operation', value: 'wordpress_create_tag' },
required: { field: 'operation', value: 'wordpress_create_tag' },
},
{
id: 'tagDescription',
title: 'Description',
type: 'long-input',
placeholder: 'Tag description',
condition: { field: 'operation', value: 'wordpress_create_tag' },
},
{
id: 'tagSlug',
title: 'Tag Slug',
type: 'short-input',
placeholder: 'URL slug (optional)',
condition: { field: 'operation', value: 'wordpress_create_tag' },
},
// User Operations
{
id: 'userId',
title: 'User ID',
type: 'short-input',
placeholder: 'Enter user ID',
condition: { field: 'operation', value: 'wordpress_get_user' },
required: { field: 'operation', value: 'wordpress_get_user' },
},
{
id: 'roles',
title: 'User Roles',
type: 'short-input',
placeholder: 'Comma-separated role names (e.g., administrator, editor)',
condition: { field: 'operation', value: 'wordpress_list_users' },
},
// Search Operations
{
id: 'query',
title: 'Search Query',
type: 'short-input',
placeholder: 'Search keywords',
condition: { field: 'operation', value: 'wordpress_search_content' },
required: { field: 'operation', value: 'wordpress_search_content' },
},
{
id: 'searchType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'All Types', id: '' },
{ label: 'Post', id: 'post' },
{ label: 'Page', id: 'page' },
{ label: 'Attachment', id: 'attachment' },
],
value: () => '',
condition: { field: 'operation', value: 'wordpress_search_content' },
},
// List Operations - Common Parameters
{
id: 'perPage',
title: 'Results Per Page',
type: 'short-input',
placeholder: '10 (max 100)',
condition: {
field: 'operation',
value: [
'wordpress_list_posts',
'wordpress_list_pages',
'wordpress_list_media',
'wordpress_list_comments',
'wordpress_list_categories',
'wordpress_list_tags',
'wordpress_list_users',
'wordpress_search_content',
],
},
},
{
id: 'page',
title: 'Page Number',
type: 'short-input',
placeholder: '1',
condition: {
field: 'operation',
value: [
'wordpress_list_posts',
'wordpress_list_pages',
'wordpress_list_media',
'wordpress_list_comments',
'wordpress_list_categories',
'wordpress_list_tags',
'wordpress_list_users',
'wordpress_search_content',
],
},
},
{
id: 'search',
title: 'Search Filter',
type: 'short-input',
placeholder: 'Search term to filter results',
condition: {
field: 'operation',
value: [
'wordpress_list_posts',
'wordpress_list_pages',
'wordpress_list_media',
'wordpress_list_comments',
'wordpress_list_categories',
'wordpress_list_tags',
'wordpress_list_users',
],
},
},
{
id: 'orderBy',
title: 'Order By',
type: 'dropdown',
options: [
{ label: 'Date', id: 'date' },
{ label: 'ID', id: 'id' },
{ label: 'Title', id: 'title' },
{ label: 'Slug', id: 'slug' },
{ label: 'Modified', id: 'modified' },
],
value: () => 'date',
condition: {
field: 'operation',
value: [
'wordpress_list_posts',
'wordpress_list_pages',
'wordpress_list_media',
'wordpress_list_comments',
],
},
},
{
id: 'order',
title: 'Order',
type: 'dropdown',
options: [
{ label: 'Descending', id: 'desc' },
{ label: 'Ascending', id: 'asc' },
],
value: () => 'desc',
condition: {
field: 'operation',
value: [
'wordpress_list_posts',
'wordpress_list_pages',
'wordpress_list_media',
'wordpress_list_comments',
'wordpress_list_categories',
'wordpress_list_tags',
'wordpress_list_users',
],
},
},
// List Posts - Status filter
{
id: 'listStatus',
title: 'Status Filter',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Published', id: 'publish' },
{ label: 'Draft', id: 'draft' },
{ label: 'Pending', id: 'pending' },
{ label: 'Private', id: 'private' },
],
value: () => '',
condition: {
field: 'operation',
value: ['wordpress_list_posts', 'wordpress_list_pages'],
},
},
// Delete Operations - Force delete
{
id: 'force',
title: 'Force Delete',
type: 'switch',
condition: {
field: 'operation',
value: [
'wordpress_delete_post',
'wordpress_delete_page',
'wordpress_delete_media',
'wordpress_delete_comment',
],
},
},
],
tools: {
access: [
'wordpress_create_post',
'wordpress_update_post',
'wordpress_delete_post',
'wordpress_get_post',
'wordpress_list_posts',
'wordpress_create_page',
'wordpress_update_page',
'wordpress_delete_page',
'wordpress_get_page',
'wordpress_list_pages',
'wordpress_upload_media',
'wordpress_get_media',
'wordpress_list_media',
'wordpress_delete_media',
'wordpress_create_comment',
'wordpress_list_comments',
'wordpress_update_comment',
'wordpress_delete_comment',
'wordpress_create_category',
'wordpress_list_categories',
'wordpress_create_tag',
'wordpress_list_tags',
'wordpress_get_current_user',
'wordpress_list_users',
'wordpress_get_user',
'wordpress_search_content',
],
config: {
tool: (params) => params.operation || 'wordpress_create_post',
params: (params) => {
// OAuth authentication for WordPress.com
const baseParams: Record<string, any> = {
credential: params.credential,
siteId: params.siteId,
}
switch (params.operation) {
case 'wordpress_create_post':
return {
...baseParams,
title: params.title,
content: params.content,
status: params.status,
excerpt: params.excerpt,
slug: params.slug,
categories: params.categories,
tags: params.tags,
featuredMedia: params.featuredMedia ? Number(params.featuredMedia) : undefined,
}
case 'wordpress_update_post':
return {
...baseParams,
postId: Number(params.postId),
title: params.title,
content: params.content,
status: params.status,
excerpt: params.excerpt,
slug: params.slug,
categories: params.categories,
tags: params.tags,
featuredMedia: params.featuredMedia ? Number(params.featuredMedia) : undefined,
}
case 'wordpress_delete_post':
return {
...baseParams,
postId: Number(params.postId),
force: params.force,
}
case 'wordpress_get_post':
return {
...baseParams,
postId: Number(params.postId),
}
case 'wordpress_list_posts':
return {
...baseParams,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
status: params.listStatus || undefined,
search: params.search,
orderBy: params.orderBy,
order: params.order,
categories: params.categories,
tags: params.tags,
}
case 'wordpress_create_page':
return {
...baseParams,
title: params.title,
content: params.content,
status: params.status,
excerpt: params.excerpt,
slug: params.slug,
parent: params.parent ? Number(params.parent) : undefined,
menuOrder: params.menuOrder ? Number(params.menuOrder) : undefined,
featuredMedia: params.featuredMedia ? Number(params.featuredMedia) : undefined,
}
case 'wordpress_update_page':
return {
...baseParams,
pageId: Number(params.pageId),
title: params.title,
content: params.content,
status: params.status,
excerpt: params.excerpt,
slug: params.slug,
parent: params.parent ? Number(params.parent) : undefined,
menuOrder: params.menuOrder ? Number(params.menuOrder) : undefined,
featuredMedia: params.featuredMedia ? Number(params.featuredMedia) : undefined,
}
case 'wordpress_delete_page':
return {
...baseParams,
pageId: Number(params.pageId),
force: params.force,
}
case 'wordpress_get_page':
return {
...baseParams,
pageId: Number(params.pageId),
}
case 'wordpress_list_pages':
return {
...baseParams,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
status: params.listStatus || undefined,
search: params.search,
orderBy: params.orderBy,
order: params.order,
parent: params.parent ? Number(params.parent) : undefined,
}
case 'wordpress_upload_media':
return {
...baseParams,
file: params.file,
filename: params.filename,
title: params.mediaTitle,
caption: params.caption,
altText: params.altText,
}
case 'wordpress_get_media':
return {
...baseParams,
mediaId: Number(params.mediaId),
}
case 'wordpress_list_media':
return {
...baseParams,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
search: params.search,
mediaType: params.mediaType || undefined,
orderBy: params.orderBy,
order: params.order,
}
case 'wordpress_delete_media':
return {
...baseParams,
mediaId: Number(params.mediaId),
force: params.force,
}
case 'wordpress_create_comment':
return {
...baseParams,
postId: Number(params.commentPostId),
content: params.commentContent,
}
case 'wordpress_list_comments':
return {
...baseParams,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
postId: params.commentPostId ? Number(params.commentPostId) : undefined,
search: params.search,
orderBy: params.orderBy,
order: params.order,
}
case 'wordpress_update_comment':
return {
...baseParams,
commentId: Number(params.commentId),
content: params.commentContent,
status: params.commentStatus,
}
case 'wordpress_delete_comment':
return {
...baseParams,
commentId: Number(params.commentId),
force: params.force,
}
case 'wordpress_create_category':
return {
...baseParams,
name: params.categoryName,
description: params.categoryDescription,
parent: params.categoryParent ? Number(params.categoryParent) : undefined,
slug: params.categorySlug,
}
case 'wordpress_list_categories':
return {
...baseParams,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
search: params.search,
order: params.order,
}
case 'wordpress_create_tag':
return {
...baseParams,
name: params.tagName,
description: params.tagDescription,
slug: params.tagSlug,
}
case 'wordpress_list_tags':
return {
...baseParams,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
search: params.search,
order: params.order,
}
case 'wordpress_get_current_user':
return baseParams
case 'wordpress_list_users':
return {
...baseParams,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
search: params.search,
roles: params.roles,
order: params.order,
}
case 'wordpress_get_user':
return {
...baseParams,
userId: Number(params.userId),
}
case 'wordpress_search_content':
return {
...baseParams,
query: params.query,
perPage: params.perPage ? Number(params.perPage) : undefined,
page: params.page ? Number(params.page) : undefined,
type: params.searchType || undefined,
}
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
siteId: { type: 'string', description: 'WordPress.com site ID or domain' },
// Post inputs
postId: { type: 'number', description: 'Post ID' },
title: { type: 'string', description: 'Post or page title' },
content: { type: 'string', description: 'Post or page content' },
status: { type: 'string', description: 'Post or page status' },
excerpt: { type: 'string', description: 'Post or page excerpt' },
slug: { type: 'string', description: 'URL slug' },
categories: { type: 'string', description: 'Category IDs (comma-separated)' },
tags: { type: 'string', description: 'Tag IDs (comma-separated)' },
featuredMedia: { type: 'number', description: 'Featured media ID' },
// Page inputs
pageId: { type: 'number', description: 'Page ID' },
parent: { type: 'number', description: 'Parent page ID' },
menuOrder: { type: 'number', description: 'Menu order' },
// Media inputs
file: { type: 'string', description: 'File data (base64) or URL' },
filename: { type: 'string', description: 'Filename with extension' },
mediaTitle: { type: 'string', description: 'Media title' },
caption: { type: 'string', description: 'Media caption' },
altText: { type: 'string', description: 'Alt text' },
mediaId: { type: 'number', description: 'Media ID' },
mediaType: { type: 'string', description: 'Media type filter' },
// Comment inputs
commentPostId: { type: 'number', description: 'Post ID for comment' },
commentContent: { type: 'string', description: 'Comment content' },
commentId: { type: 'number', description: 'Comment ID' },
commentStatus: { type: 'string', description: 'Comment status' },
// Category inputs
categoryName: { type: 'string', description: 'Category name' },
categoryDescription: { type: 'string', description: 'Category description' },
categoryParent: { type: 'number', description: 'Parent category ID' },
categorySlug: { type: 'string', description: 'Category slug' },
// Tag inputs
tagName: { type: 'string', description: 'Tag name' },
tagDescription: { type: 'string', description: 'Tag description' },
tagSlug: { type: 'string', description: 'Tag slug' },
// User inputs
userId: { type: 'number', description: 'User ID' },
roles: { type: 'string', description: 'User roles filter' },
// Search inputs
query: { type: 'string', description: 'Search query' },
searchType: { type: 'string', description: 'Content type filter' },
// List inputs
perPage: { type: 'number', description: 'Results per page' },
page: { type: 'number', description: 'Page number' },
search: { type: 'string', description: 'Search filter' },
orderBy: { type: 'string', description: 'Order by field' },
order: { type: 'string', description: 'Order direction' },
listStatus: { type: 'string', description: 'Status filter' },
force: { type: 'boolean', description: 'Force delete' },
hideEmpty: { type: 'boolean', description: 'Hide empty taxonomies' },
},
outputs: {
// Post outputs
post: { type: 'json', description: 'Post data' },
posts: { type: 'json', description: 'List of posts' },
// Page outputs
page: { type: 'json', description: 'Page data' },
pages: { type: 'json', description: 'List of pages' },
// Media outputs
media: { type: 'json', description: 'Media data' },
// Comment outputs
comment: { type: 'json', description: 'Comment data' },
comments: { type: 'json', description: 'List of comments' },
// Category outputs
category: { type: 'json', description: 'Category data' },
categories: { type: 'json', description: 'List of categories' },
// Tag outputs
tag: { type: 'json', description: 'Tag data' },
// User outputs
user: { type: 'json', description: 'User data' },
users: { type: 'json', description: 'List of users' },
// Search outputs
results: { type: 'json', description: 'Search results' },
// Common outputs
deleted: { type: 'boolean', description: 'Deletion status' },
total: { type: 'number', description: 'Total count' },
totalPages: { type: 'number', description: 'Total pages' },
},
}

View File

@@ -0,0 +1,572 @@
import { ZoomIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { ZoomResponse } from '@/tools/zoom/types'
export const ZoomBlock: BlockConfig<ZoomResponse> = {
type: 'zoom',
name: 'Zoom',
description: 'Create and manage Zoom meetings and recordings',
authMode: AuthMode.OAuth,
longDescription:
'Integrate Zoom into workflows. Create, list, update, and delete Zoom meetings. Get meeting details, invitations, recordings, and participants. Manage cloud recordings programmatically.',
docsLink: 'https://docs.sim.ai/tools/zoom',
category: 'tools',
bgColor: '#2D8CFF',
icon: ZoomIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Create Meeting', id: 'zoom_create_meeting' },
{ label: 'List Meetings', id: 'zoom_list_meetings' },
{ label: 'Get Meeting', id: 'zoom_get_meeting' },
{ label: 'Update Meeting', id: 'zoom_update_meeting' },
{ label: 'Delete Meeting', id: 'zoom_delete_meeting' },
{ label: 'Get Meeting Invitation', id: 'zoom_get_meeting_invitation' },
{ label: 'List Recordings', id: 'zoom_list_recordings' },
{ label: 'Get Meeting Recordings', id: 'zoom_get_meeting_recordings' },
{ label: 'Delete Recording', id: 'zoom_delete_recording' },
{ label: 'List Past Participants', id: 'zoom_list_past_participants' },
],
value: () => 'zoom_create_meeting',
},
{
id: 'credential',
title: 'Zoom Account',
type: 'oauth-input',
serviceId: 'zoom',
requiredScopes: [
'user:read:user',
'meeting:write:meeting',
'meeting:read:meeting',
'meeting:read:list_meetings',
'meeting:update:meeting',
'meeting:delete:meeting',
'meeting:read:invitation',
'meeting:read:list_past_participants',
'cloud_recording:read:list_user_recordings',
'cloud_recording:read:list_recording_files',
'cloud_recording:delete:recording_file',
],
placeholder: 'Select Zoom account',
required: true,
},
// User ID for create/list operations
{
id: 'userId',
title: 'User ID',
type: 'short-input',
placeholder: 'me (or user ID/email)',
required: true,
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_list_meetings', 'zoom_list_recordings'],
},
},
// Meeting ID for get/update/delete/invitation/recordings/participants operations
{
id: 'meetingId',
title: 'Meeting ID',
type: 'short-input',
placeholder: 'Enter meeting ID',
required: true,
condition: {
field: 'operation',
value: [
'zoom_get_meeting',
'zoom_update_meeting',
'zoom_delete_meeting',
'zoom_get_meeting_invitation',
'zoom_get_meeting_recordings',
'zoom_delete_recording',
'zoom_list_past_participants',
],
},
},
// Topic for create/update
{
id: 'topic',
title: 'Topic',
type: 'short-input',
placeholder: 'Meeting topic',
required: true,
condition: {
field: 'operation',
value: ['zoom_create_meeting'],
},
},
{
id: 'topicUpdate',
title: 'Topic',
type: 'short-input',
placeholder: 'Meeting topic (optional)',
condition: {
field: 'operation',
value: ['zoom_update_meeting'],
},
},
// Meeting type
{
id: 'type',
title: 'Meeting Type',
type: 'dropdown',
options: [
{ label: 'Scheduled', id: '2' },
{ label: 'Instant', id: '1' },
{ label: 'Recurring (no fixed time)', id: '3' },
{ label: 'Recurring (fixed time)', id: '8' },
],
value: () => '2',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
// Start time
{
id: 'startTime',
title: 'Start Time',
type: 'short-input',
placeholder: '2025-06-03T10:00:00Z',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
// Duration
{
id: 'duration',
title: 'Duration (minutes)',
type: 'short-input',
placeholder: '30',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
// Timezone
{
id: 'timezone',
title: 'Timezone',
type: 'short-input',
placeholder: 'America/Los_Angeles',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
// Password
{
id: 'password',
title: 'Password',
type: 'short-input',
placeholder: 'Meeting password',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
// Agenda
{
id: 'agenda',
title: 'Agenda',
type: 'long-input',
placeholder: 'Meeting agenda',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
// Meeting settings
{
id: 'hostVideo',
title: 'Host Video',
type: 'switch',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
{
id: 'participantVideo',
title: 'Participant Video',
type: 'switch',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
{
id: 'joinBeforeHost',
title: 'Join Before Host',
type: 'switch',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
{
id: 'muteUponEntry',
title: 'Mute Upon Entry',
type: 'switch',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
{
id: 'waitingRoom',
title: 'Waiting Room',
type: 'switch',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
{
id: 'autoRecording',
title: 'Auto Recording',
type: 'dropdown',
options: [
{ label: 'None', id: 'none' },
{ label: 'Local', id: 'local' },
{ label: 'Cloud', id: 'cloud' },
],
value: () => 'none',
condition: {
field: 'operation',
value: ['zoom_create_meeting', 'zoom_update_meeting'],
},
},
// List meetings filter
{
id: 'listType',
title: 'Meeting Type Filter',
type: 'dropdown',
options: [
{ label: 'Scheduled', id: 'scheduled' },
{ label: 'Live', id: 'live' },
{ label: 'Upcoming', id: 'upcoming' },
{ label: 'Upcoming Meetings', id: 'upcoming_meetings' },
{ label: 'Previous Meetings', id: 'previous_meetings' },
],
value: () => 'scheduled',
condition: {
field: 'operation',
value: ['zoom_list_meetings'],
},
},
// Pagination
{
id: 'pageSize',
title: 'Page Size',
type: 'short-input',
placeholder: 'Number of results (max 300)',
condition: {
field: 'operation',
value: ['zoom_list_meetings', 'zoom_list_recordings', 'zoom_list_past_participants'],
},
},
{
id: 'nextPageToken',
title: 'Page Token',
type: 'short-input',
placeholder: 'Token for next page',
condition: {
field: 'operation',
value: ['zoom_list_meetings', 'zoom_list_recordings', 'zoom_list_past_participants'],
},
},
// Recording date range
{
id: 'fromDate',
title: 'From Date',
type: 'short-input',
placeholder: 'yyyy-mm-dd (within last 6 months)',
condition: {
field: 'operation',
value: ['zoom_list_recordings'],
},
},
{
id: 'toDate',
title: 'To Date',
type: 'short-input',
placeholder: 'yyyy-mm-dd',
condition: {
field: 'operation',
value: ['zoom_list_recordings'],
},
},
// Recording ID for delete
{
id: 'recordingId',
title: 'Recording ID',
type: 'short-input',
placeholder: 'Specific recording file ID (optional)',
condition: {
field: 'operation',
value: ['zoom_delete_recording'],
},
},
// Delete action
{
id: 'deleteAction',
title: 'Delete Action',
type: 'dropdown',
options: [
{ label: 'Move to Trash', id: 'trash' },
{ label: 'Permanently Delete', id: 'delete' },
],
value: () => 'trash',
condition: {
field: 'operation',
value: ['zoom_delete_recording'],
},
},
// Delete options
{
id: 'occurrenceId',
title: 'Occurrence ID',
type: 'short-input',
placeholder: 'For recurring meetings',
condition: {
field: 'operation',
value: ['zoom_get_meeting', 'zoom_delete_meeting'],
},
},
{
id: 'cancelMeetingReminder',
title: 'Send Cancellation Email',
type: 'switch',
condition: {
field: 'operation',
value: ['zoom_delete_meeting'],
},
},
],
tools: {
access: [
'zoom_create_meeting',
'zoom_list_meetings',
'zoom_get_meeting',
'zoom_update_meeting',
'zoom_delete_meeting',
'zoom_get_meeting_invitation',
'zoom_list_recordings',
'zoom_get_meeting_recordings',
'zoom_delete_recording',
'zoom_list_past_participants',
],
config: {
tool: (params) => {
return params.operation || 'zoom_create_meeting'
},
params: (params) => {
const baseParams: Record<string, any> = {
credential: params.credential,
}
switch (params.operation) {
case 'zoom_create_meeting':
if (!params.userId?.trim()) {
throw new Error('User ID is required.')
}
if (!params.topic?.trim()) {
throw new Error('Topic is required.')
}
return {
...baseParams,
userId: params.userId.trim(),
topic: params.topic.trim(),
type: params.type ? Number(params.type) : 2,
startTime: params.startTime,
duration: params.duration ? Number(params.duration) : undefined,
timezone: params.timezone,
password: params.password,
agenda: params.agenda,
hostVideo: params.hostVideo,
participantVideo: params.participantVideo,
joinBeforeHost: params.joinBeforeHost,
muteUponEntry: params.muteUponEntry,
waitingRoom: params.waitingRoom,
autoRecording: params.autoRecording !== 'none' ? params.autoRecording : undefined,
}
case 'zoom_list_meetings':
if (!params.userId?.trim()) {
throw new Error('User ID is required.')
}
return {
...baseParams,
userId: params.userId.trim(),
type: params.listType,
pageSize: params.pageSize ? Number(params.pageSize) : undefined,
nextPageToken: params.nextPageToken,
}
case 'zoom_get_meeting':
if (!params.meetingId?.trim()) {
throw new Error('Meeting ID is required.')
}
return {
...baseParams,
meetingId: params.meetingId.trim(),
occurrenceId: params.occurrenceId,
}
case 'zoom_update_meeting':
if (!params.meetingId?.trim()) {
throw new Error('Meeting ID is required.')
}
return {
...baseParams,
meetingId: params.meetingId.trim(),
topic: params.topicUpdate,
type: params.type ? Number(params.type) : undefined,
startTime: params.startTime,
duration: params.duration ? Number(params.duration) : undefined,
timezone: params.timezone,
password: params.password,
agenda: params.agenda,
hostVideo: params.hostVideo,
participantVideo: params.participantVideo,
joinBeforeHost: params.joinBeforeHost,
muteUponEntry: params.muteUponEntry,
waitingRoom: params.waitingRoom,
autoRecording: params.autoRecording !== 'none' ? params.autoRecording : undefined,
}
case 'zoom_delete_meeting':
if (!params.meetingId?.trim()) {
throw new Error('Meeting ID is required.')
}
return {
...baseParams,
meetingId: params.meetingId.trim(),
occurrenceId: params.occurrenceId,
cancelMeetingReminder: params.cancelMeetingReminder,
}
case 'zoom_get_meeting_invitation':
if (!params.meetingId?.trim()) {
throw new Error('Meeting ID is required.')
}
return {
...baseParams,
meetingId: params.meetingId.trim(),
}
case 'zoom_list_recordings':
if (!params.userId?.trim()) {
throw new Error('User ID is required.')
}
return {
...baseParams,
userId: params.userId.trim(),
from: params.fromDate,
to: params.toDate,
pageSize: params.pageSize ? Number(params.pageSize) : undefined,
nextPageToken: params.nextPageToken,
}
case 'zoom_get_meeting_recordings':
if (!params.meetingId?.trim()) {
throw new Error('Meeting ID is required.')
}
return {
...baseParams,
meetingId: params.meetingId.trim(),
}
case 'zoom_delete_recording':
if (!params.meetingId?.trim()) {
throw new Error('Meeting ID is required.')
}
return {
...baseParams,
meetingId: params.meetingId.trim(),
recordingId: params.recordingId,
action: params.deleteAction,
}
case 'zoom_list_past_participants':
if (!params.meetingId?.trim()) {
throw new Error('Meeting ID is required.')
}
return {
...baseParams,
meetingId: params.meetingId.trim(),
pageSize: params.pageSize ? Number(params.pageSize) : undefined,
nextPageToken: params.nextPageToken,
}
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Zoom access token' },
userId: { type: 'string', description: 'User ID or email (use "me" for authenticated user)' },
meetingId: { type: 'string', description: 'Meeting ID' },
topic: { type: 'string', description: 'Meeting topic' },
topicUpdate: { type: 'string', description: 'Meeting topic for update' },
type: { type: 'string', description: 'Meeting type' },
startTime: { type: 'string', description: 'Start time in ISO 8601 format' },
duration: { type: 'string', description: 'Duration in minutes' },
timezone: { type: 'string', description: 'Timezone' },
password: { type: 'string', description: 'Meeting password' },
agenda: { type: 'string', description: 'Meeting agenda' },
hostVideo: { type: 'boolean', description: 'Host video on' },
participantVideo: { type: 'boolean', description: 'Participant video on' },
joinBeforeHost: { type: 'boolean', description: 'Allow join before host' },
muteUponEntry: { type: 'boolean', description: 'Mute upon entry' },
waitingRoom: { type: 'boolean', description: 'Enable waiting room' },
autoRecording: { type: 'string', description: 'Auto recording setting' },
listType: { type: 'string', description: 'Meeting type filter for list' },
pageSize: { type: 'string', description: 'Page size for list' },
nextPageToken: { type: 'string', description: 'Page token for pagination' },
occurrenceId: { type: 'string', description: 'Occurrence ID for recurring meetings' },
cancelMeetingReminder: { type: 'boolean', description: 'Send cancellation email' },
fromDate: { type: 'string', description: 'Start date for recordings list (yyyy-mm-dd)' },
toDate: { type: 'string', description: 'End date for recordings list (yyyy-mm-dd)' },
recordingId: { type: 'string', description: 'Specific recording file ID' },
deleteAction: { type: 'string', description: 'Delete action (trash or delete)' },
},
outputs: {
// Meeting outputs
meeting: { type: 'json', description: 'Meeting data' },
meetings: { type: 'json', description: 'List of meetings' },
// Specific meeting fields
id: { type: 'number', description: 'Meeting ID' },
uuid: { type: 'string', description: 'Meeting UUID' },
topic: { type: 'string', description: 'Meeting topic' },
join_url: { type: 'string', description: 'Join URL for participants' },
start_url: { type: 'string', description: 'Start URL for host' },
start_time: { type: 'string', description: 'Start time' },
duration: { type: 'number', description: 'Duration in minutes' },
timezone: { type: 'string', description: 'Timezone' },
password: { type: 'string', description: 'Meeting password' },
agenda: { type: 'string', description: 'Meeting agenda' },
settings: { type: 'json', description: 'Meeting settings' },
// Invitation
invitation: { type: 'string', description: 'Meeting invitation text' },
// Recording outputs
recording: { type: 'json', description: 'Recording data' },
recordings: { type: 'json', description: 'List of recordings' },
recording_files: { type: 'json', description: 'Recording files' },
share_url: { type: 'string', description: 'Share URL for recording' },
// Participant outputs
participants: { type: 'json', description: 'List of participants' },
// Pagination
pageInfo: { type: 'json', description: 'Pagination information' },
// Success indicator
success: { type: 'boolean', description: 'Operation success status' },
},
}

View File

@@ -1,4 +1,5 @@
import { AgentBlock } from '@/blocks/blocks/agent'
import { AhrefsBlock } from '@/blocks/blocks/ahrefs'
import { AirtableBlock } from '@/blocks/blocks/airtable'
import { ApiBlock } from '@/blocks/blocks/api'
import { ApiTriggerBlock } from '@/blocks/blocks/api_trigger'
@@ -6,14 +7,18 @@ import { ApifyBlock } from '@/blocks/blocks/apify'
import { ApolloBlock } from '@/blocks/blocks/apollo'
import { ArxivBlock } from '@/blocks/blocks/arxiv'
import { AsanaBlock } from '@/blocks/blocks/asana'
// import { BoxBlock } from '@/blocks/blocks/box' // TODO: Box OAuth integration
import { BrowserUseBlock } from '@/blocks/blocks/browser_use'
import { CalendlyBlock } from '@/blocks/blocks/calendly'
import { ChatTriggerBlock } from '@/blocks/blocks/chat_trigger'
import { ClayBlock } from '@/blocks/blocks/clay'
import { ConditionBlock } from '@/blocks/blocks/condition'
import { ConfluenceBlock } from '@/blocks/blocks/confluence'
import { DatadogBlock } from '@/blocks/blocks/datadog'
import { DiscordBlock } from '@/blocks/blocks/discord'
import { DropboxBlock } from '@/blocks/blocks/dropbox'
import { DynamoDBBlock } from '@/blocks/blocks/dynamodb'
import { ElasticsearchBlock } from '@/blocks/blocks/elasticsearch'
import { ElevenLabsBlock } from '@/blocks/blocks/elevenlabs'
import { EvaluatorBlock } from '@/blocks/blocks/evaluator'
import { ExaBlock } from '@/blocks/blocks/exa'
@@ -22,6 +27,7 @@ import { FirecrawlBlock } from '@/blocks/blocks/firecrawl'
import { FunctionBlock } from '@/blocks/blocks/function'
import { GenericWebhookBlock } from '@/blocks/blocks/generic_webhook'
import { GitHubBlock } from '@/blocks/blocks/github'
import { GitLabBlock } from '@/blocks/blocks/gitlab'
import { GmailBlock } from '@/blocks/blocks/gmail'
import { GoogleSearchBlock } from '@/blocks/blocks/google'
import { GoogleCalendarBlock } from '@/blocks/blocks/google_calendar'
@@ -30,6 +36,7 @@ import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
import { GoogleFormsBlock } from '@/blocks/blocks/google_form'
import { GoogleSheetsBlock } from '@/blocks/blocks/google_sheets'
import { GoogleVaultBlock } from '@/blocks/blocks/google_vault'
import { GrafanaBlock } from '@/blocks/blocks/grafana'
import { GuardrailsBlock } from '@/blocks/blocks/guardrails'
import { HubSpotBlock } from '@/blocks/blocks/hubspot'
import { HuggingFaceBlock } from '@/blocks/blocks/huggingface'
@@ -41,6 +48,7 @@ import { InputTriggerBlock } from '@/blocks/blocks/input_trigger'
import { IntercomBlock } from '@/blocks/blocks/intercom'
import { JinaBlock } from '@/blocks/blocks/jina'
import { JiraBlock } from '@/blocks/blocks/jira'
import { KalshiBlock } from '@/blocks/blocks/kalshi'
import { KnowledgeBlock } from '@/blocks/blocks/knowledge'
import { LinearBlock } from '@/blocks/blocks/linear'
import { LinkedInBlock } from '@/blocks/blocks/linkedin'
@@ -67,6 +75,7 @@ import { ParallelBlock } from '@/blocks/blocks/parallel'
import { PerplexityBlock } from '@/blocks/blocks/perplexity'
import { PineconeBlock } from '@/blocks/blocks/pinecone'
import { PipedriveBlock } from '@/blocks/blocks/pipedrive'
import { PolymarketBlock } from '@/blocks/blocks/polymarket'
import { PostgreSQLBlock } from '@/blocks/blocks/postgresql'
import { PostHogBlock } from '@/blocks/blocks/posthog'
import { PylonBlock } from '@/blocks/blocks/pylon'
@@ -84,8 +93,10 @@ import { SendGridBlock } from '@/blocks/blocks/sendgrid'
import { SentryBlock } from '@/blocks/blocks/sentry'
import { SerperBlock } from '@/blocks/blocks/serper'
import { SharepointBlock } from '@/blocks/blocks/sharepoint'
import { ShopifyBlock } from '@/blocks/blocks/shopify'
import { SlackBlock } from '@/blocks/blocks/slack'
import { SmtpBlock } from '@/blocks/blocks/smtp'
import { SSHBlock } from '@/blocks/blocks/ssh'
import { StagehandBlock } from '@/blocks/blocks/stagehand'
import { StagehandAgentBlock } from '@/blocks/blocks/stagehand_agent'
import { StartTriggerBlock } from '@/blocks/blocks/start_trigger'
@@ -111,17 +122,20 @@ import { WebflowBlock } from '@/blocks/blocks/webflow'
import { WebhookBlock } from '@/blocks/blocks/webhook'
import { WhatsAppBlock } from '@/blocks/blocks/whatsapp'
import { WikipediaBlock } from '@/blocks/blocks/wikipedia'
import { WordPressBlock } from '@/blocks/blocks/wordpress'
import { WorkflowBlock } from '@/blocks/blocks/workflow'
import { WorkflowInputBlock } from '@/blocks/blocks/workflow_input'
import { XBlock } from '@/blocks/blocks/x'
import { YouTubeBlock } from '@/blocks/blocks/youtube'
import { ZendeskBlock } from '@/blocks/blocks/zendesk'
import { ZepBlock } from '@/blocks/blocks/zep'
import { ZoomBlock } from '@/blocks/blocks/zoom'
import type { BlockConfig } from '@/blocks/types'
// Registry of all available blocks, alphabetically sorted
export const registry: Record<string, BlockConfig> = {
agent: AgentBlock,
ahrefs: AhrefsBlock,
airtable: AirtableBlock,
api: ApiBlock,
api_trigger: ApiTriggerBlock,
@@ -129,14 +143,18 @@ export const registry: Record<string, BlockConfig> = {
apollo: ApolloBlock,
arxiv: ArxivBlock,
asana: AsanaBlock,
// box: BoxBlock, // TODO: Box OAuth integration
browser_use: BrowserUseBlock,
calendly: CalendlyBlock,
chat_trigger: ChatTriggerBlock,
clay: ClayBlock,
condition: ConditionBlock,
confluence: ConfluenceBlock,
datadog: DatadogBlock,
discord: DiscordBlock,
dropbox: DropboxBlock,
elevenlabs: ElevenLabsBlock,
elasticsearch: ElasticsearchBlock,
evaluator: EvaluatorBlock,
exa: ExaBlock,
file: FileBlock,
@@ -144,7 +162,9 @@ export const registry: Record<string, BlockConfig> = {
function: FunctionBlock,
generic_webhook: GenericWebhookBlock,
github: GitHubBlock,
gitlab: GitLabBlock,
gmail: GmailBlock,
grafana: GrafanaBlock,
guardrails: GuardrailsBlock,
google_calendar: GoogleCalendarBlock,
google_docs: GoogleDocsBlock,
@@ -163,6 +183,7 @@ export const registry: Record<string, BlockConfig> = {
intercom: IntercomBlock,
jina: JinaBlock,
jira: JiraBlock,
kalshi: KalshiBlock,
knowledge: KnowledgeBlock,
linear: LinearBlock,
linkedin: LinkedInBlock,
@@ -189,6 +210,7 @@ export const registry: Record<string, BlockConfig> = {
perplexity: PerplexityBlock,
pinecone: PineconeBlock,
pipedrive: PipedriveBlock,
polymarket: PolymarketBlock,
postgresql: PostgreSQLBlock,
posthog: PostHogBlock,
pylon: PylonBlock,
@@ -207,8 +229,10 @@ export const registry: Record<string, BlockConfig> = {
sentry: SentryBlock,
serper: SerperBlock,
sharepoint: SharepointBlock,
shopify: ShopifyBlock,
slack: SlackBlock,
smtp: SmtpBlock,
ssh: SSHBlock,
stagehand: StagehandBlock,
stagehand_agent: StagehandAgentBlock,
starter: StarterBlock,
@@ -234,12 +258,14 @@ export const registry: Record<string, BlockConfig> = {
webhook: WebhookBlock,
whatsapp: WhatsAppBlock,
wikipedia: WikipediaBlock,
wordpress: WordPressBlock,
workflow: WorkflowBlock,
workflow_input: WorkflowInputBlock,
x: XBlock,
youtube: YouTubeBlock,
zep: ZepBlock,
zendesk: ZendeskBlock,
zoom: ZoomBlock,
}
export const getBlock = (type: string): BlockConfig | undefined => registry[type]

View File

@@ -473,6 +473,30 @@ export function GithubIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function GitLabIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<path
d='M22.65 14.39L12 22.13 1.35 14.39a.84.84 0 0 1-.3-.94l1.22-3.78 2.44-7.51A.42.42 0 0 1 4.82 2a.43.43 0 0 1 .58 0 .42.42 0 0 1 .11.18l2.44 7.49h8.1l2.44-7.51A.42.42 0 0 1 18.6 2a.43.43 0 0 1 .58 0 .42.42 0 0 1 .11.18l2.44 7.51L23 13.45a.84.84 0 0 1-.35.94z'
fill='#FC6D26'
/>
<path d='M12 22.13L16.05 9.67H7.95L12 22.13z' fill='#E24329' />
<path d='M12 22.13L7.95 9.67H1.69L12 22.13z' fill='#FC6D26' />
<path d='M1.69 9.67L.47 13.45a.84.84 0 0 0 .3.94L12 22.13 1.69 9.67z' fill='#FCA326' />
<path
d='M1.69 9.67H7.95L5.51 2.16A.42.42 0 0 0 4.93 2a.42.42 0 0 0-.11.18L1.69 9.67z'
fill='#E24329'
/>
<path d='M12 22.13L16.05 9.67H22.31L12 22.13z' fill='#FC6D26' />
<path d='M22.31 9.67L23.53 13.45a.84.84 0 0 1-.3.94L12 22.13 22.31 9.67z' fill='#FCA326' />
<path
d='M22.31 9.67H16.05l2.44-7.51A.42.42 0 0 1 19.07 2a.42.42 0 0 1 .11.18l3.13 7.49z'
fill='#E24329'
/>
</svg>
)
}
export function SerperIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox='0 0 654 600' xmlns='http://www.w3.org/2000/svg' {...props}>
@@ -649,6 +673,37 @@ export function GmailIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function GrafanaIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
width='800px'
height='800px'
viewBox='0 0 16 16'
xmlns='http://www.w3.org/2000/svg'
fill='none'
>
<path
fill='url(#grafana-color-16__paint0_linear_2372_364)'
d='M13.985 7.175a4.408 4.408 0 00-.138-.802 5.035 5.035 0 00-1.054-1.998 2.96 2.96 0 00-.366-.393c.198-.787-.245-1.468-.245-1.468-.764-.046-1.237.227-1.42.363-.031-.015-.062-.03-.092-.03-.122-.046-.26-.106-.397-.137-.138-.045-.275-.075-.413-.12-.137-.031-.29-.061-.443-.092-.03 0-.046 0-.076-.015C9.005 1.44 8.058 1 8.058 1 7.004 1.666 6.79 2.604 6.79 2.604s0 .015-.016.06l-.183.046c-.076.03-.168.06-.244.076-.077.03-.168.06-.245.09-.153.076-.32.152-.473.228-.153.09-.306.181-.443.272-.016-.015-.03-.015-.03-.015-1.467-.545-2.766.136-2.766.136-.122 1.544.58 2.528.733 2.71-.03.09-.06.196-.091.287a8.104 8.104 0 00-.245 1.09c0 .06-.015.106-.015.166C1.397 8.386 1 9.748 1 9.748c1.13 1.287 2.46 1.377 2.46 1.377.167.303.366.575.58.848.092.106.183.212.29.318a3.014 3.014 0 00.061 2.149c1.268.045 2.093-.545 2.261-.681.122.045.26.076.382.106.382.106.78.151 1.176.181h.49c.595.848 1.634.954 1.634.954.748-.772.779-1.544.779-1.71v-.015-.03-.03c.153-.107.305-.228.443-.35a5.37 5.37 0 00.779-.892c.015-.03.046-.06.061-.09.84.045 1.436-.515 1.436-.515-.138-.863-.642-1.287-.749-1.378l-.015-.015h-.015s-.015 0-.015-.015c0-.045.015-.106.015-.151 0-.091.015-.182.015-.288V9.4v-.166-.076-.152l-.015-.075c-.015-.091-.03-.197-.061-.288a3.506 3.506 0 00-.428-1.044 3.856 3.856 0 00-.718-.848 3.784 3.784 0 00-.901-.575 3.347 3.347 0 00-.993-.272c-.168-.015-.336-.03-.504-.03H9.37 9.204c-.092.015-.169.015-.26.03-.336.06-.642.181-.932.348-.275.166-.52.363-.718.605a2.579 2.579 0 00-.459.757 2.63 2.63 0 00-.183.817v.393c.015.137.046.273.077.394.076.258.183.485.336.666.137.197.32.348.504.485.183.12.382.212.58.272.199.06.382.076.565.076h.244c.031 0 .047 0 .062-.015.015 0 .046-.015.061-.015.046-.016.076-.016.122-.03l.23-.092a.869.869 0 00.198-.12c.015-.016.03-.03.046-.03a.129.129 0 00.015-.198c-.046-.06-.122-.075-.183-.03-.015.015-.03.015-.046.03-.046.03-.107.046-.168.06l-.183.046c-.03 0-.061.015-.092.015H8.73a1.519 1.519 0 01-.825-.378 1.452 1.452 0 01-.306-.378 1.655 1.655 0 01-.168-.485c-.015-.09-.015-.166-.015-.257v-.106-.03c0-.046.015-.091.015-.136.061-.364.26-.727.55-1 .077-.075.153-.136.23-.181.076-.06.167-.106.259-.151.092-.046.183-.076.29-.106a.993.993 0 01.306-.046h.321c.107.015.229.03.336.046.214.045.427.12.626.242.397.212.733.56.947.969.107.211.183.423.214.65.015.06.015.121.015.167v.363c0 .06-.015.121-.015.182 0 .06-.015.12-.03.181l-.046.182c-.03.121-.077.242-.123.363a3.183 3.183 0 01-.366.666 3.002 3.002 0 01-1.91 1.18c-.122.016-.26.03-.382.046h-.198c-.061 0-.138 0-.199-.015a3.637 3.637 0 01-.81-.151 4.068 4.068 0 01-.748-.303 4.098 4.098 0 01-1.696-1.695 4.398 4.398 0 01-.29-.742c-.076-.257-.107-.514-.137-.772v-.302-.091c0-.136.015-.258.03-.394s.046-.272.061-.393c.03-.137.061-.258.092-.394a5.33 5.33 0 01.275-.741c.214-.47.504-.893.855-1.226.092-.091.184-.167.275-.243.092-.075.184-.136.29-.211a5.39 5.39 0 01.306-.182c.046-.03.107-.045.153-.076a.26.26 0 01.076-.03.26.26 0 01.077-.03c.107-.046.229-.091.336-.121.03-.015.06-.015.091-.03.03-.016.061-.016.092-.03.061-.016.122-.031.168-.046.03-.015.061-.015.092-.015.03 0 .06-.016.091-.016.03 0 .061-.015.092-.015l.046-.015h.046c.03 0 .06-.015.091-.015.03 0 .061-.015.107-.015.03 0 .077-.015.107-.015h.764c.23.015.443.03.657.075.428.076.84.212 1.207.394.366.182.702.393.977.636l.046.045.046.045c.03.03.061.061.107.091l.092.091.091.09c.123.122.23.258.336.394.199.258.367.515.49.772.014.015.014.03.03.046.015.015.015.03.015.045l.046.09.046.092.045.09c.046.122.092.228.123.333.06.167.107.318.137.455.015.045.061.09.122.075a.104.104 0 00.107-.106c.092-.227.092-.393.077-.575z'
/>
<defs>
<linearGradient
id='grafana-color-16__paint0_linear_2372_364'
x1='7.502'
x2='7.502'
y1='18.142'
y2='5.356'
gradientUnits='userSpaceOnUse'
>
<stop stop-color='#FFF200' />
<stop offset='1' stop-color='#F15A29' />
</linearGradient>
</defs>
</svg>
)
}
export function GoogleDriveIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -1259,7 +1314,7 @@ export function ConfluenceIcon(props: SVGProps<SVGSVGElement>) {
export function TwilioIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'>
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='-8 -8 272 272'>
<path
fill='currentColor'
d='M128 0c70.656 0 128 57.344 128 128s-57.344 128-128 128S0 198.656 0 128 57.344 0 128 0zm0 33.792c-52.224 0-94.208 41.984-94.208 94.208S75.776 222.208 128 222.208s94.208-41.984 94.208-94.208S180.224 33.792 128 33.792zm31.744 99.328c14.704 0 26.624 11.92 26.624 26.624 0 14.704-11.92 26.624-26.624 26.624-14.704 0-26.624-11.92-26.624-26.624 0-14.704 11.92-26.624 26.624-26.624zm-63.488 0c14.704 0 26.624 11.92 26.624 26.624 0 14.704-11.92 26.624-26.624 26.624-14.704 0-26.624-11.92-26.624-26.624 0-14.704 11.92-26.624 26.624-26.624zm63.488-63.488c14.704 0 26.624 11.92 26.624 26.624 0 14.704-11.92 26.624-26.624 26.624-14.704 0-26.624-11.92-26.624-26.624 0-14.704 11.92-26.624 26.624-26.624zm-63.488 0c14.704 0 26.624 11.92 26.624 26.624 0 14.704-11.92 26.624-26.624 26.624-14.704 0-26.624-11.92-26.624-26.624 0-14.704 11.92-26.624 26.624-26.624z'
@@ -1666,21 +1721,13 @@ export function ElevenLabsIcon(props: SVGProps<SVGSVGElement>) {
export function LinkupIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 24 24'
width='24'
height='24'
fill='none'
>
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'>
<g transform='translate(12, 12) scale(1.3) translate(-12, -12)'>
<path
d='M20.2 14.1c-.4-.3-1.6-.4-2.9-.2.5-1.4 1.3-3.9.1-5-.6-.5-1.5-.7-2.6-.5-.3 0-.6.1-1 .2-1.1-1.6-2.4-2.5-3.8-2.5-1.6 0-3.1 1-4.1 2.9-1.2 2.1-1.9 5.1-1.9 8.8v.03l.4.3c3-.9 7.5-2.3 10.7-2.9 0 .9.1 1.9.1 2.8v.03l.4.3c.1 0 5.4-1.7 5.3-3.3 0-.2-.1-.5-.3-.7zM19.9 14.7c.03.4-1.7 1.4-4 2.3.5-.7 1-1.6 1.3-2.5 1.4-.1 2.4-.1 2.7.2zM16.4 14.6c-.3.7-.7 1.4-1.2 2-.02-.6-.1-1.2-.2-1.8.4-.1.9-.1 1.4-.2zM16.5 9.4c.8.7.9 2.4.1 5.1-.5.1-1 .1-1.5.2-.3-2-.9-3.8-1.7-5.3.3-.1.6-.2.8-.2.9-.1 1.7.05 2.3.2zM9.5 6.8c1.2 0 2.3.7 3.2 2.1-2.8 1.1-5.9 3.4-8.4 7.8.2-5.1 1.9-9.9 5.2-9.9zM4.7 17c3.4-4.9 6.4-6.8 8.4-7.8.7 1.3 1.2 2.9 1.5 4.8-3.2.6-7.3 1.8-9.9 3z'
fill='currentColor'
stroke='currentColor'
strokeWidth='0.5'
strokeLinejoin='round'
fill='#000000'
/>
</g>
</svg>
)
}
@@ -3583,6 +3630,22 @@ export function ZendeskIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function ZoomIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
width='800px'
height='800px'
viewBox='0 0 32 32'
version='1.1'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M19.283 17.4c-0.367 0.374-0.879 0.606-1.444 0.606-1.117 0-2.023-0.906-2.023-2.023s0.906-2.023 2.023-2.023c0.929 0 1.712 0.626 1.949 1.479l0.003 0.014c0.045 0.159 0.071 0.341 0.071 0.53 0 0.552-0.221 1.052-0.579 1.417l0-0zM15.471 13.586c-0.648 0.615-1.052 1.483-1.052 2.446 0 1.861 1.509 3.37 3.37 3.37s3.37-1.509 3.37-3.37c0-1.54-1.033-2.838-2.444-3.241l-0.024-0.006c-0.27-0.078-0.581-0.123-0.902-0.123-0.899 0-1.716 0.352-2.32 0.925l0.002-0.001zM28.296 12.601c-0.802 0.001-1.522 0.352-2.016 0.909l-0.002 0.003c-0.496-0.562-1.219-0.915-2.023-0.915-0.563 0-1.086 0.173-1.519 0.468l0.009-0.006c-0.316-0.278-0.73-0.451-1.184-0.462l-0.002-0v6.742l0.337-0.016c0.544-0.014 0.981-0.451 0.995-0.993l0-0.001 0.016-0.337v-2.361l0.017-0.337c0-0.001 0-0.002 0-0.003 0-0.245 0.061-0.477 0.169-0.679l-0.004 0.008c0.238-0.405 0.671-0.672 1.166-0.672s0.928 0.267 1.162 0.664l0.003 0.006c0.103 0.196 0.164 0.428 0.165 0.675v0l0.017 0.339v2.361l0.016 0.336c0.022 0.54 0.454 0.972 0.991 0.995l0.002 0 0.337 0.016v-3.708l0.015-0.337c0-0.001 0-0.002 0-0.003 0-0.247 0.062-0.48 0.171-0.683l-0.004 0.008c0.238-0.403 0.67-0.669 1.165-0.669 0.496 0 0.929 0.268 1.164 0.666l0.003 0.006c0.102 0.195 0.162 0.427 0.162 0.673 0 0.001 0 0.001 0 0.002v-0l0.019 0.337v2.361l0.016 0.336c0.020 0.541 0.454 0.975 0.993 0.995l0.002 0 0.337 0.016v-4.045c-0.001-1.488-1.208-2.694-2.697-2.694-0.001 0-0.002 0-0.003 0h0zM12.206 17.4c-0.37 0.393-0.894 0.638-1.475 0.638-1.117 0-2.023-0.906-2.023-2.023s0.906-2.023 2.023-2.023c0.924 0 1.703 0.619 1.945 1.465l0.004 0.014c0.047 0.163 0.075 0.351 0.075 0.544 0 0.536-0.209 1.024-0.549 1.386l0.001-0.001zM10.78 12.6h-0.005c-1.86 0.001-3.367 1.509-3.367 3.368s1.508 3.368 3.368 3.368 3.368-1.508 3.368-3.368c0-1.86-1.507-3.367-3.366-3.368h-0zM6.734 18.008l-0.337-0.015h-3.035l4.044-4.045-0.016-0.337c-0.013-0.544-0.451-0.982-0.994-0.995l-0.001-0-0.337-0.016h-5.052l0.018 0.337c0.026 0.538 0.455 0.967 0.99 0.995l0.002 0 0.337 0.016h3.037l-4.049 4.045 0.017 0.336c0.019 0.541 0.453 0.975 0.992 0.995l0.002 0 0.337 0.016h5.056l-0.018-0.337c-0.024-0.539-0.455-0.969-0.991-0.993l-0.002-0z' />
</svg>
)
}
export function PylonIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -3690,6 +3753,30 @@ export function SmtpIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function SshIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
width='24'
height='24'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
>
<rect x='2' y='4' width='20' height='16' rx='2' />
<path d='M6 8h.01' />
<path d='M10 8h.01' />
<path d='M14 8h8' />
<path d='M6 12l3 3-3 3' />
<path d='M12 18h6' />
</svg>
)
}
export function ApifyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -3803,3 +3890,181 @@ export function McpIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function WordpressIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 25.925 25.925'>
<g fill='currentColor'>
<path d='M1.843,12.962c0,4.401,2.557,8.205,6.267,10.008L2.805,8.437C2.189,9.819,1.843,11.35,1.843,12.962z M20.469,12.4c0-1.374-0.493-2.326-0.917-3.066c-0.563-0.917-1.092-1.691-1.092-2.608c0-1.021,0.775-1.973,1.867-1.973c0.049,0,0.096,0.006,0.145,0.008c-1.979-1.813-4.615-2.919-7.509-2.919c-3.885,0-7.303,1.993-9.291,5.013c0.261,0.008,0.507,0.013,0.716,0.013c1.163,0,2.963-0.142,2.963-0.142c0.599-0.035,0.67,0.846,0.071,0.917c0,0-0.603,0.07-1.272,0.105l4.049,12.045l2.434-7.298l-1.732-4.747c-0.599-0.035-1.167-0.105-1.167-0.105c-0.6-0.036-0.529-0.953,0.07-0.917c0,0,1.836,0.142,2.928,0.142c1.163,0,2.964-0.142,2.964-0.142c0.6-0.035,0.67,0.846,0.071,0.917c0,0-0.604,0.07-1.272,0.105l4.018,11.953l1.11-3.706C20.187,14.55,20.469,13.353,20.469,12.4z M13.158,13.935l-3.337,9.694c0.997,0.293,2.05,0.453,3.142,0.453c1.294,0,2.537-0.224,3.693-0.63c-0.029-0.048-0.057-0.099-0.08-0.153L13.158,13.935z M22.72,7.627c0.049,0.354,0.075,0.734,0.075,1.144c0,1.128-0.212,2.396-0.846,3.982l-3.396,9.82c3.306-1.928,5.529-5.509,5.529-9.611C24.082,11.028,23.588,9.21,22.72,7.627z' />
<path d='M0,12.962c0,7.147,5.815,12.963,12.962,12.963c7.149,0,12.963-5.816,12.963-12.963S20.111,0,12.963,0S0,5.814,0,12.962z M0.594,12.962c0-6.819,5.548-12.368,12.368-12.368s12.369,5.549,12.369,12.368S19.782,25.33,12.963,25.33S0.594,19.781,0.594,12.962z' />
</g>
</svg>
)
}
export function AhrefsIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1065 1300'>
<path
fillRule='evenodd'
fill='#ff8800'
d='m111.3 0.7h953.4v1264.4h-256.9v-137.8h-21.2c-127 129.9-230.4 172.4-410.4 172.4-227.8 0-376.1-127.3-376.1-339.5v-58.3c0-288.9 121.8-392.4 511-421.5l241-18.6v-180.2h-640.8zm640.8 707.7l-193.4 18.6c-203.9 18.6-248.8 47.8-251.5 169.7-2.7 87.5 42.4 127.2 148.3 127.2 98 0 217.1-53 296.6-132.5z'
/>
</svg>
)
}
export function ShopifyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 48 48'
width='48px'
height='48px'
>
<path
fill='#7cb342'
d='M37.216,11.78c-0.023-0.211-0.211-0.305-0.351-0.305s-3.21-0.234-3.21-0.234s-2.132-2.132-2.39-2.343 c-0.234-0.234-0.68-0.164-0.867-0.117c-0.023,0-0.469,0.141-1.195,0.375c-0.726-2.086-1.968-3.984-4.194-3.984h-0.211 C24.187,4.375,23.391,4,22.735,4c-5.155,0-7.639,6.444-8.412,9.725c-2.015,0.633-3.445,1.054-3.609,1.125 c-1.125,0.351-1.148,0.375-1.289,1.429c-0.117,0.797-3.046,23.456-3.046,23.456L29.179,44l12.373-2.671 C41.575,41.282,37.24,11.991,37.216,11.78z M27.937,9.483c-0.562,0.164-1.242,0.375-1.921,0.609V9.671 c0-1.265-0.164-2.296-0.469-3.117C26.718,6.695,27.445,7.984,27.937,9.483L27.937,9.483z M24.117,6.812 c0.305,0.797,0.516,1.922,0.516,3.468v0.234c-1.265,0.398-2.601,0.797-3.984,1.242C21.422,8.804,22.899,7.351,24.117,6.812 L24.117,6.812z M22.617,5.359c0.234,0,0.469,0.094,0.656,0.234c-1.664,0.773-3.421,2.718-4.148,6.655 c-1.101,0.351-2.156,0.656-3.163,0.984C16.806,10.233,18.915,5.359,22.617,5.359z'
/>
<path
fill='#558b2f'
d='M36.865,11.428c-0.141,0-3.21-0.234-3.21-0.234s-2.132-2.132-2.39-2.343 C31.17,8.757,31.053,8.71,30.96,8.71L29.249,44l12.373-2.671c0,0-4.335-29.338-4.359-29.549 C37.169,11.569,37.005,11.475,36.865,11.428z'
/>
<path
fill='#fff'
d='M24.792,18.593l-1.475,4.449c0,0-1.337-0.715-2.927-0.715c-2.374,0-2.489,1.498-2.489,1.867 c0,2.028,5.301,2.812,5.301,7.583c0,3.757-2.374,6.177-5.578,6.177c-3.872,0-5.808-2.397-5.808-2.397l1.037-3.411 c0,0,2.028,1.752,3.734,1.752c1.129,0,1.59-0.876,1.59-1.521c0-2.651-4.333-2.766-4.333-7.145c0-3.665,2.628-7.214,7.952-7.214 C23.777,17.994,24.792,18.593,24.792,18.593z'
/>
</svg>
)
}
export function BoxCompanyIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 41 22'>
<path
d='M39.7 19.2c.5.7.4 1.6-.2 2.1-.7.5-1.7.4-2.2-.2l-3.5-4.5-3.4 4.4c-.5.7-1.5.7-2.2.2-.7-.5-.8-1.4-.3-2.1l4-5.2-4-5.2c-.5-.7-.3-1.7.3-2.2.7-.5 1.7-.3 2.2.3l3.4 4.5L37.3 7c.5-.7 1.4-.8 2.2-.3.7.5.7 1.5.2 2.2L35.8 14l3.9 5.2zm-18.2-.6c-2.6 0-4.7-2-4.7-4.6 0-2.5 2.1-4.6 4.7-4.6s4.7 2.1 4.7 4.6c-.1 2.6-2.2 4.6-4.7 4.6zm-13.8 0c-2.6 0-4.7-2-4.7-4.6 0-2.5 2.1-4.6 4.7-4.6s4.7 2.1 4.7 4.6c0 2.6-2.1 4.6-4.7 4.6zM21.5 6.4c-2.9 0-5.5 1.6-6.8 4-1.3-2.4-3.9-4-6.9-4-1.8 0-3.4.6-4.7 1.5V1.5C3.1.7 2.4 0 1.6 0 .7 0 0 .7 0 1.5v12.6c.1 4.2 3.5 7.5 7.7 7.5 3 0 5.6-1.7 6.9-4.1 1.3 2.4 3.9 4.1 6.8 4.1 4.3 0 7.8-3.4 7.8-7.7.1-4.1-3.4-7.5-7.7-7.5z'
fill='currentColor'
/>
</svg>
)
}
export function DropboxIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 43 40'>
<path
d='m12.5 0l-12.5 8.1 8.7 7 12.5-7.8-8.7-7.3zm-12.5 21.9l12.5 8.2 8.7-7.3-12.5-7.7-8.7 6.8zm21.2 0.9l8.8 7.3 12.4-8.1-8.6-6.9-12.6 7.7zm21.2-14.7l-12.4-8.1-8.8 7.3 12.6 7.8 8.6-7zm-21.1 16.3l-8.8 7.3-3.7-2.5v2.8l12.5 7.5 12.5-7.5v-2.8l-3.8 2.5-8.7-7.3z'
fill='currentColor'
/>
</svg>
)
}
export function ElasticsearchIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
<path
fill='#343741'
d='M4 64c0 5.535.777 10.879 2.098 16H84c8.836 0 16-7.164 16-16s-7.164-16-16-16H6.098A63.738 63.738 0 0 0 4 64'
/>
<path
fill='#fec514'
d='M111.695 30.648A61.485 61.485 0 0 0 117.922 24C106.188 9.379 88.199 0 68 0 42.715 0 20.957 14.71 10.574 36H98.04a20.123 20.123 0 0 0 13.652-5.352'
/>
<path
fill='#00bfb3'
d='M98.04 92H10.577C20.961 113.29 42.715 128 68 128c20.2 0 38.188-9.383 49.922-24a61.1 61.1 0 0 0-6.227-6.648A20.133 20.133 0 0 0 98.04 92'
/>
</svg>
)
}
export function GitlabIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 380 380'>
<path
fill='#e24329'
d='M265.26416,174.37243l-.2134-.55822-21.19899-55.30908c-.4236-1.08359-1.18542-1.99642-2.17699-2.62689-.98837-.63373-2.14749-.93253-3.32305-.87014-1.1689.06239-2.29195.48925-3.20809,1.21821-.90957.73554-1.56629,1.73047-1.87493,2.85346l-14.31327,43.80662h-57.90965l-14.31327-43.80662c-.30864-1.12299-.96536-2.11791-1.87493-2.85346-.91614-.72895-2.03911-1.15582-3.20809-1.21821-1.17548-.06239-2.33468.23641-3.32297.87014-.99166.63047-1.75348,1.5433-2.17707,2.62689l-21.19891,55.31237-.21348.55493c-6.28158,16.38521-.92929,34.90803,13.05891,45.48782.02621.01641.04922.03611.07552.05582l.18719.14119,32.29094,24.17392,15.97151,12.09024,9.71951,7.34871c2.34117,1.77316,5.57877,1.77316,7.92002,0l9.71943-7.34871,15.96822-12.09024,32.48142-24.31511c.02958-.02299.05588-.04269.08538-.06568,13.97834-10.57977,19.32735-29.09604,13.04905-45.47796Z'
/>
<path
fill='#fc6d26'
d='M265.26416,174.37243l-.2134-.55822c-10.5174,2.16062-20.20405,6.6099-28.49844,12.81593-.1346.0985-25.20497,19.05805-46.55171,35.19699,15.84998,11.98517,29.6477,22.40405,29.6477,22.40405l32.48142-24.31511c.02958-.02299.05588-.04269.08538-.06568,13.97834-10.57977,19.32735-29.09604,13.04905-45.47796Z'
/>
<path
fill='#fca326'
d='M160.34962,244.23117l15.97151,12.09024,9.71951,7.34871c2.34117,1.77316,5.57877,1.77316,7.92002,0l9.71943-7.34871,15.96822-12.09024s-13.79772-10.41888-29.6477-22.40405c-15.85327,11.98517-29.65099,22.40405-29.65099,22.40405Z'
/>
<path
fill='#fc6d26'
d='M143.44561,186.63014c-8.29111-6.20274-17.97446-10.65531-28.49507-12.81264l-.21348.55493c-6.28158,16.38521-.92929,34.90803,13.05891,45.48782.02621.01641.04922.03611.07552.05582l.18719.14119,32.29094,24.17392s13.79772-10.41888,29.65099-22.40405c-21.34673-16.13894-46.42031-35.09848-46.55499-35.19699Z'
/>
</svg>
)
}
export function SSHIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 48 48'
width='48px'
height='48px'
>
<path
fill='#7cb342'
d='M37.216,11.78c-0.023-0.211-0.211-0.305-0.351-0.305s-3.21-0.234-3.21-0.234s-2.132-2.132-2.39-2.343 c-0.234-0.234-0.68-0.164-0.867-0.117c-0.023,0-0.469,0.141-1.195,0.375c-0.726-2.086-1.968-3.984-4.194-3.984h-0.211 C24.187,4.375,23.391,4,22.735,4c-5.155,0-7.639,6.444-8.412,9.725c-2.015,0.633-3.445,1.054-3.609,1.125 c-1.125,0.351-1.148,0.375-1.289,1.429c-0.117,0.797-3.046,23.456-3.046,23.456L29.179,44l12.373-2.671 C41.575,41.282,37.24,11.991,37.216,11.78z M27.937,9.483c-0.562,0.164-1.242,0.375-1.921,0.609V9.671 c0-1.265-0.164-2.296-0.469-3.117C26.718,6.695,27.445,7.984,27.937,9.483L27.937,9.483z M24.117,6.812 c0.305,0.797,0.516,1.922,0.516,3.468v0.234c-1.265,0.398-2.601,0.797-3.984,1.242C21.422,8.804,22.899,7.351,24.117,6.812 L24.117,6.812z M22.617,5.359c0.234,0,0.469,0.094,0.656,0.234c-1.664,0.773-3.421,2.718-4.148,6.655 c-1.101,0.351-2.156,0.656-3.163,0.984C16.806,10.233,18.915,5.359,22.617,5.359z'
/>
<path
fill='#558b2f'
d='M36.865,11.428c-0.141,0-3.21-0.234-3.21-0.234s-2.132-2.132-2.39-2.343 C31.17,8.757,31.053,8.71,30.96,8.71L29.249,44l12.373-2.671c0,0-4.335-29.338-4.359-29.549 C37.169,11.569,37.005,11.475,36.865,11.428z'
/>
<path
fill='#fff'
d='M24.792,18.593l-1.475,4.449c0,0-1.337-0.715-2.927-0.715c-2.374,0-2.489,1.498-2.489,1.867 c0,2.028,5.301,2.812,5.301,7.583c0,3.757-2.374,6.177-5.578,6.177c-3.872,0-5.808-2.397-5.808-2.397l1.037-3.411 c0,0,2.028,1.752,3.734,1.752c1.129,0,1.59-0.876,1.59-1.521c0-2.651-4.333-2.766-4.333-7.145c0-3.665,2.628-7.214,7.952-7.214 C23.777,17.994,24.792,18.593,24.792,18.593z'
/>
</svg>
)
}
export function DatadogIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'>
<g fill='currentColor'>
<path d='M57.705 33.717l1.575 20.1L31.4 58.82 29 38.72l3.983-.556c.648.278 1.112.37 1.853.556 1.204.278 2.594.648 4.63-.463.463-.278 1.482-1.112 1.853-1.667zm-43.073 23.9c-.463-.74-1.297-1.575-2.594-2.594-1.853-1.482-1.204-4.076-.093-5.65 1.4-2.686 8.707-6.206 8.337-10.56-.185-1.575-.37-3.613-1.853-5.095-.093.556 0 1.112 0 1.112s-.648-.74-.926-1.853c-.278-.37-.556-.556-.834-1.112-.185.648-.185 1.297-.185 1.297s-.463-1.204-.556-2.13c-.278.463-.37 1.297-.37 1.297s-.648-1.853-.463-2.87c-.278-.834-1.204-2.594-.926-6.484 1.575 1.112 5.095.834 6.484-1.204.463-.648.74-2.5-.185-6.114-.648-2.316-2.223-5.743-2.87-7.04l-.093.093c.37 1.02 1.02 3.242 1.297 4.354.74 3.242 1.02 4.354.648 5.836-.278 1.297-1.02 2.13-2.964 3.057-1.853.926-4.446-1.4-4.54-1.482-1.853-1.482-3.242-3.9-3.427-5.002-.185-1.297.74-2.038 1.204-3.057-.648.185-1.4.556-1.4.556s.834-.926 1.945-1.667a9.43 9.43 0 0 0 1.204-.834H10.28s1.112-.648 2.316-1.02H10.93l4.446-1.945c1.4-.556 2.686-.37 3.427.648 1.02 1.4 2.038 2.13 4.168 2.686 1.297-.556 1.76-.926 3.427-1.4 1.482-1.667 2.686-1.853 2.686-1.853s-.834.74-.926 1.575c.834-.648 1.76-1.204 1.76-1.204s-.37.463-.648 1.112l.093.093c1.02-.556 2.13-1.02 2.13-1.02s-.37.37-.74.926c.74 0 2.223 0 2.78.093 3.427.093 4.168-3.613 5.465-4.168 1.667-.556 2.408-.926 5.187 1.853 2.408 2.408 4.26 6.577 3.335 7.503-.74.74-2.316-.278-4.076-2.408-.926-1.112-1.575-2.5-1.945-4.168-.185-1.4-1.204-2.223-1.204-2.223s.648 1.4.648 2.594c0 .648.093 3.057 1.112 4.446-.093.185-.185 1.02-.278 1.112-1.204-1.482-3.9-2.5-4.26-2.87 1.482 1.204 4.817 3.9 6.114 6.484 1.204 2.5.463 4.724 1.112 5.28.185.185 2.594 3.15 3.057 4.724.834 2.686.093 5.465-1.02 7.132l-2.964.463a3.92 3.92 0 0 1-1.112-.371c.185-.37.648-1.297.648-1.482l-.185-.278c-.926 1.297-2.5 2.594-3.798 3.335-1.667.926-3.613.834-4.9.37-3.613-1.112-7.04-3.52-7.78-4.168 0 0 0 .463.093.648a28.07 28.07 0 0 0 5.002 4.168l-4.26.463 2.038 15.747c-.926.093-1.02.185-2.038.37-.834-3.057-2.5-5.002-4.354-6.206-1.575-1.02-3.798-1.297-5.928-.834l-.093.185c1.482-.185 3.242.093 5.002 1.204s3.15 3.983 3.705 5.65c.648 2.223 1.112 4.54-.648 7.04-1.297 1.76-5.002 2.78-7.966.648.834 1.297 1.853 2.316 3.335 2.5 2.13.278 4.168-.093 5.65-1.482 1.204-1.204 1.853-3.798 1.667-6.577l1.945-.278.648 4.9L62.06 56.7l-2.78-25.195-1.575.278L54.556 0 1.942 6.114l6.484 52.428z' />
<path d='M39.665 30.06c1.4 1.02 2.594 1.667 3.798 1.575.74-.093 1.482-1.297 1.945-2.408.37-.74.37-1.575-.185-1.853-.278-.093-1.4-.093-2.223 0-1.575.185-3.15.74-3.52 1.02-.556.37-.278 1.297.185 1.667m.37-10.84v.093l.093.185c.37.74.74 1.482 1.482 1.853.185 0 .37-.093.556-.093.648 0 1.02.093 1.297.185v-.556c-.093-.926.185-2.594-1.667-3.52-.74-.37-1.667-.185-2.038.185h.185c.463.185.185.37.093.556-.093.37-.185.463 0 1.112' />
<path d='M31.698 19.407c.463-.37-2.13-.926-4.168.37-1.482 1.02-1.482 3.15-.093 4.354a1.27 1.27 0 0 1 .37.278c.37-.185.926-.37 1.4-.556.926-.278 1.667-.463 2.316-.556.278-.37.648-.926.556-1.945-.093-1.4-1.204-1.112-.37-1.945m15.098 22.5l-4.446 7.4-5.187-1.575-4.54 6.947.185 2.223 24.825-4.54-1.482-15.47-4.076 8.522z' />
</g>
</svg>
)
}
export function KalshiIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 78 20' fill='currentColor' xmlns='http://www.w3.org/2000/svg'>
<path d='M40.1043 0H36.0332V19.9986H40.1043V0Z' />
<path d='M0.416887 0.0221237H4.73849V8.99348L12.818 0.0221237H18.0582L10.6468 8.24586L18.5384 20H13.3608L7.59868 11.5686L4.73849 14.7459V20H0.416887V0.0221237Z' />
<path
fillRule='evenodd'
clipRule='evenodd'
d='M34.4675 19.8117H32.4007C30.5426 19.8117 29.624 19.0017 29.6658 17.4027C29.1229 18.2334 28.4549 18.8771 27.6824 19.3132C26.8891 19.7494 25.9496 19.9778 24.8222 19.9778C23.1729 19.9778 21.8368 19.604 20.8138 18.8564C19.8117 18.088 19.3106 17.0289 19.3106 15.6582C19.3106 14.1007 19.8952 12.8962 21.0434 12.0656C22.2126 11.2141 23.9036 10.778 26.1166 10.778H29.0603V10.0719C29.0603 9.40737 28.8098 8.8882 28.3087 8.49362C27.8077 8.09905 27.1396 7.89138 26.2836 7.89138C25.532 7.89138 24.9266 8.05752 24.4464 8.36902C23.9662 8.70129 23.674 9.1374 23.5905 9.67734H19.6446C19.7699 8.18212 20.4589 7.01916 21.6697 6.18848C22.8806 5.3578 24.4882 4.92169 26.4924 4.92169C28.5801 4.92169 30.2086 5.37857 31.3359 6.29232C32.4842 7.20607 33.0688 8.53516 33.0688 10.2588V15.4298C33.0688 15.7828 33.1523 16.0321 33.2984 16.1774C33.4445 16.302 33.6951 16.3851 34.0291 16.3851H34.4675V19.8117ZM26.0749 13.4569C25.2398 13.4569 24.5717 13.6231 24.0915 13.9761C23.6322 14.3084 23.4026 14.7653 23.4026 15.3675C23.4026 15.8867 23.5905 16.2813 23.9871 16.5928C24.3838 16.9043 24.9266 17.0496 25.5947 17.0496C26.6594 17.0496 27.4945 16.7589 28.1 16.1567C28.7054 15.5544 29.0394 14.7445 29.0603 13.7269V13.4569H26.0749Z'
/>
<path d='M45.5115 14.9314C45.5741 15.5752 45.8873 16.0944 46.4718 16.5097C47.0564 16.9043 47.7871 17.112 48.6848 17.112C49.5408 17.112 50.2297 16.9874 50.7308 16.7174C51.2318 16.4266 51.4824 16.0321 51.4824 15.5129C51.4824 15.1391 51.3571 14.8483 51.1275 14.6614C50.8978 14.4745 50.5638 14.3292 50.1462 14.2669C49.7287 14.163 49.0397 14.0592 48.0794 13.9554C46.7641 13.7892 45.6785 13.5608 44.8225 13.2908C43.9665 13.0208 43.2567 12.6055 42.7557 12.024C42.2337 11.4426 41.9832 10.6949 41.9832 9.73966C41.9832 8.78438 42.2337 7.9537 42.7557 7.22685C43.2985 6.47924 44.0501 5.91853 45.0104 5.50319C45.9708 5.10861 47.0773 4.90094 48.3299 4.90094C50.355 4.92171 51.9625 5.35782 53.1943 6.1885C54.4469 7.01918 55.115 8.18213 55.2194 9.67736H51.3571C51.2945 9.11665 51.0022 8.68054 50.4594 8.3275C49.9374 7.97446 49.2694 7.78756 48.4343 7.78756C47.6618 7.78756 47.0355 7.93293 46.5553 8.22367C46.096 8.5144 45.8664 8.88821 45.8664 9.36585C45.8664 9.71889 45.9916 9.9681 46.2422 10.1342C46.4927 10.3004 46.8267 10.425 47.2234 10.508C47.6201 10.5911 48.309 10.6742 49.2485 10.7572C51.2527 10.9857 52.7768 11.4218 53.8206 12.0448C54.9062 12.647 55.4282 13.7062 55.4282 15.2222C55.4282 16.1774 55.1359 17.0081 54.5722 17.735C54.0085 18.4618 53.2361 19.0225 52.2131 19.4171C51.211 19.7909 50.0418 19.9986 48.7266 19.9986C46.6806 19.9986 44.9895 19.5417 43.716 18.6487C42.4216 17.735 41.7535 16.4889 41.67 14.9314H45.5115Z' />
<path d='M69.7503 6.72852C68.623 5.6694 67.2033 5.12946 65.4496 5.12946C63.6333 5.12946 62.1719 5.794 61.0654 7.12309V0H56.9943V19.9986H61.0654V12.4602C61.0654 11.1934 61.3368 10.2174 61.9213 9.5113C62.5059 8.80522 63.3201 8.45218 64.364 8.45218C65.3661 8.45218 66.1177 8.78445 66.6187 9.42823C67.1198 10.0512 67.3703 10.965 67.3703 12.1902V19.9986H71.4414V12.0241C71.4414 9.55283 70.8777 7.78763 69.7503 6.72852Z' />
<path d='M73.0068 5.29551H77.0779V19.9778H73.0068V5.29551Z' />
<path d='M76.473 0.581477C76.0972 0.20767 75.617 0 75.0324 0C74.4688 0 73.9677 0.20767 73.571 0.581477C73.1952 0.955283 72.9865 1.41216 72.9865 1.97287C72.9865 2.53358 73.1952 3.01122 73.571 3.38503C73.9677 3.75883 74.4688 3.9665 75.0324 3.9665C75.5961 3.9665 76.0972 3.7796 76.473 3.38503C76.8488 2.99045 77.0575 2.53358 77.0575 1.97287C77.0575 1.41216 76.8488 0.934516 76.473 0.581477Z' />
</svg>
)
}
export function PolymarketIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='51 209 123 155'>
<path
fill='currentColor'
d='M173.2,363.2L51.1,328.3v-83.7l122.1-34.9V363.2z M161.4,296.2l-89.8,25.6l89.8,25.6L161.4,296.2z M62.9,260.8v51.3l89.8-25.6L62.9,260.8z M161.4,225.3L71.6,251l89.8,25.6L161.4,225.3z'
/>
</svg>
)
}

View File

@@ -135,6 +135,13 @@ export function useConnectOAuthService() {
return { success: true }
}
// Shopify requires a custom OAuth flow with shop domain input
if (providerId === 'shopify') {
const returnUrl = encodeURIComponent(callbackURL)
window.location.href = `/api/auth/shopify/authorize?returnUrl=${returnUrl}`
return { success: true }
}
await client.oauth2.link({
providerId,
callbackURL,

View File

@@ -1423,6 +1423,66 @@ export const auth = betterAuth({
},
},
{
providerId: 'dropbox',
clientId: env.DROPBOX_CLIENT_ID as string,
clientSecret: env.DROPBOX_CLIENT_SECRET as string,
authorizationUrl: 'https://www.dropbox.com/oauth2/authorize',
tokenUrl: 'https://api.dropboxapi.com/oauth2/token',
scopes: [
'account_info.read',
'files.metadata.read',
'files.metadata.write',
'files.content.read',
'files.content.write',
'sharing.read',
'sharing.write',
],
responseType: 'code',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/dropbox`,
pkce: true,
accessType: 'offline',
prompt: 'consent',
getUserInfo: async (tokens) => {
try {
const response = await fetch(
'https://api.dropboxapi.com/2/users/get_current_account',
{
method: 'POST',
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
}
)
if (!response.ok) {
const errorText = await response.text()
logger.error('Dropbox API error:', {
status: response.status,
statusText: response.statusText,
body: errorText,
})
throw new Error(`Dropbox API error: ${response.status} ${response.statusText}`)
}
const data = await response.json()
return {
id: data.account_id,
email: data.email,
name: data.name?.display_name || data.email,
emailVerified: data.email_verified || false,
createdAt: new Date(),
updatedAt: new Date(),
image: data.profile_photo_url || undefined,
}
} catch (error) {
logger.error('Error in getUserInfo:', error)
throw error
}
},
},
{
providerId: 'asana',
clientId: env.ASANA_CLIENT_ID as string,
@@ -1634,6 +1694,117 @@ export const auth = betterAuth({
}
},
},
// Zoom provider
{
providerId: 'zoom',
clientId: env.ZOOM_CLIENT_ID as string,
clientSecret: env.ZOOM_CLIENT_SECRET as string,
authorizationUrl: 'https://zoom.us/oauth/authorize',
tokenUrl: 'https://zoom.us/oauth/token',
userInfoUrl: 'https://api.zoom.us/v2/users/me',
scopes: [
'user:read:user',
'meeting:write:meeting',
'meeting:read:meeting',
'meeting:read:list_meetings',
'meeting:update:meeting',
'meeting:delete:meeting',
'meeting:read:invitation',
'meeting:read:list_past_participants',
'cloud_recording:read:list_user_recordings',
'cloud_recording:read:list_recording_files',
'cloud_recording:delete:recording_file',
],
responseType: 'code',
accessType: 'offline',
authentication: 'basic',
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/zoom`,
getUserInfo: async (tokens) => {
try {
logger.info('Fetching Zoom user profile')
const response = await fetch('https://api.zoom.us/v2/users/me', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
})
if (!response.ok) {
logger.error('Failed to fetch Zoom user info', {
status: response.status,
statusText: response.statusText,
})
throw new Error('Failed to fetch user info')
}
const profile = await response.json()
return {
id: profile.id,
name:
`${profile.first_name || ''} ${profile.last_name || ''}`.trim() || 'Zoom User',
email: profile.email || `${profile.id}@zoom.user`,
emailVerified: profile.verified === 1,
image: profile.pic_url || undefined,
createdAt: new Date(),
updatedAt: new Date(),
}
} catch (error) {
logger.error('Error in Zoom getUserInfo:', { error })
return null
}
},
},
// WordPress.com provider
{
providerId: 'wordpress',
clientId: env.WORDPRESS_CLIENT_ID as string,
clientSecret: env.WORDPRESS_CLIENT_SECRET as string,
authorizationUrl: 'https://public-api.wordpress.com/oauth2/authorize',
tokenUrl: 'https://public-api.wordpress.com/oauth2/token',
userInfoUrl: 'https://public-api.wordpress.com/rest/v1.1/me',
scopes: ['global'],
responseType: 'code',
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/wordpress`,
getUserInfo: async (tokens) => {
try {
logger.info('Fetching WordPress.com user profile')
const response = await fetch('https://public-api.wordpress.com/rest/v1.1/me', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
})
if (!response.ok) {
logger.error('Failed to fetch WordPress.com user info', {
status: response.status,
statusText: response.statusText,
})
throw new Error('Failed to fetch user info')
}
const profile = await response.json()
return {
id: profile.ID?.toString() || profile.id?.toString(),
name: profile.display_name || profile.username || 'WordPress User',
email: profile.email || `${profile.username}@wordpress.com`,
emailVerified: profile.email_verified || false,
image: profile.avatar_URL || undefined,
createdAt: new Date(),
updatedAt: new Date(),
}
} catch (error) {
logger.error('Error in WordPress.com getUserInfo:', { error })
return null
}
},
},
],
}),
// Include SSO plugin when enabled

View File

@@ -210,6 +210,8 @@ export const env = createEnv({
PIPEDRIVE_CLIENT_SECRET: z.string().optional(), // Pipedrive OAuth client secret
LINEAR_CLIENT_ID: z.string().optional(), // Linear OAuth client ID
LINEAR_CLIENT_SECRET: z.string().optional(), // Linear OAuth client secret
DROPBOX_CLIENT_ID: z.string().optional(), // Dropbox OAuth client ID
DROPBOX_CLIENT_SECRET: z.string().optional(), // Dropbox OAuth client secret
SLACK_CLIENT_ID: z.string().optional(), // Slack OAuth client ID
SLACK_CLIENT_SECRET: z.string().optional(), // Slack OAuth client secret
REDDIT_CLIENT_ID: z.string().optional(), // Reddit OAuth client ID
@@ -219,6 +221,12 @@ export const env = createEnv({
TRELLO_API_KEY: z.string().optional(), // Trello API Key
LINKEDIN_CLIENT_ID: z.string().optional(), // LinkedIn OAuth client ID
LINKEDIN_CLIENT_SECRET: z.string().optional(), // LinkedIn OAuth client secret
SHOPIFY_CLIENT_ID: z.string().optional(), // Shopify OAuth client ID
SHOPIFY_CLIENT_SECRET: z.string().optional(), // Shopify OAuth client secret
ZOOM_CLIENT_ID: z.string().optional(), // Zoom OAuth client ID
ZOOM_CLIENT_SECRET: z.string().optional(), // Zoom OAuth client secret
WORDPRESS_CLIENT_ID: z.string().optional(), // WordPress.com OAuth client ID
WORDPRESS_CLIENT_SECRET: z.string().optional(), // WordPress.com OAuth client secret
// E2B Remote Code Execution
E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution

View File

@@ -4,6 +4,7 @@ import {
AsanaIcon,
ConfluenceIcon,
// DiscordIcon,
DropboxIcon,
GithubIcon,
GmailIcon,
GoogleCalendarIcon,
@@ -27,12 +28,15 @@ import {
PipedriveIcon,
RedditIcon,
SalesforceIcon,
ShopifyIcon,
SlackIcon,
// SupabaseIcon,
TrelloIcon,
WealthboxIcon,
WebflowIcon,
WordpressIcon,
xIcon,
ZoomIcon,
} from '@/components/icons'
import { env } from '@/lib/core/config/env'
import { createLogger } from '@/lib/logs/console/logger'
@@ -49,6 +53,7 @@ export type OAuthProvider =
| 'notion'
| 'jira'
// | 'discord'
| 'dropbox'
| 'microsoft'
| 'linear'
| 'slack'
@@ -61,6 +66,9 @@ export type OAuthProvider =
| 'hubspot'
| 'salesforce'
| 'linkedin'
| 'shopify'
| 'zoom'
| 'wordpress'
| string
export type OAuthService =
@@ -80,6 +88,7 @@ export type OAuthService =
| 'notion'
| 'jira'
// | 'discord'
| 'dropbox'
| 'microsoft-excel'
| 'microsoft-teams'
| 'microsoft-planner'
@@ -97,6 +106,9 @@ export type OAuthService =
| 'hubspot'
| 'salesforce'
| 'linkedin'
| 'shopify'
| 'zoom'
| 'wordpress'
export interface OAuthProviderConfig {
id: OAuthProvider
name: string
@@ -538,6 +550,55 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
},
defaultService: 'linear',
},
dropbox: {
id: 'dropbox',
name: 'Dropbox',
icon: (props) => DropboxIcon(props),
services: {
dropbox: {
id: 'dropbox',
name: 'Dropbox',
description: 'Upload, download, share, and manage files in Dropbox.',
providerId: 'dropbox',
icon: (props) => DropboxIcon(props),
baseProviderIcon: (props) => DropboxIcon(props),
scopes: [
'account_info.read',
'files.metadata.read',
'files.metadata.write',
'files.content.read',
'files.content.write',
'sharing.read',
'sharing.write',
],
},
},
defaultService: 'dropbox',
},
shopify: {
id: 'shopify',
name: 'Shopify',
icon: (props) => ShopifyIcon(props),
services: {
shopify: {
id: 'shopify',
name: 'Shopify',
description: 'Manage products, orders, and customers in your Shopify store.',
providerId: 'shopify',
icon: (props) => ShopifyIcon(props),
baseProviderIcon: (props) => ShopifyIcon(props),
scopes: [
'write_products',
'write_orders',
'write_customers',
'write_inventory',
'read_locations',
'write_merchant_managed_fulfillment_orders',
],
},
},
defaultService: 'shopify',
},
slack: {
id: 'slack',
name: 'Slack',
@@ -769,6 +830,52 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
},
defaultService: 'salesforce',
},
zoom: {
id: 'zoom',
name: 'Zoom',
icon: (props) => ZoomIcon(props),
services: {
zoom: {
id: 'zoom',
name: 'Zoom',
description: 'Create and manage Zoom meetings, users, and recordings.',
providerId: 'zoom',
icon: (props) => ZoomIcon(props),
baseProviderIcon: (props) => ZoomIcon(props),
scopes: [
'user:read:user',
'meeting:write:meeting',
'meeting:read:meeting',
'meeting:read:list_meetings',
'meeting:update:meeting',
'meeting:delete:meeting',
'meeting:read:invitation',
'meeting:read:list_past_participants',
'cloud_recording:read:list_user_recordings',
'cloud_recording:read:list_recording_files',
'cloud_recording:delete:recording_file',
],
},
},
defaultService: 'zoom',
},
wordpress: {
id: 'wordpress',
name: 'WordPress',
icon: (props) => WordpressIcon(props),
services: {
wordpress: {
id: 'wordpress',
name: 'WordPress.com',
description: 'Manage posts, pages, media, comments, and more on WordPress.com sites.',
providerId: 'wordpress',
icon: (props) => WordpressIcon(props),
baseProviderIcon: (props) => WordpressIcon(props),
scopes: ['global'],
},
},
defaultService: 'wordpress',
},
}
export function getServiceByProviderAndId(
@@ -1149,6 +1256,18 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
useBasicAuth: true,
}
}
case 'dropbox': {
const { clientId, clientSecret } = getCredentials(
env.DROPBOX_CLIENT_ID,
env.DROPBOX_CLIENT_SECRET
)
return {
tokenEndpoint: 'https://api.dropboxapi.com/oauth2/token',
clientId,
clientSecret,
useBasicAuth: false,
}
}
case 'slack': {
const { clientId, clientSecret } = getCredentials(
env.SLACK_CLIENT_ID,
@@ -1265,6 +1384,46 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
supportsRefreshTokenRotation: false,
}
}
case 'shopify': {
// Shopify access tokens don't expire and don't support refresh tokens
// This configuration is provided for completeness but won't be used for token refresh
const { clientId, clientSecret } = getCredentials(
env.SHOPIFY_CLIENT_ID,
env.SHOPIFY_CLIENT_SECRET
)
return {
tokenEndpoint: 'https://accounts.shopify.com/oauth/token',
clientId,
clientSecret,
useBasicAuth: false,
supportsRefreshTokenRotation: false,
}
}
case 'zoom': {
const { clientId, clientSecret } = getCredentials(env.ZOOM_CLIENT_ID, env.ZOOM_CLIENT_SECRET)
return {
tokenEndpoint: 'https://zoom.us/oauth/token',
clientId,
clientSecret,
useBasicAuth: true,
supportsRefreshTokenRotation: false,
}
}
case 'wordpress': {
// WordPress.com does NOT support refresh tokens
// Users will need to re-authorize when tokens expire (~2 weeks)
const { clientId, clientSecret } = getCredentials(
env.WORDPRESS_CLIENT_ID,
env.WORDPRESS_CLIENT_SECRET
)
return {
tokenEndpoint: 'https://public-api.wordpress.com/oauth2/token',
clientId,
clientSecret,
useBasicAuth: false,
supportsRefreshTokenRotation: false,
}
}
default:
throw new Error(`Unsupported provider: ${provider}`)
}

View File

@@ -1,6 +1,6 @@
import type { Logger } from '@/lib/logs/console/logger'
import type { StorageContext } from '@/lib/uploads'
import { ACCEPTED_FILE_TYPES } from '@/lib/uploads/utils/validation'
import { ACCEPTED_FILE_TYPES, SUPPORTED_DOCUMENT_EXTENSIONS } from '@/lib/uploads/utils/validation'
import type { UserFile } from '@/executor/types'
export interface FileAttachment {
@@ -258,13 +258,24 @@ export function validateKnowledgeBaseFile(
return `File "${file.name}" is too large. Maximum size is ${maxSizeMB}MB.`
}
if (!ACCEPTED_FILE_TYPES.includes(file.type)) {
return `File "${file.name}" has an unsupported format. Please use PDF, DOC, DOCX, TXT, CSV, XLS, XLSX, MD, PPT, PPTX, HTML, JSON, YAML, or YML files.`
// Check MIME type first
if (ACCEPTED_FILE_TYPES.includes(file.type)) {
return null
}
// Fallback: check file extension (browsers often misidentify file types like .md)
const extension = getFileExtension(file.name)
if (
SUPPORTED_DOCUMENT_EXTENSIONS.includes(
extension as (typeof SUPPORTED_DOCUMENT_EXTENSIONS)[number]
)
) {
return null
}
return `File "${file.name}" has an unsupported format. Please use PDF, DOC, DOCX, TXT, CSV, XLS, XLSX, MD, PPT, PPTX, HTML, JSON, YAML, or YML files.`
}
/**
* Extract storage key from a file path
*/

View File

@@ -50,7 +50,13 @@ export const SUPPORTED_MIME_TYPES: Record<SupportedDocumentExtension, string[]>
'application/octet-stream',
],
txt: ['text/plain', 'text/x-plain', 'application/txt'],
md: ['text/markdown', 'text/x-markdown', 'text/plain', 'application/markdown'],
md: [
'text/markdown',
'text/x-markdown',
'text/plain',
'application/markdown',
'application/octet-stream',
],
xlsx: [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/octet-stream',

View File

@@ -122,6 +122,7 @@
"resend": "^4.1.2",
"sharp": "0.34.3",
"socket.io": "^4.8.1",
"ssh2": "^1.17.0",
"stripe": "18.5.0",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
@@ -144,6 +145,7 @@
"@types/prismjs": "^1.26.5",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/ssh2": "^1.15.5",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.8",
"autoprefixer": "10.4.21",

View File

@@ -195,7 +195,7 @@ describe('Validation Integration Tests', () => {
} as any,
mergedParams
)
}).toThrow('"Url" is required for Jina Reader')
}).toThrow('Url is required for Jina Reader')
}
)
@@ -314,7 +314,7 @@ describe('Validation Integration Tests', () => {
} as any,
mergedParams
)
}).toThrow('"Subreddit" is required for Reddit Posts')
}).toThrow('Subreddit is required for Reddit Posts')
})
it.concurrent('complete success: all required fields provided correctly', () => {

View File

@@ -0,0 +1,116 @@
import type { AhrefsBacklinksParams, AhrefsBacklinksResponse } from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksResponse> = {
id: 'ahrefs_backlinks',
name: 'Ahrefs Backlinks',
description:
'Get a list of backlinks pointing to a target domain or URL. Returns details about each backlink including source URL, anchor text, and domain rating.',
version: '1.0.0',
params: {
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The target domain or URL to analyze',
},
mode: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match)',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of results to return (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of results to skip for pagination',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ahrefs API Key',
},
},
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/backlinks')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to get backlinks')
}
const backlinks = (data.backlinks || []).map((link: any) => ({
urlFrom: link.url_from || '',
urlTo: link.url_to || '',
anchor: link.anchor || '',
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
isDofollow: link.is_dofollow ?? link.dofollow ?? false,
firstSeen: link.first_seen || '',
lastVisited: link.last_visited || '',
}))
return {
success: true,
output: {
backlinks,
},
}
},
outputs: {
backlinks: {
type: 'array',
description: 'List of backlinks pointing to the target',
items: {
type: 'object',
properties: {
urlFrom: { type: 'string', description: 'The URL of the page containing the backlink' },
urlTo: { type: 'string', description: 'The URL being linked to' },
anchor: { type: 'string', description: 'The anchor text of the link' },
domainRatingSource: {
type: 'number',
description: 'Domain Rating of the linking domain',
},
isDofollow: { type: 'boolean', description: 'Whether the link is dofollow' },
firstSeen: { type: 'string', description: 'When the backlink was first discovered' },
lastVisited: { type: 'string', description: 'When the backlink was last checked' },
},
},
},
},
}

View File

@@ -0,0 +1,95 @@
import type { AhrefsBacklinksStatsParams, AhrefsBacklinksStatsResponse } from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
export const backlinksStatsTool: ToolConfig<
AhrefsBacklinksStatsParams,
AhrefsBacklinksStatsResponse
> = {
id: 'ahrefs_backlinks_stats',
name: 'Ahrefs Backlinks Stats',
description:
'Get backlink statistics for a target domain or URL. Returns totals for different backlink types including dofollow, nofollow, text, image, and redirect links.',
version: '1.0.0',
params: {
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The target domain or URL to analyze',
},
mode: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match)',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ahrefs API Key',
},
},
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/backlinks-stats')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
if (params.mode) url.searchParams.set('mode', params.mode)
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to get backlinks stats')
}
return {
success: true,
output: {
stats: {
total: data.live ?? data.total ?? 0,
dofollow: data.live_dofollow ?? data.dofollow ?? 0,
nofollow: data.live_nofollow ?? data.nofollow ?? 0,
text: data.text ?? 0,
image: data.image ?? 0,
redirect: data.redirect ?? 0,
},
},
}
},
outputs: {
stats: {
type: 'object',
description: 'Backlink statistics summary',
properties: {
total: { type: 'number', description: 'Total number of live backlinks' },
dofollow: { type: 'number', description: 'Number of dofollow backlinks' },
nofollow: { type: 'number', description: 'Number of nofollow backlinks' },
text: { type: 'number', description: 'Number of text backlinks' },
image: { type: 'number', description: 'Number of image backlinks' },
redirect: { type: 'number', description: 'Number of redirect backlinks' },
},
},
},
}

View File

@@ -0,0 +1,121 @@
import type {
AhrefsBrokenBacklinksParams,
AhrefsBrokenBacklinksResponse,
} from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
export const brokenBacklinksTool: ToolConfig<
AhrefsBrokenBacklinksParams,
AhrefsBrokenBacklinksResponse
> = {
id: 'ahrefs_broken_backlinks',
name: 'Ahrefs Broken Backlinks',
description:
'Get a list of broken backlinks pointing to a target domain or URL. Useful for identifying link reclamation opportunities.',
version: '1.0.0',
params: {
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The target domain or URL to analyze',
},
mode: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match)',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of results to return (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of results to skip for pagination',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ahrefs API Key',
},
},
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/broken-backlinks')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to get broken backlinks')
}
const brokenBacklinks = (data.backlinks || data.broken_backlinks || []).map((link: any) => ({
urlFrom: link.url_from || '',
urlTo: link.url_to || '',
httpCode: link.http_code ?? link.status_code ?? 404,
anchor: link.anchor || '',
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
}))
return {
success: true,
output: {
brokenBacklinks,
},
}
},
outputs: {
brokenBacklinks: {
type: 'array',
description: 'List of broken backlinks',
items: {
type: 'object',
properties: {
urlFrom: {
type: 'string',
description: 'The URL of the page containing the broken link',
},
urlTo: { type: 'string', description: 'The broken URL being linked to' },
httpCode: { type: 'number', description: 'HTTP status code (e.g., 404, 410)' },
anchor: { type: 'string', description: 'The anchor text of the link' },
domainRatingSource: {
type: 'number',
description: 'Domain Rating of the linking domain',
},
},
},
},
},
}

View File

@@ -0,0 +1,74 @@
import type { AhrefsDomainRatingParams, AhrefsDomainRatingResponse } from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomainRatingResponse> = {
id: 'ahrefs_domain_rating',
name: 'Ahrefs Domain Rating',
description:
"Get the Domain Rating (DR) and Ahrefs Rank for a target domain. Domain Rating shows the strength of a website's backlink profile on a scale from 0 to 100.",
version: '1.0.0',
params: {
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The target domain to analyze (e.g., example.com)',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ahrefs API Key',
},
},
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/domain-rating')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to get domain rating')
}
return {
success: true,
output: {
domainRating: data.domain_rating ?? 0,
ahrefsRank: data.ahrefs_rank ?? 0,
},
}
},
outputs: {
domainRating: {
type: 'number',
description: 'Domain Rating score (0-100)',
},
ahrefsRank: {
type: 'number',
description: 'Ahrefs Rank - global ranking based on backlink profile strength',
},
},
}

View File

@@ -0,0 +1,17 @@
import { backlinksTool } from '@/tools/ahrefs/backlinks'
import { backlinksStatsTool } from '@/tools/ahrefs/backlinks_stats'
import { brokenBacklinksTool } from '@/tools/ahrefs/broken_backlinks'
import { domainRatingTool } from '@/tools/ahrefs/domain_rating'
import { keywordOverviewTool } from '@/tools/ahrefs/keyword_overview'
import { organicKeywordsTool } from '@/tools/ahrefs/organic_keywords'
import { referringDomainsTool } from '@/tools/ahrefs/referring_domains'
import { topPagesTool } from '@/tools/ahrefs/top_pages'
export const ahrefsDomainRatingTool = domainRatingTool
export const ahrefsBacklinksTool = backlinksTool
export const ahrefsBacklinksStatsTool = backlinksStatsTool
export const ahrefsReferringDomainsTool = referringDomainsTool
export const ahrefsOrganicKeywordsTool = organicKeywordsTool
export const ahrefsTopPagesTool = topPagesTool
export const ahrefsKeywordOverviewTool = keywordOverviewTool
export const ahrefsBrokenBacklinksTool = brokenBacklinksTool

View File

@@ -0,0 +1,101 @@
import type {
AhrefsKeywordOverviewParams,
AhrefsKeywordOverviewResponse,
} from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
export const keywordOverviewTool: ToolConfig<
AhrefsKeywordOverviewParams,
AhrefsKeywordOverviewResponse
> = {
id: 'ahrefs_keyword_overview',
name: 'Ahrefs Keyword Overview',
description:
'Get detailed metrics for a keyword including search volume, keyword difficulty, CPC, clicks, and traffic potential.',
version: '1.0.0',
params: {
keyword: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The keyword to analyze',
},
country: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Country code for keyword data (e.g., us, gb, de). Default: us',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ahrefs API Key',
},
},
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/keywords-explorer/overview')
url.searchParams.set('keyword', params.keyword)
url.searchParams.set('country', params.country || 'us')
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to get keyword overview')
}
return {
success: true,
output: {
overview: {
keyword: data.keyword || '',
searchVolume: data.volume ?? 0,
keywordDifficulty: data.keyword_difficulty ?? data.difficulty ?? 0,
cpc: data.cpc ?? 0,
clicks: data.clicks ?? 0,
clicksPercentage: data.clicks_percentage ?? 0,
parentTopic: data.parent_topic || '',
trafficPotential: data.traffic_potential ?? 0,
},
},
}
},
outputs: {
overview: {
type: 'object',
description: 'Keyword metrics overview',
properties: {
keyword: { type: 'string', description: 'The analyzed keyword' },
searchVolume: { type: 'number', description: 'Monthly search volume' },
keywordDifficulty: {
type: 'number',
description: 'Keyword difficulty score (0-100)',
},
cpc: { type: 'number', description: 'Cost per click in USD' },
clicks: { type: 'number', description: 'Estimated clicks per month' },
clicksPercentage: {
type: 'number',
description: 'Percentage of searches that result in clicks',
},
parentTopic: { type: 'string', description: 'The parent topic for this keyword' },
trafficPotential: {
type: 'number',
description: 'Estimated traffic potential if ranking #1',
},
},
},
},
}

View File

@@ -0,0 +1,127 @@
import type {
AhrefsOrganicKeywordsParams,
AhrefsOrganicKeywordsResponse,
} from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
export const organicKeywordsTool: ToolConfig<
AhrefsOrganicKeywordsParams,
AhrefsOrganicKeywordsResponse
> = {
id: 'ahrefs_organic_keywords',
name: 'Ahrefs Organic Keywords',
description:
'Get organic keywords that a target domain or URL ranks for in Google search results. Returns keyword details including search volume, ranking position, and estimated traffic.',
version: '1.0.0',
params: {
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The target domain or URL to analyze',
},
country: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Country code for search results (e.g., us, gb, de). Default: us',
},
mode: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match)',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of results to return (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of results to skip for pagination',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ahrefs API Key',
},
},
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/organic-keywords')
url.searchParams.set('target', params.target)
url.searchParams.set('country', params.country || 'us')
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to get organic keywords')
}
const keywords = (data.keywords || data.organic_keywords || []).map((kw: any) => ({
keyword: kw.keyword || '',
volume: kw.volume ?? 0,
position: kw.position ?? 0,
url: kw.url || '',
traffic: kw.traffic ?? 0,
keywordDifficulty: kw.keyword_difficulty ?? kw.difficulty ?? 0,
}))
return {
success: true,
output: {
keywords,
},
}
},
outputs: {
keywords: {
type: 'array',
description: 'List of organic keywords the target ranks for',
items: {
type: 'object',
properties: {
keyword: { type: 'string', description: 'The keyword' },
volume: { type: 'number', description: 'Monthly search volume' },
position: { type: 'number', description: 'Current ranking position' },
url: { type: 'string', description: 'The URL that ranks for this keyword' },
traffic: { type: 'number', description: 'Estimated monthly organic traffic' },
keywordDifficulty: {
type: 'number',
description: 'Keyword difficulty score (0-100)',
},
},
},
},
},
}

View File

@@ -0,0 +1,125 @@
import type {
AhrefsReferringDomainsParams,
AhrefsReferringDomainsResponse,
} from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
export const referringDomainsTool: ToolConfig<
AhrefsReferringDomainsParams,
AhrefsReferringDomainsResponse
> = {
id: 'ahrefs_referring_domains',
name: 'Ahrefs Referring Domains',
description:
'Get a list of domains that link to a target domain or URL. Returns unique referring domains with their domain rating, backlink counts, and discovery dates.',
version: '1.0.0',
params: {
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The target domain or URL to analyze',
},
mode: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match)',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of results to return (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of results to skip for pagination',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ahrefs API Key',
},
},
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/refdomains')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to get referring domains')
}
const referringDomains = (data.refdomains || data.referring_domains || []).map(
(domain: any) => ({
domain: domain.domain || domain.refdomain || '',
domainRating: domain.domain_rating ?? 0,
backlinks: domain.backlinks ?? 0,
dofollowBacklinks: domain.dofollow_backlinks ?? domain.dofollow ?? 0,
firstSeen: domain.first_seen || '',
lastVisited: domain.last_visited || '',
})
)
return {
success: true,
output: {
referringDomains,
},
}
},
outputs: {
referringDomains: {
type: 'array',
description: 'List of domains linking to the target',
items: {
type: 'object',
properties: {
domain: { type: 'string', description: 'The referring domain' },
domainRating: { type: 'number', description: 'Domain Rating of the referring domain' },
backlinks: {
type: 'number',
description: 'Total number of backlinks from this domain',
},
dofollowBacklinks: {
type: 'number',
description: 'Number of dofollow backlinks from this domain',
},
firstSeen: { type: 'string', description: 'When the domain was first seen linking' },
lastVisited: { type: 'string', description: 'When the domain was last checked' },
},
},
},
},
}

View File

@@ -0,0 +1,119 @@
import type { AhrefsTopPagesParams, AhrefsTopPagesResponse } from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesResponse> = {
id: 'ahrefs_top_pages',
name: 'Ahrefs Top Pages',
description:
'Get the top pages of a target domain sorted by organic traffic. Returns page URLs with their traffic, keyword counts, and estimated traffic value.',
version: '1.0.0',
params: {
target: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The target domain to analyze',
},
country: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Country code for traffic data (e.g., us, gb, de). Default: us',
},
mode: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains)',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of results to return (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of results to skip for pagination',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Ahrefs API Key',
},
},
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/top-pages')
url.searchParams.set('target', params.target)
url.searchParams.set('country', params.country || 'us')
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to get top pages')
}
const pages = (data.pages || data.top_pages || []).map((page: any) => ({
url: page.url || '',
traffic: page.traffic ?? 0,
keywords: page.keywords ?? page.keyword_count ?? 0,
topKeyword: page.top_keyword || '',
value: page.value ?? page.traffic_value ?? 0,
}))
return {
success: true,
output: {
pages,
},
}
},
outputs: {
pages: {
type: 'array',
description: 'List of top pages by organic traffic',
items: {
type: 'object',
properties: {
url: { type: 'string', description: 'The page URL' },
traffic: { type: 'number', description: 'Estimated monthly organic traffic' },
keywords: { type: 'number', description: 'Number of keywords the page ranks for' },
topKeyword: {
type: 'string',
description: 'The top keyword driving traffic to this page',
},
value: { type: 'number', description: 'Estimated traffic value in USD' },
},
},
},
},
}

View File

@@ -0,0 +1,199 @@
// Common types for Ahrefs API tools
import type { ToolResponse } from '@/tools/types'
// Common parameters for all Ahrefs tools
export interface AhrefsBaseParams {
apiKey: string
date?: string // Date in YYYY-MM-DD format, defaults to today
}
// Target mode for analysis
export type AhrefsTargetMode = 'domain' | 'prefix' | 'subdomains' | 'exact'
// Domain Rating tool types
export interface AhrefsDomainRatingParams extends AhrefsBaseParams {
target: string
}
export interface AhrefsDomainRatingResult {
domain_rating: number
ahrefs_rank: number
}
export interface AhrefsDomainRatingResponse extends ToolResponse {
output: {
domainRating: number
ahrefsRank: number
}
}
// Backlinks tool types
export interface AhrefsBacklinksParams extends AhrefsBaseParams {
target: string
mode?: AhrefsTargetMode
limit?: number
offset?: number
}
export interface AhrefsBacklink {
urlFrom: string
urlTo: string
anchor: string
domainRatingSource: number
isDofollow: boolean
firstSeen: string
lastVisited: string
}
export interface AhrefsBacklinksResponse extends ToolResponse {
output: {
backlinks: AhrefsBacklink[]
}
}
// Backlinks Stats tool types
export interface AhrefsBacklinksStatsParams extends AhrefsBaseParams {
target: string
mode?: AhrefsTargetMode
}
export interface AhrefsBacklinksStatsResult {
total: number
dofollow: number
nofollow: number
text: number
image: number
redirect: number
}
export interface AhrefsBacklinksStatsResponse extends ToolResponse {
output: {
stats: AhrefsBacklinksStatsResult
}
}
// Referring Domains tool types
export interface AhrefsReferringDomainsParams extends AhrefsBaseParams {
target: string
mode?: AhrefsTargetMode
limit?: number
offset?: number
}
export interface AhrefsReferringDomain {
domain: string
domainRating: number
backlinks: number
dofollowBacklinks: number
firstSeen: string
lastVisited: string
}
export interface AhrefsReferringDomainsResponse extends ToolResponse {
output: {
referringDomains: AhrefsReferringDomain[]
}
}
// Organic Keywords tool types
export interface AhrefsOrganicKeywordsParams extends AhrefsBaseParams {
target: string
country?: string
mode?: AhrefsTargetMode
limit?: number
offset?: number
}
export interface AhrefsOrganicKeyword {
keyword: string
volume: number
position: number
url: string
traffic: number
keywordDifficulty: number
}
export interface AhrefsOrganicKeywordsResponse extends ToolResponse {
output: {
keywords: AhrefsOrganicKeyword[]
}
}
// Top Pages tool types
export interface AhrefsTopPagesParams extends AhrefsBaseParams {
target: string
country?: string
mode?: AhrefsTargetMode
limit?: number
offset?: number
}
export interface AhrefsTopPage {
url: string
traffic: number
keywords: number
topKeyword: string
value: number
}
export interface AhrefsTopPagesResponse extends ToolResponse {
output: {
pages: AhrefsTopPage[]
}
}
// Keyword Overview tool types
export interface AhrefsKeywordOverviewParams extends AhrefsBaseParams {
keyword: string
country?: string
}
export interface AhrefsKeywordOverviewResult {
keyword: string
searchVolume: number
keywordDifficulty: number
cpc: number
clicks: number
clicksPercentage: number
parentTopic: string
trafficPotential: number
}
export interface AhrefsKeywordOverviewResponse extends ToolResponse {
output: {
overview: AhrefsKeywordOverviewResult
}
}
// Broken Backlinks tool types
export interface AhrefsBrokenBacklinksParams extends AhrefsBaseParams {
target: string
mode?: AhrefsTargetMode
limit?: number
offset?: number
}
export interface AhrefsBrokenBacklink {
urlFrom: string
urlTo: string
httpCode: number
anchor: string
domainRatingSource: number
}
export interface AhrefsBrokenBacklinksResponse extends ToolResponse {
output: {
brokenBacklinks: AhrefsBrokenBacklink[]
}
}
// Union type for all possible responses
export type AhrefsResponse =
| AhrefsDomainRatingResponse
| AhrefsBacklinksResponse
| AhrefsBacklinksStatsResponse
| AhrefsReferringDomainsResponse
| AhrefsOrganicKeywordsResponse
| AhrefsTopPagesResponse
| AhrefsKeywordOverviewResponse
| AhrefsBrokenBacklinksResponse

View File

@@ -0,0 +1,76 @@
import type { CancelDowntimeParams, CancelDowntimeResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const cancelDowntimeTool: ToolConfig<CancelDowntimeParams, CancelDowntimeResponse> = {
id: 'datadog_cancel_downtime',
name: 'Datadog Cancel Downtime',
description: 'Cancel a scheduled downtime.',
version: '1.0.0',
params: {
downtimeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the downtime to cancel',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
return `https://api.${site}/api/v2/downtime/${params.downtimeId}`
},
method: 'DELETE',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok && response.status !== 204) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
success: false,
},
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the downtime was successfully canceled',
},
},
}

View File

@@ -0,0 +1,178 @@
import type { CreateDowntimeParams, CreateDowntimeResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const createDowntimeTool: ToolConfig<CreateDowntimeParams, CreateDowntimeResponse> = {
id: 'datadog_create_downtime',
name: 'Datadog Create Downtime',
description: 'Schedule a downtime to suppress monitor notifications during maintenance windows.',
version: '1.0.0',
params: {
scope: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Scope to apply downtime to (e.g., "host:myhost", "env:production", or "*" for all)',
},
message: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Message to display during downtime',
},
start: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Unix timestamp for downtime start (defaults to now)',
},
end: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Unix timestamp for downtime end',
},
timezone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Timezone for the downtime (e.g., "America/New_York")',
},
monitorId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Specific monitor ID to mute',
},
monitorTags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated monitor tags to match (e.g., "team:backend,priority:high")',
},
muteFirstRecoveryNotification: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Mute the first recovery notification',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
return `https://api.${site}/api/v2/downtime`
},
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
body: (params) => {
const schedule: Record<string, any> = {}
if (params.start) schedule.start = new Date(params.start * 1000).toISOString()
if (params.end) schedule.end = new Date(params.end * 1000).toISOString()
if (params.timezone) schedule.timezone = params.timezone
const body: Record<string, any> = {
data: {
type: 'downtime',
attributes: {
scope: params.scope,
schedule: Object.keys(schedule).length > 0 ? schedule : undefined,
},
},
}
if (params.message) body.data.attributes.message = params.message
if (params.muteFirstRecoveryNotification !== undefined) {
body.data.attributes.mute_first_recovery_notification = params.muteFirstRecoveryNotification
}
if (params.monitorId) {
body.data.attributes.monitor_identifier = {
monitor_id: Number.parseInt(params.monitorId, 10),
}
} else if (params.monitorTags) {
body.data.attributes.monitor_identifier = {
monitor_tags: params.monitorTags
.split(',')
.map((t: string) => t.trim())
.filter((t: string) => t.length > 0),
}
}
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
downtime: {} as any,
},
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const attrs = data.data?.attributes || {}
return {
success: true,
output: {
downtime: {
id: data.data?.id,
scope: attrs.scope ? [attrs.scope] : [],
message: attrs.message,
start: attrs.schedule?.start
? new Date(attrs.schedule.start).getTime() / 1000
: undefined,
end: attrs.schedule?.end ? new Date(attrs.schedule.end).getTime() / 1000 : undefined,
timezone: attrs.schedule?.timezone,
disabled: attrs.disabled,
active: attrs.status === 'active',
created: attrs.created ? new Date(attrs.created).getTime() / 1000 : undefined,
modified: attrs.modified ? new Date(attrs.modified).getTime() / 1000 : undefined,
},
},
}
},
outputs: {
downtime: {
type: 'object',
description: 'The created downtime details',
properties: {
id: { type: 'number', description: 'Downtime ID' },
scope: { type: 'array', description: 'Downtime scope' },
message: { type: 'string', description: 'Downtime message' },
start: { type: 'number', description: 'Start time (Unix timestamp)' },
end: { type: 'number', description: 'End time (Unix timestamp)' },
active: { type: 'boolean', description: 'Whether downtime is currently active' },
},
},
},
}

View File

@@ -0,0 +1,163 @@
import type { CreateEventParams, CreateEventResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const createEventTool: ToolConfig<CreateEventParams, CreateEventResponse> = {
id: 'datadog_create_event',
name: 'Datadog Create Event',
description:
'Post an event to the Datadog event stream. Use for deployment notifications, alerts, or any significant occurrences.',
version: '1.0.0',
params: {
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Event title',
},
text: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Event body/description. Supports markdown.',
},
alertType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Alert type: error, warning, info, success, user_update, recommendation, or snapshot',
},
priority: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Event priority: normal or low',
},
host: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Host name to associate with this event',
},
tags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of tags (e.g., "env:production,service:api")',
},
aggregationKey: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Key to aggregate events together',
},
sourceTypeName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Source type name for the event',
},
dateHappened: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Unix timestamp when the event occurred (defaults to now)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
return `https://api.${site}/api/v1/events`
},
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
}),
body: (params) => {
const body: Record<string, any> = {
title: params.title,
text: params.text,
}
if (params.alertType) body.alert_type = params.alertType
if (params.priority) body.priority = params.priority
if (params.host) body.host = params.host
if (params.aggregationKey) body.aggregation_key = params.aggregationKey
if (params.sourceTypeName) body.source_type_name = params.sourceTypeName
if (params.dateHappened) body.date_happened = params.dateHappened
if (params.tags) {
body.tags = params.tags
.split(',')
.map((t: string) => t.trim())
.filter((t: string) => t.length > 0)
}
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
event: {} as any,
},
error: errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
return {
success: true,
output: {
event: {
id: data.event?.id,
title: data.event?.title,
text: data.event?.text,
date_happened: data.event?.date_happened,
priority: data.event?.priority,
alert_type: data.event?.alert_type,
host: data.event?.host,
tags: data.event?.tags,
url: data.event?.url,
},
},
}
},
outputs: {
event: {
type: 'object',
description: 'The created event details',
properties: {
id: { type: 'number', description: 'Event ID' },
title: { type: 'string', description: 'Event title' },
text: { type: 'string', description: 'Event text' },
date_happened: { type: 'number', description: 'Unix timestamp when event occurred' },
priority: { type: 'string', description: 'Event priority' },
alert_type: { type: 'string', description: 'Alert type' },
host: { type: 'string', description: 'Associated host' },
tags: { type: 'array', description: 'Event tags' },
url: { type: 'string', description: 'URL to view the event in Datadog' },
},
},
},
}

View File

@@ -0,0 +1,169 @@
import type { CreateMonitorParams, CreateMonitorResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const createMonitorTool: ToolConfig<CreateMonitorParams, CreateMonitorResponse> = {
id: 'datadog_create_monitor',
name: 'Datadog Create Monitor',
description:
'Create a new monitor/alert in Datadog. Monitors can track metrics, service checks, events, and more.',
version: '1.0.0',
params: {
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Monitor name',
},
type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Monitor type: metric alert, service check, event alert, process alert, log alert, query alert, composite, synthetics alert, slo alert',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Monitor query (e.g., "avg(last_5m):avg:system.cpu.idle{*} < 20")',
},
message: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Message to include with notifications. Can include @-mentions and markdown.',
},
tags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of tags',
},
priority: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Monitor priority (1-5, where 1 is highest)',
},
options: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON string of monitor options (thresholds, notify_no_data, renotify_interval, etc.)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
return `https://api.${site}/api/v1/monitor`
},
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
body: (params) => {
const body: Record<string, any> = {
name: params.name,
type: params.type,
query: params.query,
}
if (params.message) body.message = params.message
if (params.priority) body.priority = params.priority
if (params.tags) {
body.tags = params.tags
.split(',')
.map((t: string) => t.trim())
.filter((t: string) => t.length > 0)
}
if (params.options) {
try {
body.options =
typeof params.options === 'string' ? JSON.parse(params.options) : params.options
} catch {
// If options parsing fails, skip it
}
}
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
monitor: {} as any,
},
error: errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
return {
success: true,
output: {
monitor: {
id: data.id,
name: data.name,
type: data.type,
query: data.query,
message: data.message,
tags: data.tags,
priority: data.priority,
options: data.options,
overall_state: data.overall_state,
created: data.created,
modified: data.modified,
creator: data.creator,
},
},
}
},
outputs: {
monitor: {
type: 'object',
description: 'The created monitor details',
properties: {
id: { type: 'number', description: 'Monitor ID' },
name: { type: 'string', description: 'Monitor name' },
type: { type: 'string', description: 'Monitor type' },
query: { type: 'string', description: 'Monitor query' },
message: { type: 'string', description: 'Notification message' },
tags: { type: 'array', description: 'Monitor tags' },
priority: { type: 'number', description: 'Monitor priority' },
overall_state: { type: 'string', description: 'Current monitor state' },
created: { type: 'string', description: 'Creation timestamp' },
modified: { type: 'string', description: 'Last modification timestamp' },
},
},
},
}

View File

@@ -0,0 +1,120 @@
import type { GetMonitorParams, GetMonitorResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const getMonitorTool: ToolConfig<GetMonitorParams, GetMonitorResponse> = {
id: 'datadog_get_monitor',
name: 'Datadog Get Monitor',
description: 'Retrieve details of a specific monitor by ID.',
version: '1.0.0',
params: {
monitorId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the monitor to retrieve',
},
groupStates: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated group states to include: alert, warn, no data, ok',
},
withDowntimes: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Include downtime data with the monitor',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
const queryParams = new URLSearchParams()
if (params.groupStates) queryParams.set('group_states', params.groupStates)
if (params.withDowntimes) queryParams.set('with_downtimes', 'true')
const queryString = queryParams.toString()
return `https://api.${site}/api/v1/monitor/${params.monitorId}${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
monitor: {} as any,
},
error: errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
return {
success: true,
output: {
monitor: {
id: data.id,
name: data.name,
type: data.type,
query: data.query,
message: data.message,
tags: data.tags,
priority: data.priority,
options: data.options,
overall_state: data.overall_state,
created: data.created,
modified: data.modified,
creator: data.creator,
},
},
}
},
outputs: {
monitor: {
type: 'object',
description: 'The monitor details',
properties: {
id: { type: 'number', description: 'Monitor ID' },
name: { type: 'string', description: 'Monitor name' },
type: { type: 'string', description: 'Monitor type' },
query: { type: 'string', description: 'Monitor query' },
message: { type: 'string', description: 'Notification message' },
tags: { type: 'array', description: 'Monitor tags' },
priority: { type: 'number', description: 'Monitor priority' },
overall_state: { type: 'string', description: 'Current monitor state' },
created: { type: 'string', description: 'Creation timestamp' },
modified: { type: 'string', description: 'Last modification timestamp' },
},
},
},
}

View File

@@ -0,0 +1,25 @@
import { cancelDowntimeTool } from '@/tools/datadog/cancel_downtime'
import { createDowntimeTool } from '@/tools/datadog/create_downtime'
import { createEventTool } from '@/tools/datadog/create_event'
import { createMonitorTool } from '@/tools/datadog/create_monitor'
import { getMonitorTool } from '@/tools/datadog/get_monitor'
import { listDowntimesTool } from '@/tools/datadog/list_downtimes'
import { listMonitorsTool } from '@/tools/datadog/list_monitors'
import { muteMonitorTool } from '@/tools/datadog/mute_monitor'
import { queryLogsTool } from '@/tools/datadog/query_logs'
import { queryTimeseriesTool } from '@/tools/datadog/query_timeseries'
import { sendLogsTool } from '@/tools/datadog/send_logs'
import { submitMetricsTool } from '@/tools/datadog/submit_metrics'
export const datadogSubmitMetricsTool = submitMetricsTool
export const datadogQueryTimeseriesTool = queryTimeseriesTool
export const datadogCreateEventTool = createEventTool
export const datadogCreateMonitorTool = createMonitorTool
export const datadogGetMonitorTool = getMonitorTool
export const datadogListMonitorsTool = listMonitorsTool
export const datadogMuteMonitorTool = muteMonitorTool
export const datadogQueryLogsTool = queryLogsTool
export const datadogSendLogsTool = sendLogsTool
export const datadogCreateDowntimeTool = createDowntimeTool
export const datadogListDowntimesTool = listDowntimesTool
export const datadogCancelDowntimeTool = cancelDowntimeTool

View File

@@ -0,0 +1,116 @@
import type { ListDowntimesParams, ListDowntimesResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const listDowntimesTool: ToolConfig<ListDowntimesParams, ListDowntimesResponse> = {
id: 'datadog_list_downtimes',
name: 'Datadog List Downtimes',
description: 'List all scheduled downtimes in Datadog.',
version: '1.0.0',
params: {
currentOnly: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Only return currently active downtimes',
},
monitorId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by monitor ID',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
const queryParams = new URLSearchParams()
if (params.currentOnly) queryParams.set('current_only', 'true')
if (params.monitorId) queryParams.set('monitor_id', params.monitorId)
const queryString = queryParams.toString()
return `https://api.${site}/api/v2/downtime${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
downtimes: [],
},
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const downtimes = (data.data || []).map((d: any) => {
const attrs = d.attributes || {}
return {
id: d.id,
scope: attrs.scope ? [attrs.scope] : [],
message: attrs.message,
start: attrs.schedule?.start ? new Date(attrs.schedule.start).getTime() / 1000 : undefined,
end: attrs.schedule?.end ? new Date(attrs.schedule.end).getTime() / 1000 : undefined,
timezone: attrs.schedule?.timezone,
disabled: attrs.disabled,
active: attrs.status === 'active',
created: attrs.created ? new Date(attrs.created).getTime() / 1000 : undefined,
modified: attrs.modified ? new Date(attrs.modified).getTime() / 1000 : undefined,
}
})
return {
success: true,
output: {
downtimes,
},
}
},
outputs: {
downtimes: {
type: 'array',
description: 'List of downtimes',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Downtime ID' },
scope: { type: 'array', description: 'Downtime scope' },
message: { type: 'string', description: 'Downtime message' },
start: { type: 'number', description: 'Start time (Unix timestamp)' },
end: { type: 'number', description: 'End time (Unix timestamp)' },
active: { type: 'boolean', description: 'Whether downtime is currently active' },
},
},
},
},
}

View File

@@ -0,0 +1,180 @@
import type { ListMonitorsParams, ListMonitorsResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const listMonitorsTool: ToolConfig<ListMonitorsParams, ListMonitorsResponse> = {
id: 'datadog_list_monitors',
name: 'Datadog List Monitors',
description: 'List all monitors in Datadog with optional filtering by name, tags, or state.',
version: '1.0.0',
params: {
groupStates: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated group states to filter by: alert, warn, no data, ok',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter monitors by name (partial match)',
},
tags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of tags to filter by',
},
monitorTags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of monitor tags to filter by',
},
withDowntimes: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Include downtime data with monitors',
},
page: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Page number for pagination (0-indexed)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Number of monitors per page (max 1000)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
const queryParams = new URLSearchParams()
if (params.groupStates) queryParams.set('group_states', params.groupStates)
if (params.name) queryParams.set('name', params.name)
if (params.tags) queryParams.set('tags', params.tags)
if (params.monitorTags) queryParams.set('monitor_tags', params.monitorTags)
if (params.withDowntimes) queryParams.set('with_downtimes', 'true')
if (params.page !== undefined) queryParams.set('page', String(params.page))
if (params.pageSize) queryParams.set('page_size', String(params.pageSize))
const queryString = queryParams.toString()
const url = `https://api.${site}/api/v1/monitor${queryString ? `?${queryString}` : ''}`
console.log(
'[Datadog List Monitors] URL:',
url,
'Site param:',
params.site,
'API Key present:',
!!params.apiKey,
'App Key present:',
!!params.applicationKey
)
return url
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
monitors: [],
},
error: errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`,
}
}
const text = await response.text()
let data: any
try {
data = JSON.parse(text)
} catch (e) {
return {
success: false,
output: { monitors: [] },
error: `Failed to parse response: ${text.substring(0, 200)}`,
}
}
if (!Array.isArray(data)) {
return {
success: false,
output: { monitors: [] },
error: `Expected array but got: ${typeof data} - ${JSON.stringify(data).substring(0, 200)}`,
}
}
const monitors = data.map((m: any) => ({
id: m.id,
name: m.name,
type: m.type,
query: m.query,
message: m.message,
tags: m.tags,
priority: m.priority,
options: m.options,
overall_state: m.overall_state,
created: m.created,
modified: m.modified,
creator: m.creator,
}))
return {
success: true,
output: {
monitors,
},
}
},
outputs: {
monitors: {
type: 'array',
description: 'List of monitors',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Monitor ID' },
name: { type: 'string', description: 'Monitor name' },
type: { type: 'string', description: 'Monitor type' },
query: { type: 'string', description: 'Monitor query' },
overall_state: { type: 'string', description: 'Current state' },
tags: { type: 'array', description: 'Tags' },
},
},
},
},
}

View File

@@ -0,0 +1,94 @@
import type { MuteMonitorParams, MuteMonitorResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const muteMonitorTool: ToolConfig<MuteMonitorParams, MuteMonitorResponse> = {
id: 'datadog_mute_monitor',
name: 'Datadog Mute Monitor',
description: 'Mute a monitor to temporarily suppress notifications.',
version: '1.0.0',
params: {
monitorId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the monitor to mute',
},
scope: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Scope to mute (e.g., "host:myhost"). If not specified, mutes all scopes.',
},
end: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Unix timestamp when the mute should end. If not specified, mutes indefinitely.',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
return `https://api.${site}/api/v1/monitor/${params.monitorId}/mute`
},
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
body: (params) => {
const body: Record<string, any> = {}
if (params.scope) body.scope = params.scope
if (params.end) body.end = params.end
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
success: false,
},
error: errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`,
}
}
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the monitor was successfully muted',
},
},
}

View File

@@ -0,0 +1,169 @@
import type { QueryLogsParams, QueryLogsResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const queryLogsTool: ToolConfig<QueryLogsParams, QueryLogsResponse> = {
id: 'datadog_query_logs',
name: 'Datadog Query Logs',
description:
'Search and retrieve logs from Datadog. Use for troubleshooting, analysis, or monitoring.',
version: '1.0.0',
params: {
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Log search query (e.g., "service:web-app status:error")',
},
from: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Start time in ISO-8601 format or relative (e.g., "now-1h")',
},
to: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'End time in ISO-8601 format or relative (e.g., "now")',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of logs to return (default: 50, max: 1000)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: timestamp (oldest first) or -timestamp (newest first)',
},
indexes: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Comma-separated list of log indexes to search',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
return `https://api.${site}/api/v2/logs/events/search`
},
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
body: (params) => {
const body: Record<string, any> = {
filter: {
query: params.query,
from: params.from,
to: params.to,
},
page: {
limit: params.limit || 50,
},
}
if (params.sort) {
body.sort = params.sort
}
if (params.indexes) {
body.filter.indexes = params.indexes
.split(',')
.map((i: string) => i.trim())
.filter((i: string) => i.length > 0)
}
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
logs: [],
},
error: errorData.errors?.[0]?.detail || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const logs = (data.data || []).map((log: any) => ({
id: log.id,
content: {
timestamp: log.attributes?.timestamp,
host: log.attributes?.host,
service: log.attributes?.service,
message: log.attributes?.message,
status: log.attributes?.status,
attributes: log.attributes?.attributes,
tags: log.attributes?.tags,
},
}))
return {
success: true,
output: {
logs,
nextLogId: data.meta?.page?.after,
},
}
},
outputs: {
logs: {
type: 'array',
description: 'List of log entries',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Log ID' },
content: {
type: 'object',
description: 'Log content',
properties: {
timestamp: { type: 'string', description: 'Log timestamp' },
host: { type: 'string', description: 'Host name' },
service: { type: 'string', description: 'Service name' },
message: { type: 'string', description: 'Log message' },
status: { type: 'string', description: 'Log status/level' },
},
},
},
},
},
nextLogId: {
type: 'string',
description: 'Cursor for pagination',
optional: true,
},
},
}

View File

@@ -0,0 +1,110 @@
import type { QueryTimeseriesParams, QueryTimeseriesResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const queryTimeseriesTool: ToolConfig<QueryTimeseriesParams, QueryTimeseriesResponse> = {
id: 'datadog_query_timeseries',
name: 'Datadog Query Timeseries',
description:
'Query metric timeseries data from Datadog. Use for analyzing trends, creating reports, or retrieving metric values.',
version: '1.0.0',
params: {
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Datadog metrics query (e.g., "avg:system.cpu.user{*}")',
},
from: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Start time as Unix timestamp in seconds',
},
to: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'End time as Unix timestamp in seconds',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
applicationKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog Application key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
const queryParams = new URLSearchParams({
query: params.query,
from: String(params.from),
to: String(params.to),
})
return `https://api.${site}/api/v1/query?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
'DD-APPLICATION-KEY': params.applicationKey,
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
series: [],
status: 'error',
},
error: errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json()
const series = (data.series || []).map((s: any) => ({
metric: s.metric || s.expression,
tags: s.tag_set || [],
points: (s.pointlist || []).map((p: [number, number]) => ({
timestamp: p[0] / 1000, // Convert from milliseconds to seconds
value: p[1],
})),
}))
return {
success: true,
output: {
series,
status: data.status || 'ok',
},
}
},
outputs: {
series: {
type: 'array',
description: 'Array of timeseries data with metric name, tags, and data points',
},
status: {
type: 'string',
description: 'Query status',
},
},
}

View File

@@ -0,0 +1,94 @@
import type { SendLogsParams, SendLogsResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const sendLogsTool: ToolConfig<SendLogsParams, SendLogsResponse> = {
id: 'datadog_send_logs',
name: 'Datadog Send Logs',
description: 'Send log entries to Datadog for centralized logging and analysis.',
version: '1.0.0',
params: {
logs: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of log entries. Each entry should have message and optionally ddsource, ddtags, hostname, service.',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
// Logs API uses a different subdomain
const logsHost =
site === 'datadoghq.com'
? 'http-intake.logs.datadoghq.com'
: site === 'datadoghq.eu'
? 'http-intake.logs.datadoghq.eu'
: `http-intake.logs.${site}`
return `https://${logsHost}/api/v2/logs`
},
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
}),
body: (params) => {
let logs: any[]
try {
logs = typeof params.logs === 'string' ? JSON.parse(params.logs) : params.logs
} catch {
throw new Error('Invalid JSON in logs parameter')
}
// Ensure each log entry has the required format
return logs.map((log: any) => ({
ddsource: log.ddsource || 'custom',
ddtags: log.ddtags || '',
hostname: log.hostname || '',
message: log.message,
service: log.service || '',
}))
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
success: false,
},
error: errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`,
}
}
return {
success: true,
output: {
success: true,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the logs were sent successfully',
},
},
}

View File

@@ -0,0 +1,101 @@
import type { SubmitMetricsParams, SubmitMetricsResponse } from '@/tools/datadog/types'
import type { ToolConfig } from '@/tools/types'
export const submitMetricsTool: ToolConfig<SubmitMetricsParams, SubmitMetricsResponse> = {
id: 'datadog_submit_metrics',
name: 'Datadog Submit Metrics',
description:
'Submit custom metrics to Datadog. Use for tracking application performance, business metrics, or custom monitoring data.',
version: '1.0.0',
params: {
series: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of metric series to submit. Each series should include metric name, type (gauge/rate/count), points (timestamp/value pairs), and optional tags.',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Datadog API key',
},
site: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Datadog site/region (default: datadoghq.com)',
},
},
request: {
url: (params) => {
const site = params.site || 'datadoghq.com'
return `https://api.${site}/api/v2/series`
},
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
'DD-API-KEY': params.apiKey,
}),
body: (params) => {
let series: any[]
try {
series = typeof params.series === 'string' ? JSON.parse(params.series) : params.series
} catch {
throw new Error('Invalid JSON in series parameter')
}
// Transform to Datadog API v2 format
const formattedSeries = series.map((s: any) => ({
metric: s.metric,
type: s.type === 'gauge' ? 0 : s.type === 'rate' ? 1 : s.type === 'count' ? 2 : 3,
points: s.points.map((p: any) => ({
timestamp: p.timestamp,
value: p.value,
})),
tags: s.tags || [],
unit: s.unit,
resources: s.resources || [{ name: 'host', type: 'host' }],
}))
return { series: formattedSeries }
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
return {
success: false,
output: {
success: false,
errors: [errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`],
},
error: errorData.errors?.[0] || `HTTP ${response.status}: ${response.statusText}`,
}
}
const data = await response.json().catch(() => ({}))
return {
success: true,
output: {
success: true,
errors: data.errors || [],
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the metrics were submitted successfully',
},
errors: {
type: 'array',
description: 'Any errors that occurred during submission',
},
},
}

View File

@@ -0,0 +1,782 @@
// Common types for Datadog tools
import type { ToolResponse } from '@/tools/types'
// Datadog Site/Region options
export type DatadogSite =
| 'datadoghq.com'
| 'us3.datadoghq.com'
| 'us5.datadoghq.com'
| 'datadoghq.eu'
| 'ap1.datadoghq.com'
| 'ddog-gov.com'
// Base parameters for write-only operations (only need API key)
export interface DatadogWriteOnlyParams {
apiKey: string
site?: DatadogSite
}
// Base parameters for read/manage operations (need both API key and Application key)
export interface DatadogBaseParams extends DatadogWriteOnlyParams {
applicationKey: string
}
// ========================
// METRICS TYPES
// ========================
export type MetricType = 'gauge' | 'rate' | 'count' | 'distribution'
export interface MetricPoint {
timestamp: number
value: number
}
export interface MetricSeries {
metric: string
type?: MetricType
points: MetricPoint[]
tags?: string[]
unit?: string
resources?: { name: string; type: string }[]
}
export interface SubmitMetricsParams extends DatadogWriteOnlyParams {
series: string // JSON string of MetricSeries[]
}
export interface SubmitMetricsOutput {
success: boolean
errors?: string[]
}
export interface SubmitMetricsResponse extends ToolResponse {
output: SubmitMetricsOutput
}
export interface QueryTimeseriesParams extends DatadogBaseParams {
query: string
from: number // Unix timestamp in seconds
to: number // Unix timestamp in seconds
}
export interface TimeseriesPoint {
timestamp: number
value: number
}
export interface TimeseriesResult {
metric: string
tags: string[]
points: TimeseriesPoint[]
}
export interface QueryTimeseriesOutput {
series: TimeseriesResult[]
status: string
}
export interface QueryTimeseriesResponse extends ToolResponse {
output: QueryTimeseriesOutput
}
export interface ListMetricsParams extends DatadogBaseParams {
from?: number // Unix timestamp - only return metrics active since this time
host?: string // Filter by host name
tags?: string // Filter by tags (comma-separated)
}
export interface ListMetricsOutput {
metrics: string[]
}
export interface ListMetricsResponse extends ToolResponse {
output: ListMetricsOutput
}
export interface GetMetricMetadataParams extends DatadogBaseParams {
metricName: string
}
export interface MetricMetadata {
description?: string
short_name?: string
unit?: string
per_unit?: string
type?: string
integration?: string
}
export interface GetMetricMetadataOutput {
metadata: MetricMetadata
}
export interface GetMetricMetadataResponse extends ToolResponse {
output: GetMetricMetadataOutput
}
// ========================
// EVENTS TYPES
// ========================
export type EventAlertType =
| 'error'
| 'warning'
| 'info'
| 'success'
| 'user_update'
| 'recommendation'
| 'snapshot'
export type EventPriority = 'normal' | 'low'
export interface CreateEventParams extends DatadogWriteOnlyParams {
title: string
text: string
alertType?: EventAlertType
priority?: EventPriority
host?: string
tags?: string // Comma-separated tags
aggregationKey?: string
sourceTypeName?: string
dateHappened?: number // Unix timestamp
}
export interface EventData {
id: number
title: string
text: string
date_happened: number
priority: string
alert_type: string
host?: string
tags?: string[]
url?: string
}
export interface CreateEventOutput {
event: EventData
}
export interface CreateEventResponse extends ToolResponse {
output: CreateEventOutput
}
export interface GetEventParams extends DatadogBaseParams {
eventId: string
}
export interface GetEventOutput {
event: EventData
}
export interface GetEventResponse extends ToolResponse {
output: GetEventOutput
}
export interface QueryEventsParams extends DatadogBaseParams {
start: number // Unix timestamp
end: number // Unix timestamp
priority?: EventPriority
sources?: string // Comma-separated source names
tags?: string // Comma-separated tags
unaggregated?: boolean
excludeAggregate?: boolean
page?: number
}
export interface QueryEventsOutput {
events: EventData[]
}
export interface QueryEventsResponse extends ToolResponse {
output: QueryEventsOutput
}
// ========================
// MONITORS TYPES
// ========================
export type MonitorType =
| 'metric alert'
| 'service check'
| 'event alert'
| 'process alert'
| 'log alert'
| 'query alert'
| 'composite'
| 'synthetics alert'
| 'trace-analytics alert'
| 'slo alert'
export interface MonitorThresholds {
critical?: number
critical_recovery?: number
warning?: number
warning_recovery?: number
ok?: number
}
export interface MonitorOptions {
notify_no_data?: boolean
no_data_timeframe?: number
notify_audit?: boolean
renotify_interval?: number
escalation_message?: string
thresholds?: MonitorThresholds
include_tags?: boolean
require_full_window?: boolean
timeout_h?: number
evaluation_delay?: number
new_group_delay?: number
min_location_failed?: number
}
export interface CreateMonitorParams extends DatadogBaseParams {
name: string
type: MonitorType
query: string
message?: string
tags?: string // Comma-separated tags
priority?: number // 1-5
options?: string // JSON string of MonitorOptions
}
export interface MonitorData {
id: number
name: string
type: string
query: string
message?: string
tags?: string[]
priority?: number
options?: MonitorOptions
overall_state?: string
created?: string
modified?: string
creator?: { email: string; handle: string; name: string }
}
export interface CreateMonitorOutput {
monitor: MonitorData
}
export interface CreateMonitorResponse extends ToolResponse {
output: CreateMonitorOutput
}
export interface GetMonitorParams extends DatadogBaseParams {
monitorId: string
groupStates?: string // Comma-separated states: alert, warn, no data
withDowntimes?: boolean
}
export interface GetMonitorOutput {
monitor: MonitorData
}
export interface GetMonitorResponse extends ToolResponse {
output: GetMonitorOutput
}
export interface UpdateMonitorParams extends DatadogBaseParams {
monitorId: string
name?: string
query?: string
message?: string
tags?: string // Comma-separated tags
priority?: number
options?: string // JSON string of MonitorOptions
}
export interface UpdateMonitorOutput {
monitor: MonitorData
}
export interface UpdateMonitorResponse extends ToolResponse {
output: UpdateMonitorOutput
}
export interface DeleteMonitorParams extends DatadogBaseParams {
monitorId: string
force?: boolean
}
export interface DeleteMonitorOutput {
deleted_monitor_id: number
}
export interface DeleteMonitorResponse extends ToolResponse {
output: DeleteMonitorOutput
}
export interface ListMonitorsParams extends DatadogBaseParams {
groupStates?: string // Comma-separated states
name?: string // Filter by name
tags?: string // Filter by tags (comma-separated)
monitorTags?: string // Filter by monitor tags
withDowntimes?: boolean
idOffset?: number
page?: number
pageSize?: number
}
export interface ListMonitorsOutput {
monitors: MonitorData[]
}
export interface ListMonitorsResponse extends ToolResponse {
output: ListMonitorsOutput
}
export interface MuteMonitorParams extends DatadogBaseParams {
monitorId: string
scope?: string // Scope to mute (e.g., "host:myhost")
end?: number // Unix timestamp when mute ends
}
export interface MuteMonitorOutput {
success: boolean
}
export interface MuteMonitorResponse extends ToolResponse {
output: MuteMonitorOutput
}
export interface UnmuteMonitorParams extends DatadogBaseParams {
monitorId: string
scope?: string
allScopes?: boolean
}
export interface UnmuteMonitorOutput {
success: boolean
}
export interface UnmuteMonitorResponse extends ToolResponse {
output: UnmuteMonitorOutput
}
// ========================
// LOGS TYPES
// ========================
export interface LogEntry {
ddsource?: string
ddtags?: string
hostname?: string
message: string
service?: string
}
export interface SendLogsParams extends DatadogWriteOnlyParams {
logs: string // JSON string of LogEntry[]
}
export interface SendLogsOutput {
success: boolean
}
export interface SendLogsResponse extends ToolResponse {
output: SendLogsOutput
}
export interface QueryLogsParams extends DatadogBaseParams {
query: string
from: string // ISO-8601 or relative (now-1h)
to: string // ISO-8601 or relative (now)
limit?: number
sort?: 'timestamp' | '-timestamp'
indexes?: string // Comma-separated index names
}
export interface LogData {
id: string
content: {
timestamp: string
host?: string
service?: string
message: string
status?: string
attributes?: Record<string, any>
tags?: string[]
}
}
export interface QueryLogsOutput {
logs: LogData[]
nextLogId?: string
}
export interface QueryLogsResponse extends ToolResponse {
output: QueryLogsOutput
}
// ========================
// DOWNTIME TYPES
// ========================
export interface CreateDowntimeParams extends DatadogBaseParams {
scope: string // Scope to apply downtime (e.g., "host:myhost" or "*")
message?: string
start?: number // Unix timestamp, defaults to now
end?: number // Unix timestamp
timezone?: string
monitorId?: string // Monitor ID to mute
monitorTags?: string // Comma-separated tags to match monitors
muteFirstRecoveryNotification?: boolean
notifyEndTypes?: string // Comma-separated: "canceled", "expired"
recurrence?: string // JSON string of recurrence config
}
export interface DowntimeData {
id: number
scope: string[]
message?: string
start?: number
end?: number
timezone?: string
monitor_id?: number
monitor_tags?: string[]
mute_first_recovery_notification?: boolean
disabled?: boolean
created?: number
modified?: number
creator_id?: number
canceled?: number
active?: boolean
}
export interface CreateDowntimeOutput {
downtime: DowntimeData
}
export interface CreateDowntimeResponse extends ToolResponse {
output: CreateDowntimeOutput
}
export interface ListDowntimesParams extends DatadogBaseParams {
currentOnly?: boolean
withCreator?: boolean
monitorId?: string
}
export interface ListDowntimesOutput {
downtimes: DowntimeData[]
}
export interface ListDowntimesResponse extends ToolResponse {
output: ListDowntimesOutput
}
export interface CancelDowntimeParams extends DatadogBaseParams {
downtimeId: string
}
export interface CancelDowntimeOutput {
success: boolean
}
export interface CancelDowntimeResponse extends ToolResponse {
output: CancelDowntimeOutput
}
// ========================
// SLO TYPES
// ========================
export type SloType = 'metric' | 'monitor' | 'time_slice'
export interface SloThreshold {
timeframe: '7d' | '30d' | '90d' | 'custom'
target: number // Target percentage (e.g., 99.9)
target_display?: string
warning?: number
warning_display?: string
}
export interface CreateSloParams extends DatadogBaseParams {
name: string
type: SloType
description?: string
tags?: string // Comma-separated tags
thresholds: string // JSON string of SloThreshold[]
// For metric-based SLO
query?: string // JSON string of { numerator: string, denominator: string }
// For monitor-based SLO
monitorIds?: string // Comma-separated monitor IDs
groups?: string // Comma-separated group names
}
export interface SloData {
id: string
name: string
type: string
description?: string
tags?: string[]
thresholds: SloThreshold[]
creator?: { email: string; handle: string; name: string }
created_at?: number
modified_at?: number
}
export interface CreateSloOutput {
slo: SloData
}
export interface CreateSloResponse extends ToolResponse {
output: CreateSloOutput
}
export interface GetSloHistoryParams extends DatadogBaseParams {
sloId: string
fromTs: number // Unix timestamp
toTs: number // Unix timestamp
target?: number // Target SLO percentage
}
export interface SloHistoryData {
from_ts: number
to_ts: number
type: string
type_id: number
sli_value?: number
overall: {
name: string
sli_value: number
span_precision: number
precision: { [key: string]: number }
}
series?: {
times: number[]
values: number[]
}
}
export interface GetSloHistoryOutput {
history: SloHistoryData
}
export interface GetSloHistoryResponse extends ToolResponse {
output: GetSloHistoryOutput
}
// ========================
// DASHBOARD TYPES
// ========================
export type DashboardLayoutType = 'ordered' | 'free'
export interface CreateDashboardParams extends DatadogBaseParams {
title: string
layoutType: DashboardLayoutType
description?: string
widgets?: string // JSON string of widget definitions
isReadOnly?: boolean
notifyList?: string // Comma-separated user handles to notify
templateVariables?: string // JSON string of template variable definitions
tags?: string // Comma-separated tags
}
export interface DashboardData {
id: string
title: string
layout_type: string
description?: string
url?: string
author_handle?: string
created_at?: string
modified_at?: string
is_read_only?: boolean
tags?: string[]
}
export interface CreateDashboardOutput {
dashboard: DashboardData
}
export interface CreateDashboardResponse extends ToolResponse {
output: CreateDashboardOutput
}
export interface GetDashboardParams extends DatadogBaseParams {
dashboardId: string
}
export interface GetDashboardOutput {
dashboard: DashboardData
}
export interface GetDashboardResponse extends ToolResponse {
output: GetDashboardOutput
}
export interface ListDashboardsParams extends DatadogBaseParams {
filterShared?: boolean
filterDeleted?: boolean
count?: number
start?: number
}
export interface DashboardSummary {
id: string
title: string
description?: string
layout_type: string
url?: string
author_handle?: string
created_at?: string
modified_at?: string
is_read_only?: boolean
popularity?: number
}
export interface ListDashboardsOutput {
dashboards: DashboardSummary[]
total?: number
}
export interface ListDashboardsResponse extends ToolResponse {
output: ListDashboardsOutput
}
// ========================
// HOSTS TYPES
// ========================
export interface ListHostsParams extends DatadogBaseParams {
filter?: string // Filter hosts by name, alias, or tag
sortField?: string // Field to sort by
sortDir?: 'asc' | 'desc'
start?: number // Starting offset
count?: number // Max hosts to return
from?: number // Unix timestamp - hosts seen in last N seconds
includeMutedHostsData?: boolean
includeHostsMetadata?: boolean
}
export interface HostData {
name: string
id: number
aliases?: string[]
apps?: string[]
aws_name?: string
host_name?: string
is_muted?: boolean
last_reported_time?: number
meta?: {
agent_version?: string
cpu_cores?: number
gohai?: string
machine?: string
platform?: string
}
metrics?: {
cpu?: number
iowait?: number
load?: number
}
mute_timeout?: number
sources?: string[]
tags_by_source?: Record<string, string[]>
up?: boolean
}
export interface ListHostsOutput {
hosts: HostData[]
total_matching?: number
total_returned?: number
}
export interface ListHostsResponse extends ToolResponse {
output: ListHostsOutput
}
// ========================
// INCIDENTS TYPES
// ========================
export type IncidentSeverity = 'SEV-1' | 'SEV-2' | 'SEV-3' | 'SEV-4' | 'SEV-5' | 'UNKNOWN'
export type IncidentState = 'active' | 'stable' | 'resolved'
export interface CreateIncidentParams extends DatadogBaseParams {
title: string
customerImpacted: boolean
severity?: IncidentSeverity
fields?: string // JSON string of additional fields
}
export interface IncidentData {
id: string
type: string
attributes: {
title: string
customer_impacted: boolean
severity?: IncidentSeverity
state?: IncidentState
created?: string
modified?: string
resolved?: string
detected?: string
customer_impact_scope?: string
customer_impact_start?: string
customer_impact_end?: string
public_id?: number
time_to_detect?: number
time_to_internal_response?: number
time_to_repair?: number
time_to_resolve?: number
}
}
export interface CreateIncidentOutput {
incident: IncidentData
}
export interface CreateIncidentResponse extends ToolResponse {
output: CreateIncidentOutput
}
export interface ListIncidentsParams extends DatadogBaseParams {
query?: string
pageSize?: number
pageOffset?: number
include?: string // Comma-separated: users, attachments
}
export interface ListIncidentsOutput {
incidents: IncidentData[]
}
export interface ListIncidentsResponse extends ToolResponse {
output: ListIncidentsOutput
}
// Union type for all Datadog responses
export type DatadogResponse =
| SubmitMetricsResponse
| QueryTimeseriesResponse
| ListMetricsResponse
| GetMetricMetadataResponse
| CreateEventResponse
| GetEventResponse
| QueryEventsResponse
| CreateMonitorResponse
| GetMonitorResponse
| UpdateMonitorResponse
| DeleteMonitorResponse
| ListMonitorsResponse
| MuteMonitorResponse
| UnmuteMonitorResponse
| SendLogsResponse
| QueryLogsResponse
| CreateDowntimeResponse
| ListDowntimesResponse
| CancelDowntimeResponse
| CreateSloResponse
| GetSloHistoryResponse
| CreateDashboardResponse
| GetDashboardResponse
| ListDashboardsResponse
| ListHostsResponse
| CreateIncidentResponse
| ListIncidentsResponse

View File

@@ -0,0 +1,87 @@
import type { DropboxCopyParams, DropboxCopyResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxCopyTool: ToolConfig<DropboxCopyParams, DropboxCopyResponse> = {
id: 'dropbox_copy',
name: 'Dropbox Copy',
description: 'Copy a file or folder in Dropbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
fromPath: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The source path of the file or folder to copy',
},
toPath: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The destination path for the copied file or folder',
},
autorename: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, rename the file if there is a conflict at destination',
},
},
request: {
url: 'https://api.dropboxapi.com/2/files/copy_v2',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
from_path: params.fromPath,
to_path: params.toPath,
autorename: params.autorename ?? false,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to copy file/folder',
output: {},
}
}
return {
success: true,
output: {
metadata: data.metadata,
},
}
},
outputs: {
metadata: {
type: 'object',
description: 'Metadata of the copied item',
properties: {
'.tag': { type: 'string', description: 'Type: file or folder' },
id: { type: 'string', description: 'Unique identifier' },
name: { type: 'string', description: 'Name of the copied item' },
path_display: { type: 'string', description: 'Display path' },
size: { type: 'number', description: 'Size in bytes (files only)' },
},
},
},
}

View File

@@ -0,0 +1,82 @@
import type { DropboxCreateFolderParams, DropboxCreateFolderResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxCreateFolderTool: ToolConfig<
DropboxCreateFolderParams,
DropboxCreateFolderResponse
> = {
id: 'dropbox_create_folder',
name: 'Dropbox Create Folder',
description: 'Create a new folder in Dropbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The path where the folder should be created (e.g., /new-folder)',
},
autorename: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, rename the folder if there is a conflict',
},
},
request: {
url: 'https://api.dropboxapi.com/2/files/create_folder_v2',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
path: params.path,
autorename: params.autorename ?? false,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to create folder',
output: {},
}
}
return {
success: true,
output: {
folder: data.metadata,
},
}
},
outputs: {
folder: {
type: 'object',
description: 'The created folder metadata',
properties: {
id: { type: 'string', description: 'Unique identifier for the folder' },
name: { type: 'string', description: 'Name of the folder' },
path_display: { type: 'string', description: 'Display path of the folder' },
path_lower: { type: 'string', description: 'Lowercase path of the folder' },
},
},
},
}

View File

@@ -0,0 +1,131 @@
import type {
DropboxCreateSharedLinkParams,
DropboxCreateSharedLinkResponse,
} from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxCreateSharedLinkTool: ToolConfig<
DropboxCreateSharedLinkParams,
DropboxCreateSharedLinkResponse
> = {
id: 'dropbox_create_shared_link',
name: 'Dropbox Create Shared Link',
description: 'Create a shareable link for a file or folder in Dropbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The path of the file or folder to share',
},
requestedVisibility: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Visibility: public, team_only, or password',
},
linkPassword: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for the shared link (only if visibility is password)',
},
expires: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Expiration date in ISO 8601 format (e.g., 2025-12-31T23:59:59Z)',
},
},
request: {
url: 'https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
const body: Record<string, any> = {
path: params.path,
}
const settings: Record<string, any> = {}
if (params.requestedVisibility) {
settings.requested_visibility = { '.tag': params.requestedVisibility }
}
if (params.linkPassword) {
settings.link_password = params.linkPassword
}
if (params.expires) {
settings.expires = params.expires
}
if (Object.keys(settings).length > 0) {
body.settings = settings
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
// Check if a shared link already exists
if (data.error_summary?.includes('shared_link_already_exists')) {
return {
success: false,
error:
'A shared link already exists for this path. Use list_shared_links to get the existing link.',
output: {},
}
}
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to create shared link',
output: {},
}
}
return {
success: true,
output: {
sharedLink: data,
},
}
},
outputs: {
sharedLink: {
type: 'object',
description: 'The created shared link',
properties: {
url: { type: 'string', description: 'The shared link URL' },
name: { type: 'string', description: 'Name of the shared item' },
path_lower: { type: 'string', description: 'Lowercase path of the shared item' },
expires: { type: 'string', description: 'Expiration date if set' },
link_permissions: {
type: 'object',
description: 'Permissions for the shared link',
},
},
},
},
}

View File

@@ -0,0 +1,76 @@
import type { DropboxDeleteParams, DropboxDeleteResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxDeleteTool: ToolConfig<DropboxDeleteParams, DropboxDeleteResponse> = {
id: 'dropbox_delete',
name: 'Dropbox Delete',
description: 'Delete a file or folder in Dropbox (moves to trash)',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The path of the file or folder to delete',
},
},
request: {
url: 'https://api.dropboxapi.com/2/files/delete_v2',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
path: params.path,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to delete file/folder',
output: {},
}
}
return {
success: true,
output: {
metadata: data.metadata,
deleted: true,
},
}
},
outputs: {
metadata: {
type: 'object',
description: 'Metadata of the deleted item',
properties: {
'.tag': { type: 'string', description: 'Type: file, folder, or deleted' },
name: { type: 'string', description: 'Name of the deleted item' },
path_display: { type: 'string', description: 'Display path' },
},
},
deleted: {
type: 'boolean',
description: 'Whether the deletion was successful',
},
},
}

View File

@@ -0,0 +1,82 @@
import type { DropboxDownloadParams, DropboxDownloadResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxDownloadTool: ToolConfig<DropboxDownloadParams, DropboxDownloadResponse> = {
id: 'dropbox_download',
name: 'Dropbox Download File',
description: 'Download a file from Dropbox and get a temporary link',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The path of the file to download (e.g., /folder/document.pdf)',
},
},
request: {
url: 'https://api.dropboxapi.com/2/files/get_temporary_link',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
path: params.path,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to download file',
output: {},
}
}
return {
success: true,
output: {
file: data.metadata,
content: '', // Content will be available via the temporary link
temporaryLink: data.link,
},
}
},
outputs: {
file: {
type: 'object',
description: 'The file metadata',
properties: {
id: { type: 'string', description: 'Unique identifier for the file' },
name: { type: 'string', description: 'Name of the file' },
path_display: { type: 'string', description: 'Display path of the file' },
size: { type: 'number', description: 'Size of the file in bytes' },
},
},
temporaryLink: {
type: 'string',
description: 'Temporary link to download the file (valid for ~4 hours)',
},
content: {
type: 'string',
description: 'Base64 encoded file content (if fetched)',
},
},
}

View File

@@ -0,0 +1,95 @@
import type { DropboxGetMetadataParams, DropboxGetMetadataResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxGetMetadataTool: ToolConfig<
DropboxGetMetadataParams,
DropboxGetMetadataResponse
> = {
id: 'dropbox_get_metadata',
name: 'Dropbox Get Metadata',
description: 'Get metadata for a file or folder in Dropbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The path of the file or folder to get metadata for',
},
includeMediaInfo: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, include media info for photos/videos',
},
includeDeleted: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, include deleted files in results',
},
},
request: {
url: 'https://api.dropboxapi.com/2/files/get_metadata',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
path: params.path,
include_media_info: params.includeMediaInfo ?? false,
include_deleted: params.includeDeleted ?? false,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to get metadata',
output: {},
}
}
return {
success: true,
output: {
metadata: data,
},
}
},
outputs: {
metadata: {
type: 'object',
description: 'Metadata for the file or folder',
properties: {
'.tag': { type: 'string', description: 'Type: file, folder, or deleted' },
id: { type: 'string', description: 'Unique identifier' },
name: { type: 'string', description: 'Name of the item' },
path_display: { type: 'string', description: 'Display path' },
path_lower: { type: 'string', description: 'Lowercase path' },
size: { type: 'number', description: 'Size in bytes (files only)' },
client_modified: { type: 'string', description: 'Client modification time' },
server_modified: { type: 'string', description: 'Server modification time' },
rev: { type: 'string', description: 'Revision identifier' },
content_hash: { type: 'string', description: 'Content hash' },
},
},
},
}

View File

@@ -0,0 +1,23 @@
import { dropboxCopyTool } from '@/tools/dropbox/copy'
import { dropboxCreateFolderTool } from '@/tools/dropbox/create_folder'
import { dropboxCreateSharedLinkTool } from '@/tools/dropbox/create_shared_link'
import { dropboxDeleteTool } from '@/tools/dropbox/delete'
import { dropboxDownloadTool } from '@/tools/dropbox/download'
import { dropboxGetMetadataTool } from '@/tools/dropbox/get_metadata'
import { dropboxListFolderTool } from '@/tools/dropbox/list_folder'
import { dropboxMoveTool } from '@/tools/dropbox/move'
import { dropboxSearchTool } from '@/tools/dropbox/search'
import { dropboxUploadTool } from '@/tools/dropbox/upload'
export {
dropboxCopyTool,
dropboxCreateFolderTool,
dropboxCreateSharedLinkTool,
dropboxDeleteTool,
dropboxDownloadTool,
dropboxGetMetadataTool,
dropboxListFolderTool,
dropboxMoveTool,
dropboxSearchTool,
dropboxUploadTool,
}

View File

@@ -0,0 +1,115 @@
import type { DropboxListFolderParams, DropboxListFolderResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxListFolderTool: ToolConfig<DropboxListFolderParams, DropboxListFolderResponse> =
{
id: 'dropbox_list_folder',
name: 'Dropbox List Folder',
description: 'List the contents of a folder in Dropbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The path of the folder to list (use "" for root)',
},
recursive: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, list contents recursively',
},
includeDeleted: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, include deleted files/folders',
},
includeMediaInfo: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, include media info for photos/videos',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of results to return (default: 500)',
},
},
request: {
url: 'https://api.dropboxapi.com/2/files/list_folder',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
path: params.path === '/' ? '' : params.path,
recursive: params.recursive ?? false,
include_deleted: params.includeDeleted ?? false,
include_media_info: params.includeMediaInfo ?? false,
limit: params.limit,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to list folder',
output: {},
}
}
return {
success: true,
output: {
entries: data.entries,
cursor: data.cursor,
hasMore: data.has_more,
},
}
},
outputs: {
entries: {
type: 'array',
description: 'List of files and folders in the directory',
items: {
type: 'object',
properties: {
'.tag': { type: 'string', description: 'Type: file, folder, or deleted' },
id: { type: 'string', description: 'Unique identifier' },
name: { type: 'string', description: 'Name of the file/folder' },
path_display: { type: 'string', description: 'Display path' },
size: { type: 'number', description: 'Size in bytes (files only)' },
},
},
},
cursor: {
type: 'string',
description: 'Cursor for pagination',
},
hasMore: {
type: 'boolean',
description: 'Whether there are more results',
},
},
}

View File

@@ -0,0 +1,87 @@
import type { DropboxMoveParams, DropboxMoveResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxMoveTool: ToolConfig<DropboxMoveParams, DropboxMoveResponse> = {
id: 'dropbox_move',
name: 'Dropbox Move',
description: 'Move or rename a file or folder in Dropbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
fromPath: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The source path of the file or folder to move',
},
toPath: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The destination path for the moved file or folder',
},
autorename: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, rename the file if there is a conflict at destination',
},
},
request: {
url: 'https://api.dropboxapi.com/2/files/move_v2',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
from_path: params.fromPath,
to_path: params.toPath,
autorename: params.autorename ?? false,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to move file/folder',
output: {},
}
}
return {
success: true,
output: {
metadata: data.metadata,
},
}
},
outputs: {
metadata: {
type: 'object',
description: 'Metadata of the moved item',
properties: {
'.tag': { type: 'string', description: 'Type: file or folder' },
id: { type: 'string', description: 'Unique identifier' },
name: { type: 'string', description: 'Name of the moved item' },
path_display: { type: 'string', description: 'Display path' },
size: { type: 'number', description: 'Size in bytes (files only)' },
},
},
},
}

View File

@@ -0,0 +1,135 @@
import type { DropboxSearchParams, DropboxSearchResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxSearchTool: ToolConfig<DropboxSearchParams, DropboxSearchResponse> = {
id: 'dropbox_search',
name: 'Dropbox Search',
description: 'Search for files and folders in Dropbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The search query',
},
path: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Limit search to a specific folder path',
},
fileExtensions: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Comma-separated list of file extensions to filter by (e.g., pdf,xlsx)',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of results to return (default: 100)',
},
},
request: {
url: 'https://api.dropboxapi.com/2/files/search_v2',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
const body: Record<string, any> = {
query: params.query,
}
const options: Record<string, any> = {}
if (params.path) {
options.path = params.path
}
if (params.fileExtensions) {
const extensions = params.fileExtensions
.split(',')
.map((ext) => ext.trim())
.filter((ext) => ext.length > 0)
if (extensions.length > 0) {
options.file_extensions = extensions
}
}
if (params.maxResults) {
options.max_results = params.maxResults
}
if (Object.keys(options).length > 0) {
body.options = options
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to search files',
output: {},
}
}
return {
success: true,
output: {
matches: data.matches || [],
hasMore: data.has_more || false,
cursor: data.cursor,
},
}
},
outputs: {
matches: {
type: 'array',
description: 'Search results',
items: {
type: 'object',
properties: {
match_type: {
type: 'object',
description: 'Type of match: filename, content, or both',
},
metadata: {
type: 'object',
description: 'File or folder metadata',
},
},
},
},
hasMore: {
type: 'boolean',
description: 'Whether there are more results',
},
cursor: {
type: 'string',
description: 'Cursor for pagination',
},
},
}

View File

@@ -0,0 +1,244 @@
import type { ToolResponse } from '@/tools/types'
// ===== Core Types =====
export interface DropboxFileMetadata {
'.tag': 'file'
id: string
name: string
path_display: string
path_lower: string
size: number
client_modified: string
server_modified: string
rev: string
content_hash?: string
is_downloadable?: boolean
}
export interface DropboxFolderMetadata {
'.tag': 'folder'
id: string
name: string
path_display: string
path_lower: string
}
export interface DropboxDeletedMetadata {
'.tag': 'deleted'
name: string
path_display: string
path_lower: string
}
export type DropboxMetadata = DropboxFileMetadata | DropboxFolderMetadata | DropboxDeletedMetadata
export interface DropboxSharedLinkMetadata {
url: string
name: string
path_lower: string
link_permissions: {
can_revoke: boolean
resolved_visibility: {
'.tag': 'public' | 'team_only' | 'password' | 'team_and_password' | 'shared_folder_only'
}
revoke_failure_reason?: {
'.tag': string
}
}
expires?: string
id?: string
}
export interface DropboxSearchMatch {
match_type: {
'.tag': 'filename' | 'content' | 'both'
}
metadata: {
'.tag': 'metadata'
metadata: DropboxMetadata
}
}
// ===== Base Params =====
export interface DropboxBaseParams {
accessToken?: string
}
// ===== Upload Params =====
export interface DropboxUploadParams extends DropboxBaseParams {
path: string
fileContent: string // Base64 encoded file content
fileName?: string
mode?: 'add' | 'overwrite'
autorename?: boolean
mute?: boolean
}
export interface DropboxUploadResponse extends ToolResponse {
output: {
file?: DropboxFileMetadata
}
}
// ===== Download Params =====
export interface DropboxDownloadParams extends DropboxBaseParams {
path: string
}
export interface DropboxDownloadResponse extends ToolResponse {
output: {
file?: DropboxFileMetadata
content?: string // Base64 encoded file content
temporaryLink?: string
}
}
// ===== List Folder Params =====
export interface DropboxListFolderParams extends DropboxBaseParams {
path: string
recursive?: boolean
includeDeleted?: boolean
includeMediaInfo?: boolean
limit?: number
}
export interface DropboxListFolderResponse extends ToolResponse {
output: {
entries?: DropboxMetadata[]
cursor?: string
hasMore?: boolean
}
}
// ===== Create Folder Params =====
export interface DropboxCreateFolderParams extends DropboxBaseParams {
path: string
autorename?: boolean
}
export interface DropboxCreateFolderResponse extends ToolResponse {
output: {
folder?: DropboxFolderMetadata
}
}
// ===== Delete Params =====
export interface DropboxDeleteParams extends DropboxBaseParams {
path: string
}
export interface DropboxDeleteResponse extends ToolResponse {
output: {
metadata?: DropboxMetadata
deleted?: boolean
}
}
// ===== Copy Params =====
export interface DropboxCopyParams extends DropboxBaseParams {
fromPath: string
toPath: string
autorename?: boolean
}
export interface DropboxCopyResponse extends ToolResponse {
output: {
metadata?: DropboxMetadata
}
}
// ===== Move Params =====
export interface DropboxMoveParams extends DropboxBaseParams {
fromPath: string
toPath: string
autorename?: boolean
}
export interface DropboxMoveResponse extends ToolResponse {
output: {
metadata?: DropboxMetadata
}
}
// ===== Get Metadata Params =====
export interface DropboxGetMetadataParams extends DropboxBaseParams {
path: string
includeMediaInfo?: boolean
includeDeleted?: boolean
}
export interface DropboxGetMetadataResponse extends ToolResponse {
output: {
metadata?: DropboxMetadata
}
}
// ===== Create Shared Link Params =====
export interface DropboxCreateSharedLinkParams extends DropboxBaseParams {
path: string
requestedVisibility?: 'public' | 'team_only' | 'password'
linkPassword?: string
expires?: string
}
export interface DropboxCreateSharedLinkResponse extends ToolResponse {
output: {
sharedLink?: DropboxSharedLinkMetadata
}
}
// ===== Search Params =====
export interface DropboxSearchParams extends DropboxBaseParams {
query: string
path?: string
fileExtensions?: string
maxResults?: number
}
export interface DropboxSearchResponse extends ToolResponse {
output: {
matches?: DropboxSearchMatch[]
hasMore?: boolean
cursor?: string
}
}
// ===== Get Temporary Link Params =====
export interface DropboxGetTemporaryLinkParams extends DropboxBaseParams {
path: string
}
export interface DropboxGetTemporaryLinkResponse extends ToolResponse {
output: {
metadata?: DropboxFileMetadata
link?: string
}
}
// ===== Combined Response Type =====
export type DropboxResponse =
| DropboxUploadResponse
| DropboxDownloadResponse
| DropboxListFolderResponse
| DropboxCreateFolderResponse
| DropboxDeleteResponse
| DropboxCopyResponse
| DropboxMoveResponse
| DropboxGetMetadataResponse
| DropboxCreateSharedLinkResponse
| DropboxSearchResponse
| DropboxGetTemporaryLinkResponse

View File

@@ -0,0 +1,119 @@
import type { DropboxUploadParams, DropboxUploadResponse } from '@/tools/dropbox/types'
import type { ToolConfig } from '@/tools/types'
export const dropboxUploadTool: ToolConfig<DropboxUploadParams, DropboxUploadResponse> = {
id: 'dropbox_upload',
name: 'Dropbox Upload File',
description: 'Upload a file to Dropbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'dropbox',
},
params: {
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'The path in Dropbox where the file should be saved (e.g., /folder/document.pdf)',
},
fileContent: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The base64 encoded content of the file to upload',
},
fileName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional filename (used if path is a folder)',
},
mode: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Write mode: add (default) or overwrite',
},
autorename: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'If true, rename the file if there is a conflict',
},
mute: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: "If true, don't notify the user about this upload",
},
},
request: {
url: 'https://content.dropboxapi.com/2/files/upload',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Dropbox API request')
}
const dropboxApiArg = {
path: params.path,
mode: params.mode || 'add',
autorename: params.autorename ?? true,
mute: params.mute ?? false,
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/octet-stream',
'Dropbox-API-Arg': JSON.stringify(dropboxApiArg),
}
},
body: (params) => {
// The body should be the raw binary data
// In this case we're passing the base64 content which will be decoded
return params.fileContent
},
},
transformResponse: async (response, params) => {
const data = await response.json()
if (!response.ok) {
return {
success: false,
error: data.error_summary || data.error?.message || 'Failed to upload file',
output: {},
}
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'object',
description: 'The uploaded file metadata',
properties: {
id: { type: 'string', description: 'Unique identifier for the file' },
name: { type: 'string', description: 'Name of the file' },
path_display: { type: 'string', description: 'Display path of the file' },
path_lower: { type: 'string', description: 'Lowercase path of the file' },
size: { type: 'number', description: 'Size of the file in bytes' },
client_modified: { type: 'string', description: 'Client modification time' },
server_modified: { type: 'string', description: 'Server modification time' },
rev: { type: 'string', description: 'Revision identifier' },
content_hash: { type: 'string', description: 'Content hash for the file' },
},
},
},
}

View File

@@ -0,0 +1,181 @@
import type {
ElasticsearchBulkParams,
ElasticsearchBulkResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchBulkParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchBulkParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/x-ndjson',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const bulkTool: ToolConfig<ElasticsearchBulkParams, ElasticsearchBulkResponse> = {
id: 'elasticsearch_bulk',
name: 'Elasticsearch Bulk Operations',
description:
'Perform multiple index, create, delete, or update operations in a single request for high performance.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: false,
description: 'Default index for operations that do not specify one',
},
operations: {
type: 'string',
required: true,
description: 'Bulk operations as NDJSON string (newline-delimited JSON)',
},
refresh: {
type: 'string',
required: false,
description: 'Refresh policy: true, false, or wait_for',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
let url = params.index
? `${baseUrl}/${encodeURIComponent(params.index)}/_bulk`
: `${baseUrl}/_bulk`
if (params.refresh) {
url += `?refresh=${params.refresh}`
}
return url
},
method: 'POST',
headers: (params) => buildAuthHeaders(params),
body: (params) => {
// The body should be NDJSON format - we pass it as raw string
// Ensure it ends with a newline
// Note: The executor in tools/utils.ts handles NDJSON content-type specially
// and accepts string bodies directly
let operations = params.operations.trim()
if (!operations.endsWith('\n')) {
operations += '\n'
}
return operations as unknown as Record<string, any>
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: { took: 0, errors: true, items: [] },
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
took: data.took,
errors: data.errors,
items: data.items,
},
}
},
outputs: {
took: {
type: 'number',
description: 'Time in milliseconds the bulk operation took',
},
errors: {
type: 'boolean',
description: 'Whether any operation had an error',
},
items: {
type: 'array',
description: 'Results for each operation',
},
},
}

View File

@@ -0,0 +1,212 @@
import type {
ElasticsearchClusterHealthParams,
ElasticsearchClusterHealthResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchClusterHealthParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchClusterHealthParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const clusterHealthTool: ToolConfig<
ElasticsearchClusterHealthParams,
ElasticsearchClusterHealthResponse
> = {
id: 'elasticsearch_cluster_health',
name: 'Elasticsearch Cluster Health',
description: 'Get the health status of the Elasticsearch cluster.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
waitForStatus: {
type: 'string',
required: false,
description: 'Wait until cluster reaches this status: green, yellow, or red',
},
timeout: {
type: 'string',
required: false,
description: 'Timeout for the wait operation (e.g., 30s, 1m)',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
let url = `${baseUrl}/_cluster/health`
const queryParams: string[] = []
if (params.waitForStatus) {
queryParams.push(`wait_for_status=${params.waitForStatus}`)
}
if (params.timeout) {
queryParams.push(`timeout=${encodeURIComponent(params.timeout)}`)
}
if (queryParams.length > 0) {
url += `?${queryParams.join('&')}`
}
return url
},
method: 'GET',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {
cluster_name: '',
status: 'red' as const,
timed_out: true,
number_of_nodes: 0,
number_of_data_nodes: 0,
active_primary_shards: 0,
active_shards: 0,
relocating_shards: 0,
initializing_shards: 0,
unassigned_shards: 0,
delayed_unassigned_shards: 0,
number_of_pending_tasks: 0,
number_of_in_flight_fetch: 0,
task_max_waiting_in_queue_millis: 0,
active_shards_percent_as_number: 0,
},
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
cluster_name: data.cluster_name,
status: data.status,
timed_out: data.timed_out,
number_of_nodes: data.number_of_nodes,
number_of_data_nodes: data.number_of_data_nodes,
active_primary_shards: data.active_primary_shards,
active_shards: data.active_shards,
relocating_shards: data.relocating_shards,
initializing_shards: data.initializing_shards,
unassigned_shards: data.unassigned_shards,
delayed_unassigned_shards: data.delayed_unassigned_shards,
number_of_pending_tasks: data.number_of_pending_tasks,
number_of_in_flight_fetch: data.number_of_in_flight_fetch,
task_max_waiting_in_queue_millis: data.task_max_waiting_in_queue_millis,
active_shards_percent_as_number: data.active_shards_percent_as_number,
},
}
},
outputs: {
cluster_name: {
type: 'string',
description: 'Name of the cluster',
},
status: {
type: 'string',
description: 'Cluster health status: green, yellow, or red',
},
number_of_nodes: {
type: 'number',
description: 'Total number of nodes in the cluster',
},
number_of_data_nodes: {
type: 'number',
description: 'Number of data nodes',
},
active_shards: {
type: 'number',
description: 'Number of active shards',
},
unassigned_shards: {
type: 'number',
description: 'Number of unassigned shards',
},
},
}

View File

@@ -0,0 +1,177 @@
import type {
ElasticsearchClusterStatsParams,
ElasticsearchClusterStatsResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchClusterStatsParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchClusterStatsParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const clusterStatsTool: ToolConfig<
ElasticsearchClusterStatsParams,
ElasticsearchClusterStatsResponse
> = {
id: 'elasticsearch_cluster_stats',
name: 'Elasticsearch Cluster Stats',
description: 'Get comprehensive statistics about the Elasticsearch cluster.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/_cluster/stats`
},
method: 'GET',
headers: (params) => buildAuthHeaders(params),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {
cluster_name: '',
cluster_uuid: '',
status: 'red',
nodes: {
count: { total: 0, data: 0, master: 0 },
versions: [],
},
indices: {
count: 0,
docs: { count: 0, deleted: 0 },
store: { size_in_bytes: 0 },
shards: { total: 0, primaries: 0 },
},
},
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
cluster_name: data.cluster_name,
cluster_uuid: data.cluster_uuid,
status: data.status,
nodes: {
count: data.nodes?.count || { total: 0, data: 0, master: 0 },
versions: data.nodes?.versions || [],
},
indices: {
count: data.indices?.count || 0,
docs: data.indices?.docs || { count: 0, deleted: 0 },
store: data.indices?.store || { size_in_bytes: 0 },
shards: data.indices?.shards || { total: 0, primaries: 0 },
},
},
}
},
outputs: {
cluster_name: {
type: 'string',
description: 'Name of the cluster',
},
status: {
type: 'string',
description: 'Cluster health status',
},
nodes: {
type: 'object',
description: 'Node statistics including count and versions',
},
indices: {
type: 'object',
description: 'Index statistics including document count and store size',
},
},
}

View File

@@ -0,0 +1,164 @@
import type {
ElasticsearchCountParams,
ElasticsearchCountResponse,
} from '@/tools/elasticsearch/types'
import type { ToolConfig } from '@/tools/types'
function buildBaseUrl(params: ElasticsearchCountParams): string {
if (params.deploymentType === 'cloud' && params.cloudId) {
const parts = params.cloudId.split(':')
if (parts.length >= 2) {
try {
const decoded = Buffer.from(parts[1], 'base64').toString('utf-8')
const [esHost] = decoded.split('$')
if (esHost) {
return `https://${parts[0]}.${esHost}`
}
} catch {
// Fallback
}
}
throw new Error('Invalid Cloud ID format')
}
if (!params.host) {
throw new Error('Host is required for self-hosted deployments')
}
return params.host.replace(/\/$/, '')
}
function buildAuthHeaders(params: ElasticsearchCountParams): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
if (params.authMethod === 'api_key' && params.apiKey) {
headers.Authorization = `ApiKey ${params.apiKey}`
} else if (params.authMethod === 'basic_auth' && params.username && params.password) {
const credentials = Buffer.from(`${params.username}:${params.password}`).toString('base64')
headers.Authorization = `Basic ${credentials}`
} else {
throw new Error('Invalid authentication configuration')
}
return headers
}
export const countTool: ToolConfig<ElasticsearchCountParams, ElasticsearchCountResponse> = {
id: 'elasticsearch_count',
name: 'Elasticsearch Count',
description: 'Count documents matching a query in Elasticsearch.',
version: '1.0.0',
params: {
deploymentType: {
type: 'string',
required: true,
description: 'Deployment type: self_hosted or cloud',
},
host: {
type: 'string',
required: false,
description: 'Elasticsearch host URL (for self-hosted)',
},
cloudId: {
type: 'string',
required: false,
description: 'Elastic Cloud ID (for cloud deployments)',
},
authMethod: {
type: 'string',
required: true,
description: 'Authentication method: api_key or basic_auth',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Elasticsearch API key',
},
username: {
type: 'string',
required: false,
description: 'Username for basic auth',
},
password: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Password for basic auth',
},
index: {
type: 'string',
required: true,
description: 'Index name to count documents in',
},
query: {
type: 'string',
required: false,
description: 'Optional query to filter documents (JSON string)',
},
},
request: {
url: (params) => {
const baseUrl = buildBaseUrl(params)
return `${baseUrl}/${encodeURIComponent(params.index)}/_count`
},
method: 'POST',
headers: (params) => buildAuthHeaders(params),
body: (params) => {
if (params.query) {
try {
return { query: JSON.parse(params.query) }
} catch {
return {}
}
}
return {}
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
let errorMessage = `Elasticsearch error: ${response.status}`
try {
const errorJson = JSON.parse(errorText)
errorMessage = errorJson.error?.reason || errorJson.error?.type || errorMessage
} catch {
errorMessage = errorText || errorMessage
}
return {
success: false,
output: {
count: 0,
_shards: { total: 0, successful: 0, skipped: 0, failed: 0 },
},
error: errorMessage,
}
}
const data = await response.json()
return {
success: true,
output: {
count: data.count,
_shards: data._shards,
},
}
},
outputs: {
count: {
type: 'number',
description: 'Number of documents matching the query',
},
_shards: {
type: 'object',
description: 'Shard statistics',
},
},
}

Some files were not shown because too many files have changed in this diff Show More