Compare commits

...

14 Commits

Author SHA1 Message Date
dependabot[bot]
01406b9e62 build(deps): bump the npm_and_yarn group across 3 directories with 4 updates
Bumps the npm_and_yarn group with 3 updates in the / directory: [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk), [better-auth](https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth) and [js-yaml](https://github.com/nodeca/js-yaml).
Bumps the npm_and_yarn group with 3 updates in the /apps/sim directory: [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk), [better-auth](https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth) and [js-yaml](https://github.com/nodeca/js-yaml).
Bumps the npm_and_yarn group with 1 update in the /scripts directory: [glob](https://github.com/isaacs/node-glob).


Updates `@modelcontextprotocol/sdk` from 1.20.2 to 1.24.0
- [Release notes](https://github.com/modelcontextprotocol/typescript-sdk/releases)
- [Commits](https://github.com/modelcontextprotocol/typescript-sdk/compare/1.20.2...1.24.0)

Updates `better-auth` from 1.3.12 to 1.4.5
- [Release notes](https://github.com/better-auth/better-auth/releases)
- [Commits](https://github.com/better-auth/better-auth/commits/v1.4.5/packages/better-auth)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `@modelcontextprotocol/sdk` from 1.20.2 to 1.24.0
- [Release notes](https://github.com/modelcontextprotocol/typescript-sdk/releases)
- [Commits](https://github.com/modelcontextprotocol/typescript-sdk/compare/1.20.2...1.24.0)

Updates `better-auth` from 1.3.12 to 1.4.5
- [Release notes](https://github.com/better-auth/better-auth/releases)
- [Commits](https://github.com/better-auth/better-auth/commits/v1.4.5/packages/better-auth)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `@modelcontextprotocol/sdk` from 1.20.2 to 1.24.0
- [Release notes](https://github.com/modelcontextprotocol/typescript-sdk/releases)
- [Commits](https://github.com/modelcontextprotocol/typescript-sdk/compare/1.20.2...1.24.0)

Updates `better-auth` from 1.3.12 to 1.4.5
- [Release notes](https://github.com/better-auth/better-auth/releases)
- [Commits](https://github.com/better-auth/better-auth/commits/v1.4.5/packages/better-auth)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `@modelcontextprotocol/sdk` from 1.20.2 to 1.24.0
- [Release notes](https://github.com/modelcontextprotocol/typescript-sdk/releases)
- [Commits](https://github.com/modelcontextprotocol/typescript-sdk/compare/1.20.2...1.24.0)

Updates `better-auth` from 1.3.12 to 1.4.5
- [Release notes](https://github.com/better-auth/better-auth/releases)
- [Commits](https://github.com/better-auth/better-auth/commits/v1.4.5/packages/better-auth)

Updates `js-yaml` from 4.1.0 to 4.1.1
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

Updates `glob` from 11.0.2 to 11.1.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v11.0.2...v11.1.0)

---
updated-dependencies:
- dependency-name: "@modelcontextprotocol/sdk"
  dependency-version: 1.24.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: better-auth
  dependency-version: 1.4.5
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: "@modelcontextprotocol/sdk"
  dependency-version: 1.24.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: better-auth
  dependency-version: 1.4.5
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: "@modelcontextprotocol/sdk"
  dependency-version: 1.24.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: better-auth
  dependency-version: 1.4.5
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: "@modelcontextprotocol/sdk"
  dependency-version: 1.24.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: better-auth
  dependency-version: 1.4.5
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: glob
  dependency-version: 11.1.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 10:19:26 +00:00
Waleed
f895bf469b v0.5.46: build improvements, greptile, light mode improvements 2025-12-29 02:17:52 -08:00
Waleed
88065088bf fix(deploy): fix workflow change detection to handle old variable reference format (#2623) 2025-12-29 02:09:38 -08:00
Emir Karabeg
1c626dfcae improvement(globals): light colors (#2620)
* improvement(globals): light colors

* improvement(ui): logs, templates, kb, light globals

* improvement(subflows): start node and ui/ux
2025-12-28 13:28:36 -08:00
Waleed
132aae1615 feat(i18n): update translations (#2619)
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
2025-12-28 12:37:20 -08:00
Waleed
f44fc18041 fix(build): add tsconfig to db dockerfile (#2617)
* fix(build): add tsconfig to db dockerfile

* ack pr comment
2025-12-28 12:29:34 -08:00
Waleed
7761b16b87 feat(tools): added greptile tools/block, updated copilot panel styling (#2618) 2025-12-28 12:12:22 -08:00
Waleed
71130c8b0a improvement(monorepo): added tsconfig package, resolved type errors in testing package (#2613) 2025-12-28 00:36:48 -08:00
Waleed
dd3209af06 v0.5.45: light mode fixes, realtime usage indicator, docker build improvements 2025-12-27 19:57:42 -08:00
Waleed
7c0a3c15ac improvement(build): migrate to blacksmith sticky disks for faster builds, other build improvements (#2611) 2025-12-27 18:09:29 -08:00
Waleed
cdc1a832d7 fix(docker): add logger package to realtime dockerfile (#2610) 2025-12-27 17:55:16 -08:00
Waleed
aa9cc5604a improvement(usage-indicator): update query invalidation for usage to update in realtime (#2607)
* improvement(usage-indicator): update query invalidation for usage to update in realtime

* ack PR comments
2025-12-27 15:23:02 -08:00
Emir Karabeg
fdba1cfac2 improvement: required permissions, oauth modal badge (#2609) 2025-12-27 15:18:03 -08:00
Waleed
2e1ccb16f5 improvement(ui): hide divider when following subblock value is null (#2608)
* improvement(ui): hide divider when following subblock value is null

* closed gap in mcp dynamic args subblock
2025-12-27 15:15:43 -08:00
102 changed files with 3162 additions and 1392 deletions

View File

@@ -23,16 +23,17 @@ jobs:
with:
node-version: latest
- name: Cache Bun dependencies
uses: actions/cache@v4
- name: Mount Bun cache (Sticky Disk)
uses: useblacksmith/stickydisk@v1
with:
path: |
~/.bun/install/cache
node_modules
**/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
key: ${{ github.repository }}-bun-cache
path: ~/.bun/install/cache
- name: Mount node_modules (Sticky Disk)
uses: useblacksmith/stickydisk@v1
with:
key: ${{ github.repository }}-node-modules
path: ./node_modules
- name: Install dependencies
run: bun install --frozen-lockfile

View File

@@ -4344,3 +4344,16 @@ export function CirclebackIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function GreptileIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<path
clipRule='evenodd'
fillRule='evenodd'
fill='#44A775'
d='M3.353.004a6.074 6.074 0 01-.265.045C2.63.12 2.092.348 1.71.633 1.426.846.717 1.575.557 1.819a3.359 3.359 0 00-.23 3.296c.154.322.35.59.71.972.187.198.434.486.55.64a6.629 6.629 0 011.305 3.546c.01.138.035 1.607.057 3.264.043 3.273.038 3.18.203 3.485.266.494.94.79 1.474.648.29-.077.463-.204 1.353-.986.957-.84 1.092-.932 1.446-.98.124-.017.631 0 1.66.053 1.513.08 1.622.079 1.85-.016.393-.164.539-.4.661-1.074.247-1.36 1.296-2.56 2.64-3.022.116-.04.373-.104.572-.144.198-.04.426-.102.506-.138.296-.136.515-.424.566-.744.017-.11-.007-.549-.089-1.602-.091-1.179-.107-1.483-.083-1.621.057-.342.139-.46 1.01-1.448.447-.506.85-.976.895-1.043.262-.39.288-.91.068-1.345a1.44 1.44 0 00-.822-.67c-.1-.029-.834-.037-3.544-.038H9.897l-.335-.063c-.958-.179-1.765-.49-2.484-.958-.362-.236-.583-.41-1.018-.804-.408-.37-.59-.502-.921-.67A3.018 3.018 0 003.744.005a3.942 3.942 0 00-.391 0zm15.728 5.858c-.132.049-.217.127-.48.44-.592.707-.74 1.336-.531 2.256.106.466.163.572.361.673.105.054.169.055 2.637.046l2.53-.009.118-.063a.551.551 0 00.095-.895 184.88 184.88 0 00-2.223-1.254c-2.293-1.282-2.281-1.276-2.507-1.194zm-3.216 6.71a9.258 9.258 0 00-1.364.696c-.844.557-1.454 1.36-1.923 2.53-.211.525-.202.75.04.935.111.087 6.478 3.14 6.667 3.198.153.047.27.027.43-.074a.538.538 0 00.24-.434c0-.06-.03-.18-.065-.264-.156-.368-3.098-6.467-3.158-6.545-.168-.222-.394-.232-.867-.042zm-8.48 5.457c-.453.102-.83.32-1.285.745-.296.277-.336.468-.167.798.053.103.61 1.104 1.236 2.224 1.297 2.317 1.22 2.206 1.558 2.202.152-.002.198-.015.296-.084a.662.662 0 00.173-.193c.058-.11.06-.152.08-2.595.018-1.93.015-2.51-.011-2.606a.569.569 0 00-.138-.227c-.09-.091-.14-.112-.406-.176-.582-.138-.992-.165-1.336-.088z'
/>
</svg>
)
}

View File

@@ -42,6 +42,7 @@ import {
GoogleVaultIcon,
GrafanaIcon,
GrainIcon,
GreptileIcon,
HubspotIcon,
HuggingFaceIcon,
HunterIOIcon,
@@ -158,6 +159,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_vault: GoogleVaultIcon,
grafana: GrafanaIcon,
grain: GrainIcon,
greptile: GreptileIcon,
hubspot: HubspotIcon,
huggingface: HuggingFaceIcon,
hunter: HunterIOIcon,

View File

@@ -0,0 +1,136 @@
---
title: Greptile
description: KI-gestützte Codebase-Suche und Fragen & Antworten
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="greptile"
color="#e5e5e5"
/>
{/* MANUAL-CONTENT-START:intro */}
[Greptile](https://greptile.com/) ist ein KI-gestütztes Entwicklertool zum Durchsuchen und Abfragen von Quellcode über ein oder mehrere Repositories hinweg. Greptile ermöglicht es Entwicklern, komplexe Fragen zur Codebase schnell in natürlicher Sprache zu beantworten, relevante Dateien oder Symbole zu finden und Einblicke in unbekannten oder Legacy-Code zu gewinnen.
Mit Greptile können Sie:
- **Komplexe Fragen zu Ihrer Codebase in natürlicher Sprache stellen**: Erhalten Sie KI-generierte Antworten zu Architektur, Verwendungsmustern oder spezifischen Implementierungen.
- **Relevanten Code, Dateien oder Funktionen sofort finden**: Suchen Sie mit Schlüsselwörtern oder natürlichsprachlichen Abfragen und springen Sie direkt zu passenden Zeilen, Dateien oder Codeblöcken.
- **Abhängigkeiten und Beziehungen verstehen**: Entdecken Sie, wo Funktionen aufgerufen werden, wie Module miteinander verbunden sind oder wo APIs in großen Codebasen verwendet werden.
- **Onboarding und Code-Exploration beschleunigen**: Arbeiten Sie sich schnell in neue Projekte ein oder debuggen Sie knifflige Probleme, ohne tiefgreifenden Vorkontext zu benötigen.
Die Sim Greptile-Integration ermöglicht es Ihren KI-Agenten:
- Private und öffentliche Repositories mithilfe der fortschrittlichen Sprachmodelle von Greptile abzufragen und zu durchsuchen.
- Kontextuell relevante Code-Snippets, Dateiverweise und Erklärungen abzurufen, um Code-Reviews, Dokumentation und Entwicklungsworkflows zu unterstützen.
- Automatisierungen in Sim-Workflows basierend auf Such-/Abfrageergebnissen auszulösen oder Code-Intelligenz direkt in Ihre Prozesse einzubetten.
Egal, ob Sie die Produktivität von Entwicklern beschleunigen, Dokumentation automatisieren oder das Verständnis Ihres Teams für eine komplexe Codebase verbessern möchten Greptile und Sim bieten nahtlosen Zugriff auf Code-Intelligenz und Suche, genau dort, wo Sie sie benötigen.
{/* MANUAL-CONTENT-END */}
## Nutzungsanleitung
Fragen Sie Codebasen mit natürlicher Sprache über Greptile ab und durchsuchen Sie sie. Erhalten Sie KI-generierte Antworten zu Ihrem Code, finden Sie relevante Dateien und verstehen Sie komplexe Codebasen.
## Tools
### `greptile_query`
Durchsuchen Sie Repositories in natürlicher Sprache und erhalten Sie Antworten mit relevanten Code-Referenzen. Greptile nutzt KI, um Ihre Codebasis zu verstehen und Fragen zu beantworten.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `query` | string | Ja | Frage in natürlicher Sprache zur Codebasis |
| `repositories` | string | Ja | Kommagetrennte Liste von Repositories. Format: "github:branch:owner/repo" oder nur "owner/repo" \(Standard ist github:main\) |
| `sessionId` | string | Nein | Sitzungs-ID für Gesprächskontinuität |
| `genius` | boolean | Nein | Genius-Modus für gründlichere Analyse aktivieren \(langsamer, aber genauer\) |
| `apiKey` | string | Ja | Greptile-API-Schlüssel |
| `githubToken` | string | Ja | GitHub Personal Access Token mit Lesezugriff auf Repositories |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `message` | string | KI-generierte Antwort auf die Anfrage |
| `sources` | array | Relevante Code-Referenzen, die die Antwort unterstützen |
### `greptile_search`
Durchsuchen Sie Repositories in natürlicher Sprache und erhalten Sie relevante Code-Referenzen ohne Generierung einer Antwort. Nützlich zum Auffinden spezifischer Code-Stellen.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `query` | string | Ja | Suchanfrage in natürlicher Sprache zum Auffinden relevanten Codes |
| `repositories` | string | Ja | Kommagetrennte Liste von Repositories. Format: "github:branch:owner/repo" oder nur "owner/repo" \(Standard ist github:main\) |
| `sessionId` | string | Nein | Sitzungs-ID für Gesprächskontinuität |
| `genius` | boolean | Nein | Genius-Modus für gründlichere Suche aktivieren \(langsamer, aber genauer\) |
| `apiKey` | string | Ja | Greptile-API-Schlüssel |
| `githubToken` | string | Ja | GitHub Personal Access Token mit Lesezugriff auf Repositories |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `sources` | array | Relevante Code-Referenzen, die zur Suchanfrage passen |
### `greptile_index_repo`
Übermitteln Sie ein Repository zur Indexierung durch Greptile. Die Indexierung muss abgeschlossen sein, bevor das Repository abgefragt werden kann. Kleine Repositories benötigen 3-5 Minuten, größere können über eine Stunde dauern.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `remote` | string | Ja | Git-Remote-Typ: github oder gitlab |
| `repository` | string | Ja | Repository im Format owner/repo \(z. B. "facebook/react"\) |
| `branch` | string | Ja | Zu indexierender Branch \(z. B. "main" oder "master"\) |
| `reload` | boolean | Nein | Neuindexierung erzwingen, auch wenn bereits indexiert |
| `notify` | boolean | Nein | E-Mail-Benachrichtigung senden, wenn Indexierung abgeschlossen ist |
| `apiKey` | string | Ja | Greptile-API-Schlüssel |
| `githubToken` | string | Ja | GitHub Personal Access Token mit Lesezugriff auf Repository |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `repositoryId` | string | Eindeutige Kennung für das indexierte Repository \(Format: remote:branch:owner/repo\) |
| `statusEndpoint` | string | URL-Endpunkt zur Überprüfung des Indexierungsstatus |
| `message` | string | Statusmeldung über den Indexierungsvorgang |
### `greptile_status`
Überprüfen Sie den Indexierungsstatus eines Repositories. Verwenden Sie dies, um zu verifizieren, ob ein Repository abfragebereit ist, oder um den Indexierungsfortschritt zu überwachen.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `remote` | string | Ja | Git-Remote-Typ: github oder gitlab |
| `repository` | string | Ja | Repository im Format owner/repo \(z. B. "facebook/react"\) |
| `branch` | string | Ja | Branch-Name \(z. B. "main" oder "master"\) |
| `apiKey` | string | Ja | Greptile-API-Schlüssel |
| `githubToken` | string | Ja | GitHub Personal Access Token mit Lesezugriff auf das Repository |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `repository` | string | Repository-Name \(owner/repo\) |
| `remote` | string | Git-Remote \(github/gitlab\) |
| `branch` | string | Branch-Name |
| `private` | boolean | Ob das Repository privat ist |
| `status` | string | Indexierungsstatus: submitted, cloning, processing, completed oder failed |
| `filesProcessed` | number | Anzahl der bisher verarbeiteten Dateien |
| `numFiles` | number | Gesamtanzahl der Dateien im Repository |
| `sampleQuestions` | array | Beispielfragen für das indexierte Repository |
| `sha` | string | Git-Commit-SHA der indexierten Version |
## Hinweise
- Kategorie: `tools`
- Typ: `greptile`

View File

@@ -0,0 +1,141 @@
---
title: Greptile
description: AI-powered codebase search and Q&A
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="greptile"
color="#e5e5e5"
/>
{/* MANUAL-CONTENT-START:intro */}
[Greptile](https://greptile.com/) is an AI-powered developer tool for searching and querying source code across one or more repositories. Greptile enables engineers to quickly answer complex codebase questions in natural language, locate relevant files or symbols, and gain insights into unfamiliar or legacy code.
With Greptile, you can:
- **Ask complex questions about your codebase in natural language**: Get AI-generated answers about architecture, usage patterns, or specific implementations.
- **Find relevant code, files, or functions instantly**: Search using keywords or natural language queries and jump right to matching lines, files, or code blocks.
- **Understand dependencies and relationships**: Uncover where functions are called, how modules are related, or where APIs are used across large codebases.
- **Accelerate onboarding and code exploration**: Quickly ramp up on new projects or debug tricky issues without needing deep prior context.
The Sim Greptile integration allows your AI agents to:
- Query and search private and public repositories using Greptiles advanced language models.
- Retrieve contextually relevant code snippets, file references, and explanations to support code review, documentation, and development workflows.
- Trigger automations in Sim workflows based on search/query results or embed code intelligence directly into your processes.
Whether youre trying to accelerate developer productivity, automate documentation, or supercharge your teams understanding of a complex codebase, Greptile and Sim provide seamless access to code intelligence and search—right where you need it.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Query and search codebases using natural language with Greptile. Get AI-generated answers about your code, find relevant files, and understand complex codebases.
## Tools
### `greptile_query`
Query repositories in natural language and get answers with relevant code references. Greptile uses AI to understand your codebase and answer questions.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | Natural language question about the codebase |
| `repositories` | string | Yes | Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" \(defaults to github:main\) |
| `sessionId` | string | No | Session ID for conversation continuity |
| `genius` | boolean | No | Enable genius mode for more thorough analysis \(slower but more accurate\) |
| `apiKey` | string | Yes | Greptile API key |
| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | AI-generated answer to the query |
| `sources` | array | Relevant code references that support the answer |
### `greptile_search`
Search repositories in natural language and get relevant code references without generating an answer. Useful for finding specific code locations.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | Natural language search query to find relevant code |
| `repositories` | string | Yes | Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" \(defaults to github:main\) |
| `sessionId` | string | No | Session ID for conversation continuity |
| `genius` | boolean | No | Enable genius mode for more thorough search \(slower but more accurate\) |
| `apiKey` | string | Yes | Greptile API key |
| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `sources` | array | Relevant code references matching the search query |
### `greptile_index_repo`
Submit a repository to be indexed by Greptile. Indexing must complete before the repository can be queried. Small repos take 3-5 minutes, larger ones can take over an hour.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `remote` | string | Yes | Git remote type: github or gitlab |
| `repository` | string | Yes | Repository in owner/repo format \(e.g., "facebook/react"\) |
| `branch` | string | Yes | Branch to index \(e.g., "main" or "master"\) |
| `reload` | boolean | No | Force re-indexing even if already indexed |
| `notify` | boolean | No | Send email notification when indexing completes |
| `apiKey` | string | Yes | Greptile API key |
| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `repositoryId` | string | Unique identifier for the indexed repository \(format: remote:branch:owner/repo\) |
| `statusEndpoint` | string | URL endpoint to check indexing status |
| `message` | string | Status message about the indexing operation |
### `greptile_status`
Check the indexing status of a repository. Use this to verify if a repository is ready to be queried or to monitor indexing progress.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `remote` | string | Yes | Git remote type: github or gitlab |
| `repository` | string | Yes | Repository in owner/repo format \(e.g., "facebook/react"\) |
| `branch` | string | Yes | Branch name \(e.g., "main" or "master"\) |
| `apiKey` | string | Yes | Greptile API key |
| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `repository` | string | Repository name \(owner/repo\) |
| `remote` | string | Git remote \(github/gitlab\) |
| `branch` | string | Branch name |
| `private` | boolean | Whether the repository is private |
| `status` | string | Indexing status: submitted, cloning, processing, completed, or failed |
| `filesProcessed` | number | Number of files processed so far |
| `numFiles` | number | Total number of files in the repository |
| `sampleQuestions` | array | Sample questions for the indexed repository |
| `sha` | string | Git commit SHA of the indexed version |
## Notes
- Category: `tools`
- Type: `greptile`

View File

@@ -37,6 +37,7 @@
"google_vault",
"grafana",
"grain",
"greptile",
"hubspot",
"huggingface",
"hunter",

View File

@@ -0,0 +1,136 @@
---
title: Greptile
description: Búsqueda de código base y preguntas y respuestas con IA
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="greptile"
color="#e5e5e5"
/>
{/* MANUAL-CONTENT-START:intro */}
[Greptile](https://greptile.com/) es una herramienta de desarrollo impulsada por IA para buscar y consultar código fuente en uno o más repositorios. Greptile permite a los ingenieros responder rápidamente preguntas complejas sobre el código base en lenguaje natural, localizar archivos o símbolos relevantes y obtener información sobre código desconocido o heredado.
Con Greptile, puedes:
- **Hacer preguntas complejas sobre tu código base en lenguaje natural**: Obtén respuestas generadas por IA sobre arquitectura, patrones de uso o implementaciones específicas.
- **Encontrar código, archivos o funciones relevantes al instante**: Busca usando palabras clave o consultas en lenguaje natural y ve directamente a las líneas, archivos o bloques de código coincidentes.
- **Comprender dependencias y relaciones**: Descubre dónde se llaman las funciones, cómo se relacionan los módulos o dónde se usan las API en grandes bases de código.
- **Acelerar la incorporación y exploración de código**: Ponte al día rápidamente en nuevos proyectos o depura problemas complicados sin necesitar un contexto previo profundo.
La integración de Sim Greptile permite a tus agentes de IA:
- Consultar y buscar repositorios privados y públicos usando los modelos de lenguaje avanzados de Greptile.
- Recuperar fragmentos de código contextualmente relevantes, referencias de archivos y explicaciones para apoyar la revisión de código, documentación y flujos de trabajo de desarrollo.
- Activar automatizaciones en flujos de trabajo de Sim basadas en resultados de búsqueda/consulta o integrar inteligencia de código directamente en tus procesos.
Ya sea que estés tratando de acelerar la productividad del desarrollador, automatizar la documentación o potenciar la comprensión de tu equipo sobre un código base complejo, Greptile y Sim proporcionan acceso fluido a la inteligencia y búsqueda de código, justo donde lo necesitas.
{/* MANUAL-CONTENT-END */}
## Instrucciones de uso
Consulta y busca en bases de código usando lenguaje natural con Greptile. Obtén respuestas generadas por IA sobre tu código, encuentra archivos relevantes y comprende bases de código complejas.
## Herramientas
### `greptile_query`
Consulta repositorios en lenguaje natural y obtén respuestas con referencias de código relevantes. Greptile utiliza IA para comprender tu código base y responder preguntas.
#### Entrada
| Parámetro | Tipo | Requerido | Descripción |
| --------- | ---- | -------- | ----------- |
| `query` | string | Sí | Pregunta en lenguaje natural sobre el código base |
| `repositories` | string | Sí | Lista de repositorios separados por comas. Formato: "github:branch:owner/repo" o simplemente "owner/repo" \(por defecto github:main\) |
| `sessionId` | string | No | ID de sesión para continuidad de la conversación |
| `genius` | boolean | No | Activar modo genius para un análisis más exhaustivo \(más lento pero más preciso\) |
| `apiKey` | string | Sí | Clave API de Greptile |
| `githubToken` | string | Sí | Token de acceso personal de GitHub con acceso de lectura al repositorio |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `message` | string | Respuesta generada por IA a la consulta |
| `sources` | array | Referencias de código relevantes que respaldan la respuesta |
### `greptile_search`
Busca en repositorios en lenguaje natural y obtén referencias de código relevantes sin generar una respuesta. Útil para encontrar ubicaciones específicas de código.
#### Entrada
| Parámetro | Tipo | Requerido | Descripción |
| --------- | ---- | -------- | ----------- |
| `query` | string | Sí | Consulta de búsqueda en lenguaje natural para encontrar código relevante |
| `repositories` | string | Sí | Lista de repositorios separados por comas. Formato: "github:branch:owner/repo" o simplemente "owner/repo" \(por defecto github:main\) |
| `sessionId` | string | No | ID de sesión para continuidad de la conversación |
| `genius` | boolean | No | Activar modo genius para una búsqueda más exhaustiva \(más lento pero más preciso\) |
| `apiKey` | string | Sí | Clave API de Greptile |
| `githubToken` | string | Sí | Token de acceso personal de GitHub con acceso de lectura al repositorio |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `sources` | array | Referencias de código relevantes que coinciden con la consulta de búsqueda |
### `greptile_index_repo`
Envía un repositorio para ser indexado por Greptile. La indexación debe completarse antes de que el repositorio pueda ser consultado. Los repositorios pequeños tardan de 3 a 5 minutos, los más grandes pueden tardar más de una hora.
#### Entrada
| Parámetro | Tipo | Requerido | Descripción |
| --------- | ---- | -------- | ----------- |
| `remote` | string | Sí | Tipo de remoto Git: github o gitlab |
| `repository` | string | Sí | Repositorio en formato propietario/repo \(ej., "facebook/react"\) |
| `branch` | string | Sí | Rama a indexar \(ej., "main" o "master"\) |
| `reload` | boolean | No | Forzar re-indexación incluso si ya está indexado |
| `notify` | boolean | No | Enviar notificación por correo electrónico cuando se complete la indexación |
| `apiKey` | string | Sí | Clave API de Greptile |
| `githubToken` | string | Sí | Token de acceso personal de GitHub con acceso de lectura al repositorio |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `repositoryId` | string | Identificador único para el repositorio indexado \(formato: remoto:rama:propietario/repo\) |
| `statusEndpoint` | string | URL del endpoint para verificar el estado de indexación |
| `message` | string | Mensaje de estado sobre la operación de indexación |
### `greptile_status`
Verifica el estado de indexación de un repositorio. Usa esto para verificar si un repositorio está listo para ser consultado o para monitorear el progreso de indexación.
#### Entrada
| Parámetro | Tipo | Requerido | Descripción |
| --------- | ---- | -------- | ----------- |
| `remote` | string | Sí | Tipo de remoto Git: github o gitlab |
| `repository` | string | Sí | Repositorio en formato propietario/repo \(ej., "facebook/react"\) |
| `branch` | string | Sí | Nombre de la rama \(ej., "main" o "master"\) |
| `apiKey` | string | Sí | Clave API de Greptile |
| `githubToken` | string | Sí | Token de acceso personal de GitHub con acceso de lectura al repositorio |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `repository` | string | Nombre del repositorio \(propietario/repo\) |
| `remote` | string | Remoto Git \(github/gitlab\) |
| `branch` | string | Nombre de la rama |
| `private` | boolean | Si el repositorio es privado |
| `status` | string | Estado de indexación: submitted, cloning, processing, completed o failed |
| `filesProcessed` | number | Número de archivos procesados hasta el momento |
| `numFiles` | number | Número total de archivos en el repositorio |
| `sampleQuestions` | array | Preguntas de ejemplo para el repositorio indexado |
| `sha` | string | SHA del commit Git de la versión indexada |
## Notas
- Categoría: `tools`
- Tipo: `greptile`

View File

@@ -0,0 +1,136 @@
---
title: Greptile
description: Recherche de base de code et questions-réponses alimentées par l'IA
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="greptile"
color="#e5e5e5"
/>
{/* MANUAL-CONTENT-START:intro */}
[Greptile](https://greptile.com/) est un outil de développement alimenté par l'IA pour rechercher et interroger le code source dans un ou plusieurs dépôts. Greptile permet aux ingénieurs de répondre rapidement à des questions complexes sur la base de code en langage naturel, de localiser des fichiers ou symboles pertinents et d'obtenir des informations sur du code inconnu ou hérité.
Avec Greptile, vous pouvez :
- **Poser des questions complexes sur votre base de code en langage naturel** : obtenez des réponses générées par l'IA sur l'architecture, les modèles d'utilisation ou des implémentations spécifiques.
- **Trouver instantanément du code, des fichiers ou des fonctions pertinents** : recherchez à l'aide de mots-clés ou de requêtes en langage naturel et accédez directement aux lignes, fichiers ou blocs de code correspondants.
- **Comprendre les dépendances et les relations** : découvrez où les fonctions sont appelées, comment les modules sont liés ou où les API sont utilisées dans de grandes bases de code.
- **Accélérer l'intégration et l'exploration du code** : montez rapidement en compétence sur de nouveaux projets ou déboguez des problèmes complexes sans avoir besoin d'un contexte préalable approfondi.
L'intégration Sim Greptile permet à vos agents IA de :
- Interroger et rechercher des dépôts privés et publics en utilisant les modèles de langage avancés de Greptile.
- Récupérer des extraits de code contextuellement pertinents, des références de fichiers et des explications pour soutenir la revue de code, la documentation et les flux de travail de développement.
- Déclencher des automatisations dans les workflows Sim en fonction des résultats de recherche/requête ou intégrer l'intelligence du code directement dans vos processus.
Que vous cherchiez à accélérer la productivité des développeurs, à automatiser la documentation ou à renforcer la compréhension de votre équipe d'une base de code complexe, Greptile et Sim offrent un accès transparent à l'intelligence et à la recherche de code, exactement là où vous en avez besoin.
{/* MANUAL-CONTENT-END */}
## Instructions d'utilisation
Interrogez et recherchez des bases de code en langage naturel avec Greptile. Obtenez des réponses générées par l'IA sur votre code, trouvez des fichiers pertinents et comprenez des bases de code complexes.
## Outils
### `greptile_query`
Interrogez les dépôts en langage naturel et obtenez des réponses avec des références de code pertinentes. Greptile utilise l'IA pour comprendre votre base de code et répondre aux questions.
#### Entrée
| Paramètre | Type | Requis | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Oui | Question en langage naturel sur la base de code |
| `repositories` | string | Oui | Liste de dépôts séparés par des virgules. Format : "github:branch:owner/repo" ou simplement "owner/repo" \(par défaut github:main\) |
| `sessionId` | string | Non | ID de session pour la continuité de la conversation |
| `genius` | boolean | Non | Activer le mode genius pour une analyse plus approfondie \(plus lent mais plus précis\) |
| `apiKey` | string | Oui | Clé API Greptile |
| `githubToken` | string | Oui | Jeton d'accès personnel GitHub avec accès en lecture au dépôt |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Réponse générée par l'IA à la requête |
| `sources` | array | Références de code pertinentes qui appuient la réponse |
### `greptile_search`
Recherchez dans les dépôts en langage naturel et obtenez des références de code pertinentes sans générer de réponse. Utile pour trouver des emplacements de code spécifiques.
#### Entrée
| Paramètre | Type | Requis | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Oui | Requête de recherche en langage naturel pour trouver du code pertinent |
| `repositories` | string | Oui | Liste de dépôts séparés par des virgules. Format : "github:branch:owner/repo" ou simplement "owner/repo" \(par défaut github:main\) |
| `sessionId` | string | Non | ID de session pour la continuité de la conversation |
| `genius` | boolean | Non | Activer le mode genius pour une recherche plus approfondie \(plus lent mais plus précis\) |
| `apiKey` | string | Oui | Clé API Greptile |
| `githubToken` | string | Oui | Jeton d'accès personnel GitHub avec accès en lecture au dépôt |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `sources` | array | Références de code pertinentes correspondant à la requête de recherche |
### `greptile_index_repo`
Soumettre un dépôt pour qu'il soit indexé par Greptile. L'indexation doit être terminée avant que le dépôt puisse être interrogé. Les petits dépôts prennent 3 à 5 minutes, les plus grands peuvent prendre plus d'une heure.
#### Entrée
| Paramètre | Type | Requis | Description |
| --------- | ---- | -------- | ----------- |
| `remote` | string | Oui | Type de dépôt distant : github ou gitlab |
| `repository` | string | Oui | Dépôt au format propriétaire/dépôt \(par exemple, "facebook/react"\) |
| `branch` | string | Oui | Branche à indexer \(par exemple, "main" ou "master"\) |
| `reload` | boolean | Non | Forcer la réindexation même si déjà indexé |
| `notify` | boolean | Non | Envoyer une notification par e-mail lorsque l'indexation est terminée |
| `apiKey` | string | Oui | Clé API Greptile |
| `githubToken` | string | Oui | Jeton d'accès personnel GitHub avec accès en lecture au dépôt |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `repositoryId` | string | Identifiant unique du dépôt indexé \(format : distant:branche:propriétaire/dépôt\) |
| `statusEndpoint` | string | Point de terminaison URL pour vérifier l'état de l'indexation |
| `message` | string | Message d'état concernant l'opération d'indexation |
### `greptile_status`
Vérifier l'état d'indexation d'un dépôt. Utilisez ceci pour vérifier si un dépôt est prêt à être interrogé ou pour surveiller la progression de l'indexation.
#### Entrée
| Paramètre | Type | Requis | Description |
| --------- | ---- | -------- | ----------- |
| `remote` | string | Oui | Type de dépôt distant Git : github ou gitlab |
| `repository` | string | Oui | Dépôt au format propriétaire/dépôt \(par ex., "facebook/react"\) |
| `branch` | string | Oui | Nom de la branche \(par ex., "main" ou "master"\) |
| `apiKey` | string | Oui | Clé API Greptile |
| `githubToken` | string | Oui | Jeton d'accès personnel GitHub avec accès en lecture au dépôt |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `repository` | string | Nom du dépôt \(propriétaire/dépôt\) |
| `remote` | string | Dépôt distant Git \(github/gitlab\) |
| `branch` | string | Nom de la branche |
| `private` | boolean | Indique si le dépôt est privé |
| `status` | string | Statut d'indexation : submitted, cloning, processing, completed ou failed |
| `filesProcessed` | number | Nombre de fichiers traités jusqu'à présent |
| `numFiles` | number | Nombre total de fichiers dans le dépôt |
| `sampleQuestions` | array | Exemples de questions pour le dépôt indexé |
| `sha` | string | SHA du commit Git de la version indexée |
## Remarques
- Catégorie : `tools`
- Type : `greptile`

View File

@@ -0,0 +1,136 @@
---
title: Greptile
description: AI搭載のコードベース検索とQ&A
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="greptile"
color="#e5e5e5"
/>
{/* MANUAL-CONTENT-START:intro */}
[Greptile](https://greptile.com/)は、1つまたは複数のリポジトリにわたってソースコードを検索およびクエリするためのAI搭載開発者ツールです。Greptileを使用すると、エンジニアは自然言語で複雑なコードベースの質問に素早く回答し、関連するファイルやシンボルを見つけ、馴染みのないコードやレガシーコードについての洞察を得ることができます。
Greptileでできること:
- **自然言語でコードベースについて複雑な質問をする**: アーキテクチャ、使用パターン、または特定の実装についてAIが生成した回答を取得します。
- **関連するコード、ファイル、または関数を即座に見つける**: キーワードまたは自然言語クエリを使用して検索し、一致する行、ファイル、またはコードブロックに直接ジャンプします。
- **依存関係と関連性を理解する**: 大規模なコードベース全体で、関数がどこで呼び出されているか、モジュールがどのように関連しているか、またはAPIがどこで使用されているかを明らかにします。
- **オンボーディングとコード探索を加速する**: 深い事前知識がなくても、新しいプロジェクトを素早く立ち上げたり、厄介な問題をデバッグしたりできます。
Sim Greptile統合により、AIエージェントは次のことが可能になります:
- Greptileの高度な言語モデルを使用して、プライベートおよびパブリックリポジトリをクエリおよび検索します。
- コンテキストに関連するコードスニペット、ファイル参照、および説明を取得して、コードレビュー、ドキュメント、および開発ワークフローをサポートします。
- 検索/クエリ結果に基づいてSimワークフローで自動化をトリガーするか、コードインテリジェンスをプロセスに直接埋め込みます。
開発者の生産性を加速したり、ドキュメントを自動化したり、複雑なコードベースに対するチームの理解を強化したりする場合でも、GreptileとSimは、必要な場所でコードインテリジェンスと検索へのシームレスなアクセスを提供します。
{/* MANUAL-CONTENT-END */}
## 使用方法
Greptileを使用して自然言語でコードベースをクエリおよび検索します。コードについてAIが生成した回答を取得し、関連するファイルを見つけ、複雑なコードベースを理解します。
## ツール
### `greptile_query`
自然言語でリポジトリを検索し、関連するコード参照とともに回答を取得します。Greptileは、AIを使用してコードベースを理解し、質問に答えます。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `query` | string | はい | コードベースに関する自然言語の質問 |
| `repositories` | string | はい | カンマ区切りのリポジトリリスト。形式:「github:branch:owner/repo」または「owner/repo」のみ(デフォルトはgithub:main) |
| `sessionId` | string | いいえ | 会話の継続性を保つためのセッションID |
| `genius` | boolean | いいえ | より徹底的な分析のためのジーニアスモードを有効化(遅いがより正確) |
| `apiKey` | string | はい | Greptile APIキー |
| `githubToken` | string | はい | リポジトリ読み取りアクセス権を持つGitHub個人アクセストークン |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `message` | string | クエリに対するAI生成の回答 |
| `sources` | array | 回答を裏付ける関連コード参照 |
### `greptile_search`
自然言語でリポジトリを検索し、回答を生成せずに関連するコード参照を取得します。特定のコードの場所を見つけるのに便利です。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `query` | string | はい | 関連するコードを見つけるための自然言語検索クエリ |
| `repositories` | string | はい | カンマ区切りのリポジトリリスト。形式:「github:branch:owner/repo」または「owner/repo」のみ(デフォルトはgithub:main) |
| `sessionId` | string | いいえ | 会話の継続性を保つためのセッションID |
| `genius` | boolean | いいえ | より徹底的な検索のためのジーニアスモードを有効化(遅いがより正確) |
| `apiKey` | string | はい | Greptile APIキー |
| `githubToken` | string | はい | リポジトリ読み取りアクセス権を持つGitHub個人アクセストークン |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `sources` | array | 検索クエリに一致する関連コード参照 |
### `greptile_index_repo`
Greptileでインデックス化するリポジトリを送信します。リポジトリをクエリする前に、インデックス化を完了する必要があります。小規模なリポジトリは3〜5分、大規模なものは1時間以上かかる場合があります。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `remote` | string | はい | Gitリモートタイプ: githubまたはgitlab |
| `repository` | string | はい | owner/repo形式のリポジトリ例:「facebook/react」 |
| `branch` | string | はい | インデックス化するブランチ(例:「main」または「master」 |
| `reload` | boolean | いいえ | すでにインデックス化されている場合でも強制的に再インデックス化 |
| `notify` | boolean | いいえ | インデックス化完了時にメール通知を送信 |
| `apiKey` | string | はい | Greptile APIキー |
| `githubToken` | string | はい | リポジトリ読み取りアクセス権を持つGitHub Personal Access Token |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `repositoryId` | string | インデックス化されたリポジトリの一意の識別子(形式: remote:branch:owner/repo |
| `statusEndpoint` | string | インデックス化ステータスを確認するためのURLエンドポイント |
| `message` | string | インデックス化操作に関するステータスメッセージ |
### `greptile_status`
リポジトリのインデックス化ステータスを確認します。リポジトリがクエリ可能な状態かどうかを確認したり、インデックス化の進行状況を監視したりするために使用します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `remote` | string | はい | Gitリモートタイプ: githubまたはgitlab |
| `repository` | string | はい | owner/repo形式のリポジトリ \(例: "facebook/react"\) |
| `branch` | string | はい | ブランチ名 \(例: "main"または"master"\) |
| `apiKey` | string | はい | Greptile APIキー |
| `githubToken` | string | はい | リポジトリ読み取りアクセス権を持つGitHub Personal Access Token |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `repository` | string | リポジトリ名 \(owner/repo\) |
| `remote` | string | Gitリモート \(github/gitlab\) |
| `branch` | string | ブランチ名 |
| `private` | boolean | リポジトリがプライベートかどうか |
| `status` | string | インデックス作成ステータス: submitted、cloning、processing、completed、またはfailed |
| `filesProcessed` | number | これまでに処理されたファイル数 |
| `numFiles` | number | リポジトリ内のファイルの総数 |
| `sampleQuestions` | array | インデックス化されたリポジトリのサンプル質問 |
| `sha` | string | インデックス化されたバージョンのGitコミットSHA |
## 注記
- カテゴリ: `tools`
- タイプ: `greptile`

View File

@@ -0,0 +1,136 @@
---
title: Greptile
description: AI 驱动的代码库搜索与问答
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="greptile"
color="#e5e5e5"
/>
{/* MANUAL-CONTENT-START:intro */}
[Greptile](https://greptile.com/) 是一款 AI 驱动的开发者工具可用于在一个或多个代码仓库中搜索和查询源代码。Greptile 让工程师能够用自然语言快速解答复杂的代码库问题,定位相关文件或符号,并深入了解陌生或遗留代码。
使用 Greptile您可以
- **用自然语言就代码库提出复杂问题**:获取关于架构、使用模式或具体实现的 AI 生成答案。
- **即时查找相关代码、文件或函数**:通过关键词或自然语言查询搜索,直接跳转到匹配的行、文件或代码块。
- **理解依赖关系和关联**:发现函数被调用的位置、模块之间的关系,或 API 在大型代码库中的使用情况。
- **加速入职和代码探索**:快速上手新项目,或在无需深厚背景知识的情况下排查棘手问题。
Sim Greptile 集成让您的 AI 代理能够:
- 利用 Greptile 的先进语言模型查询和搜索私有及公共仓库。
- 获取与上下文相关的代码片段、文件引用和解释,支持代码评审、文档编写和开发流程。
- 根据搜索/查询结果在 Sim 工作流中触发自动化,或将代码智能直接嵌入您的流程。
无论您是想提升开发效率、自动化文档还是增强团队对复杂代码库的理解Greptile 与 Sim 都能为您无缝提供代码智能与搜索服务——就在您需要的地方。
{/* MANUAL-CONTENT-END */}
## 使用说明
使用 Greptile 通过自然语言查询和搜索代码库。获取 AI 生成的代码解答,查找相关文件,理解复杂代码库。
## 工具
### `greptile_query`
使用自然语言查询代码仓库并获得带有相关代码引用的答案。Greptile 利用 AI 理解您的代码库并回答问题。
#### 输入
| 参数 | 类型 | 必填 | 描述 |
| --------- | ---- | -------- | ----------- |
| `query` | string | 是 | 关于代码库的自然语言问题 |
| `repositories` | string | 是 | 以逗号分隔的仓库列表。格式:"github:branch:owner/repo" 或 "owner/repo"(默认为 github:main |
| `sessionId` | string | 否 | 用于会话连续性的会话 ID |
| `genius` | boolean | 否 | 启用 genius 模式以进行更深入的分析(速度较慢但更准确) |
| `apiKey` | string | 是 | Greptile API 密钥 |
| `githubToken` | string | 是 | 具有仓库读取权限的 GitHub 个人访问令牌 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `message` | string | AI 生成的查询答案 |
| `sources` | array | 支持答案的相关代码引用 |
### `greptile_search`
使用自然语言搜索代码仓库,获取相关代码引用而不生成答案。适用于查找特定代码位置。
#### 输入
| 参数 | 类型 | 必填 | 描述 |
| --------- | ---- | -------- | ----------- |
| `query` | string | 是 | 用于查找相关代码的自然语言搜索查询 |
| `repositories` | string | 是 | 以逗号分隔的仓库列表。格式:"github:branch:owner/repo" 或 "owner/repo"(默认为 github:main |
| `sessionId` | string | 否 | 用于会话连续性的会话 ID |
| `genius` | boolean | 否 | 启用 genius 模式以进行更深入的搜索(速度较慢但更准确) |
| `apiKey` | string | 是 | Greptile API 密钥 |
| `githubToken` | string | 是 | 具有仓库读取权限的 GitHub 个人访问令牌 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `sources` | array | 与搜索查询匹配的相关代码引用 |
### `greptile_index_repo`
提交一个仓库以供 Greptile 索引。索引完成后才能对仓库进行查询。小型仓库大约需要 3-5 分钟,大型仓库可能需要一个小时以上。
#### 输入
| 参数 | 类型 | 必填 | 描述 |
| --------- | ---- | ------ | ----------- |
| `remote` | string | 是 | Git 远程类型github 或 gitlab |
| `repository` | string | 是 | 以 owner/repo 格式填写的仓库(例如,"facebook/react" |
| `branch` | string | 是 | 要索引的分支(例如,"main" 或 "master" |
| `reload` | boolean | 否 | 即使已被索引也强制重新索引 |
| `notify` | boolean | 否 | 索引完成后发送邮件通知 |
| `apiKey` | string | 是 | Greptile API 密钥 |
| `githubToken` | string | 是 | 具有仓库读取权限的 GitHub 个人访问令牌 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `repositoryId` | string | 已索引仓库的唯一标识符格式remote:branch:owner/repo |
| `statusEndpoint` | string | 用于检查索引状态的 URL 端点 |
| `message` | string | 关于索引操作的状态信息 |
### `greptile_status`
检查仓库的索引状态。可用于验证仓库是否已准备好被查询,或监控索引进度。
#### 输入
| 参数 | 类型 | 必填 | 描述 |
| --------- | ---- | -------- | ----------- |
| `remote` | string | 是 | Git 远程类型github 或 gitlab |
| `repository` | string | 是 | 仓库,格式为 owner/repo例如"facebook/react" |
| `branch` | string | 是 | 分支名称(例如,"main" 或 "master" |
| `apiKey` | string | 是 | Greptile API 密钥 |
| `githubToken` | string | 是 | 具有仓库读取权限的 GitHub 个人访问令牌 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `repository` | string | 仓库名称owner/repo |
| `remote` | string | Git 远程github/gitlab |
| `branch` | string | 分支名称 |
| `private` | boolean | 仓库是否为私有 |
| `status` | string | 索引状态submitted、cloning、processing、completed 或 failed |
| `filesProcessed` | number | 已处理的文件数 |
| `numFiles` | number | 仓库中的文件总数 |
| `sampleQuestions` | array | 已索引仓库的示例问题 |
| `sha` | string | 已索引版本的 Git 提交 SHA |
## 备注
- 分类:`tools`
- 类型:`greptile`

View File

@@ -143,7 +143,6 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| `published_at` | string | 表单发布时间戳ISO 8601 格式) |
| `_links` | object | 相关资源链接,包括公开表单 URL |
### `typeform_create_form`
创建一个包含字段和设置的新表单
@@ -175,7 +174,6 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| `thankyou_screens` | array | 感谢页数组 |
| `_links` | object | 相关资源链接,包括公开表单 URL |
### `typeform_update_form`
使用 JSON Patch 操作更新现有表单

View File

@@ -49960,3 +49960,43 @@ checksums:
content/11: 972721b310d5e3e6e08ec33dc9630f62
content/12: b3f310d5ef115bea5a8b75bf25d7ea9a
content/13: 06a9cbcec05366fe1c873c90c36b4f44
cde6c2ec1df03f206847ed139f21f2d6:
meta/title: d625514dc93a2c27c439aa3f05ef6825
meta/description: ba29063c3aa33a2bd7afe5837c7fdb9e
content/0: 1b031fb0c62c46b177aeed5c3d3f8f80
content/1: 38ab553787a3ea3d2cda30851aedc0ad
content/2: ad8cbe2b67d463500c7df82249a43130
content/3: 68e7aba40d1cd92f4a42ae47e959647c
content/4: 9a605b0b546c260c6274c6090d6f3581
content/5: b0072e1727b0b3aa280be0f214373362
content/6: f23d61d0b583ae7e014fad11cd88d650
content/7: 711f36e27a659049cf42b8678e67156c
content/8: 821e6394b0a953e2b0842b04ae8f3105
content/9: 954eb151461a8567f7c8132661927740
content/10: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
content/11: 7a7984f05e34660cc71f06c220198e31
content/12: dae004748239e77e2532d74494a10d7e
content/13: 371d0e46b4bd2c23f559b8bc112f6955
content/14: 436dadcd195dd06d65d306d054d855a1
content/15: bcadfc362b69078beee0088e5936c98b
content/16: 8ed05ca8d0bb1d22992af58adba9e363
content/17: 0c04ebd8a688a9658529c0dbeb9e91da
content/18: eef5d898e4cd4c4fa684f6f30b5bff63
content/19: 371d0e46b4bd2c23f559b8bc112f6955
content/20: 447485a51605776e0801a7e6b3e57d69
content/21: bcadfc362b69078beee0088e5936c98b
content/22: 9eb1ac86dbadc526a2a97d4d49f5398a
content/23: 5b7448ffa97b9b0f7c92ce378d90d814
content/24: c9b99feb41660b7dfca04fe4cfb5c674
content/25: 371d0e46b4bd2c23f559b8bc112f6955
content/26: 0ce6d9d41e298509e192e4dd0fc654c6
content/27: bcadfc362b69078beee0088e5936c98b
content/28: 97fd7cc117408e1f7a076724a8bcbddf
content/29: c2e4dd92b12a214c7021ab345acb28c6
content/30: de9c09e2e23cfe69029a739ed7a51d83
content/31: 371d0e46b4bd2c23f559b8bc112f6955
content/32: a664478ba3bc1ebecaaebc39fe0d54ec
content/33: bcadfc362b69078beee0088e5936c98b
content/34: 492b7c5af2dd4be062ee7af19778566a
content/35: b3f310d5ef115bea5a8b75bf25d7ea9a
content/36: 1305f85599a560b30c009091311a8dd0

View File

@@ -29,6 +29,7 @@
"tailwind-merge": "^3.0.2"
},
"devDependencies": {
"@sim/tsconfig": "workspace:*",
"@tailwindcss/postcss": "^4.0.12",
"@types/mdx": "^2.0.13",
"@types/node": "^22.14.1",

View File

@@ -1,29 +1,11 @@
{
"extends": "@sim/tsconfig/nextjs.json",
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"paths": {
"@/.source/*": ["./.source/*"],
"@/*": ["./*"]
},
"plugins": [
{
"name": "next"
}
]
}
},
"include": [
"next-env.d.ts",

View File

@@ -47,32 +47,32 @@
@layer base {
:root,
.light {
--bg: #f9faf8; /* main canvas - near white */
--surface-1: #f9faf8; /* sidebar, panels - light warm gray */
--surface-2: #fdfdfb; /* blocks, cards, modals - soft warm white */
--surface-3: #f4f5f1; /* popovers, headers - more contrast */
--surface-4: #f2f3ef; /* buttons base */
--border: #d7dcda; /* primary border */
--surface-5: #f0f1ed; /* inputs, form elements - subtle */
--border-1: #d7dcda; /* stronger border - sage gray */
--surface-6: #eceee9; /* popovers, elevated surfaces */
--surface-7: #e8e9e4;
--bg: #fdfdfd; /* main canvas - neutral near-white */
--surface-1: #fcfcfc; /* sidebar, panels */
--surface-2: #ffffff; /* blocks, cards, modals - pure white */
--surface-3: #f7f7f7; /* popovers, headers */
--surface-4: #f5f5f5; /* buttons base */
--border: #e0e0e0; /* primary border */
--surface-5: #f3f3f3; /* inputs, form elements */
--border-1: #e0e0e0; /* stronger border */
--surface-6: #f0f0f0; /* popovers, elevated surfaces */
--surface-7: #ececec;
--workflow-edge: #d7dcda; /* workflow handles/edges - matches border-1 */
--workflow-edge: #e0e0e0; /* workflow handles/edges - matches border-1 */
/* Text - warm neutrals */
/* Text - neutral */
--text-primary: #2d2d2d;
--text-secondary: #404040;
--text-tertiary: #5c5c5c;
--text-muted: #737373;
--text-subtle: #8c8c8c;
--text-inverse: #f0fff6;
--text-inverse: #ffffff;
--text-error: #ef4444;
/* Borders / dividers */
--divider: #e8e9e4;
--border-muted: #dfe0db;
--border-success: #d7dcda;
--divider: #ededed;
--border-muted: #e4e4e4;
--border-success: #e0e0e0;
/* Brand & state */
--brand-400: #8e4cfb;
@@ -152,13 +152,13 @@
--c-883827: #7c2d12;
/* Terminal status badges */
--terminal-status-error-bg: #feeeee;
--terminal-status-error-bg: #fef2f2;
--terminal-status-error-border: #f87171;
--terminal-status-info-bg: #f5f5f4;
--terminal-status-info-border: #a8a29e;
--terminal-status-info-color: #57534e;
--terminal-status-warning-bg: #fef9e7;
--terminal-status-warning-border: #f5c842;
--terminal-status-info-bg: #f7f7f7;
--terminal-status-info-border: #a3a3a3;
--terminal-status-info-color: #525252;
--terminal-status-warning-bg: #fefce8;
--terminal-status-warning-border: #facc15;
--terminal-status-warning-color: #a16207;
}
.dark {
@@ -391,6 +391,17 @@
color: var(--text-primary);
}
/**
* Subblock divider visibility
* Hides dividers when adjacent subblocks render empty content (e.g., schedule-info without data).
* Uses CSS :has() to detect empty .subblock-content elements and hide associated dividers.
* Selectors ordered by ascending specificity: (0,4,0) then (0,5,0)
*/
.subblock-row:has(> .subblock-content:empty) > .subblock-divider,
.subblock-row:has(+ .subblock-row > .subblock-content:empty) > .subblock-divider {
display: none;
}
/**
* Dark mode specific overrides
*/

View File

@@ -14,7 +14,7 @@ export async function GET() {
headers: {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'SimStudio/1.0',
'User-Agent': 'Sim/1.0',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
next: { revalidate: 3600 },

View File

@@ -68,7 +68,7 @@ function TemplateDetailsLoading({ isWorkspaceContext, workspaceId }: TemplateDet
<div
className={cn(
'flex flex-1 flex-col px-[24px] pt-[24px] pb-[24px]',
isWorkspaceContext ? 'overflow-auto' : 'overflow-visible'
isWorkspaceContext ? 'overflow-auto bg-white dark:bg-[var(--bg)]' : 'overflow-visible'
)}
>
{/* Breadcrumb navigation */}
@@ -638,7 +638,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
<div
className={cn(
'flex flex-1 flex-col px-[24px] pt-[24px] pb-[24px]',
isWorkspaceContext ? 'overflow-auto' : 'overflow-visible'
isWorkspaceContext ? 'overflow-auto bg-white dark:bg-[var(--bg)]' : 'overflow-visible'
)}
>
{/* Breadcrumb navigation */}

View File

@@ -212,7 +212,7 @@ function DocumentLoading({
return (
<div className='flex h-full flex-1 flex-col'>
<div className='flex flex-1 overflow-hidden'>
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[24px] pb-[24px]'>
<div className='flex flex-1 flex-col overflow-auto bg-white px-[24px] pt-[24px] pb-[24px] dark:bg-[var(--bg)]'>
<Breadcrumb items={breadcrumbItems} />
<div className='mt-[14px] flex items-center justify-between'>
@@ -234,7 +234,7 @@ function DocumentLoading({
</div>
<div className='mt-[14px] flex items-center justify-between'>
<div className='flex h-[32px] w-[400px] items-center gap-[6px] rounded-[8px] bg-[var(--surface-4)] px-[8px]'>
<div className='flex h-[32px] w-[400px] items-center gap-[6px] rounded-[8px] bg-[var(--surface-3)] px-[8px] dark:bg-[var(--surface-4)]'>
<Search className='h-[14px] w-[14px] text-[var(--text-subtle)]' />
<Input
placeholder='Search chunks...'
@@ -788,7 +788,7 @@ export function Document({
return (
<div className='flex h-full flex-1 flex-col'>
<div className='flex flex-1 overflow-hidden'>
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[24px] pb-[24px]'>
<div className='flex flex-1 flex-col overflow-auto bg-white px-[24px] pt-[24px] pb-[24px] dark:bg-[var(--bg)]'>
<Breadcrumb items={breadcrumbItems} />
<div className='mt-[14px] flex items-center justify-between'>

View File

@@ -171,7 +171,7 @@ function KnowledgeBaseLoading({ knowledgeBaseName }: KnowledgeBaseLoadingProps)
return (
<div className='flex h-full flex-1 flex-col'>
<div className='flex flex-1 overflow-hidden'>
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[24px] pb-[24px]'>
<div className='flex flex-1 flex-col overflow-auto bg-white px-[24px] pt-[24px] pb-[24px] dark:bg-[var(--bg)]'>
<Breadcrumb items={breadcrumbItems} />
<div className='mt-[14px] flex items-center justify-between'>
@@ -193,7 +193,7 @@ function KnowledgeBaseLoading({ knowledgeBaseName }: KnowledgeBaseLoadingProps)
</div>
<div className='mt-[14px] flex items-center justify-between'>
<div className='flex h-[32px] w-[400px] items-center gap-[6px] rounded-[8px] bg-[var(--surface-4)] px-[8px]'>
<div className='flex h-[32px] w-[400px] items-center gap-[6px] rounded-[8px] bg-[var(--surface-3)] px-[8px] dark:bg-[var(--surface-4)]'>
<Search className='h-[14px] w-[14px] text-[var(--text-subtle)]' />
<Input
placeholder='Search documents...'
@@ -987,7 +987,7 @@ export function KnowledgeBase({
return (
<div className='flex h-full flex-1 flex-col'>
<div className='flex flex-1 overflow-hidden'>
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[24px] pb-[24px]'>
<div className='flex flex-1 flex-col overflow-auto bg-white px-[24px] pt-[24px] pb-[24px] dark:bg-[var(--bg)]'>
<Breadcrumb items={breadcrumbItems} />
<div className='mt-[14px] flex items-center justify-between'>

View File

@@ -67,26 +67,26 @@ function formatAbsoluteDate(dateString: string): string {
*/
export function BaseCardSkeleton() {
return (
<div className='group flex h-full cursor-pointer flex-col gap-[12px] rounded-[4px] bg-[var(--surface-4)] px-[8px] py-[6px] transition-colors hover:bg-[var(--surface-5)]'>
<div className='group flex h-full cursor-pointer flex-col gap-[12px] rounded-[4px] bg-[var(--surface-3)] px-[8px] py-[6px] transition-colors hover:bg-[var(--surface-4)] dark:bg-[var(--surface-4)] dark:hover:bg-[var(--surface-5)]'>
<div className='flex items-center justify-between gap-[8px]'>
<div className='h-[17px] w-[120px] animate-pulse rounded-[4px] bg-[var(--surface-5)]' />
<div className='h-[22px] w-[90px] animate-pulse rounded-[4px] bg-[var(--surface-4)]' />
<div className='h-[17px] w-[120px] animate-pulse rounded-[4px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='h-[22px] w-[90px] animate-pulse rounded-[4px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
</div>
<div className='flex flex-1 flex-col gap-[8px]'>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-[6px]'>
<div className='h-[12px] w-[12px] animate-pulse rounded-[2px] bg-[var(--surface-5)]' />
<div className='h-[15px] w-[45px] animate-pulse rounded-[4px] bg-[var(--surface-5)]' />
<div className='h-[12px] w-[12px] animate-pulse rounded-[2px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='h-[15px] w-[45px] animate-pulse rounded-[4px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
</div>
<div className='h-[15px] w-[120px] animate-pulse rounded-[4px] bg-[var(--surface-4)]' />
<div className='h-[15px] w-[120px] animate-pulse rounded-[4px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
</div>
<div className='h-0 w-full border-[var(--divider)] border-t' />
<div className='flex h-[36px] flex-col gap-[6px]'>
<div className='h-[15px] w-full animate-pulse rounded-[4px] bg-[var(--surface-4)]' />
<div className='h-[15px] w-[75%] animate-pulse rounded-[4px] bg-[var(--surface-4)]' />
<div className='h-[15px] w-full animate-pulse rounded-[4px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='h-[15px] w-[75%] animate-pulse rounded-[4px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
</div>
</div>
</div>
@@ -122,7 +122,7 @@ export function BaseCard({ id, title, docCount, description, updatedAt }: BaseCa
return (
<Link href={href} prefetch={true} className='h-full'>
<div className='group flex h-full cursor-pointer flex-col gap-[12px] rounded-[4px] bg-[var(--surface-4)] px-[8px] py-[6px] transition-colors hover:bg-[var(--surface-5)]'>
<div className='group flex h-full cursor-pointer flex-col gap-[12px] rounded-[4px] bg-[var(--surface-3)] px-[8px] py-[6px] transition-colors hover:bg-[var(--surface-4)] dark:bg-[var(--surface-4)] dark:hover:bg-[var(--surface-5)]'>
<div className='flex items-center justify-between gap-[8px]'>
<h3 className='min-w-0 flex-1 truncate font-medium text-[14px] text-[var(--text-primary)]'>
{title}

View File

@@ -131,7 +131,7 @@ export function Knowledge() {
<>
<div className='flex h-full flex-1 flex-col'>
<div className='flex flex-1 overflow-hidden'>
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
<div className='flex flex-1 flex-col overflow-auto bg-white px-[24px] pt-[28px] pb-[24px] dark:bg-[var(--bg)]'>
<div>
<div className='flex items-start gap-[12px]'>
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#5BB377] bg-[#E8F7EE] dark:border-[#1E5A3E] dark:bg-[#0F3D2C]'>

View File

@@ -2,5 +2,9 @@
* Knowledge Base layout - applies sidebar padding for all knowledge routes.
*/
export default function KnowledgeLayout({ children }: { children: React.ReactNode }) {
return <div className='flex h-full flex-1 flex-col overflow-hidden pl-60'>{children}</div>
return (
<div className='flex h-full flex-1 flex-col overflow-hidden pl-[var(--sidebar-width)]'>
{children}
</div>
)
}

View File

@@ -35,9 +35,9 @@ export function WorkflowsList({
const { workflows } = useWorkflowRegistry()
return (
<div className='flex h-full flex-col overflow-hidden rounded-[6px] bg-[var(--surface-1)]'>
<div className='flex h-full flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)] dark:bg-[var(--surface-1)]'>
{/* Table header */}
<div className='flex-shrink-0 rounded-t-[6px] bg-[var(--surface-3)] px-[24px] py-[10px]'>
<div className='flex-shrink-0 rounded-t-[6px] bg-[var(--surface-3)] px-[24px] py-[10px] dark:bg-[var(--surface-3)]'>
<div className='flex items-center gap-[16px]'>
<span className='w-[160px] flex-shrink-0 font-medium text-[12px] text-[var(--text-tertiary)]'>
Workflow
@@ -66,8 +66,8 @@ export function WorkflowsList({
<div
key={workflow.workflowId}
className={cn(
'flex h-[44px] cursor-pointer items-center gap-[16px] px-[24px] hover:bg-[var(--surface-6)] dark:hover:bg-[var(--surface-4)]',
isSelected && 'bg-[var(--surface-6)] dark:bg-[var(--surface-4)]'
'flex h-[44px] cursor-pointer items-center gap-[16px] px-[24px] hover:bg-[var(--surface-3)] dark:hover:bg-[var(--surface-4)]',
isSelected && 'bg-[var(--surface-3)] dark:bg-[var(--surface-4)]'
)}
onClick={() => onToggleWorkflow(workflow.workflowId)}
>

View File

@@ -36,14 +36,14 @@ const SKELETON_BAR_HEIGHTS = [
function GraphCardSkeleton({ title }: { title: string }) {
return (
<div className='flex flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)]'>
<div className='flex min-w-0 items-center justify-between gap-[8px] bg-[var(--surface-3)] px-[16px] py-[9px]'>
<div className='flex flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)] dark:bg-[var(--surface-2)]'>
<div className='flex min-w-0 items-center justify-between gap-[8px] bg-[var(--surface-3)] px-[16px] py-[9px] dark:bg-[var(--surface-3)]'>
<span className='min-w-0 truncate font-medium text-[var(--text-primary)] text-sm'>
{title}
</span>
<Skeleton className='h-[20px] w-[40px]' />
</div>
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-2)] px-[14px] py-[10px] dark:bg-[var(--surface-1)]'>
<div className='flex h-[166px] flex-col justify-end gap-[4px]'>
<div className='flex items-end gap-[2px]'>
{SKELETON_BAR_HEIGHTS.map((height, i) => (
@@ -81,8 +81,8 @@ function WorkflowRowSkeleton() {
function WorkflowsListSkeleton({ rowCount = 5 }: { rowCount?: number }) {
return (
<div className='flex h-full flex-col overflow-hidden rounded-[6px] bg-[var(--surface-1)]'>
<div className='flex-shrink-0 rounded-t-[6px] bg-[var(--surface-3)] px-[24px] py-[10px]'>
<div className='flex h-full flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)] dark:bg-[var(--surface-1)]'>
<div className='flex-shrink-0 rounded-t-[6px] bg-[var(--surface-3)] px-[24px] py-[10px] dark:bg-[var(--surface-3)]'>
<div className='flex items-center gap-[16px]'>
<span className='w-[160px] flex-shrink-0 font-medium text-[12px] text-[var(--text-tertiary)]'>
Workflow
@@ -570,8 +570,8 @@ export default function Dashboard({ logs, isLoading, error }: DashboardProps) {
<div className='mt-[24px] flex min-h-0 flex-1 flex-col pb-[24px]'>
<div className='mb-[16px] flex-shrink-0'>
<div className='grid grid-cols-1 gap-[16px] md:grid-cols-3'>
<div className='flex flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)]'>
<div className='flex min-w-0 items-center justify-between gap-[8px] bg-[var(--surface-3)] px-[16px] py-[9px]'>
<div className='flex flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)] dark:bg-[var(--surface-2)]'>
<div className='flex min-w-0 items-center justify-between gap-[8px] bg-[var(--surface-3)] px-[16px] py-[9px] dark:bg-[var(--surface-3)]'>
<span className='min-w-0 truncate font-medium text-[var(--text-primary)] text-sm'>
Runs
</span>
@@ -581,7 +581,7 @@ export default function Dashboard({ logs, isLoading, error }: DashboardProps) {
</span>
)}
</div>
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-2)] px-[14px] py-[10px] dark:bg-[var(--surface-1)]'>
{globalDetails ? (
<LineChart
data={globalDetails.executionCounts}
@@ -597,8 +597,8 @@ export default function Dashboard({ logs, isLoading, error }: DashboardProps) {
</div>
</div>
<div className='flex flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)]'>
<div className='flex min-w-0 items-center justify-between gap-[8px] bg-[var(--surface-3)] px-[16px] py-[9px]'>
<div className='flex flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)] dark:bg-[var(--surface-2)]'>
<div className='flex min-w-0 items-center justify-between gap-[8px] bg-[var(--surface-3)] px-[16px] py-[9px] dark:bg-[var(--surface-3)]'>
<span className='min-w-0 truncate font-medium text-[var(--text-primary)] text-sm'>
Errors
</span>
@@ -608,7 +608,7 @@ export default function Dashboard({ logs, isLoading, error }: DashboardProps) {
</span>
)}
</div>
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-2)] px-[14px] py-[10px] dark:bg-[var(--surface-1)]'>
{globalDetails ? (
<LineChart
data={globalDetails.failureCounts}
@@ -624,8 +624,8 @@ export default function Dashboard({ logs, isLoading, error }: DashboardProps) {
</div>
</div>
<div className='flex flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)]'>
<div className='flex min-w-0 items-center justify-between gap-[8px] bg-[var(--surface-3)] px-[16px] py-[9px]'>
<div className='flex flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)] dark:bg-[var(--surface-2)]'>
<div className='flex min-w-0 items-center justify-between gap-[8px] bg-[var(--surface-3)] px-[16px] py-[9px] dark:bg-[var(--surface-3)]'>
<span className='min-w-0 truncate font-medium text-[var(--text-primary)] text-sm'>
Latency
</span>
@@ -635,7 +635,7 @@ export default function Dashboard({ logs, isLoading, error }: DashboardProps) {
</span>
)}
</div>
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-1)] px-[14px] py-[10px]'>
<div className='flex-1 overflow-y-auto rounded-t-[6px] bg-[var(--surface-2)] px-[14px] py-[10px] dark:bg-[var(--surface-1)]'>
{globalDetails ? (
<LineChart
data={globalDetails.latencies}

View File

@@ -38,8 +38,8 @@ const LogRow = memo(
<div
ref={isSelected ? selectedRowRef : null}
className={cn(
'relative flex h-[44px] cursor-pointer items-center px-[24px] hover:bg-[var(--surface-4)]',
isSelected && 'bg-[var(--surface-4)]'
'relative flex h-[44px] cursor-pointer items-center px-[24px] hover:bg-[var(--surface-3)] dark:hover:bg-[var(--surface-4)]',
isSelected && 'bg-[var(--surface-3)] dark:bg-[var(--surface-4)]'
)}
onClick={handleClick}
>

View File

@@ -163,7 +163,7 @@ export function AutocompleteSearch({
}}
>
<PopoverAnchor asChild>
<div className='relative flex h-[32px] w-[400px] items-center rounded-[8px] bg-[var(--surface-4)]'>
<div className='relative flex h-[32px] w-[400px] items-center rounded-[8px] bg-[var(--surface-3)] dark:bg-[var(--surface-4)]'>
{/* Search Icon */}
<Search className='mr-[6px] ml-[8px] h-[14px] w-[14px] flex-shrink-0 text-[var(--text-subtle)]' />
@@ -179,7 +179,7 @@ export function AutocompleteSearch({
className={cn(
'h-6 shrink-0 cursor-pointer whitespace-nowrap rounded-md px-2 text-[11px]',
highlightedBadgeIndex === index &&
'ring-1 ring-[var(--border-focus)] ring-offset-1 ring-offset-[var(--surface-5)]'
'ring-1 ring-[var(--border-focus)] ring-offset-1 ring-offset-[var(--surface-3)] dark:ring-offset-[var(--surface-5)]'
)}
onClick={() => removeBadge(index)}
onKeyDown={(e) => {

View File

@@ -1,3 +1,7 @@
export default function LogsLayout({ children }: { children: React.ReactNode }) {
return <div className='flex h-full flex-1 flex-col overflow-hidden pl-60'>{children}</div>
return (
<div className='flex h-full flex-1 flex-col overflow-hidden pl-[var(--sidebar-width)]'>
{children}
</div>
)
}

View File

@@ -344,7 +344,7 @@ export default function Logs() {
return (
<div className='flex h-full flex-1 flex-col overflow-hidden'>
<div className='flex flex-1 overflow-hidden'>
<div className='flex flex-1 flex-col overflow-auto pt-[28px] pl-[24px]'>
<div className='flex flex-1 flex-col overflow-auto bg-white pt-[28px] pl-[24px] dark:bg-[var(--bg)]'>
<div className='pr-[24px]'>
<LogsToolbar
viewMode={viewMode}
@@ -385,9 +385,9 @@ export default function Logs() {
)}
>
{/* Table container */}
<div className='relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-[6px] bg-[var(--surface-1)]'>
<div className='relative flex min-h-0 flex-1 flex-col overflow-hidden rounded-[6px] bg-[var(--surface-2)] dark:bg-[var(--surface-1)]'>
{/* Table header */}
<div className='flex-shrink-0 rounded-t-[6px] bg-[var(--surface-3)] px-[24px] py-[10px]'>
<div className='flex-shrink-0 rounded-t-[6px] bg-[var(--surface-3)] px-[24px] py-[10px] dark:bg-[var(--surface-3)]'>
<div className='flex items-center'>
<span className='w-[8%] min-w-[70px] font-medium text-[12px] text-[var(--text-tertiary)]'>
Date

View File

@@ -29,19 +29,19 @@ export function TemplateCardSkeleton({ className }: { className?: string }) {
return (
<div
className={cn(
'h-[268px] w-full rounded-[8px] bg-[var(--surface-4)] p-[8px] transition-colors hover:bg-[var(--surface-5)]',
'h-[268px] w-full rounded-[8px] bg-[var(--surface-3)] p-[8px] transition-colors hover:bg-[var(--surface-4)] dark:bg-[var(--surface-4)] dark:hover:bg-[var(--surface-5)]',
className
)}
>
<div className='h-[180px] w-full animate-pulse rounded-[6px] bg-[var(--surface-5)]' />
<div className='h-[180px] w-full animate-pulse rounded-[6px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='mt-[14px] flex items-center justify-between'>
<div className='h-4 w-32 animate-pulse rounded bg-[var(--surface-5)]' />
<div className='h-4 w-32 animate-pulse rounded bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='flex items-center gap-[-4px]'>
{Array.from({ length: 3 }).map((_, index) => (
<div
key={index}
className='h-[18px] w-[18px] animate-pulse rounded-[4px] bg-[var(--surface-5)]'
className='h-[18px] w-[18px] animate-pulse rounded-[4px] bg-[var(--surface-4)] dark:bg-[var(--surface-5)]'
/>
))}
</div>
@@ -49,14 +49,14 @@ export function TemplateCardSkeleton({ className }: { className?: string }) {
<div className='mt-[14px] flex items-center justify-between'>
<div className='flex items-center gap-[6px]'>
<div className='h-[20px] w-[20px] animate-pulse rounded-full bg-[var(--surface-5)]' />
<div className='h-3 w-20 animate-pulse rounded bg-[var(--surface-5)]' />
<div className='h-[20px] w-[20px] animate-pulse rounded-full bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='h-3 w-20 animate-pulse rounded bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
</div>
<div className='flex items-center gap-[6px]'>
<div className='h-3 w-3 animate-pulse rounded bg-[var(--surface-5)]' />
<div className='h-3 w-6 animate-pulse rounded bg-[var(--surface-5)]' />
<div className='h-3 w-3 animate-pulse rounded bg-[var(--surface-5)]' />
<div className='h-3 w-6 animate-pulse rounded bg-[var(--surface-5)]' />
<div className='h-3 w-3 animate-pulse rounded bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='h-3 w-6 animate-pulse rounded bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='h-3 w-3 animate-pulse rounded bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
<div className='h-3 w-6 animate-pulse rounded bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
</div>
</div>
</div>
@@ -202,7 +202,7 @@ function TemplateCardInner({
<div
onClick={handleCardClick}
className={cn(
'w-full cursor-pointer rounded-[8px] bg-[var(--surface-4)] p-[8px] transition-colors hover:bg-[var(--surface-5)]',
'w-full cursor-pointer rounded-[8px] bg-[var(--surface-3)] p-[8px] transition-colors hover:bg-[var(--surface-4)] dark:bg-[var(--surface-4)] dark:hover:bg-[var(--surface-5)]',
className
)}
>
@@ -223,7 +223,7 @@ function TemplateCardInner({
cursorStyle='pointer'
/>
) : (
<div className='h-full w-full bg-[var(--surface-4)]' />
<div className='h-full w-full bg-[var(--surface-4)] dark:bg-[var(--surface-5)]' />
)}
</div>

View File

@@ -2,5 +2,9 @@
* Templates layout - applies sidebar padding for all template routes.
*/
export default function TemplatesLayout({ children }: { children: React.ReactNode }) {
return <main className='flex h-full flex-1 flex-col overflow-hidden pl-60'>{children}</main>
return (
<main className='flex h-full flex-1 flex-col overflow-hidden pl-[var(--sidebar-width)]'>
{children}
</main>
)
}

View File

@@ -172,7 +172,7 @@ export default function Templates({
return (
<div className='flex h-full flex-1 flex-col'>
<div className='flex flex-1 overflow-hidden'>
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
<div className='flex flex-1 flex-col overflow-auto bg-white px-[24px] pt-[28px] pb-[24px] dark:bg-[var(--bg)]'>
<div>
<div className='flex items-start gap-[12px]'>
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#5BA8D9] bg-[#E8F4FB] dark:border-[#1A5070] dark:bg-[#153347]'>

View File

@@ -115,7 +115,6 @@ interface CopilotMarkdownRendererProps {
export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRendererProps) {
const [copiedCodeBlocks, setCopiedCodeBlocks] = useState<Record<string, boolean>>({})
// Reset copy success state after 2 seconds
useEffect(() => {
const timers: Record<string, NodeJS.Timeout> = {}
@@ -132,17 +131,14 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
}
}, [copiedCodeBlocks])
// Custom components for react-markdown with current styling - memoized to prevent re-renders
const markdownComponents = useMemo(
() => ({
// Paragraph
p: ({ children }: React.HTMLAttributes<HTMLParagraphElement>) => (
<p className='mb-1 font-base font-season text-[var(--text-primary)] text-sm leading-[1.25rem] last:mb-0 dark:font-[470]'>
<p className='mb-2 font-base font-season text-[var(--text-primary)] text-sm leading-[1.25rem] last:mb-0 dark:font-[470]'>
{children}
</p>
),
// Headings
h1: ({ children }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h1 className='mt-3 mb-3 font-season font-semibold text-2xl text-[var(--text-primary)]'>
{children}
@@ -159,15 +155,14 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</h3>
),
h4: ({ children }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h4 className='mt-5 mb-2 font-season font-semibold text-[var(--text-primary)] text-base'>
<h4 className='mt-2 mb-2 font-season font-semibold text-[var(--text-primary)] text-base'>
{children}
</h4>
),
// Lists
ul: ({ children }: React.HTMLAttributes<HTMLUListElement>) => (
<ul
className='mt-1 mb-1 space-y-1 pl-6 font-base font-season text-[var(--text-primary)] dark:font-[470]'
className='mt-1 mb-1 space-y-1.5 pl-6 font-base font-season text-[var(--text-primary)] dark:font-[470]'
style={{ listStyleType: 'disc' }}
>
{children}
@@ -175,7 +170,7 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
),
ol: ({ children }: React.HTMLAttributes<HTMLOListElement>) => (
<ol
className='mt-1 mb-1 space-y-1 pl-6 font-base font-season text-[var(--text-primary)] dark:font-[470]'
className='mt-1 mb-1 space-y-1.5 pl-6 font-base font-season text-[var(--text-primary)] dark:font-[470]'
style={{ listStyleType: 'decimal' }}
>
{children}
@@ -193,7 +188,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</li>
),
// Code blocks - render using shared Code.Viewer for consistent styling
pre: ({ children }: React.HTMLAttributes<HTMLPreElement>) => {
let codeContent: React.ReactNode = children
let language = 'code'
@@ -210,15 +204,12 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
language = childElement.props.className?.replace('language-', '') || 'code'
}
// Extract actual text content
let actualCodeText = ''
if (typeof codeContent === 'string') {
actualCodeText = codeContent
} else if (React.isValidElement(codeContent)) {
// If it's a React element, try to get its text content
actualCodeText = getTextContent(codeContent)
} else if (Array.isArray(codeContent)) {
// If it's an array of elements, join their text content
actualCodeText = codeContent
.map((child) =>
typeof child === 'string'
@@ -232,7 +223,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
actualCodeText = String(codeContent || '')
}
// Create a unique key for this code block based on content
const codeText = actualCodeText || 'code'
const codeBlockKey = `${language}-${codeText.substring(0, 30).replace(/\s/g, '-')}-${codeText.length}`
@@ -246,7 +236,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
}
}
// Map markdown language tag to Code.Viewer supported languages
const normalizedLanguage = (language || '').toLowerCase()
const viewerLanguage: 'javascript' | 'json' | 'python' =
normalizedLanguage === 'json'
@@ -256,7 +245,7 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
: 'javascript'
return (
<div className='my-6 w-0 min-w-full overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)] text-sm'>
<div className='mt-6 mb-6 w-0 min-w-full overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)] text-sm'>
<div className='flex items-center justify-between border-[var(--border-1)] border-b px-4 py-1.5'>
<span className='font-season text-[var(--text-muted)] text-xs'>
{language === 'code' ? viewerLanguage : language}
@@ -274,16 +263,15 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</button>
</div>
<Code.Viewer
code={actualCodeText}
code={actualCodeText.replace(/\n+$/, '')}
showGutter
language={viewerLanguage}
className='[&_pre]:!pb-0 m-0 rounded-none border-0 bg-transparent'
className='m-0 min-h-0 rounded-none border-0 bg-transparent'
/>
</div>
)
},
// Inline code
code: ({
inline,
className,
@@ -307,44 +295,36 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
)
},
// Bold text
strong: ({ children }: React.HTMLAttributes<HTMLElement>) => (
<strong className='font-semibold text-[var(--text-primary)]'>{children}</strong>
),
// Bold text (alternative)
b: ({ children }: React.HTMLAttributes<HTMLElement>) => (
<b className='font-semibold text-[var(--text-primary)]'>{children}</b>
),
// Italic text
em: ({ children }: React.HTMLAttributes<HTMLElement>) => (
<em className='text-[var(--text-primary)] italic'>{children}</em>
),
// Italic text (alternative)
i: ({ children }: React.HTMLAttributes<HTMLElement>) => (
<i className='text-[var(--text-primary)] italic'>{children}</i>
),
// Blockquotes
blockquote: ({ children }: React.HTMLAttributes<HTMLQuoteElement>) => (
<blockquote className='my-4 border-[var(--border-1)] border-l-4 py-1 pl-4 font-season text-[var(--text-secondary)] italic'>
{children}
</blockquote>
),
// Horizontal rule
hr: () => <hr className='my-8 border-[var(--divider)] border-t' />,
// Links
a: ({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
<LinkWithPreview href={href || '#'} {...props}>
{children}
</LinkWithPreview>
),
// Tables
table: ({ children }: React.TableHTMLAttributes<HTMLTableElement>) => (
<div className='my-4 max-w-full overflow-x-auto'>
<table className='min-w-full table-auto border border-[var(--border-1)] font-season text-sm'>
@@ -376,7 +356,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</td>
),
// Images
img: ({ src, alt, ...props }: React.ImgHTMLAttributes<HTMLImageElement>) => (
<img
src={src}

View File

@@ -3,7 +3,15 @@
import { useMemo } from 'react'
import { createLogger } from '@sim/logger'
import { Check } from 'lucide-react'
import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/emcn'
import {
Badge,
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
} from '@/components/emcn'
import { client } from '@/lib/auth/auth-client'
import {
getProviderIdFromServiceId,
@@ -407,9 +415,9 @@ export function OAuthRequiredModal({
<div className='flex flex-1 items-center gap-[8px] text-[12px] text-[var(--text-primary)]'>
<span>{getScopeDescription(scope)}</span>
{newScopesSet.has(scope) && (
<span className='inline-flex items-center gap-[6px] rounded-[6px] bg-[#fde68a] px-[7px] py-[1px] font-medium text-[#a16207] text-[11px] dark:bg-[rgba(245,158,11,0.2)] dark:text-[#fcd34d]'>
<Badge variant='amber' size='sm'>
New
</span>
</Badge>
)}
</div>
</li>

View File

@@ -228,7 +228,7 @@ export function CredentialSelector({
)
return (
<>
<div>
<Combobox
options={comboboxOptions}
value={inputValue}
@@ -247,9 +247,20 @@ export function CredentialSelector({
/>
{needsUpdate && (
<div className='mt-2 flex items-center justify-between rounded-[6px] border border-amber-300/40 bg-amber-50/60 px-2 py-1 font-medium text-[12px] transition-colors dark:bg-amber-950/10'>
<span>Additional permissions required</span>
{!isForeign && <Button onClick={() => setShowOAuthModal(true)}>Update access</Button>}
<div className='mt-[8px] flex flex-col gap-[4px] rounded-[4px] border bg-[var(--surface-2)] px-[8px] py-[6px]'>
<div className='flex items-center font-medium text-[12px]'>
<span className='mr-[6px] inline-block h-[6px] w-[6px] rounded-[2px] bg-amber-500' />
Additional permissions required
</div>
{!isForeign && (
<Button
variant='active'
onClick={() => setShowOAuthModal(true)}
className='w-full px-[8px] py-[4px] font-medium text-[12px]'
>
Update access
</Button>
)}
</div>
)}
@@ -264,7 +275,7 @@ export function CredentialSelector({
serviceId={serviceId}
/>
)}
</>
</div>
)
}

View File

@@ -537,7 +537,7 @@ export function McpDynamicArgs({
}
return (
<div className='relative space-y-4'>
<div className='relative'>
{/* Hidden dummy inputs to prevent browser password manager autofill */}
<input
type='text'
@@ -563,28 +563,30 @@ export function McpDynamicArgs({
tabIndex={-1}
readOnly
/>
{toolSchema.properties &&
Object.entries(toolSchema.properties).map(([paramName, paramSchema]) => {
const inputType = getInputType(paramSchema as any)
const showLabel = inputType !== 'switch'
<div className='space-y-4'>
{toolSchema.properties &&
Object.entries(toolSchema.properties).map(([paramName, paramSchema]) => {
const inputType = getInputType(paramSchema as any)
const showLabel = inputType !== 'switch'
return (
<div key={paramName} className='space-y-2'>
{showLabel && (
<Label
className={cn(
'font-medium text-sm',
toolSchema.required?.includes(paramName) &&
'after:ml-1 after:text-red-500 after:content-["*"]'
)}
>
{formatParameterLabel(paramName)}
</Label>
)}
{renderParameterInput(paramName, paramSchema as any)}
</div>
)
})}
return (
<div key={paramName} className='space-y-2'>
{showLabel && (
<Label
className={cn(
'font-medium text-sm',
toolSchema.required?.includes(paramName) &&
'after:ml-1 after:text-red-500 after:content-["*"]'
)}
>
{formatParameterLabel(paramName)}
</Label>
)}
{renderParameterInput(paramName, paramSchema as any)}
</div>
)
})}
</div>
</div>
)
}

View File

@@ -200,7 +200,7 @@ export function ToolCredentialSelector({
)
return (
<>
<div>
<Combobox
options={comboboxOptions}
value={inputValue}
@@ -217,9 +217,20 @@ export function ToolCredentialSelector({
/>
{needsUpdate && (
<div className='mt-2 flex items-center justify-between rounded-[6px] border border-amber-300/40 bg-amber-50/60 px-2 py-1 font-medium text-[12px] transition-colors dark:bg-amber-950/10'>
<span>Additional permissions required</span>
{!isForeign && <Button onClick={() => setShowOAuthModal(true)}>Update access</Button>}
<div className='mt-[8px] flex flex-col gap-[4px] rounded-[4px] border bg-[var(--surface-2)] px-[8px] py-[6px]'>
<div className='flex items-center font-medium text-[12px]'>
<span className='mr-[6px] inline-block h-[6px] w-[6px] rounded-[2px] bg-amber-500' />
Additional permissions required
</div>
{!isForeign && (
<Button
variant='active'
onClick={() => setShowOAuthModal(true)}
className='w-full px-[8px] py-[4px] font-medium text-[12px]'
>
Update access
</Button>
)}
</div>
)}
@@ -234,7 +245,7 @@ export function ToolCredentialSelector({
serviceId={serviceId}
/>
)}
</>
</div>
)
}

View File

@@ -866,7 +866,7 @@ function SubBlockComponent({
}
return (
<div onMouseDown={handleMouseDown} className='flex flex-col gap-[10px]'>
<div onMouseDown={handleMouseDown} className='subblock-content flex flex-col gap-[10px]'>
{renderLabel(
config,
isValidJson,

View File

@@ -341,7 +341,7 @@ export function Editor() {
)
return (
<div key={stableKey}>
<div key={stableKey} className='subblock-row'>
<SubBlock
blockId={currentBlockId}
config={subBlock}
@@ -352,7 +352,7 @@ export function Editor() {
allowExpandInPreview={false}
/>
{index < subBlocks.length - 1 && (
<div className='px-[2px] pt-[16px] pb-[13px]'>
<div className='subblock-divider px-[2px] pt-[16px] pb-[13px]'>
<div
className='h-[1.25px]'
style={{

View File

@@ -108,12 +108,12 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
*/
const getHandleClasses = (position: 'left' | 'right') => {
const baseClasses = '!z-[10] !cursor-crosshair !border-none !transition-[colors] !duration-150'
const colorClasses = '!bg-[var(--surface-7)]'
const colorClasses = '!bg-[var(--workflow-edge)]'
const positionClasses = {
left: '!left-[-7px] !h-5 !w-[7px] !rounded-l-[2px] !rounded-r-none hover:!left-[-10px] hover:!w-[10px] hover:!rounded-l-full',
left: '!left-[-8px] !h-5 !w-[7px] !rounded-l-[2px] !rounded-r-none hover:!left-[-11px] hover:!w-[10px] hover:!rounded-l-full',
right:
'!right-[-7px] !h-5 !w-[7px] !rounded-r-[2px] !rounded-l-none hover:!right-[-10px] hover:!w-[10px] hover:!rounded-r-full',
'!right-[-8px] !h-5 !w-[7px] !rounded-r-[2px] !rounded-l-none hover:!right-[-11px] hover:!w-[10px] hover:!rounded-r-full',
}
return cn(baseClasses, colorClasses, positionClasses[position])
@@ -205,13 +205,12 @@ export const SubflowNodeComponent = memo(({ data, id }: NodeProps<SubflowNodeDat
data-dragarea='true'
style={{
position: 'relative',
minHeight: '100%',
pointerEvents: isPreview ? 'none' : 'auto',
}}
>
{/* Subflow Start */}
<div
className='absolute top-[16px] left-[16px] flex items-center justify-center rounded-[8px] bg-[var(--surface-2)] px-[12px] py-[6px]'
className='absolute top-[16px] left-[16px] flex items-center justify-center rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-2)] px-[12px] py-[6px]'
style={{ pointerEvents: isPreview ? 'none' : 'auto' }}
data-parent-id={id}
data-node-role={`${data.kind}-start`}

View File

@@ -252,7 +252,7 @@ export function useWand({
})
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.user() })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
}, 1000)
} catch (error: any) {
if (error.name === 'AbortError') {

View File

@@ -573,7 +573,7 @@ export function useWorkflowExecution() {
// Invalidate subscription queries to update usage
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.user() })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
}, 1000)
safeEnqueue(encodeSSE({ event: 'final', data: result }))
@@ -646,7 +646,7 @@ export function useWorkflowExecution() {
// Invalidate subscription queries to update usage
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.user() })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
}, 1000)
}
return result

View File

@@ -165,7 +165,7 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub
logger.info('Subscription restored successfully', result)
}
await queryClient.invalidateQueries({ queryKey: subscriptionKeys.user() })
await queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
if (activeOrgId) {
await queryClient.invalidateQueries({ queryKey: organizationKeys.detail(activeOrgId) })
await queryClient.invalidateQueries({ queryKey: organizationKeys.billing(activeOrgId) })

View File

@@ -199,7 +199,7 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
useEffect(() => {
const handleOperationConfirmed = () => {
setTimeout(() => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.user() })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
}, 1000)
}
onOperationConfirmed(handleOperationConfirmed)

View File

@@ -30,9 +30,11 @@ function WorkflowPreviewSubflowInner({ data }: NodeProps<WorkflowPreviewSubflowD
const startHandleId = isLoop ? 'loop-start-source' : 'parallel-start-source'
const endHandleId = isLoop ? 'loop-end-source' : 'parallel-end-source'
// Handle styles matching the actual subflow component
const handleClass =
'!border-none !bg-[var(--surface-7)] !h-5 !w-[7px] !rounded-l-[2px] !rounded-r-[2px]'
// Handle styles matching the workflow-block component
const leftHandleClass =
'!z-[10] !border-none !bg-[var(--workflow-edge)] !h-5 !w-[7px] !rounded-l-[2px] !rounded-r-none'
const rightHandleClass =
'!z-[10] !border-none !bg-[var(--workflow-edge)] !h-5 !w-[7px] !rounded-r-[2px] !rounded-l-none'
return (
<div
@@ -47,9 +49,9 @@ function WorkflowPreviewSubflowInner({ data }: NodeProps<WorkflowPreviewSubflowD
type='target'
position={Position.Left}
id='target'
className={handleClass}
className={leftHandleClass}
style={{
left: '-7px',
left: '-8px',
top: `${HANDLE_POSITIONS.DEFAULT_Y_OFFSET}px`,
transform: 'translateY(-50%)',
}}
@@ -69,14 +71,14 @@ function WorkflowPreviewSubflowInner({ data }: NodeProps<WorkflowPreviewSubflowD
</div>
{/* Start handle inside - connects to first block in subflow */}
<div className='absolute top-[56px] left-[16px] flex items-center justify-center rounded-[8px] bg-[var(--surface-2)] px-[12px] py-[6px]'>
<div className='absolute top-[56px] left-[16px] flex items-center justify-center rounded-[8px] border border-[var(--border-1)] bg-[var(--surface-2)] px-[12px] py-[6px]'>
<span className='font-medium text-[14px] text-white'>Start</span>
<Handle
type='source'
position={Position.Right}
id={startHandleId}
className={handleClass}
style={{ right: '-7px', top: '50%', transform: 'translateY(-50%)' }}
className={rightHandleClass}
style={{ right: '-8px', top: '50%', transform: 'translateY(-50%)' }}
/>
</div>
@@ -85,9 +87,9 @@ function WorkflowPreviewSubflowInner({ data }: NodeProps<WorkflowPreviewSubflowD
type='source'
position={Position.Right}
id={endHandleId}
className={handleClass}
className={rightHandleClass}
style={{
right: '-7px',
right: '-8px',
top: `${HANDLE_POSITIONS.DEFAULT_Y_OFFSET}px`,
transform: 'translateY(-50%)',
}}

View File

@@ -0,0 +1,223 @@
import { GreptileIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { GreptileResponse } from '@/tools/greptile/types'
export const GreptileBlock: BlockConfig<GreptileResponse> = {
type: 'greptile',
name: 'Greptile',
description: 'AI-powered codebase search and Q&A',
authMode: AuthMode.ApiKey,
longDescription:
'Query and search codebases using natural language with Greptile. Get AI-generated answers about your code, find relevant files, and understand complex codebases.',
docsLink: 'https://docs.sim.ai/tools/greptile',
category: 'tools',
bgColor: '#e5e5e5',
icon: GreptileIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Query', id: 'greptile_query' },
// { label: 'Search', id: 'greptile_search' }, // Disabled: Greptile search endpoint returning v1 deprecation error
{ label: 'Index Repository', id: 'greptile_index_repo' },
{ label: 'Check Status', id: 'greptile_status' },
],
value: () => 'greptile_query',
},
// Query operation inputs
{
id: 'query',
title: 'Query',
type: 'long-input',
placeholder: 'Ask a question about the codebase...',
condition: { field: 'operation', value: 'greptile_query' },
required: true,
},
{
id: 'repositories',
title: 'Repositories',
type: 'long-input',
placeholder: 'owner/repo, github:main:owner/repo (comma-separated)',
condition: { field: 'operation', value: 'greptile_query' },
required: true,
},
{
id: 'sessionId',
title: 'Session ID',
type: 'short-input',
placeholder: 'Optional session ID for conversation continuity',
condition: { field: 'operation', value: 'greptile_query' },
},
{
id: 'genius',
title: 'Genius Mode',
type: 'switch',
condition: { field: 'operation', value: 'greptile_query' },
},
// Search operation inputs - Disabled: Greptile search endpoint returning v1 deprecation error
// {
// id: 'query',
// title: 'Search Query',
// type: 'long-input',
// placeholder: 'Search for code patterns, functions, or concepts...',
// condition: { field: 'operation', value: 'greptile_search' },
// required: true,
// },
// {
// id: 'repositories',
// title: 'Repositories',
// type: 'long-input',
// placeholder: 'owner/repo, github:main:owner/repo (comma-separated)',
// condition: { field: 'operation', value: 'greptile_search' },
// required: true,
// },
// {
// id: 'sessionId',
// title: 'Session ID',
// type: 'short-input',
// placeholder: 'Optional session ID for conversation continuity',
// condition: { field: 'operation', value: 'greptile_search' },
// },
// {
// id: 'genius',
// title: 'Genius Mode',
// type: 'switch',
// condition: { field: 'operation', value: 'greptile_search' },
// },
// Index operation inputs
{
id: 'remote',
title: 'Git Remote',
type: 'dropdown',
options: [
{ label: 'GitHub', id: 'github' },
{ label: 'GitLab', id: 'gitlab' },
],
value: () => 'github',
condition: { field: 'operation', value: 'greptile_index_repo' },
},
{
id: 'repository',
title: 'Repository',
type: 'short-input',
placeholder: 'owner/repo',
condition: { field: 'operation', value: 'greptile_index_repo' },
required: true,
},
{
id: 'branch',
title: 'Branch',
type: 'short-input',
placeholder: 'main',
condition: { field: 'operation', value: 'greptile_index_repo' },
required: true,
},
{
id: 'reload',
title: 'Force Re-index',
type: 'switch',
condition: { field: 'operation', value: 'greptile_index_repo' },
},
{
id: 'notify',
title: 'Email Notification',
type: 'switch',
condition: { field: 'operation', value: 'greptile_index_repo' },
},
// Status operation inputs
{
id: 'remote',
title: 'Git Remote',
type: 'dropdown',
options: [
{ label: 'GitHub', id: 'github' },
{ label: 'GitLab', id: 'gitlab' },
],
value: () => 'github',
condition: { field: 'operation', value: 'greptile_status' },
},
{
id: 'repository',
title: 'Repository',
type: 'short-input',
placeholder: 'owner/repo',
condition: { field: 'operation', value: 'greptile_status' },
required: true,
},
{
id: 'branch',
title: 'Branch',
type: 'short-input',
placeholder: 'main',
condition: { field: 'operation', value: 'greptile_status' },
required: true,
},
// API Keys (common)
{
id: 'apiKey',
title: 'Greptile API Key',
type: 'short-input',
placeholder: 'Enter your Greptile API key',
password: true,
required: true,
},
{
id: 'githubToken',
title: 'GitHub Token',
type: 'short-input',
placeholder: 'Enter your GitHub Personal Access Token',
password: true,
required: true,
},
],
tools: {
access: ['greptile_query', /* 'greptile_search', */ 'greptile_index_repo', 'greptile_status'],
config: {
tool: (params) => params.operation,
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
apiKey: { type: 'string', description: 'Greptile API key' },
githubToken: { type: 'string', description: 'GitHub Personal Access Token' },
// Query/Search inputs
query: { type: 'string', description: 'Natural language query or search term' },
repositories: { type: 'string', description: 'Comma-separated list of repositories' },
sessionId: { type: 'string', description: 'Session ID for conversation continuity' },
genius: { type: 'boolean', description: 'Enable genius mode for more thorough analysis' },
// Index/Status inputs
remote: { type: 'string', description: 'Git remote type (github/gitlab)' },
repository: { type: 'string', description: 'Repository in owner/repo format' },
branch: { type: 'string', description: 'Branch name' },
reload: { type: 'boolean', description: 'Force re-indexing' },
notify: { type: 'boolean', description: 'Send email notification' },
},
outputs: {
// Query output
message: { type: 'string', description: 'AI-generated answer to the query' },
// Query/Search output
sources: {
type: 'json',
description: 'Relevant code references with filepath, line numbers, and summary',
},
// Index output
repositoryId: {
type: 'string',
description: 'Repository identifier (format: remote:branch:owner/repo)',
},
statusEndpoint: { type: 'string', description: 'URL endpoint to check indexing status' },
// Status output
status: {
type: 'string',
description: 'Indexing status: submitted, cloning, processing, completed, or failed',
},
private: { type: 'boolean', description: 'Whether the repository is private' },
filesProcessed: { type: 'number', description: 'Number of files processed' },
numFiles: { type: 'number', description: 'Total number of files' },
sampleQuestions: { type: 'json', description: 'Sample questions for the indexed repository' },
sha: { type: 'string', description: 'Git commit SHA' },
},
}

View File

@@ -43,6 +43,7 @@ import { GoogleSlidesBlock } from '@/blocks/blocks/google_slides'
import { GoogleVaultBlock } from '@/blocks/blocks/google_vault'
import { GrafanaBlock } from '@/blocks/blocks/grafana'
import { GrainBlock } from '@/blocks/blocks/grain'
import { GreptileBlock } from '@/blocks/blocks/greptile'
import { GuardrailsBlock } from '@/blocks/blocks/guardrails'
import { HubSpotBlock } from '@/blocks/blocks/hubspot'
import { HuggingFaceBlock } from '@/blocks/blocks/huggingface'
@@ -178,6 +179,7 @@ export const registry: Record<string, BlockConfig> = {
gmail: GmailBlock,
grain: GrainBlock,
grafana: GrafanaBlock,
greptile: GreptileBlock,
guardrails: GuardrailsBlock,
google_calendar: GoogleCalendarBlock,
google_docs: GoogleDocsBlock,

View File

@@ -4344,3 +4344,16 @@ export function CirclebackIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function GreptileIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<path
clipRule='evenodd'
fillRule='evenodd'
fill='#44A775'
d='M3.353.004a6.074 6.074 0 01-.265.045C2.63.12 2.092.348 1.71.633 1.426.846.717 1.575.557 1.819a3.359 3.359 0 00-.23 3.296c.154.322.35.59.71.972.187.198.434.486.55.64a6.629 6.629 0 011.305 3.546c.01.138.035 1.607.057 3.264.043 3.273.038 3.18.203 3.485.266.494.94.79 1.474.648.29-.077.463-.204 1.353-.986.957-.84 1.092-.932 1.446-.98.124-.017.631 0 1.66.053 1.513.08 1.622.079 1.85-.016.393-.164.539-.4.661-1.074.247-1.36 1.296-2.56 2.64-3.022.116-.04.373-.104.572-.144.198-.04.426-.102.506-.138.296-.136.515-.424.566-.744.017-.11-.007-.549-.089-1.602-.091-1.179-.107-1.483-.083-1.621.057-.342.139-.46 1.01-1.448.447-.506.85-.976.895-1.043.262-.39.288-.91.068-1.345a1.44 1.44 0 00-.822-.67c-.1-.029-.834-.037-3.544-.038H9.897l-.335-.063c-.958-.179-1.765-.49-2.484-.958-.362-.236-.583-.41-1.018-.804-.408-.37-.59-.502-.921-.67A3.018 3.018 0 003.744.005a3.942 3.942 0 00-.391 0zm15.728 5.858c-.132.049-.217.127-.48.44-.592.707-.74 1.336-.531 2.256.106.466.163.572.361.673.105.054.169.055 2.637.046l2.53-.009.118-.063a.551.551 0 00.095-.895 184.88 184.88 0 00-2.223-1.254c-2.293-1.282-2.281-1.276-2.507-1.194zm-3.216 6.71a9.258 9.258 0 00-1.364.696c-.844.557-1.454 1.36-1.923 2.53-.211.525-.202.75.04.935.111.087 6.478 3.14 6.667 3.198.153.047.27.027.43-.074a.538.538 0 00.24-.434c0-.06-.03-.18-.065-.264-.156-.368-3.098-6.467-3.158-6.545-.168-.222-.394-.232-.867-.042zm-8.48 5.457c-.453.102-.83.32-1.285.745-.296.277-.336.468-.167.798.053.103.61 1.104 1.236 2.224 1.297 2.317 1.22 2.206 1.558 2.202.152-.002.198-.015.296-.084a.662.662 0 00.173-.193c.058-.11.06-.152.08-2.595.018-1.93.015-2.51-.011-2.606a.569.569 0 00-.138-.227c-.09-.091-.14-.112-.406-.176-.582-.138-.992-.165-1.336-.088z'
/>
</svg>
)
}

View File

@@ -98,13 +98,13 @@ export function useUpdateUsageLimit() {
return response.json()
},
onMutate: async ({ limit }) => {
await queryClient.cancelQueries({ queryKey: subscriptionKeys.user() })
await queryClient.cancelQueries({ queryKey: subscriptionKeys.usage() })
await queryClient.cancelQueries({ queryKey: subscriptionKeys.all })
const previousSubscriptionData = queryClient.getQueryData(subscriptionKeys.user())
const previousSubscriptionData = queryClient.getQueryData(subscriptionKeys.user(false))
const previousSubscriptionDataWithOrg = queryClient.getQueryData(subscriptionKeys.user(true))
const previousUsageData = queryClient.getQueryData(subscriptionKeys.usage())
queryClient.setQueryData(subscriptionKeys.user(), (old: any) => {
const updateSubscriptionData = (old: any) => {
if (!old) return old
const currentUsage = old.data?.usage?.current || 0
const newPercentUsed = limit > 0 ? (currentUsage / limit) * 100 : 0
@@ -120,7 +120,10 @@ export function useUpdateUsageLimit() {
},
},
}
})
}
queryClient.setQueryData(subscriptionKeys.user(false), updateSubscriptionData)
queryClient.setQueryData(subscriptionKeys.user(true), updateSubscriptionData)
queryClient.setQueryData(subscriptionKeys.usage(), (old: any) => {
if (!old) return old
@@ -133,19 +136,24 @@ export function useUpdateUsageLimit() {
}
})
return { previousSubscriptionData, previousUsageData }
return { previousSubscriptionData, previousSubscriptionDataWithOrg, previousUsageData }
},
onError: (_err, _variables, context) => {
if (context?.previousSubscriptionData) {
queryClient.setQueryData(subscriptionKeys.user(), context.previousSubscriptionData)
queryClient.setQueryData(subscriptionKeys.user(false), context.previousSubscriptionData)
}
if (context?.previousSubscriptionDataWithOrg) {
queryClient.setQueryData(
subscriptionKeys.user(true),
context.previousSubscriptionDataWithOrg
)
}
if (context?.previousUsageData) {
queryClient.setQueryData(subscriptionKeys.usage(), context.previousUsageData)
}
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: subscriptionKeys.user() })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.usage() })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
},
})
}

View File

@@ -138,7 +138,10 @@ describe('OAuth Token Refresh', () => {
})
)
const [, requestOptions] = mockFetch.mock.calls[0]
const [, requestOptions] = mockFetch.mock.calls[0] as [
string,
{ headers: Record<string, string>; body: string },
]
const authHeader = requestOptions.headers.Authorization
expect(authHeader).toMatch(/^Basic /)
@@ -251,7 +254,10 @@ describe('OAuth Token Refresh', () => {
})
)
const [, requestOptions] = mockFetch.mock.calls[0]
const [, requestOptions] = mockFetch.mock.calls[0] as [
string,
{ headers: Record<string, string>; body: string },
]
expect(requestOptions.headers.Authorization).toBeUndefined()
@@ -279,7 +285,10 @@ describe('OAuth Token Refresh', () => {
await withMockFetch(mockFetch, () => refreshOAuthToken('github', refreshToken))
const [, requestOptions] = mockFetch.mock.calls[0]
const [, requestOptions] = mockFetch.mock.calls[0] as [
string,
{ headers: Record<string, string>; body: string },
]
expect(requestOptions.headers.Accept).toBe('application/json')
})
@@ -289,7 +298,10 @@ describe('OAuth Token Refresh', () => {
await withMockFetch(mockFetch, () => refreshOAuthToken('reddit', refreshToken))
const [, requestOptions] = mockFetch.mock.calls[0]
const [, requestOptions] = mockFetch.mock.calls[0] as [
string,
{ headers: Record<string, string>; body: string },
]
expect(requestOptions.headers['User-Agent']).toBe(
'sim-studio/1.0 (https://github.com/simstudioai/sim)'
)

View File

@@ -652,7 +652,7 @@ async function processEmails(
headers: {
'Content-Type': 'application/json',
'X-Webhook-Secret': webhookData.secret || '',
'User-Agent': 'SimStudio/1.0',
'User-Agent': 'Sim/1.0',
},
body: JSON.stringify(payload),
})

View File

@@ -465,7 +465,7 @@ async function processOutlookEmails(
headers: {
'Content-Type': 'application/json',
'X-Webhook-Secret': webhookData.secret || '',
'User-Agent': 'SimStudio/1.0',
'User-Agent': 'Sim/1.0',
},
body: JSON.stringify(payload),
})

View File

@@ -60,7 +60,7 @@ export interface RssWebhookPayload {
const parser = new Parser({
timeout: 30000,
headers: {
'User-Agent': 'SimStudio/1.0 RSS Poller',
'User-Agent': 'Sim/1.0 RSS Poller',
},
})
@@ -255,7 +255,7 @@ async function fetchNewRssItems(
const response = await fetch(pinnedUrl, {
headers: {
Host: urlValidation.originalHostname!,
'User-Agent': 'SimStudio/1.0 RSS Poller',
'User-Agent': 'Sim/1.0 RSS Poller',
Accept: 'application/rss+xml, application/xml, text/xml, */*',
},
signal: AbortSignal.timeout(30000),
@@ -362,7 +362,7 @@ async function processRssItems(
headers: {
'Content-Type': 'application/json',
'X-Webhook-Secret': webhookData.secret || '',
'User-Agent': 'SimStudio/1.0',
'User-Agent': 'Sim/1.0',
},
body: JSON.stringify(payload),
})

View File

@@ -2523,4 +2523,181 @@ describe('hasWorkflowChanged', () => {
}
)
})
describe('Variables (UI-only fields should not trigger change)', () => {
it.concurrent('should not detect change when validationError differs', () => {
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(deployedState as any).variables = {
var1: {
id: 'var1',
workflowId: 'workflow1',
name: 'myVar',
type: 'plain',
value: 'test',
},
}
const currentState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(currentState as any).variables = {
var1: {
id: 'var1',
workflowId: 'workflow1',
name: 'myVar',
type: 'plain',
value: 'test',
validationError: undefined,
},
}
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
})
it.concurrent('should not detect change when validationError has value vs missing', () => {
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(deployedState as any).variables = {
var1: {
id: 'var1',
workflowId: 'workflow1',
name: 'myVar',
type: 'number',
value: 'invalid',
},
}
const currentState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(currentState as any).variables = {
var1: {
id: 'var1',
workflowId: 'workflow1',
name: 'myVar',
type: 'number',
value: 'invalid',
validationError: 'Not a valid number',
},
}
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
})
it.concurrent('should detect change when variable value differs', () => {
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(deployedState as any).variables = {
var1: {
id: 'var1',
workflowId: 'workflow1',
name: 'myVar',
type: 'plain',
value: 'old value',
},
}
const currentState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(currentState as any).variables = {
var1: {
id: 'var1',
workflowId: 'workflow1',
name: 'myVar',
type: 'plain',
value: 'new value',
validationError: undefined,
},
}
expect(hasWorkflowChanged(currentState, deployedState)).toBe(true)
})
it.concurrent('should detect change when variable is added', () => {
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(deployedState as any).variables = {}
const currentState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(currentState as any).variables = {
var1: {
id: 'var1',
workflowId: 'workflow1',
name: 'myVar',
type: 'plain',
value: 'test',
},
}
expect(hasWorkflowChanged(currentState, deployedState)).toBe(true)
})
it.concurrent('should detect change when variable is removed', () => {
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(deployedState as any).variables = {
var1: {
id: 'var1',
workflowId: 'workflow1',
name: 'myVar',
type: 'plain',
value: 'test',
},
}
const currentState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(currentState as any).variables = {}
expect(hasWorkflowChanged(currentState, deployedState)).toBe(true)
})
it.concurrent('should not detect change when empty array vs empty object', () => {
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(deployedState as any).variables = []
const currentState = createWorkflowState({
blocks: {
block1: createBlock('block1'),
},
})
;(currentState as any).variables = {}
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
})
})
})

View File

@@ -6,8 +6,10 @@ import {
normalizeLoop,
normalizeParallel,
normalizeValue,
normalizeVariables,
sanitizeInputFormat,
sanitizeTools,
sanitizeVariable,
sortEdges,
} from './normalize'
@@ -228,11 +230,17 @@ export function hasWorkflowChanged(
}
// 6. Compare variables
const currentVariables = (currentState as any).variables || {}
const deployedVariables = (deployedState as any).variables || {}
const currentVariables = normalizeVariables((currentState as any).variables)
const deployedVariables = normalizeVariables((deployedState as any).variables)
const normalizedCurrentVars = normalizeValue(currentVariables)
const normalizedDeployedVars = normalizeValue(deployedVariables)
const normalizedCurrentVars = normalizeValue(
Object.fromEntries(Object.entries(currentVariables).map(([id, v]) => [id, sanitizeVariable(v)]))
)
const normalizedDeployedVars = normalizeValue(
Object.fromEntries(
Object.entries(deployedVariables).map(([id, v]) => [id, sanitizeVariable(v)])
)
)
if (normalizedStringify(normalizedCurrentVars) !== normalizedStringify(normalizedDeployedVars)) {
return true

View File

@@ -88,6 +88,30 @@ export function sanitizeTools(tools: any[] | undefined): any[] {
return tools.map(({ isExpanded, ...rest }) => rest)
}
/**
* Sanitizes a variable by removing UI-only fields like validationError
* @param variable - The variable object
* @returns Sanitized variable object
*/
export function sanitizeVariable(variable: any): any {
if (!variable || typeof variable !== 'object') return variable
const { validationError, ...rest } = variable
return rest
}
/**
* Normalizes the variables structure to always be an object.
* Handles legacy data where variables might be stored as an empty array.
* @param variables - The variables to normalize
* @returns A normalized variables object
*/
export function normalizeVariables(variables: any): Record<string, any> {
if (!variables) return {}
if (Array.isArray(variables)) return {}
if (typeof variables !== 'object') return {}
return variables
}
/**
* Sanitizes inputFormat array by removing UI-only fields like value and collapsed
* @param inputFormat - Array of input format configurations

View File

@@ -46,10 +46,9 @@ function asAppBlocks<T>(blocks: T): Record<string, AppBlockState> {
* These tests intentionally use old SubBlockTypes (textarea, select, messages-input, input)
* to verify the migration logic converts them to new types.
*/
function legacySubBlocks<T>(
subBlocks: T
): Record<string, { id: string; type: string; value: any }> {
return subBlocks as Record<string, { id: string; type: string; value: any }>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function legacySubBlocks(subBlocks: Record<string, any>): any {
return subBlocks
}
const { mockDb, mockWorkflowBlocks, mockWorkflowEdges, mockWorkflowSubflows } = vi.hoisted(() => {
@@ -1022,7 +1021,7 @@ describe('Database Helpers', () => {
position: { x: 100, y: 100 },
height: 150,
advancedMode: false,
subBlocks: { model: { id: 'model', type: 'select', value: 'gpt-4o' } },
subBlocks: legacySubBlocks({ model: { id: 'model', type: 'select', value: 'gpt-4o' } }),
}),
mockWorkflowId
)
@@ -1034,11 +1033,11 @@ describe('Database Helpers', () => {
position: { x: 200, y: 100 },
height: 200,
advancedMode: true,
subBlocks: {
subBlocks: legacySubBlocks({
systemPrompt: { id: 'systemPrompt', type: 'textarea', value: 'System prompt' },
userPrompt: { id: 'userPrompt', type: 'textarea', value: 'User prompt' },
model: { id: 'model', type: 'select', value: 'gpt-4o' },
},
}),
}),
mockWorkflowId
)
@@ -1153,7 +1152,7 @@ describe('Database Helpers', () => {
'agent-1': createAgentBlock({
id: 'agent-1',
name: 'Test Agent',
subBlocks: {
subBlocks: legacySubBlocks({
systemPrompt: {
id: 'systemPrompt',
type: 'textarea',
@@ -1164,7 +1163,7 @@ describe('Database Helpers', () => {
type: 'textarea',
value: 'Hello world',
},
},
}),
}),
}
@@ -1183,13 +1182,13 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
systemPrompt: {
id: 'systemPrompt',
type: 'textarea',
value: 'You are helpful',
},
},
}),
}),
}
@@ -1204,13 +1203,13 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
userPrompt: {
id: 'userPrompt',
type: 'textarea',
value: 'Hello',
},
},
}),
}),
}
@@ -1225,13 +1224,13 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
userPrompt: {
id: 'userPrompt',
type: 'textarea',
value: { input: 'Hello from object' },
},
},
}),
}),
}
@@ -1246,13 +1245,13 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
userPrompt: {
id: 'userPrompt',
type: 'textarea',
value: { foo: 'bar', baz: 123 },
},
},
}),
}),
}
@@ -1268,7 +1267,7 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
systemPrompt: {
id: 'systemPrompt',
type: 'textarea',
@@ -1284,7 +1283,7 @@ describe('Database Helpers', () => {
type: 'messages-input',
value: existingMessages,
},
},
}),
}),
}
@@ -1297,13 +1296,13 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
model: {
id: 'model',
type: 'select',
value: 'gpt-4o',
},
},
}),
}),
}
@@ -1316,13 +1315,13 @@ describe('Database Helpers', () => {
const blocks = {
'api-1': createApiBlock({
id: 'api-1',
subBlocks: {
subBlocks: legacySubBlocks({
url: {
id: 'url',
type: 'input',
value: 'https://example.com',
},
},
}),
}),
}
@@ -1336,18 +1335,18 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
systemPrompt: { id: 'systemPrompt', type: 'textarea', value: 'System 1' },
},
}),
}),
'api-1': createApiBlock({
id: 'api-1',
}),
'agent-2': createAgentBlock({
id: 'agent-2',
subBlocks: {
subBlocks: legacySubBlocks({
userPrompt: { id: 'userPrompt', type: 'textarea', value: 'User 2' },
},
}),
}),
}
@@ -1368,10 +1367,10 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
systemPrompt: { id: 'systemPrompt', type: 'textarea', value: '' },
userPrompt: { id: 'userPrompt', type: 'textarea', value: '' },
},
}),
}),
}
@@ -1384,9 +1383,9 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
systemPrompt: { id: 'systemPrompt', type: 'textarea', value: 123 },
},
}),
}),
}
@@ -1401,9 +1400,9 @@ describe('Database Helpers', () => {
const blocks = {
'agent-1': createAgentBlock({
id: 'agent-1',
subBlocks: {
subBlocks: legacySubBlocks({
systemPrompt: { id: 'systemPrompt', type: 'textarea', value: 'System' },
},
}),
}),
}

View File

@@ -23,7 +23,6 @@
"generate-docs": "bun run ../../scripts/generate-docs.ts"
},
"dependencies": {
"@sim/logger": "workspace:*",
"@anthropic-ai/sdk": "^0.39.0",
"@aws-sdk/client-dynamodb": "3.940.0",
"@aws-sdk/client-rds-data": "3.940.0",
@@ -40,6 +39,8 @@
"@e2b/code-interpreter": "^2.0.0",
"@google/genai": "1.34.0",
"@hookform/resolvers": "^4.1.3",
"@linear/sdk": "40.0.0",
"@modelcontextprotocol/sdk": "1.24.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/exporter-jaeger": "2.1.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
@@ -70,10 +71,14 @@
"@radix-ui/react-visually-hidden": "1.2.4",
"@react-email/components": "^0.0.34",
"@react-email/render": "2.0.0",
"@sim/logger": "workspace:*",
"@t3-oss/env-nextjs": "0.13.4",
"@tanstack/react-query": "5.90.8",
"@tanstack/react-query-devtools": "5.90.2",
"@trigger.dev/sdk": "4.1.2",
"@types/react-window": "2.0.0",
"@types/three": "0.177.0",
"better-auth": "1.3.12",
"better-auth": "1.4.5",
"binary-extensions": "^2.0.0",
"browser-image-compression": "^2.0.2",
"chalk": "5.6.2",
@@ -82,37 +87,49 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"croner": "^9.0.0",
"cronstrue": "3.3.0",
"csv-parse": "6.1.0",
"date-fns": "4.1.0",
"decimal.js": "10.6.0",
"drizzle-orm": "^0.44.5",
"encoding": "0.1.13",
"entities": "6.0.1",
"ffmpeg-static": "5.3.0",
"fluent-ffmpeg": "2.1.3",
"framer-motion": "^12.5.0",
"fuse.js": "7.1.0",
"google-auth-library": "10.5.0",
"gray-matter": "^4.0.3",
"groq-sdk": "^0.15.0",
"html-to-image": "1.11.13",
"html-to-text": "^9.0.5",
"input-otp": "^1.4.2",
"ioredis": "^5.6.0",
"isolated-vm": "6.0.2",
"jose": "6.0.11",
"js-tiktoken": "1.0.21",
"js-yaml": "4.1.0",
"js-yaml": "4.1.1",
"jszip": "3.10.1",
"jwt-decode": "^4.0.0",
"lodash": "4.17.21",
"lucide-react": "^0.479.0",
"mammoth": "^1.9.0",
"mongodb": "6.19.0",
"mysql2": "3.14.3",
"nanoid": "^3.3.7",
"neo4j-driver": "6.0.1",
"next": "16.1.0-canary.21",
"next-mdx-remote": "^5.0.0",
"next-runtime-env": "3.3.0",
"next-themes": "^0.4.6",
"nodemailer": "7.0.11",
"officeparser": "^5.2.0",
"onedollarstats": "0.0.10",
"openai": "^4.91.1",
"papaparse": "5.5.3",
"posthog-js": "1.268.9",
"posthog-node": "5.9.2",
"postgres": "^3.4.5",
"prismjs": "^1.30.0",
"react": "19.2.1",
"react-colorful": "5.6.1",
@@ -126,14 +143,17 @@
"rehype-slug": "^6.0.0",
"remark-gfm": "4.0.1",
"resend": "^4.1.2",
"rss-parser": "3.13.0",
"sharp": "0.34.3",
"socket.io": "^4.8.1",
"socket.io-client": "4.8.1",
"ssh2": "^1.17.0",
"stripe": "18.5.0",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"thread-stream": "4.0.0",
"three": "0.177.0",
"twilio": "5.9.0",
"unpdf": "1.4.0",
"uuid": "^11.1.0",
"xlsx": "0.18.5",
@@ -142,13 +162,16 @@
},
"devDependencies": {
"@sim/testing": "workspace:*",
"@sim/tsconfig": "workspace:*",
"@testing-library/jest-dom": "^6.6.3",
"@trigger.dev/build": "4.1.2",
"@types/fluent-ffmpeg": "2.1.28",
"@types/html-to-text": "9.0.4",
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.7",
"@types/lodash": "^4.17.16",
"@types/node": "24.2.1",
"@types/nodemailer": "7.0.4",
"@types/papaparse": "5.3.16",
"@types/prismjs": "^1.26.5",
"@types/react": "^19",
@@ -171,6 +194,8 @@
"trustedDependencies": [
"canvas",
"better-sqlite3",
"ffmpeg-static",
"isolated-vm",
"sharp"
],
"overrides": {

View File

@@ -2661,7 +2661,7 @@ export const useCopilotStore = create<CopilotStore>()(
// Invalidate subscription queries to update usage
setTimeout(() => {
const queryClient = getQueryClient()
queryClient.invalidateQueries({ queryKey: subscriptionKeys.user() })
queryClient.invalidateQueries({ queryKey: subscriptionKeys.all })
}, 1000)
} finally {
clearTimeout(timeoutId)

View File

@@ -23,6 +23,7 @@ import {
} from '@sim/testing'
import { beforeEach, describe, expect, it } from 'vitest'
import { runWithUndoRedoRecordingSuspended, useUndoRedoStore } from '@/stores/undo-redo/store'
import type { DuplicateBlockOperation, UpdateParentOperation } from '@/stores/undo-redo/types'
describe('useUndoRedoStore', () => {
const workflowId = 'wf-test'
@@ -663,7 +664,8 @@ describe('useUndoRedoStore', () => {
)
const entry = undo(workflowId, userId)
expect(entry?.operation.data.duplicatedBlockSnapshot).toMatchObject({
const operation = entry?.operation as DuplicateBlockOperation
expect(operation.data.duplicatedBlockSnapshot).toMatchObject({
id: 'duplicated-block',
name: 'Duplicated Agent',
type: 'agent',
@@ -716,10 +718,11 @@ describe('useUndoRedoStore', () => {
)
const entry = undo(workflowId, userId)
expect(entry?.inverse.data.oldParentId).toBe('loop-2')
expect(entry?.inverse.data.newParentId).toBe('loop-1')
expect(entry?.inverse.data.oldPosition).toEqual({ x: 100, y: 100 })
expect(entry?.inverse.data.newPosition).toEqual({ x: 0, y: 0 })
const inverse = entry?.inverse as UpdateParentOperation
expect(inverse.data.oldParentId).toBe('loop-2')
expect(inverse.data.newParentId).toBe('loop-1')
expect(inverse.data.oldPosition).toEqual({ x: 100, y: 100 })
expect(inverse.data.newPosition).toEqual({ x: 0, y: 0 })
})
})

View File

@@ -0,0 +1,9 @@
import { indexRepoTool } from '@/tools/greptile/index_repo'
import { queryTool } from '@/tools/greptile/query'
import { searchTool } from '@/tools/greptile/search'
import { statusTool } from '@/tools/greptile/status'
export const greptileQueryTool = queryTool
export const greptileSearchTool = searchTool
export const greptileIndexRepoTool = indexRepoTool
export const greptileStatusTool = statusTool

View File

@@ -0,0 +1,123 @@
import type { GreptileIndexParams, GreptileIndexResponse } from '@/tools/greptile/types'
import type { ToolConfig } from '@/tools/types'
export const indexRepoTool: ToolConfig<GreptileIndexParams, GreptileIndexResponse> = {
id: 'greptile_index_repo',
name: 'Greptile Index Repository',
description:
'Submit a repository to be indexed by Greptile. Indexing must complete before the repository can be queried. Small repos take 3-5 minutes, larger ones can take over an hour.',
version: '1.0.0',
params: {
remote: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Git remote type: github or gitlab',
},
repository: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Repository in owner/repo format (e.g., "facebook/react")',
},
branch: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Branch to index (e.g., "main" or "master")',
},
reload: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Force re-indexing even if already indexed',
},
notify: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Send email notification when indexing completes',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greptile API key',
},
githubToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with repo read access',
},
},
request: {
url: 'https://api.greptile.com/v2/repositories',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.apiKey}`,
'X-Github-Token': params.githubToken,
}),
body: (params) => {
const body: Record<string, unknown> = {
remote: params.remote,
repository: params.repository,
branch: params.branch,
}
if (params.reload != null) {
body.reload = params.reload
}
if (params.notify != null) {
body.notify = params.notify
}
return body
},
},
transformResponse: async (response: Response, params) => {
const data = await response.json()
let repositoryId = ''
if (data.statusEndpoint) {
const match = data.statusEndpoint.match(/\/repositories\/(.+)$/)
if (match) {
repositoryId = decodeURIComponent(match[1])
}
}
if (!repositoryId && params) {
repositoryId = `${params.remote}:${params.branch}:${params.repository}`
}
return {
success: true,
output: {
repositoryId,
statusEndpoint: data.statusEndpoint || '',
message: data.message || 'Repository submitted for indexing',
},
}
},
outputs: {
repositoryId: {
type: 'string',
description:
'Unique identifier for the indexed repository (format: remote:branch:owner/repo)',
},
statusEndpoint: {
type: 'string',
description: 'URL endpoint to check indexing status',
},
message: {
type: 'string',
description: 'Status message about the indexing operation',
},
},
}

View File

@@ -0,0 +1,128 @@
import type { GreptileQueryParams, GreptileQueryResponse } from '@/tools/greptile/types'
import { parseRepositories } from '@/tools/greptile/utils'
import type { ToolConfig } from '@/tools/types'
export const queryTool: ToolConfig<GreptileQueryParams, GreptileQueryResponse> = {
id: 'greptile_query',
name: 'Greptile Query',
description:
'Query repositories in natural language and get answers with relevant code references. Greptile uses AI to understand your codebase and answer questions.',
version: '1.0.0',
params: {
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Natural language question about the codebase',
},
repositories: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" (defaults to github:main)',
},
sessionId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Session ID for conversation continuity',
},
genius: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Enable genius mode for more thorough analysis (slower but more accurate)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greptile API key',
},
githubToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with repo read access',
},
},
request: {
url: 'https://api.greptile.com/v2/query',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.apiKey}`,
'X-Github-Token': params.githubToken,
}),
body: (params) => {
const body: Record<string, unknown> = {
messages: [
{
role: 'user',
content: params.query,
},
],
repositories: parseRepositories(params.repositories),
stream: false,
}
if (params.sessionId) {
body.sessionId = params.sessionId
}
if (params.genius != null) {
body.genius = params.genius
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
message: data.message || '',
sources: (data.sources || []).map((source: Record<string, unknown>) => ({
repository: source.repository || '',
remote: source.remote || '',
branch: source.branch || '',
filepath: source.filepath || '',
linestart: source.linestart,
lineend: source.lineend,
summary: source.summary,
distance: source.distance,
})),
},
}
},
outputs: {
message: {
type: 'string',
description: 'AI-generated answer to the query',
},
sources: {
type: 'array',
description: 'Relevant code references that support the answer',
items: {
type: 'object',
properties: {
repository: { type: 'string', description: 'Repository name (owner/repo)' },
remote: { type: 'string', description: 'Git remote (github/gitlab)' },
branch: { type: 'string', description: 'Branch name' },
filepath: { type: 'string', description: 'Path to the file' },
linestart: { type: 'number', description: 'Starting line number' },
lineend: { type: 'number', description: 'Ending line number' },
summary: { type: 'string', description: 'Summary of the code section' },
distance: { type: 'number', description: 'Similarity score (lower = more relevant)' },
},
},
},
},
}

View File

@@ -0,0 +1,117 @@
import type { GreptileSearchParams, GreptileSearchResponse } from '@/tools/greptile/types'
import { parseRepositories } from '@/tools/greptile/utils'
import type { ToolConfig } from '@/tools/types'
export const searchTool: ToolConfig<GreptileSearchParams, GreptileSearchResponse> = {
id: 'greptile_search',
name: 'Greptile Search',
description:
'Search repositories in natural language and get relevant code references without generating an answer. Useful for finding specific code locations.',
version: '1.0.0',
params: {
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Natural language search query to find relevant code',
},
repositories: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" (defaults to github:main)',
},
sessionId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Session ID for conversation continuity',
},
genius: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Enable genius mode for more thorough search (slower but more accurate)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greptile API key',
},
githubToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with repo read access',
},
},
request: {
url: 'https://api.greptile.com/v2/search',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.apiKey}`,
'X-Github-Token': params.githubToken,
}),
body: (params) => {
const body: Record<string, unknown> = {
query: params.query,
repositories: parseRepositories(params.repositories),
}
if (params.sessionId) {
body.sessionId = params.sessionId
}
if (params.genius != null) {
body.genius = params.genius
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
sources: (data.sources || data || []).map((source: Record<string, unknown>) => ({
repository: source.repository || '',
remote: source.remote || '',
branch: source.branch || '',
filepath: source.filepath || '',
linestart: source.linestart,
lineend: source.lineend,
summary: source.summary,
distance: source.distance,
})),
},
}
},
outputs: {
sources: {
type: 'array',
description: 'Relevant code references matching the search query',
items: {
type: 'object',
properties: {
repository: { type: 'string', description: 'Repository name (owner/repo)' },
remote: { type: 'string', description: 'Git remote (github/gitlab)' },
branch: { type: 'string', description: 'Branch name' },
filepath: { type: 'string', description: 'Path to the file' },
linestart: { type: 'number', description: 'Starting line number' },
lineend: { type: 'number', description: 'Ending line number' },
summary: { type: 'string', description: 'Summary of the code section' },
distance: { type: 'number', description: 'Similarity score (lower = more relevant)' },
},
},
},
},
}

View File

@@ -0,0 +1,115 @@
import type { GreptileStatusParams, GreptileStatusResponse } from '@/tools/greptile/types'
import type { ToolConfig } from '@/tools/types'
export const statusTool: ToolConfig<GreptileStatusParams, GreptileStatusResponse> = {
id: 'greptile_status',
name: 'Greptile Repository Status',
description:
'Check the indexing status of a repository. Use this to verify if a repository is ready to be queried or to monitor indexing progress.',
version: '1.0.0',
params: {
remote: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Git remote type: github or gitlab',
},
repository: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Repository in owner/repo format (e.g., "facebook/react")',
},
branch: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Branch name (e.g., "main" or "master")',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Greptile API key',
},
githubToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with repo read access',
},
},
request: {
url: (params) => {
// Repository ID format: remote:branch:owner/repo (URL encoded)
const repositoryId = `${params.remote}:${params.branch}:${params.repository}`
return `https://api.greptile.com/v2/repositories/${encodeURIComponent(repositoryId)}`
},
method: 'GET',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.apiKey}`,
'X-Github-Token': params.githubToken,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
repository: data.repository || '',
remote: data.remote || '',
branch: data.branch || '',
private: data.private || false,
status: data.status || 'unknown',
filesProcessed: data.filesProcessed,
numFiles: data.numFiles,
sampleQuestions: data.sampleQuestions || [],
sha: data.sha,
},
}
},
outputs: {
repository: {
type: 'string',
description: 'Repository name (owner/repo)',
},
remote: {
type: 'string',
description: 'Git remote (github/gitlab)',
},
branch: {
type: 'string',
description: 'Branch name',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
status: {
type: 'string',
description: 'Indexing status: submitted, cloning, processing, completed, or failed',
},
filesProcessed: {
type: 'number',
description: 'Number of files processed so far',
},
numFiles: {
type: 'number',
description: 'Total number of files in the repository',
},
sampleQuestions: {
type: 'array',
description: 'Sample questions for the indexed repository',
},
sha: {
type: 'string',
description: 'Git commit SHA of the indexed version',
},
},
}

View File

@@ -0,0 +1,129 @@
import type { ToolResponse } from '@/tools/types'
/**
* Common parameters for all Greptile tools
*/
export interface GreptileBaseParams {
apiKey: string
githubToken: string
}
/**
* Repository identifier format
*/
export interface GreptileRepository {
remote: 'github' | 'gitlab'
branch: string
repository: string
}
/**
* Query tool parameters
*/
export interface GreptileQueryParams extends GreptileBaseParams {
query: string
repositories: string
sessionId?: string
stream?: boolean
genius?: boolean
}
/**
* Source reference in query/search results
*/
export interface GreptileSource {
repository: string
remote: string
branch: string
filepath: string
linestart?: number
lineend?: number
summary?: string
distance?: number
}
/**
* Query response
*/
export interface GreptileQueryResponse extends ToolResponse {
output: {
message: string
sources: GreptileSource[]
}
}
/**
* Search tool parameters
*/
export interface GreptileSearchParams extends GreptileBaseParams {
query: string
repositories: string
sessionId?: string
genius?: boolean
}
/**
* Search response
*/
export interface GreptileSearchResponse extends ToolResponse {
output: {
sources: GreptileSource[]
}
}
/**
* Index repository tool parameters
*/
export interface GreptileIndexParams extends GreptileBaseParams {
remote: 'github' | 'gitlab'
repository: string
branch: string
reload?: boolean
notify?: boolean
}
/**
* Index repository response
*/
export interface GreptileIndexResponse extends ToolResponse {
output: {
repositoryId: string
statusEndpoint: string
message: string
}
}
/**
* Get repository status tool parameters
*/
export interface GreptileStatusParams extends GreptileBaseParams {
remote: 'github' | 'gitlab'
repository: string
branch: string
}
/**
* Repository status response
*/
export interface GreptileStatusResponse extends ToolResponse {
output: {
repository: string
remote: string
branch: string
private: boolean
status: 'submitted' | 'cloning' | 'processing' | 'completed' | 'failed'
filesProcessed?: number
numFiles?: number
sampleQuestions?: string[]
sha?: string
}
}
/**
* Union type for all Greptile responses
*/
export type GreptileResponse =
| GreptileQueryResponse
| GreptileSearchResponse
| GreptileIndexResponse
| GreptileStatusResponse

View File

@@ -0,0 +1,29 @@
/**
* Parse repository string into structured format for Greptile API
* Accepts formats like "github:main:owner/repo" or "owner/repo" (defaults to github:main)
*/
export function parseRepositories(repoString: string): Array<{
remote: string
branch: string
repository: string
}> {
return repoString
.split(',')
.map((r) => r.trim())
.filter((r) => r.length > 0)
.map((repo) => {
const parts = repo.split(':')
if (parts.length === 3) {
return {
remote: parts[0],
branch: parts[1],
repository: parts[2],
}
}
return {
remote: 'github',
branch: 'main',
repository: repo,
}
})
}

View File

@@ -369,6 +369,12 @@ import {
grainListRecordingsTool,
grainListTeamsTool,
} from '@/tools/grain'
import {
greptileIndexRepoTool,
greptileQueryTool,
greptileSearchTool,
greptileStatusTool,
} from '@/tools/greptile'
import { guardrailsValidateTool } from '@/tools/guardrails'
import { httpRequestTool } from '@/tools/http'
import {
@@ -1761,6 +1767,10 @@ export const tools: Record<string, ToolConfig> = {
grain_create_hook: grainCreateHookTool,
grain_list_hooks: grainListHooksTool,
grain_delete_hook: grainDeleteHookTool,
greptile_query: greptileQueryTool,
greptile_search: greptileSearchTool,
greptile_index_repo: greptileIndexRepoTool,
greptile_status: greptileStatusTool,
elasticsearch_search: elasticsearchSearchTool,
elasticsearch_index_document: elasticsearchIndexDocumentTool,
elasticsearch_get_document: elasticsearchGetDocumentTool,

View File

@@ -27,7 +27,7 @@ export const pageContentTool: ToolConfig<WikipediaPageContentParams, WikipediaPa
},
method: 'GET',
headers: () => ({
'User-Agent': 'SimStudio/1.0 (https://sim.ai)',
'User-Agent': 'Sim/1.0 (https://sim.ai)',
Accept:
'text/html; charset=utf-8; profile="https://www.mediawiki.org/wiki/Specs/HTML/2.1.0"',
}),

View File

@@ -15,7 +15,7 @@ export const randomPageTool: ToolConfig<Record<string, never>, WikipediaRandomPa
},
method: 'GET',
headers: () => ({
'User-Agent': 'SimStudio/1.0 (https://sim.ai)',
'User-Agent': 'Sim/1.0 (https://sim.ai)',
Accept: 'application/json',
}),
},

View File

@@ -41,7 +41,7 @@ export const searchTool: ToolConfig<WikipediaSearchParams, WikipediaSearchRespon
},
method: 'GET',
headers: () => ({
'User-Agent': 'SimStudio/1.0 (https://sim.ai)',
'User-Agent': 'Sim/1.0 (https://sim.ai)',
Accept: 'application/json',
}),
},

View File

@@ -27,7 +27,7 @@ export const pageSummaryTool: ToolConfig<WikipediaPageSummaryParams, WikipediaPa
},
method: 'GET',
headers: () => ({
'User-Agent': 'SimStudio/1.0 (https://sim.ai)',
'User-Agent': 'Sim/1.0 (https://sim.ai)',
Accept: 'application/json',
}),
},

View File

@@ -1,13 +1,6 @@
{
"extends": "@sim/tsconfig/nextjs.json",
"compilerOptions": {
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["es2022", "dom", "dom.iterable"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
@@ -28,19 +21,7 @@
"@sim/db/*": ["../../packages/db/*"],
"@/executor": ["./executor"],
"@/executor/*": ["./executor/*"]
},
"allowJs": true,
"noEmit": true,
"incremental": true,
"resolveJsonModule": true,
"isolatedModules": true,
"allowImportingTsExtensions": true,
"jsx": "react-jsx",
"plugins": [
{
"name": "next"
}
]
}
},
"include": [
"**/*.ts",

653
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +1,36 @@
# ========================================
# Base Stage: Debian-based Bun
# Base Stage: Debian-based Bun with Node.js 22
# ========================================
FROM oven/bun:1.3.3-slim AS base
# Install Node.js 22 and common dependencies once in base stage
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv make g++ curl ca-certificates bash ffmpeg \
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y nodejs
# ========================================
# Dependencies Stage: Install Dependencies
# ========================================
FROM base AS deps
WORKDIR /app
# Install Node.js 22 for isolated-vm compilation (requires node-gyp and V8)
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 make g++ curl ca-certificates \
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
COPY package.json bun.lock turbo.json ./
RUN mkdir -p apps packages/db packages/testing packages/logger
RUN mkdir -p apps packages/db packages/testing packages/logger packages/tsconfig
COPY apps/sim/package.json ./apps/sim/package.json
COPY packages/db/package.json ./packages/db/package.json
COPY packages/testing/package.json ./packages/testing/package.json
COPY packages/logger/package.json ./packages/logger/package.json
COPY packages/tsconfig/package.json ./packages/tsconfig/package.json
# Install turbo globally, then dependencies, then rebuild isolated-vm for Node.js
RUN --mount=type=cache,id=bun-cache,target=/root/.bun/install/cache \
--mount=type=cache,id=npm-cache,target=/root/.npm \
bun install -g turbo && \
HUSKY=0 bun install --omit=dev --ignore-scripts && \
cd $(readlink -f node_modules/isolated-vm) && npx node-gyp rebuild --release && cd /app
cd node_modules/.bun/isolated-vm@*/node_modules/isolated-vm && npx node-gyp rebuild --release && cd /app
# ========================================
# Builder Stage: Build the Application
@@ -89,13 +92,7 @@ RUN bun run build
FROM base AS runner
WORKDIR /app
# Install Node.js 22 (for isolated-vm worker), Python, and other runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv bash ffmpeg curl ca-certificates \
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
# Node.js 22, Python, ffmpeg, etc. are already installed in base stage
ENV NODE_ENV=production
# Create non-root user and group
@@ -108,20 +105,20 @@ COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/.next/static ./apps/sim/.next/static
# Copy isolated-vm native module (compiled for Node.js in deps stage)
COPY --from=deps --chown=nextjs:nodejs /app/node_modules/isolated-vm ./node_modules/isolated-vm
COPY --from=deps --chown=nextjs:nodejs /app/node_modules/.bun/isolated-vm@6.0.2/node_modules/isolated-vm ./node_modules/isolated-vm
# Copy the isolated-vm worker script
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/lib/execution/isolated-vm-worker.cjs ./apps/sim/lib/execution/isolated-vm-worker.cjs
# Guardrails setup (files need to be owned by nextjs for runtime)
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/lib/guardrails/setup.sh ./apps/sim/lib/guardrails/setup.sh
# Guardrails setup with pip caching
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/lib/guardrails/requirements.txt ./apps/sim/lib/guardrails/requirements.txt
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim/lib/guardrails/validate_pii.py ./apps/sim/lib/guardrails/validate_pii.py
# Run guardrails setup as root, then fix ownership of generated venv files
RUN chmod +x ./apps/sim/lib/guardrails/setup.sh && \
cd ./apps/sim/lib/guardrails && \
./setup.sh && \
# Install Python dependencies with pip cache mount for faster rebuilds
RUN --mount=type=cache,target=/root/.cache/pip \
python3 -m venv ./apps/sim/lib/guardrails/venv && \
./apps/sim/lib/guardrails/venv/bin/pip install --upgrade pip && \
./apps/sim/lib/guardrails/venv/bin/pip install -r ./apps/sim/lib/guardrails/requirements.txt && \
chown -R nextjs:nodejs /app/apps/sim/lib/guardrails
# Create .next/cache directory with correct ownership

View File

@@ -11,8 +11,9 @@ WORKDIR /app
# Copy only package files needed for migrations (these change less frequently)
COPY package.json bun.lock turbo.json ./
RUN mkdir -p packages/db
RUN mkdir -p packages/db packages/tsconfig
COPY packages/db/package.json ./packages/db/package.json
COPY packages/tsconfig/package.json ./packages/tsconfig/package.json
# Install dependencies with cache mount for faster builds
RUN --mount=type=cache,id=bun-cache,target=/root/.bun/install/cache \
@@ -34,6 +35,9 @@ COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
# Copy package configuration files (needed for migrations)
COPY --chown=nextjs:nodejs packages/db/drizzle.config.ts ./packages/db/drizzle.config.ts
# Copy tsconfig package (needed for workspace symlink resolution)
COPY --chown=nextjs:nodejs packages/tsconfig ./packages/tsconfig
# Copy database package source code (changes most frequently - placed last)
COPY --chown=nextjs:nodejs packages/db ./packages/db

View File

@@ -11,11 +11,12 @@ RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json bun.lock turbo.json ./
RUN mkdir -p apps packages/db packages/testing packages/logger
RUN mkdir -p apps packages/db packages/testing packages/logger packages/tsconfig
COPY apps/sim/package.json ./apps/sim/package.json
COPY packages/db/package.json ./packages/db/package.json
COPY packages/testing/package.json ./packages/testing/package.json
COPY packages/logger/package.json ./packages/logger/package.json
COPY packages/tsconfig/package.json ./packages/tsconfig/package.json
# Install dependencies with cache mount for faster builds
RUN --mount=type=cache,id=bun-cache,target=/root/.bun/install/cache \
@@ -62,6 +63,9 @@ COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
# Copy db package (needed by socket)
COPY --from=builder --chown=nextjs:nodejs /app/packages/db ./packages/db
# Copy logger package (workspace dependency used by socket)
COPY --from=builder --chown=nextjs:nodejs /app/packages/logger ./packages/logger
# Copy sim app (changes most frequently - placed last)
COPY --from=builder --chown=nextjs:nodejs /app/apps/sim ./apps/sim

View File

@@ -33,37 +33,11 @@
"drizzle-orm": "^0.44.5",
"postgres": "^3.4.5"
},
"dependencies": {
"@linear/sdk": "40.0.0",
"@modelcontextprotocol/sdk": "1.20.2",
"@t3-oss/env-nextjs": "0.13.4",
"@tanstack/react-query": "5.90.8",
"@tanstack/react-query-devtools": "5.90.2",
"@types/fluent-ffmpeg": "2.1.28",
"cronstrue": "3.3.0",
"decimal.js": "10.6.0",
"drizzle-orm": "^0.44.5",
"ffmpeg-static": "5.3.0",
"fluent-ffmpeg": "2.1.3",
"isolated-vm": "6.0.2",
"mongodb": "6.19.0",
"neo4j-driver": "6.0.1",
"next-runtime-env": "3.3.0",
"nodemailer": "7.0.11",
"onedollarstats": "0.0.10",
"postgres": "^3.4.5",
"remark-gfm": "4.0.1",
"rss-parser": "3.13.0",
"socket.io-client": "4.8.1",
"twilio": "5.9.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@biomejs/biome": "2.0.0-beta.5",
"@next/env": "16.1.0-canary.21",
"@octokit/rest": "^21.0.0",
"@tailwindcss/typography": "0.5.19",
"@types/nodemailer": "7.0.4",
"drizzle-kit": "^0.31.4",
"husky": "9.1.7",
"lint-staged": "16.0.0",
@@ -75,8 +49,6 @@
]
},
"trustedDependencies": [
"ffmpeg-static",
"isolated-vm",
"sharp"
]
}

View File

@@ -1,249 +1,18 @@
# Sim SDKs
# Packages
This directory contains the official SDKs for [Sim](https://sim.ai), allowing developers to execute workflows programmatically from their applications.
## Internal
## Available SDKs
| Package | Description |
|---------|-------------|
| [@sim/tsconfig](./tsconfig) | Shared TypeScript configs (base, nextjs, library, library-build) |
| [@sim/db](./db) | Database schema and Drizzle ORM utilities |
| [@sim/logger](./logger) | Structured logging with colored output |
| [@sim/testing](./testing) | Test factories, builders, and assertions |
### Package Installation Commands
## Published
- **TypeScript/JavaScript**: `npm install simstudio-ts-sdk`
- **Python**: `pip install simstudio-sdk`
### 🟢 TypeScript/JavaScript SDK (`simstudio-ts-sdk`)
**Directory:** `ts-sdk/`
The TypeScript SDK provides type-safe workflow execution for Node.js and browser environments.
**Installation:**
```bash
npm install simstudio-ts-sdk
# or
yarn add simstudio-ts-sdk
# or
bun add simstudio-ts-sdk
```
**Quick Start:**
```typescript
import { SimStudioClient } from 'simstudio-ts-sdk';
const client = new SimStudioClient({
apiKey: 'your-api-key-here'
});
const result = await client.executeWorkflow('workflow-id', {
input: { message: 'Hello, world!' }
});
```
### 🐍 Python SDK (`simstudio-sdk`)
**Directory:** `python-sdk/`
The Python SDK provides Pythonic workflow execution with comprehensive error handling and data classes.
**Installation:**
```bash
pip install simstudio-sdk
```
**Quick Start:**
```python
from simstudio import SimStudioClient
client = SimStudioClient(api_key='your-api-key-here')
result = client.execute_workflow('workflow-id',
input_data={'message': 'Hello, world!'})
```
## Core Features
Both SDKs provide the same core functionality:
**Workflow Execution** - Execute deployed workflows with optional input data
**Status Checking** - Check deployment status and workflow readiness
**Error Handling** - Comprehensive error handling with specific error codes
**Timeout Support** - Configurable timeouts for workflow execution
**Input Validation** - Validate workflows before execution
**Type Safety** - Full type definitions (TypeScript) and data classes (Python)
## API Compatibility
Both SDKs are built on top of the same REST API endpoints:
- `POST /api/workflows/{id}/execute` - Execute workflow (with or without input)
- `GET /api/workflows/{id}/status` - Get workflow status
## Authentication
Both SDKs use API key authentication via the `X-API-Key` header. You can obtain an API key by:
1. Logging in to your [Sim](https://sim.ai) account
2. Navigating to your workflow
3. Clicking "Deploy" to deploy your workflow
4. Creating or selecting an API key during deployment
## Environment Variables
Both SDKs support environment variable configuration:
```bash
# Required
SIM_API_KEY=your-api-key-here
# Optional
SIM_BASE_URL=https://sim.ai # or your custom domain
```
## Error Handling
Both SDKs provide consistent error handling with these error codes:
| Code | Description |
|------|-------------|
| `UNAUTHORIZED` | Invalid API key |
| `TIMEOUT` | Request timed out |
| `USAGE_LIMIT_EXCEEDED` | Account usage limit exceeded |
| `INVALID_JSON` | Invalid JSON in request body |
| `EXECUTION_ERROR` | General execution error |
| `STATUS_ERROR` | Error getting workflow status |
## Examples
### TypeScript Example
```typescript
import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk';
const client = new SimStudioClient({
apiKey: process.env.SIM_API_KEY!
});
try {
// Check if workflow is ready
const isReady = await client.validateWorkflow('workflow-id');
if (!isReady) {
throw new Error('Workflow not deployed');
}
// Execute workflow
const result = await client.executeWorkflow('workflow-id', {
input: { data: 'example' },
timeout: 30000
});
if (result.success) {
console.log('Output:', result.output);
}
} catch (error) {
if (error instanceof SimStudioError) {
console.error(`Error ${error.code}: ${error.message}`);
}
}
```
### Python Example
```python
from simstudio import SimStudioClient, SimStudioError
import os
client = SimStudioClient(api_key=os.getenv('SIM_API_KEY'))
try:
# Check if workflow is ready
is_ready = client.validate_workflow('workflow-id')
if not is_ready:
raise Exception('Workflow not deployed')
# Execute workflow
result = client.execute_workflow('workflow-id',
input_data={'data': 'example'},
timeout=30.0)
if result.success:
print(f'Output: {result.output}')
except SimStudioError as error:
print(f'Error {error.code}: {error}')
```
## Development
### Building the SDKs
**TypeScript SDK:**
```bash
cd packages/ts-sdk
bun install
bun run build
```
**Python SDK:**
```bash
cd packages/python-sdk
pip install -e ".[dev]"
python -m build
```
### Running Examples
**TypeScript:**
```bash
cd packages/ts-sdk
SIM_API_KEY=your-key bun run examples/basic-usage.ts
```
**Python:**
```bash
cd packages/python-sdk
SIM_API_KEY=your-key python examples/basic_usage.py
```
### Testing
**TypeScript:**
```bash
cd packages/ts-sdk
bun run test
```
**Python:**
```bash
cd packages/python-sdk
pytest
```
## Publishing
The SDKs are automatically published to npm and PyPI when changes are pushed to the main branch. See [Publishing Setup](../.github/PUBLISHING.md) for details on:
- Setting up GitHub secrets for automated publishing
- Manual publishing instructions
- Version management and semantic versioning
- Troubleshooting common issues
## Contributing
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/amazing-feature`
3. Make your changes
4. Add tests for your changes
5. Run the test suite: `bun run test` (TypeScript) or `pytest` (Python)
6. Update version numbers if needed
7. Commit your changes: `git commit -m 'Add amazing feature'`
8. Push to the branch: `git push origin feature/amazing-feature`
9. Open a Pull Request
## License
Both SDKs are licensed under the Apache-2.0 License. See the [LICENSE](../LICENSE) file for details.
## Support
- 📖 [Documentation](https://docs.sim.ai)
- 💬 [Discord Community](https://discord.gg/simstudio)
- 🐛 [Issue Tracker](https://github.com/simstudioai/sim/issues)
- 📧 [Email Support](mailto:support@sim.ai)
| Package | npm | Description |
|---------|-----|-------------|
| [cli](./cli) | `simstudio` | Run Sim locally via Docker |
| [ts-sdk](./ts-sdk) | `simstudio-ts-sdk` | TypeScript SDK for workflow execution |
| [python-sdk](./python-sdk) | `simstudio-sdk` | Python SDK for workflow execution |

View File

@@ -2,14 +2,19 @@
"name": "simstudio",
"version": "0.1.19",
"description": "Sim CLI - Run Sim with a single command",
"main": "dist/index.js",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"bin": {
"simstudio": "dist/index.js"
},
"scripts": {
"build": "bun run build:tsc",
"build:tsc": "tsc",
"build": "tsc",
"type-check": "tsc --noEmit",
"prepublishOnly": "bun run build"
},
"files": [
@@ -19,17 +24,16 @@
"simstudio",
"ai",
"workflow",
"ui",
"cli",
"sim",
"sim-studio",
"agent",
"agents",
"automation",
"docker"
],
"author": "Sim",
"license": "Apache-2.0",
"engines": {
"node": ">=16"
},
"dependencies": {
"chalk": "^4.1.2",
"commander": "^11.1.0",
@@ -38,20 +42,9 @@
"listr2": "^6.6.1"
},
"devDependencies": {
"@sim/tsconfig": "workspace:*",
"@types/inquirer": "^8.2.6",
"@types/node": "^20.5.1",
"typescript": "^5.1.6"
},
"engines": {
"node": ">=16"
},
"turbo": {
"tasks": {
"build": {
"outputs": [
"dist/**"
]
}
}
"typescript": "^5.7.3"
}
}

View File

@@ -1,15 +1,10 @@
{
"extends": "@sim/tsconfig/library-build.json",
"compilerOptions": {
"target": "es2020",
"module": "ESNext",
"target": "ES2020",
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"forceConsistentCasingInFileNames": true
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]

View File

@@ -24,15 +24,13 @@
"db:studio": "bunx drizzle-kit studio --config=./drizzle.config.ts",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"dependencies": {
"drizzle-orm": "^0.44.5",
"postgres": "^3.4.5"
"postgres": "^3.4.5",
"zod": "^3.24.2"
},
"devDependencies": {
"@sim/tsconfig": "workspace:*",
"typescript": "^5.7.3"
},
"overrides": {
"drizzle-orm": "^0.44.5",
"postgres": "^3.4.5"
}
}

View File

@@ -1,21 +1,12 @@
{
"extends": "@sim/tsconfig/library.json",
"compilerOptions": {
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["es2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@sim/db": ["./index.ts"],
"@sim/db/*": ["./*"]
},
"resolveJsonModule": true,
"noEmit": true
}
},
"include": ["**/*.ts"],
"exclude": ["node_modules"]
"exclude": ["node_modules", "scripts"]
}

View File

@@ -23,6 +23,7 @@
"chalk": "5.6.2"
},
"devDependencies": {
"@sim/tsconfig": "workspace:*",
"typescript": "^5.7.3",
"vitest": "^3.0.8"
}

View File

@@ -1,19 +1,5 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"noEmit": true,
"isolatedModules": true,
"resolveJsonModule": true
},
"extends": "@sim/tsconfig/library.json",
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -41,7 +41,11 @@
"vitest": "^3.0.0"
},
"devDependencies": {
"@sim/tsconfig": "workspace:*",
"typescript": "^5.7.3",
"vitest": "^3.0.8"
},
"dependencies": {
"nanoid": "5.1.6"
}
}

View File

@@ -1,5 +1,6 @@
import { expect } from 'vitest'
import type { BlockState, Edge, WorkflowState } from '../types'
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Asserts that a block exists in the workflow.
@@ -12,7 +13,7 @@ import type { BlockState, Edge, WorkflowState } from '../types'
* ```
*/
export function expectBlockExists(
blocks: Record<string, BlockState>,
blocks: Record<string, any>,
blockId: string,
expectedType?: string
): void {
@@ -33,7 +34,7 @@ export function expectBlockExists(
* expectBlockNotExists(workflow.blocks, 'deleted-block')
* ```
*/
export function expectBlockNotExists(blocks: Record<string, BlockState>, blockId: string): void {
export function expectBlockNotExists(blocks: Record<string, any>, blockId: string): void {
expect(blocks[blockId], `Block "${blockId}" should not exist`).toBeUndefined()
}
@@ -45,7 +46,7 @@ export function expectBlockNotExists(blocks: Record<string, BlockState>, blockId
* expectEdgeConnects(workflow.edges, 'block-0', 'block-1')
* ```
*/
export function expectEdgeConnects(edges: Edge[], sourceId: string, targetId: string): void {
export function expectEdgeConnects(edges: any[], sourceId: string, targetId: string): void {
const edge = edges.find((e) => e.source === sourceId && e.target === targetId)
expect(edge, `Edge from "${sourceId}" to "${targetId}" should exist`).toBeDefined()
}
@@ -58,7 +59,7 @@ export function expectEdgeConnects(edges: Edge[], sourceId: string, targetId: st
* expectNoEdgeBetween(workflow.edges, 'block-1', 'block-0') // No reverse edge
* ```
*/
export function expectNoEdgeBetween(edges: Edge[], sourceId: string, targetId: string): void {
export function expectNoEdgeBetween(edges: any[], sourceId: string, targetId: string): void {
const edge = edges.find((e) => e.source === sourceId && e.target === targetId)
expect(edge, `Edge from "${sourceId}" to "${targetId}" should not exist`).toBeUndefined()
}
@@ -72,7 +73,7 @@ export function expectNoEdgeBetween(edges: Edge[], sourceId: string, targetId: s
* ```
*/
export function expectBlockHasParent(
blocks: Record<string, BlockState>,
blocks: Record<string, any>,
childId: string,
expectedParentId: string
): void {
@@ -91,7 +92,7 @@ export function expectBlockHasParent(
* expectBlockCount(workflow, 5)
* ```
*/
export function expectBlockCount(workflow: WorkflowState, expectedCount: number): void {
export function expectBlockCount(workflow: any, expectedCount: number): void {
const actualCount = Object.keys(workflow.blocks).length
expect(actualCount, `Workflow should have ${expectedCount} blocks`).toBe(expectedCount)
}
@@ -104,7 +105,7 @@ export function expectBlockCount(workflow: WorkflowState, expectedCount: number)
* expectEdgeCount(workflow, 4)
* ```
*/
export function expectEdgeCount(workflow: WorkflowState, expectedCount: number): void {
export function expectEdgeCount(workflow: any, expectedCount: number): void {
expect(workflow.edges.length, `Workflow should have ${expectedCount} edges`).toBe(expectedCount)
}
@@ -117,7 +118,7 @@ export function expectEdgeCount(workflow: WorkflowState, expectedCount: number):
* ```
*/
export function expectBlockPosition(
blocks: Record<string, BlockState>,
blocks: Record<string, any>,
blockId: string,
expectedPosition: { x: number; y: number }
): void {
@@ -135,7 +136,7 @@ export function expectBlockPosition(
* expectBlockEnabled(workflow.blocks, 'block-1')
* ```
*/
export function expectBlockEnabled(blocks: Record<string, BlockState>, blockId: string): void {
export function expectBlockEnabled(blocks: Record<string, any>, blockId: string): void {
const block = blocks[blockId]
expect(block, `Block "${blockId}" should exist`).toBeDefined()
expect(block.enabled, `Block "${blockId}" should be enabled`).toBe(true)
@@ -149,7 +150,7 @@ export function expectBlockEnabled(blocks: Record<string, BlockState>, blockId:
* expectBlockDisabled(workflow.blocks, 'disabled-block')
* ```
*/
export function expectBlockDisabled(blocks: Record<string, BlockState>, blockId: string): void {
export function expectBlockDisabled(blocks: Record<string, any>, blockId: string): void {
const block = blocks[blockId]
expect(block, `Block "${blockId}" should exist`).toBeDefined()
expect(block.enabled, `Block "${blockId}" should be disabled`).toBe(false)
@@ -164,7 +165,7 @@ export function expectBlockDisabled(blocks: Record<string, BlockState>, blockId:
* ```
*/
export function expectLoopExists(
workflow: WorkflowState,
workflow: any,
loopId: string,
expectedConfig?: { iterations?: number; loopType?: string; nodes?: string[] }
): void {
@@ -193,7 +194,7 @@ export function expectLoopExists(
* ```
*/
export function expectParallelExists(
workflow: WorkflowState,
workflow: any,
parallelId: string,
expectedConfig?: { count?: number; parallelType?: string; nodes?: string[] }
): void {
@@ -222,7 +223,7 @@ export function expectParallelExists(
* expectEmptyWorkflow(workflow)
* ```
*/
export function expectEmptyWorkflow(workflow: WorkflowState): void {
export function expectEmptyWorkflow(workflow: any): void {
expect(Object.keys(workflow.blocks).length, 'Workflow should have no blocks').toBe(0)
expect(workflow.edges.length, 'Workflow should have no edges').toBe(0)
expect(Object.keys(workflow.loops).length, 'Workflow should have no loops').toBe(0)
@@ -237,7 +238,7 @@ export function expectEmptyWorkflow(workflow: WorkflowState): void {
* expectLinearChain(workflow.edges, ['start', 'step1', 'step2', 'end'])
* ```
*/
export function expectLinearChain(edges: Edge[], blockIds: string[]): void {
export function expectLinearChain(edges: any[], blockIds: string[]): void {
for (let i = 0; i < blockIds.length - 1; i++) {
expectEdgeConnects(edges, blockIds[i], blockIds[i + 1])
}

View File

@@ -4,7 +4,9 @@ import {
createFunctionBlock,
createStarterBlock,
} from '../factories/block.factory'
import type { BlockState, Edge, Loop, Parallel, Position, WorkflowState } from '../types'
import type { Position } from '../types'
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Fluent builder for creating complex workflow states.
@@ -29,11 +31,11 @@ import type { BlockState, Edge, Loop, Parallel, Position, WorkflowState } from '
* ```
*/
export class WorkflowBuilder {
private blocks: Record<string, BlockState> = {}
private edges: Edge[] = []
private loops: Record<string, Loop> = {}
private parallels: Record<string, Parallel> = {}
private variables: WorkflowState['variables'] = []
private blocks: Record<string, any> = {}
private edges: any[] = []
private loops: Record<string, any> = {}
private parallels: Record<string, any> = {}
private variables: any[] = []
private isDeployed = false
/**
@@ -245,8 +247,9 @@ export class WorkflowBuilder {
/**
* Builds and returns the workflow state.
* Returns `any` to be assignable to any app's workflow type.
*/
build(): WorkflowState {
build(): any {
return {
blocks: this.blocks,
edges: this.edges,

View File

@@ -1,15 +1,18 @@
import type { BlockData, BlockOutput, BlockState, Position, SubBlockState } from '../types'
import type { BlockData, BlockOutput, Position } from '../types'
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Options for creating a mock block.
* All fields are optional - sensible defaults are provided.
* Uses `any` for subBlocks to accept any app type without conflicts.
*/
export interface BlockFactoryOptions {
id?: string
type?: string
name?: string
position?: Position
subBlocks?: Record<string, SubBlockState>
subBlocks?: Record<string, any>
outputs?: Record<string, BlockOutput>
enabled?: boolean
horizontalHandles?: boolean
@@ -43,7 +46,7 @@ function generateBlockId(prefix = 'block'): string {
* const block = createBlock({ type: 'function', parentId: 'loop-1' })
* ```
*/
export function createBlock(options: BlockFactoryOptions = {}): BlockState {
export function createBlock(options: BlockFactoryOptions = {}): any {
const id = options.id ?? generateBlockId(options.type ?? 'block')
const data: BlockData = options.data ?? {}
@@ -72,7 +75,7 @@ export function createBlock(options: BlockFactoryOptions = {}): BlockState {
/**
* Creates a starter block (workflow entry point).
*/
export function createStarterBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createStarterBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'starter',
@@ -83,7 +86,7 @@ export function createStarterBlock(options: Omit<BlockFactoryOptions, 'type'> =
/**
* Creates an agent block (AI agent execution).
*/
export function createAgentBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createAgentBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'agent',
@@ -94,7 +97,7 @@ export function createAgentBlock(options: Omit<BlockFactoryOptions, 'type'> = {}
/**
* Creates a function block (code execution).
*/
export function createFunctionBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createFunctionBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'function',
@@ -105,7 +108,7 @@ export function createFunctionBlock(options: Omit<BlockFactoryOptions, 'type'> =
/**
* Creates a condition block (branching logic).
*/
export function createConditionBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createConditionBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'condition',
@@ -121,7 +124,7 @@ export function createLoopBlock(
loopType?: 'for' | 'forEach' | 'while' | 'doWhile'
count?: number
} = {}
): BlockState {
): any {
const data: BlockData = {
...options.data,
loopType: options.loopType ?? 'for',
@@ -145,7 +148,7 @@ export function createParallelBlock(
parallelType?: 'count' | 'collection'
count?: number
} = {}
): BlockState {
): any {
const data: BlockData = {
...options.data,
parallelType: options.parallelType ?? 'count',
@@ -164,7 +167,7 @@ export function createParallelBlock(
/**
* Creates a router block (output routing).
*/
export function createRouterBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createRouterBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'router',
@@ -175,7 +178,7 @@ export function createRouterBlock(options: Omit<BlockFactoryOptions, 'type'> = {
/**
* Creates an API block (HTTP requests).
*/
export function createApiBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createApiBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'api',
@@ -186,7 +189,7 @@ export function createApiBlock(options: Omit<BlockFactoryOptions, 'type'> = {}):
/**
* Creates a response block (workflow output).
*/
export function createResponseBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createResponseBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'response',
@@ -197,7 +200,7 @@ export function createResponseBlock(options: Omit<BlockFactoryOptions, 'type'> =
/**
* Creates a webhook trigger block.
*/
export function createWebhookBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createWebhookBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'webhook',
@@ -208,7 +211,7 @@ export function createWebhookBlock(options: Omit<BlockFactoryOptions, 'type'> =
/**
* Creates a knowledge block (vector search).
*/
export function createKnowledgeBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): BlockState {
export function createKnowledgeBlock(options: Omit<BlockFactoryOptions, 'type'> = {}): any {
return createBlock({
...options,
type: 'knowledge',

View File

@@ -1,4 +1,4 @@
import type { Edge } from '../types'
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Options for creating a mock edge.
@@ -36,7 +36,7 @@ function generateEdgeId(source: string, target: string): string {
* })
* ```
*/
export function createEdge(options: EdgeFactoryOptions): Edge {
export function createEdge(options: EdgeFactoryOptions): any {
return {
id: options.id ?? generateEdgeId(options.source, options.target),
source: options.source,
@@ -66,7 +66,7 @@ export function createEdges(
sourceHandle?: string
targetHandle?: string
}>
): Edge[] {
): any[] {
return connections.map((conn) => createEdge(conn))
}
@@ -79,8 +79,8 @@ export function createEdges(
* const edges = createLinearEdges(['a', 'b', 'c', 'd'])
* ```
*/
export function createLinearEdges(blockIds: string[]): Edge[] {
const edges: Edge[] = []
export function createLinearEdges(blockIds: string[]): any[] {
const edges: any[] = []
for (let i = 0; i < blockIds.length - 1; i++) {
edges.push(createEdge({ source: blockIds[i], target: blockIds[i + 1] }))
}

View File

@@ -1,5 +1,6 @@
import { nanoid } from 'nanoid'
import type { BlockState, Edge } from '../types'
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Operation types supported by the undo/redo store.
@@ -51,8 +52,8 @@ export interface RemoveBlockOperation extends BaseOperation {
type: 'remove-block'
data: {
blockId: string
blockSnapshot: BlockState | null
edgeSnapshots?: Edge[]
blockSnapshot: any
edgeSnapshots?: any[]
}
}
@@ -69,7 +70,7 @@ export interface AddEdgeOperation extends BaseOperation {
*/
export interface RemoveEdgeOperation extends BaseOperation {
type: 'remove-edge'
data: { edgeId: string; edgeSnapshot: Edge | null }
data: { edgeId: string; edgeSnapshot: any }
}
/**
@@ -80,7 +81,7 @@ export interface DuplicateBlockOperation extends BaseOperation {
data: {
sourceBlockId: string
duplicatedBlockId: string
duplicatedBlockSnapshot: BlockState
duplicatedBlockSnapshot: any
}
}
@@ -127,10 +128,7 @@ interface OperationEntryOptions {
/**
* Creates a mock add-block operation entry.
*/
export function createAddBlockEntry(
blockId: string,
options: OperationEntryOptions = {}
): OperationEntry {
export function createAddBlockEntry(blockId: string, options: OperationEntryOptions = {}): any {
const { id = nanoid(8), workflowId = 'wf-1', userId = 'user-1', createdAt = Date.now() } = options
const timestamp = Date.now()
@@ -161,9 +159,9 @@ export function createAddBlockEntry(
*/
export function createRemoveBlockEntry(
blockId: string,
blockSnapshot: BlockState | null = null,
blockSnapshot: any = null,
options: OperationEntryOptions = {}
): OperationEntry {
): any {
const { id = nanoid(8), workflowId = 'wf-1', userId = 'user-1', createdAt = Date.now() } = options
const timestamp = Date.now()
@@ -192,10 +190,7 @@ export function createRemoveBlockEntry(
/**
* Creates a mock add-edge operation entry.
*/
export function createAddEdgeEntry(
edgeId: string,
options: OperationEntryOptions = {}
): OperationEntry {
export function createAddEdgeEntry(edgeId: string, options: OperationEntryOptions = {}): any {
const { id = nanoid(8), workflowId = 'wf-1', userId = 'user-1', createdAt = Date.now() } = options
const timestamp = Date.now()
@@ -226,9 +221,9 @@ export function createAddEdgeEntry(
*/
export function createRemoveEdgeEntry(
edgeId: string,
edgeSnapshot: Edge | null = null,
edgeSnapshot: any = null,
options: OperationEntryOptions = {}
): OperationEntry {
): any {
const { id = nanoid(8), workflowId = 'wf-1', userId = 'user-1', createdAt = Date.now() } = options
const timestamp = Date.now()
@@ -262,10 +257,7 @@ interface MoveBlockOptions extends OperationEntryOptions {
/**
* Creates a mock move-block operation entry.
*/
export function createMoveBlockEntry(
blockId: string,
options: MoveBlockOptions = {}
): OperationEntry {
export function createMoveBlockEntry(blockId: string, options: MoveBlockOptions = {}): any {
const {
id = nanoid(8),
workflowId = 'wf-1',
@@ -304,9 +296,9 @@ export function createMoveBlockEntry(
export function createDuplicateBlockEntry(
sourceBlockId: string,
duplicatedBlockId: string,
duplicatedBlockSnapshot: BlockState,
duplicatedBlockSnapshot: any,
options: OperationEntryOptions = {}
): OperationEntry {
): any {
const { id = nanoid(8), workflowId = 'wf-1', userId = 'user-1', createdAt = Date.now() } = options
const timestamp = Date.now()
@@ -343,7 +335,7 @@ export function createUpdateParentEntry(
oldPosition?: { x: number; y: number }
newPosition?: { x: number; y: number }
} = {}
): OperationEntry {
): any {
const {
id = nanoid(8),
workflowId = 'wf-1',

View File

@@ -1,18 +1,20 @@
import type { BlockState, Edge, Loop, Parallel, WorkflowState } from '../types'
import { createBlock, createFunctionBlock, createStarterBlock } from './block.factory'
import { createLinearEdges } from './edge.factory'
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Options for creating a mock workflow state.
* Uses `any` for complex types to avoid conflicts with app types.
*/
export interface WorkflowFactoryOptions {
blocks?: Record<string, BlockState>
edges?: Edge[]
loops?: Record<string, Loop>
parallels?: Record<string, Parallel>
blocks?: Record<string, any>
edges?: any[]
loops?: Record<string, any>
parallels?: Record<string, any>
lastSaved?: number
isDeployed?: boolean
variables?: WorkflowState['variables']
variables?: any[]
}
/**
@@ -23,7 +25,7 @@ export interface WorkflowFactoryOptions {
* const workflow = createWorkflowState()
* ```
*/
export function createWorkflowState(options: WorkflowFactoryOptions = {}): WorkflowState {
export function createWorkflowState(options: WorkflowFactoryOptions = {}): any {
return {
blocks: options.blocks ?? {},
edges: options.edges ?? [],
@@ -45,12 +47,12 @@ export function createWorkflowState(options: WorkflowFactoryOptions = {}): Workf
* const workflow = createLinearWorkflow(3)
* ```
*/
export function createLinearWorkflow(blockCount: number, spacing = 200): WorkflowState {
export function createLinearWorkflow(blockCount: number, spacing = 200): any {
if (blockCount < 1) {
return createWorkflowState()
}
const blocks: Record<string, BlockState> = {}
const blocks: Record<string, any> = {}
const blockIds: string[] = []
for (let i = 0; i < blockCount; i++) {
@@ -87,8 +89,8 @@ export function createLinearWorkflow(blockCount: number, spacing = 200): Workflo
* └─→ false-branch ┘
* ```
*/
export function createBranchingWorkflow(): WorkflowState {
const blocks: Record<string, BlockState> = {
export function createBranchingWorkflow(): any {
const blocks: Record<string, any> = {
start: createStarterBlock({ id: 'start', position: { x: 0, y: 0 } }),
condition: createBlock({
id: 'condition',
@@ -109,7 +111,7 @@ export function createBranchingWorkflow(): WorkflowState {
end: createFunctionBlock({ id: 'end', name: 'End', position: { x: 600, y: 0 } }),
}
const edges: Edge[] = [
const edges: any[] = [
{ id: 'e1', source: 'start', target: 'condition' },
{ id: 'e2', source: 'condition', target: 'true-branch', sourceHandle: 'condition-if' },
{ id: 'e3', source: 'condition', target: 'false-branch', sourceHandle: 'condition-else' },
@@ -128,8 +130,8 @@ export function createBranchingWorkflow(): WorkflowState {
* start ─→ loop[loop-body] ─→ end
* ```
*/
export function createLoopWorkflow(iterations = 3): WorkflowState {
const blocks: Record<string, BlockState> = {
export function createLoopWorkflow(iterations = 3): any {
const blocks: Record<string, any> = {
start: createStarterBlock({ id: 'start', position: { x: 0, y: 0 } }),
loop: createBlock({
id: 'loop',
@@ -147,12 +149,12 @@ export function createLoopWorkflow(iterations = 3): WorkflowState {
end: createFunctionBlock({ id: 'end', name: 'End', position: { x: 500, y: 0 } }),
}
const edges: Edge[] = [
const edges: any[] = [
{ id: 'e1', source: 'start', target: 'loop' },
{ id: 'e2', source: 'loop', target: 'end' },
]
const loops: Record<string, Loop> = {
const loops: Record<string, any> = {
loop: {
id: 'loop',
nodes: ['loop-body'],
@@ -172,8 +174,8 @@ export function createLoopWorkflow(iterations = 3): WorkflowState {
* start ─→ parallel[parallel-task] ─→ end
* ```
*/
export function createParallelWorkflow(count = 2): WorkflowState {
const blocks: Record<string, BlockState> = {
export function createParallelWorkflow(count = 2): any {
const blocks: Record<string, any> = {
start: createStarterBlock({ id: 'start', position: { x: 0, y: 0 } }),
parallel: createBlock({
id: 'parallel',
@@ -191,12 +193,12 @@ export function createParallelWorkflow(count = 2): WorkflowState {
end: createFunctionBlock({ id: 'end', name: 'End', position: { x: 500, y: 0 } }),
}
const edges: Edge[] = [
const edges: any[] = [
{ id: 'e1', source: 'start', target: 'parallel' },
{ id: 'e2', source: 'parallel', target: 'end' },
]
const parallels: Record<string, Parallel> = {
const parallels: Record<string, any> = {
parallel: {
id: 'parallel',
nodes: ['parallel-task'],

View File

@@ -1,9 +1,15 @@
/**
* Core types for the testing package.
* These are simplified versions of the actual types used in apps/sim,
* designed for test scenarios without requiring all dependencies.
*
* These are intentionally loose/permissive types that accept any shape of data
* from the app. The testing package should not try to mirror app types exactly -
* that creates maintenance burden and type drift issues.
*
* Tests themselves provide type safety through their actual usage of app types.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface Position {
x: number
y: number
@@ -11,72 +17,26 @@ export interface Position {
export interface BlockData {
parentId?: string
extent?: 'parent'
extent?: string
width?: number
height?: number
count?: number
loopType?: 'for' | 'forEach' | 'while' | 'doWhile'
parallelType?: 'count' | 'collection'
loopType?: string
parallelType?: string
collection?: any
whileCondition?: string
doWhileCondition?: string
type?: string
[key: string]: any
}
/**
* SubBlockType union for testing.
* Matches the SubBlockType values from the app (apps/sim/blocks/types.ts).
*/
export type SubBlockType =
| 'short-input'
| 'long-input'
| 'dropdown'
| 'combobox'
| 'slider'
| 'table'
| 'code'
| 'switch'
| 'tool-input'
| 'checkbox-list'
| 'grouped-checkbox-list'
| 'condition-input'
| 'eval-input'
| 'time-input'
| 'oauth-input'
| 'webhook-config'
| 'schedule-info'
| 'file-selector'
| 'project-selector'
| 'channel-selector'
| 'user-selector'
| 'folder-selector'
| 'knowledge-base-selector'
| 'knowledge-tag-filters'
| 'document-selector'
| 'document-tag-entry'
| 'mcp-server-selector'
| 'mcp-tool-selector'
| 'mcp-dynamic-args'
| 'input-format'
export interface SubBlockState {
id: string
type: SubBlockType
value: string | number | string[][] | null
type: string
value: any
}
/**
* Primitive value types for block outputs.
*/
export type PrimitiveValueType = 'string' | 'number' | 'boolean'
/**
* BlockOutput type matching the app's structure.
* Can be a primitive type or an object with string keys.
*/
export type BlockOutput =
| PrimitiveValueType
| { [key: string]: PrimitiveValueType | Record<string, any> }
export type BlockOutput = any
export interface BlockState {
id: string
@@ -91,38 +51,39 @@ export interface BlockState {
advancedMode?: boolean
triggerMode?: boolean
data?: BlockData
layout?: {
measuredWidth?: number
measuredHeight?: number
}
layout?: Record<string, any>
[key: string]: any
}
export interface Edge {
id: string
source: string
target: string
sourceHandle?: string
targetHandle?: string
sourceHandle?: string | null
targetHandle?: string | null
type?: string
data?: Record<string, any>
[key: string]: any
}
export interface Loop {
id: string
nodes: string[]
iterations: number
loopType: 'for' | 'forEach' | 'while' | 'doWhile'
forEachItems?: any[] | Record<string, any> | string
loopType: string
forEachItems?: any
whileCondition?: string
doWhileCondition?: string
[key: string]: any
}
export interface Parallel {
id: string
nodes: string[]
distribution?: any[] | Record<string, any> | string
distribution?: any
count?: number
parallelType?: 'count' | 'collection'
parallelType?: string
[key: string]: any
}
export interface WorkflowState {
@@ -135,12 +96,8 @@ export interface WorkflowState {
isDeployed?: boolean
deployedAt?: Date
needsRedeployment?: boolean
variables?: Array<{
id: string
name: string
type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'plain'
value: any
}>
variables?: any[]
[key: string]: any
}
export interface ExecutionContext {
@@ -164,6 +121,7 @@ export interface ExecutionContext {
completedLoops: Set<string>
activeExecutionPath: Set<string>
abortSignal?: AbortSignal
[key: string]: any
}
export interface User {
@@ -171,6 +129,7 @@ export interface User {
email: string
name?: string
image?: string
[key: string]: any
}
export interface Workspace {
@@ -179,6 +138,7 @@ export interface Workspace {
ownerId: string
createdAt: Date
updatedAt: Date
[key: string]: any
}
export interface Workflow {
@@ -189,4 +149,5 @@ export interface Workflow {
createdAt: Date
updatedAt: Date
isDeployed?: boolean
[key: string]: any
}

View File

@@ -1,20 +1,11 @@
{
"extends": "@sim/tsconfig/library.json",
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "./src",
"baseUrl": ".",
"paths": {
"@sim/testing/*": ["./src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
"exclude": ["node_modules"]
}

View File

@@ -3,9 +3,6 @@
"version": "0.1.1",
"description": "Sim SDK - Execute workflows programmatically",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
@@ -13,8 +10,8 @@
}
},
"scripts": {
"build": "bun run build:tsc",
"build:tsc": "tsc",
"build": "tsc",
"type-check": "tsc --noEmit",
"dev:watch": "tsc --watch",
"prepublishOnly": "bun run build",
"test": "vitest run",
@@ -38,10 +35,11 @@
"node-fetch": "^3.3.2"
},
"devDependencies": {
"@sim/tsconfig": "workspace:*",
"@types/node": "^20.5.1",
"typescript": "^5.1.6",
"vitest": "^3.0.8",
"@vitest/coverage-v8": "^3.0.8"
"@vitest/coverage-v8": "^3.0.8",
"typescript": "^5.7.3",
"vitest": "^3.0.8"
},
"engines": {
"node": ">=16"

View File

@@ -1,19 +1,12 @@
{
"extends": "@sim/tsconfig/library-build.json",
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"moduleResolution": "node",
"resolveJsonModule": true
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Base",
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": false,
"noUncheckedIndexedAccess": false,
"noEmit": true
},
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,11 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Library (Build)",
"extends": "./base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noEmit": false
}
}

View File

@@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Library (No Emit)",
"extends": "./base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"noEmit": true
}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js",
"extends": "./base.json",
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"allowJs": true,
"incremental": true,
"allowImportingTsExtensions": true,
"plugins": [
{
"name": "next"
}
]
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "@sim/tsconfig",
"version": "0.0.0",
"private": true,
"license": "Apache-2.0",
"description": "Shared TypeScript configurations for Sim monorepo",
"exports": {
"./base.json": "./base.json",
"./nextjs.json": "./nextjs.json",
"./library.json": "./library.json",
"./library-build.json": "./library-build.json"
}
}

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