Compare commits

..

19 Commits

Author SHA1 Message Date
Waleed
c27c233da0 v0.5.21: google groups, virtualized code viewer, ui, autolayout, docs improvements 2025-12-08 13:10:50 -08:00
Waleed
4fb039f3e8 feat(i18n): update translations (#2249)
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-12-08 12:44:37 -08:00
Waleed
feb591867e fix(docs): fix salesforce docs & update styling (#2248) 2025-12-08 12:08:53 -08:00
Waleed
598c3bc684 feat(i18n): update translations (#2246)
Co-authored-by: icecrasher321 <icecrasher321@users.noreply.github.com>
2025-12-08 10:45:48 -08:00
Vikhyath Mondreti
01b2f809d1 feat(cursor): add cursor block and tools (#2245)
* fix(autolayout): reduce horizontal spacing in autolayout

* feat(cursor): add cursor cloud agents tools

* address greptile prompt
2025-12-08 09:49:28 -08:00
Waleed
d200f664e8 feat(i18n): update translations (#2244)
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-12-08 00:26:39 -08:00
Waleed
e2b077f582 fix(conditional): don't error in condition blocks when no conditions are satisfied (#2243)
* fix(conditional): don't error in condition blocks when no conditions are satisfied

* updated docs
2025-12-08 00:15:51 -08:00
Waleed
d09fd6cf92 fix(import): fixed trigger save on export/import flow (#2239)
* fix(import): fixed trigger save on export/import flow

* optimized test runners

* ack PR comments
2025-12-07 23:00:28 -08:00
Vikhyath Mondreti
434d12956e fix(autolayout): reduce horizontal spacing in autolayout (#2240) 2025-12-07 22:36:10 -08:00
Waleed
b49b8eeb9d feat(i18n): update translations (#2238)
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-12-07 19:43:12 -08:00
Waleed
5b9f3d3d02 feat(docs): added additional self-hosting documentation (#2237)
* feat(docs): added additional self-hosting documentation

* added more
2025-12-07 19:25:44 -08:00
Vikhyath Mondreti
05022e3468 fix(copilot-autolayout): more subflow cases and deal with resizing (#2236)
* fix sidebar hydration issue accurately

* improve autolayout subflow cases

* fix DOM structure to prevent HMR hydration issues
2025-12-07 19:14:18 -08:00
Vikhyath Mondreti
9f884c151c feat(credits): prepurchase credits (#2174)
* add credit balances

* add migrations

* remove handling for disputes

* fix idempotency key

* prep merge into staging

* code cleanup

* add back migration + prevent enterprise from purchasing credits

* remove circular import

* add dispute blocking

* fix lint

* fix: hydration error

* remove migration before merge staging
'

* moved credits addition to invoice payment success

---------

Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
2025-12-06 19:11:58 -08:00
Adam Gough
92c03b825b improvement(salesforce): fixed refresh and added endpoints (#2177)
* added salesforce other tools

* modified scope and change salesforce refresh

* fixed refresh

* remove dup code
2025-12-06 19:03:25 -08:00
Vikhyath Mondreti
e9d53042f6 fix(inactivity-notif): add cron to helm (#2235) 2025-12-06 18:54:58 -08:00
Waleed
22c9384f19 improvement(code): removed dedicated code-optimized virtualized viewer, baked it into the code component (#2234)
* improvement(code): removed dedicated code-optimized virtualized viewer, baked it into the code component

* ack PR comments
2025-12-06 18:12:51 -08:00
Waleed
6e02a73259 feat(i18n): update translations (#2233)
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-12-06 17:57:46 -08:00
Adam Gough
683b4476fa fix(google-drive): added support for shared drive (#2232)
* added param for shared drive

* removed comments

---------

Co-authored-by: aadamgough <adam@sim.ai>
2025-12-06 17:37:02 -08:00
Adam Gough
ae7937280e feat(google-groups): added google groups (#2229)
* added google-groups

* added a few more endpoints

* removed comments

* removed named imports, fixed comments

* fixed google groups tool names

---------

Co-authored-by: aadamgough <adam@sim.ai>
Co-authored-by: waleed <walif6@gmail.com>
2025-12-06 17:32:30 -08:00
285 changed files with 19811 additions and 4162 deletions

View File

@@ -89,6 +89,36 @@ Wait for the model to download, then visit [http://localhost:3000](http://localh
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.1:8b
```
#### Using an External Ollama Instance
If you already have Ollama running on your host machine (outside Docker), you need to configure the `OLLAMA_URL` to use `host.docker.internal` instead of `localhost`:
```bash
# Docker Desktop (macOS/Windows)
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
# Linux (add extra_hosts or use host IP)
docker compose -f docker-compose.prod.yml up -d # Then set OLLAMA_URL to your host's IP
```
**Why?** When running inside Docker, `localhost` refers to the container itself, not your host machine. `host.docker.internal` is a special DNS name that resolves to the host.
For Linux users, you can either:
- Use your host machine's actual IP address (e.g., `http://192.168.1.100:11434`)
- Add `extra_hosts: ["host.docker.internal:host-gateway"]` to the simstudio service in your compose file
#### Using vLLM
Sim also supports [vLLM](https://docs.vllm.ai/) for self-hosted models with OpenAI-compatible API:
```bash
# Set these environment variables
VLLM_BASE_URL=http://your-vllm-server:8000
VLLM_API_KEY=your_optional_api_key # Only if your vLLM instance requires auth
```
When running with Docker, use `host.docker.internal` if vLLM is on your host machine (same as Ollama above).
### Self-hosted: Dev Containers
1. Open VS Code with the [Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
@@ -190,6 +220,46 @@ Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:
- Go to https://sim.ai → Settings → Copilot and generate a Copilot API key
- Set `COPILOT_API_KEY` environment variable in your self-hosted apps/sim/.env file to that value
## Environment Variables
Key environment variables for self-hosted deployments (see `apps/sim/.env.example` for full list):
| Variable | Required | Description |
|----------|----------|-------------|
| `DATABASE_URL` | Yes | PostgreSQL connection string with pgvector |
| `BETTER_AUTH_SECRET` | Yes | Auth secret (`openssl rand -hex 32`) |
| `BETTER_AUTH_URL` | Yes | Your app URL (e.g., `http://localhost:3000`) |
| `NEXT_PUBLIC_APP_URL` | Yes | Public app URL (same as above) |
| `ENCRYPTION_KEY` | Yes | Encryption key (`openssl rand -hex 32`) |
| `OLLAMA_URL` | No | Ollama server URL (default: `http://localhost:11434`) |
| `VLLM_BASE_URL` | No | vLLM server URL for self-hosted models |
| `COPILOT_API_KEY` | No | API key from sim.ai for Copilot features |
## Troubleshooting
### Ollama models not showing in dropdown (Docker)
If you're running Ollama on your host machine and Sim in Docker, change `OLLAMA_URL` from `localhost` to `host.docker.internal`:
```bash
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
```
See [Using an External Ollama Instance](#using-an-external-ollama-instance) for details.
### Database connection issues
Ensure PostgreSQL has the pgvector extension installed. When using Docker, wait for the database to be healthy before running migrations.
### Port conflicts
If ports 3000, 3002, or 5432 are in use, configure alternatives:
```bash
# Custom ports
NEXT_PUBLIC_APP_URL=http://localhost:3100 POSTGRES_PORT=5433 docker compose up -d
```
## Tech Stack
- **Framework**: [Next.js](https://nextjs.org/) (App Router)

View File

@@ -182,7 +182,11 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
tableOfContent={{
style: 'clerk',
enabled: true,
header: <div className='mb-2 font-medium text-sm'>On this page</div>,
header: (
<div key='toc-header' className='mb-2 font-medium text-sm'>
On this page
</div>
),
footer: <TOCFooter />,
single: false,
}}

View File

@@ -101,9 +101,6 @@ export default async function Layout({ children, params }: LayoutProps) {
<Navbar />
<DocsLayout
tree={source.pageTree[lang]}
themeSwitch={{
enabled: false,
}}
nav={{
title: (
<Image
@@ -128,7 +125,7 @@ export default async function Layout({ children, params }: LayoutProps) {
},
}}
containerProps={{
className: '!pt-10',
className: '!pt-0',
}}
>
{children}

View File

@@ -96,45 +96,48 @@ aside#nd-sidebar {
border-right: none !important;
}
/* Responsive sidebar positioning */
/* Mobile: Fumadocs handles drawer */
@media (min-width: 768px) and (max-width: 1024px) {
aside[data-sidebar],
aside#nd-sidebar {
left: var(--sidebar-offset) !important;
/* Fumadocs v16: Add sidebar placeholder styling for grid area */
[data-sidebar-placeholder] {
background: transparent !important;
}
/* Fumadocs v16: Hide sidebar panel (floating collapse button) */
[data-sidebar-panel] {
display: none !important;
}
/* Mobile only: Reduce gap between navbar and content */
@media (max-width: 1023px) {
#nd-docs-layout {
margin-top: -25px;
}
}
/* Desktop layout alignment */
@media (min-width: 1025px) {
[data-sidebar-container] {
margin-left: var(--sidebar-offset) !important;
}
aside[data-sidebar],
aside#nd-sidebar {
left: var(--sidebar-offset) !important;
}
/* TOC positioning - target all possible selectors */
[data-toc],
aside[data-toc],
div[data-toc],
.fd-toc,
#nd-toc,
nav[data-toc],
aside:has([role="complementary"]) {
right: var(--toc-offset) !important;
/* Desktop only: Apply custom navbar offset, sidebar width and margin offsets */
/* On mobile, let fumadocs handle the layout natively */
@media (min-width: 1024px) {
:root {
--fd-banner-height: 64px !important;
}
/* Alternative TOC container targeting */
[data-docs-page] > aside:last-child,
main ~ aside {
right: var(--toc-offset) !important;
#nd-docs-layout {
--fd-docs-height: calc(100dvh - 64px) !important;
--fd-sidebar-width: 300px !important;
margin-left: var(--sidebar-offset) !important;
margin-right: var(--toc-offset) !important;
}
/* Hide fumadocs nav on desktop - we use custom navbar there */
#nd-docs-layout > header {
display: none !important;
}
}
/* Sidebar spacing - compact like turborepo */
[data-sidebar-viewport] {
padding: 0.5rem 20px 12px;
/* Fumadocs v16: [data-sidebar-viewport] doesn't exist, target #nd-sidebar > div instead */
[data-sidebar-viewport],
#nd-sidebar > div {
padding: 0.5rem 12px 12px;
background: transparent !important;
background-color: transparent !important;
}
@@ -142,8 +145,9 @@ aside#nd-sidebar {
/* Override sidebar item styling to match Raindrop */
/* Target Link and button elements in sidebar - override Fumadocs itemVariants */
/* Exclude the small chevron-only toggle buttons */
#nd-sidebar a,
#nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
/* Using html prefix for higher specificity over Tailwind v4 utilities */
html #nd-sidebar a,
html #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
font-size: 0.9375rem !important; /* 15px to match Raindrop */
line-height: 1.4 !important;
padding: 0.5rem 0.75rem !important; /* More compact like Raindrop */
@@ -154,14 +158,14 @@ aside#nd-sidebar {
}
/* Dark mode sidebar text */
.dark #nd-sidebar a,
.dark #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
html.dark #nd-sidebar a,
html.dark #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
color: rgba(255, 255, 255, 0.6) !important;
}
/* Light mode sidebar text */
:root:not(.dark) #nd-sidebar a,
:root:not(.dark) #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
html:not(.dark) #nd-sidebar a,
html:not(.dark) #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
color: rgba(0, 0, 0, 0.6) !important;
}
@@ -194,7 +198,10 @@ aside#nd-sidebar {
}
/* Section headers should be slightly larger */
[data-sidebar-viewport] [data-separator] {
/* Fumadocs v16: Also target #nd-sidebar for compatibility */
[data-sidebar-viewport] [data-separator],
#nd-sidebar [data-separator],
#nd-sidebar p {
font-size: 0.75rem !important;
font-weight: 600 !important;
text-transform: uppercase !important;
@@ -218,61 +225,61 @@ aside#nd-sidebar {
}
/* Dark mode active state */
.dark #nd-sidebar a[data-active="true"],
.dark #nd-sidebar button[data-active="true"],
.dark #nd-sidebar a.bg-fd-primary\/10,
.dark #nd-sidebar a.text-fd-primary,
.dark #nd-sidebar a[class*="bg-fd-primary"],
.dark #nd-sidebar a[class*="text-fd-primary"],
.dark #nd-sidebar a.bg-purple-50\/80,
.dark #nd-sidebar a.text-purple-600,
.dark #nd-sidebar a[class*="bg-purple"],
.dark #nd-sidebar a[class*="text-purple"] {
html.dark #nd-sidebar a[data-active="true"],
html.dark #nd-sidebar button[data-active="true"],
html.dark #nd-sidebar a.bg-fd-primary\/10,
html.dark #nd-sidebar a.text-fd-primary,
html.dark #nd-sidebar a[class*="bg-fd-primary"],
html.dark #nd-sidebar a[class*="text-fd-primary"],
html.dark #nd-sidebar a.bg-purple-50\/80,
html.dark #nd-sidebar a.text-purple-600,
html.dark #nd-sidebar a[class*="bg-purple"],
html.dark #nd-sidebar a[class*="text-purple"] {
background-color: rgba(255, 255, 255, 0.15) !important;
color: rgba(255, 255, 255, 1) !important;
}
/* Light mode active state */
:root:not(.dark) #nd-sidebar a[data-active="true"],
:root:not(.dark) #nd-sidebar button[data-active="true"],
:root:not(.dark) #nd-sidebar a.bg-fd-primary\/10,
:root:not(.dark) #nd-sidebar a.text-fd-primary,
:root:not(.dark) #nd-sidebar a[class*="bg-fd-primary"],
:root:not(.dark) #nd-sidebar a[class*="text-fd-primary"],
:root:not(.dark) #nd-sidebar a.bg-purple-50\/80,
:root:not(.dark) #nd-sidebar a.text-purple-600,
:root:not(.dark) #nd-sidebar a[class*="bg-purple"],
:root:not(.dark) #nd-sidebar a[class*="text-purple"] {
html:not(.dark) #nd-sidebar a[data-active="true"],
html:not(.dark) #nd-sidebar button[data-active="true"],
html:not(.dark) #nd-sidebar a.bg-fd-primary\/10,
html:not(.dark) #nd-sidebar a.text-fd-primary,
html:not(.dark) #nd-sidebar a[class*="bg-fd-primary"],
html:not(.dark) #nd-sidebar a[class*="text-fd-primary"],
html:not(.dark) #nd-sidebar a.bg-purple-50\/80,
html:not(.dark) #nd-sidebar a.text-purple-600,
html:not(.dark) #nd-sidebar a[class*="bg-purple"],
html:not(.dark) #nd-sidebar a[class*="text-purple"] {
background-color: rgba(0, 0, 0, 0.07) !important;
color: rgba(0, 0, 0, 0.9) !important;
}
/* Dark mode hover state */
.dark #nd-sidebar a:hover:not([data-active="true"]),
.dark #nd-sidebar button:hover:not([data-active="true"]) {
html.dark #nd-sidebar a:hover:not([data-active="true"]),
html.dark #nd-sidebar button:hover:not([data-active="true"]) {
background-color: rgba(255, 255, 255, 0.08) !important;
}
/* Light mode hover state */
:root:not(.dark) #nd-sidebar a:hover:not([data-active="true"]),
:root:not(.dark) #nd-sidebar button:hover:not([data-active="true"]) {
html:not(.dark) #nd-sidebar a:hover:not([data-active="true"]),
html:not(.dark) #nd-sidebar button:hover:not([data-active="true"]) {
background-color: rgba(0, 0, 0, 0.03) !important;
}
/* Dark mode - ensure active/selected items don't change on hover */
.dark #nd-sidebar a.bg-purple-50\/80:hover,
.dark #nd-sidebar a[class*="bg-purple"]:hover,
.dark #nd-sidebar a[data-active="true"]:hover,
.dark #nd-sidebar button[data-active="true"]:hover {
html.dark #nd-sidebar a.bg-purple-50\/80:hover,
html.dark #nd-sidebar a[class*="bg-purple"]:hover,
html.dark #nd-sidebar a[data-active="true"]:hover,
html.dark #nd-sidebar button[data-active="true"]:hover {
background-color: rgba(255, 255, 255, 0.15) !important;
color: rgba(255, 255, 255, 1) !important;
}
/* Light mode - ensure active/selected items don't change on hover */
:root:not(.dark) #nd-sidebar a.bg-purple-50\/80:hover,
:root:not(.dark) #nd-sidebar a[class*="bg-purple"]:hover,
:root:not(.dark) #nd-sidebar a[data-active="true"]:hover,
:root:not(.dark) #nd-sidebar button[data-active="true"]:hover {
html:not(.dark) #nd-sidebar a.bg-purple-50\/80:hover,
html:not(.dark) #nd-sidebar a[class*="bg-purple"]:hover,
html:not(.dark) #nd-sidebar a[data-active="true"]:hover,
html:not(.dark) #nd-sidebar button[data-active="true"]:hover {
background-color: rgba(0, 0, 0, 0.07) !important;
color: rgba(0, 0, 0, 0.9) !important;
}
@@ -351,7 +358,16 @@ aside[data-sidebar] > *:not([data-sidebar-viewport]) {
[data-sidebar] [data-title],
#nd-sidebar > a:first-child,
#nd-sidebar > div:first-child > a:first-child,
#nd-sidebar img[alt="Sim"] {
#nd-sidebar img[alt="Sim"],
/* Hide theme toggle at bottom of sidebar on desktop */
#nd-sidebar
> footer,
#nd-sidebar footer,
aside#nd-sidebar > *:last-child:not(div),
#nd-sidebar > button:last-child,
#nd-sidebar button[aria-label*="theme" i],
#nd-sidebar button[aria-label*="Theme"],
#nd-sidebar > div:last-child > button {
display: none !important;
visibility: hidden !important;
height: 0 !important;
@@ -498,13 +514,14 @@ main article,
============================================ */
/* Main content area - center and constrain like turborepo/raindrop */
/* Note: --sidebar-offset and --toc-offset are now applied at #nd-docs-layout level */
main[data-main] {
max-width: var(--spacing-fd-container, 1400px);
margin-left: auto;
margin-right: auto;
padding-top: 1rem;
padding-left: calc(var(--sidebar-offset) + var(--content-gap));
padding-right: calc(var(--toc-offset) + var(--content-gap));
padding-left: var(--content-gap);
padding-right: var(--content-gap);
order: 1 !important;
}

View File

@@ -7,8 +7,23 @@ import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { cn } from '@/lib/utils'
const LANG_PREFIXES = ['/en', '/es', '/fr', '/de', '/ja', '/zh']
function stripLangPrefix(path: string): string {
for (const prefix of LANG_PREFIXES) {
if (path === prefix) return '/'
if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length)
}
return path
}
function isActive(url: string, pathname: string, nested = true): boolean {
return url === pathname || (nested && pathname.startsWith(`${url}/`))
const normalizedPathname = stripLangPrefix(pathname)
const normalizedUrl = stripLangPrefix(url)
return (
normalizedUrl === normalizedPathname ||
(nested && normalizedPathname.startsWith(`${normalizedUrl}/`))
)
}
export function SidebarItem({ item }: { item: Item }) {
@@ -16,97 +31,158 @@ export function SidebarItem({ item }: { item: Item }) {
const active = isActive(item.url, pathname, false)
return (
<li className='mb-[0.0625rem] list-none'>
<Link
href={item.url}
className={cn(
'block rounded-md px-2.5 py-1.5 font-normal text-[13px] leading-tight transition-colors',
'text-gray-600 dark:text-gray-400',
!active && 'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
active &&
'bg-purple-50/80 font-medium text-purple-600 dark:bg-purple-900/15 dark:text-purple-400'
)}
>
{item.name}
</Link>
</li>
<Link
href={item.url}
data-active={active}
className={cn(
// Mobile styles (default)
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
active && 'bg-fd-primary/10 font-medium text-fd-primary',
// Desktop styles (lg+)
'lg:mb-[0.0625rem] lg:block lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight',
'lg:text-gray-600 lg:dark:text-gray-400',
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
active &&
'lg:bg-purple-50/80 lg:font-normal lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
)}
>
{item.name}
</Link>
)
}
export function SidebarFolder({ item, children }: { item: Folder; children: ReactNode }) {
const pathname = usePathname()
const hasActiveChild = checkHasActiveChild(item, pathname)
const hasChildren = item.children.length > 0
const [open, setOpen] = useState(hasActiveChild)
useEffect(() => {
setOpen(hasActiveChild)
}, [hasActiveChild])
const active = item.index ? isActive(item.index.url, pathname, false) : false
if (item.index && !hasChildren) {
return (
<Link
href={item.index.url}
data-active={active}
className={cn(
// Mobile styles (default)
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
active && 'bg-fd-primary/10 font-medium text-fd-primary',
// Desktop styles (lg+)
'lg:mb-[0.0625rem] lg:block lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight',
'lg:text-gray-600 lg:dark:text-gray-400',
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
active &&
'lg:bg-purple-50/80 lg:font-normal lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
)}
>
{item.name}
</Link>
)
}
return (
<li className='mb-[0.0625rem] list-none'>
{item.index ? (
<div className='flex items-center gap-0.5'>
<div className='flex flex-col lg:mb-[0.0625rem]'>
<div className='flex w-full items-center lg:gap-0.5'>
{item.index ? (
<Link
href={item.index.url}
data-active={active}
className={cn(
'block flex-1 rounded-md px-2.5 py-1.5 font-medium text-[13px] leading-tight transition-colors',
'text-gray-800 dark:text-gray-200',
!isActive(item.index.url, pathname, false) &&
'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
isActive(item.index.url, pathname, false) &&
'bg-purple-50/80 text-purple-600 dark:bg-purple-900/15 dark:text-purple-400'
// Mobile styles (default)
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
active && 'bg-fd-primary/10 font-medium text-fd-primary',
// Desktop styles (lg+)
'lg:block lg:flex-1 lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-medium lg:text-[13px] lg:leading-tight',
'lg:text-gray-800 lg:dark:text-gray-200',
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
active &&
'lg:bg-purple-50/80 lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
)}
>
{item.name}
</Link>
) : (
<button
onClick={() => setOpen(!open)}
className='cursor-pointer rounded p-1 transition-colors hover:bg-gray-100/60 dark:hover:bg-gray-800/40'
aria-label={open ? 'Collapse' : 'Expand'}
className={cn(
// Mobile styles (default)
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
'text-fd-muted-foreground hover:bg-fd-accent/50',
// Desktop styles (lg+)
'lg:flex lg:w-full lg:cursor-pointer lg:items-center lg:justify-between lg:rounded-md lg:px-2.5 lg:py-1.5 lg:text-left lg:font-medium lg:text-[13px] lg:leading-tight',
'lg:text-gray-800 lg:hover:bg-gray-100/60 lg:dark:text-gray-200 lg:dark:hover:bg-gray-800/40'
)}
>
<span>{item.name}</span>
{/* Desktop-only chevron for non-index folders */}
<ChevronRight
className={cn(
'h-3 w-3 text-gray-400 transition-transform duration-200 ease-in-out dark:text-gray-500',
'ml-auto hidden h-3 w-3 flex-shrink-0 text-gray-400 transition-transform duration-200 ease-in-out lg:block dark:text-gray-500',
open && 'rotate-90'
)}
/>
</button>
</div>
) : (
<button
onClick={() => setOpen(!open)}
)}
{hasChildren && (
<button
onClick={() => setOpen(!open)}
className={cn(
// Mobile styles
'rounded p-1 hover:bg-fd-accent/50',
// Desktop styles
'lg:cursor-pointer lg:rounded lg:p-1 lg:transition-colors lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40'
)}
aria-label={open ? 'Collapse' : 'Expand'}
>
<ChevronRight
className={cn(
// Mobile styles
'h-4 w-4 transition-transform',
// Desktop styles
'lg:h-3 lg:w-3 lg:text-gray-400 lg:duration-200 lg:ease-in-out lg:dark:text-gray-500',
open && 'rotate-90'
)}
/>
</button>
)}
</div>
{hasChildren && (
<div
className={cn(
'flex w-full cursor-pointer items-center justify-between rounded-md px-2.5 py-1.5 text-left font-medium text-[13px] leading-tight transition-colors',
'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
'text-gray-800 dark:text-gray-200'
'overflow-hidden transition-all duration-200 ease-in-out',
open ? 'max-h-[10000px] opacity-100' : 'max-h-0 opacity-0'
)}
>
<span>{item.name}</span>
<ChevronRight
className={cn(
'ml-auto h-3 w-3 flex-shrink-0 text-gray-400 transition-transform duration-200 ease-in-out dark:text-gray-500',
open && 'rotate-90'
)}
/>
</button>
{/* Mobile: simple indent */}
<div className='ml-4 flex flex-col gap-0.5 lg:hidden'>{children}</div>
{/* Desktop: styled with border */}
<ul className='mt-0.5 ml-2 hidden space-y-[0.0625rem] border-gray-200/60 border-l pl-2.5 lg:block dark:border-gray-700/60'>
{children}
</ul>
</div>
)}
<div
className={cn(
'overflow-hidden transition-all duration-200 ease-in-out',
open ? 'max-h-[10000px] opacity-100' : 'max-h-0 opacity-0'
)}
>
<ul className='mt-0.5 ml-2 space-y-[0.0625rem] border-gray-200/60 border-l pl-2.5 dark:border-gray-700/60'>
{children}
</ul>
</div>
</li>
</div>
)
}
export function SidebarSeparator({ item }: { item: Separator }) {
return (
<p className='mt-4 mb-1.5 px-2.5 font-semibold text-[10px] text-gray-500/80 uppercase tracking-wide dark:text-gray-500'>
<p
className={cn(
// Mobile styles
'mt-4 mb-2 px-2 font-medium text-fd-muted-foreground text-xs',
// Desktop styles
'lg:mt-4 lg:mb-1.5 lg:px-2.5 lg:font-semibold lg:text-[10px] lg:text-gray-500/80 lg:uppercase lg:tracking-wide lg:dark:text-gray-500'
)}
>
{item.name}
</p>
)

View File

@@ -4089,3 +4089,43 @@ export function PolymarketIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function GoogleGroupsIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 950 950'>
<path
d='M0 0 C0.94278809 0.16097168 1.88557617 0.32194336 2.85693359 0.48779297 C41.39640021 7.38000818 77.6089919 23.60492603 108.359375 47.75439453 C110.82847014 49.62899623 113.39874033 51.31473268 116 53 C116.46293457 52.57372314 116.92586914 52.14744629 117.40283203 51.70825195 C122.59800275 47.03131713 128.0429109 43.0311917 133.875 39.1875 C134.79337646 38.58075439 135.71175293 37.97400879 136.65795898 37.34887695 C193.06543269 0.50131391 260.71306352 -11.46626137 326.5625 2.1875 C345.0682729 6.30166442 362.9846016 12.66357256 380 21 C380.84159668 21.41121094 381.68319336 21.82242187 382.55029297 22.24609375 C394.71831585 28.25763667 406.12081333 34.88025718 417 43 C417.91386475 43.680625 417.91386475 43.680625 418.84619141 44.375 C427.3442083 50.7615534 435.33491318 57.63400459 443 65 C443.89187012 65.85102295 443.89187012 65.85102295 444.80175781 66.71923828 C453.69930679 75.25198316 461.69744094 84.07076231 469 94 C469.42619629 94.57073242 469.85239258 95.14146484 470.29150391 95.72949219 C480.43354301 109.33663206 488.67420729 123.71010258 496 139 C496.44988281 139.91910156 496.89976562 140.83820313 497.36328125 141.78515625 C507.8727502 163.93377648 513.86707037 187.75512603 517 212 C517.17128418 213.27705811 517.34256836 214.55411621 517.51904297 215.86987305 C518.22536878 222.32344617 518.20134476 228.78767265 518.23828125 235.2734375 C518.24439454 236.28303968 518.24439454 236.28303968 518.25063133 237.31303787 C518.27135228 240.8724256 518.28571641 244.43177631 518.29516602 247.99121094 C518.30623374 251.63452388 518.34067753 255.27722681 518.38033772 258.92032433 C518.40666551 261.75333675 518.41487174 264.58620664 518.41844749 267.4193306 C518.42558411 269.40605687 518.45193759 271.39268495 518.47883606 273.37924194 C518.44942207 285.76015309 515.12264488 295.57193005 507 305 C499.87428202 312.02979486 489.28626689 317.89258596 479.05601447 318.02519969 C476.62337781 317.96406397 474.19532718 317.86193279 471.76460171 317.74760151 C470.82658438 317.70850022 469.88856705 317.66939893 468.92212495 317.62911275 C466.85886798 317.54255897 464.79572187 317.45333201 462.73267936 317.36180875 C458.15557952 317.15958497 453.5778828 316.97272504 449.00018999 316.78448407 C445.21423965 316.62851756 441.42837797 316.470502 437.64251518 316.31242561 C398.91465695 314.70443054 360.18475613 313.32679155 321.43299866 312.45133972 C320.24242123 312.42435066 320.24242123 312.42435066 319.02779173 312.39681636 C302.59808087 312.02523843 286.16791507 311.67886855 269.73699983 311.36500082 C260.42194939 311.18705349 251.10704348 311.00250961 241.79223633 310.81225586 C240.94943279 310.79516155 240.10662926 310.77806724 239.23828622 310.76045491 C214.73870723 310.26060136 190.27214589 309.42986365 165.79391344 308.29660819 C125.90250763 306.44979005 86.03732773 305.17982958 46.09943641 305.14658036 C44.50897189 305.14521365 42.91850784 305.14311607 41.32804524 305.14028964 C-37.5133746 305.00531502 -103.97082682 308.91076335 -163.52726094 367.12336398 C-188.38087655 391.97851495 -210.60272126 420.04013588 -229.42095524 449.719724 C-234.44627637 457.53470195 -238.00370356 461.2158497 -247.01953125 463.8984375 C-257.45600695 465.67920678 -265.74521563 464.625344 -274.70703125 458.76953125 C-282.12356812 453.04603687 -286.55153627 446.30121318 -288 437 C-288.0944182 434.21858347 -288.13925894 431.46261367 -288.13371277 428.68151855 C-288.13597833 427.84676797 -288.13824388 427.01201739 -288.14057809 426.15197134 C-288.14690588 423.35585743 -288.14608946 420.55979215 -288.14526367 417.76367188 C-288.14825623 415.75762367 -288.15165898 413.75157604 -288.15544128 411.74552917 C-288.16428321 406.29210673 -288.16661969 400.83869999 -288.16725707 395.38527107 C-288.16796834 391.97072817 -288.17010429 388.55618925 -288.17275429 385.14164734 C-288.18201368 373.20657092 -288.1860891 361.27150567 -288.18530273 349.33642578 C-288.18470423 338.25260833 -288.19520972 327.16883991 -288.2110464 316.08503485 C-288.22418718 306.54413041 -288.22948379 297.00324184 -288.22884732 287.46232843 C-288.22859392 281.77588842 -288.23135606 276.08949183 -288.24202538 270.40306091 C-288.25181491 265.03831145 -288.25171239 259.67364588 -288.24450874 254.3088932 C-288.2434757 252.35683887 -288.24574798 250.40477901 -288.2518692 248.45273399 C-288.30129251 231.57587659 -287.43735147 214.67304784 -284.25 198.0625 C-284.07936035 197.16249268 -283.9087207 196.26248535 -283.73291016 195.33520508 C-277.33281977 162.80659035 -264.83654002 129.80613517 -245 103 C-244.30777344 102.03835938 -243.61554688 101.07671875 -242.90234375 100.0859375 C-231.62223929 84.68769966 -218.86054597 69.06796015 -204 57 C-202.82757852 56.01627594 -201.65576659 55.0318252 -200.484375 54.046875 C-165.05335732 24.48458427 -123.47627349 6.4544733 -78 -1 C-77.21238281 -1.13132324 -76.42476562 -1.26264648 -75.61328125 -1.39794922 C-51.08216194 -5.26757047 -24.38186362 -4.16780456 0 0 Z '
fill='#4185F3'
transform='translate(394,440)'
/>
<path
d='M0 0 C0.84159668 0.41121094 1.68319336 0.82242187 2.55029297 1.24609375 C14.71831585 7.25763667 26.12081333 13.88025718 37 22 C37.91386475 22.680625 37.91386475 22.680625 38.84619141 23.375 C47.3442083 29.7615534 55.33491318 36.63400459 63 44 C63.59458008 44.56734863 64.18916016 45.13469727 64.80175781 45.71923828 C73.69930679 54.25198316 81.69744094 63.07076231 89 73 C89.63929443 73.85609863 89.63929443 73.85609863 90.29150391 74.72949219 C100.43354301 88.33663206 108.67420729 102.71010258 116 118 C116.67482422 119.37865234 116.67482422 119.37865234 117.36328125 120.78515625 C127.8727502 142.93377648 133.86707037 166.75512603 137 191 C137.17128418 192.27705811 137.34256836 193.55411621 137.51904297 194.86987305 C138.22536878 201.32344617 138.20134476 207.78767265 138.23828125 214.2734375 C138.24439454 215.28303968 138.24439454 215.28303968 138.25063133 216.31303787 C138.27135228 219.8724256 138.28571641 223.43177631 138.29516602 226.99121094 C138.30623374 230.63452388 138.34067753 234.27722681 138.38033772 237.92032433 C138.40666551 240.75333675 138.41487174 243.58620664 138.41844749 246.4193306 C138.42558411 248.40605687 138.45193759 250.39268495 138.47883606 252.37924194 C138.44942207 264.76015309 135.12264488 274.57193005 127 284 C118.57776719 292.3088566 107.25707403 297.12771911 95.43405437 297.12304783 C94.31986273 297.12503888 93.20567109 297.12702993 92.05771607 297.12908131 C90.83884967 297.12616847 89.61998327 297.12325563 88.36418152 297.12025452 C86.41604418 297.12153615 86.41604418 297.12153615 84.42855054 297.12284368 C80.82553724 297.1250799 77.22255366 297.12107927 73.61954415 297.11606562 C69.7334335 297.1117488 65.84732453 297.11320842 61.96121216 297.1139679 C55.22888479 297.11449235 48.49656711 297.11135375 41.76424217 297.10573006 C32.03042012 297.09760442 22.29660275 297.09499874 12.56277768 297.09374207 C-3.22955387 297.09155322 -19.02188025 297.08489767 -34.81420898 297.07543945 C-50.15535455 297.06626101 -65.49649872 297.05918229 -80.83764648 297.05493164 C-81.78321047 297.05466892 -82.72877445 297.05440619 -83.70299188 297.05413551 C-88.44658929 297.05283048 -93.1901867 297.05156677 -97.93378413 297.05032361 C-137.28919303 297.03995621 -176.64459618 297.02234244 -216 297 C-216 296.67 -216 296.34 -216 296 C-215.10539063 295.78085937 -214.21078125 295.56171875 -213.2890625 295.3359375 C-199.60744526 291.74779481 -190.30226454 286.44506049 -182.33886719 274.51049805 C-175.8694217 262.19130537 -177.24804735 248.40718553 -177.47143555 234.86889648 C-177.50929034 232.14367461 -177.51761785 229.41872657 -177.52514935 226.69327831 C-177.58592774 206.53538226 -178.58970103 187.22051454 -183.1875 167.5625 C-183.34061646 166.90023407 -183.49373291 166.23796814 -183.65148926 165.55563354 C-193.05991068 125.06154086 -212.75392634 88.11244854 -240.77734375 57.4453125 C-242.91048395 55.09848338 -244.98632073 52.70965414 -247.0625 50.3125 C-250.74767655 46.15843419 -254.73024771 42.54055591 -259 39 C-259.90878906 38.23623047 -259.90878906 38.23623047 -260.8359375 37.45703125 C-262.2186283 36.29829741 -263.60842508 35.14804931 -265 34 C-261.24101804 29.06359373 -257.22764272 25.6831341 -252.125 22.1875 C-251.37541016 21.66760498 -250.62582031 21.14770996 -249.85351562 20.61206055 C-242.75668826 15.74085214 -235.4948349 11.22981506 -228 7 C-227.34596191 6.6277832 -226.69192383 6.25556641 -226.01806641 5.87207031 C-214.74568717 -0.48948891 -203.12149343 -5.48811078 -191 -10 C-189.783125 -10.46148437 -188.56625 -10.92296875 -187.3125 -11.3984375 C-127.13030363 -32.87253938 -57.14586678 -27.9977207 0 0 Z '
fill='#4185F3'
transform='translate(774,461)'
/>
<path
d='M0 0 C2.5086616 2.21585252 4.93546061 4.49626878 7.3515625 6.8125 C8.23457031 7.62074219 9.11757813 8.42898437 10.02734375 9.26171875 C26.83279315 25.16390495 37.10842709 45.7665863 43.3515625 67.8125 C43.69316406 68.99070312 44.03476562 70.16890625 44.38671875 71.3828125 C50.59092755 97.64248697 47.11935597 127.30989764 36.3515625 151.8125 C35.92665527 152.79379883 35.92665527 152.79379883 35.49316406 153.79492188 C25.5268296 176.37059662 8.35413683 195.88783887 -12.6484375 208.8125 C-13.55078125 209.410625 -14.453125 210.00875 -15.3828125 210.625 C-42.35348642 227.69118915 -77.57347657 232.41053788 -108.6484375 226.1875 C-132.14362975 220.89155145 -153.29033371 209.45309871 -170.6484375 192.8125 C-171.53144531 192.00425781 -172.41445313 191.19601562 -173.32421875 190.36328125 C-190.12966815 174.46109505 -200.40530209 153.8584137 -206.6484375 131.8125 C-206.99003906 130.63429688 -207.33164062 129.45609375 -207.68359375 128.2421875 C-213.88780255 101.98251303 -210.41623097 72.31510236 -199.6484375 47.8125 C-199.36516602 47.15830078 -199.08189453 46.50410156 -198.79003906 45.83007812 C-188.8237046 23.25440338 -171.65101183 3.73716113 -150.6484375 -9.1875 C-149.29492188 -10.0846875 -149.29492188 -10.0846875 -147.9140625 -11 C-102.93060045 -39.46411158 -40.49856726 -33.45014405 0 0 Z '
fill='#4285F4'
transform='translate(753.6484375,172.1875)'
/>
<path
d='M0 0 C2.5086616 2.21585252 4.93546061 4.49626878 7.3515625 6.8125 C8.23457031 7.62074219 9.11757813 8.42898437 10.02734375 9.26171875 C26.83279315 25.16390495 37.10842709 45.7665863 43.3515625 67.8125 C43.69316406 68.99070312 44.03476562 70.16890625 44.38671875 71.3828125 C50.59092755 97.64248697 47.11935597 127.30989764 36.3515625 151.8125 C35.92665527 152.79379883 35.92665527 152.79379883 35.49316406 153.79492188 C25.5268296 176.37059662 8.35413683 195.88783887 -12.6484375 208.8125 C-13.55078125 209.410625 -14.453125 210.00875 -15.3828125 210.625 C-42.35348642 227.69118915 -77.57347657 232.41053788 -108.6484375 226.1875 C-132.14362975 220.89155145 -153.29033371 209.45309871 -170.6484375 192.8125 C-171.53144531 192.00425781 -172.41445313 191.19601562 -173.32421875 190.36328125 C-190.12966815 174.46109505 -200.40530209 153.8584137 -206.6484375 131.8125 C-206.99003906 130.63429688 -207.33164063 129.45609375 -207.68359375 128.2421875 C-213.88780255 101.98251303 -210.41623097 72.31510236 -199.6484375 47.8125 C-199.36516602 47.15830078 -199.08189453 46.50410156 -198.79003906 45.83007812 C-188.8237046 23.25440338 -171.65101183 3.73716113 -150.6484375 -9.1875 C-149.29492188 -10.0846875 -149.29492188 -10.0846875 -147.9140625 -11 C-102.93060045 -39.46411158 -40.49856726 -33.45014405 0 0 Z '
fill='#4285F4'
transform='translate(433.6484375,172.1875)'
/>
<path
d='M0 0 C3.81562395 1.29823475 6.07090879 2.74966858 8.8125 5.6875 C11.4212352 8.40859646 14.04942755 10.95293127 16.9375 13.375 C51.99603414 43.73637132 74.26678331 91.46037764 84.375 135.8125 C84.61371826 136.84769775 84.85243652 137.88289551 85.09838867 138.94946289 C89.0693436 157.37670439 89.42863544 175.94083356 89.48046875 194.72631836 C89.48930372 197.45992471 89.51740015 200.19229146 89.5625 202.92553711 C90.0727242 234.5058769 90.0727242 234.5058769 78.75 248.5 C67.10259629 260.32182406 52.90572797 263.38672329 36.8588047 263.52641392 C33.85098681 263.53023974 30.8436979 263.51414279 27.8359375 263.49609375 C25.70992992 263.49624909 23.58392206 263.49774385 21.45791626 263.50050354 C17.02520058 263.50188017 12.59281136 263.48945128 8.16015625 263.46655273 C2.50723581 263.43813586 -3.14514462 263.44092501 -8.79810524 263.45364285 C-13.18043552 263.46062972 -17.56264313 263.45277705 -21.94495773 263.44038582 C-24.02834985 263.43590298 -26.11175527 263.43550647 -28.19514847 263.43945122 C-45.28053358 263.45666805 -61.10730832 262.67785762 -74.25 250.4375 C-84.29292795 239.84189519 -87.30277119 227.81294372 -87.3359375 213.64453125 C-87.34418045 212.55703079 -87.3524234 211.46953033 -87.36091614 210.34907532 C-87.38009551 206.83674954 -87.38011101 203.32487017 -87.375 199.8125 C-87.37545319 198.61937195 -87.37590637 197.4262439 -87.37637329 196.19696045 C-87.35610617 178.09521781 -86.46012832 160.79921916 -83 143 C-82.75469856 141.54891669 -82.51332288 140.0971597 -82.27734375 138.64453125 C-78.04339545 114.58454961 -68.4884574 91.45015164 -57 70 C-56.38769531 68.84628906 -55.77539062 67.69257812 -55.14453125 66.50390625 C-47.06102443 51.82025778 -37.08959704 38.53023956 -26 26 C-25.22914063 25.11828125 -24.45828125 24.2365625 -23.6640625 23.328125 C-16.29692403 15.00455101 -8.575012 7.08370557 0 0 Z '
fill='#1967D2'
transform='translate(508,495)'
/>
</svg>
)
}
export function CursorIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 546 546' fill='currentColor'>
<path d='m466.383 137.073-206.469-119.2034c-6.63-3.8287-14.811-3.8287-21.441 0l-206.4586 119.2034c-5.5734 3.218-9.0144 9.169-9.0144 15.615v240.375c0 6.436 3.441 12.397 9.0144 15.615l206.4686 119.203c6.63 3.829 14.811 3.829 21.441 0l206.468-119.203c5.574-3.218 9.015-9.17 9.015-15.615v-240.375c0-6.436-3.441-12.397-9.015-15.615zm-12.969 25.25-199.316 345.223c-1.347 2.326-4.904 1.376-4.904-1.319v-226.048c0-4.517-2.414-8.695-6.33-10.963l-195.7577-113.019c-2.3263-1.347-1.3764-4.905 1.3182-4.905h398.6305c5.661 0 9.199 6.136 6.368 11.041h-.009z' />
</svg>
)
}

View File

@@ -20,7 +20,7 @@ export function Navbar() {
<div
className='relative flex w-full items-center justify-between'
style={{
paddingLeft: 'calc(var(--sidebar-offset) + 20px)',
paddingLeft: 'calc(var(--sidebar-offset) + 32px)',
paddingRight: 'calc(var(--toc-offset) + 60px)',
}}
>

View File

@@ -15,6 +15,7 @@ import {
CalendlyIcon,
ClayIcon,
ConfluenceIcon,
CursorIcon,
DatadogIcon,
DiscordIcon,
DocumentIcon,
@@ -32,6 +33,7 @@ import {
GoogleDocsIcon,
GoogleDriveIcon,
GoogleFormsIcon,
GoogleGroupsIcon,
GoogleIcon,
GoogleSheetsIcon,
GoogleSlidesIcon,
@@ -195,6 +197,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_vault: GoogleVaultIcon,
google_slides: GoogleSlidesIcon,
google_sheets: GoogleSheetsIcon,
google_groups: GoogleGroupsIcon,
google_forms: GoogleFormsIcon,
google_drive: GoogleDriveIcon,
google_docs: GoogleDocsIcon,
@@ -212,6 +215,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
dropbox: DropboxIcon,
discord: DiscordIcon,
datadog: DatadogIcon,
cursor: CursorIcon,
confluence: ConfluenceIcon,
clay: ClayIcon,
calendly: CalendlyIcon,

View File

@@ -143,7 +143,7 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## Bewährte Praktiken
- **Bedingungen korrekt anordnen**: Platzieren Sie spezifischere Bedingungen vor allgemeinen, um sicherzustellen, dass spezifische Logik Vorrang vor Fallbacks hat
- **Eine Standardbedingung einfügen**: Fügen Sie eine Auffangbedingung (`true`) als letzte Bedingung hinzu, um nicht übereinstimmende Fälle zu behandeln und zu verhindern, dass die Workflow-Ausführung stecken bleibt
- **Ausdrücke einfach halten**: Verwenden Sie klare, unkomplizierte boolesche Ausdrücke für bessere Lesbarkeit und einfachere Fehlersuche
- **Verwenden Sie den Else-Zweig bei Bedarf**: Wenn keine Bedingungen übereinstimmen und der Else-Zweig nicht verbunden ist, endet der Workflow-Zweig ordnungsgemäß. Verbinden Sie den Else-Zweig, wenn Sie einen Fallback-Pfad für nicht übereinstimmende Fälle benötigen
- **Halten Sie Ausdrücke einfach**: Verwenden Sie klare, unkomplizierte boolesche Ausdrücke für bessere Lesbarkeit und einfachere Fehlersuche
- **Dokumentieren Sie Ihre Bedingungen**: Fügen Sie Beschreibungen hinzu, um den Zweck jeder Bedingung für bessere Teamzusammenarbeit und Wartung zu erklären
- **Grenzfälle testen**: Überprüfen Sie, ob Bedingungen Grenzwerte korrekt behandeln, indem Sie mit Werten an den Grenzen Ihrer Bedingungsbereiche testen
- **Testen Sie Grenzfälle**: Überprüfen Sie, ob Bedingungen Grenzwerte korrekt behandeln, indem Sie mit Werten an den Grenzen Ihrer Bedingungsbereiche testen

View File

@@ -72,7 +72,7 @@ Für benutzerdefinierte Integrationen nutzen Sie unsere [MCP (Model Context Prot
<Video src="introduction/integrations-sidebar.mp4" width={700} height={450} />
</div>
## KI-gesteuerter Copilot
## Copilot
**Fragen stellen & Anleitung erhalten**
Der Copilot beantwortet Fragen zu Sim, erklärt Ihre Workflows und gibt Verbesserungsvorschläge. Verwenden Sie das `@` Symbol, um auf Workflows, Blöcke, Dokumentation, Wissen und Protokolle für kontextbezogene Unterstützung zu verweisen.

View File

@@ -1,5 +1,7 @@
---
title: Wissensdatenbank
title: Übersicht
description: Laden Sie Ihre Dokumente hoch, verarbeiten und durchsuchen Sie sie
mit intelligenter Vektorsuche und Chunking
---
import { Video } from '@/components/ui/video'

View File

@@ -0,0 +1,155 @@
---
title: Docker
description: Sim Studio mit Docker Compose bereitstellen
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Schnellstart
```bash
# Clone and start
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
Öffnen Sie [http://localhost:3000](http://localhost:3000)
## Produktionseinrichtung
### 1. Umgebung konfigurieren
```bash
# Generate secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
```
### 2. Dienste starten
```bash
docker compose -f docker-compose.prod.yml up -d
```
### 3. SSL einrichten
<Tabs items={['Caddy (Empfohlen)', 'Nginx + Certbot']}>
<Tab value="Caddy (Empfohlen)">
Caddy verwaltet SSL-Zertifikate automatisch.
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
```
Erstellen Sie `/etc/caddy/Caddyfile`:
```
sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}
```
```bash
sudo systemctl restart caddy
```
</Tab>
<Tab value="Nginx + Certbot">
```bash
# Install
sudo apt install nginx certbot python3-certbot-nginx -y
# Create /etc/nginx/sites-available/sim
server {
listen 80;
server_name sim.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Enable and get certificate
sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
sudo certbot --nginx -d sim.yourdomain.com
```
</Tab>
</Tabs>
## Ollama
```bash
# With GPU
docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
# CPU only
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
```
Zusätzliche Modelle herunterladen:
```bash
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
```
### Externes Ollama
Wenn Ollama auf Ihrem Host-Rechner läuft (nicht in Docker):
```bash
# macOS/Windows
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
# Linux - use your host IP
OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
```
<Callout type="warning">
Innerhalb von Docker bezieht sich `localhost` auf den Container, nicht auf Ihren Host. Verwenden Sie `host.docker.internal` oder die IP-Adresse Ihres Hosts.
</Callout>
## Befehle
```bash
# View logs
docker compose -f docker-compose.prod.yml logs -f simstudio
# Stop
docker compose -f docker-compose.prod.yml down
# Update
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
# Backup database
docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
```

View File

@@ -0,0 +1,87 @@
---
title: Umgebungsvariablen
description: Konfigurationsreferenz für Sim Studio
---
import { Callout } from 'fumadocs-ui/components/callout'
## Erforderlich
| Variable | Beschreibung |
|----------|-------------|
| `DATABASE_URL` | PostgreSQL-Verbindungszeichenfolge |
| `BETTER_AUTH_SECRET` | Auth-Secret (32 Hex-Zeichen): `openssl rand -hex 32` |
| `BETTER_AUTH_URL` | Ihre App-URL |
| `ENCRYPTION_KEY` | Verschlüsselungsschlüssel (32 Hex-Zeichen): `openssl rand -hex 32` |
| `INTERNAL_API_SECRET` | Internes API-Secret (32 Hex-Zeichen): `openssl rand -hex 32` |
| `NEXT_PUBLIC_APP_URL` | Öffentliche App-URL |
| `NEXT_PUBLIC_SOCKET_URL` | WebSocket-URL (Standard: `http://localhost:3002`) |
## KI-Anbieter
| Variable | Anbieter |
|----------|----------|
| `OPENAI_API_KEY` | OpenAI |
| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
| `GEMINI_API_KEY_1` | Google Gemini |
| `MISTRAL_API_KEY` | Mistral |
| `OLLAMA_URL` | Ollama (Standard: `http://localhost:11434`) |
<Callout type="info">
Für Lastausgleich fügen Sie mehrere Schlüssel mit den Suffixen `_1`, `_2`, `_3` hinzu (z.B. `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`). Funktioniert mit OpenAI, Anthropic und Gemini.
</Callout>
<Callout type="info">
In Docker verwenden Sie `OLLAMA_URL=http://host.docker.internal:11434` für Ollama auf dem Host-System.
</Callout>
### Azure OpenAI
| Variable | Beschreibung |
|----------|-------------|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API-Schlüssel |
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI Endpoint-URL |
| `AZURE_OPENAI_API_VERSION` | API-Version (z.B. `2024-02-15-preview`) |
### vLLM (Selbst-gehostet)
| Variable | Beschreibung |
|----------|-------------|
| `VLLM_BASE_URL` | vLLM-Server-URL (z.B. `http://localhost:8000/v1`) |
| `VLLM_API_KEY` | Optionaler Bearer-Token für vLLM |
## OAuth-Anbieter
| Variable | Beschreibung |
|----------|-------------|
| `GOOGLE_CLIENT_ID` | Google OAuth Client-ID |
| `GOOGLE_CLIENT_SECRET` | Google OAuth Client-Secret |
| `GITHUB_CLIENT_ID` | GitHub OAuth Client-ID |
| `GITHUB_CLIENT_SECRET` | GitHub OAuth Client-Secret |
## Optional
| Variable | Beschreibung |
|----------|-------------|
| `API_ENCRYPTION_KEY` | Verschlüsselt gespeicherte API-Schlüssel (32 Hex-Zeichen): `openssl rand -hex 32` |
| `COPILOT_API_KEY` | API-Schlüssel für Copilot-Funktionen |
| `ADMIN_API_KEY` | Admin-API-Schlüssel für GitOps-Operationen |
| `RESEND_API_KEY` | E-Mail-Dienst für Benachrichtigungen |
| `ALLOWED_LOGIN_DOMAINS` | Registrierungen auf Domains beschränken (durch Kommas getrennt) |
| `ALLOWED_LOGIN_EMAILS` | Registrierungen auf bestimmte E-Mails beschränken (durch Kommas getrennt) |
| `DISABLE_REGISTRATION` | Auf `true` setzen, um neue Benutzerregistrierungen zu deaktivieren |
## Beispiel .env
```bash
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=<openssl rand -hex 32>
BETTER_AUTH_URL=https://sim.yourdomain.com
ENCRYPTION_KEY=<openssl rand -hex 32>
INTERNAL_API_SECRET=<openssl rand -hex 32>
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
OPENAI_API_KEY=sk-...
```
Siehe `apps/sim/.env.example` für alle Optionen.

View File

@@ -0,0 +1,50 @@
---
title: Self-Hosting
description: Stellen Sie Sim Studio auf Ihrer eigenen Infrastruktur bereit
---
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Callout } from 'fumadocs-ui/components/callout'
Stellen Sie Sim Studio auf Ihrer eigenen Infrastruktur mit Docker oder Kubernetes bereit.
## Anforderungen
| Ressource | Minimum | Empfohlen |
|----------|---------|-------------|
| CPU | 2 Kerne | 4+ Kerne |
| RAM | 12 GB | 16+ GB |
| Speicher | 20 GB SSD | 50+ GB SSD |
| Docker | 20.10+ | Neueste Version |
## Schnellstart
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
Öffnen Sie [http://localhost:3000](http://localhost:3000)
## Bereitstellungsoptionen
<Cards>
<Card title="Docker" href="/self-hosting/docker">
Bereitstellung mit Docker Compose auf jedem Server
</Card>
<Card title="Kubernetes" href="/self-hosting/kubernetes">
Bereitstellung mit Helm auf Kubernetes-Clustern
</Card>
<Card title="Cloud-Plattformen" href="/self-hosting/platforms">
Anleitungen für Railway, DigitalOcean, AWS, Azure, GCP
</Card>
</Cards>
## Architektur
| Komponente | Port | Beschreibung |
|-----------|------|-------------|
| simstudio | 3000 | Hauptanwendung |
| realtime | 3002 | WebSocket-Server |
| db | 5432 | PostgreSQL mit pgvector |
| migrations | - | Datenbank-Migrationen (werden einmal ausgeführt) |

View File

@@ -0,0 +1,133 @@
---
title: Kubernetes
description: Sim Studio mit Helm bereitstellen
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Voraussetzungen
- Kubernetes 1.19+
- Helm 3.0+
- PV-Provisioner-Unterstützung
## Installation
```bash
# Clone repo
git clone https://github.com/simstudioai/sim.git && cd sim
# Generate secrets
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
# Install
helm install sim ./helm/sim \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--namespace simstudio --create-namespace
```
## Cloud-spezifische Werte
<Tabs items={['AWS EKS', 'Azure AKS', 'GCP GKE']}>
<Tab value="AWS EKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-aws.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="Azure AKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-azure.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="GCP GKE">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-gcp.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
</Tabs>
## Schlüsselkonfiguration
```yaml
# Custom values.yaml
app:
replicaCount: 2
env:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
OPENAI_API_KEY: "sk-..."
postgresql:
persistence:
size: 50Gi
ingress:
enabled: true
className: nginx
tls:
enabled: true
app:
host: sim.yourdomain.com
```
Siehe `helm/sim/values.yaml` für alle Optionen.
## Externe Datenbank
```yaml
postgresql:
enabled: false
externalDatabase:
enabled: true
host: "your-db-host"
port: 5432
username: "postgres"
password: "your-password"
database: "simstudio"
sslMode: "require"
```
## Befehle
```bash
# Port forward for local access
kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
# View logs
kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
# Upgrade
helm upgrade sim ./helm/sim --namespace simstudio
# Uninstall
helm uninstall sim --namespace simstudio
```

View File

@@ -0,0 +1,124 @@
---
title: Cloud-Plattformen
description: Sim Studio auf Cloud-Plattformen bereitstellen
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Railway
Bereitstellung mit einem Klick und automatischer PostgreSQL-Bereitstellung.
[
![Auf Railway bereitstellen](https://railway.app/button.svg)
](https://railway.com/new/template/sim-studio)
Nach der Bereitstellung fügen Sie Umgebungsvariablen im Railway-Dashboard hinzu:
- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (automatisch generiert)
- `OPENAI_API_KEY` oder andere KI-Anbieter-Schlüssel
- Benutzerdefinierte Domain in Einstellungen → Netzwerk
## VPS-Bereitstellung
Für DigitalOcean, AWS EC2, Azure VMs oder jeden Linux-Server:
<Tabs items={['DigitalOcean', 'AWS EC2', 'Azure VM']}>
<Tab value="DigitalOcean">
**Empfohlen:** 16 GB RAM Droplet, Ubuntu 24.04
```bash
# Create Droplet via console, then SSH in
ssh root@your-droplet-ip
```
</Tab>
<Tab value="AWS EC2">
**Empfohlen:** t3.xlarge (16 GB RAM), Ubuntu 24.04
```bash
ssh -i your-key.pem ubuntu@your-ec2-ip
```
</Tab>
<Tab value="Azure VM">
**Empfohlen:** Standard_D4s_v3 (16 GB RAM), Ubuntu 24.04
```bash
ssh azureuser@your-vm-ip
```
</Tab>
</Tabs>
### Docker installieren
```bash
# Install Docker (official method)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Logout and reconnect, then verify
docker --version
```
### Sim Studio bereitstellen
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
# Create .env with secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
# Start
docker compose -f docker-compose.prod.yml up -d
```
### SSL mit Caddy
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Configure (replace domain)
echo 'sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}' | sudo tee /etc/caddy/Caddyfile
sudo systemctl restart caddy
```
Richten Sie den DNS A-Eintrag Ihrer Domain auf die IP-Adresse Ihres Servers.
## Kubernetes (EKS, AKS, GKE)
Siehe den [Kubernetes-Leitfaden](/self-hosting/kubernetes) für Helm-Deployment auf verwaltetem Kubernetes.
## Verwaltete Datenbank (Optional)
Für den Produktivbetrieb sollten Sie einen verwalteten PostgreSQL-Dienst verwenden:
- **AWS RDS** / **Azure Database** / **Cloud SQL** - Aktivieren Sie die pgvector-Erweiterung
- **Supabase** / **Neon** - pgvector enthalten
Setzen Sie `DATABASE_URL` in Ihrer Umgebung:
```bash
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
```

View File

@@ -0,0 +1,113 @@
---
title: Fehlerbehebung
description: Häufige Probleme und Lösungen
---
## Datenbankverbindung fehlgeschlagen
```bash
# Check database is running
docker compose ps db
# Test connection
docker compose exec db psql -U postgres -c "SELECT 1"
```
Überprüfen Sie das `DATABASE_URL` Format: `postgresql://user:pass@host:5432/database`
## Ollama-Modelle werden nicht angezeigt
In Docker ist `localhost` = der Container, nicht Ihr Host-Rechner.
```bash
# For host-machine Ollama, use:
OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
```
## WebSocket/Echtzeit funktioniert nicht
1. Prüfen Sie, ob `NEXT_PUBLIC_SOCKET_URL` mit Ihrer Domain übereinstimmt
2. Überprüfen Sie, ob der Echtzeit-Dienst läuft: `docker compose ps realtime`
3. Stellen Sie sicher, dass der Reverse-Proxy WebSocket-Upgrades weiterleitet (siehe [Docker-Anleitung](/self-hosting/docker))
## 502 Bad Gateway
```bash
# Check app is running
docker compose ps simstudio
docker compose logs simstudio
# Common causes: out of memory, database not ready
```
## Migrationsfehler
```bash
# View migration logs
docker compose logs migrations
# Run manually
docker compose exec simstudio bun run db:migrate
```
## pgvector nicht gefunden
Verwenden Sie das richtige PostgreSQL-Image:
```yaml
image: pgvector/pgvector:pg17 # NOT postgres:17
```
## Zertifikatsfehler (CERT_HAS_EXPIRED)
Wenn Sie SSL-Zertifikatsfehler beim Aufrufen externer APIs sehen:
```bash
# Update CA certificates in container
docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
# Or set in environment (not recommended for production)
NODE_TLS_REJECT_UNAUTHORIZED=0
```
## Leere Seite nach dem Login
1. Überprüfen Sie die Browser-Konsole auf Fehler
2. Stellen Sie sicher, dass `NEXT_PUBLIC_APP_URL` mit Ihrer tatsächlichen Domain übereinstimmt
3. Löschen Sie Browser-Cookies und lokalen Speicher
4. Prüfen Sie, ob alle Dienste laufen: `docker compose ps`
## Windows-spezifische Probleme
**Turbopack-Fehler unter Windows:**
```bash
# Use WSL2 for better compatibility
wsl --install
# Or disable Turbopack in package.json
# Change "next dev --turbopack" to "next dev"
```
**Zeilenende-Probleme:**
```bash
# Configure git to use LF
git config --global core.autocrlf input
```
## Logs anzeigen
```bash
# All services
docker compose logs -f
# Specific service
docker compose logs -f simstudio
```
## Hilfe erhalten
- [GitHub Issues](https://github.com/simstudioai/sim/issues)
- [Discord](https://discord.gg/Hr4UWYEcTT)

View File

@@ -0,0 +1,182 @@
---
title: Cursor
description: Starten und verwalten Sie Cursor Cloud-Agenten zur Arbeit an
GitHub-Repositories
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="cursor"
color="#1E1E1E"
/>
{/* MANUAL-CONTENT-START:intro */}
[Cursor](https://www.cursor.so/) ist eine KI-IDE und cloudbasierte Plattform, mit der Sie leistungsstarke KI-Agenten starten und verwalten können, die direkt mit Ihren GitHub-Repositories arbeiten können. Cursor-Agenten können Entwicklungsaufgaben automatisieren, die Produktivität Ihres Teams steigern und mit Ihnen zusammenarbeiten, indem sie Codeänderungen vornehmen, auf natürlichsprachliche Anweisungen reagieren und einen Gesprächsverlauf über ihre Aktivitäten führen.
Mit Cursor können Sie:
- **Cloud-Agenten für Codebasen starten**: Erstellen Sie sofort neue KI-Agenten, die in der Cloud an Ihren Repositories arbeiten
- **Codierungsaufgaben mit natürlicher Sprache delegieren**: Leiten Sie Agenten mit schriftlichen Anweisungen, Änderungen und Klarstellungen an
- **Fortschritt und Ergebnisse überwachen**: Rufen Sie den Agentenstatus ab, sehen Sie detaillierte Ergebnisse und prüfen Sie aktuelle oder abgeschlossene Aufgaben
- **Zugriff auf den vollständigen Gesprächsverlauf**: Überprüfen Sie alle Eingabeaufforderungen und KI-Antworten für Transparenz und Nachvollziehbarkeit
- **Steuerung und Verwaltung des Agenten-Lebenszyklus**: Listen Sie aktive Agenten auf, beenden Sie Agenten und verwalten Sie API-basierte Agentenstarts und Nachverfolgungen
In Sim ermöglicht die Cursor-Integration Ihren Agenten und Workflows, programmatisch mit Cursor-Cloud-Agenten zu interagieren. Das bedeutet, Sie können Sim verwenden, um:
- Alle Cloud-Agenten auflisten und ihren aktuellen Status durchsuchen (`cursor_list_agents`)
- Aktuellen Status und Ausgaben für jeden Agenten abrufen (`cursor_get_agent`)
- Den vollständigen Gesprächsverlauf für jeden Codierungsagenten anzeigen (`cursor_get_conversation`)
- Nachfolgende Anweisungen oder neue Eingabeaufforderungen zu einem laufenden Agenten hinzufügen
- Agenten nach Bedarf verwalten und beenden
Diese Integration hilft Ihnen, die flexible Intelligenz von Sim-Agenten mit den leistungsstarken Automatisierungsfunktionen von Cursor zu kombinieren, wodurch es möglich wird, KI-gesteuerte Entwicklung über Ihre Projekte hinweg zu skalieren.
{/* MANUAL-CONTENT-END */}
## Gebrauchsanweisung
Interagieren Sie mit der Cursor Cloud Agents API, um KI-Agenten zu starten, die an Ihren GitHub-Repositories arbeiten können. Unterstützt das Starten von Agenten, das Hinzufügen von Folgeanweisungen, die Statusprüfung, die Anzeige von Konversationen und die Verwaltung des Agenten-Lebenszyklus.
## Tools
### `cursor_list_agents`
Listet alle Cloud-Agenten für den authentifizierten Benutzer mit optionaler Paginierung auf.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Ja | Cursor API-Schlüssel |
| `limit` | number | Nein | Anzahl der zurückzugebenden Agenten \(Standard: 20, max: 100\) |
| `cursor` | string | Nein | Paginierungscursor aus der vorherigen Antwort |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Menschenlesbare Liste der Agenten |
| `metadata` | object | Metadaten der Agentenliste |
### `cursor_get_agent`
Ruft den aktuellen Status und die Ergebnisse eines Cloud-Agenten ab.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Ja | Cursor API-Schlüssel |
| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Menschenlesbare Agentendetails |
| `metadata` | object | Agenten-Metadaten |
### `cursor_get_conversation`
Ruft den Konversationsverlauf eines Cloud-Agenten ab, einschließlich aller Benutzeraufforderungen und Assistentenantworten.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Ja | Cursor API-Schlüssel |
| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
#### Output
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Menschenlesbarer Konversationsverlauf |
| `metadata` | object | Konversations-Metadaten |
### `cursor_launch_agent`
Starten Sie einen neuen Cloud-Agenten, um an einem GitHub-Repository mit den angegebenen Anweisungen zu arbeiten.
#### Input
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Ja | Cursor API-Schlüssel |
| `repository` | string | Ja | GitHub-Repository-URL \(z.B. https://github.com/your-org/your-repo\) |
| `ref` | string | Nein | Branch, Tag oder Commit, von dem aus gearbeitet werden soll \(standardmäßig der Hauptbranch\) |
| `promptText` | string | Ja | Der Anweisungstext für den Agenten |
| `promptImages` | string | Nein | JSON-Array von Bildobjekten mit Base64-Daten und Abmessungen |
| `model` | string | Nein | Zu verwendendes Modell \(leer lassen für automatische Auswahl\) |
| `branchName` | string | Nein | Benutzerdefinierter Branch-Name für den Agenten |
| `autoCreatePr` | boolean | Nein | Automatisches Erstellen eines PR, wenn der Agent fertig ist |
| `openAsCursorGithubApp` | boolean | Nein | Öffnen des PR als Cursor GitHub App |
| `skipReviewerRequest` | boolean | Nein | Überspringen der Anfrage nach Prüfern für den PR |
#### Output
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Erfolgsmeldung mit Agenten-Details |
| `metadata` | object | Metadaten zum Startergebnis |
### `cursor_add_followup`
Fügen Sie einem bestehenden Cloud-Agenten eine Folgeanweisung hinzu.
#### Input
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Ja | Cursor API-Schlüssel |
| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
| `followupPromptText` | string | Ja | Der Folgeanweisungstext für den Agenten |
| `promptImages` | string | Nein | JSON-Array von Bildobjekten mit Base64-Daten und Abmessungen \(max. 5\) |
#### Output
| Parameter | Type | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Erfolgsmeldung |
| `metadata` | object | Ergebnis-Metadaten |
### `cursor_stop_agent`
Stoppt einen laufenden Cloud-Agenten. Dies pausiert den Agenten, ohne ihn zu löschen.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Ja | Cursor API-Schlüssel |
| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Erfolgsmeldung |
| `metadata` | object | Ergebnis-Metadaten |
### `cursor_delete_agent`
Löscht einen Cloud-Agenten dauerhaft. Diese Aktion kann nicht rückgängig gemacht werden.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Ja | Cursor API-Schlüssel |
| `agentId` | string | Ja | Eindeutige Kennung für den Cloud-Agenten \(z.B. bc_abc123\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `content` | string | Erfolgsmeldung |
| `metadata` | object | Ergebnis-Metadaten |
## Hinweise
- Kategorie: `tools`
- Typ: `cursor`

View File

@@ -0,0 +1,217 @@
---
title: Google Groups
description: Google Workspace-Gruppen und deren Mitglieder verwalten
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_groups"
color="#E8F0FE"
/>
## Gebrauchsanweisung
Verbinden Sie sich mit Google Workspace, um Gruppen und deren Mitglieder mit der Admin SDK Directory API zu erstellen, zu aktualisieren und zu verwalten.
## Tools
### `google_groups_list_groups`
Alle Gruppen in einer Google Workspace-Domain auflisten
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `customer` | string | Nein | Kunden-ID oder "my_customer" für die Domain des authentifizierten Benutzers |
| `domain` | string | Nein | Domainname zum Filtern von Gruppen |
| `maxResults` | number | Nein | Maximale Anzahl der zurückzugebenden Ergebnisse \(1-200\) |
| `pageToken` | string | Nein | Token für Paginierung |
| `query` | string | Nein | Suchabfrage zum Filtern von Gruppen \(z.B. "email:admin*"\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_get_group`
Details einer bestimmten Google-Gruppe nach E-Mail oder Gruppen-ID abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_create_group`
Eine neue Google-Gruppe in der Domain erstellen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `email` | string | Ja | E-Mail-Adresse für die neue Gruppe (z.B. team@yourdomain.com) |
| `name` | string | Ja | Anzeigename für die Gruppe |
| `description` | string | Nein | Beschreibung der Gruppe |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_update_group`
Eine bestehende Google-Gruppe aktualisieren
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
| `name` | string | Nein | Neuer Anzeigename für die Gruppe |
| `description` | string | Nein | Neue Beschreibung für die Gruppe |
| `email` | string | Nein | Neue E-Mail-Adresse für die Gruppe |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_delete_group`
Eine Google-Gruppe löschen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID zum Löschen |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_list_members`
Alle Mitglieder einer Google-Gruppe auflisten
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
| `maxResults` | number | Nein | Maximale Anzahl der zurückzugebenden Ergebnisse \(1-200\) |
| `pageToken` | string | Nein | Token für Seitenumbruch |
| `roles` | string | Nein | Nach Rollen filtern \(durch Komma getrennt: OWNER, MANAGER, MEMBER\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_get_member`
Details eines bestimmten Mitglieds in einer Google-Gruppe abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
| `memberKey` | string | Ja | E-Mail-Adresse des Mitglieds oder eindeutige Mitglieds-ID |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_add_member`
Ein neues Mitglied zu einer Google-Gruppe hinzufügen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
| `email` | string | Ja | E-Mail-Adresse des hinzuzufügenden Mitglieds |
| `role` | string | Nein | Rolle für das Mitglied \(MEMBER, MANAGER oder OWNER\). Standardmäßig MEMBER. |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_remove_member`
Ein Mitglied aus einer Google-Gruppe entfernen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
| `memberKey` | string | Ja | E-Mail-Adresse oder eindeutige ID des zu entfernenden Mitglieds |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_update_member`
Ein Mitglied aktualisieren
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
| `memberKey` | string | Ja | E-Mail-Adresse des Mitglieds oder eindeutige Mitglieds-ID |
| `role` | string | Ja | Neue Rolle für das Mitglied \(MEMBER, MANAGER oder OWNER\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
### `google_groups_has_member`
Prüfen, ob ein Benutzer Mitglied einer Google-Gruppe ist
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Ja | E-Mail-Adresse der Gruppe oder eindeutige Gruppen-ID |
| `memberKey` | string | Ja | Zu prüfende E-Mail-Adresse des Mitglieds oder eindeutige Mitglieds-ID |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API-Antwortdaten |
## Hinweise
- Kategorie: `tools`
- Typ: `google_groups`

View File

@@ -135,283 +135,684 @@ Löschen eines Kontos aus Salesforce CRM
### `salesforce_get_contacts`
Kontakt(e) aus Salesforce abrufen - einzelner Kontakt, wenn ID angegeben, oder Liste, wenn nicht
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `contactId` | string | Nein | Kontakt-ID \(wenn angegeben, wird ein einzelner Kontakt zurückgegeben\) |
| `limit` | string | Nein | Anzahl der Ergebnisse \(Standard: 100, max: 2000\). Nur für Listenabfrage. |
| `fields` | string | Nein | Kommagetrennte Felder \(z.B. "Id,FirstName,LastName,Email,Phone"\) |
| `orderBy` | string | Nein | Sortierfeld \(z.B. "LastName ASC"\). Nur für Listenabfrage. |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `output` | object | Kontaktdaten |
### `salesforce_create_contact`
Einen neuen Kontakt im Salesforce CRM erstellen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `lastName` | string | Ja | Nachname \(erforderlich\) |
| `firstName` | string | Nein | Vorname |
| `email` | string | Nein | E-Mail-Adresse |
| `phone` | string | Nein | Telefonnummer |
| `accountId` | string | Nein | Konto-ID, mit der der Kontakt verknüpft werden soll |
| `title` | string | Nein | Keine Beschreibung |
| `department` | string | Nein | Abteilung |
| `mailingStreet` | string | Nein | Postanschrift \(Straße\) |
| `mailingCity` | string | Nein | Postanschrift \(Stadt\) |
| `mailingState` | string | Nein | Postanschrift \(Bundesland\) |
| `mailingPostalCode` | string | Nein | Postanschrift \(Postleitzahl\) |
| `mailingCountry` | string | Nein | Postanschrift \(Land\) |
| `description` | string | Nein | Kontaktbeschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `output` | object | Daten des erstellten Kontakts |
### `salesforce_update_contact`
Aktualisieren eines bestehenden Kontakts in Salesforce CRM
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `contactId` | string | Ja | Zu aktualisierende Kontakt-ID \(erforderlich\) |
| `lastName` | string | Nein | Nachname |
| `firstName` | string | Nein | Vorname |
| `email` | string | Nein | E-Mail-Adresse |
| `phone` | string | Nein | Telefonnummer |
| `accountId` | string | Nein | Zu verknüpfende Konto-ID |
| `title` | string | Nein | Keine Beschreibung |
| `department` | string | Nein | Abteilung |
| `mailingStreet` | string | Nein | Postanschrift (Straße) |
| `mailingCity` | string | Nein | Postanschrift (Stadt) |
| `mailingState` | string | Nein | Postanschrift (Bundesland) |
| `mailingPostalCode` | string | Nein | Postanschrift (Postleitzahl) |
| `mailingCountry` | string | Nein | Postanschrift (Land) |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `output` | object | Daten des aktualisierten Kontakts |
### `salesforce_delete_contact`
Löschen eines Kontakts aus Salesforce CRM
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `contactId` | string | Ja | Zu löschende Kontakt-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `output` | object | Daten des gelöschten Kontakts |
### `salesforce_get_leads`
Lead(s) aus Salesforce abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `leadId` | string | Nein | Lead-ID \(optional\) |
| `limit` | string | Nein | Maximale Ergebnisse \(Standard: 100\) |
| `fields` | string | Nein | Kommagetrennte Felder |
| `orderBy` | string | Nein | Sortierfeld |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Lead-Daten |
### `salesforce_create_lead`
Einen neuen Lead erstellen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `lastName` | string | Ja | Nachname \(erforderlich\) |
| `company` | string | Ja | Unternehmen \(erforderlich\) |
| `firstName` | string | Nein | Vorname |
| `email` | string | Nein | Keine Beschreibung |
| `phone` | string | Nein | Keine Beschreibung |
| `status` | string | Nein | Lead-Status |
| `leadSource` | string | Nein | Lead-Quelle |
| `title` | string | Nein | Keine Beschreibung |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Erstellter Lead |
### `salesforce_update_lead`
Aktualisieren eines vorhandenen Leads
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `leadId` | string | Ja | Lead-ID (erforderlich) |
| `lastName` | string | Nein | Nachname |
| `company` | string | Nein | Keine Beschreibung |
| `firstName` | string | Nein | Vorname |
| `email` | string | Nein | Keine Beschreibung |
| `phone` | string | Nein | Keine Beschreibung |
| `status` | string | Nein | Lead-Status |
| `leadSource` | string | Nein | Lead-Quelle |
| `title` | string | Nein | Keine Beschreibung |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Aktualisierter Lead |
### `salesforce_delete_lead`
Löschen eines Leads
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `leadId` | string | Ja | Lead-ID (erforderlich) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Gelöschter Lead |
### `salesforce_get_opportunities`
Verkaufschance(n) aus Salesforce abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `opportunityId` | string | Nein | Verkaufschancen-ID \(optional\) |
| `limit` | string | Nein | Maximale Ergebnisse \(Standard: 100\) |
| `fields` | string | Nein | Kommagetrennte Felder |
| `orderBy` | string | Nein | Sortierfeld |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Verkaufschancendaten |
### `salesforce_create_opportunity`
Eine neue Verkaufschance erstellen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `name` | string | Ja | Name der Verkaufschance \(erforderlich\) |
| `stageName` | string | Ja | Phasenname \(erforderlich\) |
| `closeDate` | string | Ja | Abschlussdatum JJJJ-MM-TT \(erforderlich\) |
| `accountId` | string | Nein | Konto-ID |
| `amount` | string | Nein | Betrag \(Zahl\) |
| `probability` | string | Nein | Wahrscheinlichkeit \(0-100\) |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Erstellte Verkaufschance |
### `salesforce_update_opportunity`
Aktualisieren einer bestehenden Verkaufschance
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `opportunityId` | string | Ja | Verkaufschancen-ID (erforderlich) |
| `name` | string | Nein | Name der Verkaufschance |
| `stageName` | string | Nein | Phasenname |
| `closeDate` | string | Nein | Abschlussdatum JJJJ-MM-TT |
| `accountId` | string | Nein | Konto-ID |
| `amount` | string | Nein | Keine Beschreibung |
| `probability` | string | Nein | Wahrscheinlichkeit (0-100) |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Aktualisierte Verkaufschance |
### `salesforce_delete_opportunity`
Löschen einer Verkaufschance
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `opportunityId` | string | Ja | Verkaufschancen-ID (erforderlich) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Gelöschte Verkaufschance |
### `salesforce_get_cases`
Fall/Fälle aus Salesforce abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `caseId` | string | Nein | Fall-ID \(optional\) |
| `limit` | string | Nein | Maximale Ergebnisse \(Standard: 100\) |
| `fields` | string | Nein | Kommagetrennte Felder |
| `orderBy` | string | Nein | Sortierfeld |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Falldaten |
### `salesforce_create_case`
Einen neuen Fall erstellen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `subject` | string | Ja | Fallbetreff \(erforderlich\) |
| `status` | string | Nein | Status \(z.B. Neu, In Bearbeitung, Eskaliert\) |
| `priority` | string | Nein | Priorität \(z.B. Niedrig, Mittel, Hoch\) |
| `origin` | string | Nein | Ursprung \(z.B. Telefon, E-Mail, Web\) |
| `contactId` | string | Nein | Kontakt-ID |
| `accountId` | string | Nein | Konto-ID |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Erstellter Fall |
### `salesforce_update_case`
Aktualisieren eines vorhandenen Falls
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `caseId` | string | Ja | Fall-ID \(erforderlich\) |
| `subject` | string | Nein | Fallbetreff |
| `status` | string | Nein | Status |
| `priority` | string | Nein | Priorität |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Aktualisierter Fall |
### `salesforce_delete_case`
Löschen eines Falls
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `caseId` | string | Ja | Fall-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Gelöschter Fall |
### `salesforce_get_tasks`
Aufgabe(n) von Salesforce abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `taskId` | string | Nein | Aufgaben-ID \(optional\) |
| `limit` | string | Nein | Maximale Ergebnisse \(Standard: 100\) |
| `fields` | string | Nein | Kommagetrennte Felder |
| `orderBy` | string | Nein | Sortierfeld |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Aufgabendaten |
### `salesforce_create_task`
Neue Aufgabe erstellen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `subject` | string | Ja | Aufgabenbetreff \(erforderlich\) |
| `status` | string | Nein | Status \(z.B. Nicht begonnen, In Bearbeitung, Abgeschlossen\) |
| `priority` | string | Nein | Priorität \(z.B. Niedrig, Normal, Hoch\) |
| `activityDate` | string | Nein | Fälligkeitsdatum JJJJ-MM-TT |
| `whoId` | string | Nein | Zugehörige Kontakt-/Lead-ID |
| `whatId` | string | Nein | Zugehörige Konto-/Opportunity-ID |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Erstellte Aufgabe |
### `salesforce_update_task`
Bestehende Aufgabe aktualisieren
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `taskId` | string | Ja | Aufgaben-ID \(erforderlich\) |
| `subject` | string | Nein | Aufgabenbetreff |
| `status` | string | Nein | Status |
| `priority` | string | Nein | Priorität |
| `activityDate` | string | Nein | Fälligkeitsdatum JJJJ-MM-TT |
| `description` | string | Nein | Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Aktualisierte Aufgabe |
### `salesforce_delete_task`
Aufgabe löschen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `taskId` | string | Ja | Aufgaben-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Status des Operationserfolgs |
| `output` | json | Ergebnisdaten der Operation |
| `success` | boolean | Erfolg |
| `output` | object | Gelöschte Aufgabe |
### `salesforce_list_reports`
Liste der für den aktuellen Benutzer zugänglichen Berichte abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `folderName` | string | Nein | Nach Ordnernamen filtern |
| `searchTerm` | string | Nein | Suchbegriff zum Filtern von Berichten nach Namen |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Berichtsdaten |
### `salesforce_get_report`
Metadaten und Beschreibungsinformationen für einen bestimmten Bericht abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `reportId` | string | Ja | Berichts-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Berichtsmetadaten |
### `salesforce_run_report`
Einen Bericht ausführen und die Ergebnisse abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `reportId` | string | Ja | Berichts-ID \(erforderlich\) |
| `includeDetails` | string | Nein | Detailzeilen einschließen \(true/false, Standard: true\) |
| `filters` | string | Nein | JSON-String der anzuwendenden Berichtsfilter |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Berichtsergebnisse |
### `salesforce_list_report_types`
Eine Liste der verfügbaren Berichtstypen abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Daten zu Berichtstypen |
### `salesforce_list_dashboards`
Eine Liste der für den aktuellen Benutzer zugänglichen Dashboards abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `folderName` | string | Nein | Nach Ordnernamen filtern |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Dashboard-Daten |
### `salesforce_get_dashboard`
Details und Ergebnisse für ein bestimmtes Dashboard abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `dashboardId` | string | Ja | Dashboard-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Dashboard-Daten |
### `salesforce_refresh_dashboard`
Ein Dashboard aktualisieren, um die neuesten Daten zu erhalten
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `dashboardId` | string | Ja | Dashboard-ID \(erforderlich\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Aktualisierte Dashboard-Daten |
### `salesforce_query`
Eine benutzerdefinierte SOQL-Abfrage ausführen, um Daten aus Salesforce abzurufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `query` | string | Ja | SOQL-Abfrage zur Ausführung \(z.B. SELECT Id, Name FROM Account LIMIT 10\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Abfrageergebnisse |
### `salesforce_query_more`
Abrufen zusätzlicher Abfrageergebnisse mit der nextRecordsUrl aus einer vorherigen Abfrage
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `nextRecordsUrl` | string | Ja | Die nextRecordsUrl aus einer vorherigen Abfrageantwort |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Abfrageergebnisse |
### `salesforce_describe_object`
Metadaten und Feldinformationen für ein Salesforce-Objekt abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
| `objectName` | string | Ja | API-Name des Objekts \(z.B. Account, Contact, Lead, Custom_Object__c\) |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Objekt-Metadaten |
### `salesforce_list_objects`
Liste aller verfügbaren Salesforce-Objekte abrufen
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Nein | Keine Beschreibung |
| `instanceUrl` | string | Nein | Keine Beschreibung |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Erfolgsstatus |
| `output` | object | Objektliste |
## Hinweise

View File

@@ -135,7 +135,7 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## Best Practices
- **Order conditions correctly**: Place more specific conditions before general ones to ensure specific logic takes precedence over fallbacks
- **Include a default condition**: Add a catch-all condition (`true`) as the last condition to handle unmatched cases and prevent workflow execution from getting stuck
- **Use the else branch when needed**: If no conditions match and the else branch is not connected, the workflow branch will end gracefully. Connect the else branch if you need a fallback path for unmatched cases
- **Keep expressions simple**: Use clear, straightforward boolean expressions for better readability and easier debugging
- **Document your conditions**: Add descriptions to explain the purpose of each condition for better team collaboration and maintenance
- **Test edge cases**: Verify conditions handle boundary values correctly by testing with values at the edges of your condition ranges

View File

@@ -72,7 +72,7 @@ For custom integrations, leverage our [MCP (Model Context Protocol) support](/mc
<Video src="introduction/integrations-sidebar.mp4" width={700} height={450} />
</div>
## AI-Powered Copilot
## Copilot
**Ask Questions & Get Guidance**
Copilot answers questions about Sim, explains your workflows, and provides suggestions for improvements. Use the `@` symbol to reference workflows, blocks, documentation, knowledge, and logs for contextual assistance.

View File

@@ -1,5 +1,6 @@
---
title: Knowledgebase
title: Overview
description: Upload, process, and search through your documents with intelligent vector search and chunking
---
import { Video } from '@/components/ui/video'

View File

@@ -0,0 +1,4 @@
{
"title": "Knowledgebase",
"pages": ["index", "tags"]
}

View File

@@ -13,7 +13,8 @@
"variables",
"execution",
"permissions",
"sdks"
"sdks",
"self-hosting"
],
"defaultOpen": false
}

View File

@@ -0,0 +1,150 @@
---
title: Docker
description: Deploy Sim Studio with Docker Compose
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Quick Start
```bash
# Clone and start
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
Open [http://localhost:3000](http://localhost:3000)
## Production Setup
### 1. Configure Environment
```bash
# Generate secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
```
### 2. Start Services
```bash
docker compose -f docker-compose.prod.yml up -d
```
### 3. Set Up SSL
<Tabs items={['Caddy (Recommended)', 'Nginx + Certbot']}>
<Tab value="Caddy (Recommended)">
Caddy automatically handles SSL certificates.
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
```
Create `/etc/caddy/Caddyfile`:
```
sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}
```
```bash
sudo systemctl restart caddy
```
</Tab>
<Tab value="Nginx + Certbot">
```bash
# Install
sudo apt install nginx certbot python3-certbot-nginx -y
# Create /etc/nginx/sites-available/sim
server {
listen 80;
server_name sim.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Enable and get certificate
sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
sudo certbot --nginx -d sim.yourdomain.com
```
</Tab>
</Tabs>
## Ollama
```bash
# With GPU
docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
# CPU only
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
```
Pull additional models:
```bash
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
```
### External Ollama
If Ollama runs on your host machine (not in Docker):
```bash
# macOS/Windows
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
# Linux - use your host IP
OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
```
<Callout type="warning">
Inside Docker, `localhost` refers to the container, not your host. Use `host.docker.internal` or your host's IP.
</Callout>
## Commands
```bash
# View logs
docker compose -f docker-compose.prod.yml logs -f simstudio
# Stop
docker compose -f docker-compose.prod.yml down
# Update
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
# Backup database
docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
```

View File

@@ -0,0 +1,87 @@
---
title: Environment Variables
description: Configuration reference for Sim Studio
---
import { Callout } from 'fumadocs-ui/components/callout'
## Required
| Variable | Description |
|----------|-------------|
| `DATABASE_URL` | PostgreSQL connection string |
| `BETTER_AUTH_SECRET` | Auth secret (32 hex chars): `openssl rand -hex 32` |
| `BETTER_AUTH_URL` | Your app URL |
| `ENCRYPTION_KEY` | Encryption key (32 hex chars): `openssl rand -hex 32` |
| `INTERNAL_API_SECRET` | Internal API secret (32 hex chars): `openssl rand -hex 32` |
| `NEXT_PUBLIC_APP_URL` | Public app URL |
| `NEXT_PUBLIC_SOCKET_URL` | WebSocket URL (default: `http://localhost:3002`) |
## AI Providers
| Variable | Provider |
|----------|----------|
| `OPENAI_API_KEY` | OpenAI |
| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
| `GEMINI_API_KEY_1` | Google Gemini |
| `MISTRAL_API_KEY` | Mistral |
| `OLLAMA_URL` | Ollama (default: `http://localhost:11434`) |
<Callout type="info">
For load balancing, add multiple keys with `_1`, `_2`, `_3` suffixes (e.g., `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`). Works with OpenAI, Anthropic, and Gemini.
</Callout>
<Callout type="info">
In Docker, use `OLLAMA_URL=http://host.docker.internal:11434` for host-machine Ollama.
</Callout>
### Azure OpenAI
| Variable | Description |
|----------|-------------|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key |
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint URL |
| `AZURE_OPENAI_API_VERSION` | API version (e.g., `2024-02-15-preview`) |
### vLLM (Self-Hosted)
| Variable | Description |
|----------|-------------|
| `VLLM_BASE_URL` | vLLM server URL (e.g., `http://localhost:8000/v1`) |
| `VLLM_API_KEY` | Optional bearer token for vLLM |
## OAuth Providers
| Variable | Description |
|----------|-------------|
| `GOOGLE_CLIENT_ID` | Google OAuth client ID |
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |
| `GITHUB_CLIENT_ID` | GitHub OAuth client ID |
| `GITHUB_CLIENT_SECRET` | GitHub OAuth client secret |
## Optional
| Variable | Description |
|----------|-------------|
| `API_ENCRYPTION_KEY` | Encrypts stored API keys (32 hex chars): `openssl rand -hex 32` |
| `COPILOT_API_KEY` | API key for copilot features |
| `ADMIN_API_KEY` | Admin API key for GitOps operations |
| `RESEND_API_KEY` | Email service for notifications |
| `ALLOWED_LOGIN_DOMAINS` | Restrict signups to domains (comma-separated) |
| `ALLOWED_LOGIN_EMAILS` | Restrict signups to specific emails (comma-separated) |
| `DISABLE_REGISTRATION` | Set to `true` to disable new user signups |
## Example .env
```bash
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=<openssl rand -hex 32>
BETTER_AUTH_URL=https://sim.yourdomain.com
ENCRYPTION_KEY=<openssl rand -hex 32>
INTERNAL_API_SECRET=<openssl rand -hex 32>
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
OPENAI_API_KEY=sk-...
```
See `apps/sim/.env.example` for all options.

View File

@@ -0,0 +1,50 @@
---
title: Self-Hosting
description: Deploy Sim Studio on your own infrastructure
---
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Callout } from 'fumadocs-ui/components/callout'
Deploy Sim Studio on your own infrastructure with Docker or Kubernetes.
## Requirements
| Resource | Minimum | Recommended |
|----------|---------|-------------|
| CPU | 2 cores | 4+ cores |
| RAM | 12 GB | 16+ GB |
| Storage | 20 GB SSD | 50+ GB SSD |
| Docker | 20.10+ | Latest |
## Quick Start
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
Open [http://localhost:3000](http://localhost:3000)
## Deployment Options
<Cards>
<Card title="Docker" href="/self-hosting/docker">
Deploy with Docker Compose on any server
</Card>
<Card title="Kubernetes" href="/self-hosting/kubernetes">
Deploy with Helm on Kubernetes clusters
</Card>
<Card title="Cloud Platforms" href="/self-hosting/platforms">
Railway, DigitalOcean, AWS, Azure, GCP guides
</Card>
</Cards>
## Architecture
| Component | Port | Description |
|-----------|------|-------------|
| simstudio | 3000 | Main application |
| realtime | 3002 | WebSocket server |
| db | 5432 | PostgreSQL with pgvector |
| migrations | - | Database migrations (runs once) |

View File

@@ -0,0 +1,127 @@
---
title: Kubernetes
description: Deploy Sim Studio with Helm
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Prerequisites
- Kubernetes 1.19+
- Helm 3.0+
- PV provisioner support
## Installation
```bash
# Clone repo
git clone https://github.com/simstudioai/sim.git && cd sim
# Generate secrets
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
# Install
helm install sim ./helm/sim \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--namespace simstudio --create-namespace
```
## Cloud-Specific Values
<Tabs items={['AWS EKS', 'Azure AKS', 'GCP GKE']}>
<Tab value="AWS EKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-aws.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="Azure AKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-azure.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="GCP GKE">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-gcp.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
</Tabs>
## Key Configuration
```yaml
# Custom values.yaml
app:
replicaCount: 2
env:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
OPENAI_API_KEY: "sk-..."
postgresql:
persistence:
size: 50Gi
ingress:
enabled: true
className: nginx
tls:
enabled: true
app:
host: sim.yourdomain.com
```
See `helm/sim/values.yaml` for all options.
## External Database
```yaml
postgresql:
enabled: false
externalDatabase:
enabled: true
host: "your-db-host"
port: 5432
username: "postgres"
password: "your-password"
database: "simstudio"
sslMode: "require"
```
## Commands
```bash
# Port forward for local access
kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
# View logs
kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
# Upgrade
helm upgrade sim ./helm/sim --namespace simstudio
# Uninstall
helm uninstall sim --namespace simstudio
```

View File

@@ -0,0 +1,12 @@
{
"title": "Self-Hosting",
"pages": [
"index",
"docker",
"kubernetes",
"platforms",
"environment-variables",
"troubleshooting"
],
"defaultOpen": false
}

View File

@@ -0,0 +1,116 @@
---
title: Cloud Platforms
description: Deploy Sim Studio on cloud platforms
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Railway
One-click deployment with automatic PostgreSQL provisioning.
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.com/new/template/sim-studio)
After deployment, add environment variables in Railway dashboard:
- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (auto-generated)
- `OPENAI_API_KEY` or other AI provider keys
- Custom domain in Settings → Networking
## VPS Deployment
For DigitalOcean, AWS EC2, Azure VMs, or any Linux server:
<Tabs items={['DigitalOcean', 'AWS EC2', 'Azure VM']}>
<Tab value="DigitalOcean">
**Recommended:** 16 GB RAM Droplet, Ubuntu 24.04
```bash
# Create Droplet via console, then SSH in
ssh root@your-droplet-ip
```
</Tab>
<Tab value="AWS EC2">
**Recommended:** t3.xlarge (16 GB RAM), Ubuntu 24.04
```bash
ssh -i your-key.pem ubuntu@your-ec2-ip
```
</Tab>
<Tab value="Azure VM">
**Recommended:** Standard_D4s_v3 (16 GB RAM), Ubuntu 24.04
```bash
ssh azureuser@your-vm-ip
```
</Tab>
</Tabs>
### Install Docker
```bash
# Install Docker (official method)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Logout and reconnect, then verify
docker --version
```
### Deploy Sim Studio
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
# Create .env with secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
# Start
docker compose -f docker-compose.prod.yml up -d
```
### SSL with Caddy
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Configure (replace domain)
echo 'sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}' | sudo tee /etc/caddy/Caddyfile
sudo systemctl restart caddy
```
Point your domain's DNS A record to your server IP.
## Kubernetes (EKS, AKS, GKE)
See the [Kubernetes guide](/self-hosting/kubernetes) for Helm deployment on managed Kubernetes.
## Managed Database (Optional)
For production, use a managed PostgreSQL service:
- **AWS RDS** / **Azure Database** / **Cloud SQL** - Enable pgvector extension
- **Supabase** / **Neon** - pgvector included
Set `DATABASE_URL` in your environment:
```bash
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
```

View File

@@ -0,0 +1,110 @@
---
title: Troubleshooting
description: Common issues and solutions
---
## Database Connection Failed
```bash
# Check database is running
docker compose ps db
# Test connection
docker compose exec db psql -U postgres -c "SELECT 1"
```
Verify `DATABASE_URL` format: `postgresql://user:pass@host:5432/database`
## Ollama Models Not Showing
Inside Docker, `localhost` = the container, not your host machine.
```bash
# For host-machine Ollama, use:
OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
```
## WebSocket/Realtime Not Working
1. Check `NEXT_PUBLIC_SOCKET_URL` matches your domain
2. Verify realtime service is running: `docker compose ps realtime`
3. Ensure reverse proxy passes WebSocket upgrades (see [Docker guide](/self-hosting/docker))
## 502 Bad Gateway
```bash
# Check app is running
docker compose ps simstudio
docker compose logs simstudio
# Common causes: out of memory, database not ready
```
## Migration Errors
```bash
# View migration logs
docker compose logs migrations
# Run manually
docker compose exec simstudio bun run db:migrate
```
## pgvector Not Found
Use the correct PostgreSQL image:
```yaml
image: pgvector/pgvector:pg17 # NOT postgres:17
```
## Certificate Errors (CERT_HAS_EXPIRED)
If you see SSL certificate errors when calling external APIs:
```bash
# Update CA certificates in container
docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
# Or set in environment (not recommended for production)
NODE_TLS_REJECT_UNAUTHORIZED=0
```
## Blank Page After Login
1. Check browser console for errors
2. Verify `NEXT_PUBLIC_APP_URL` matches your actual domain
3. Clear browser cookies and local storage
4. Check that all services are running: `docker compose ps`
## Windows-Specific Issues
**Turbopack errors on Windows:**
```bash
# Use WSL2 for better compatibility
wsl --install
# Or disable Turbopack in package.json
# Change "next dev --turbopack" to "next dev"
```
**Line ending issues:**
```bash
# Configure git to use LF
git config --global core.autocrlf input
```
## View Logs
```bash
# All services
docker compose logs -f
# Specific service
docker compose logs -f simstudio
```
## Getting Help
- [GitHub Issues](https://github.com/simstudioai/sim/issues)
- [Discord](https://discord.gg/Hr4UWYEcTT)

View File

@@ -0,0 +1,186 @@
---
title: Cursor
description: Launch and manage Cursor cloud agents to work on GitHub repositories
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="cursor"
color="#1E1E1E"
/>
{/* MANUAL-CONTENT-START:intro */}
[Cursor](https://www.cursor.so/) is an AI IDE and cloud-based platform that lets you launch and manage powerful AI agents able to work directly on your GitHub repositories. Cursor agents can automate development tasks, enhance your team's productivity, and collaborate with you by making code changes, responding to natural language instructions, and maintaining conversation history about their activities.
With Cursor, you can:
- **Launch cloud agents for codebases**: Instantly create new AI agents that work on your repositories in the cloud
- **Delegate coding tasks using natural language**: Guide agents with written instructions, amendments, and clarifications
- **Monitor progress and outputs**: Retrieve agent status, view detailed results, and inspect current or completed tasks
- **Access full conversation history**: Review all prompts and AI responses for transparency and auditability
- **Control and manage agent lifecycle**: List active agents, terminate agents, and manage API-based agent launches and follow-ups
In Sim, the Cursor integration enables your agents and workflows to interact programmatically with Cursor cloud agents. This means you can use Sim to:
- List all cloud agents and browse their current state (`cursor_list_agents`)
- Retrieve up-to-date status and outputs for any agent (`cursor_get_agent`)
- View the full conversation history for any coding agent (`cursor_get_conversation`)
- Add follow-up instructions or new prompts to a running agent
- Manage and terminate agents as needed
This integration helps you combine the flexible intelligence of Sim agents with the powerful development automation capabilities of Cursor, making it possible to scale AI-driven development across your projects.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Interact with Cursor Cloud Agents API to launch AI agents that can work on your GitHub repositories. Supports launching agents, adding follow-up instructions, checking status, viewing conversations, and managing agent lifecycle.
## Tools
### `cursor_list_agents`
List all cloud agents for the authenticated user with optional pagination.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Cursor API key |
| `limit` | number | No | Number of agents to return \(default: 20, max: 100\) |
| `cursor` | string | No | Pagination cursor from previous response |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Human-readable list of agents |
| `metadata` | object | Agent list metadata |
### `cursor_get_agent`
Retrieve the current status and results of a cloud agent.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Cursor API key |
| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Human-readable agent details |
| `metadata` | object | Agent metadata |
### `cursor_get_conversation`
Retrieve the conversation history of a cloud agent, including all user prompts and assistant responses.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Cursor API key |
| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Human-readable conversation history |
| `metadata` | object | Conversation metadata |
### `cursor_launch_agent`
Start a new cloud agent to work on a GitHub repository with the given instructions.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Cursor API key |
| `repository` | string | Yes | GitHub repository URL \(e.g., https://github.com/your-org/your-repo\) |
| `ref` | string | No | Branch, tag, or commit to work from \(defaults to default branch\) |
| `promptText` | string | Yes | The instruction text for the agent |
| `promptImages` | string | No | JSON array of image objects with base64 data and dimensions |
| `model` | string | No | Model to use \(leave empty for auto-selection\) |
| `branchName` | string | No | Custom branch name for the agent to use |
| `autoCreatePr` | boolean | No | Automatically create a PR when the agent finishes |
| `openAsCursorGithubApp` | boolean | No | Open the PR as the Cursor GitHub App |
| `skipReviewerRequest` | boolean | No | Skip requesting reviewers on the PR |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Success message with agent details |
| `metadata` | object | Launch result metadata |
### `cursor_add_followup`
Add a follow-up instruction to an existing cloud agent.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Cursor API key |
| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
| `followupPromptText` | string | Yes | The follow-up instruction text for the agent |
| `promptImages` | string | No | JSON array of image objects with base64 data and dimensions \(max 5\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Success message |
| `metadata` | object | Result metadata |
### `cursor_stop_agent`
Stop a running cloud agent. This pauses the agent without deleting it.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Cursor API key |
| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Success message |
| `metadata` | object | Result metadata |
### `cursor_delete_agent`
Permanently delete a cloud agent. This action cannot be undone.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Cursor API key |
| `agentId` | string | Yes | Unique identifier for the cloud agent \(e.g., bc_abc123\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Success message |
| `metadata` | object | Result metadata |
## Notes
- Category: `tools`
- Type: `cursor`

View File

@@ -0,0 +1,221 @@
---
title: Google Groups
description: Manage Google Workspace Groups and their members
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_groups"
color="#E8F0FE"
/>
## Usage Instructions
Connect to Google Workspace to create, update, and manage groups and their members using the Admin SDK Directory API.
## Tools
### `google_groups_list_groups`
List all groups in a Google Workspace domain
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `customer` | string | No | Customer ID or "my_customer" for the authenticated user\'s domain |
| `domain` | string | No | Domain name to filter groups by |
| `maxResults` | number | No | Maximum number of results to return \(1-200\) |
| `pageToken` | string | No | Token for pagination |
| `query` | string | No | Search query to filter groups \(e.g., "email:admin*"\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_get_group`
Get details of a specific Google Group by email or group ID
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_create_group`
Create a new Google Group in the domain
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `email` | string | Yes | Email address for the new group \(e.g., team@yourdomain.com\) |
| `name` | string | Yes | Display name for the group |
| `description` | string | No | Description of the group |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_update_group`
Update an existing Google Group
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID |
| `name` | string | No | New display name for the group |
| `description` | string | No | New description for the group |
| `email` | string | No | New email address for the group |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_delete_group`
Delete a Google Group
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID to delete |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_list_members`
List all members of a Google Group
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID |
| `maxResults` | number | No | Maximum number of results to return \(1-200\) |
| `pageToken` | string | No | Token for pagination |
| `roles` | string | No | Filter by roles \(comma-separated: OWNER, MANAGER, MEMBER\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_get_member`
Get details of a specific member in a Google Group
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID |
| `memberKey` | string | Yes | Member email address or unique member ID |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_add_member`
Add a new member to a Google Group
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID |
| `email` | string | Yes | Email address of the member to add |
| `role` | string | No | Role for the member \(MEMBER, MANAGER, or OWNER\). Defaults to MEMBER. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_remove_member`
Remove a member from a Google Group
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID |
| `memberKey` | string | Yes | Email address or unique ID of the member to remove |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_update_member`
Update a member
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID |
| `memberKey` | string | Yes | Member email address or unique member ID |
| `role` | string | Yes | New role for the member \(MEMBER, MANAGER, or OWNER\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
### `google_groups_has_member`
Check if a user is a member of a Google Group
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Yes | Group email address or unique group ID |
| `memberKey` | string | Yes | Member email address or unique member ID to check |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API response data |
## Notes
- Category: `tools`
- Type: `google_groups`

View File

@@ -11,6 +11,7 @@
"calendly",
"clay",
"confluence",
"cursor",
"datadog",
"discord",
"dropbox",
@@ -27,6 +28,7 @@
"google_docs",
"google_drive",
"google_forms",
"google_groups",
"google_search",
"google_sheets",
"google_slides",

View File

@@ -138,283 +138,684 @@ Delete an account from Salesforce CRM
### `salesforce_get_contacts`
Get contact(s) from Salesforce - single contact if ID provided, or list if not
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `contactId` | string | No | Contact ID \(if provided, returns single contact\) |
| `limit` | string | No | Number of results \(default: 100, max: 2000\). Only for list query. |
| `fields` | string | No | Comma-separated fields \(e.g., "Id,FirstName,LastName,Email,Phone"\) |
| `orderBy` | string | No | Order by field \(e.g., "LastName ASC"\). Only for list query. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `output` | object | Contact\(s\) data |
### `salesforce_create_contact`
Create a new contact in Salesforce CRM
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `lastName` | string | Yes | Last name \(required\) |
| `firstName` | string | No | First name |
| `email` | string | No | Email address |
| `phone` | string | No | Phone number |
| `accountId` | string | No | Account ID to associate contact with |
| `title` | string | No | No description |
| `department` | string | No | Department |
| `mailingStreet` | string | No | Mailing street |
| `mailingCity` | string | No | Mailing city |
| `mailingState` | string | No | Mailing state |
| `mailingPostalCode` | string | No | Mailing postal code |
| `mailingCountry` | string | No | Mailing country |
| `description` | string | No | Contact description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `output` | object | Created contact data |
### `salesforce_update_contact`
Update an existing contact in Salesforce CRM
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `contactId` | string | Yes | Contact ID to update \(required\) |
| `lastName` | string | No | Last name |
| `firstName` | string | No | First name |
| `email` | string | No | Email address |
| `phone` | string | No | Phone number |
| `accountId` | string | No | Account ID to associate with |
| `title` | string | No | No description |
| `department` | string | No | Department |
| `mailingStreet` | string | No | Mailing street |
| `mailingCity` | string | No | Mailing city |
| `mailingState` | string | No | Mailing state |
| `mailingPostalCode` | string | No | Mailing postal code |
| `mailingCountry` | string | No | Mailing country |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `output` | object | Updated contact data |
### `salesforce_delete_contact`
Delete a contact from Salesforce CRM
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `contactId` | string | Yes | Contact ID to delete \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `output` | object | Deleted contact data |
### `salesforce_get_leads`
Get lead(s) from Salesforce
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `leadId` | string | No | Lead ID \(optional\) |
| `limit` | string | No | Max results \(default: 100\) |
| `fields` | string | No | Comma-separated fields |
| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success status |
| `output` | object | Lead data |
### `salesforce_create_lead`
Create a new lead
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `lastName` | string | Yes | Last name \(required\) |
| `company` | string | Yes | Company \(required\) |
| `firstName` | string | No | First name |
| `email` | string | No | No description |
| `phone` | string | No | No description |
| `status` | string | No | Lead status |
| `leadSource` | string | No | Lead source |
| `title` | string | No | No description |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Created lead |
### `salesforce_update_lead`
Update an existing lead
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `leadId` | string | Yes | Lead ID \(required\) |
| `lastName` | string | No | Last name |
| `company` | string | No | No description |
| `firstName` | string | No | First name |
| `email` | string | No | No description |
| `phone` | string | No | No description |
| `status` | string | No | Lead status |
| `leadSource` | string | No | Lead source |
| `title` | string | No | No description |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Updated lead |
### `salesforce_delete_lead`
Delete a lead
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `leadId` | string | Yes | Lead ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Deleted lead |
### `salesforce_get_opportunities`
Get opportunity(ies) from Salesforce
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `opportunityId` | string | No | Opportunity ID \(optional\) |
| `limit` | string | No | Max results \(default: 100\) |
| `fields` | string | No | Comma-separated fields |
| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Opportunity data |
### `salesforce_create_opportunity`
Create a new opportunity
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `name` | string | Yes | Opportunity name \(required\) |
| `stageName` | string | Yes | Stage name \(required\) |
| `closeDate` | string | Yes | Close date YYYY-MM-DD \(required\) |
| `accountId` | string | No | Account ID |
| `amount` | string | No | Amount \(number\) |
| `probability` | string | No | Probability \(0-100\) |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Created opportunity |
### `salesforce_update_opportunity`
Update an existing opportunity
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `opportunityId` | string | Yes | Opportunity ID \(required\) |
| `name` | string | No | Opportunity name |
| `stageName` | string | No | Stage name |
| `closeDate` | string | No | Close date YYYY-MM-DD |
| `accountId` | string | No | Account ID |
| `amount` | string | No | No description |
| `probability` | string | No | Probability \(0-100\) |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Updated opportunity |
### `salesforce_delete_opportunity`
Delete an opportunity
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `opportunityId` | string | Yes | Opportunity ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Deleted opportunity |
### `salesforce_get_cases`
Get case(s) from Salesforce
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `caseId` | string | No | Case ID \(optional\) |
| `limit` | string | No | Max results \(default: 100\) |
| `fields` | string | No | Comma-separated fields |
| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Case data |
### `salesforce_create_case`
Create a new case
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `subject` | string | Yes | Case subject \(required\) |
| `status` | string | No | Status \(e.g., New, Working, Escalated\) |
| `priority` | string | No | Priority \(e.g., Low, Medium, High\) |
| `origin` | string | No | Origin \(e.g., Phone, Email, Web\) |
| `contactId` | string | No | Contact ID |
| `accountId` | string | No | Account ID |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Created case |
### `salesforce_update_case`
Update an existing case
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `caseId` | string | Yes | Case ID \(required\) |
| `subject` | string | No | Case subject |
| `status` | string | No | Status |
| `priority` | string | No | Priority |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Updated case |
### `salesforce_delete_case`
Delete a case
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `caseId` | string | Yes | Case ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Deleted case |
### `salesforce_get_tasks`
Get task(s) from Salesforce
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `taskId` | string | No | Task ID \(optional\) |
| `limit` | string | No | Max results \(default: 100\) |
| `fields` | string | No | Comma-separated fields |
| `orderBy` | string | No | Order by field |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Task data |
### `salesforce_create_task`
Create a new task
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `subject` | string | Yes | Task subject \(required\) |
| `status` | string | No | Status \(e.g., Not Started, In Progress, Completed\) |
| `priority` | string | No | Priority \(e.g., Low, Normal, High\) |
| `activityDate` | string | No | Due date YYYY-MM-DD |
| `whoId` | string | No | Related Contact/Lead ID |
| `whatId` | string | No | Related Account/Opportunity ID |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Created task |
### `salesforce_update_task`
Update an existing task
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `taskId` | string | Yes | Task ID \(required\) |
| `subject` | string | No | Task subject |
| `status` | string | No | Status |
| `priority` | string | No | Priority |
| `activityDate` | string | No | Due date YYYY-MM-DD |
| `description` | string | No | Description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Updated task |
### `salesforce_delete_task`
Delete a task
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `taskId` | string | Yes | Task ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Operation success status |
| `output` | json | Operation result data |
| `success` | boolean | Success |
| `output` | object | Deleted task |
### `salesforce_list_reports`
Get a list of reports accessible by the current user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `folderName` | string | No | Filter by folder name |
| `searchTerm` | string | No | Search term to filter reports by name |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Reports data |
### `salesforce_get_report`
Get metadata and describe information for a specific report
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `reportId` | string | Yes | Report ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Report metadata |
### `salesforce_run_report`
Execute a report and retrieve the results
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `reportId` | string | Yes | Report ID \(required\) |
| `includeDetails` | string | No | Include detail rows \(true/false, default: true\) |
| `filters` | string | No | JSON string of report filters to apply |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Report results |
### `salesforce_list_report_types`
Get a list of available report types
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Report types data |
### `salesforce_list_dashboards`
Get a list of dashboards accessible by the current user
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `folderName` | string | No | Filter by folder name |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Dashboards data |
### `salesforce_get_dashboard`
Get details and results for a specific dashboard
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `dashboardId` | string | Yes | Dashboard ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Dashboard data |
### `salesforce_refresh_dashboard`
Refresh a dashboard to get the latest data
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `dashboardId` | string | Yes | Dashboard ID \(required\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Refreshed dashboard data |
### `salesforce_query`
Execute a custom SOQL query to retrieve data from Salesforce
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `query` | string | Yes | SOQL query to execute \(e.g., SELECT Id, Name FROM Account LIMIT 10\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Query results |
### `salesforce_query_more`
Retrieve additional query results using the nextRecordsUrl from a previous query
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `nextRecordsUrl` | string | Yes | The nextRecordsUrl from a previous query response |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Query results |
### `salesforce_describe_object`
Get metadata and field information for a Salesforce object
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
| `objectName` | string | Yes | API name of the object \(e.g., Account, Contact, Lead, Custom_Object__c\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Object metadata |
### `salesforce_list_objects`
Get a list of all available Salesforce objects
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | No description |
| `instanceUrl` | string | No | No description |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Success status |
| `output` | object | Objects list |

View File

@@ -142,8 +142,8 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## Mejores prácticas
- **Ordenar las condiciones correctamente**: Coloca las condiciones más específicas antes que las generales para asegurar que la lógica específica tenga prioridad sobre las alternativas
- **Incluir una condición predeterminada**: Añade una condición general (`true`) como última condición para manejar casos no coincidentes y evitar que la ejecución del flujo de trabajo se detenga
- **Mantener las expresiones simples**: Usa expresiones booleanas claras y directas para mejorar la legibilidad y facilitar la depuración
- **Documentar tus condiciones**: Añade descripciones para explicar el propósito de cada condición para una mejor colaboración en equipo y mantenimiento
- **Probar casos límite**: Verifica que las condiciones manejen correctamente los valores límite probando con valores en los extremos de tus rangos de condición
- **Ordena las condiciones correctamente**: Coloca las condiciones más específicas antes que las generales para asegurar que la lógica específica tenga prioridad sobre las alternativas
- **Usa la rama else cuando sea necesario**: Si ninguna condición coincide y la rama else no está conectada, la rama del flujo de trabajo terminará correctamente. Conecta la rama else si necesitas una ruta alternativa para casos no coincidentes
- **Mantén las expresiones simples**: Usa expresiones booleanas claras y directas para mejorar la legibilidad y facilitar la depuración
- **Documenta tus condiciones**: Añade descripciones para explicar el propósito de cada condición para una mejor colaboración en equipo y mantenimiento
- **Prueba casos límite**: Verifica que las condiciones manejen correctamente los valores límite probando con valores en los extremos de los rangos de tus condiciones

View File

@@ -72,7 +72,7 @@ Para integraciones personalizadas, aprovecha nuestro [soporte MCP (Protocolo de
<Video src="introduction/integrations-sidebar.mp4" width={700} height={450} />
</div>
## Copiloto potenciado por IA
## Copilot
**Haz preguntas y recibe orientación**
Copiloto responde preguntas sobre Sim, explica tus flujos de trabajo y proporciona sugerencias para mejorarlos. Usa el símbolo `@` para hacer referencia a flujos de trabajo, bloques, documentación, conocimiento y registros para obtener asistencia contextual.

View File

@@ -1,5 +1,7 @@
---
title: Base de conocimientos
title: Descripción general
description: Sube, procesa y busca a través de tus documentos con búsqueda
vectorial inteligente y fragmentación
---
import { Video } from '@/components/ui/video'

View File

@@ -0,0 +1,155 @@
---
title: Docker
description: Despliega Sim Studio con Docker Compose
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Inicio rápido
```bash
# Clone and start
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
Abre [http://localhost:3000](http://localhost:3000)
## Configuración de producción
### 1. Configurar entorno
```bash
# Generate secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
```
### 2. Iniciar servicios
```bash
docker compose -f docker-compose.prod.yml up -d
```
### 3. Configurar SSL
<Tabs items={['Caddy (Recomendado)', 'Nginx + Certbot']}>
<Tab value="Caddy (Recomendado)">
Caddy gestiona automáticamente los certificados SSL.
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
```
Crea `/etc/caddy/Caddyfile`:
```
sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}
```
```bash
sudo systemctl restart caddy
```
</Tab>
<Tab value="Nginx + Certbot">
```bash
# Install
sudo apt install nginx certbot python3-certbot-nginx -y
# Create /etc/nginx/sites-available/sim
server {
listen 80;
server_name sim.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Enable and get certificate
sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
sudo certbot --nginx -d sim.yourdomain.com
```
</Tab>
</Tabs>
## Ollama
```bash
# With GPU
docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
# CPU only
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
```
Descarga modelos adicionales:
```bash
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
```
### Ollama externo
Si Ollama se ejecuta en tu máquina host (no en Docker):
```bash
# macOS/Windows
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
# Linux - use your host IP
OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
```
<Callout type="warning">
Dentro de Docker, `localhost` se refiere al contenedor, no a tu host. Usa `host.docker.internal` o la IP de tu host.
</Callout>
## Comandos
```bash
# View logs
docker compose -f docker-compose.prod.yml logs -f simstudio
# Stop
docker compose -f docker-compose.prod.yml down
# Update
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
# Backup database
docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
```

View File

@@ -0,0 +1,87 @@
---
title: Variables de entorno
description: Referencia de configuración para Sim Studio
---
import { Callout } from 'fumadocs-ui/components/callout'
## Requeridas
| Variable | Descripción |
|----------|-------------|
| `DATABASE_URL` | Cadena de conexión PostgreSQL |
| `BETTER_AUTH_SECRET` | Secreto de autenticación (32 caracteres hex): `openssl rand -hex 32` |
| `BETTER_AUTH_URL` | URL de tu aplicación |
| `ENCRYPTION_KEY` | Clave de cifrado (32 caracteres hex): `openssl rand -hex 32` |
| `INTERNAL_API_SECRET` | Secreto de API interna (32 caracteres hex): `openssl rand -hex 32` |
| `NEXT_PUBLIC_APP_URL` | URL pública de la aplicación |
| `NEXT_PUBLIC_SOCKET_URL` | URL de WebSocket (predeterminado: `http://localhost:3002`) |
## Proveedores de IA
| Variable | Proveedor |
|----------|----------|
| `OPENAI_API_KEY` | OpenAI |
| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
| `GEMINI_API_KEY_1` | Google Gemini |
| `MISTRAL_API_KEY` | Mistral |
| `OLLAMA_URL` | Ollama (predeterminado: `http://localhost:11434`) |
<Callout type="info">
Para balanceo de carga, añade múltiples claves con sufijos `_1`, `_2`, `_3` (p. ej., `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`). Funciona con OpenAI, Anthropic y Gemini.
</Callout>
<Callout type="info">
En Docker, usa `OLLAMA_URL=http://host.docker.internal:11434` para Ollama en la máquina host.
</Callout>
### Azure OpenAI
| Variable | Descripción |
|----------|-------------|
| `AZURE_OPENAI_API_KEY` | Clave de API de Azure OpenAI |
| `AZURE_OPENAI_ENDPOINT` | URL del endpoint de Azure OpenAI |
| `AZURE_OPENAI_API_VERSION` | Versión de API (p. ej., `2024-02-15-preview`) |
### vLLM (autoalojado)
| Variable | Descripción |
|----------|-------------|
| `VLLM_BASE_URL` | URL del servidor vLLM (p. ej., `http://localhost:8000/v1`) |
| `VLLM_API_KEY` | Token bearer opcional para vLLM |
## Proveedores OAuth
| Variable | Descripción |
|----------|-------------|
| `GOOGLE_CLIENT_ID` | ID de cliente OAuth de Google |
| `GOOGLE_CLIENT_SECRET` | Secreto de cliente OAuth de Google |
| `GITHUB_CLIENT_ID` | ID de cliente OAuth de GitHub |
| `GITHUB_CLIENT_SECRET` | Secreto de cliente OAuth de GitHub |
## Opcional
| Variable | Descripción |
|----------|-------------|
| `API_ENCRYPTION_KEY` | Encripta las claves API almacenadas (32 caracteres hexadecimales): `openssl rand -hex 32` |
| `COPILOT_API_KEY` | Clave API para funciones de copilot |
| `ADMIN_API_KEY` | Clave API de administrador para operaciones GitOps |
| `RESEND_API_KEY` | Servicio de correo electrónico para notificaciones |
| `ALLOWED_LOGIN_DOMAINS` | Restringir registros a dominios (separados por comas) |
| `ALLOWED_LOGIN_EMAILS` | Restringir registros a correos electrónicos específicos (separados por comas) |
| `DISABLE_REGISTRATION` | Establecer como `true` para deshabilitar nuevos registros de usuarios |
## Ejemplo de archivo .env
```bash
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=<openssl rand -hex 32>
BETTER_AUTH_URL=https://sim.yourdomain.com
ENCRYPTION_KEY=<openssl rand -hex 32>
INTERNAL_API_SECRET=<openssl rand -hex 32>
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
OPENAI_API_KEY=sk-...
```
Consulta `apps/sim/.env.example` para todas las opciones.

View File

@@ -0,0 +1,50 @@
---
title: Autoalojamiento
description: Despliega Sim Studio en tu propia infraestructura
---
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Callout } from 'fumadocs-ui/components/callout'
Despliega Sim Studio en tu propia infraestructura con Docker o Kubernetes.
## Requisitos
| Recurso | Mínimo | Recomendado |
|----------|---------|-------------|
| CPU | 2 núcleos | 4+ núcleos |
| RAM | 12 GB | 16+ GB |
| Almacenamiento | 20 GB SSD | 50+ GB SSD |
| Docker | 20.10+ | Última versión |
## Inicio rápido
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
Abre [http://localhost:3000](http://localhost:3000)
## Opciones de despliegue
<Cards>
<Card title="Docker" href="/self-hosting/docker">
Despliega con Docker Compose en cualquier servidor
</Card>
<Card title="Kubernetes" href="/self-hosting/kubernetes">
Despliega con Helm en clústeres de Kubernetes
</Card>
<Card title="Plataformas en la nube" href="/self-hosting/platforms">
Guías para Railway, DigitalOcean, AWS, Azure, GCP
</Card>
</Cards>
## Arquitectura
| Componente | Puerto | Descripción |
|-----------|------|-------------|
| simstudio | 3000 | Aplicación principal |
| realtime | 3002 | Servidor WebSocket |
| db | 5432 | PostgreSQL con pgvector |
| migrations | - | Migraciones de base de datos (se ejecuta una vez) |

View File

@@ -0,0 +1,133 @@
---
title: Kubernetes
description: Desplegar Sim Studio con Helm
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Requisitos previos
- Kubernetes 1.19+
- Helm 3.0+
- Soporte de aprovisionador PV
## Instalación
```bash
# Clone repo
git clone https://github.com/simstudioai/sim.git && cd sim
# Generate secrets
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
# Install
helm install sim ./helm/sim \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--namespace simstudio --create-namespace
```
## Valores específicos para la nube
<Tabs items={['AWS EKS', 'Azure AKS', 'GCP GKE']}>
<Tab value="AWS EKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-aws.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="Azure AKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-azure.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="GCP GKE">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-gcp.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
</Tabs>
## Configuración clave
```yaml
# Custom values.yaml
app:
replicaCount: 2
env:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
OPENAI_API_KEY: "sk-..."
postgresql:
persistence:
size: 50Gi
ingress:
enabled: true
className: nginx
tls:
enabled: true
app:
host: sim.yourdomain.com
```
Consulta `helm/sim/values.yaml` para todas las opciones.
## Base de datos externa
```yaml
postgresql:
enabled: false
externalDatabase:
enabled: true
host: "your-db-host"
port: 5432
username: "postgres"
password: "your-password"
database: "simstudio"
sslMode: "require"
```
## Comandos
```bash
# Port forward for local access
kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
# View logs
kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
# Upgrade
helm upgrade sim ./helm/sim --namespace simstudio
# Uninstall
helm uninstall sim --namespace simstudio
```

View File

@@ -0,0 +1,124 @@
---
title: Plataformas en la nube
description: Despliega Sim Studio en plataformas en la nube
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Railway
Despliegue con un solo clic con aprovisionamiento automático de PostgreSQL.
[
![Desplegar en Railway](https://railway.app/button.svg)
](https://railway.com/new/template/sim-studio)
Después del despliegue, añade variables de entorno en el panel de Railway:
- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (generadas automáticamente)
- `OPENAI_API_KEY` u otras claves de proveedores de IA
- Dominio personalizado en Configuración → Redes
## Despliegue en VPS
Para DigitalOcean, AWS EC2, Azure VMs o cualquier servidor Linux:
<Tabs items={['DigitalOcean', 'AWS EC2', 'Azure VM']}>
<Tab value="DigitalOcean">
**Recomendado:** Droplet de 16 GB RAM, Ubuntu 24.04
```bash
# Create Droplet via console, then SSH in
ssh root@your-droplet-ip
```
</Tab>
<Tab value="AWS EC2">
**Recomendado:** t3.xlarge (16 GB RAM), Ubuntu 24.04
```bash
ssh -i your-key.pem ubuntu@your-ec2-ip
```
</Tab>
<Tab value="Azure VM">
**Recomendado:** Standard_D4s_v3 (16 GB RAM), Ubuntu 24.04
```bash
ssh azureuser@your-vm-ip
```
</Tab>
</Tabs>
### Instalar Docker
```bash
# Install Docker (official method)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Logout and reconnect, then verify
docker --version
```
### Desplegar Sim Studio
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
# Create .env with secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
# Start
docker compose -f docker-compose.prod.yml up -d
```
### SSL con Caddy
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Configure (replace domain)
echo 'sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}' | sudo tee /etc/caddy/Caddyfile
sudo systemctl restart caddy
```
Apunta el registro DNS A de tu dominio a la IP de tu servidor.
## Kubernetes (EKS, AKS, GKE)
Consulta la [guía de Kubernetes](/self-hosting/kubernetes) para la implementación con Helm en Kubernetes gestionado.
## Base de datos gestionada (Opcional)
Para producción, utiliza un servicio de PostgreSQL gestionado:
- **AWS RDS** / **Azure Database** / **Cloud SQL** - Habilita la extensión pgvector
- **Supabase** / **Neon** - pgvector incluido
Establece `DATABASE_URL` en tu entorno:
```bash
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
```

View File

@@ -0,0 +1,113 @@
---
title: Solución de problemas
description: Problemas comunes y soluciones
---
## Falló la conexión a la base de datos
```bash
# Check database is running
docker compose ps db
# Test connection
docker compose exec db psql -U postgres -c "SELECT 1"
```
Verifica el formato de `DATABASE_URL`: `postgresql://user:pass@host:5432/database`
## Los modelos de Ollama no se muestran
Dentro de Docker, `localhost` = el contenedor, no tu máquina host.
```bash
# For host-machine Ollama, use:
OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
```
## WebSocket/Tiempo real no funciona
1. Comprueba que `NEXT_PUBLIC_SOCKET_URL` coincida con tu dominio
2. Verifica que el servicio en tiempo real esté funcionando: `docker compose ps realtime`
3. Asegúrate de que el proxy inverso pase las actualizaciones de WebSocket (consulta la [guía de Docker](/self-hosting/docker))
## Error 502 Bad Gateway
```bash
# Check app is running
docker compose ps simstudio
docker compose logs simstudio
# Common causes: out of memory, database not ready
```
## Errores de migración
```bash
# View migration logs
docker compose logs migrations
# Run manually
docker compose exec simstudio bun run db:migrate
```
## pgvector no encontrado
Usa la imagen correcta de PostgreSQL:
```yaml
image: pgvector/pgvector:pg17 # NOT postgres:17
```
## Errores de certificado (CERT_HAS_EXPIRED)
Si ves errores de certificado SSL al llamar a APIs externas:
```bash
# Update CA certificates in container
docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
# Or set in environment (not recommended for production)
NODE_TLS_REJECT_UNAUTHORIZED=0
```
## Página en blanco después del inicio de sesión
1. Revisa la consola del navegador para ver errores
2. Verifica que `NEXT_PUBLIC_APP_URL` coincida con tu dominio actual
3. Borra las cookies del navegador y el almacenamiento local
4. Comprueba que todos los servicios estén funcionando: `docker compose ps`
## Problemas específicos de Windows
**Errores de Turbopack en Windows:**
```bash
# Use WSL2 for better compatibility
wsl --install
# Or disable Turbopack in package.json
# Change "next dev --turbopack" to "next dev"
```
**Problemas de fin de línea:**
```bash
# Configure git to use LF
git config --global core.autocrlf input
```
## Ver registros
```bash
# All services
docker compose logs -f
# Specific service
docker compose logs -f simstudio
```
## Obtener ayuda
- [GitHub Issues](https://github.com/simstudioai/sim/issues)
- [Discord](https://discord.gg/Hr4UWYEcTT)

View File

@@ -0,0 +1,182 @@
---
title: Cursor
description: Lanza y gestiona agentes en la nube de Cursor para trabajar en
repositorios de GitHub
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="cursor"
color="#1E1E1E"
/>
{/* MANUAL-CONTENT-START:intro */}
[Cursor](https://www.cursor.so/) es un IDE con IA y una plataforma basada en la nube que te permite lanzar y gestionar potentes agentes de IA capaces de trabajar directamente en tus repositorios de GitHub. Los agentes de Cursor pueden automatizar tareas de desarrollo, mejorar la productividad de tu equipo y colaborar contigo realizando cambios en el código, respondiendo a instrucciones en lenguaje natural y manteniendo un historial de conversaciones sobre sus actividades.
Con Cursor, puedes:
- **Lanzar agentes en la nube para bases de código**: Crear instantáneamente nuevos agentes de IA que trabajen en tus repositorios en la nube
- **Delegar tareas de programación usando lenguaje natural**: Guiar a los agentes con instrucciones escritas, modificaciones y aclaraciones
- **Monitorizar el progreso y los resultados**: Obtener el estado del agente, ver resultados detallados e inspeccionar tareas actuales o completadas
- **Acceder al historial completo de conversaciones**: Revisar todos los prompts y respuestas de IA para mayor transparencia y capacidad de auditoría
- **Controlar y gestionar el ciclo de vida del agente**: Listar agentes activos, terminar agentes y gestionar lanzamientos de agentes basados en API y seguimientos
En Sim, la integración con Cursor permite que tus agentes y flujos de trabajo interactúen programáticamente con los agentes en la nube de Cursor. Esto significa que puedes usar Sim para:
- Listar todos los agentes en la nube y explorar su estado actual (`cursor_list_agents`)
- Recuperar el estado actualizado y los resultados de cualquier agente (`cursor_get_agent`)
- Ver el historial completo de conversaciones de cualquier agente de programación (`cursor_get_conversation`)
- Añadir instrucciones de seguimiento o nuevos prompts a un agente en ejecución
- Gestionar y terminar agentes según sea necesario
Esta integración te ayuda a combinar la inteligencia flexible de los agentes de Sim con las potentes capacidades de automatización de desarrollo de Cursor, haciendo posible escalar el desarrollo impulsado por IA en todos tus proyectos.
{/* MANUAL-CONTENT-END */}
## Instrucciones de uso
Interactúa con la API de Agentes en la Nube de Cursor para lanzar agentes de IA que pueden trabajar en tus repositorios de GitHub. Permite lanzar agentes, añadir instrucciones de seguimiento, verificar el estado, ver conversaciones y gestionar el ciclo de vida de los agentes.
## Herramientas
### `cursor_list_agents`
Lista todos los agentes en la nube para el usuario autenticado con paginación opcional.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Sí | Clave API de Cursor |
| `limit` | number | No | Número de agentes a devolver \(predeterminado: 20, máximo: 100\) |
| `cursor` | string | No | Cursor de paginación de la respuesta anterior |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Lista legible de agentes |
| `metadata` | object | Metadatos de la lista de agentes |
### `cursor_get_agent`
Recupera el estado actual y los resultados de un agente en la nube.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Sí | Clave API de Cursor |
| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Detalles del agente en formato legible |
| `metadata` | object | Metadatos del agente |
### `cursor_get_conversation`
Recupera el historial de conversación de un agente en la nube, incluyendo todas las instrucciones del usuario y las respuestas del asistente.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Sí | Clave API de Cursor |
| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Historial de conversación en formato legible |
| `metadata` | object | Metadatos de la conversación |
### `cursor_launch_agent`
Inicia un nuevo agente en la nube para trabajar en un repositorio de GitHub con las instrucciones proporcionadas.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Sí | Clave API de Cursor |
| `repository` | string | Sí | URL del repositorio de GitHub \(p. ej., https://github.com/your-org/your-repo\) |
| `ref` | string | No | Rama, etiqueta o commit desde donde trabajar \(por defecto usa la rama principal\) |
| `promptText` | string | Sí | El texto de instrucciones para el agente |
| `promptImages` | string | No | Array JSON de objetos de imagen con datos en base64 y dimensiones |
| `model` | string | No | Modelo a utilizar \(dejar vacío para selección automática\) |
| `branchName` | string | No | Nombre de rama personalizado para que el agente utilice |
| `autoCreatePr` | boolean | No | Crear automáticamente un PR cuando el agente termine |
| `openAsCursorGithubApp` | boolean | No | Abrir el PR como la aplicación de GitHub de Cursor |
| `skipReviewerRequest` | boolean | No | Omitir la solicitud de revisores en el PR |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Mensaje de éxito con detalles del agente |
| `metadata` | object | Metadatos del resultado de lanzamiento |
### `cursor_add_followup`
Añade una instrucción de seguimiento a un agente en la nube existente.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Sí | Clave API de Cursor |
| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
| `followupPromptText` | string | Sí | El texto de instrucción de seguimiento para el agente |
| `promptImages` | string | No | Array JSON de objetos de imagen con datos en base64 y dimensiones \(máximo 5\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Mensaje de éxito |
| `metadata` | object | Metadatos del resultado |
### `cursor_stop_agent`
Detener un agente en la nube en ejecución. Esto pausa el agente sin eliminarlo.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Sí | Clave API de Cursor |
| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Mensaje de éxito |
| `metadata` | object | Metadatos del resultado |
### `cursor_delete_agent`
Eliminar permanentemente un agente en la nube. Esta acción no se puede deshacer.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Sí | Clave API de Cursor |
| `agentId` | string | Sí | Identificador único para el agente en la nube \(p. ej., bc_abc123\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `content` | string | Mensaje de éxito |
| `metadata` | object | Metadatos del resultado |
## Notas
- Categoría: `tools`
- Tipo: `cursor`

View File

@@ -0,0 +1,217 @@
---
title: Google Groups
description: Administra los Grupos de Google Workspace y sus miembros
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_groups"
color="#E8F0FE"
/>
## Instrucciones de uso
Conéctate a Google Workspace para crear, actualizar y administrar grupos y sus miembros utilizando la API de directorio de Admin SDK.
## Herramientas
### `google_groups_list_groups`
Listar todos los grupos en un dominio de Google Workspace
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `customer` | string | No | ID del cliente o "my_customer" para el dominio del usuario autenticado |
| `domain` | string | No | Nombre de dominio para filtrar grupos |
| `maxResults` | number | No | Número máximo de resultados a devolver (1-200) |
| `pageToken` | string | No | Token para paginación |
| `query` | string | No | Consulta de búsqueda para filtrar grupos (p. ej., "email:admin*") |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
### `google_groups_get_group`
Obtener detalles de un Grupo de Google específico por correo electrónico o ID de grupo
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
### `google_groups_create_group`
Crear un nuevo Grupo de Google en el dominio
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `email` | string | Sí | Dirección de correo electrónico para el nuevo grupo (p. ej., equipo@tudominio.com) |
| `name` | string | Sí | Nombre visible para el grupo |
| `description` | string | No | Descripción del grupo |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
### `google_groups_update_group`
Actualizar un grupo de Google existente
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
| `name` | string | No | Nuevo nombre visible para el grupo |
| `description` | string | No | Nueva descripción para el grupo |
| `email` | string | No | Nueva dirección de correo electrónico para el grupo |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
### `google_groups_delete_group`
Eliminar un grupo de Google
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo a eliminar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
### `google_groups_list_members`
Listar todos los miembros de un Grupo de Google
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
| `maxResults` | number | No | Número máximo de resultados a devolver \(1-200\) |
| `pageToken` | string | No | Token para paginación |
| `roles` | string | No | Filtrar por roles \(separados por comas: OWNER, MANAGER, MEMBER\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Grupos de Google |
### `google_groups_get_member`
Obtener detalles de un miembro específico en un Grupo de Google
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
| `memberKey` | string | Sí | Dirección de correo electrónico del miembro o ID único del miembro |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Grupos de Google |
### `google_groups_add_member`
Añadir un nuevo miembro a un Grupo de Google
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
| `email` | string | Sí | Dirección de correo electrónico del miembro a añadir |
| `role` | string | No | Rol para el miembro \(MEMBER, MANAGER, o OWNER\). Por defecto es MEMBER. |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
### `google_groups_remove_member`
Eliminar un miembro de un grupo de Google
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
| `memberKey` | string | Sí | Dirección de correo electrónico o ID único del miembro a eliminar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
### `google_groups_update_member`
Actualizar un miembro
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
| `memberKey` | string | Sí | Dirección de correo electrónico del miembro o ID único del miembro |
| `role` | string | Sí | Nuevo rol para el miembro \(MEMBER, MANAGER, o OWNER\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
### `google_groups_has_member`
Comprobar si un usuario es miembro de un grupo de Google
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Sí | Dirección de correo electrónico del grupo o ID único del grupo |
| `memberKey` | string | Sí | Dirección de correo electrónico del miembro o ID único del miembro a comprobar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `output` | json | Datos de respuesta de la API de Google Groups |
## Notas
- Categoría: `tools`
- Tipo: `google_groups`

View File

@@ -135,283 +135,684 @@ Eliminar una cuenta de Salesforce CRM
### `salesforce_get_contacts`
Obtener contacto(s) de Salesforce - un solo contacto si se proporciona ID, o lista si no
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `contactId` | string | No | ID de contacto \(si se proporciona, devuelve un solo contacto\) |
| `limit` | string | No | Número de resultados \(predeterminado: 100, máximo: 2000\). Solo para consulta de lista. |
| `fields` | string | No | Campos separados por comas \(p. ej., "Id,FirstName,LastName,Email,Phone"\) |
| `orderBy` | string | No | Campo para ordenar \(p. ej., "LastName ASC"\). Solo para consulta de lista. |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `output` | object | Datos de contacto(s) |
### `salesforce_create_contact`
Crear un nuevo contacto en Salesforce CRM
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `lastName` | string | Sí | Apellido \(obligatorio\) |
| `firstName` | string | No | Nombre |
| `email` | string | No | Dirección de correo electrónico |
| `phone` | string | No | Número de teléfono |
| `accountId` | string | No | ID de cuenta para asociar con el contacto |
| `title` | string | No | Sin descripción |
| `department` | string | No | Departamento |
| `mailingStreet` | string | No | Calle de correo |
| `mailingCity` | string | No | Ciudad de correo |
| `mailingState` | string | No | Estado de correo |
| `mailingPostalCode` | string | No | Código postal de correo |
| `mailingCountry` | string | No | País de correo |
| `description` | string | No | Descripción del contacto |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `output` | object | Datos del contacto creado |
### `salesforce_update_contact`
Actualizar un contacto existente en Salesforce CRM
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `contactId` | string | Sí | ID del contacto a actualizar (obligatorio) |
| `lastName` | string | No | Apellido |
| `firstName` | string | No | Nombre |
| `email` | string | No | Dirección de correo electrónico |
| `phone` | string | No | Número de teléfono |
| `accountId` | string | No | ID de la cuenta para asociar |
| `title` | string | No | Sin descripción |
| `department` | string | No | Departamento |
| `mailingStreet` | string | No | Calle de correo |
| `mailingCity` | string | No | Ciudad de correo |
| `mailingState` | string | No | Estado de correo |
| `mailingPostalCode` | string | No | Código postal de correo |
| `mailingCountry` | string | No | País de correo |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `output` | object | Datos del contacto actualizado |
### `salesforce_delete_contact`
Eliminar un contacto de Salesforce CRM
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `contactId` | string | Sí | ID del contacto a eliminar \(obligatorio\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `output` | object | Datos del contacto eliminado |
### `salesforce_get_leads`
Obtener candidato(s) de Salesforce
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `leadId` | string | No | ID del candidato \(opcional\) |
| `limit` | string | No | Máximo de resultados \(predeterminado: 100\) |
| `fields` | string | No | Campos separados por comas |
| `orderBy` | string | No | Campo para ordenar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Datos del candidato |
### `salesforce_create_lead`
Crear un nuevo candidato
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `lastName` | string | Sí | Apellido \(obligatorio\) |
| `company` | string | Sí | Empresa \(obligatorio\) |
| `firstName` | string | No | Nombre |
| `email` | string | No | Sin descripción |
| `phone` | string | No | Sin descripción |
| `status` | string | No | Estado del candidato |
| `leadSource` | string | No | Origen del candidato |
| `title` | string | No | Sin descripción |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Cliente potencial creado |
### `salesforce_update_lead`
Actualizar un cliente potencial existente
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `leadId` | string | Sí | ID del cliente potencial (obligatorio) |
| `lastName` | string | No | Apellido |
| `company` | string | No | Sin descripción |
| `firstName` | string | No | Nombre |
| `email` | string | No | Sin descripción |
| `phone` | string | No | Sin descripción |
| `status` | string | No | Estado del cliente potencial |
| `leadSource` | string | No | Origen del cliente potencial |
| `title` | string | No | Sin descripción |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Cliente potencial actualizado |
### `salesforce_delete_lead`
Eliminar un cliente potencial
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `leadId` | string | Sí | ID del cliente potencial (obligatorio) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Cliente potencial eliminado |
### `salesforce_get_opportunities`
Obtener oportunidad(es) de Salesforce
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `opportunityId` | string | No | ID de oportunidad (opcional) |
| `limit` | string | No | Máximo de resultados (predeterminado: 100) |
| `fields` | string | No | Campos separados por comas |
| `orderBy` | string | No | Campo para ordenar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Datos de la oportunidad |
### `salesforce_create_opportunity`
Crear una nueva oportunidad
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `name` | string | Sí | Nombre de la oportunidad (obligatorio) |
| `stageName` | string | Sí | Nombre de la etapa (obligatorio) |
| `closeDate` | string | Sí | Fecha de cierre AAAA-MM-DD (obligatorio) |
| `accountId` | string | No | ID de la cuenta |
| `amount` | string | No | Importe (número) |
| `probability` | string | No | Probabilidad (0-100) |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Oportunidad creada |
### `salesforce_update_opportunity`
Actualizar una oportunidad existente
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `opportunityId` | string | Sí | ID de la oportunidad \(obligatorio\) |
| `name` | string | No | Nombre de la oportunidad |
| `stageName` | string | No | Nombre de la etapa |
| `closeDate` | string | No | Fecha de cierre AAAA-MM-DD |
| `accountId` | string | No | ID de la cuenta |
| `amount` | string | No | Sin descripción |
| `probability` | string | No | Probabilidad \(0-100\) |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Oportunidad actualizada |
### `salesforce_delete_opportunity`
Eliminar una oportunidad
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `opportunityId` | string | Sí | ID de la oportunidad \(obligatorio\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Oportunidad eliminada |
### `salesforce_get_cases`
Obtener caso(s) de Salesforce
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `caseId` | string | No | ID del caso \(opcional\) |
| `limit` | string | No | Resultados máximos \(predeterminado: 100\) |
| `fields` | string | No | Campos separados por comas |
| `orderBy` | string | No | Campo para ordenar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Éxito |
| `output` | object | Datos del caso |
### `salesforce_create_case`
Crear un nuevo caso
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `subject` | string | Sí | Asunto del caso \(obligatorio\) |
| `status` | string | No | Estado \(p. ej., Nuevo, En proceso, Escalado\) |
| `priority` | string | No | Prioridad \(p. ej., Baja, Media, Alta\) |
| `origin` | string | No | Origen \(p. ej., Teléfono, Email, Web\) |
| `contactId` | string | No | ID del contacto |
| `accountId` | string | No | ID de la cuenta |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Éxito |
| `output` | object | Caso creado |
### `salesforce_update_case`
Actualizar un caso existente
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `caseId` | string | Sí | ID del caso \(obligatorio\) |
| `subject` | string | No | Asunto del caso |
| `status` | string | No | Estado |
| `priority` | string | No | Prioridad |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Éxito |
| `output` | object | Caso actualizado |
### `salesforce_delete_case`
Eliminar un caso
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `caseId` | string | Sí | ID del caso \(obligatorio\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Éxito |
| `output` | object | Caso eliminado |
### `salesforce_get_tasks`
Obtener tarea(s) de Salesforce
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `taskId` | string | No | ID de la tarea \(opcional\) |
| `limit` | string | No | Máximo de resultados \(predeterminado: 100\) |
| `fields` | string | No | Campos separados por comas |
| `orderBy` | string | No | Campo para ordenar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `output` | object | Datos de la tarea |
### `salesforce_create_task`
Crear una nueva tarea
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `subject` | string | Sí | Asunto de la tarea \(obligatorio\) |
| `status` | string | No | Estado \(p. ej., No iniciada, En progreso, Completada\) |
| `priority` | string | No | Prioridad \(p. ej., Baja, Normal, Alta\) |
| `activityDate` | string | No | Fecha de vencimiento AAAA-MM-DD |
| `whoId` | string | No | ID de contacto/cliente potencial relacionado |
| `whatId` | string | No | ID de cuenta/oportunidad relacionada |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `output` | object | Tarea creada |
### `salesforce_update_task`
Actualizar una tarea existente
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `taskId` | string | Sí | ID de la tarea \(obligatorio\) |
| `subject` | string | No | Asunto de la tarea |
| `status` | string | No | Estado |
| `priority` | string | No | Prioridad |
| `activityDate` | string | No | Fecha de vencimiento AAAA-MM-DD |
| `description` | string | No | Descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Tarea actualizada |
### `salesforce_delete_task`
Eliminar una tarea
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `taskId` | string | Sí | ID de la tarea (obligatorio) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito de la operación |
| `output` | json | Datos del resultado de la operación |
| `success` | boolean | Estado de éxito |
| `output` | object | Tarea eliminada |
### `salesforce_list_reports`
Obtener una lista de informes accesibles por el usuario actual
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `folderName` | string | No | Filtrar por nombre de carpeta |
| `searchTerm` | string | No | Término de búsqueda para filtrar informes por nombre |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Datos de los informes |
### `salesforce_get_report`
Obtener metadatos e información descriptiva para un informe específico
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `reportId` | string | Sí | ID del informe (obligatorio) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Metadatos del informe |
### `salesforce_run_report`
Ejecutar un informe y obtener los resultados
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `reportId` | string | Sí | ID del informe \(obligatorio\) |
| `includeDetails` | string | No | Incluir filas detalladas \(true/false, predeterminado: true\) |
| `filters` | string | No | Cadena JSON de filtros de informe para aplicar |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Resultados del informe |
### `salesforce_list_report_types`
Obtener una lista de tipos de informes disponibles
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Datos de tipos de informes |
### `salesforce_list_dashboards`
Obtener una lista de paneles accesibles por el usuario actual
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `folderName` | string | No | Filtrar por nombre de carpeta |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Datos de los paneles |
### `salesforce_get_dashboard`
Obtener detalles y resultados de un panel específico
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `dashboardId` | string | Sí | ID del panel \(obligatorio\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Datos del panel |
### `salesforce_refresh_dashboard`
Actualizar un panel para obtener los datos más recientes
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `dashboardId` | string | Sí | ID del panel \(obligatorio\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Datos del panel actualizado |
### `salesforce_query`
Ejecutar una consulta SOQL personalizada para recuperar datos de Salesforce
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `query` | string | Sí | Consulta SOQL a ejecutar \(p. ej., SELECT Id, Name FROM Account LIMIT 10\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Resultados de la consulta |
### `salesforce_query_more`
Recuperar resultados adicionales de consulta utilizando el nextRecordsUrl de una consulta anterior
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `nextRecordsUrl` | string | Sí | El nextRecordsUrl de una respuesta de consulta anterior |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Resultados de la consulta |
### `salesforce_describe_object`
Obtener metadatos e información de campos para un objeto de Salesforce
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
| `objectName` | string | Sí | Nombre API del objeto \(p. ej., Account, Contact, Lead, Custom_Object__c\) |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Metadatos del objeto |
### `salesforce_list_objects`
Obtener una lista de todos los objetos disponibles de Salesforce
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | No | Sin descripción |
| `instanceUrl` | string | No | Sin descripción |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Estado de éxito |
| `output` | object | Lista de objetos |
## Notas

View File

@@ -142,8 +142,8 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## Bonnes pratiques
- **Ordonner correctement les conditions** : placer les conditions spécifiques avant les générales pour garantir que la logique spécifique prévaut sur les solutions de repli
- **Inclure une condition par défaut** : ajouter une condition fourre-tout (`true`) comme dernière condition pour gérer les cas non correspondants et éviter que l'exécution du flux de travail ne se bloque
- **Garder les expressions simples** : utiliser des expressions booléennes claires et directes pour une meilleure lisibilité et un débogage plus facile
- **Documenter vos conditions** : ajouter des descriptions pour expliquer l'objectif de chaque condition pour une meilleure collaboration en équipe et une maintenance plus facile
- **Tester les cas limites** : vérifier que les conditions gèrent correctement les valeurs limites en testant avec des valeurs aux extrémités de vos plages de conditions
- **Ordonnez correctement les conditions** : placez les conditions plus spécifiques avant les générales pour garantir que la logique spécifique prévaut sur les solutions de repli
- **Utilisez la branche else quand nécessaire** : si aucune condition ne correspond et que la branche else n'est pas connectée, la branche du workflow se terminera gracieusement. Connectez la branche else si vous avez besoin d'un chemin de repli pour les cas non correspondants
- **Gardez les expressions simples** : utilisez des expressions booléennes claires et directes pour une meilleure lisibilité et un débogage plus facile
- **Documentez vos conditions** : ajoutez des descriptions pour expliquer l'objectif de chaque condition pour une meilleure collaboration en équipe et une maintenance plus facile
- **Testez les cas limites** : vérifiez que les conditions gèrent correctement les valeurs limites en testant avec des valeurs aux extrémités de vos plages de conditions

View File

@@ -72,7 +72,7 @@ Pour les intégrations personnalisées, utilisez notre [support MCP (Model Conte
<Video src="introduction/integrations-sidebar.mp4" width={700} height={450} />
</div>
## Copilote propulsé par l'IA
## Copilot
**Posez des questions et obtenez des conseils**
Le copilote répond aux questions sur Sim, explique vos flux de travail et propose des suggestions d'amélioration. Utilisez le symbole `@` pour référencer les flux de travail, les blocs, la documentation, les connaissances et les journaux pour une assistance contextuelle.

View File

@@ -1,5 +1,7 @@
---
title: Base de connaissances
title: Aperçu
description: Téléchargez, traitez et recherchez dans vos documents grâce à la
recherche vectorielle intelligente et au découpage
---
import { Video } from '@/components/ui/video'

View File

@@ -0,0 +1,155 @@
---
title: Docker
description: Déployer Sim Studio avec Docker Compose
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Démarrage rapide
```bash
# Clone and start
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
Ouvrez [http://localhost:3000](http://localhost:3000)
## Configuration de production
### 1. Configurer l'environnement
```bash
# Generate secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
```
### 2. Démarrer les services
```bash
docker compose -f docker-compose.prod.yml up -d
```
### 3. Configurer SSL
<Tabs items={['Caddy (Recommandé)', 'Nginx + Certbot']}>
<Tab value="Caddy (Recommandé)">
Caddy gère automatiquement les certificats SSL.
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
```
Créez `/etc/caddy/Caddyfile` :
```
sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}
```
```bash
sudo systemctl restart caddy
```
</Tab>
<Tab value="Nginx + Certbot">
```bash
# Install
sudo apt install nginx certbot python3-certbot-nginx -y
# Create /etc/nginx/sites-available/sim
server {
listen 80;
server_name sim.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Enable and get certificate
sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
sudo certbot --nginx -d sim.yourdomain.com
```
</Tab>
</Tabs>
## Ollama
```bash
# With GPU
docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
# CPU only
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
```
Télécharger des modèles supplémentaires :
```bash
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
```
### Ollama externe
Si Ollama s'exécute sur votre machine hôte (pas dans Docker) :
```bash
# macOS/Windows
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
# Linux - use your host IP
OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
```
<Callout type="warning">
À l'intérieur de Docker, `localhost` fait référence au conteneur, pas à votre hôte. Utilisez `host.docker.internal` ou l'IP de votre hôte.
</Callout>
## Commandes
```bash
# View logs
docker compose -f docker-compose.prod.yml logs -f simstudio
# Stop
docker compose -f docker-compose.prod.yml down
# Update
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
# Backup database
docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
```

View File

@@ -0,0 +1,87 @@
---
title: Variables d'environnement
description: Référence de configuration pour Sim Studio
---
import { Callout } from 'fumadocs-ui/components/callout'
## Obligatoires
| Variable | Description |
|----------|-------------|
| `DATABASE_URL` | Chaîne de connexion PostgreSQL |
| `BETTER_AUTH_SECRET` | Secret d'authentification (32 caractères hexadécimaux) : `openssl rand -hex 32` |
| `BETTER_AUTH_URL` | URL de votre application |
| `ENCRYPTION_KEY` | Clé de chiffrement (32 caractères hexadécimaux) : `openssl rand -hex 32` |
| `INTERNAL_API_SECRET` | Secret API interne (32 caractères hexadécimaux) : `openssl rand -hex 32` |
| `NEXT_PUBLIC_APP_URL` | URL publique de l'application |
| `NEXT_PUBLIC_SOCKET_URL` | URL WebSocket (par défaut : `http://localhost:3002`) |
## Fournisseurs d'IA
| Variable | Fournisseur |
|----------|----------|
| `OPENAI_API_KEY` | OpenAI |
| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
| `GEMINI_API_KEY_1` | Google Gemini |
| `MISTRAL_API_KEY` | Mistral |
| `OLLAMA_URL` | Ollama (par défaut : `http://localhost:11434`) |
<Callout type="info">
Pour l'équilibrage de charge, ajoutez plusieurs clés avec les suffixes `_1`, `_2`, `_3` (par exemple, `OPENAI_API_KEY_1`, `OPENAI_API_KEY_2`). Fonctionne avec OpenAI, Anthropic et Gemini.
</Callout>
<Callout type="info">
Dans Docker, utilisez `OLLAMA_URL=http://host.docker.internal:11434` pour Ollama sur la machine hôte.
</Callout>
### Azure OpenAI
| Variable | Description |
|----------|-------------|
| `AZURE_OPENAI_API_KEY` | Clé API Azure OpenAI |
| `AZURE_OPENAI_ENDPOINT` | URL du point de terminaison Azure OpenAI |
| `AZURE_OPENAI_API_VERSION` | Version de l'API (par exemple, `2024-02-15-preview`) |
### vLLM (auto-hébergé)
| Variable | Description |
|----------|-------------|
| `VLLM_BASE_URL` | URL du serveur vLLM (par exemple, `http://localhost:8000/v1`) |
| `VLLM_API_KEY` | Jeton bearer optionnel pour vLLM |
## Fournisseurs OAuth
| Variable | Description |
|----------|-------------|
| `GOOGLE_CLIENT_ID` | ID client OAuth Google |
| `GOOGLE_CLIENT_SECRET` | Secret client OAuth Google |
| `GITHUB_CLIENT_ID` | ID client OAuth GitHub |
| `GITHUB_CLIENT_SECRET` | Secret client OAuth GitHub |
## Optionnel
| Variable | Description |
|----------|-------------|
| `API_ENCRYPTION_KEY` | Chiffre les clés API stockées (32 caractères hexadécimaux) : `openssl rand -hex 32` |
| `COPILOT_API_KEY` | Clé API pour les fonctionnalités copilot |
| `ADMIN_API_KEY` | Clé API administrateur pour les opérations GitOps |
| `RESEND_API_KEY` | Service de messagerie pour les notifications |
| `ALLOWED_LOGIN_DOMAINS` | Restreindre les inscriptions à des domaines (séparés par des virgules) |
| `ALLOWED_LOGIN_EMAILS` | Restreindre les inscriptions à des emails spécifiques (séparés par des virgules) |
| `DISABLE_REGISTRATION` | Définir à `true` pour désactiver les inscriptions de nouveaux utilisateurs |
## Exemple de fichier .env
```bash
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=<openssl rand -hex 32>
BETTER_AUTH_URL=https://sim.yourdomain.com
ENCRYPTION_KEY=<openssl rand -hex 32>
INTERNAL_API_SECRET=<openssl rand -hex 32>
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
OPENAI_API_KEY=sk-...
```
Voir `apps/sim/.env.example` pour toutes les options.

View File

@@ -0,0 +1,50 @@
---
title: Auto-hébergement
description: Déployez Sim Studio sur votre propre infrastructure
---
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Callout } from 'fumadocs-ui/components/callout'
Déployez Sim Studio sur votre propre infrastructure avec Docker ou Kubernetes.
## Prérequis
| Ressource | Minimum | Recommandé |
|----------|---------|-------------|
| CPU | 2 cœurs | 4+ cœurs |
| RAM | 12 Go | 16+ Go |
| Stockage | 20 Go SSD | 50+ Go SSD |
| Docker | 20.10+ | Dernière version |
## Démarrage rapide
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
Ouvrez [http://localhost:3000](http://localhost:3000)
## Options de déploiement
<Cards>
<Card title="Docker" href="/self-hosting/docker">
Déployez avec Docker Compose sur n'importe quel serveur
</Card>
<Card title="Kubernetes" href="/self-hosting/kubernetes">
Déployez avec Helm sur des clusters Kubernetes
</Card>
<Card title="Plateformes cloud" href="/self-hosting/platforms">
Guides pour Railway, DigitalOcean, AWS, Azure, GCP
</Card>
</Cards>
## Architecture
| Composant | Port | Description |
|-----------|------|-------------|
| simstudio | 3000 | Application principale |
| realtime | 3002 | Serveur WebSocket |
| db | 5432 | PostgreSQL avec pgvector |
| migrations | - | Migrations de base de données (exécutées une seule fois) |

View File

@@ -0,0 +1,133 @@
---
title: Kubernetes
description: Déployer Sim Studio avec Helm
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Prérequis
- Kubernetes 1.19+
- Helm 3.0+
- Support du provisionneur PV
## Installation
```bash
# Clone repo
git clone https://github.com/simstudioai/sim.git && cd sim
# Generate secrets
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
# Install
helm install sim ./helm/sim \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--namespace simstudio --create-namespace
```
## Valeurs spécifiques au cloud
<Tabs items={['AWS EKS', 'Azure AKS', 'GCP GKE']}>
<Tab value="AWS EKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-aws.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="Azure AKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-azure.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="GCP GKE">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-gcp.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
</Tabs>
## Configuration clé
```yaml
# Custom values.yaml
app:
replicaCount: 2
env:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
OPENAI_API_KEY: "sk-..."
postgresql:
persistence:
size: 50Gi
ingress:
enabled: true
className: nginx
tls:
enabled: true
app:
host: sim.yourdomain.com
```
Voir `helm/sim/values.yaml` pour toutes les options.
## Base de données externe
```yaml
postgresql:
enabled: false
externalDatabase:
enabled: true
host: "your-db-host"
port: 5432
username: "postgres"
password: "your-password"
database: "simstudio"
sslMode: "require"
```
## Commandes
```bash
# Port forward for local access
kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
# View logs
kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
# Upgrade
helm upgrade sim ./helm/sim --namespace simstudio
# Uninstall
helm uninstall sim --namespace simstudio
```

View File

@@ -0,0 +1,124 @@
---
title: Plateformes cloud
description: Déployer Sim Studio sur des plateformes cloud
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Railway
Déploiement en un clic avec provisionnement automatique de PostgreSQL.
[
![Déployer sur Railway](https://railway.app/button.svg)
](https://railway.com/new/template/sim-studio)
Après le déploiement, ajoutez des variables d'environnement dans le tableau de bord Railway :
- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (générées automatiquement)
- `OPENAI_API_KEY` ou d'autres clés de fournisseur d'IA
- Domaine personnalisé dans Paramètres → Réseau
## Déploiement VPS
Pour DigitalOcean, AWS EC2, Azure VMs, ou tout serveur Linux :
<Tabs items={['DigitalOcean', 'AWS EC2', 'Azure VM']}>
<Tab value="DigitalOcean">
**Recommandé :** Droplet de 16 Go de RAM, Ubuntu 24.04
```bash
# Create Droplet via console, then SSH in
ssh root@your-droplet-ip
```
</Tab>
<Tab value="AWS EC2">
**Recommandé :** t3.xlarge (16 Go de RAM), Ubuntu 24.04
```bash
ssh -i your-key.pem ubuntu@your-ec2-ip
```
</Tab>
<Tab value="Azure VM">
**Recommandé :** Standard_D4s_v3 (16 Go de RAM), Ubuntu 24.04
```bash
ssh azureuser@your-vm-ip
```
</Tab>
</Tabs>
### Installer Docker
```bash
# Install Docker (official method)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Logout and reconnect, then verify
docker --version
```
### Déployer Sim Studio
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
# Create .env with secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
# Start
docker compose -f docker-compose.prod.yml up -d
```
### SSL avec Caddy
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Configure (replace domain)
echo 'sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}' | sudo tee /etc/caddy/Caddyfile
sudo systemctl restart caddy
```
Pointez l'enregistrement DNS A de votre domaine vers l'IP de votre serveur.
## Kubernetes (EKS, AKS, GKE)
Consultez le [guide Kubernetes](/self-hosting/kubernetes) pour le déploiement Helm sur Kubernetes géré.
## Base de données gérée (optionnel)
Pour la production, utilisez un service PostgreSQL géré :
- **AWS RDS** / **Azure Database** / **Cloud SQL** - Activez l'extension pgvector
- **Supabase** / **Neon** - pgvector inclus
Définissez `DATABASE_URL` dans votre environnement :
```bash
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
```

View File

@@ -0,0 +1,113 @@
---
title: Dépannage
description: Problèmes courants et solutions
---
## Échec de connexion à la base de données
```bash
# Check database is running
docker compose ps db
# Test connection
docker compose exec db psql -U postgres -c "SELECT 1"
```
Vérifiez le format de `DATABASE_URL` : `postgresql://user:pass@host:5432/database`
## Les modèles Ollama ne s'affichent pas
Dans Docker, `localhost` = le conteneur, pas votre machine hôte.
```bash
# For host-machine Ollama, use:
OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
```
## WebSocket/Temps réel ne fonctionne pas
1. Vérifiez que `NEXT_PUBLIC_SOCKET_URL` correspond à votre domaine
2. Vérifiez que le service temps réel est en cours d'exécution : `docker compose ps realtime`
3. Assurez-vous que le proxy inverse transmet les mises à niveau WebSocket (voir [Guide Docker](/self-hosting/docker))
## Erreur 502 Bad Gateway
```bash
# Check app is running
docker compose ps simstudio
docker compose logs simstudio
# Common causes: out of memory, database not ready
```
## Erreurs de migration
```bash
# View migration logs
docker compose logs migrations
# Run manually
docker compose exec simstudio bun run db:migrate
```
## pgvector introuvable
Utilisez l'image PostgreSQL correcte :
```yaml
image: pgvector/pgvector:pg17 # NOT postgres:17
```
## Erreurs de certificat (CERT_HAS_EXPIRED)
Si vous voyez des erreurs de certificat SSL lors de l'appel d'API externes :
```bash
# Update CA certificates in container
docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
# Or set in environment (not recommended for production)
NODE_TLS_REJECT_UNAUTHORIZED=0
```
## Page blanche après connexion
1. Vérifiez la console du navigateur pour les erreurs
2. Vérifiez que `NEXT_PUBLIC_APP_URL` correspond à votre domaine réel
3. Effacez les cookies et le stockage local du navigateur
4. Vérifiez que tous les services sont en cours d'exécution : `docker compose ps`
## Problèmes spécifiques à Windows
**Erreurs Turbopack sur Windows :**
```bash
# Use WSL2 for better compatibility
wsl --install
# Or disable Turbopack in package.json
# Change "next dev --turbopack" to "next dev"
```
**Problèmes de fin de ligne :**
```bash
# Configure git to use LF
git config --global core.autocrlf input
```
## Consulter les journaux
```bash
# All services
docker compose logs -f
# Specific service
docker compose logs -f simstudio
```
## Obtenir de l'aide
- [Problèmes GitHub](https://github.com/simstudioai/sim/issues)
- [Discord](https://discord.gg/Hr4UWYEcTT)

View File

@@ -0,0 +1,181 @@
---
title: Cursor
description: Lancez et gérez des agents cloud Cursor pour travailler sur des dépôts GitHub
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="cursor"
color="#1E1E1E"
/>
{/* MANUAL-CONTENT-START:intro */}
[Cursor](https://www.cursor.so/) est un IDE IA et une plateforme cloud qui vous permet de lancer et de gérer de puissants agents IA capables de travailler directement sur vos dépôts GitHub. Les agents Cursor peuvent automatiser les tâches de développement, améliorer la productivité de votre équipe et collaborer avec vous en apportant des modifications au code, en répondant aux instructions en langage naturel et en conservant l'historique des conversations sur leurs activités.
Avec Cursor, vous pouvez :
- **Lancer des agents cloud pour les bases de code** : créer instantanément de nouveaux agents IA qui travaillent sur vos dépôts dans le cloud
- **Déléguer des tâches de codage en langage naturel** : guider les agents avec des instructions écrites, des modifications et des clarifications
- **Suivre les progrès et les résultats** : récupérer l'état des agents, consulter les résultats détaillés et inspecter les tâches en cours ou terminées
- **Accéder à l'historique complet des conversations** : examiner toutes les requêtes et réponses de l'IA pour plus de transparence et de traçabilité
- **Contrôler et gérer le cycle de vie des agents** : lister les agents actifs, terminer des agents et gérer les lancements d'agents et les suivis via API
Dans Sim, l'intégration de Cursor permet à vos agents et flux de travail d'interagir par programmation avec les agents cloud de Cursor. Cela signifie que vous pouvez utiliser Sim pour :
- Lister tous les agents cloud et parcourir leur état actuel (`cursor_list_agents`)
- Récupérer l'état et les résultats à jour de n'importe quel agent (`cursor_get_agent`)
- Consulter l'historique complet des conversations pour tout agent de codage (`cursor_get_conversation`)
- Ajouter des instructions de suivi ou de nouvelles requêtes à un agent en cours d'exécution
- Gérer et terminer des agents selon les besoins
Cette intégration vous aide à combiner l'intelligence flexible des agents Sim avec les puissantes capacités d'automatisation du développement de Cursor, rendant possible l'extension du développement piloté par l'IA à travers vos projets.
{/* MANUAL-CONTENT-END */}
## Instructions d'utilisation
Interagissez avec l'API Cursor Cloud Agents pour lancer des agents IA qui peuvent travailler sur vos dépôts GitHub. Prend en charge le lancement d'agents, l'ajout d'instructions complémentaires, la vérification du statut, la visualisation des conversations et la gestion du cycle de vie des agents.
## Outils
### `cursor_list_agents`
Liste tous les agents cloud pour l'utilisateur authentifié avec pagination optionnelle.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Oui | Clé API Cursor |
| `limit` | number | Non | Nombre d'agents à retourner \(par défaut : 20, max : 100\) |
| `cursor` | string | Non | Curseur de pagination de la réponse précédente |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Liste lisible des agents |
| `metadata` | object | Métadonnées de la liste d'agents |
### `cursor_get_agent`
Récupère le statut actuel et les résultats d'un agent cloud.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `apiKey` | chaîne | Oui | Clé API Cursor |
| `agentId` | chaîne | Oui | Identifiant unique pour l'agent cloud \(ex. : bc_abc123\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Détails de l'agent en format lisible |
| `metadata` | object | Métadonnées de l'agent |
### `cursor_get_conversation`
Récupère l'historique de conversation d'un agent cloud, y compris toutes les instructions de l'utilisateur et les réponses de l'assistant.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Oui | Clé API Cursor |
| `agentId` | string | Oui | Identifiant unique pour l'agent cloud \(ex. : bc_abc123\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Historique de conversation en format lisible |
| `metadata` | object | Métadonnées de la conversation |
### `cursor_launch_agent`
Démarrer un nouvel agent cloud pour travailler sur un dépôt GitHub avec les instructions données.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `apiKey` | chaîne | Oui | Clé API Cursor |
| `repository` | chaîne | Oui | URL du dépôt GitHub \(ex. : https://github.com/your-org/your-repo\) |
| `ref` | chaîne | Non | Branche, tag ou commit à partir duquel travailler \(par défaut : branche principale\) |
| `promptText` | chaîne | Oui | Le texte d'instruction pour l'agent |
| `promptImages` | chaîne | Non | Tableau JSON d'objets d'image avec données base64 et dimensions |
| `model` | chaîne | Non | Modèle à utiliser \(laisser vide pour sélection automatique\) |
| `branchName` | chaîne | Non | Nom de branche personnalisé pour l'agent |
| `autoCreatePr` | booléen | Non | Créer automatiquement une PR lorsque l'agent termine |
| `openAsCursorGithubApp` | booléen | Non | Ouvrir la PR en tant qu'application GitHub Cursor |
| `skipReviewerRequest` | booléen | Non | Ignorer la demande de relecteurs sur la PR |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | chaîne | Message de succès avec les détails de l'agent |
| `metadata` | objet | Métadonnées du résultat de lancement |
### `cursor_add_followup`
Ajouter une instruction complémentaire à un agent cloud existant.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `apiKey` | chaîne | Oui | Clé API Cursor |
| `agentId` | chaîne | Oui | Identifiant unique pour l'agent cloud \(ex. : bc_abc123\) |
| `followupPromptText` | chaîne | Oui | Le texte d'instruction complémentaire pour l'agent |
| `promptImages` | chaîne | Non | Tableau JSON d'objets d'image avec données base64 et dimensions \(max 5\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Message de succès |
| `metadata` | object | Métadonnées du résultat |
### `cursor_stop_agent`
Arrêter un agent cloud en cours d'exécution. Cela met l'agent en pause sans le supprimer.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `apiKey` | chaîne | Oui | Clé API Cursor |
| `agentId` | chaîne | Oui | Identifiant unique pour l'agent cloud \(ex., bc_abc123\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | chaîne | Message de succès |
| `metadata` | objet | Métadonnées du résultat |
### `cursor_delete_agent`
Supprimer définitivement un agent cloud. Cette action ne peut pas être annulée.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `apiKey` | string | Oui | Clé API Cursor |
| `agentId` | string | Oui | Identifiant unique pour l'agent cloud \(ex., bc_abc123\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `content` | string | Message de succès |
| `metadata` | object | Métadonnées du résultat |
## Notes
- Catégorie : `tools`
- Type : `cursor`

View File

@@ -0,0 +1,217 @@
---
title: Google Groups
description: Gérer les groupes Google Workspace et leurs membres
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_groups"
color="#E8F0FE"
/>
## Instructions d'utilisation
Connectez-vous à Google Workspace pour créer, mettre à jour et gérer les groupes et leurs membres à l'aide de l'API Admin SDK Directory.
## Outils
### `google_groups_list_groups`
Lister tous les groupes dans un domaine Google Workspace
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `customer` | string | Non | ID client ou "my_customer" pour le domaine de l'utilisateur authentifié |
| `domain` | string | Non | Nom de domaine pour filtrer les groupes |
| `maxResults` | number | Non | Nombre maximum de résultats à retourner (1-200) |
| `pageToken` | string | Non | Jeton pour la pagination |
| `query` | string | Non | Requête de recherche pour filtrer les groupes (ex. : "email:admin*") |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_get_group`
Obtenir les détails d'un groupe Google spécifique par email ou ID de groupe
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Oui | Adresse email du groupe ou ID unique du groupe |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_create_group`
Créer un nouveau groupe Google dans le domaine
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `email` | chaîne | Oui | Adresse e-mail pour le nouveau groupe \(ex., team@yourdomain.com\) |
| `name` | chaîne | Oui | Nom d'affichage pour le groupe |
| `description` | chaîne | Non | Description du groupe |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_update_group`
Mettre à jour un groupe Google existant
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
| `name` | chaîne | Non | Nouveau nom d'affichage pour le groupe |
| `description` | chaîne | Non | Nouvelle description pour le groupe |
| `email` | chaîne | Non | Nouvelle adresse e-mail pour le groupe |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_delete_group`
Supprimer un groupe Google
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ---------- | ----------- |
| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe à supprimer |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_list_members`
Lister tous les membres d'un groupe Google
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
| `maxResults` | nombre | Non | Nombre maximum de résultats à retourner \(1-200\) |
| `pageToken` | chaîne | Non | Jeton pour la pagination |
| `roles` | chaîne | Non | Filtrer par rôles \(séparés par des virgules : OWNER, MANAGER, MEMBER\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_get_member`
Obtenir les détails d'un membre spécifique dans un groupe Google
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
| `memberKey` | chaîne | Oui | Adresse e-mail du membre ou identifiant unique du membre |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_add_member`
Ajouter un nouveau membre à un groupe Google
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | chaîne | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
| `email` | chaîne | Oui | Adresse e-mail du membre à ajouter |
| `role` | chaîne | Non | Rôle pour le membre \(MEMBER, MANAGER, ou OWNER\). Par défaut MEMBER. |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_remove_member`
Supprimer un membre d'un groupe Google
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
| `memberKey` | string | Oui | Adresse e-mail ou identifiant unique du membre à supprimer |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_update_member`
Mettre à jour un membre
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
| `memberKey` | string | Oui | Adresse e-mail du membre ou identifiant unique du membre |
| `role` | string | Oui | Nouveau rôle pour le membre \(MEMBER, MANAGER ou OWNER\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
### `google_groups_has_member`
Vérifier si un utilisateur est membre d'un groupe Google
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | Oui | Adresse e-mail du groupe ou identifiant unique du groupe |
| `memberKey` | string | Oui | Adresse e-mail du membre ou identifiant unique du membre à vérifier |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `output` | json | Données de réponse de l'API Google Groups |
## Notes
- Catégorie : `tools`
- Type : `google_groups`

View File

@@ -135,283 +135,684 @@ Supprimer un compte de Salesforce CRM
### `salesforce_get_contacts`
Obtenir un ou des contact(s) depuis Salesforce - contact unique si l'ID est fourni, ou liste si non
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `contactId` | string | Non | ID du contact \(si fourni, renvoie un contact unique\) |
| `limit` | string | Non | Nombre de résultats \(par défaut : 100, max : 2000\). Uniquement pour les requêtes de liste. |
| `fields` | string | Non | Champs séparés par des virgules \(ex. : "Id,FirstName,LastName,Email,Phone"\) |
| `orderBy` | string | Non | Champ pour l'ordre de tri \(ex. : "LastName ASC"\). Uniquement pour les requêtes de liste. |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `output` | object | Données du/des contact(s) |
### `salesforce_create_contact`
Créer un nouveau contact dans Salesforce CRM
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `lastName` | string | Oui | Nom de famille \(obligatoire\) |
| `firstName` | string | Non | Prénom |
| `email` | string | Non | Adresse e-mail |
| `phone` | string | Non | Numéro de téléphone |
| `accountId` | string | Non | ID du compte à associer au contact |
| `title` | string | Non | Pas de description |
| `department` | string | Non | Département |
| `mailingStreet` | string | Non | Rue d'adresse postale |
| `mailingCity` | string | Non | Ville d'adresse postale |
| `mailingState` | string | Non | État/province d'adresse postale |
| `mailingPostalCode` | string | Non | Code postal d'adresse postale |
| `mailingCountry` | string | Non | Pays d'adresse postale |
| `description` | string | Non | Description du contact |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `output` | object | Données du contact créé |
### `salesforce_update_contact`
Mettre à jour un contact existant dans Salesforce CRM
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `contactId` | string | Oui | ID du contact à mettre à jour \(obligatoire\) |
| `lastName` | string | Non | Nom de famille |
| `firstName` | string | Non | Prénom |
| `email` | string | Non | Adresse e-mail |
| `phone` | string | Non | Numéro de téléphone |
| `accountId` | string | Non | ID du compte à associer |
| `title` | string | Non | Pas de description |
| `department` | string | Non | Département |
| `mailingStreet` | string | Non | Rue de l'adresse postale |
| `mailingCity` | string | Non | Ville de l'adresse postale |
| `mailingState` | string | Non | État/province de l'adresse postale |
| `mailingPostalCode` | string | Non | Code postal de l'adresse postale |
| `mailingCountry` | string | Non | Pays de l'adresse postale |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `output` | object | Données du contact mis à jour |
### `salesforce_delete_contact`
Supprimer un contact de Salesforce CRM
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `contactId` | string | Oui | ID du contact à supprimer \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `output` | object | Données du contact supprimé |
### `salesforce_get_leads`
Obtenir un ou plusieurs prospects de Salesforce
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `leadId` | string | Non | ID du prospect \(facultatif\) |
| `limit` | string | Non | Nombre maximum de résultats \(par défaut : 100\) |
| `fields` | string | Non | Champs séparés par des virgules |
| `orderBy` | string | Non | Champ pour l'ordre de tri |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Statut de réussite |
| `output` | object | Données du prospect |
### `salesforce_create_lead`
Créer un nouveau prospect
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `lastName` | string | Oui | Nom de famille \(obligatoire\) |
| `company` | string | Oui | Société \(obligatoire\) |
| `firstName` | string | Non | Prénom |
| `email` | string | Non | Pas de description |
| `phone` | string | Non | Pas de description |
| `status` | string | Non | Statut du prospect |
| `leadSource` | string | Non | Source du prospect |
| `title` | string | Non | Pas de description |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Prospect créé |
### `salesforce_update_lead`
Mettre à jour un prospect existant
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `leadId` | string | Oui | ID du prospect \(obligatoire\) |
| `lastName` | string | Non | Nom de famille |
| `company` | string | Non | Pas de description |
| `firstName` | string | Non | Prénom |
| `email` | string | Non | Pas de description |
| `phone` | string | Non | Pas de description |
| `status` | string | Non | Statut du prospect |
| `leadSource` | string | Non | Source du prospect |
| `title` | string | Non | Pas de description |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Prospect mis à jour |
### `salesforce_delete_lead`
Supprimer un prospect
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `leadId` | string | Oui | ID du prospect \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `output` | object | Prospect supprimé |
### `salesforce_get_opportunities`
Obtenir une ou plusieurs opportunités depuis Salesforce
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `opportunityId` | string | Non | ID de l'opportunité \(facultatif\) |
| `limit` | string | Non | Nombre maximum de résultats \(par défaut : 100\) |
| `fields` | string | Non | Champs séparés par des virgules |
| `orderBy` | string | Non | Champ pour l'ordre de tri |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `output` | object | Données de l'opportunité |
### `salesforce_create_opportunity`
Créer une nouvelle opportunité
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `name` | string | Oui | Nom de l'opportunité \(obligatoire\) |
| `stageName` | string | Oui | Nom de l'étape \(obligatoire\) |
| `closeDate` | string | Oui | Date de clôture AAAA-MM-JJ \(obligatoire\) |
| `accountId` | string | Non | ID du compte |
| `amount` | string | Non | Montant \(nombre\) |
| `probability` | string | Non | Probabilité \(0-100\) |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Opportunité créée |
### `salesforce_update_opportunity`
Mettre à jour une opportunité existante
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `opportunityId` | string | Oui | ID de l'opportunité \(obligatoire\) |
| `name` | string | Non | Nom de l'opportunité |
| `stageName` | string | Non | Nom de l'étape |
| `closeDate` | string | Non | Date de clôture AAAA-MM-JJ |
| `accountId` | string | Non | ID du compte |
| `amount` | string | Non | Pas de description |
| `probability` | string | Non | Probabilité \(0-100\) |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Opportunité mise à jour |
### `salesforce_delete_opportunity`
Supprimer une opportunité
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `opportunityId` | string | Oui | ID de l'opportunité \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Opportunité supprimée |
### `salesforce_get_cases`
Obtenir un ou des dossiers depuis Salesforce
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `caseId` | string | Non | ID du dossier \(facultatif\) |
| `limit` | string | Non | Nombre maximum de résultats \(par défaut : 100\) |
| `fields` | string | Non | Champs séparés par des virgules |
| `orderBy` | string | Non | Champ pour le tri |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Données du dossier |
### `salesforce_create_case`
Créer un nouveau dossier
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `subject` | string | Oui | Objet du dossier \(obligatoire\) |
| `status` | string | Non | Statut \(ex., Nouveau, En cours, Escaladé\) |
| `priority` | string | Non | Priorité \(ex., Basse, Moyenne, Haute\) |
| `origin` | string | Non | Origine \(ex., Téléphone, Email, Web\) |
| `contactId` | string | Non | ID du contact |
| `accountId` | string | Non | ID du compte |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Dossier créé |
### `salesforce_update_case`
Mettre à jour un dossier existant
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `caseId` | string | Oui | ID du dossier \(obligatoire\) |
| `subject` | string | Non | Objet du dossier |
| `status` | string | Non | Statut |
| `priority` | string | Non | Priorité |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Dossier mis à jour |
### `salesforce_delete_case`
Supprimer un dossier
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `caseId` | string | Oui | ID du dossier \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Succès |
| `output` | object | Dossier supprimé |
### `salesforce_get_tasks`
Obtenir une ou plusieurs tâches de Salesforce
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `taskId` | string | Non | ID de la tâche \(facultatif\) |
| `limit` | string | Non | Nombre maximum de résultats \(par défaut : 100\) |
| `fields` | string | Non | Champs séparés par des virgules |
| `orderBy` | string | Non | Champ pour l'ordre de tri |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `output` | object | Données de la tâche |
### `salesforce_create_task`
Créer une nouvelle tâche
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `subject` | string | Oui | Objet de la tâche \(obligatoire\) |
| `status` | string | Non | Statut \(ex., Non commencée, En cours, Terminée\) |
| `priority` | string | Non | Priorité \(ex., Basse, Normale, Haute\) |
| `activityDate` | string | Non | Date d'échéance AAAA-MM-JJ |
| `whoId` | string | Non | ID du contact/prospect associé |
| `whatId` | string | Non | ID du compte/opportunité associé |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `output` | object | Tâche créée |
### `salesforce_update_task`
Mettre à jour une tâche existante
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `taskId` | string | Oui | ID de la tâche \(obligatoire\) |
| `subject` | string | Non | Objet de la tâche |
| `status` | string | Non | Statut |
| `priority` | string | Non | Priorité |
| `activityDate` | string | Non | Date d'échéance AAAA-MM-JJ |
| `description` | string | Non | Description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Statut de réussite |
| `output` | object | Tâche mise à jour |
### `salesforce_delete_task`
Supprimer une tâche
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `taskId` | string | Oui | ID de la tâche \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite de l'opération |
| `output` | json | Données résultant de l'opération |
| `success` | boolean | Statut de réussite |
| `output` | object | Tâche supprimée |
### `salesforce_list_reports`
Obtenir une liste des rapports accessibles par l'utilisateur actuel
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `folderName` | string | Non | Filtrer par nom de dossier |
| `searchTerm` | string | Non | Terme de recherche pour filtrer les rapports par nom |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Données des rapports |
### `salesforce_get_report`
Obtenir les métadonnées et les informations descriptives d'un rapport spécifique
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `reportId` | string | Oui | ID du rapport \(obligatoire\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Métadonnées du rapport |
### `salesforce_run_report`
Exécuter un rapport et récupérer les résultats
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `reportId` | string | Oui | ID du rapport \(obligatoire\) |
| `includeDetails` | string | Non | Inclure les lignes détaillées \(true/false, par défaut : true\) |
| `filters` | string | Non | Chaîne JSON des filtres de rapport à appliquer |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Résultats du rapport |
### `salesforce_list_report_types`
Obtenir une liste des types de rapports disponibles
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Données des types de rapports |
### `salesforce_list_dashboards`
Obtenir une liste des tableaux de bord accessibles par l'utilisateur actuel
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `folderName` | string | Non | Filtrer par nom de dossier |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Données des tableaux de bord |
### `salesforce_get_dashboard`
Obtenir les détails et les résultats d'un tableau de bord spécifique
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `dashboardId` | string | Oui | ID du tableau de bord (obligatoire) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Données du tableau de bord |
### `salesforce_refresh_dashboard`
Actualiser un tableau de bord pour obtenir les données les plus récentes
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `dashboardId` | string | Oui | ID du tableau de bord (obligatoire) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Données du tableau de bord actualisé |
### `salesforce_query`
Exécuter une requête SOQL personnalisée pour récupérer des données de Salesforce
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `query` | string | Oui | Requête SOQL à exécuter (ex. : SELECT Id, Name FROM Account LIMIT 10) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Résultats de la requête |
### `salesforce_query_more`
Récupérer des résultats de requête supplémentaires en utilisant le nextRecordsUrl d'une requête précédente
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `nextRecordsUrl` | string | Oui | Le nextRecordsUrl d'une réponse de requête précédente |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Résultats de la requête |
### `salesforce_describe_object`
Obtenir les métadonnées et les informations sur les champs d'un objet Salesforce
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
| `objectName` | string | Oui | Nom API de l'objet \(ex., Account, Contact, Lead, Custom_Object__c\) |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Métadonnées de l'objet |
### `salesforce_list_objects`
Obtenir une liste de tous les objets Salesforce disponibles
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | Non | Pas de description |
| `instanceUrl` | string | Non | Pas de description |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Statut de réussite |
| `output` | object | Liste des objets |
## Notes

View File

@@ -142,8 +142,8 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## ベストプラクティス
- **条件を正しく順序付ける**: より具体的な条件を一般的な条件の前に配置し、特定のロジックがフォールバックよりも優先されるようにします
- **デフォルト条件を含める**: 最後の条件として包括的な条件(`true`)を追加し、マッチしないケースを処理してワークフローの実行が停止しないようにします
- **式をシンプルに保つ**: 読みやすさとデバッグのしやすさのために、明確で簡潔な真偽式を使用します
- **条件を正しく順序付ける**: より具体的な条件を一般的な条件の前に配置し、特定のロジックがフォールバックよりも優先されるようにします
- **必要に応じてelse分岐を使用する**: 条件が一致せず、else分岐が接続されていない場合、ワークフロー分岐は正常に終了します。一致しないケースのフォールバックパスが必要な場合は、else分岐を接続してください
- **式をシンプルに保つ**: 読みやすさとデバッグのしやすさのために、明確で簡潔なブール式を使用します
- **条件を文書化する**: チームのコラボレーションとメンテナンスを向上させるために、各条件の目的を説明する説明を追加します
- **エッジケースをテストする**: 条件範囲の境界値でテストすることで、条件が境界値を正しく処理することを確認します

View File

@@ -72,7 +72,7 @@ Simは複数のカテゴリにわたる80以上のサービスとネイティブ
<Video src="introduction/integrations-sidebar.mp4" width={700} height={450} />
</div>
## AI搭載コパイロット
## Copilot
**質問と指導を受ける**
コパイロットはSimに関する質問に答え、ワークフローを説明し、改善のための提案を提供します。`@`記号を使用してワークフロー、ブロック、ドキュメント、ナレッジ、ログを参照し、文脈に応じたサポートを受けられます。

View File

@@ -1,5 +1,6 @@
---
title: ナレッジベース
title: 概要
description: インテリジェントなベクトル検索とチャンキングを使用して、ドキュメントをアップロード、処理、検索
---
import { Video } from '@/components/ui/video'

View File

@@ -0,0 +1,155 @@
---
title: Docker
description: Docker Composeを使用してSim Studioをデプロイする
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## クイックスタート
```bash
# Clone and start
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
[http://localhost:3000](http://localhost:3000)を開く
## 本番環境のセットアップ
### 1. 環境の設定
```bash
# Generate secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
```
### 2. サービスの起動
```bash
docker compose -f docker-compose.prod.yml up -d
```
### 3. SSLの設定
<Tabs items={['Caddy (推奨)', 'Nginx + Certbot']}>
<Tab value="Caddy (推奨)">
Caddyは自動的にSSL証明書を処理します。
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
```
`/etc/caddy/Caddyfile`を作成します:
```
sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}
```
```bash
sudo systemctl restart caddy
```
</Tab>
<Tab value="Nginx + Certbot">
```bash
# Install
sudo apt install nginx certbot python3-certbot-nginx -y
# Create /etc/nginx/sites-available/sim
server {
listen 80;
server_name sim.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Enable and get certificate
sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
sudo certbot --nginx -d sim.yourdomain.com
```
</Tab>
</Tabs>
## Ollama
```bash
# With GPU
docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
# CPU only
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
```
追加モデルを取得:
```bash
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
```
### 外部Ollama
Ollamaがホストマシン上で実行されている場合Dockerではない
```bash
# macOS/Windows
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
# Linux - use your host IP
OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
```
<Callout type="warning">
Docker内では、`localhost`はホストではなくコンテナを指します。`host.docker.internal`またはホストのIPを使用してください。
</Callout>
## コマンド
```bash
# View logs
docker compose -f docker-compose.prod.yml logs -f simstudio
# Stop
docker compose -f docker-compose.prod.yml down
# Update
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
# Backup database
docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
```

View File

@@ -0,0 +1,87 @@
---
title: 環境変数
description: Sim Studioの設定リファレンス
---
import { Callout } from 'fumadocs-ui/components/callout'
## 必須項目
| 変数 | 説明 |
|----------|-------------|
| `DATABASE_URL` | PostgreSQL接続文字列 |
| `BETTER_AUTH_SECRET` | 認証シークレット32桁の16進数: `openssl rand -hex 32` |
| `BETTER_AUTH_URL` | アプリのURL |
| `ENCRYPTION_KEY` | 暗号化キー32桁の16進数: `openssl rand -hex 32` |
| `INTERNAL_API_SECRET` | 内部APIシークレット32桁の16進数: `openssl rand -hex 32` |
| `NEXT_PUBLIC_APP_URL` | 公開アプリURL |
| `NEXT_PUBLIC_SOCKET_URL` | WebSocket URLデフォルト: `http://localhost:3002` |
## AIプロバイダー
| 変数 | プロバイダー |
|----------|----------|
| `OPENAI_API_KEY` | OpenAI |
| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
| `GEMINI_API_KEY_1` | Google Gemini |
| `MISTRAL_API_KEY` | Mistral |
| `OLLAMA_URL` | Ollamaデフォルト: `http://localhost:11434` |
<Callout type="info">
負荷分散のために、`_1`、`_2`、`_3`のサフィックスを持つ複数のキーを追加できます(例:`OPENAI_API_KEY_1`、`OPENAI_API_KEY_2`。OpenAI、Anthropic、Geminiで動作します。
</Callout>
<Callout type="info">
Dockerでは、ホストマシンのOllamaに接続するために`OLLAMA_URL=http://host.docker.internal:11434`を使用してください。
</Callout>
### Azure OpenAI
| 変数 | 説明 |
|----------|-------------|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI APIキー |
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAIエンドポイントURL |
| `AZURE_OPENAI_API_VERSION` | APIバージョン`2024-02-15-preview` |
### vLLMセルフホスト
| 変数 | 説明 |
|----------|-------------|
| `VLLM_BASE_URL` | vLLMサーバーURL`http://localhost:8000/v1` |
| `VLLM_API_KEY` | vLLM用のオプションベアラートークン |
## OAuth プロバイダー
| 変数 | 説明 |
|----------|-------------|
| `GOOGLE_CLIENT_ID` | Google OAuthクライアントID |
| `GOOGLE_CLIENT_SECRET` | Google OAuthクライアントシークレット |
| `GITHUB_CLIENT_ID` | GitHub OAuthクライアントID |
| `GITHUB_CLIENT_SECRET` | GitHub OAuthクライアントシークレット |
## オプション
| 変数 | 説明 |
|----------|-------------|
| `API_ENCRYPTION_KEY` | 保存されたAPIキーを暗号化します32桁の16進数: `openssl rand -hex 32` |
| `COPILOT_API_KEY` | コパイロット機能用のAPIキー |
| `ADMIN_API_KEY` | GitOps操作用の管理者APIキー |
| `RESEND_API_KEY` | 通知用のメールサービス |
| `ALLOWED_LOGIN_DOMAINS` | サインアップをドメインに制限(カンマ区切り) |
| `ALLOWED_LOGIN_EMAILS` | サインアップを特定のメールに制限(カンマ区切り) |
| `DISABLE_REGISTRATION` | 新規ユーザーのサインアップを無効にするには `true` に設定 |
## .envの例
```bash
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=<openssl rand -hex 32>
BETTER_AUTH_URL=https://sim.yourdomain.com
ENCRYPTION_KEY=<openssl rand -hex 32>
INTERNAL_API_SECRET=<openssl rand -hex 32>
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
OPENAI_API_KEY=sk-...
```
すべてのオプションについては `apps/sim/.env.example` を参照してください。

View File

@@ -0,0 +1,50 @@
---
title: セルフホスティング
description: 自社のインフラストラクチャにSim Studioをデプロイ
---
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Callout } from 'fumadocs-ui/components/callout'
DockerまたはKubernetesを使用して、自社のインフラストラクチャにSim Studioをデプロイします。
## 要件
| リソース | 最小 | 推奨 |
|----------|---------|-------------|
| CPU | 2コア | 4+コア |
| RAM | 12 GB | 16+ GB |
| ストレージ | 20 GB SSD | 50+ GB SSD |
| Docker | 20.10+ | 最新版 |
## クイックスタート
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
[http://localhost:3000](http://localhost:3000)を開く
## デプロイオプション
<Cards>
<Card title="Docker" href="/self-hosting/docker">
任意のサーバーでDocker Composeを使用してデプロイ
</Card>
<Card title="Kubernetes" href="/self-hosting/kubernetes">
KubernetesクラスターでHelmを使用してデプロイ
</Card>
<Card title="クラウドプラットフォーム" href="/self-hosting/platforms">
Railway、DigitalOcean、AWS、Azure、GCPのガイド
</Card>
</Cards>
## アーキテクチャ
| コンポーネント | ポート | 説明 |
|-----------|------|-------------|
| simstudio | 3000 | メインアプリケーション |
| realtime | 3002 | WebSocketサーバー |
| db | 5432 | pgvector搭載のPostgreSQL |
| migrations | - | データベースマイグレーション(一度だけ実行) |

View File

@@ -0,0 +1,133 @@
---
title: Kubernetes
description: Helmを使用してSim Studioをデプロイする
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## 前提条件
- Kubernetes 1.19+
- Helm 3.0+
- PVプロビジョナーのサポート
## インストール
```bash
# Clone repo
git clone https://github.com/simstudioai/sim.git && cd sim
# Generate secrets
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
# Install
helm install sim ./helm/sim \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--namespace simstudio --create-namespace
```
## クラウド固有の値
<Tabs items={['AWS EKS', 'Azure AKS', 'GCP GKE']}>
<Tab value="AWS EKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-aws.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="Azure AKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-azure.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="GCP GKE">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-gcp.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
</Tabs>
## 主要な設定
```yaml
# Custom values.yaml
app:
replicaCount: 2
env:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
OPENAI_API_KEY: "sk-..."
postgresql:
persistence:
size: 50Gi
ingress:
enabled: true
className: nginx
tls:
enabled: true
app:
host: sim.yourdomain.com
```
すべてのオプションについては `helm/sim/values.yaml` を参照してください。
## 外部データベース
```yaml
postgresql:
enabled: false
externalDatabase:
enabled: true
host: "your-db-host"
port: 5432
username: "postgres"
password: "your-password"
database: "simstudio"
sslMode: "require"
```
## コマンド
```bash
# Port forward for local access
kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
# View logs
kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
# Upgrade
helm upgrade sim ./helm/sim --namespace simstudio
# Uninstall
helm uninstall sim --namespace simstudio
```

View File

@@ -0,0 +1,124 @@
---
title: クラウドプラットフォーム
description: クラウドプラットフォームにSim Studioをデプロイする
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Railway
ワンクリックデプロイメントで自動的にPostgreSQLをプロビジョニングします。
[
![Railwayにデプロイ](https://railway.app/button.svg)
](https://railway.com/new/template/sim-studio)
デプロイ後、Railwayダッシュボードで環境変数を追加してください
- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET` (自動生成)
- `OPENAI_API_KEY` または他のAIプロバイダーキー
- 設定 → ネットワーキングでカスタムドメイン
## VPSデプロイメント
DigitalOcean、AWS EC2、Azure VMsまたは任意のLinuxサーバー向け
<Tabs items={['DigitalOcean', 'AWS EC2', 'Azure VM']}>
<Tab value="DigitalOcean">
**推奨:** 16 GB RAMドロップレット、Ubuntu 24.04
```bash
# Create Droplet via console, then SSH in
ssh root@your-droplet-ip
```
</Tab>
<Tab value="AWS EC2">
**推奨:** t3.xlarge (16 GB RAM)、Ubuntu 24.04
```bash
ssh -i your-key.pem ubuntu@your-ec2-ip
```
</Tab>
<Tab value="Azure VM">
**推奨:** Standard_D4s_v3 (16 GB RAM)、Ubuntu 24.04
```bash
ssh azureuser@your-vm-ip
```
</Tab>
</Tabs>
### Dockerのインストール
```bash
# Install Docker (official method)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Logout and reconnect, then verify
docker --version
```
### Sim Studioのデプロイ
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
# Create .env with secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
# Start
docker compose -f docker-compose.prod.yml up -d
```
### CaddyによるSSL
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Configure (replace domain)
echo 'sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}' | sudo tee /etc/caddy/Caddyfile
sudo systemctl restart caddy
```
ドメインのDNS AレコードをサーバーのIPアドレスに向けてください。
## Kubernetes (EKS, AKS, GKE)
[Kubernetesガイド](/self-hosting/kubernetes)でマネージドKubernetesへのHelmデプロイメントについて確認してください。
## マネージドデータベース(オプション)
本番環境では、マネージドPostgreSQLサービスを使用してください
- **AWS RDS** / **Azure Database** / **Cloud SQL** - pgvector拡張機能を有効化
- **Supabase** / **Neon** - pgvector搭載済み
環境に`DATABASE_URL`を設定してください:
```bash
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
```

View File

@@ -0,0 +1,113 @@
---
title: トラブルシューティング
description: 一般的な問題と解決策
---
## データベース接続に失敗
```bash
# Check database is running
docker compose ps db
# Test connection
docker compose exec db psql -U postgres -c "SELECT 1"
```
`DATABASE_URL` 形式を確認してください: `postgresql://user:pass@host:5432/database`
## Ollamaモデルが表示されない
Docker内では、`localhost` = ホストマシンではなく、コンテナを指します。
```bash
# For host-machine Ollama, use:
OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
```
## WebSocket/リアルタイム機能が動作しない
1. `NEXT_PUBLIC_SOCKET_URL` がドメインと一致しているか確認する
2. リアルタイムサービスが実行されているか確認する: `docker compose ps realtime`
3. リバースプロキシがWebSocketアップグレードを通過させていることを確認する[Dockerガイド](/self-hosting/docker)を参照)
## 502 Bad Gateway
```bash
# Check app is running
docker compose ps simstudio
docker compose logs simstudio
# Common causes: out of memory, database not ready
```
## マイグレーションエラー
```bash
# View migration logs
docker compose logs migrations
# Run manually
docker compose exec simstudio bun run db:migrate
```
## pgvectorが見つからない
正しいPostgreSQLイメージを使用してください
```yaml
image: pgvector/pgvector:pg17 # NOT postgres:17
```
## 証明書エラーCERT_HAS_EXPIRED
外部APIを呼び出す際にSSL証明書エラーが表示される場合
```bash
# Update CA certificates in container
docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
# Or set in environment (not recommended for production)
NODE_TLS_REJECT_UNAUTHORIZED=0
```
## ログイン後の空白ページ
1. ブラウザコンソールでエラーを確認する
2. `NEXT_PUBLIC_APP_URL` が実際のドメインと一致しているか確認する
3. ブラウザのCookieとローカルストレージをクリアする
4. すべてのサービスが実行されているか確認する: `docker compose ps`
## Windows特有の問題
**WindowsでのTurbopackエラー**
```bash
# Use WSL2 for better compatibility
wsl --install
# Or disable Turbopack in package.json
# Change "next dev --turbopack" to "next dev"
```
**改行の問題:**
```bash
# Configure git to use LF
git config --global core.autocrlf input
```
## ログを表示
```bash
# All services
docker compose logs -f
# Specific service
docker compose logs -f simstudio
```
## ヘルプを得る
- [GitHub Issues](https://github.com/simstudioai/sim/issues)
- [Discord](https://discord.gg/Hr4UWYEcTT)

View File

@@ -0,0 +1,181 @@
---
title: Cursor
description: GitHubリポジトリで作業するためのCursorクラウドエージェントを起動および管理する
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="cursor"
color="#1E1E1E"
/>
{/* MANUAL-CONTENT-START:intro */}
[Cursor](https://www.cursor.so/)はAI IDEおよびクラウドベースのプラットフォームで、GitHubリポジトリで直接作業できる強力なAIエージェントを起動・管理することができます。Cursorエージェントは開発タスクを自動化し、チームの生産性を向上させ、コード変更の実施、自然言語指示への応答、活動に関する会話履歴の維持によってあなたと協力します。
Cursorでは以下のことができます
- **コードベース用のクラウドエージェントを起動**: クラウド上であなたのリポジトリで作業する新しいAIエージェントを即座に作成
- **自然言語を使用してコーディングタスクを委任**: 書面による指示、修正、説明でエージェントを導く
- **進捗と出力を監視**: エージェントのステータスを取得し、詳細な結果を表示し、現在または完了したタスクを検査
- **完全な会話履歴にアクセス**: 透明性と監査可能性のためにすべてのプロンプトとAI応答をレビュー
- **エージェントのライフサイクルを制御・管理**: アクティブなエージェントをリスト表示し、エージェントを終了し、APIベースのエージェント起動とフォローアップを管理
Simでは、Cursor統合によりエージェントとワークフローがCursorクラウドエージェントとプログラム的に対話できるようになります。つまり、Simを使用して以下のことができます
- すべてのクラウドエージェントをリスト表示し、現在の状態を閲覧 (`cursor_list_agents`)
- 任意のエージェントの最新ステータスと出力を取得 (`cursor_get_agent`)
- 任意のコーディングエージェントの完全な会話履歴を表示 (`cursor_get_conversation`)
- 実行中のエージェントにフォローアップ指示や新しいプロンプトを追加
- 必要に応じてエージェントを管理・終了
この統合により、Simエージェントの柔軟なインテリジェンスとCursorの強力な開発自動化機能を組み合わせることができ、プロジェクト全体でAI駆動の開発をスケールすることが可能になります。
{/* MANUAL-CONTENT-END */}
## 使用方法
Cursor Cloud Agents APIを使用して、GitHubリポジトリで作業できるAIエージェントを起動します。エージェントの起動、フォローアップ指示の追加、ステータスの確認、会話の表示、およびエージェントのライフサイクル管理をサポートしています。
## ツール
### `cursor_list_agents`
認証されたユーザーのすべてのクラウドエージェントをオプションのページネーションで一覧表示します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | はい | Cursor APIキー |
| `limit` | number | いいえ | 返すエージェントの数デフォルト20、最大100 |
| `cursor` | string | いいえ | 前の応答からのページネーションカーソル |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | 人間が読めるエージェントのリスト |
| `metadata` | object | エージェントリストのメタデータ |
### `cursor_get_agent`
クラウドエージェントの現在のステータスと結果を取得します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | はい | Cursor APIキー |
| `agentId` | string | はい | クラウドエージェントの一意の識別子bc_abc123 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | 人間が読めるエージェントの詳細 |
| `metadata` | object | エージェントのメタデータ |
### `cursor_get_conversation`
クラウドエージェントの会話履歴(すべてのユーザープロンプトとアシスタントの応答を含む)を取得します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | はい | Cursor APIキー |
| `agentId` | string | はい | クラウドエージェントの一意の識別子bc_abc123 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | 人間が読める会話履歴 |
| `metadata` | object | 会話のメタデータ |
### `cursor_launch_agent`
指定された指示でGitHubリポジトリに取り組む新しいクラウドエージェントを開始します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | はい | Cursor APIキー |
| `repository` | string | はい | GitHubリポジトリURLhttps://github.com/your-org/your-repo |
| `ref` | string | いいえ | 作業するブランチ、タグ、またはコミット(デフォルトはデフォルトブランチ) |
| `promptText` | string | はい | エージェントへの指示テキスト |
| `promptImages` | string | いいえ | base64データと寸法を持つ画像オブジェクトのJSON配列 |
| `model` | string | いいえ | 使用するモデル(自動選択の場合は空のままにする) |
| `branchName` | string | いいえ | エージェントが使用するカスタムブランチ名 |
| `autoCreatePr` | boolean | いいえ | エージェントが終了したときに自動的にPRを作成する |
| `openAsCursorGithubApp` | boolean | いいえ | Cursor GitHub AppとしてPRを開く |
| `skipReviewerRequest` | boolean | いいえ | PRでのレビュアーのリクエストをスキップする |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | エージェントの詳細を含む成功メッセージ |
| `metadata` | object | 起動結果のメタデータ |
### `cursor_add_followup`
既存のクラウドエージェントにフォローアップ指示を追加します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | はい | Cursor APIキー |
| `agentId` | string | はい | クラウドエージェントの一意の識別子bc_abc123 |
| `followupPromptText` | string | はい | エージェントへのフォローアップ指示テキスト |
| `promptImages` | string | いいえ | base64データと寸法を持つ画像オブジェクトのJSON配列最大5つ |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | 成功メッセージ |
| `metadata` | object | 結果メタデータ |
### `cursor_stop_agent`
実行中のクラウドエージェントを停止します。これはエージェントを削除せずに一時停止します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | はい | Cursor APIキー |
| `agentId` | string | はい | クラウドエージェントの一意の識別子bc_abc123 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | 成功メッセージ |
| `metadata` | object | 結果メタデータ |
### `cursor_delete_agent`
クラウドエージェントを完全に削除します。この操作は元に戻せません。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | はい | Cursor APIキー |
| `agentId` | string | はい | クラウドエージェントの一意の識別子bc_abc123 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `content` | string | 成功メッセージ |
| `metadata` | object | 結果メタデータ |
## 注意事項
- カテゴリー: `tools`
- タイプ: `cursor`

View File

@@ -0,0 +1,217 @@
---
title: Google グループ
description: Google Workspace グループとそのメンバーを管理する
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_groups"
color="#E8F0FE"
/>
## 使用方法
Google Workspaceに接続して、Admin SDK Directory APIを使用してグループとそのメンバーを作成、更新、管理します。
## ツール
### `google_groups_list_groups`
Google Workspaceドメイン内のすべてのグループを一覧表示する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `customer` | string | いいえ | 顧客IDまたは認証済みユーザーのドメインの場合は「my_customer」 |
| `domain` | string | いいえ | グループをフィルタリングするドメイン名 |
| `maxResults` | number | いいえ | 返す結果の最大数1-200 |
| `pageToken` | string | いいえ | ページネーション用のトークン |
| `query` | string | いいえ | グループをフィルタリングする検索クエリ「email:admin*」) |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google グループ API レスポンスデータ |
### `google_groups_get_group`
メールアドレスまたはグループIDで特定のGoogle グループの詳細を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google グループ API レスポンスデータ |
### `google_groups_create_group`
ドメイン内に新しいGoogle グループを作成する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `email` | string | はい | 新しいグループのメールアドレスteam@yourdomain.com |
| `name` | string | はい | グループの表示名 |
| `description` | string | いいえ | グループの説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups APIのレスポンスデータ |
### `google_groups_update_group`
既存のGoogleグループを更新する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
| `name` | string | いいえ | グループの新しい表示名 |
| `description` | string | いいえ | グループの新しい説明 |
| `email` | string | いいえ | グループの新しいメールアドレス |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups APIのレスポンスデータ |
### `google_groups_delete_group`
Googleグループを削除する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | 削除するグループのメールアドレスまたは一意のグループID |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups APIのレスポンスデータ |
### `google_groups_list_members`
Google グループのすべてのメンバーを一覧表示する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
| `maxResults` | number | いいえ | 返す結果の最大数1-200 |
| `pageToken` | string | いいえ | ページネーション用のトークン |
| `roles` | string | いいえ | ロールによるフィルタリング(カンマ区切り: OWNER, MANAGER, MEMBER |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups APIのレスポンスデータ |
### `google_groups_get_member`
Google グループ内の特定のメンバーの詳細を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
| `memberKey` | string | はい | メンバーのメールアドレスまたは一意のメンバーID |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups APIのレスポンスデータ |
### `google_groups_add_member`
Google グループに新しいメンバーを追加する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
| `email` | string | はい | 追加するメンバーのメールアドレス |
| `role` | string | いいえ | メンバーのロールMEMBER、MANAGER、またはOWNER。デフォルトはMEMBER。 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups APIレスポンスデータ |
### `google_groups_remove_member`
Google Groupからメンバーを削除する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
| `memberKey` | string | はい | 削除するメンバーのメールアドレスまたは一意のID |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups APIレスポンスデータ |
### `google_groups_update_member`
メンバーを更新する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
| `memberKey` | string | はい | メンバーのメールアドレスまたは一意のメンバーID |
| `role` | string | はい | メンバーの新しい役割 \(MEMBER、MANAGER、またはOWNER\) |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups APIレスポンスデータ |
### `google_groups_has_member`
ユーザーがGoogle Groupのメンバーかどうかを確認する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | はい | グループのメールアドレスまたは一意のグループID |
| `memberKey` | string | はい | 確認するメンバーのメールアドレスまたは一意のメンバーID |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `output` | json | Google グループ API レスポンスデータ |
## 注意事項
- カテゴリー: `tools`
- タイプ: `google_groups`

View File

@@ -135,283 +135,684 @@ Salesforce CRMからアカウントを削除する
### `salesforce_get_contacts`
Salesforceから取引先責任者を取得 - IDが提供されている場合は単一の取引先責任者、そうでない場合はリスト
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `contactId` | string | いいえ | 取引先責任者ID提供されている場合、単一の取引先責任者を返す |
| `limit` | string | いいえ | 結果の数デフォルト100、最大2000。リストクエリの場合のみ。 |
| `fields` | string | いいえ | カンマ区切りのフィールド(例:"Id,FirstName,LastName,Email,Phone" |
| `orderBy` | string | いいえ | 並べ替えるフィールド(例:"LastName ASC")。リストクエリの場合のみ。 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 取引先責任者データ |
### `salesforce_create_contact`
Salesforce CRMに新しい取引先責任者を作成する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `lastName` | string | はい | 姓(必須) |
| `firstName` | string | いいえ | 名 |
| `email` | string | いいえ | メールアドレス |
| `phone` | string | いいえ | 電話番号 |
| `accountId` | string | いいえ | 関連付ける取引先ID |
| `title` | string | いいえ | 説明なし |
| `department` | string | いいえ | 部署 |
| `mailingStreet` | string | いいえ | 郵送先住所 |
| `mailingCity` | string | いいえ | 郵送先市区町村 |
| `mailingState` | string | いいえ | 郵送先都道府県 |
| `mailingPostalCode` | string | いいえ | 郵送先郵便番号 |
| `mailingCountry` | string | いいえ | 郵送先国 |
| `description` | string | いいえ | 取引先責任者の説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 作成された取引先責任者データ |
### `salesforce_update_contact`
Salesforce CRMの既存の取引先責任者を更新する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `contactId` | string | はい | 更新する取引先責任者ID必須 |
| `lastName` | string | いいえ | 姓 |
| `firstName` | string | いいえ | 名 |
| `email` | string | いいえ | メールアドレス |
| `phone` | string | いいえ | 電話番号 |
| `accountId` | string | いいえ | 関連付けるアカウントID |
| `title` | string | いいえ | 説明なし |
| `department` | string | いいえ | 部署 |
| `mailingStreet` | string | いいえ | 郵送先住所 |
| `mailingCity` | string | いいえ | 郵送先市区町村 |
| `mailingState` | string | いいえ | 郵送先都道府県 |
| `mailingPostalCode` | string | いいえ | 郵送先郵便番号 |
| `mailingCountry` | string | いいえ | 郵送先国 |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 更新された取引先責任者データ |
### `salesforce_delete_contact`
Salesforce CRMから取引先責任者を削除する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `contactId` | string | はい | 削除する取引先責任者ID必須 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 削除された取引先責任者データ |
### `salesforce_get_leads`
Salesforceからリードを取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `leadId` | string | いいえ | リードIDオプション |
| `limit` | string | いいえ | 最大結果数デフォルト100 |
| `fields` | string | いいえ | カンマ区切りフィールド |
| `orderBy` | string | いいえ | 並べ替えフィールド |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | リードデータ |
### `salesforce_create_lead`
新しいリードを作成する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `lastName` | string | はい | 姓(必須) |
| `company` | string | はい | 会社名(必須) |
| `firstName` | string | いいえ | 名 |
| `email` | string | いいえ | 説明なし |
| `phone` | string | いいえ | 説明なし |
| `status` | string | いいえ | リードステータス |
| `leadSource` | string | いいえ | リードソース |
| `title` | string | いいえ | 説明なし |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 作成されたリード |
### `salesforce_update_lead`
既存のリードを更新する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `leadId` | string | はい | リードID必須 |
| `lastName` | string | いいえ | 姓 |
| `company` | string | いいえ | 説明なし |
| `firstName` | string | いいえ | 名 |
| `email` | string | いいえ | 説明なし |
| `phone` | string | いいえ | 説明なし |
| `status` | string | いいえ | リードステータス |
| `leadSource` | string | いいえ | リードソース |
| `title` | string | いいえ | 説明なし |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 更新されたリード |
### `salesforce_delete_lead`
リードを削除する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `leadId` | string | はい | リードID必須 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 削除されたリード |
### `salesforce_get_opportunities`
Salesforceから商談を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `opportunityId` | string | いいえ | 商談IDオプション |
| `limit` | string | いいえ | 最大結果数デフォルト100 |
| `fields` | string | いいえ | カンマ区切りのフィールド |
| `orderBy` | string | いいえ | 並べ替えフィールド |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 商談データ |
### `salesforce_create_opportunity`
新しい商談を作成する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `name` | string | はい | 商談名(必須) |
| `stageName` | string | はい | ステージ名(必須) |
| `closeDate` | string | はい | 完了予定日 YYYY-MM-DD必須 |
| `accountId` | string | いいえ | アカウントID |
| `amount` | string | いいえ | 金額(数値) |
| `probability` | string | いいえ | 確度0-100 |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 作成された商談 |
### `salesforce_update_opportunity`
既存の商談を更新する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `opportunityId` | string | はい | 商談ID必須 |
| `name` | string | いいえ | 商談名 |
| `stageName` | string | いいえ | ステージ名 |
| `closeDate` | string | いいえ | 完了予定日 YYYY-MM-DD |
| `accountId` | string | いいえ | アカウントID |
| `amount` | string | いいえ | 説明なし |
| `probability` | string | いいえ | 確度0-100 |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 更新された商談 |
### `salesforce_delete_opportunity`
商談を削除する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `opportunityId` | string | はい | 商談ID必須 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 削除された商談 |
### `salesforce_get_cases`
Salesforceからケースを取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `caseId` | string | いいえ | ケースID任意 |
| `limit` | string | いいえ | 最大結果数デフォルト100 |
| `fields` | string | いいえ | カンマ区切りフィールド |
| `orderBy` | string | いいえ | 並べ替えフィールド |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `success` | boolean | 成功 |
| `output` | object | ケースデータ |
### `salesforce_create_case`
新しいケースを作成する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `subject` | string | はい | ケース件名(必須) |
| `status` | string | いいえ | ステータス(例:新規、対応中、エスカレーション) |
| `priority` | string | いいえ | 優先度(例:低、中、高) |
| `origin` | string | いいえ | 発生源(例:電話、メール、ウェブ) |
| `contactId` | string | いいえ | 取引先責任者ID |
| `accountId` | string | いいえ | 取引先ID |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `success` | boolean | 成功 |
| `output` | object | 作成されたケース |
### `salesforce_update_case`
既存のケースを更新する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `caseId` | string | はい | ケースID必須 |
| `subject` | string | いいえ | ケース件名 |
| `status` | string | いいえ | ステータス |
| `priority` | string | いいえ | 優先度 |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 更新されたケース |
### `salesforce_delete_case`
ケースを削除する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `caseId` | string | はい | ケースID必須 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 削除されたケース |
### `salesforce_get_tasks`
Salesforceからタスクを取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `taskId` | string | いいえ | タスクIDオプション |
| `limit` | string | いいえ | 最大結果数デフォルト100 |
| `fields` | string | いいえ | カンマ区切りのフィールド |
| `orderBy` | string | いいえ | 並べ替えフィールド |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | タスクデータ |
### `salesforce_create_task`
新しいタスクを作成する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `subject` | string | はい | タスク件名(必須) |
| `status` | string | いいえ | ステータス(例:未開始、進行中、完了) |
| `priority` | string | いいえ | 優先度(例:低、普通、高) |
| `activityDate` | string | いいえ | 期日 YYYY-MM-DD |
| `whoId` | string | いいえ | 関連する取引先責任者/リードID |
| `whatId` | string | いいえ | 関連する取引先/商談ID |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | 作成されたタスク |
### `salesforce_update_task`
既存のタスクを更新する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `taskId` | string | はい | タスクID必須 |
| `subject` | string | いいえ | タスク件名 |
| `status` | string | いいえ | ステータス |
| `priority` | string | いいえ | 優先度 |
| `activityDate` | string | いいえ | 期日 YYYY-MM-DD |
| `description` | string | いいえ | 説明 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `success` | boolean | 成功 |
| `output` | object | 更新されたタスク |
### `salesforce_delete_task`
タスクを削除する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `taskId` | string | はい | タスクID必須 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功 |
| `output` | object | 削除されたタスク |
### `salesforce_list_reports`
現在のユーザーがアクセスできるレポートのリストを取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `folderName` | string | いいえ | フォルダ名でフィルタリング |
| `searchTerm` | string | いいえ | 名前でレポートをフィルタリングする検索語 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功ステータス |
| `output` | object | レポートデータ |
### `salesforce_get_report`
特定のレポートのメタデータと説明情報を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `reportId` | string | はい | レポートID必須 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | json | 操作結果データ |
| `output` | object | レポートメタデータ |
### `salesforce_run_report`
レポートを実行して結果を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `reportId` | string | はい | レポートID必須 |
| `includeDetails` | string | いいえ | 詳細行を含めるtrue/false、デフォルトtrue |
| `filters` | string | いいえ | 適用するレポートフィルターのJSON文字列 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | レポート結果 |
### `salesforce_list_report_types`
利用可能なレポートタイプの一覧を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | レポートタイプデータ |
### `salesforce_list_dashboards`
現在のユーザーがアクセスできるダッシュボードの一覧を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `folderName` | string | いいえ | フォルダ名でフィルタリング |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | ダッシュボードデータ |
### `salesforce_get_dashboard`
特定のダッシュボードの詳細と結果を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `dashboardId` | string | はい | ダッシュボードID必須 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | ダッシュボードデータ |
### `salesforce_refresh_dashboard`
最新データを取得するためにダッシュボードを更新する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `dashboardId` | string | はい | ダッシュボードID必須 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | 更新されたダッシュボードデータ |
### `salesforce_query`
Salesforceからデータを取得するためのカスタムSOQLクエリを実行する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `query` | string | はい | 実行するSOQLクエリSELECT Id, Name FROM Account LIMIT 10 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | クエリ結果 |
### `salesforce_query_more`
前回のクエリからnextRecordsUrlを使用して追加のクエリ結果を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `nextRecordsUrl` | string | はい | 前回のクエリレスポンスからのnextRecordsUrl |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | クエリ結果 |
### `salesforce_describe_object`
Salesforceオブジェクトのメタデータとフィールド情報を取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
| `objectName` | string | はい | オブジェクトのAPI名Account、Contact、Lead、Custom_Object__c |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | オブジェクトメタデータ |
### `salesforce_list_objects`
利用可能なすべてのSalesforceオブジェクトのリストを取得する
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | いいえ | 説明なし |
| `instanceUrl` | string | いいえ | 説明なし |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功ステータス |
| `output` | object | オブジェクトリスト |
## 注意事項

View File

@@ -142,8 +142,8 @@ Function (Process) → Condition (account_type === 'enterprise') → Advanced or
## 最佳实践
- **正确排条件**:将更具体的条件放在一般条件之前,以确保特定逻辑优先于回退逻辑
- **包含默认条件**:添加一个兜底条件(`true`)作为最后一个条件,以处理未匹配的情况并防止工作流执行卡住
- **保持表达式简单**:使用清晰、直观的布尔表达式以提高可读性并简化调试
- **记录条件**:添加描述以解释每个条件的目的,从而提高团队协作和维护效率
- **正确排条件顺序**:将更具体的条件放在一般条件之前,以确保特定逻辑优先于回退逻辑
- **在需要时使用 else 分支**:如果没有条件匹配且 else 分支未连接,工作流分支将优雅地结束。如果需要为未匹配的情况提供回退路径,请连接 else 分支
- **保持表达式简单**:使用清晰、直观的布尔表达式以提高可读性和调试的便利性
- **为条件添加文档说明**:添加描述以解释每个条件的目的,从而提高团队协作和维护效率
- **测试边界情况**:通过测试条件范围边界值,验证条件是否正确处理边界值

View File

@@ -72,7 +72,7 @@ Sim 提供了跨多个类别的 80 多种服务的原生集成:
<Video src="introduction/integrations-sidebar.mp4" width={700} height={450} />
</div>
## AI 驱动的 Copilot
## Copilot
**提问并获取指导**
Copilot 回答关于 Sim 的问题,解释您的工作流程,并提供改进建议。使用 `@` 符号引用工作流程、模块、文档、知识和日志,以获得上下文帮助。

View File

@@ -1,5 +1,6 @@
---
title: 知识库
title: 概览
description: 通过智能向量搜索和分块功能上传、处理并搜索您的文档
---
import { Video } from '@/components/ui/video'

View File

@@ -0,0 +1,155 @@
---
title: Docker
description: 使用 Docker Compose 部署 Sim Studio
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## 快速开始
```bash
# Clone and start
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
打开 [http://localhost:3000](http://localhost:3000)
## 生产环境设置
### 1. 配置环境
```bash
# Generate secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
```
### 2. 启动服务
```bash
docker compose -f docker-compose.prod.yml up -d
```
### 3. 设置 SSL
<Tabs items={['Caddy (推荐)', 'Nginx + Certbot']}>
<Tab value="Caddy (推荐)">
Caddy 会自动处理 SSL 证书。
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
```
创建 `/etc/caddy/Caddyfile`
```
sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}
```
```bash
sudo systemctl restart caddy
```
</Tab>
<Tab value="Nginx + Certbot">
```bash
# Install
sudo apt install nginx certbot python3-certbot-nginx -y
# Create /etc/nginx/sites-available/sim
server {
listen 80;
server_name sim.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://127.0.0.1:3002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Enable and get certificate
sudo ln -s /etc/nginx/sites-available/sim /etc/nginx/sites-enabled/
sudo certbot --nginx -d sim.yourdomain.com
```
</Tab>
</Tabs>
## Ollama
```bash
# With GPU
docker compose -f docker-compose.ollama.yml --profile gpu --profile setup up -d
# CPU only
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
```
拉取其他模型:
```bash
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.2
```
### 外部 Ollama
如果 Ollama 在您的主机上运行(而不是在 Docker 中):
```bash
# macOS/Windows
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
# Linux - use your host IP
OLLAMA_URL=http://192.168.1.100:11434 docker compose -f docker-compose.prod.yml up -d
```
<Callout type="warning">
在 Docker 内,`localhost` 指的是容器,而不是您的主机。请使用 `host.docker.internal` 或您的主机 IP。
</Callout>
## 命令
```bash
# View logs
docker compose -f docker-compose.prod.yml logs -f simstudio
# Stop
docker compose -f docker-compose.prod.yml down
# Update
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
# Backup database
docker compose -f docker-compose.prod.yml exec db pg_dump -U postgres simstudio > backup.sql
```

View File

@@ -0,0 +1,87 @@
---
title: 环境变量
description: Sim Studio 的配置参考
---
import { Callout } from 'fumadocs-ui/components/callout'
## 必需
| 变量 | 描述 |
|----------|-------------|
| `DATABASE_URL` | PostgreSQL 连接字符串 |
| `BETTER_AUTH_SECRET` | 认证密钥32 个十六进制字符):`openssl rand -hex 32` |
| `BETTER_AUTH_URL` | 您的应用程序 URL |
| `ENCRYPTION_KEY` | 加密密钥32 个十六进制字符):`openssl rand -hex 32` |
| `INTERNAL_API_SECRET` | 内部 API 密钥32 个十六进制字符):`openssl rand -hex 32` |
| `NEXT_PUBLIC_APP_URL` | 公共应用程序 URL |
| `NEXT_PUBLIC_SOCKET_URL` | WebSocket URL默认值`http://localhost:3002` |
## AI 提供商
| 变量 | 提供商 |
|----------|----------|
| `OPENAI_API_KEY` | OpenAI |
| `ANTHROPIC_API_KEY_1` | Anthropic Claude |
| `GEMINI_API_KEY_1` | Google Gemini |
| `MISTRAL_API_KEY` | Mistral |
| `OLLAMA_URL` | Ollama默认值`http://localhost:11434` |
<Callout type="info">
为了负载均衡,请添加带有 `_1`、`_2`、`_3` 后缀的多个密钥(例如,`OPENAI_API_KEY_1`、`OPENAI_API_KEY_2`)。适用于 OpenAI、Anthropic 和 Gemini。
</Callout>
<Callout type="info">
在 Docker 中,使用 `OLLAMA_URL=http://host.docker.internal:11434` 作为主机机器的 Ollama。
</Callout>
### Azure OpenAI
| 变量 | 描述 |
|----------|-------------|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API 密钥 |
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI 端点 URL |
| `AZURE_OPENAI_API_VERSION` | API 版本(例如,`2024-02-15-preview` |
### vLLM自托管
| 变量 | 描述 |
|----------|-------------|
| `VLLM_BASE_URL` | vLLM 服务器 URL例如`http://localhost:8000/v1` |
| `VLLM_API_KEY` | vLLM 的可选 Bearer Token |
## OAuth 提供商
| 变量 | 描述 |
|----------|-------------|
| `GOOGLE_CLIENT_ID` | Google OAuth 客户端 ID |
| `GOOGLE_CLIENT_SECRET` | Google OAuth 客户端密钥 |
| `GITHUB_CLIENT_ID` | GitHub OAuth 客户端 ID |
| `GITHUB_CLIENT_SECRET` | GitHub OAuth 客户端密钥 |
## 可选
| 变量 | 描述 |
|----------|-------------|
| `API_ENCRYPTION_KEY` | 加密存储的 API 密钥32 个十六进制字符):`openssl rand -hex 32` |
| `COPILOT_API_KEY` | 用于 copilot 功能的 API 密钥 |
| `ADMIN_API_KEY` | 用于 GitOps 操作的管理员 API 密钥 |
| `RESEND_API_KEY` | 用于通知的电子邮件服务 |
| `ALLOWED_LOGIN_DOMAINS` | 限制注册到特定域(逗号分隔) |
| `ALLOWED_LOGIN_EMAILS` | 限制注册到特定电子邮件(逗号分隔) |
| `DISABLE_REGISTRATION` | 设置为 `true` 以禁用新用户注册 |
## 示例 .env
```bash
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=<openssl rand -hex 32>
BETTER_AUTH_URL=https://sim.yourdomain.com
ENCRYPTION_KEY=<openssl rand -hex 32>
INTERNAL_API_SECRET=<openssl rand -hex 32>
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
OPENAI_API_KEY=sk-...
```
查看 `apps/sim/.env.example` 以获取所有选项。

View File

@@ -0,0 +1,50 @@
---
title: 自托管
description: 在您自己的基础设施上部署 Sim Studio
---
import { Card, Cards } from 'fumadocs-ui/components/card'
import { Callout } from 'fumadocs-ui/components/callout'
使用 Docker 或 Kubernetes 在您自己的基础设施上部署 Sim Studio。
## 要求
| 资源 | 最低要求 | 推荐配置 |
|----------|---------|-------------|
| CPU | 2 核 | 4 核及以上 |
| 内存 | 12 GB | 16 GB 及以上 |
| 存储 | 20 GB SSD | 50 GB 及以上 SSD |
| Docker | 20.10+ | 最新版本 |
## 快速开始
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
```
打开 [http://localhost:3000](http://localhost:3000)
## 部署选项
<Cards>
<Card title="Docker" href="/self-hosting/docker">
使用 Docker Compose 在任何服务器上部署
</Card>
<Card title="Kubernetes" href="/self-hosting/kubernetes">
使用 Helm 在 Kubernetes 集群上部署
</Card>
<Card title="Cloud Platforms" href="/self-hosting/platforms">
Railway、DigitalOcean、AWS、Azure、GCP 指南
</Card>
</Cards>
## 架构
| 组件 | 端口 | 描述 |
|-----------|------|-------------|
| simstudio | 3000 | 主应用程序 |
| realtime | 3002 | WebSocket 服务器 |
| db | 5432 | 带有 pgvector 的 PostgreSQL |
| migrations | - | 数据库迁移(运行一次) |

View File

@@ -0,0 +1,133 @@
---
title: Kubernetes
description: 使用 Helm 部署 Sim Studio
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## 前置条件
- Kubernetes 1.19+
- Helm 3.0+
- 支持 PV 提供程序
## 安装
```bash
# Clone repo
git clone https://github.com/simstudioai/sim.git && cd sim
# Generate secrets
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
# Install
helm install sim ./helm/sim \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--namespace simstudio --create-namespace
```
## 云特定值
<Tabs items={['AWS EKS', 'Azure AKS', 'GCP GKE']}>
<Tab value="AWS EKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-aws.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="Azure AKS">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-azure.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
<Tab value="GCP GKE">
```bash
helm install sim ./helm/sim \
--values ./helm/sim/examples/values-gcp.yaml \
--set app.env.BETTER_AUTH_SECRET="$BETTER_AUTH_SECRET" \
--set app.env.ENCRYPTION_KEY="$ENCRYPTION_KEY" \
--set app.env.INTERNAL_API_SECRET="$INTERNAL_API_SECRET" \
--set app.env.NEXT_PUBLIC_APP_URL="https://sim.yourdomain.com" \
--namespace simstudio --create-namespace
```
</Tab>
</Tabs>
## 关键配置
```yaml
# Custom values.yaml
app:
replicaCount: 2
env:
NEXT_PUBLIC_APP_URL: "https://sim.yourdomain.com"
OPENAI_API_KEY: "sk-..."
postgresql:
persistence:
size: 50Gi
ingress:
enabled: true
className: nginx
tls:
enabled: true
app:
host: sim.yourdomain.com
```
查看 `helm/sim/values.yaml` 了解所有选项。
## 外部数据库
```yaml
postgresql:
enabled: false
externalDatabase:
enabled: true
host: "your-db-host"
port: 5432
username: "postgres"
password: "your-password"
database: "simstudio"
sslMode: "require"
```
## 命令
```bash
# Port forward for local access
kubectl port-forward deployment/sim-sim-app 3000:3000 -n simstudio
# View logs
kubectl logs -l app.kubernetes.io/component=app -n simstudio --tail=100
# Upgrade
helm upgrade sim ./helm/sim --namespace simstudio
# Uninstall
helm uninstall sim --namespace simstudio
```

View File

@@ -0,0 +1,124 @@
---
title: 云平台
description: 在云平台上部署 Sim Studio
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Callout } from 'fumadocs-ui/components/callout'
## Railway
一键部署并自动配置 PostgreSQL。
[
![在 Railway 上部署](https://railway.app/button.svg)
](https://railway.com/new/template/sim-studio)
部署后,在 Railway 仪表板中添加环境变量:
- `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, `INTERNAL_API_SECRET`(自动生成)
- `OPENAI_API_KEY` 或其他 AI 提供商密钥
- 自定义域名:设置 → 网络
## VPS 部署
适用于 DigitalOcean、AWS EC2、Azure VMs 或任何 Linux 服务器:
<Tabs items={['DigitalOcean', 'AWS EC2', 'Azure VM']}>
<Tab value="DigitalOcean">
**推荐配置:** 16 GB RAM DropletUbuntu 24.04
```bash
# Create Droplet via console, then SSH in
ssh root@your-droplet-ip
```
</Tab>
<Tab value="AWS EC2">
**推荐配置:** t3.xlarge16 GB RAMUbuntu 24.04
```bash
ssh -i your-key.pem ubuntu@your-ec2-ip
```
</Tab>
<Tab value="Azure VM">
**推荐配置:** Standard_D4s_v316 GB RAMUbuntu 24.04
```bash
ssh azureuser@your-vm-ip
```
</Tab>
</Tabs>
### 安装 Docker
```bash
# Install Docker (official method)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# Logout and reconnect, then verify
docker --version
```
### 部署 Sim Studio
```bash
git clone https://github.com/simstudioai/sim.git && cd sim
# Create .env with secrets
cat > .env << EOF
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
BETTER_AUTH_SECRET=$(openssl rand -hex 32)
ENCRYPTION_KEY=$(openssl rand -hex 32)
INTERNAL_API_SECRET=$(openssl rand -hex 32)
NEXT_PUBLIC_APP_URL=https://sim.yourdomain.com
BETTER_AUTH_URL=https://sim.yourdomain.com
NEXT_PUBLIC_SOCKET_URL=https://sim.yourdomain.com
EOF
# Start
docker compose -f docker-compose.prod.yml up -d
```
### 使用 Caddy 配置 SSL
```bash
# Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Configure (replace domain)
echo 'sim.yourdomain.com {
reverse_proxy localhost:3000
handle /socket.io/* {
reverse_proxy localhost:3002
}
}' | sudo tee /etc/caddy/Caddyfile
sudo systemctl restart caddy
```
将您的域名的 DNS A 记录指向您的服务器 IP。
## KubernetesEKS、AKS、GKE
有关在托管 Kubernetes 上使用 Helm 部署的详细信息,请参阅 [Kubernetes 指南](/self-hosting/kubernetes)。
## 托管数据库(可选)
在生产环境中,请使用托管的 PostgreSQL 服务:
- **AWS RDS** / **Azure Database** / **Cloud SQL** - 启用 pgvector 扩展
- **Supabase** / **Neon** - 已包含 pgvector
在您的环境中设置 `DATABASE_URL`
```bash
DATABASE_URL="postgresql://user:pass@host:5432/db?sslmode=require"
```

View File

@@ -0,0 +1,113 @@
---
title: 故障排除
description: 常见问题及解决方案
---
## 数据库连接失败
```bash
# Check database is running
docker compose ps db
# Test connection
docker compose exec db psql -U postgres -c "SELECT 1"
```
验证 `DATABASE_URL` 格式:`postgresql://user:pass@host:5432/database`
## Ollama 模型未显示
在 Docker 中,`localhost` = 容器,而不是您的主机。
```bash
# For host-machine Ollama, use:
OLLAMA_URL=http://host.docker.internal:11434 # macOS/Windows
OLLAMA_URL=http://192.168.1.x:11434 # Linux (use actual IP)
```
## WebSocket/实时功能无法正常工作
1. 检查 `NEXT_PUBLIC_SOCKET_URL` 是否与您的域名匹配
2. 验证实时服务是否正在运行:`docker compose ps realtime`
3. 确保反向代理支持 WebSocket 升级(参见 [Docker 指南](/self-hosting/docker)
## 502 错误网关
```bash
# Check app is running
docker compose ps simstudio
docker compose logs simstudio
# Common causes: out of memory, database not ready
```
## 迁移错误
```bash
# View migration logs
docker compose logs migrations
# Run manually
docker compose exec simstudio bun run db:migrate
```
## 找不到 pgvector
使用正确的 PostgreSQL 镜像:
```yaml
image: pgvector/pgvector:pg17 # NOT postgres:17
```
## 证书错误 (CERT_HAS_EXPIRED)
如果调用外部 API 时出现 SSL 证书错误:
```bash
# Update CA certificates in container
docker compose exec simstudio apt-get update && apt-get install -y ca-certificates
# Or set in environment (not recommended for production)
NODE_TLS_REJECT_UNAUTHORIZED=0
```
## 登录后出现空白页面
1. 检查浏览器控制台是否有错误
2. 验证 `NEXT_PUBLIC_APP_URL` 是否与您的实际域名匹配
3. 清除浏览器的 Cookie 和本地存储
4. 检查所有服务是否正在运行:`docker compose ps`
## Windows 特定问题
**Windows 上的 Turbopack 错误:**
```bash
# Use WSL2 for better compatibility
wsl --install
# Or disable Turbopack in package.json
# Change "next dev --turbopack" to "next dev"
```
**行尾问题:**
```bash
# Configure git to use LF
git config --global core.autocrlf input
```
## 查看日志
```bash
# All services
docker compose logs -f
# Specific service
docker compose logs -f simstudio
```
## 获取帮助
- [GitHub Issues](https://github.com/simstudioai/sim/issues)
- [Discord](https://discord.gg/Hr4UWYEcTT)

View File

@@ -0,0 +1,181 @@
---
title: Cursor
description: 启动并管理 Cursor 云代理以处理 GitHub 仓库
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="cursor"
color="#1E1E1E"
/>
{/* MANUAL-CONTENT-START:intro */}
[Cursor](https://www.cursor.so/) 是一个 AI 集成开发环境IDE和基于云的平台允许您启动和管理能够直接在您的 GitHub 仓库上工作的强大 AI 代理。Cursor 代理可以自动化开发任务,提高团队的生产力,并通过进行代码更改、响应自然语言指令以及维护其活动的对话历史与您协作。
使用 Cursor您可以
- **为代码库启动云代理**:即时创建在云端处理您仓库的 AI 代理
- **使用自然语言委派编码任务**:通过书面指令、修改和澄清来指导代理
- **监控进度和输出**:获取代理状态,查看详细结果,并检查当前或已完成的任务
- **访问完整的对话历史**:审查所有提示和 AI 响应,以确保透明性和可审计性
- **控制和管理代理生命周期**:列出活动代理,终止代理,并管理基于 API 的代理启动和后续操作
在 Sim 中Cursor 集成使您的代理和工作流能够以编程方式与 Cursor 云代理交互。这意味着您可以使用 Sim 来:
- 列出所有云代理并浏览其当前状态(`cursor_list_agents`
- 获取任何代理的最新状态和输出(`cursor_get_agent`
- 查看任何编码代理的完整对话历史(`cursor_get_conversation`
- 为正在运行的代理添加后续指令或新提示
- 根据需要管理和终止代理
此集成帮助您将 Sim 代理的灵活智能与 Cursor 的强大开发自动化功能相结合,使您能够在项目中扩展 AI 驱动的开发。
{/* MANUAL-CONTENT-END */}
## 使用说明
与 Cursor 云代理 API 交互,启动可以在您的 GitHub 仓库上工作的 AI 代理。支持启动代理、添加后续指令、检查状态、查看对话以及管理代理生命周期。
## 工具
### `cursor_list_agents`
列出经过身份验证的用户的所有云代理,并支持可选的分页功能。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | 是 | Cursor API 密钥 |
| `limit` | number | 否 | 要返回的代理数量 \(默认值20最大值100\) |
| `cursor` | string | 否 | 上一个响应的分页游标 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 可读的代理列表 |
| `metadata` | object | 代理列表元数据 |
### `cursor_get_agent`
检索云代理的当前状态和结果。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | 是 | Cursor API 密钥 |
| `agentId` | string | 是 | 云代理的唯一标识符 \(例如bc_abc123\) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 可读的代理详细信息 |
| `metadata` | object | 代理元数据 |
### `cursor_get_conversation`
检索云代理的对话历史,包括所有用户提示和助手响应。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | 是 | Cursor API 密钥 |
| `agentId` | string | 是 | 云代理的唯一标识符 \(例如bc_abc123\) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 可读的对话历史 |
| `metadata` | object | 对话元数据 |
### `cursor_launch_agent`
启动一个新的云代理,根据给定的指令处理 GitHub 仓库。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | 是 | Cursor API 密钥 |
| `repository` | string | 是 | GitHub 仓库 URL \(例如https://github.com/your-org/your-repo\) |
| `ref` | string | 否 | 要处理的分支、标签或提交 \(默认为默认分支\) |
| `promptText` | string | 是 | 代理的指令文本 |
| `promptImages` | string | 否 | 包含 base64 数据和尺寸的图像对象的 JSON 数组 |
| `model` | string | 否 | 要使用的模型 \(留空以自动选择\) |
| `branchName` | string | 否 | 代理使用的自定义分支名称 |
| `autoCreatePr` | boolean | 否 | 当代理完成时自动创建 PR |
| `openAsCursorGithubApp` | boolean | 否 | 以 Cursor GitHub App 的身份打开 PR |
| `skipReviewerRequest` | boolean | 否 | 跳过在 PR 上请求审阅者 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 包含代理详细信息的成功消息 |
| `metadata` | object | 启动结果的元数据 |
### `cursor_add_followup`
为现有的云代理添加后续指令。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | 是 | Cursor API 密钥 |
| `agentId` | string | 是 | 云代理的唯一标识符 \(例如bc_abc123\) |
| `followupPromptText` | string | 是 | 代理的后续指令文本 |
| `promptImages` | string | 否 | 包含 base64 数据和尺寸的图像对象的 JSON 数组 \(最多 5 个\) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 成功消息 |
| `metadata` | object | 结果元数据 |
### `cursor_stop_agent`
停止运行中的云代理。这将暂停代理,但不会删除它。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | 是 | Cursor API 密钥 |
| `agentId` | string | 是 | 云代理的唯一标识符 \(例如bc_abc123\) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 成功消息 |
| `metadata` | object | 结果元数据 |
### `cursor_delete_agent`
永久删除云代理。此操作无法撤销。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | 是 | Cursor API 密钥 |
| `agentId` | string | 是 | 云代理的唯一标识符 \(例如bc_abc123\) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `content` | string | 成功消息 |
| `metadata` | object | 结果元数据 |
## 注意事项
- 类别:`tools`
- 类型:`cursor`

View File

@@ -0,0 +1,217 @@
---
title: Google 群组
description: 管理 Google Workspace 群组及其成员
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="google_groups"
color="#E8F0FE"
/>
## 使用说明
连接到 Google Workspace使用 Admin SDK Directory API 创建、更新和管理群组及其成员。
## 工具
### `google_groups_list_groups`
列出 Google Workspace 域中的所有群组
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `customer` | string | 否 | 客户 ID 或 "my_customer" 表示已认证用户的域 |
| `domain` | string | 否 | 用于筛选群组的域名 |
| `maxResults` | number | 否 | 返回的最大结果数 \(1-200\) |
| `pageToken` | string | 否 | 分页的令牌 |
| `query` | string | 否 | 用于筛选群组的搜索查询 \(例如:"email:admin*"\) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_get_group`
通过电子邮件或群组 ID 获取特定 Google 群组的详细信息
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 群组电子邮件地址或唯一群组 ID |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_create_group`
在域中创建一个新的 Google 群组
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `email` | string | 是 | 新组的电子邮件地址 \(例如team@yourdomain.com\) |
| `name` | string | 是 | 组的显示名称 |
| `description` | string | 否 | 组的描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_update_group`
更新现有的 Google 群组
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
| `name` | string | 否 | 群组的新显示名称 |
| `description` | string | 否 | 群组的新描述 |
| `email` | string | 否 | 群组的新电子邮件地址 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_delete_group`
删除 Google 群组
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 要删除的群组电子邮件地址或唯一群组 ID |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_list_members`
列出 Google 群组的所有成员
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
| `maxResults` | number | 否 | 返回的最大结果数 \(1-200\) |
| `pageToken` | string | 否 | 分页的令牌 |
| `roles` | string | 否 | 按角色筛选 \(逗号分隔OWNER, MANAGER, MEMBER\) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_get_member`
获取 Google 群组中特定成员的详细信息
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
| `memberKey` | string | 是 | 成员的电子邮件地址或唯一成员 ID |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_add_member`
向 Google 群组添加新成员
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
| `email` | string | 是 | 要添加成员的电子邮件地址 |
| `role` | string | 否 | 成员的角色 \(MEMBER, MANAGER, 或 OWNER\)。默认为 MEMBER。 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_remove_member`
从 Google 群组中移除成员
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
| `memberKey` | string | 是 | 要移除的成员的电子邮件地址或唯一 ID |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_update_member`
更新成员信息
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
| `memberKey` | string | 是 | 成员的电子邮件地址或唯一成员 ID |
| `role` | string | 是 | 成员的新角色 \(MEMBER, MANAGER, 或 OWNER\) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
### `google_groups_has_member`
检查用户是否为 Google 群组的成员
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `groupKey` | string | 是 | 群组的电子邮件地址或唯一群组 ID |
| `memberKey` | string | 是 | 要检查的成员的电子邮件地址或唯一成员 ID |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `output` | json | Google Groups API 响应数据 |
## 注意事项
- 类别:`tools`
- 类型:`google_groups`

View File

@@ -135,285 +135,686 @@ Salesforce 工具非常适合需要简化销售、账户管理、潜在客户生
### `salesforce_get_contacts`
从 Salesforce 获取联系人 - 如果提供了 ID则返回单个联系人如果未提供则返回列表
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `contactId` | string | 否 | 联系人 ID如果提供则返回单个联系人 |
| `limit` | string | 否 | 返回结果数量默认100最大2000。仅适用于列表查询。 |
| `fields` | string | 否 | 逗号分隔的字段(例如:"Id,FirstName,LastName,Email,Phone" |
| `orderBy` | string | 否 | 排序字段(例如:"LastName ASC")。仅适用于列表查询。 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `output` | object | 联系人数据 |
### `salesforce_create_contact`
在 Salesforce CRM 中创建新联系人
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `lastName` | string | 是 | 姓(必需) |
| `firstName` | string | 否 | 名 |
| `email` | string | 否 | 电子邮件地址 |
| `phone` | string | 否 | 电话号码 |
| `accountId` | string | 否 | 要关联的账户 ID |
| `title` | string | 否 | 无描述 |
| `department` | string | 否 | 部门 |
| `mailingStreet` | string | 否 | 邮寄街道 |
| `mailingCity` | string | 否 | 邮寄城市 |
| `mailingState` | string | 否 | 邮寄州 |
| `mailingPostalCode` | string | 否 | 邮寄邮政编码 |
| `mailingCountry` | string | 否 | 邮寄国家 |
| `description` | string | 否 | 联系人描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `output` | object | 创建的联系人数据 |
### `salesforce_update_contact`
更新 Salesforce CRM 中的现有联系人
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `contactId` | string | 是 | 要更新的联系人 ID必需 |
| `lastName` | string | 否 | 姓 |
| `firstName` | string | 否 | 名 |
| `email` | string | 否 | 邮箱地址 |
| `phone` | string | 否 | 电话号码 |
| `accountId` | string | 否 | 要关联的账户 ID |
| `title` | string | 否 | 无描述 |
| `department` | string | 否 | 部门 |
| `mailingStreet` | string | 否 | 邮寄街道 |
| `mailingCity` | string | 否 | 邮寄城市 |
| `mailingState` | string | 否 | 邮寄州 |
| `mailingPostalCode` | string | 否 | 邮寄邮政编码 |
| `mailingCountry` | string | 否 | 邮寄国家 |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `output` | object | 更新的联系人数据 |
### `salesforce_delete_contact`
从 Salesforce CRM 中删除联系人
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `contactId` | string | 是 | 要删除的联系人 ID必需 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `output` | object | 已删除的联系人数据 |
### `salesforce_get_leads`
从 Salesforce 获取潜在客户
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `leadId` | string | 否 | 潜在客户 ID可选 |
| `limit` | string | 否 | 最大结果数默认100 |
| `fields` | string | 否 | 逗号分隔的字段 |
| `orderBy` | string | 否 | 排序字段 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功状态 |
| `output` | object | 潜在客户数据 |
### `salesforce_create_lead`
创建新潜在客户
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `lastName` | string | 是 | 姓(必需) |
| `company` | string | 是 | 公司(必需) |
| `firstName` | string | 否 | 名 |
| `email` | string | 否 | 无描述 |
| `phone` | string | 否 | 无描述 |
| `status` | string | 否 | 潜在客户状态 |
| `leadSource` | string | 否 | 潜在客户来源 |
| `title` | string | 否 | 无描述 |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 创建的潜在客户 |
### `salesforce_update_lead`
更新现有的潜在客户
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `leadId` | string | 是 | 潜在客户 ID必需 |
| `lastName` | string | 否 | 姓氏 |
| `company` | string | 否 | 无描述 |
| `firstName` | string | 否 | 名字 |
| `email` | string | 否 | 无描述 |
| `phone` | string | 否 | 无描述 |
| `status` | string | 否 | 潜在客户状态 |
| `leadSource` | string | 否 | 潜在客户来源 |
| `title` | string | 否 | 无描述 |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 更新的潜在客户 |
### `salesforce_delete_lead`
删除潜在客户
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `leadId` | string | 是 | 潜在客户 ID必需 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 已删除的潜在客户 |
### `salesforce_get_opportunities`
从 Salesforce 获取机会
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `opportunityId` | string | 否 | 机会 ID可选 |
| `limit` | string | 否 | 最大结果数默认100 |
| `fields` | string | 否 | 逗号分隔的字段 |
| `orderBy` | string | 否 | 排序字段 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 机会数据 |
### `salesforce_create_opportunity`
创建新机会
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `name` | string | 是 | 机会名称(必需) |
| `stageName` | string | 是 | 阶段名称(必需) |
| `closeDate` | string | 是 | 关闭日期 YYYY-MM-DD必需 |
| `accountId` | string | 否 | 账户 ID |
| `amount` | string | 否 | 金额(数字) |
| `probability` | string | 否 | 概率0-100 |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 创建的机会 |
### `salesforce_update_opportunity`
更新现有的机会
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `opportunityId` | string | 是 | 机会 ID (必需) |
| `name` | string | 否 | 机会名称 |
| `stageName` | string | 否 | 阶段名称 |
| `closeDate` | string | 否 | 关闭日期 YYYY-MM-DD |
| `accountId` | string | 否 | 账户 ID |
| `amount` | string | 否 | 无描述 |
| `probability` | string | 否 | 概率0-100 |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 更新的机会 |
### `salesforce_delete_opportunity`
删除一个机会
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `opportunityId` | string | 是 | 机会 ID (必需) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 删除的机会 |
### `salesforce_get_cases`
从 Salesforce 获取案例
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `caseId` | string | 否 | 案例 ID可选 |
| `limit` | string | 否 | 最大结果数默认100 |
| `fields` | string | 否 | 逗号分隔的字段 |
| `orderBy` | string | 否 | 排序字段 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 案例数据 |
### `salesforce_create_case`
创建新案例
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `subject` | string | 是 | 案例主题(必需) |
| `status` | string | 否 | 状态(例如:新建、处理中、已升级) |
| `priority` | string | 否 | 优先级(例如:低、中、高) |
| `origin` | string | 否 | 来源(例如:电话、电子邮件、网页) |
| `contactId` | string | 否 | 联系人 ID |
| `accountId` | string | 否 | 账户 ID |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 创建的案例 |
### `salesforce_update_case`
更新现有案例
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `caseId` | string | 是 | 案例 ID必需 |
| `subject` | string | 否 | 案例主题 |
| `status` | string | 否 | 状态 |
| `priority` | string | 否 | 优先级 |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 更新的案例 |
### `salesforce_delete_case`
删除案例
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `caseId` | string | 是 | 案例 ID必需 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 已删除的案例 |
### `salesforce_get_tasks`
从 Salesforce 获取任务
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `taskId` | string | 否 | 任务 ID可选 |
| `limit` | string | 否 | 最大结果数默认100 |
| `fields` | string | 否 | 逗号分隔的字段 |
| `orderBy` | string | 否 | 按字段排序 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 任务数据 |
### `salesforce_create_task`
创建新任务
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `subject` | string | 是 | 任务主题(必需) |
| `status` | string | 否 | 状态(例如:未开始、进行中、已完成) |
| `priority` | string | 否 | 优先级(例如:低、普通、高) |
| `activityDate` | string | 否 | 截止日期 YYYY-MM-DD |
| `whoId` | string | 否 | 相关联系人/潜在客户 ID |
| `whatId` | string | 否 | 相关账户/机会 ID |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 已创建的任务 |
### `salesforce_update_task`
更新现有任务
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `taskId` | string | 是 | 任务 ID必需 |
| `subject` | string | 否 | 任务主题 |
| `status` | string | 否 | 状态 |
| `priority` | string | 否 | 优先级 |
| `activityDate` | string | 否 | 截止日期 YYYY-MM-DD |
| `description` | string | 否 | 描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 更新的任务 |
### `salesforce_delete_task`
删除任务
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `taskId` | string | 是 | 任务 ID必需 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 操作成功状态 |
| `output` | json | 操作结果数据 |
| `success` | boolean | 成功 |
| `output` | object | 已删除的任务 |
### `salesforce_list_reports`
获取当前用户可访问的报告列表
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `folderName` | string | 否 | 按文件夹名称筛选 |
| `searchTerm` | string | 否 | 按名称筛选报告的搜索词 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 报告数据 |
### `salesforce_get_report`
获取特定报告的元数据和描述信息
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `reportId` | string | 是 | 报告 ID必需 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 报告元数据 |
### `salesforce_run_report`
执行报告并检索结果
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `reportId` | string | 是 | 报告 ID必需 |
| `includeDetails` | string | 否 | 包含详细行true/false默认值true |
| `filters` | string | 否 | 要应用的报告过滤器的 JSON 字符串 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 报告结果 |
### `salesforce_list_report_types`
获取可用报告类型的列表
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 报告类型数据 |
### `salesforce_list_dashboards`
获取当前用户可访问的仪表板列表
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `folderName` | string | 否 | 按文件夹名称过滤 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 仪表板数据 |
### `salesforce_get_dashboard`
获取特定仪表板的详细信息和结果
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `dashboardId` | string | 是 | 仪表板 ID (必需) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 仪表板数据 |
### `salesforce_refresh_dashboard`
刷新仪表板以获取最新数据
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `dashboardId` | string | 是 | 仪表板 ID (必需) |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 刷新后的仪表板数据 |
### `salesforce_query`
执行自定义 SOQL 查询以从 Salesforce 检索数据
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `query` | string | 是 | 要执行的 SOQL 查询例如SELECT Id, Name FROM Account LIMIT 10 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 查询结果 |
### `salesforce_query_more`
使用上一次查询的 nextRecordsUrl 检索更多查询结果
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `nextRecordsUrl` | string | 是 | 上一次查询响应中的 nextRecordsUrl |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 查询结果 |
### `salesforce_describe_object`
获取 Salesforce 对象的元数据和字段信息
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
| `objectName` | string | 是 | 对象的 API 名称例如Account、Contact、Lead、Custom_Object__c |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 对象元数据 |
### `salesforce_list_objects`
获取所有可用的 Salesforce 对象列表
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `idToken` | string | 否 | 无描述 |
| `instanceUrl` | string | 否 | 无描述 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 成功状态 |
| `output` | object | 对象列表 |
## 注意事项
- 类别: `tools`
- 类型: `salesforce`
- 类别`tools`
- 类型`salesforce`

View File

@@ -4382,7 +4382,8 @@ checksums:
content/26: 2a887132ff0d5bbf448d743b7a25a7b3
content/27: eca2e960c67c9918ce37cf65abe1f114
3a760527a9e1a7b3b1301cbc07f3e738:
meta/title: ae1419e3ad32c5504122516c7e7b9bf0
meta/title: 30c54e4dc4ce599b87d94be34a8617f5
meta/description: c82dc86de28f9579a59fca5632654382
content/0: 5bd8bded22a12230b63ee51c0a9b9bd5
content/1: 2cc3c41321402d1c2c615d2efbe5d567
content/2: f6ba7ccd55a61b252bbff3391eba64cd
@@ -4437,7 +4438,7 @@ checksums:
content/17: d869967bc0a87215c81a91aaaf6cc544
content/18: c4442ebf954ce44792673e999fa5ae57
content/19: 8a5bf876779f64c00bbfe410a76b876d
content/20: 3baabe4ef51745247512b0226e9722da
content/20: cb438f299cfb632a6fd51ae7c4c8b4cf
content/21: aaa108acd920202f8fe7dc5234db1c19
content/22: 6539573d8f58d31b8195280f668ddbc8
content/23: d658c027072bdb8ac9d579e238635e37
@@ -5121,7 +5122,7 @@ checksums:
content/36: d0867474e7471bfc16e5c83fc76cee6d
content/37: 1e842e38ba5a718348720c584db2220a
content/38: b2a4a0c279f47d58a2456f25a1e1c6f9
content/39: a81fff6a729e47d493c2bc07ebc5245d
content/39: 5118934bc313f3e604af5274d14c5035
ba44e50cb1c597d3c5756a3e2fb0ca6a:
meta/title: 01d9819514e27056dcc69463194b63d2
content/0: e4684b7201c2aed215c82606e9eaa293
@@ -6501,107 +6502,193 @@ checksums:
content/31: bcadfc362b69078beee0088e5936c98b
content/32: 14c0df89bf4b2633315bf0d2a8b3987d
content/33: 061400fbe790008a9b7bb4e568d25313
content/34: 371d0e46b4bd2c23f559b8bc112f6955
content/35: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/36: bcadfc362b69078beee0088e5936c98b
content/37: 4a2e706133d34b12b398b9ab2a895001
content/38: a4dfe4fd01ca877c873338d48d9ec162
content/39: 371d0e46b4bd2c23f559b8bc112f6955
content/40: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/41: bcadfc362b69078beee0088e5936c98b
content/42: 4a2e706133d34b12b398b9ab2a895001
content/43: f39442609c0ddf9da722270da2f75c96
content/44: 371d0e46b4bd2c23f559b8bc112f6955
content/45: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/46: bcadfc362b69078beee0088e5936c98b
content/47: 4a2e706133d34b12b398b9ab2a895001
content/48: 25c3374c18698301689fb849f7262afd
content/49: 371d0e46b4bd2c23f559b8bc112f6955
content/50: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/51: bcadfc362b69078beee0088e5936c98b
content/52: 4a2e706133d34b12b398b9ab2a895001
content/53: d70f7ca67dc0127139a74324e72d5cfa
content/54: 371d0e46b4bd2c23f559b8bc112f6955
content/55: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/56: bcadfc362b69078beee0088e5936c98b
content/57: 4a2e706133d34b12b398b9ab2a895001
content/58: e1841ff010b3addb5251d10bc9e2a4a8
content/34: 18cf0db1eebe0c999fbf15f1059634fe
content/35: 371d0e46b4bd2c23f559b8bc112f6955
content/36: a019ab5fbf31b53e720276c823cee5ad
content/37: bcadfc362b69078beee0088e5936c98b
content/38: b509768bb76919395b6fb80fd1e59831
content/39: a4dfe4fd01ca877c873338d48d9ec162
content/40: e8da1f9b212d48d11bd6b3a92fab35f0
content/41: 371d0e46b4bd2c23f559b8bc112f6955
content/42: 96950606febb7ff7d49a22c694ffbe8c
content/43: bcadfc362b69078beee0088e5936c98b
content/44: 59c08999f9c404330ebd8f8a7d21e1a1
content/45: f39442609c0ddf9da722270da2f75c96
content/46: 6d29ca777cebe3231492841fa17de96a
content/47: 371d0e46b4bd2c23f559b8bc112f6955
content/48: a235cd33346b35e001f6bc7b9f4718bb
content/49: bcadfc362b69078beee0088e5936c98b
content/50: a9096a341b00ce4f4891daaca2586d1c
content/51: 25c3374c18698301689fb849f7262afd
content/52: a2e198c8c422957b62334a545ba5fff0
content/53: 371d0e46b4bd2c23f559b8bc112f6955
content/54: 034bc531f82e732846e3c25f2cb4112c
content/55: bcadfc362b69078beee0088e5936c98b
content/56: 684a228552c8090b312d2ef80e185240
content/57: d70f7ca67dc0127139a74324e72d5cfa
content/58: 52f7e98edef0fd236e7d08aa59978878
content/59: 371d0e46b4bd2c23f559b8bc112f6955
content/60: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/60: ada3e2dca07a23757a99b573c891c2f8
content/61: bcadfc362b69078beee0088e5936c98b
content/62: 4a2e706133d34b12b398b9ab2a895001
content/63: ec33ee506164782e079a5c3d1fdb3805
content/64: 371d0e46b4bd2c23f559b8bc112f6955
content/65: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/66: bcadfc362b69078beee0088e5936c98b
content/67: 4a2e706133d34b12b398b9ab2a895001
content/68: 62763db101bf33507d3b9e2b15f9011a
content/69: 371d0e46b4bd2c23f559b8bc112f6955
content/70: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/71: bcadfc362b69078beee0088e5936c98b
content/72: 4a2e706133d34b12b398b9ab2a895001
content/73: 434790df3914ff2403a5e92260a49395
content/74: 371d0e46b4bd2c23f559b8bc112f6955
content/75: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/76: bcadfc362b69078beee0088e5936c98b
content/77: 4a2e706133d34b12b398b9ab2a895001
content/78: 412852540e461afcf7588e1e54c13d3d
content/79: 371d0e46b4bd2c23f559b8bc112f6955
content/80: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/81: bcadfc362b69078beee0088e5936c98b
content/82: 4a2e706133d34b12b398b9ab2a895001
content/83: 63972cc4d72025f47aa6b329cf5f8290
content/84: 371d0e46b4bd2c23f559b8bc112f6955
content/85: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/86: bcadfc362b69078beee0088e5936c98b
content/87: 4a2e706133d34b12b398b9ab2a895001
content/88: 383217087ecc00735f5f0e498c676395
content/62: f24b33eb878cea74220118388cbe66ae
content/63: e1841ff010b3addb5251d10bc9e2a4a8
content/64: 8ed96afe1fcde026359d12cb769eba4d
content/65: 371d0e46b4bd2c23f559b8bc112f6955
content/66: d7e72d73f55ff400aae5dd71d87289c1
content/67: bcadfc362b69078beee0088e5936c98b
content/68: 0b65a5803483e3c656eddf173fb9a504
content/69: ec33ee506164782e079a5c3d1fdb3805
content/70: aa1327893e96d2e120dd06b5db7fd25f
content/71: 371d0e46b4bd2c23f559b8bc112f6955
content/72: 23913a58dcb81ea42a93a588fba9ff6c
content/73: bcadfc362b69078beee0088e5936c98b
content/74: 58f51278335501d47230071b11663410
content/75: 62763db101bf33507d3b9e2b15f9011a
content/76: dc0bb7ba0e924f787ec7fb8c59b86007
content/77: 371d0e46b4bd2c23f559b8bc112f6955
content/78: ab7a75b7a9d50e3cc0bb0d49a8acafea
content/79: bcadfc362b69078beee0088e5936c98b
content/80: a16ff36b57debf98549f27e2938e5fa1
content/81: 434790df3914ff2403a5e92260a49395
content/82: 1dead520cd1a2fad55caa46db7879cc7
content/83: 371d0e46b4bd2c23f559b8bc112f6955
content/84: 79fb8fdcb85d78b252bdfcaa8a87b634
content/85: bcadfc362b69078beee0088e5936c98b
content/86: 7314f75c28332dea6fa37f56a1e104d2
content/87: 412852540e461afcf7588e1e54c13d3d
content/88: e7dd1afe7c48184ba6c4e179d47a1acb
content/89: 371d0e46b4bd2c23f559b8bc112f6955
content/90: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/90: 9a213157fe766442bb94d63a4913b3b0
content/91: bcadfc362b69078beee0088e5936c98b
content/92: 4a2e706133d34b12b398b9ab2a895001
content/93: c27f212932a83b343c35d3a2c7f332d2
content/94: 371d0e46b4bd2c23f559b8bc112f6955
content/95: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/96: bcadfc362b69078beee0088e5936c98b
content/97: 4a2e706133d34b12b398b9ab2a895001
content/98: 5ab51f77464b13249fa4ad9072090bd6
content/99: 371d0e46b4bd2c23f559b8bc112f6955
content/100: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/101: bcadfc362b69078beee0088e5936c98b
content/102: 4a2e706133d34b12b398b9ab2a895001
content/103: 47655d20e26f3c4f3bf7e67fbc669719
content/104: 371d0e46b4bd2c23f559b8bc112f6955
content/105: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/106: bcadfc362b69078beee0088e5936c98b
content/107: 4a2e706133d34b12b398b9ab2a895001
content/108: 2b3a08646ecf5bc2689081de63c89350
content/109: 371d0e46b4bd2c23f559b8bc112f6955
content/110: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/111: bcadfc362b69078beee0088e5936c98b
content/112: 4a2e706133d34b12b398b9ab2a895001
content/113: 5447fd9b1af11c698a8b153bd90cfc10
content/114: 371d0e46b4bd2c23f559b8bc112f6955
content/115: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/116: bcadfc362b69078beee0088e5936c98b
content/117: 4a2e706133d34b12b398b9ab2a895001
content/118: 3cee2834919b0519b79a9a5d466bfedd
content/92: f50627fa2c9fb75436c751d1e1d8ee46
content/93: 63972cc4d72025f47aa6b329cf5f8290
content/94: ece657a13e2b366dc71302cb86c9654c
content/95: 371d0e46b4bd2c23f559b8bc112f6955
content/96: 69e5202d94190d6c371fb65eadf190d2
content/97: bcadfc362b69078beee0088e5936c98b
content/98: acf742acec926b11a8b6ec7d0bb9b1ea
content/99: 383217087ecc00735f5f0e498c676395
content/100: 10ed4bfd54581eef5fe7143d05c2e18a
content/101: 371d0e46b4bd2c23f559b8bc112f6955
content/102: 9ec9d7a05190f09754ecbf7a95ea9e21
content/103: bcadfc362b69078beee0088e5936c98b
content/104: 1badbadbec8323f7d90b282b45d4fff2
content/105: c27f212932a83b343c35d3a2c7f332d2
content/106: 6608ca534d752592738c5959967b297e
content/107: 371d0e46b4bd2c23f559b8bc112f6955
content/108: 9289173ea3d3ebd9d861518c945ad11c
content/109: bcadfc362b69078beee0088e5936c98b
content/110: 21b270b30cc7117001d760b5fa164b3c
content/111: 5ab51f77464b13249fa4ad9072090bd6
content/112: dada37d12334ba3eb4d79ada3d4d1b8a
content/113: 371d0e46b4bd2c23f559b8bc112f6955
content/114: 94ffeb9cd631e63a8d938504a306dbae
content/115: bcadfc362b69078beee0088e5936c98b
content/116: e3466779eefb5a5444e91b241e8016ee
content/117: 47655d20e26f3c4f3bf7e67fbc669719
content/118: d6655a220249a9070bdc3f48defdc258
content/119: 371d0e46b4bd2c23f559b8bc112f6955
content/120: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/120: 322ef0d468ed677f30fd1a7ce5082f4f
content/121: bcadfc362b69078beee0088e5936c98b
content/122: 4a2e706133d34b12b398b9ab2a895001
content/123: 5a1e3da373873351a92d4986b649baf4
content/124: 371d0e46b4bd2c23f559b8bc112f6955
content/125: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/126: bcadfc362b69078beee0088e5936c98b
content/127: 4a2e706133d34b12b398b9ab2a895001
content/128: 420d56aab6760592bc5fc15eb17b0bb0
content/129: 371d0e46b4bd2c23f559b8bc112f6955
content/130: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/131: bcadfc362b69078beee0088e5936c98b
content/132: 4a2e706133d34b12b398b9ab2a895001
content/133: b3f310d5ef115bea5a8b75bf25d7ea9a
content/134: a575a9bbb6adc3614e075cfe43a9dad9
content/122: 60b28498e0f6145a602a5eac695fd9e8
content/123: 2b3a08646ecf5bc2689081de63c89350
content/124: 19dbb4c197de0be572c705c714dacbcd
content/125: 371d0e46b4bd2c23f559b8bc112f6955
content/126: a34a46c7a128a774f3ef981ef21d7895
content/127: bcadfc362b69078beee0088e5936c98b
content/128: e813715d6bca3d0bff6248e630328cc6
content/129: 5447fd9b1af11c698a8b153bd90cfc10
content/130: 1ba2c46eef9787197d13ee2cab16ba1e
content/131: 371d0e46b4bd2c23f559b8bc112f6955
content/132: b354f23bae5f679e00314ad7b77f6dce
content/133: bcadfc362b69078beee0088e5936c98b
content/134: 48e0012b76ea32ebedc3a374b8f3ebf7
content/135: 3cee2834919b0519b79a9a5d466bfedd
content/136: 01aa5fc7468b1550c1220280b89670d4
content/137: 371d0e46b4bd2c23f559b8bc112f6955
content/138: 78353b7fbbfefed6d3ef282b1518e125
content/139: bcadfc362b69078beee0088e5936c98b
content/140: 671d2167bd997bf7695d4d4b8ed31e2a
content/141: 5a1e3da373873351a92d4986b649baf4
content/142: 426c2a21e6c81cd1af5c85c6210f5acf
content/143: 371d0e46b4bd2c23f559b8bc112f6955
content/144: c2121ca1e8478658f85585d421d72b11
content/145: bcadfc362b69078beee0088e5936c98b
content/146: 1faeb74ada0cc6f71c5d3164dd0e9000
content/147: 420d56aab6760592bc5fc15eb17b0bb0
content/148: 852944e64ebf66693bcc7c481e29a17e
content/149: 371d0e46b4bd2c23f559b8bc112f6955
content/150: fbc8013bcfc518c7c0fa5b7c0c2716c0
content/151: bcadfc362b69078beee0088e5936c98b
content/152: 4e3b4ca915120fb0d4447927f89cc291
content/153: 754d4f8d75b844298aed2b79618d9129
content/154: ab1ac4a2b2bf86dad67d8b672e293a4e
content/155: 371d0e46b4bd2c23f559b8bc112f6955
content/156: 7ddb8c77a9b218a78d3f65d72f5265c9
content/157: bcadfc362b69078beee0088e5936c98b
content/158: ecfca300796cf3b369e90fb35b63b144
content/159: 7f6a03dca71ef1eb9e156dfd4a1f77e1
content/160: 45d7dcf3663691a09d963c4a1cc9c23b
content/161: 371d0e46b4bd2c23f559b8bc112f6955
content/162: f86869bf5c27562c7b7bf1544b21ff4b
content/163: bcadfc362b69078beee0088e5936c98b
content/164: 714311657d473d9a0fd1a8e396e44b22
content/165: 2bfd4048d729ab4db20cb54a37a07290
content/166: c19eb093b847fe9ef9f9cac53961af68
content/167: 371d0e46b4bd2c23f559b8bc112f6955
content/168: b010aa231976354c2c2e5ee7184c9927
content/169: bcadfc362b69078beee0088e5936c98b
content/170: d429fd601fe01e36a23d0c29acdea72d
content/171: f1dafa02590e8218c18d95055eea9ea9
content/172: 705bb937841afd1f0e64305e3c3460ee
content/173: 371d0e46b4bd2c23f559b8bc112f6955
content/174: 634db00b2e0fcb753f20c338f34313ca
content/175: bcadfc362b69078beee0088e5936c98b
content/176: 11f50d87ddbc4ddbda712e38a41ab8eb
content/177: d3dabb641b05ca5eabc8f4d177dfc82e
content/178: bfa0d5b1ec884d8a42fef66ba23b6eeb
content/179: 371d0e46b4bd2c23f559b8bc112f6955
content/180: 97d3ca5b5e434d4dd7d7e29e5f72fdfe
content/181: bcadfc362b69078beee0088e5936c98b
content/182: ff5b4b13ad2cb1c22dceb123eb00e700
content/183: 537b72ddd0a26379d0b0ab3618ac4b99
content/184: 54fbe210b8272881394def0e68acb300
content/185: 371d0e46b4bd2c23f559b8bc112f6955
content/186: eca6e3991f28fee68a07e92c61064693
content/187: bcadfc362b69078beee0088e5936c98b
content/188: 9b85ce580c3e7ef2ed02eb5986bd982c
content/189: 249e3e1c1ceb337014c40c9587834504
content/190: f82155a066f1902bc5f47f228f7f2507
content/191: 371d0e46b4bd2c23f559b8bc112f6955
content/192: eca6e3991f28fee68a07e92c61064693
content/193: bcadfc362b69078beee0088e5936c98b
content/194: 9ac81ba2615e78d981313bd066c0e7ce
content/195: 6c1cb8caa156a1270ba95c255549058f
content/196: 1e2203c6b2398f8e4d26d3c6e2bc7f9a
content/197: 371d0e46b4bd2c23f559b8bc112f6955
content/198: 4d7b9733eb25896fd9164a896355e641
content/199: bcadfc362b69078beee0088e5936c98b
content/200: 2b816b45e5ac21d335405e9c2b17642b
content/201: 0dffa8d835db9ce2ee41a2a6e69efb31
content/202: 65aa571a8a832a683236f360971b3cd5
content/203: 371d0e46b4bd2c23f559b8bc112f6955
content/204: f7009d0b073656cc456e6671c229f94d
content/205: bcadfc362b69078beee0088e5936c98b
content/206: 2b816b45e5ac21d335405e9c2b17642b
content/207: 11168096d5edb1f826080453e2996d4a
content/208: 0951f95604bb649b50eb0ff05e93f6ae
content/209: 371d0e46b4bd2c23f559b8bc112f6955
content/210: 302c501a18a71ae74511dc4c997ed833
content/211: bcadfc362b69078beee0088e5936c98b
content/212: f938cb9d792d611a7801c9501fb3e314
content/213: 3422cb7d11dd2bb9091679288ad8dbce
content/214: be814a3fae2403d49fbd63cd09cbff87
content/215: 371d0e46b4bd2c23f559b8bc112f6955
content/216: 634db00b2e0fcb753f20c338f34313ca
content/217: bcadfc362b69078beee0088e5936c98b
content/218: 6e6bab9df38a8003940563ab7e009d43
content/219: b3f310d5ef115bea5a8b75bf25d7ea9a
content/220: a575a9bbb6adc3614e075cfe43a9dad9
6f170cd9c9fab731de5f71cf485eeef5:
meta/title: 341fcb2f99eb4527e898e7cc78eaedd8
meta/description: 75e5f7b311444862e09f872b1a3797d1
@@ -48857,3 +48944,291 @@ checksums:
content/51: f23cc6d685827f8880df368d65c9ee88
content/52: b3f310d5ef115bea5a8b75bf25d7ea9a
content/53: dce0795eb6bcefcb20aa65529826adab
01c58bab23799551db5e78192e636ca4:
meta/title: 93ac99150c9bd9eaa51699942c068143
meta/description: 58537dba08c9c56fc7eab16e2a3cffcd
content/0: 1b031fb0c62c46b177aeed5c3d3f8f80
content/1: 3badb4388552c1830eb8f023fa6560e0
content/2: 821e6394b0a953e2b0842b04ae8f3105
content/3: e87e6120d74ada777b70557384b046d7
content/4: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
content/5: a60f3195d4d499d3c96a3471fd3e6f6e
content/6: e9374911aee840916bfd750f3955dac7
content/7: 371d0e46b4bd2c23f559b8bc112f6955
content/8: ddf9f33ab00bc63ba73d935e9a9461bb
content/9: bcadfc362b69078beee0088e5936c98b
content/10: 83f29573676a9d1ccb33827ff578858a
content/11: 16b29a33d78bf2344e7b204ca98c0b82
content/12: 97b8063d8d1ada29666d9ec525694a93
content/13: 371d0e46b4bd2c23f559b8bc112f6955
content/14: 6e295e5dcf37ad9f7ee602652b93c5bf
content/15: bcadfc362b69078beee0088e5936c98b
content/16: 83f29573676a9d1ccb33827ff578858a
content/17: 20c596f9dbdfe0cfa2d8f5b60dc71514
content/18: 1448d524a996ed212e7dbdbe967339b0
content/19: 371d0e46b4bd2c23f559b8bc112f6955
content/20: 0c9e28a7cf7bcfee1fe692fbd502dbb4
content/21: bcadfc362b69078beee0088e5936c98b
content/22: 83f29573676a9d1ccb33827ff578858a
content/23: 24e966f4a7a7f97a355b749b74377e64
content/24: 968f985de62e1fe2ad21716d7c215fc3
content/25: 371d0e46b4bd2c23f559b8bc112f6955
content/26: fd89e6852579854ef98eef8a76a261c5
content/27: bcadfc362b69078beee0088e5936c98b
content/28: 83f29573676a9d1ccb33827ff578858a
content/29: 84bf7ab082392d81a1347dd4091df95c
content/30: e6d21b5e83b22fcee600f5863114f76b
content/31: 371d0e46b4bd2c23f559b8bc112f6955
content/32: 6357650b93cf00ec79d620f75336a063
content/33: bcadfc362b69078beee0088e5936c98b
content/34: 83f29573676a9d1ccb33827ff578858a
content/35: 9a047f1df87fa8980889528cd4a7e23e
content/36: 10a705b69f4de0cafb55b579a091ff3e
content/37: 371d0e46b4bd2c23f559b8bc112f6955
content/38: a5f1409dffad379396a370fc6ef0b784
content/39: bcadfc362b69078beee0088e5936c98b
content/40: 83f29573676a9d1ccb33827ff578858a
content/41: bc110e334defc4a0037ba2ef9fb75083
content/42: 741dfd7becb14378932cbad9bd3f1aaa
content/43: 371d0e46b4bd2c23f559b8bc112f6955
content/44: 5bf2c3d8e1f679dffa34a23728e09d3d
content/45: bcadfc362b69078beee0088e5936c98b
content/46: 83f29573676a9d1ccb33827ff578858a
content/47: 007ad7c8b597dd1638c1d730ae57c18b
content/48: 66114378d52751790fb30b1d4a49fb4b
content/49: 371d0e46b4bd2c23f559b8bc112f6955
content/50: 2f9f05b2bdf789cfda1791b0a274de0f
content/51: bcadfc362b69078beee0088e5936c98b
content/52: 83f29573676a9d1ccb33827ff578858a
content/53: b812f4c86dc86447c8aab2e6fc7ee77b
content/54: d368e64c4bc2884ac4af1c046d3043fc
content/55: 371d0e46b4bd2c23f559b8bc112f6955
content/56: 8d96a5aad7ba60cbbe6c4ab8b816c8f5
content/57: bcadfc362b69078beee0088e5936c98b
content/58: 83f29573676a9d1ccb33827ff578858a
content/59: bb444040039dcefd837703242f69b3f2
content/60: 2ca4d597cf85afe0c09441a1c55dc588
content/61: 371d0e46b4bd2c23f559b8bc112f6955
content/62: f90f9e7f20b3c4bf832a71e2dcbc1fe5
content/63: bcadfc362b69078beee0088e5936c98b
content/64: 83f29573676a9d1ccb33827ff578858a
content/65: cdbf6d4d4066c241de37637f23944a51
content/66: 1f1ad7c241c27cf4614ba7b0b67fb584
content/67: 371d0e46b4bd2c23f559b8bc112f6955
content/68: 705cf28994842a8a455ba3dcb61c3f11
content/69: bcadfc362b69078beee0088e5936c98b
content/70: 83f29573676a9d1ccb33827ff578858a
content/71: b3f310d5ef115bea5a8b75bf25d7ea9a
content/72: fe020be6c017a995d0355c99c6b034ec
1b5c57a63b2d38e097b7f96b6e054db8:
meta/title: 6ac3805eb202cc0ba10ad61dc1ada045
meta/description: 9d15289c788bd388e5227cb563a740f8
content/0: 788f3a864cc39c0564d51e1d1cd9ff22
content/1: 9be86f736d43388837d7c3255cb3c4d7
content/2: a0ee15c76fc6046002638b95447f96ff
content/3: fa1ff96b560f555e4e3d8ab8dde2f48a
content/4: 49147be50168a5a9cd29f74dd0e5070c
content/5: 23d12cd73347011326dc510f5a64ebd1
content/6: 734332de1f2a5bf11a2592b719c364dd
content/7: 95250f345f24a86e24bb4a709e2e974f
content/8: e793c95422005f54a9cec21d6970d2c8
content/9: 05d7473426b6c16f74e7d198cfbb0cb7
content/10: 1b9cf53ffdbe9f66a832adb01b9adb7d
content/11: 49686bc01877cc2cd905c07856b60968
content/12: ee8f3e459896a02058984d223f84782c
content/13: 7e67336940094a9c4af1d33718425bbb
content/14: 4079240d26f493561febcc0d2424a762
content/15: b792a368c791d2a4057053cacdce118c
content/16: 6e96fbce23fe7557a17e9ef86a1281d5
content/17: 55e329c2041769a139ad41bb1a5f330d
content/18: 50c787548872a04fd1284e031435abc1
content/19: 1e66bd6d20fe0e2dcf92f6d0f5368747
content/20: 1144109c71063d3ff843c8d4c9e5b152
content/21: 1d03eb3c81f0ec3b7d180d86bba23ca1
content/22: 9727c72124aebb118edec2d78b2b262d
content/23: 1f217cf2f2ea40bf0b288371646c4b64
content/24: 4264d1f59e6bccb77c960392dd14c9c7
content/25: 9dd663b7aaa0e1aa54acfb5c049b5a55
content/26: 0b58eed72d57c695813a8ab2d4e38a43
content/27: 1e733832735cc568035073b340ca0f4e
content/28: 0468f49bf9ba3fc25037ed47c84502ef
5b14b034c1063c2e7d408c6cadba9a1e:
meta/title: a2d9323f87fb0028f39dfa4e0a4d7d71
meta/description: 9cbe42b3ae37e29953900095a016c90c
content/0: f0b49646269c72e3fe82ab242d31a5c7
content/1: d5ac1176896f40c95ee6a9dbb0f04e60
content/2: 437859c58d04ea7fe3f7c17ff07f651c
content/3: c7315dc914f85a8e7be1553e0458fdda
content/4: 1352ef721cad0d6ba681bec14478a120
content/5: ba8058adffeed2edded227ade405b3d6
content/6: cc0443e0cd2d1957adbc5ec41f170c81
content/7: 24f8cc625d382d32b9322ec76916d45d
content/8: 942e3d5cbc20729b09a5bb19681b7601
content/9: 2992441e06f8c7759773e82e93175cd1
content/10: 44194a2283027a76dba38f110adc41ad
content/11: 6d9288feed6ba2a6d269e161c4a8bb19
content/12: 8e6335029427810edaa8f328852b7fbe
content/13: 676041ae8ea3d298396e956e2a8c976f
content/14: 1b2df0a6fe68827b69139eef5d37bde9
content/15: 3304a33dfb626c6e2267c062e8956a9d
content/16: 46babdd66f738104252f4d4aed6d7263
content/17: 79d5a9ec463aa51c206c437c84413a73
content/18: e33326c95467278a9f4f959bf29a7ea7
content/19: 25f161b9292300600117ef6f752ede94
content/20: 0dd2c95befe432a96884cbd329ba6614
content/21: d79e98f5ea058b22817a2815a5204c2f
content/22: 5530aae43311704e87cbd7356f652671
content/23: 77a8b64f9daee5eb35869a722582af9c
content/24: 26375dab1609c61de13bb061f3474c95
content/25: 618d7c3aeb14efcbf76ee9fdb0a9cb5d
content/26: c0467f69ad04c70f4c5bf7f514150f0a
content/27: 4b806fdc34160e17187a83a78e569c8a
content/28: bc385d2332fc9efb9d21755f8704010b
content/29: 373512a6210dcae033357e1028b90982
729af5a9a28e14b58b4111da6b0cf13c:
meta/title: 9a975fb182899afd051bbf56347dd3b3
meta/description: 650a17c852f56056e62fba9adede01bc
content/0: f0b49646269c72e3fe82ab242d31a5c7
content/1: 48361e78de327edccd0c2d8c6c4a5494
content/2: 9f533c3891128fe351189c94f6e495f5
content/3: 391128dee61b5d0d43eba88567aaef42
content/4: 07022204d43750c73c0753d75683b16f
content/5: 3ea053b7a26239743b6de62a32cd7a46
content/6: 23fbbe701b60dfbd781f44ca2c5dee8b
content/7: a23cd40dcc0fb6090eeb9db009ec391e
content/8: a71f699ec09f09214f0798a4ca775fca
content/9: c867eb1641a114009759c5e9f06c31c5
content/10: 6f4f745603fc23f24eae43e367735da3
content/11: 79dbacaf848d294223c6f1cd11fd91c3
content/12: 3304a33dfb626c6e2267c062e8956a9d
content/13: 051222c54b5add12c2048c57840e25e0
content/14: 7d576599d17b8ff76fc3eb51bcfb5584
content/15: 7cfde5ca8164b52afe12074a95b6a21f
content/16: 8908a88d73243c63da438ce43ed49870
content/17: 61372fd9711dfe5eec9e3799a7c90a2b
content/18: 746bc0998b79e81e1912e049a05865d3
content/19: 6a8d5f97b70a4555b499868575f11141
fd6f0d1fa41bbacc06386340625ff1aa:
meta/title: 58e9ce12b9b0e0f84f98a118dd3da37b
meta/description: 6aa21d9bb5dd3152b2cd6de8b7775e0d
content/0: 3a9daa61782f1fc39bef9469f491d27d
content/1: ac754b22b6a1ed9e6926ae4081498b4f
content/2: d62c9575cc66feec7589fba95c9f7aee
content/3: c0f38deb15a6623bff04226b4783af98
content/4: 56ea06288e338a3c329d80a0f845b4a0
content/5: 1d183e4b16ea089353690b86ee5d9123
content/6: 6480198c8935f30f57cec7f1f761a97d
content/7: 3cb16053db18d8997d72e38b8e336438
content/8: 0fa272de829c5b8672a44f6a9f87bd99
content/9: a3c2e315559c0df45b4ca39a9a02236f
content/10: 7c1e364f142a0f32c546b7129bd18afa
93422a94ee0e64a54dd84ce24b99efcb:
meta/title: 538ccbbb5bae1e2b53c526e8e06feef3
meta/description: 32cd350ff688ff53a1c29518531c003e
content/0: 232be69c8f3053a40f695f9c9dcb3f2e
content/1: 9a97f1047f7127c9caa6777d78893113
content/2: 811be6985fdcb266ed9092ad2b1812e3
content/3: a341918411b2287ced6505929d64d80f
content/4: 022680615d8fba85da1779987de58ffe
content/5: c3bad935a189da8bf2b92b505e4d7dde
content/6: b19c0618fb41641a515afeb5ffc2ebf6
content/7: 95a511d4bf1ee9ab32758ae877389e81
content/8: 8db40d50eb73a860c1378e83c777a7a9
content/9: 6fc08be0705b3bc0d210e43730c3354b
content/10: 704c33c06da3370c9726df90b4db11eb
content/11: c0561ecff29648963a26914cf51a1ccf
content/12: bc941a7212c82f7952a980b8cdff173e
content/13: 6a25e03bdd4286e44f269b04276b10de
content/14: 4fff00e8bd2dd9613576c3d47279b3aa
content/15: d25efcec9572e48cc3f4434bf9b7dd9a
content/16: 7b7ce6b9f12b4044d6e64e9ffefb3601
content/17: 995ae47689f0c1f6fa8dc8190e106b38
fbb88b12cbab45a5fa14ba520d197fb3:
meta/title: 0de4d47501a7bb79ab94f9da307dc6fc
meta/description: 6367e2f210e1bcf2007e5fa79ea0e6b9
content/0: f0b49646269c72e3fe82ab242d31a5c7
content/1: 56ea06288e338a3c329d80a0f845b4a0
content/2: cf4f06554ad7f32852a1b4b93617cb01
content/3: 6480198c8935f30f57cec7f1f761a97d
content/4: d431b26b4ceb2cf0609b411d10a05d3a
content/5: efb59989d3e662dbf0b28fa00e4c2ae6
content/6: c5e556fb0b6ed41712484f3e961c5544
content/7: 100aefe3f9f07261c7769c39b82dffb1
content/8: 4f6ba176ad2f6ccce074889450706363
content/9: 84a1da3d7d5fe492f9eec281ab8b384e
content/10: c9a712b04a3d268c0906c74338b6ba4e
content/11: 86c3411ef57bcac05a9760d903acd84a
content/12: 5520363779134f65b88a06c55fc8cdcc
content/13: 51a33ae79443b6237c2ca32ad84d1379
content/14: a2004e35fe83b1fd8848d1eb6e4bf94c
content/15: f71dcc42ca7c2a65544e1d4c6e552e76
content/16: 616b46c575631095adcaed620829dcac
content/17: 3304a33dfb626c6e2267c062e8956a9d
content/18: 8290e53f98b03dbbca8f0c5673291607
content/19: de7cfb42a8774fdf07cee29f4b4b06c4
content/20: ea29416edca1c463b0fe50ccd95dc2ac
content/21: 370ac33cf8edc549eb3d4b3dd767121b
content/22: ec531368a8d1acf9978aa6266d454142
content/23: b49fc056af3cd1853d9d31854b1d515f
content/24: 5f8c8cc500a8362fb14fd816af7f52b2
content/25: 38f65a5fcf96ad88df123dc0ae4e6556
content/26: 746bc0998b79e81e1912e049a05865d3
content/27: 755eb21269b2c83655633ce1984f3558
20fd655a4cdb94d147d82d1611c06fe4:
meta/title: 7fd4a31b926070c583e409649b88694a
meta/description: 6f0a0fc3324606b469e022a8fa4e8ff8
content/0: 1b031fb0c62c46b177aeed5c3d3f8f80
content/1: 75591c909ad874c4733b4c66568a53d8
content/2: 531114f37d53ef0bf5b675f38b925230
content/3: b34ccdd230dee482b3248637160adbee
content/4: ddc6f320044838c1f703e845b4abdaf8
content/5: 672cb353d026fd23304d45ea8c4a0d4a
content/6: beb43d40f5f9562cbea54b50362f0d4e
content/7: cb788116b070ca702e05dadc46540579
content/8: 821e6394b0a953e2b0842b04ae8f3105
content/9: 010470c994fc10eaf5a369342533ed0d
content/10: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
content/11: 9085af1052bbd1b4a6a3e3e593fdc1e3
content/12: ceace0cdd474e50130ec1fc069399350
content/13: 371d0e46b4bd2c23f559b8bc112f6955
content/14: d097acee816b07cb65bad33fec5a83a5
content/15: bcadfc362b69078beee0088e5936c98b
content/16: 5c96636264d7f57ddac41000f6384ba4
content/17: eab48ffdef9c89f3a3df243d04b94b81
content/18: a6a0e6b8cf6c28587bee3a06e09c6af3
content/19: 371d0e46b4bd2c23f559b8bc112f6955
content/20: 9e0c0ba2b0df2454be80c83108047631
content/21: bcadfc362b69078beee0088e5936c98b
content/22: 353db07a018a23f8a3b6acbd2565ad09
content/23: c5e41b2d64f87372d5eb3c2c5071dcaa
content/24: 97885674f5c5f1325a53513bf07602b4
content/25: 371d0e46b4bd2c23f559b8bc112f6955
content/26: 9e0c0ba2b0df2454be80c83108047631
content/27: bcadfc362b69078beee0088e5936c98b
content/28: 6ede6c73bbbc3df5ac212c4c2368959e
content/29: 6b44b3e0ce956141e531afd7e41e6b1a
content/30: 1b9eb7896b93f0565f90f6f15218ab94
content/31: 371d0e46b4bd2c23f559b8bc112f6955
content/32: 50dffb7983dcba194c867e70e786e2d5
content/33: bcadfc362b69078beee0088e5936c98b
content/34: c54b98c7e00b649b379fbc226171e0a9
content/35: d1563f58482787d79fbea1e64cbc2b41
content/36: e01b0952cd4c9511030a4fc719549d89
content/37: 371d0e46b4bd2c23f559b8bc112f6955
content/38: c144056cae63b46734bb4d75b7fdace4
content/39: bcadfc362b69078beee0088e5936c98b
content/40: bcb37c2bc190c3c12e5c721d376909f7
content/41: 33e09b436c7bed97b18a331521cf9791
content/42: 8c9ee32d66137a7e2ab3997c6c01ee24
content/43: 371d0e46b4bd2c23f559b8bc112f6955
content/44: 9e0c0ba2b0df2454be80c83108047631
content/45: bcadfc362b69078beee0088e5936c98b
content/46: bcb37c2bc190c3c12e5c721d376909f7
content/47: 034ddae6d1cf3f0e7c3837ea13daf3bc
content/48: 55795caec4263261082c7f4dc5af0182
content/49: 371d0e46b4bd2c23f559b8bc112f6955
content/50: 9e0c0ba2b0df2454be80c83108047631
content/51: bcadfc362b69078beee0088e5936c98b
content/52: bcb37c2bc190c3c12e5c721d376909f7
content/53: b3f310d5ef115bea5a8b75bf25d7ea9a
content/54: dafdefed393d3f02fe15ef832c922450

View File

@@ -5,9 +5,15 @@
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
describe('OAuth Utils', () => {
const mockSession = { user: { id: 'test-user-id' } }
const mockDb = {
const mockSession = { user: { id: 'test-user-id' } }
const mockGetSession = vi.fn()
vi.mock('@/lib/auth', () => ({
getSession: () => mockGetSession(),
}))
vi.mock('@sim/db', () => ({
db: {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
@@ -15,33 +21,41 @@ describe('OAuth Utils', () => {
update: vi.fn().mockReturnThis(),
set: vi.fn().mockReturnThis(),
orderBy: vi.fn().mockReturnThis(),
}
const mockRefreshOAuthToken = vi.fn()
const mockLogger = {
},
}))
vi.mock('@/lib/oauth/oauth', () => ({
refreshOAuthToken: vi.fn(),
}))
vi.mock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn().mockReturnValue({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
}
}),
}))
import { db } from '@sim/db'
import { createLogger } from '@/lib/logs/console/logger'
import { refreshOAuthToken } from '@/lib/oauth/oauth'
import {
getCredential,
getUserId,
refreshAccessTokenIfNeeded,
refreshTokenIfNeeded,
} from '@/app/api/auth/oauth/utils'
const mockDb = db as any
const mockRefreshOAuthToken = refreshOAuthToken as any
const mockLogger = (createLogger as any)()
describe('OAuth Utils', () => {
beforeEach(() => {
vi.resetModules()
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue(mockSession),
}))
vi.doMock('@sim/db', () => ({
db: mockDb,
}))
vi.doMock('@/lib/oauth/oauth', () => ({
refreshOAuthToken: mockRefreshOAuthToken,
}))
vi.doMock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn().mockReturnValue(mockLogger),
}))
vi.clearAllMocks()
mockGetSession.mockResolvedValue(mockSession)
mockDb.limit.mockReturnValue([])
})
afterEach(() => {
@@ -50,8 +64,6 @@ describe('OAuth Utils', () => {
describe('getUserId', () => {
it('should get user ID from session when no workflowId is provided', async () => {
const { getUserId } = await import('@/app/api/auth/oauth/utils')
const userId = await getUserId('request-id')
expect(userId).toBe('test-user-id')
@@ -60,8 +72,6 @@ describe('OAuth Utils', () => {
it('should get user ID from workflow when workflowId is provided', async () => {
mockDb.limit.mockReturnValueOnce([{ userId: 'workflow-owner-id' }])
const { getUserId } = await import('@/app/api/auth/oauth/utils')
const userId = await getUserId('request-id', 'workflow-id')
expect(mockDb.select).toHaveBeenCalled()
@@ -72,11 +82,7 @@ describe('OAuth Utils', () => {
})
it('should return undefined if no session is found', async () => {
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue(null),
}))
const { getUserId } = await import('@/app/api/auth/oauth/utils')
mockGetSession.mockResolvedValueOnce(null)
const userId = await getUserId('request-id')
@@ -87,8 +93,6 @@ describe('OAuth Utils', () => {
it('should return undefined if workflow is not found', async () => {
mockDb.limit.mockReturnValueOnce([])
const { getUserId } = await import('@/app/api/auth/oauth/utils')
const userId = await getUserId('request-id', 'nonexistent-workflow-id')
expect(userId).toBeUndefined()
@@ -101,8 +105,6 @@ describe('OAuth Utils', () => {
const mockCredential = { id: 'credential-id', userId: 'test-user-id' }
mockDb.limit.mockReturnValueOnce([mockCredential])
const { getCredential } = await import('@/app/api/auth/oauth/utils')
const credential = await getCredential('request-id', 'credential-id', 'test-user-id')
expect(mockDb.select).toHaveBeenCalled()
@@ -116,8 +118,6 @@ describe('OAuth Utils', () => {
it('should return undefined when credential is not found', async () => {
mockDb.limit.mockReturnValueOnce([])
const { getCredential } = await import('@/app/api/auth/oauth/utils')
const credential = await getCredential('request-id', 'nonexistent-id', 'test-user-id')
expect(credential).toBeUndefined()
@@ -135,8 +135,6 @@ describe('OAuth Utils', () => {
providerId: 'google',
}
const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
expect(mockRefreshOAuthToken).not.toHaveBeenCalled()
@@ -159,8 +157,6 @@ describe('OAuth Utils', () => {
refreshToken: 'new-refresh-token',
})
const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token')
@@ -183,8 +179,6 @@ describe('OAuth Utils', () => {
mockRefreshOAuthToken.mockResolvedValueOnce(null)
const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
await expect(
refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
).rejects.toThrow('Failed to refresh token')
@@ -201,8 +195,6 @@ describe('OAuth Utils', () => {
providerId: 'google',
}
const { refreshTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
expect(mockRefreshOAuthToken).not.toHaveBeenCalled()
@@ -222,8 +214,6 @@ describe('OAuth Utils', () => {
}
mockDb.limit.mockReturnValueOnce([mockCredential])
const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
expect(mockRefreshOAuthToken).not.toHaveBeenCalled()
@@ -247,8 +237,6 @@ describe('OAuth Utils', () => {
refreshToken: 'new-refresh-token',
})
const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token')
@@ -260,8 +248,6 @@ describe('OAuth Utils', () => {
it('should return null if credential not found', async () => {
mockDb.limit.mockReturnValueOnce([])
const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
const token = await refreshAccessTokenIfNeeded('nonexistent-id', 'test-user-id', 'request-id')
expect(token).toBeNull()
@@ -281,8 +267,6 @@ describe('OAuth Utils', () => {
mockRefreshOAuthToken.mockResolvedValueOnce(null)
const { refreshAccessTokenIfNeeded } = await import('@/app/api/auth/oauth/utils')
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
expect(token).toBeNull()

View File

@@ -0,0 +1,65 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { getCreditBalance } from '@/lib/billing/credits/balance'
import { purchaseCredits } from '@/lib/billing/credits/purchase'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('CreditsAPI')
const PurchaseSchema = z.object({
amount: z.number().min(10).max(1000),
requestId: z.string().uuid(),
})
export async function GET() {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
try {
const { balance, entityType, entityId } = await getCreditBalance(session.user.id)
return NextResponse.json({
success: true,
data: { balance, entityType, entityId },
})
} catch (error) {
logger.error('Failed to get credit balance', { error, userId: session.user.id })
return NextResponse.json({ error: 'Failed to get credit balance' }, { status: 500 })
}
}
export async function POST(request: NextRequest) {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
try {
const body = await request.json()
const validation = PurchaseSchema.safeParse(body)
if (!validation.success) {
return NextResponse.json(
{ error: 'Invalid amount. Must be between $10 and $1000' },
{ status: 400 }
)
}
const result = await purchaseCredits({
userId: session.user.id,
amountDollars: validation.data.amount,
requestId: validation.data.requestId,
})
if (!result.success) {
return NextResponse.json({ error: result.error }, { status: 400 })
}
return NextResponse.json({ success: true })
} catch (error) {
logger.error('Failed to purchase credits', { error, userId: session.user.id })
return NextResponse.json({ error: 'Failed to purchase credits' }, { status: 500 })
}
}

View File

@@ -7,6 +7,76 @@ import { getSimplifiedBillingSummary } from '@/lib/billing/core/billing'
import { getOrganizationBillingData } from '@/lib/billing/core/organization'
import { createLogger } from '@/lib/logs/console/logger'
/**
* Gets the effective billing blocked status for a user.
* If user is in an org, also checks if the org owner is blocked.
*/
async function getEffectiveBillingStatus(userId: string): Promise<{
billingBlocked: boolean
billingBlockedReason: 'payment_failed' | 'dispute' | null
blockedByOrgOwner: boolean
}> {
// Check user's own status
const userStatsRows = await db
.select({
blocked: userStats.billingBlocked,
blockedReason: userStats.billingBlockedReason,
})
.from(userStats)
.where(eq(userStats.userId, userId))
.limit(1)
const userBlocked = userStatsRows.length > 0 ? !!userStatsRows[0].blocked : false
const userBlockedReason = userStatsRows.length > 0 ? userStatsRows[0].blockedReason : null
if (userBlocked) {
return {
billingBlocked: true,
billingBlockedReason: userBlockedReason,
blockedByOrgOwner: false,
}
}
// Check if user is in an org where owner is blocked
const memberships = await db
.select({ organizationId: member.organizationId })
.from(member)
.where(eq(member.userId, userId))
for (const m of memberships) {
const owners = await db
.select({ userId: member.userId })
.from(member)
.where(and(eq(member.organizationId, m.organizationId), eq(member.role, 'owner')))
.limit(1)
if (owners.length > 0 && owners[0].userId !== userId) {
const ownerStats = await db
.select({
blocked: userStats.billingBlocked,
blockedReason: userStats.billingBlockedReason,
})
.from(userStats)
.where(eq(userStats.userId, owners[0].userId))
.limit(1)
if (ownerStats.length > 0 && ownerStats[0].blocked) {
return {
billingBlocked: true,
billingBlockedReason: ownerStats[0].blockedReason,
blockedByOrgOwner: true,
}
}
}
}
return {
billingBlocked: false,
billingBlockedReason: null,
blockedByOrgOwner: false,
}
}
const logger = createLogger('UnifiedBillingAPI')
/**
@@ -45,15 +115,13 @@ export async function GET(request: NextRequest) {
if (context === 'user') {
// Get user billing (may include organization if they're part of one)
billingData = await getSimplifiedBillingSummary(session.user.id, contextId || undefined)
// Attach billingBlocked status for the current user
const stats = await db
.select({ blocked: userStats.billingBlocked })
.from(userStats)
.where(eq(userStats.userId, session.user.id))
.limit(1)
// Attach effective billing blocked status (includes org owner check)
const billingStatus = await getEffectiveBillingStatus(session.user.id)
billingData = {
...billingData,
billingBlocked: stats.length > 0 ? !!stats[0].blocked : false,
billingBlocked: billingStatus.billingBlocked,
billingBlockedReason: billingStatus.billingBlockedReason,
blockedByOrgOwner: billingStatus.blockedByOrgOwner,
}
} else {
// Get user role in organization for permission checks first
@@ -104,17 +172,15 @@ export async function GET(request: NextRequest) {
const userRole = memberRecord[0].role
// Include the requesting user's blocked flag as well so UI can reflect it
const stats = await db
.select({ blocked: userStats.billingBlocked })
.from(userStats)
.where(eq(userStats.userId, session.user.id))
.limit(1)
// Get effective billing blocked status (includes org owner check)
const billingStatus = await getEffectiveBillingStatus(session.user.id)
// Merge blocked flag into data for convenience
billingData = {
...billingData,
billingBlocked: stats.length > 0 ? !!stats[0].blocked : false,
billingBlocked: billingStatus.billingBlocked,
billingBlockedReason: billingStatus.billingBlockedReason,
blockedByOrgOwner: billingStatus.blockedByOrgOwner,
}
return NextResponse.json({
@@ -123,6 +189,8 @@ export async function GET(request: NextRequest) {
data: billingData,
userRole,
billingBlocked: billingData.billingBlocked,
billingBlockedReason: billingData.billingBlockedReason,
blockedByOrgOwner: billingData.blockedByOrgOwner,
})
}

View File

@@ -3,6 +3,7 @@ import { userStats } from '@sim/db/schema'
import { eq, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { deductFromCredits } from '@/lib/billing/credits/balance'
import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
import { checkInternalApiKey } from '@/lib/copilot/utils'
import { isBillingEnabled } from '@/lib/core/config/environment'
@@ -90,13 +91,18 @@ export async function POST(req: NextRequest) {
)
return NextResponse.json({ error: 'User stats record not found' }, { status: 500 })
}
// Update existing user stats record
const { creditsUsed, overflow } = await deductFromCredits(userId, cost)
if (creditsUsed > 0) {
logger.info(`[${requestId}] Deducted cost from credits`, { userId, creditsUsed, overflow })
}
const costToStore = overflow
const updateFields = {
totalCost: sql`total_cost + ${cost}`,
currentPeriodCost: sql`current_period_cost + ${cost}`,
// Copilot usage tracking increments
totalCopilotCost: sql`total_copilot_cost + ${cost}`,
currentPeriodCopilotCost: sql`current_period_copilot_cost + ${cost}`,
totalCost: sql`total_cost + ${costToStore}`,
currentPeriodCost: sql`current_period_cost + ${costToStore}`,
totalCopilotCost: sql`total_copilot_cost + ${costToStore}`,
currentPeriodCopilotCost: sql`current_period_copilot_cost + ${costToStore}`,
totalCopilotCalls: sql`total_copilot_calls + 1`,
lastActive: new Date(),
}

View File

@@ -1,47 +1,60 @@
import { NextRequest } from 'next/server'
/**
* Tests for function execution API route
*
* @vitest-environment node
*/
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createMockRequest } from '@/app/api/__test-utils__/utils'
const mockCreateContext = vi.fn()
const mockRunInContext = vi.fn()
const mockLogger = {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
}
const mockScript = vi.fn()
const mockExecuteInE2B = vi.fn()
vi.mock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn(() => ({
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
})),
}))
vi.mock('vm', () => ({
createContext: vi.fn(),
Script: vi.fn(),
}))
vi.mock('@/lib/execution/e2b', () => ({
executeInE2B: vi.fn(),
}))
import { createContext, Script } from 'vm'
import { validateProxyUrl } from '@/lib/core/security/input-validation'
import { executeInE2B } from '@/lib/execution/e2b'
import { createLogger } from '@/lib/logs/console/logger'
import { POST } from './route'
const mockedCreateContext = vi.mocked(createContext)
const mockedScript = vi.mocked(Script)
const mockedExecuteInE2B = vi.mocked(executeInE2B)
const mockedCreateLogger = vi.mocked(createLogger)
describe('Function Execute API Route', () => {
beforeEach(() => {
vi.resetModules()
vi.resetAllMocks()
vi.doMock('vm', () => ({
createContext: mockCreateContext,
Script: vi.fn().mockImplementation(() => ({
runInContext: mockRunInContext,
})),
}))
vi.doMock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn().mockReturnValue(mockLogger),
}))
vi.doMock('@/lib/execution/e2b', () => ({
executeInE2B: vi.fn().mockResolvedValue({
result: 'e2b success',
stdout: 'e2b output',
sandboxId: 'test-sandbox-id',
}),
}))
vi.clearAllMocks()
mockedCreateContext.mockReturnValue({})
mockRunInContext.mockResolvedValue('vm success')
mockCreateContext.mockReturnValue({})
mockedScript.mockImplementation((): any => ({
runInContext: mockRunInContext,
}))
mockedExecuteInE2B.mockResolvedValue({
result: 'e2b success',
stdout: 'e2b output',
sandboxId: 'test-sandbox-id',
})
})
afterEach(() => {
@@ -54,20 +67,17 @@ describe('Function Execute API Route', () => {
code: 'return "test"',
})
const { POST } = await import('@/app/api/function/execute/route')
await POST(req)
expect(mockCreateContext).toHaveBeenCalled()
const contextArgs = mockCreateContext.mock.calls[0][0]
expect(mockedCreateContext).toHaveBeenCalled()
const contextArgs = mockedCreateContext.mock.calls[0][0]
expect(contextArgs).toHaveProperty('fetch')
expect(typeof contextArgs.fetch).toBe('function')
expect(typeof (contextArgs as any).fetch).toBe('function')
expect(contextArgs.fetch.name).toBe('secureFetch')
expect((contextArgs as any).fetch?.name).toBe('secureFetch')
})
it.concurrent('should block SSRF attacks through secure fetch wrapper', async () => {
const { validateProxyUrl } = await import('@/lib/core/security/input-validation')
expect(validateProxyUrl('http://169.254.169.254/latest/meta-data/').isValid).toBe(false)
expect(validateProxyUrl('http://127.0.0.1:8080/admin').isValid).toBe(false)
expect(validateProxyUrl('http://192.168.1.1/config').isValid).toBe(false)
@@ -75,16 +85,12 @@ describe('Function Execute API Route', () => {
})
it.concurrent('should allow legitimate external URLs', async () => {
const { validateProxyUrl } = await import('@/lib/core/security/input-validation')
expect(validateProxyUrl('https://api.github.com/user').isValid).toBe(true)
expect(validateProxyUrl('https://httpbin.org/get').isValid).toBe(true)
expect(validateProxyUrl('https://example.com/api').isValid).toBe(true)
})
it.concurrent('should block dangerous protocols', async () => {
const { validateProxyUrl } = await import('@/lib/core/security/input-validation')
expect(validateProxyUrl('file:///etc/passwd').isValid).toBe(false)
expect(validateProxyUrl('ftp://internal.server/files').isValid).toBe(false)
expect(validateProxyUrl('gopher://old.server/menu').isValid).toBe(false)
@@ -98,7 +104,6 @@ describe('Function Execute API Route', () => {
timeout: 5000,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -113,7 +118,6 @@ describe('Function Execute API Route', () => {
timeout: 5000,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -127,12 +131,11 @@ describe('Function Execute API Route', () => {
code: 'return "test"',
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
expect(response.status).toBe(200)
// The logger now logs execution success, not the request details
expect(mockLogger.info).toHaveBeenCalled()
expect(data.success).toBe(true)
})
})
@@ -145,11 +148,9 @@ describe('Function Execute API Route', () => {
},
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
// The code should be resolved to: return "secret-key-123"
})
it.concurrent('should resolve tag variables with <tag_name> syntax', async () => {
@@ -160,11 +161,9 @@ describe('Function Execute API Route', () => {
},
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
// The code should be resolved with the email object
})
it.concurrent('should NOT treat email addresses as template variables', async () => {
@@ -178,11 +177,9 @@ describe('Function Execute API Route', () => {
},
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
// Should not try to replace <waleed@sim.ai> as a template variable
})
it.concurrent('should only match valid variable names in angle brackets', async () => {
@@ -194,11 +191,9 @@ describe('Function Execute API Route', () => {
},
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
// Should replace <validVar> and <another_valid> but not <invalid@email.com>
})
})
@@ -230,7 +225,6 @@ describe('Function Execute API Route', () => {
params: gmailData,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
@@ -255,7 +249,6 @@ describe('Function Execute API Route', () => {
params: complexEmailData,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
@@ -273,11 +266,9 @@ describe('Function Execute API Route', () => {
isCustomTool: true,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
// For custom tools, parameters should be directly accessible as variables
})
})
@@ -289,7 +280,6 @@ describe('Function Execute API Route', () => {
headers: { 'Content-Type': 'application/json' },
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(500)
@@ -301,15 +291,11 @@ describe('Function Execute API Route', () => {
timeout: 10000,
})
const { POST } = await import('@/app/api/function/execute/route')
await POST(req)
const response = await POST(req)
const data = await response.json()
expect(mockLogger.info).toHaveBeenCalledWith(
expect.stringMatching(/\[.*\] Function execution request/),
expect.objectContaining({
timeout: 10000,
})
)
expect(response.status).toBe(200)
expect(data.success).toBe(true)
})
it.concurrent('should handle empty parameters object', async () => {
@@ -318,7 +304,6 @@ describe('Function Execute API Route', () => {
params: {},
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
@@ -327,31 +312,25 @@ describe('Function Execute API Route', () => {
describe('Enhanced Error Handling', () => {
it('should provide detailed syntax error with line content', async () => {
// Mock VM Script to throw a syntax error
const mockScript = vi.fn().mockImplementation(() => {
const error = new Error('Invalid or unexpected token')
error.name = 'SyntaxError'
error.stack = `user-function.js:5
const syntaxError = new Error('Invalid or unexpected token')
syntaxError.name = 'SyntaxError'
syntaxError.stack = `user-function.js:5
description: "This has a missing closing quote
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: Invalid or unexpected token
at new Script (node:vm:117:7)
at POST (/path/to/route.ts:123:24)`
throw error
})
vi.doMock('vm', () => ({
createContext: mockCreateContext,
Script: mockScript,
}))
mockedScript.mockImplementationOnce(() => {
throw syntaxError
})
const req = createMockRequest('POST', {
code: 'const obj = {\n name: "test",\n description: "This has a missing closing quote\n};\nreturn obj;',
timeout: 5000,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -363,7 +342,6 @@ SyntaxError: Invalid or unexpected token
expect(data.error).toContain('Invalid or unexpected token')
expect(data.error).toContain('(Check for missing quotes, brackets, or semicolons)')
// Check debug information
expect(data.debug).toBeDefined()
expect(data.debug.line).toBe(3)
expect(data.debug.errorType).toBe('SyntaxError')
@@ -371,7 +349,6 @@ SyntaxError: Invalid or unexpected token
})
it('should provide detailed runtime error with line and column', async () => {
// Create the error object first
const runtimeError = new Error("Cannot read properties of null (reading 'someMethod')")
runtimeError.name = 'TypeError'
runtimeError.stack = `TypeError: Cannot read properties of null (reading 'someMethod')
@@ -379,22 +356,13 @@ SyntaxError: Invalid or unexpected token
at user-function.js:9:3
at Script.runInContext (node:vm:147:14)`
// Mock successful script creation but runtime error
const mockScript = vi.fn().mockImplementation(() => ({
runInContext: vi.fn().mockRejectedValue(runtimeError),
}))
vi.doMock('vm', () => ({
createContext: mockCreateContext,
Script: mockScript,
}))
mockRunInContext.mockRejectedValueOnce(runtimeError)
const req = createMockRequest('POST', {
code: 'const obj = null;\nreturn obj.someMethod();',
timeout: 5000,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -405,7 +373,6 @@ SyntaxError: Invalid or unexpected token
expect(data.error).toContain('return obj.someMethod();')
expect(data.error).toContain('Cannot read properties of null')
// Check debug information
expect(data.debug).toBeDefined()
expect(data.debug.line).toBe(2)
expect(data.debug.column).toBe(16)
@@ -414,28 +381,19 @@ SyntaxError: Invalid or unexpected token
})
it('should handle ReferenceError with enhanced details', async () => {
// Create the error object first
const referenceError = new Error('undefinedVariable is not defined')
referenceError.name = 'ReferenceError'
referenceError.stack = `ReferenceError: undefinedVariable is not defined
at user-function.js:4:8
at Script.runInContext (node:vm:147:14)`
const mockScript = vi.fn().mockImplementation(() => ({
runInContext: vi.fn().mockRejectedValue(referenceError),
}))
vi.doMock('vm', () => ({
createContext: mockCreateContext,
Script: mockScript,
}))
mockRunInContext.mockRejectedValueOnce(referenceError)
const req = createMockRequest('POST', {
code: 'const x = 42;\nreturn undefinedVariable + x;',
timeout: 5000,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -448,24 +406,18 @@ SyntaxError: Invalid or unexpected token
})
it('should handle errors without line content gracefully', async () => {
const mockScript = vi.fn().mockImplementation(() => {
const error = new Error('Generic error without stack trace')
error.name = 'Error'
// No stack trace
throw error
})
const genericError = new Error('Generic error without stack trace')
genericError.name = 'Error'
vi.doMock('vm', () => ({
createContext: mockCreateContext,
Script: mockScript,
}))
mockedScript.mockImplementationOnce(() => {
throw genericError
})
const req = createMockRequest('POST', {
code: 'return "test";',
timeout: 5000,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -473,7 +425,6 @@ SyntaxError: Invalid or unexpected token
expect(data.success).toBe(false)
expect(data.error).toBe('Generic error without stack trace')
// Should still have debug info, but without line details
expect(data.debug).toBeDefined()
expect(data.debug.errorType).toBe('Error')
expect(data.debug.line).toBeUndefined()
@@ -481,58 +432,47 @@ SyntaxError: Invalid or unexpected token
})
it('should extract line numbers from different stack trace formats', async () => {
const mockScript = vi.fn().mockImplementation(() => {
const error = new Error('Test error')
error.name = 'Error'
error.stack = `Error: Test error
const testError = new Error('Test error')
testError.name = 'Error'
testError.stack = `Error: Test error
at user-function.js:7:25
at async function
at Script.runInContext (node:vm:147:14)`
throw error
})
vi.doMock('vm', () => ({
createContext: mockCreateContext,
Script: mockScript,
}))
mockedScript.mockImplementationOnce(() => {
throw testError
})
const req = createMockRequest('POST', {
code: 'const a = 1;\nconst b = 2;\nconst c = 3;\nconst d = 4;\nreturn a + b + c + d;',
timeout: 5000,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
expect(response.status).toBe(500)
expect(data.success).toBe(false)
// Line 7 in VM should map to line 5 in user code (7 - 3 + 1 = 5)
expect(data.debug.line).toBe(5)
expect(data.debug.column).toBe(25)
expect(data.debug.lineContent).toBe('return a + b + c + d;')
})
it.concurrent('should provide helpful suggestions for common syntax errors', async () => {
const mockScript = vi.fn().mockImplementation(() => {
const error = new Error('Unexpected end of input')
error.name = 'SyntaxError'
error.stack = 'user-function.js:4\nSyntaxError: Unexpected end of input'
throw error
})
const syntaxError = new Error('Unexpected end of input')
syntaxError.name = 'SyntaxError'
syntaxError.stack = 'user-function.js:4\nSyntaxError: Unexpected end of input'
vi.doMock('vm', () => ({
createContext: mockCreateContext,
Script: mockScript,
}))
mockedScript.mockImplementationOnce(() => {
throw syntaxError
})
const req = createMockRequest('POST', {
code: 'const obj = {\n name: "test"\n// Missing closing brace',
timeout: 5000,
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
const data = await response.json()
@@ -546,7 +486,6 @@ SyntaxError: Invalid or unexpected token
describe('Utility Functions', () => {
it.concurrent('should properly escape regex special characters', async () => {
// This tests the escapeRegExp function indirectly
const req = createMockRequest('POST', {
code: 'return {{special.chars+*?}}',
envVars: {
@@ -554,15 +493,12 @@ SyntaxError: Invalid or unexpected token
},
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)
// Should handle special regex characters in variable names
})
it.concurrent('should handle JSON serialization edge cases', async () => {
// Test with complex but not circular data first
const req = createMockRequest('POST', {
code: 'return <complexData>',
params: {
@@ -578,7 +514,6 @@ SyntaxError: Invalid or unexpected token
},
})
const { POST } = await import('@/app/api/function/execute/route')
const response = await POST(req)
expect(response.status).toBe(200)

View File

@@ -4,148 +4,207 @@
* @vitest-environment node
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
createMockRequest,
mockExecutionDependencies,
sampleWorkflowState,
} from '@/app/api/__test-utils__/utils'
import { createMockRequest, mockExecutionDependencies } from '@/app/api/__test-utils__/utils'
const {
mockGetSession,
mockGetUserEntityPermissions,
mockSelectLimit,
mockInsertValues,
mockOnConflictDoUpdate,
mockInsert,
mockUpdate,
mockDelete,
mockTransaction,
mockRandomUUID,
mockGetScheduleTimeValues,
mockGetSubBlockValue,
mockGenerateCronExpression,
mockCalculateNextRunTime,
mockValidateCronExpression,
} = vi.hoisted(() => ({
mockGetSession: vi.fn(),
mockGetUserEntityPermissions: vi.fn(),
mockSelectLimit: vi.fn(),
mockInsertValues: vi.fn(),
mockOnConflictDoUpdate: vi.fn(),
mockInsert: vi.fn(),
mockUpdate: vi.fn(),
mockDelete: vi.fn(),
mockTransaction: vi.fn(),
mockRandomUUID: vi.fn(),
mockGetScheduleTimeValues: vi.fn(),
mockGetSubBlockValue: vi.fn(),
mockGenerateCronExpression: vi.fn(),
mockCalculateNextRunTime: vi.fn(),
mockValidateCronExpression: vi.fn(),
}))
vi.mock('@/lib/auth', () => ({
getSession: mockGetSession,
}))
vi.mock('@/lib/workspaces/permissions/utils', () => ({
getUserEntityPermissions: mockGetUserEntityPermissions,
}))
vi.mock('@sim/db', () => ({
db: {
select: vi.fn().mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: mockSelectLimit,
}),
}),
}),
insert: mockInsert,
update: mockUpdate,
delete: mockDelete,
},
}))
vi.mock('@sim/db/schema', () => ({
workflow: {
id: 'workflow_id',
userId: 'user_id',
workspaceId: 'workspace_id',
},
workflowSchedule: {
id: 'schedule_id',
workflowId: 'workflow_id',
blockId: 'block_id',
cronExpression: 'cron_expression',
nextRunAt: 'next_run_at',
status: 'status',
},
}))
vi.mock('drizzle-orm', () => ({
eq: vi.fn((...args) => ({ type: 'eq', args })),
and: vi.fn((...args) => ({ type: 'and', args })),
}))
vi.mock('crypto', () => ({
randomUUID: mockRandomUUID,
default: {
randomUUID: mockRandomUUID,
},
}))
vi.mock('@/lib/workflows/schedules/utils', () => ({
getScheduleTimeValues: mockGetScheduleTimeValues,
getSubBlockValue: mockGetSubBlockValue,
generateCronExpression: mockGenerateCronExpression,
calculateNextRunTime: mockCalculateNextRunTime,
validateCronExpression: mockValidateCronExpression,
BlockState: {},
}))
vi.mock('@/lib/core/utils/request', () => ({
generateRequestId: vi.fn(() => 'test-request-id'),
}))
vi.mock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn(() => ({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
})),
}))
vi.mock('@/lib/core/telemetry', () => ({
trackPlatformEvent: vi.fn(),
}))
import { db } from '@sim/db'
import { POST } from '@/app/api/schedules/route'
describe('Schedule Configuration API Route', () => {
beforeEach(() => {
vi.resetModules()
vi.clearAllMocks()
;(db as any).transaction = mockTransaction
mockExecutionDependencies()
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: {
id: 'user-id',
email: 'test@example.com',
},
}),
}))
vi.doMock('@/lib/workspaces/permissions/utils', () => ({
getUserEntityPermissions: vi.fn().mockResolvedValue('admin'), // User has admin permissions
}))
const _workflowStateWithSchedule = {
...sampleWorkflowState,
blocks: {
...sampleWorkflowState.blocks,
'starter-id': {
...sampleWorkflowState.blocks['starter-id'],
subBlocks: {
...sampleWorkflowState.blocks['starter-id'].subBlocks,
startWorkflow: { id: 'startWorkflow', type: 'dropdown', value: 'schedule' },
scheduleType: { id: 'scheduleType', type: 'dropdown', value: 'daily' },
scheduleTime: { id: 'scheduleTime', type: 'time-input', value: '09:30' },
dailyTime: { id: 'dailyTime', type: 'time-input', value: '09:30' },
},
},
mockGetSession.mockResolvedValue({
user: {
id: 'user-id',
email: 'test@example.com',
},
}
vi.doMock('@sim/db', () => {
let callCount = 0
const mockInsert = {
values: vi.fn().mockImplementation(() => ({
onConflictDoUpdate: vi.fn().mockResolvedValue({}),
})),
}
const mockDb = {
select: vi.fn().mockImplementation(() => ({
from: vi.fn().mockImplementation(() => ({
where: vi.fn().mockImplementation(() => ({
limit: vi.fn().mockImplementation(() => {
callCount++
// First call: workflow lookup for authorization
if (callCount === 1) {
return [
{
id: 'workflow-id',
userId: 'user-id',
workspaceId: null, // User owns the workflow directly
},
]
}
// Second call: existing schedule lookup - return existing schedule for update test
return [
{
id: 'existing-schedule-id',
workflowId: 'workflow-id',
blockId: 'starter-id',
cronExpression: '0 9 * * *',
nextRunAt: new Date(),
status: 'active',
},
]
}),
})),
})),
})),
insert: vi.fn().mockReturnValue(mockInsert),
update: vi.fn().mockImplementation(() => ({
set: vi.fn().mockImplementation(() => ({
where: vi.fn().mockResolvedValue([]),
})),
})),
delete: vi.fn().mockImplementation(() => ({
where: vi.fn().mockResolvedValue([]),
})),
transaction: vi.fn().mockImplementation(async (callback) => {
const tx = {
insert: vi.fn().mockReturnValue(mockInsert),
}
return callback(tx)
}),
}
return { db: mockDb }
})
vi.doMock('crypto', () => ({
randomUUID: vi.fn(() => 'test-uuid'),
default: {
randomUUID: vi.fn(() => 'test-uuid'),
mockGetUserEntityPermissions.mockResolvedValue('admin')
mockSelectLimit.mockReturnValue([
{
id: 'workflow-id',
userId: 'user-id',
workspaceId: null,
},
])
mockInsertValues.mockImplementation(() => ({
onConflictDoUpdate: mockOnConflictDoUpdate,
}))
mockOnConflictDoUpdate.mockResolvedValue({})
mockInsert.mockReturnValue({
values: mockInsertValues,
})
mockUpdate.mockImplementation(() => ({
set: vi.fn().mockImplementation(() => ({
where: vi.fn().mockResolvedValue([]),
})),
}))
vi.doMock('@/lib/workflows/schedules/utils', () => ({
getScheduleTimeValues: vi.fn().mockReturnValue({
scheduleTime: '09:30',
minutesInterval: 15,
hourlyMinute: 0,
dailyTime: [9, 30],
weeklyDay: 1,
weeklyTime: [9, 30],
monthlyDay: 1,
monthlyTime: [9, 30],
}),
getSubBlockValue: vi.fn().mockImplementation((block: any, id: string) => {
const subBlocks = {
startWorkflow: 'schedule',
scheduleType: 'daily',
scheduleTime: '09:30',
dailyTime: '09:30',
}
return subBlocks[id as keyof typeof subBlocks] || ''
}),
generateCronExpression: vi.fn().mockReturnValue('0 9 * * *'),
calculateNextRunTime: vi.fn().mockReturnValue(new Date()),
validateCronExpression: vi.fn().mockReturnValue({ isValid: true }),
BlockState: {},
mockDelete.mockImplementation(() => ({
where: vi.fn().mockResolvedValue([]),
}))
mockTransaction.mockImplementation(async (callback) => {
const tx = {
insert: vi.fn().mockReturnValue({
values: mockInsertValues,
}),
}
return callback(tx)
})
mockRandomUUID.mockReturnValue('test-uuid')
mockGetScheduleTimeValues.mockReturnValue({
scheduleTime: '09:30',
minutesInterval: 15,
hourlyMinute: 0,
dailyTime: [9, 30],
weeklyDay: 1,
weeklyTime: [9, 30],
monthlyDay: 1,
monthlyTime: [9, 30],
})
mockGetSubBlockValue.mockImplementation((block: any, id: string) => {
const subBlocks = {
startWorkflow: 'schedule',
scheduleType: 'daily',
scheduleTime: '09:30',
dailyTime: '09:30',
}
return subBlocks[id as keyof typeof subBlocks] || ''
})
mockGenerateCronExpression.mockReturnValue('0 9 * * *')
mockCalculateNextRunTime.mockReturnValue(new Date())
mockValidateCronExpression.mockReturnValue({ isValid: true })
})
afterEach(() => {
vi.clearAllMocks()
})
/**
* Test creating a new schedule
*/
it('should create a new schedule successfully', async () => {
const req = createMockRequest('POST', {
workflowId: 'workflow-id',
@@ -166,8 +225,6 @@ describe('Schedule Configuration API Route', () => {
},
})
const { POST } = await import('@/app/api/schedules/route')
const response = await POST(req)
expect(response).toBeDefined()
@@ -177,38 +234,16 @@ describe('Schedule Configuration API Route', () => {
expect(responseData).toHaveProperty('message', 'Schedule updated')
expect(responseData).toHaveProperty('cronExpression', '0 9 * * *')
expect(responseData).toHaveProperty('nextRunAt')
// We can't verify the utility functions were called directly
// since we're mocking them at the module level
// Instead, we just verify that the response has the expected properties
})
/**
* Test error handling
*/
it('should handle errors gracefully', async () => {
vi.doMock('@sim/db', () => ({
db: {
select: vi.fn().mockImplementation(() => ({
from: vi.fn().mockImplementation(() => ({
where: vi.fn().mockImplementation(() => ({
limit: vi.fn().mockImplementation(() => []),
})),
})),
})),
insert: vi.fn().mockImplementation(() => {
throw new Error('Database error')
}),
},
}))
mockSelectLimit.mockReturnValue([])
const req = createMockRequest('POST', {
workflowId: 'workflow-id',
state: { blocks: {}, edges: [], loops: {} },
})
const { POST } = await import('@/app/api/schedules/route')
const response = await POST(req)
expect(response.status).toBeGreaterThanOrEqual(400)
@@ -216,21 +251,14 @@ describe('Schedule Configuration API Route', () => {
expect(data).toHaveProperty('error')
})
/**
* Test authentication requirement
*/
it('should require authentication', async () => {
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue(null),
}))
mockGetSession.mockResolvedValue(null)
const req = createMockRequest('POST', {
workflowId: 'workflow-id',
state: { blocks: {}, edges: [], loops: {} },
})
const { POST } = await import('@/app/api/schedules/route')
const response = await POST(req)
expect(response.status).toBe(401)
@@ -238,16 +266,11 @@ describe('Schedule Configuration API Route', () => {
expect(data).toHaveProperty('error', 'Unauthorized')
})
/**
* Test invalid data handling
*/
it('should validate input data', async () => {
const req = createMockRequest('POST', {
workflowId: 'workflow-id',
})
const { POST } = await import('@/app/api/schedules/route')
const response = await POST(req)
expect(response.status).toBe(400)

View File

@@ -57,6 +57,35 @@ export async function GET(request: NextRequest) {
}
)
if (!response.ok && response.status === 404) {
logger.info(`[${requestId}] File not found, checking if it's a shared drive`)
const driveResponse = await fetch(
`https://www.googleapis.com/drive/v3/drives/${fileId}?fields=id,name`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
)
if (driveResponse.ok) {
const driveData = await driveResponse.json()
logger.info(`[${requestId}] Found shared drive: ${driveData.name}`)
return NextResponse.json(
{
file: {
id: driveData.id,
name: driveData.name,
mimeType: 'application/vnd.google-apps.folder',
iconLink:
'https://ssl.gstatic.com/docs/doclist/images/icon_11_shared_collection_list_1.png',
},
},
{ status: 200 }
)
}
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } }))
logger.error(`[${requestId}] Google Drive API error`, {
@@ -112,12 +141,12 @@ export async function GET(request: NextRequest) {
if (!file.exportLinks) {
file.downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}/export?mimeType=${encodeURIComponent(
format
)}`
)}&supportsAllDrives=true`
} else {
file.downloadUrl = file.exportLinks[format]
}
} else {
file.downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`
file.downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media&supportsAllDrives=true`
}
return NextResponse.json({ file }, { status: 200 })

View File

@@ -12,6 +12,62 @@ function escapeForDriveQuery(value: string): string {
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")
}
interface SharedDrive {
id: string
name: string
kind: string
}
interface DriveFile {
id: string
name: string
mimeType: string
iconLink?: string
webViewLink?: string
thumbnailLink?: string
createdTime?: string
modifiedTime?: string
size?: string
owners?: any[]
parents?: string[]
}
/**
* Fetches shared drives the user has access to
*/
async function fetchSharedDrives(accessToken: string, requestId: string): Promise<DriveFile[]> {
try {
const response = await fetch(
'https://www.googleapis.com/drive/v3/drives?pageSize=100&fields=drives(id,name)',
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
)
if (!response.ok) {
logger.warn(`[${requestId}] Failed to fetch shared drives`, {
status: response.status,
})
return []
}
const data = await response.json()
const drives: SharedDrive[] = data.drives || []
return drives.map((drive) => ({
id: drive.id,
name: drive.name,
mimeType: 'application/vnd.google-apps.folder',
iconLink: 'https://ssl.gstatic.com/docs/doclist/images/icon_11_shared_collection_list_1.png',
}))
} catch (error) {
logger.error(`[${requestId}] Error fetching shared drives`, error)
return []
}
}
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
logger.info(`[${requestId}] Google Drive files request received`)
@@ -65,7 +121,7 @@ export async function GET(request: NextRequest) {
const q = encodeURIComponent(qParts.join(' and '))
const response = await fetch(
`https://www.googleapis.com/drive/v3/files?q=${q}&supportsAllDrives=true&includeItemsFromAllDrives=true&spaces=drive&fields=files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,parents)`,
`https://www.googleapis.com/drive/v3/files?q=${q}&corpora=allDrives&supportsAllDrives=true&includeItemsFromAllDrives=true&fields=files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,parents)`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
@@ -88,14 +144,26 @@ export async function GET(request: NextRequest) {
}
const data = await response.json()
let files = data.files || []
let files: DriveFile[] = data.files || []
if (mimeType === 'application/vnd.google-apps.spreadsheet') {
files = files.filter(
(file: any) => file.mimeType === 'application/vnd.google-apps.spreadsheet'
(file: DriveFile) => file.mimeType === 'application/vnd.google-apps.spreadsheet'
)
} else if (mimeType === 'application/vnd.google-apps.document') {
files = files.filter((file: any) => file.mimeType === 'application/vnd.google-apps.document')
files = files.filter(
(file: DriveFile) => file.mimeType === 'application/vnd.google-apps.document'
)
}
const isRootFolderListing =
!folderId && mimeType === 'application/vnd.google-apps.folder' && !query
if (isRootFolderListing) {
const sharedDrives = await fetchSharedDrives(accessToken, requestId)
if (sharedDrives.length > 0) {
logger.info(`[${requestId}] Found ${sharedDrives.length} shared drives`)
files = [...sharedDrives, ...files]
}
}
return NextResponse.json({ files }, { status: 200 })

View File

@@ -111,7 +111,10 @@ export async function PUT(request: NextRequest) {
const userId = session.user.id
if (context === 'user') {
await updateUserUsageLimit(userId, limit)
const result = await updateUserUsageLimit(userId, limit)
if (!result.success) {
return NextResponse.json({ error: result.error }, { status: 400 })
}
} else if (context === 'organization') {
// organizationId is guaranteed to exist by Zod refinement
const hasPermission = await isOrganizationOwnerOrAdmin(session.user.id, organizationId!)

View File

@@ -8,45 +8,62 @@
import { NextRequest } from 'next/server'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
describe('Workflow By ID API Route', () => {
const mockLogger = {
const mockGetSession = vi.fn()
const mockLoadWorkflowFromNormalizedTables = vi.fn()
const mockGetWorkflowById = vi.fn()
const mockGetWorkflowAccessContext = vi.fn()
const mockDbDelete = vi.fn()
const mockDbUpdate = vi.fn()
vi.mock('@/lib/auth', () => ({
getSession: () => mockGetSession(),
}))
vi.mock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
})),
}))
vi.mock('@/lib/workflows/persistence/utils', () => ({
loadWorkflowFromNormalizedTables: (workflowId: string) =>
mockLoadWorkflowFromNormalizedTables(workflowId),
}))
vi.mock('@/lib/workflows/utils', async () => {
const actual =
await vi.importActual<typeof import('@/lib/workflows/utils')>('@/lib/workflows/utils')
return {
...actual,
getWorkflowById: (workflowId: string) => mockGetWorkflowById(workflowId),
getWorkflowAccessContext: (workflowId: string, userId?: string) =>
mockGetWorkflowAccessContext(workflowId, userId),
}
})
const mockGetWorkflowById = vi.fn()
const mockGetWorkflowAccessContext = vi.fn()
vi.mock('@sim/db', () => ({
db: {
delete: () => mockDbDelete(),
update: () => mockDbUpdate(),
},
workflow: {},
}))
import { DELETE, GET, PUT } from './route'
describe('Workflow By ID API Route', () => {
beforeEach(() => {
vi.resetModules()
vi.clearAllMocks()
vi.stubGlobal('crypto', {
randomUUID: vi.fn().mockReturnValue('mock-request-id-12345678'),
})
vi.doMock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn().mockReturnValue(mockLogger),
}))
vi.doMock('@/lib/workflows/persistence/utils', () => ({
loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(null),
}))
mockGetWorkflowById.mockReset()
mockGetWorkflowAccessContext.mockReset()
vi.doMock('@/lib/workflows/utils', async () => {
const actual =
await vi.importActual<typeof import('@/lib/workflows/utils')>('@/lib/workflows/utils')
return {
...actual,
getWorkflowById: mockGetWorkflowById,
getWorkflowAccessContext: mockGetWorkflowAccessContext,
}
})
mockLoadWorkflowFromNormalizedTables.mockResolvedValue(null)
})
afterEach(() => {
@@ -55,14 +72,11 @@ describe('Workflow By ID API Route', () => {
describe('GET /api/workflows/[id]', () => {
it('should return 401 when user is not authenticated', async () => {
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue(null),
}))
mockGetSession.mockResolvedValue(null)
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(401)
@@ -71,14 +85,12 @@ describe('Workflow By ID API Route', () => {
})
it('should return 404 when workflow does not exist', async () => {
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(null)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(null)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: null,
workspaceOwnerId: null,
workspacePermission: null,
@@ -89,7 +101,6 @@ describe('Workflow By ID API Route', () => {
const req = new NextRequest('http://localhost:3000/api/workflows/nonexistent')
const params = Promise.resolve({ id: 'nonexistent' })
const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(404)
@@ -113,14 +124,12 @@ describe('Workflow By ID API Route', () => {
isFromNormalizedTables: true,
}
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -128,23 +137,11 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
vi.doMock('@/lib/workflows/persistence/utils', () => ({
loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(mockNormalizedData),
}))
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
isOwner: true,
isWorkspaceOwner: false,
})
mockLoadWorkflowFromNormalizedTables.mockResolvedValue(mockNormalizedData)
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(200)
@@ -168,27 +165,12 @@ describe('Workflow By ID API Route', () => {
isFromNormalizedTables: true,
}
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'admin',
isOwner: false,
isWorkspaceOwner: false,
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
vi.doMock('@/lib/workflows/persistence/utils', () => ({
loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(mockNormalizedData),
}))
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'read',
@@ -196,15 +178,11 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
vi.doMock('@/lib/workspaces/permissions/utils', () => ({
getUserEntityPermissions: vi.fn().mockResolvedValue('read'),
hasAdminPermission: vi.fn().mockResolvedValue(false),
}))
mockLoadWorkflowFromNormalizedTables.mockResolvedValue(mockNormalizedData)
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(200)
@@ -220,14 +198,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: 'workspace-456',
}
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: null,
@@ -238,7 +214,6 @@ describe('Workflow By ID API Route', () => {
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(403)
@@ -262,14 +237,12 @@ describe('Workflow By ID API Route', () => {
isFromNormalizedTables: true,
}
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -277,14 +250,11 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
vi.doMock('@/lib/workflows/persistence/utils', () => ({
loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(mockNormalizedData),
}))
mockLoadWorkflowFromNormalizedTables.mockResolvedValue(mockNormalizedData)
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(200)
@@ -303,14 +273,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: null,
}
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -318,14 +286,9 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
vi.doMock('@sim/db', () => ({
db: {
delete: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([{ id: 'workflow-123' }]),
}),
},
workflow: {},
}))
mockDbDelete.mockReturnValue({
where: vi.fn().mockResolvedValue([{ id: 'workflow-123' }]),
})
global.fetch = vi.fn().mockResolvedValue({
ok: true,
@@ -336,7 +299,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
const { DELETE } = await import('@/app/api/workflows/[id]/route')
const response = await DELETE(req, { params })
expect(response.status).toBe(200)
@@ -352,14 +314,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: 'workspace-456',
}
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'admin',
@@ -367,14 +327,9 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
vi.doMock('@sim/db', () => ({
db: {
delete: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([{ id: 'workflow-123' }]),
}),
},
workflow: {},
}))
mockDbDelete.mockReturnValue({
where: vi.fn().mockResolvedValue([{ id: 'workflow-123' }]),
})
global.fetch = vi.fn().mockResolvedValue({
ok: true,
@@ -385,7 +340,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
const { DELETE } = await import('@/app/api/workflows/[id]/route')
const response = await DELETE(req, { params })
expect(response.status).toBe(200)
@@ -401,14 +355,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: 'workspace-456',
}
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: null,
@@ -421,7 +373,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
const { DELETE } = await import('@/app/api/workflows/[id]/route')
const response = await DELETE(req, { params })
expect(response.status).toBe(403)
@@ -442,14 +393,12 @@ describe('Workflow By ID API Route', () => {
const updateData = { name: 'Updated Workflow' }
const updatedWorkflow = { ...mockWorkflow, ...updateData, updatedAt: new Date() }
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -457,18 +406,13 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
vi.doMock('@sim/db', () => ({
db: {
update: vi.fn().mockReturnValue({
set: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
returning: vi.fn().mockResolvedValue([updatedWorkflow]),
}),
}),
mockDbUpdate.mockReturnValue({
set: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
returning: vi.fn().mockResolvedValue([updatedWorkflow]),
}),
},
workflow: {},
}))
}),
})
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123', {
method: 'PUT',
@@ -476,7 +420,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
const { PUT } = await import('@/app/api/workflows/[id]/route')
const response = await PUT(req, { params })
expect(response.status).toBe(200)
@@ -495,14 +438,12 @@ describe('Workflow By ID API Route', () => {
const updateData = { name: 'Updated Workflow' }
const updatedWorkflow = { ...mockWorkflow, ...updateData, updatedAt: new Date() }
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'write',
@@ -510,18 +451,13 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
vi.doMock('@sim/db', () => ({
db: {
update: vi.fn().mockReturnValue({
set: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
returning: vi.fn().mockResolvedValue([updatedWorkflow]),
}),
}),
mockDbUpdate.mockReturnValue({
set: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
returning: vi.fn().mockResolvedValue([updatedWorkflow]),
}),
},
workflow: {},
}))
}),
})
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123', {
method: 'PUT',
@@ -529,7 +465,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
const { PUT } = await import('@/app/api/workflows/[id]/route')
const response = await PUT(req, { params })
expect(response.status).toBe(200)
@@ -547,14 +482,12 @@ describe('Workflow By ID API Route', () => {
const updateData = { name: 'Updated Workflow' }
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: 'workspace-456',
workspacePermission: 'read',
@@ -568,7 +501,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
const { PUT } = await import('@/app/api/workflows/[id]/route')
const response = await PUT(req, { params })
expect(response.status).toBe(403)
@@ -584,14 +516,12 @@ describe('Workflow By ID API Route', () => {
workspaceId: null,
}
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockResolvedValueOnce(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValueOnce({
mockGetWorkflowById.mockResolvedValue(mockWorkflow)
mockGetWorkflowAccessContext.mockResolvedValue({
workflow: mockWorkflow,
workspaceOwnerId: null,
workspacePermission: null,
@@ -599,7 +529,6 @@ describe('Workflow By ID API Route', () => {
isWorkspaceOwner: false,
})
// Invalid data - empty name
const invalidData = { name: '' }
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123', {
@@ -608,7 +537,6 @@ describe('Workflow By ID API Route', () => {
})
const params = Promise.resolve({ id: 'workflow-123' })
const { PUT } = await import('@/app/api/workflows/[id]/route')
const response = await PUT(req, { params })
expect(response.status).toBe(400)
@@ -619,24 +547,20 @@ describe('Workflow By ID API Route', () => {
describe('Error handling', () => {
it.concurrent('should handle database errors gracefully', async () => {
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({
user: { id: 'user-123' },
}),
}))
mockGetSession.mockResolvedValue({
user: { id: 'user-123' },
})
mockGetWorkflowById.mockRejectedValueOnce(new Error('Database connection timeout'))
mockGetWorkflowById.mockRejectedValue(new Error('Database connection timeout'))
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
const params = Promise.resolve({ id: 'workflow-123' })
const { GET } = await import('@/app/api/workflows/[id]/route')
const response = await GET(req, { params })
expect(response.status).toBe(500)
const data = await response.json()
expect(data.error).toBe('Internal server error')
expect(mockLogger.error).toHaveBeenCalled()
})
})
})

View File

@@ -152,7 +152,23 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ data: finalWorkflowData }, { status: 200 })
}
return NextResponse.json({ error: 'Workflow has no normalized data' }, { status: 400 })
const emptyWorkflowData = {
...workflowData,
state: {
deploymentStatuses: {},
blocks: {},
edges: [],
loops: {},
parallels: {},
lastSaved: Date.now(),
isDeployed: workflowData.isDeployed || false,
deployedAt: workflowData.deployedAt,
},
variables: workflowData.variables || {},
}
return NextResponse.json({ data: emptyWorkflowData }, { status: 200 })
} catch (error: any) {
const elapsed = Date.now() - startTime
logger.error(`[${requestId}] Error fetching workflow ${workflowId} after ${elapsed}ms`, error)

View File

@@ -1,6 +1,5 @@
import { NextRequest } from 'next/server'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { mockAuth, mockConsoleLogger } from '@/app/api/__test-utils__/utils'
/**
* Tests for workspace invitation by ID API route
@@ -9,137 +8,154 @@ import { mockAuth, mockConsoleLogger } from '@/app/api/__test-utils__/utils'
* @vitest-environment node
*/
describe('Workspace Invitation [invitationId] API Route', () => {
const mockUser = {
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
}
const mockGetSession = vi.fn()
const mockHasWorkspaceAdminAccess = vi.fn()
const mockWorkspace = {
id: 'workspace-456',
name: 'Test Workspace',
}
let dbSelectResults: any[] = []
let dbSelectCallIndex = 0
const mockInvitation = {
id: 'invitation-789',
workspaceId: 'workspace-456',
email: 'invited@example.com',
inviterId: 'inviter-321',
status: 'pending',
token: 'token-abc123',
permissions: 'read',
expiresAt: new Date(Date.now() + 86400000), // 1 day from now
createdAt: new Date(),
updatedAt: new Date(),
}
const mockDbSelect = vi.fn().mockImplementation(() => ({
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
then: vi.fn().mockImplementation((callback: (rows: any[]) => any) => {
const result = dbSelectResults[dbSelectCallIndex] || []
dbSelectCallIndex++
return Promise.resolve(callback ? callback(result) : result)
}),
}))
let mockDbResults: any[] = []
let mockGetSession: any
let mockHasWorkspaceAdminAccess: any
let mockTransaction: any
const mockDbInsert = vi.fn().mockImplementation(() => ({
values: vi.fn().mockResolvedValue(undefined),
}))
beforeEach(async () => {
vi.resetModules()
vi.resetAllMocks()
const mockDbUpdate = vi.fn().mockImplementation(() => ({
set: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue(undefined),
}))
mockDbResults = []
mockConsoleLogger()
mockAuth(mockUser)
const mockDbDelete = vi.fn().mockImplementation(() => ({
where: vi.fn().mockResolvedValue(undefined),
}))
vi.doMock('crypto', () => ({
randomUUID: vi.fn().mockReturnValue('mock-uuid-1234'),
}))
mockGetSession = vi.fn()
vi.doMock('@/lib/auth', () => ({
getSession: mockGetSession,
}))
mockHasWorkspaceAdminAccess = vi.fn()
vi.doMock('@/lib/workspaces/permissions/utils', () => ({
hasWorkspaceAdminAccess: mockHasWorkspaceAdminAccess,
}))
vi.doMock('@/lib/core/config/env', () => {
const mockEnv = {
NEXT_PUBLIC_APP_URL: 'https://test.sim.ai',
BILLING_ENABLED: false,
}
return {
env: mockEnv,
isTruthy: (value: string | boolean | number | undefined) =>
typeof value === 'string'
? value.toLowerCase() === 'true' || value === '1'
: Boolean(value),
getEnv: (variable: string) =>
mockEnv[variable as keyof typeof mockEnv] ?? process.env[variable],
}
})
mockTransaction = vi.fn()
const mockDbChain = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
then: vi.fn().mockImplementation((callback: any) => {
const result = mockDbResults.shift() || []
return callback ? callback(result) : Promise.resolve(result)
}),
insert: vi.fn().mockReturnThis(),
const mockDbTransaction = vi.fn().mockImplementation(async (callback: any) => {
await callback({
insert: vi.fn().mockReturnValue({
values: vi.fn().mockResolvedValue(undefined),
update: vi.fn().mockReturnThis(),
set: vi.fn().mockReturnThis(),
delete: vi.fn().mockReturnThis(),
transaction: mockTransaction,
}
}),
update: vi.fn().mockReturnValue({
set: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue(undefined),
}),
}),
})
})
vi.doMock('@sim/db', () => ({
db: mockDbChain,
}))
vi.mock('@/lib/auth', () => ({
getSession: () => mockGetSession(),
}))
vi.doMock('@sim/db/schema', () => ({
workspaceInvitation: {
id: 'id',
workspaceId: 'workspaceId',
email: 'email',
inviterId: 'inviterId',
status: 'status',
token: 'token',
permissions: 'permissions',
expiresAt: 'expiresAt',
},
workspace: {
id: 'id',
name: 'name',
},
user: {
id: 'id',
email: 'email',
},
permissions: {
id: 'id',
entityType: 'entityType',
entityId: 'entityId',
userId: 'userId',
permissionType: 'permissionType',
},
}))
vi.mock('@/lib/workspaces/permissions/utils', () => ({
hasWorkspaceAdminAccess: (userId: string, workspaceId: string) =>
mockHasWorkspaceAdminAccess(userId, workspaceId),
}))
vi.doMock('drizzle-orm', () => ({
eq: vi.fn((a, b) => ({ type: 'eq', a, b })),
and: vi.fn((...args) => ({ type: 'and', args })),
}))
vi.mock('@/lib/logs/console/logger', () => ({
createLogger: vi.fn().mockReturnValue({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
}),
}))
vi.mock('@/lib/core/utils/urls', () => ({
getBaseUrl: vi.fn().mockReturnValue('https://test.sim.ai'),
}))
vi.mock('@sim/db', () => ({
db: {
select: () => mockDbSelect(),
insert: (table: any) => mockDbInsert(table),
update: (table: any) => mockDbUpdate(table),
delete: (table: any) => mockDbDelete(table),
transaction: (callback: any) => mockDbTransaction(callback),
},
}))
vi.mock('@sim/db/schema', () => ({
workspaceInvitation: {
id: 'id',
workspaceId: 'workspaceId',
email: 'email',
inviterId: 'inviterId',
status: 'status',
token: 'token',
permissions: 'permissions',
expiresAt: 'expiresAt',
},
workspace: {
id: 'id',
name: 'name',
},
user: {
id: 'id',
email: 'email',
},
permissions: {
id: 'id',
entityType: 'entityType',
entityId: 'entityId',
userId: 'userId',
permissionType: 'permissionType',
},
}))
vi.mock('drizzle-orm', () => ({
eq: vi.fn((a, b) => ({ type: 'eq', a, b })),
and: vi.fn((...args) => ({ type: 'and', args })),
}))
vi.mock('crypto', () => ({
randomUUID: vi.fn().mockReturnValue('mock-uuid-1234'),
}))
import { DELETE, GET } from './route'
const mockUser = {
id: 'user-123',
email: 'test@example.com',
name: 'Test User',
}
const mockWorkspace = {
id: 'workspace-456',
name: 'Test Workspace',
}
const mockInvitation = {
id: 'invitation-789',
workspaceId: 'workspace-456',
email: 'invited@example.com',
inviterId: 'inviter-321',
status: 'pending',
token: 'token-abc123',
permissions: 'read',
expiresAt: new Date(Date.now() + 86400000), // 1 day from now
createdAt: new Date(),
updatedAt: new Date(),
}
describe('Workspace Invitation [invitationId] API Route', () => {
beforeEach(() => {
vi.clearAllMocks()
dbSelectResults = []
dbSelectCallIndex = 0
})
describe('GET /api/workspaces/invitations/[invitationId]', () => {
it('should return invitation details when called without token', async () => {
const { GET } = await import('./route')
mockGetSession.mockResolvedValue({ user: mockUser })
mockDbResults.push([mockInvitation])
mockDbResults.push([mockWorkspace])
dbSelectResults = [[mockInvitation], [mockWorkspace]]
const request = new NextRequest('http://localhost/api/workspaces/invitations/invitation-789')
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -157,8 +173,6 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should redirect to login when unauthenticated with token', async () => {
const { GET } = await import('./route')
mockGetSession.mockResolvedValue(null)
const request = new NextRequest(
@@ -174,27 +188,30 @@ describe('Workspace Invitation [invitationId] API Route', () => {
)
})
it('should accept invitation when called with valid token', async () => {
const { GET } = await import('./route')
it('should return 401 when unauthenticated without token', async () => {
mockGetSession.mockResolvedValue(null)
const request = new NextRequest('http://localhost/api/workspaces/invitations/invitation-789')
const params = Promise.resolve({ invitationId: 'invitation-789' })
const response = await GET(request, { params })
const data = await response.json()
expect(response.status).toBe(401)
expect(data).toEqual({ error: 'Unauthorized' })
})
it('should accept invitation when called with valid token', async () => {
mockGetSession.mockResolvedValue({
user: { ...mockUser, email: 'invited@example.com' },
})
mockDbResults.push([mockInvitation])
mockDbResults.push([mockWorkspace])
mockDbResults.push([{ ...mockUser, email: 'invited@example.com' }])
mockDbResults.push([])
mockTransaction.mockImplementation(async (callback: any) => {
await callback({
insert: vi.fn().mockReturnThis(),
values: vi.fn().mockResolvedValue(undefined),
update: vi.fn().mockReturnThis(),
set: vi.fn().mockReturnThis(),
where: vi.fn().mockResolvedValue(undefined),
})
})
dbSelectResults = [
[mockInvitation], // invitation lookup
[mockWorkspace], // workspace lookup
[{ ...mockUser, email: 'invited@example.com' }], // user lookup
[], // existing permission check (empty = no existing)
]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/token-abc123?token=token-abc123'
@@ -208,8 +225,6 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should redirect to error page when invitation expired', async () => {
const { GET } = await import('./route')
mockGetSession.mockResolvedValue({
user: { ...mockUser, email: 'invited@example.com' },
})
@@ -219,8 +234,7 @@ describe('Workspace Invitation [invitationId] API Route', () => {
expiresAt: new Date(Date.now() - 86400000), // 1 day ago
}
mockDbResults.push([expiredInvitation])
mockDbResults.push([mockWorkspace])
dbSelectResults = [[expiredInvitation], [mockWorkspace]]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/token-abc123?token=token-abc123'
@@ -236,15 +250,15 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should redirect to error page when email mismatch', async () => {
const { GET } = await import('./route')
mockGetSession.mockResolvedValue({
user: { ...mockUser, email: 'wrong@example.com' },
})
mockDbResults.push([mockInvitation])
mockDbResults.push([mockWorkspace])
mockDbResults.push([{ ...mockUser, email: 'wrong@example.com' }])
dbSelectResults = [
[mockInvitation],
[mockWorkspace],
[{ ...mockUser, email: 'wrong@example.com' }],
]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/token-abc123?token=token-abc123'
@@ -258,19 +272,29 @@ describe('Workspace Invitation [invitationId] API Route', () => {
'https://test.sim.ai/invite/invitation-789?error=email-mismatch'
)
})
it('should return 404 when invitation not found', async () => {
mockGetSession.mockResolvedValue({ user: mockUser })
dbSelectResults = [[]] // Empty result
const request = new NextRequest('http://localhost/api/workspaces/invitations/non-existent')
const params = Promise.resolve({ invitationId: 'non-existent' })
const response = await GET(request, { params })
const data = await response.json()
expect(response.status).toBe(404)
expect(data).toEqual({ error: 'Invitation not found or has expired' })
})
})
describe('DELETE /api/workspaces/invitations/[invitationId]', () => {
it('should return 401 when user is not authenticated', async () => {
const { DELETE } = await import('./route')
mockGetSession.mockResolvedValue(null)
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
{
method: 'DELETE',
}
{ method: 'DELETE' }
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -282,11 +306,8 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should return 404 when invitation does not exist', async () => {
const { DELETE } = await import('./route')
mockGetSession.mockResolvedValue({ user: mockUser })
mockDbResults.push([])
dbSelectResults = [[]]
const request = new NextRequest('http://localhost/api/workspaces/invitations/non-existent', {
method: 'DELETE',
@@ -301,18 +322,13 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should return 403 when user lacks admin access', async () => {
const { DELETE } = await import('./route')
mockGetSession.mockResolvedValue({ user: mockUser })
mockHasWorkspaceAdminAccess.mockResolvedValue(false)
mockDbResults.push([mockInvitation])
dbSelectResults = [[mockInvitation]]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
{
method: 'DELETE',
}
{ method: 'DELETE' }
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -325,19 +341,15 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should return 400 when trying to delete non-pending invitation', async () => {
const { DELETE } = await import('./route')
mockGetSession.mockResolvedValue({ user: mockUser })
mockHasWorkspaceAdminAccess.mockResolvedValue(true)
const acceptedInvitation = { ...mockInvitation, status: 'accepted' }
mockDbResults.push([acceptedInvitation])
dbSelectResults = [[acceptedInvitation]]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
{
method: 'DELETE',
}
{ method: 'DELETE' }
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -349,18 +361,13 @@ describe('Workspace Invitation [invitationId] API Route', () => {
})
it('should successfully delete pending invitation when user has admin access', async () => {
const { DELETE } = await import('./route')
mockGetSession.mockResolvedValue({ user: mockUser })
mockHasWorkspaceAdminAccess.mockResolvedValue(true)
mockDbResults.push([mockInvitation])
dbSelectResults = [[mockInvitation]]
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
{
method: 'DELETE',
}
{ method: 'DELETE' }
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
@@ -370,61 +377,5 @@ describe('Workspace Invitation [invitationId] API Route', () => {
expect(response.status).toBe(200)
expect(data).toEqual({ success: true })
})
it('should return 500 when database error occurs', async () => {
vi.resetModules()
const mockErrorDb = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
then: vi.fn().mockRejectedValue(new Error('Database connection failed')),
}
vi.doMock('@sim/db', () => ({ db: mockErrorDb }))
vi.doMock('@/lib/auth', () => ({
getSession: vi.fn().mockResolvedValue({ user: mockUser }),
}))
vi.doMock('@/lib/workspaces/permissions/utils', () => ({
hasWorkspaceAdminAccess: vi.fn(),
}))
vi.doMock('@/lib/core/config/env', () => {
const mockEnv = {
NEXT_PUBLIC_APP_URL: 'https://test.sim.ai',
BILLING_ENABLED: false,
}
return {
env: mockEnv,
isTruthy: (value: string | boolean | number | undefined) =>
typeof value === 'string'
? value.toLowerCase() === 'true' || value === '1'
: Boolean(value),
getEnv: (variable: string) =>
mockEnv[variable as keyof typeof mockEnv] ?? process.env[variable],
}
})
vi.doMock('@sim/db/schema', () => ({
workspaceInvitation: { id: 'id' },
}))
vi.doMock('drizzle-orm', () => ({
eq: vi.fn(),
}))
const { DELETE } = await import('./route')
const request = new NextRequest(
'http://localhost/api/workspaces/invitations/invitation-789',
{
method: 'DELETE',
}
)
const params = Promise.resolve({ invitationId: 'invitation-789' })
const response = await DELETE(request, { params })
const data = await response.json()
expect(response.status).toBe(500)
expect(data).toEqual({ error: 'Failed to delete invitation' })
})
})
})

View File

@@ -14,12 +14,14 @@ export default function WorkspaceLayout({ children }: { children: React.ReactNod
<ProviderModelsLoader />
<GlobalCommandsProvider>
<Tooltip.Provider delayDuration={600} skipDelayDuration={0}>
<WorkspacePermissionsProvider>
<div className='flex min-h-screen w-full'>
<SidebarNew />
<div className='flex flex-1 flex-col'>{children}</div>
</div>
</WorkspacePermissionsProvider>
<div className='flex min-h-screen w-full'>
<WorkspacePermissionsProvider>
<div className='shrink-0' suppressHydrationWarning>
<SidebarNew />
</div>
{children}
</WorkspacePermissionsProvider>
</div>
</Tooltip.Provider>
</GlobalCommandsProvider>
</>

View File

@@ -1,3 +1,7 @@
export default function TemplatesLayout({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
return (
<main className='flex h-full flex-1 flex-col overflow-hidden bg-muted/40'>
<div>{children}</div>
</main>
)
}

View File

@@ -36,6 +36,12 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'https://www.googleapis.com/auth/forms.responses.readonly': 'View responses to your Google Forms',
'https://www.googleapis.com/auth/ediscovery': 'Access Google Vault for eDiscovery',
'https://www.googleapis.com/auth/devstorage.read_only': 'Read files from Google Cloud Storage',
'https://www.googleapis.com/auth/admin.directory.group': 'Manage Google Workspace groups',
'https://www.googleapis.com/auth/admin.directory.group.member':
'Manage Google Workspace group memberships',
'https://www.googleapis.com/auth/admin.directory.group.readonly': 'View Google Workspace groups',
'https://www.googleapis.com/auth/admin.directory.group.member.readonly':
'View Google Workspace group memberships',
'read:confluence-content.all': 'Read all Confluence content',
'read:confluence-space.summary': 'Read Confluence space information',
'read:space:confluence': 'View Confluence spaces',

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