mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-10 15:38:00 -05:00
Compare commits
14 Commits
feat/snowf
...
v0.5.19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12c4c2d44f | ||
|
|
929a352edb | ||
|
|
6cd078b0fe | ||
|
|
31874939ee | ||
|
|
e157ce5fbc | ||
|
|
774e5d585c | ||
|
|
54cc93743f | ||
|
|
8c32ad4c0d | ||
|
|
1d08796853 | ||
|
|
ebcd243942 | ||
|
|
b7e814b721 | ||
|
|
842ef27ed9 | ||
|
|
31c34b2ea3 | ||
|
|
8f0ef58056 |
@@ -1084,27 +1084,6 @@ export function GoogleDocsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function GoogleSlidesIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 48 48'
|
||||
width='96px'
|
||||
height='96px'
|
||||
>
|
||||
<path
|
||||
fill='#FFC107'
|
||||
d='M37,45H11c-1.657,0-3-1.343-3-3V6c0-1.657,1.343-3,3-3h19l10,10v29C40,43.657,38.657,45,37,45z'
|
||||
/>
|
||||
<path fill='#FFECB3' d='M40 13L30 13 30 3z' />
|
||||
<path fill='#FFA000' d='M30 13L40 23 40 13z' />
|
||||
<path fill='#FFF8E1' d='M14 21H34V35H14z' />
|
||||
<path fill='#FFA000' d='M16 23H32V26H16zM16 28H28V30H16z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function GoogleCalendarIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
GoogleFormsIcon,
|
||||
GoogleIcon,
|
||||
GoogleSheetsIcon,
|
||||
GoogleSlidesIcon,
|
||||
GoogleVaultIcon,
|
||||
GrafanaIcon,
|
||||
HubspotIcon,
|
||||
@@ -193,7 +192,6 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
hubspot: HubspotIcon,
|
||||
grafana: GrafanaIcon,
|
||||
google_vault: GoogleVaultIcon,
|
||||
google_slides: GoogleSlidesIcon,
|
||||
google_sheets: GoogleSheetsIcon,
|
||||
google_forms: GoogleFormsIcon,
|
||||
google_drive: GoogleDriveIcon,
|
||||
|
||||
@@ -142,11 +142,10 @@ Erhalte die Top-Seiten einer Zieldomain, sortiert nach organischem Traffic. Lief
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `target` | string | Ja | Die zu analysierende Zieldomäne |
|
||||
| `country` | string | Nein | Ländercode für Verkehrsdaten \(z.B. us, gb, de\). Standard: us |
|
||||
| `mode` | string | Nein | Analysemodus: domain \(gesamte Domäne\), prefix \(URL-Präfix\), subdomains \(alle Subdomänen einschließen\) |
|
||||
| `mode` | string | Nein | Analysemodus: domain \(gesamte Domain\), prefix \(URL-Präfix\), subdomains \(alle Subdomains einschließen\) |
|
||||
| `date` | string | Nein | Datum für historische Daten im Format JJJJ-MM-TT \(standardmäßig heute\) |
|
||||
| `limit` | number | Nein | Maximale Anzahl der zurückzugebenden Ergebnisse \(Standard: 100\) |
|
||||
| `offset` | number | Nein | Anzahl der zu überspringenden Ergebnisse für Paginierung |
|
||||
| `select` | string | Nein | Kommagetrennte Liste der zurückzugebenden Felder \(z.B. url,traffic,keywords,top_keyword,value\). Standard: url,traffic,keywords,top_keyword,value |
|
||||
| `apiKey` | string | Ja | Ahrefs API-Schlüssel |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -38,14 +38,14 @@ Erstellt ein neues Ereignis in Google Kalender
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `calendarId` | string | Nein | Kalender-ID (standardmäßig primary) |
|
||||
| `calendarId` | string | Nein | Kalender-ID \(standardmäßig primary\) |
|
||||
| `summary` | string | Ja | Ereignistitel/Zusammenfassung |
|
||||
| `description` | string | Nein | Ereignisbeschreibung |
|
||||
| `location` | string | Nein | Ereignisort |
|
||||
| `startDateTime` | string | Ja | Startdatum und -uhrzeit. MUSS Zeitzonen-Offset enthalten (z.B. 2025-06-03T10:00:00-08:00) ODER timeZone-Parameter bereitstellen |
|
||||
| `endDateTime` | string | Ja | Enddatum und -uhrzeit. MUSS Zeitzonen-Offset enthalten (z.B. 2025-06-03T11:00:00-08:00) ODER timeZone-Parameter bereitstellen |
|
||||
| `timeZone` | string | Nein | Zeitzone (z.B. America/Los_Angeles). Erforderlich, wenn datetime keinen Offset enthält. Standardmäßig America/Los_Angeles, wenn nicht angegeben. |
|
||||
| `attendees` | array | Nein | Array von E-Mail-Adressen der Teilnehmer |
|
||||
| `startDateTime` | string | Ja | Startdatum und -uhrzeit \(RFC3339-Format, z.B. 2025-06-03T10:00:00-08:00\) |
|
||||
| `endDateTime` | string | Ja | Enddatum und -uhrzeit \(RFC3339-Format, z.B. 2025-06-03T11:00:00-08:00\) |
|
||||
| `timeZone` | string | Nein | Zeitzone \(z.B. America/Los_Angeles\) |
|
||||
| `attendees` | array | Nein | Array mit E-Mail-Adressen der Teilnehmer |
|
||||
| `sendUpdates` | string | Nein | Wie Updates an Teilnehmer gesendet werden: all, externalOnly oder none |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -108,10 +108,10 @@ Dateien und Ordner in Google Drive auflisten
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `folderSelector` | string | Nein | Wählen Sie den Ordner aus, aus dem Dateien aufgelistet werden sollen |
|
||||
| `folderSelector` | string | Nein | Den Ordner auswählen, aus dem Dateien aufgelistet werden sollen |
|
||||
| `folderId` | string | Nein | Die ID des Ordners, aus dem Dateien aufgelistet werden sollen (interne Verwendung) |
|
||||
| `query` | string | Nein | Suchbegriff, um Dateien nach Namen zu filtern (z.B. "budget" findet Dateien mit "budget" im Namen). Verwenden Sie hier KEINE Google Drive-Abfragesyntax - geben Sie einfach einen einfachen Suchbegriff ein. |
|
||||
| `pageSize` | number | Nein | Die maximale Anzahl der zurückzugebenden Dateien (Standard: 100) |
|
||||
| `query` | string | Nein | Eine Abfrage zum Filtern der Dateien |
|
||||
| `pageSize` | number | Nein | Die Anzahl der zurückzugebenden Dateien |
|
||||
| `pageToken` | string | Nein | Das Seitentoken für die Paginierung |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -88,8 +88,8 @@ Daten aus einer Google Sheets-Tabelle lesen
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | Ja | Die ID der Tabelle \(zu finden in der URL: docs.google.com/spreadsheets/d/\{SPREADSHEET_ID\}/edit\). |
|
||||
| `range` | string | Nein | Der A1-Notationsbereich zum Lesen \(z.B. "Sheet1!A1:D10", "A1:B5"\). Standardmäßig wird das erste Tabellenblatt A1:Z1000 verwendet, wenn nicht angegeben. |
|
||||
| `spreadsheetId` | string | Ja | Die ID der Tabelle, aus der gelesen werden soll |
|
||||
| `range` | string | Nein | Der Zellbereich, aus dem gelesen werden soll |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -106,9 +106,9 @@ Daten in eine Google Sheets-Tabelle schreiben
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | Ja | Die ID der Tabelle |
|
||||
| `range` | string | Nein | Der A1-Notationsbereich, in den geschrieben werden soll \(z.B. "Sheet1!A1:D10", "A1:B5"\) |
|
||||
| `values` | array | Ja | Die zu schreibenden Daten als 2D-Array \(z.B. \[\["Name", "Alter"\], \["Alice", 30\], \["Bob", 25\]\]\) oder Array von Objekten. |
|
||||
| `spreadsheetId` | string | Ja | Die ID der Tabelle, in die geschrieben werden soll |
|
||||
| `range` | string | Nein | Der Zellbereich, in den geschrieben werden soll |
|
||||
| `values` | array | Ja | Die Daten, die in die Tabelle geschrieben werden sollen |
|
||||
| `valueInputOption` | string | Nein | Das Format der zu schreibenden Daten |
|
||||
| `includeValuesInResponse` | boolean | Nein | Ob die geschriebenen Werte in der Antwort enthalten sein sollen |
|
||||
|
||||
@@ -131,8 +131,8 @@ Daten in einer Google Sheets-Tabelle aktualisieren
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | Ja | Die ID der zu aktualisierenden Tabelle |
|
||||
| `range` | string | Nein | Der A1-Notationsbereich, der aktualisiert werden soll \(z.B. "Sheet1!A1:D10", "A1:B5"\) |
|
||||
| `values` | array | Ja | Die zu aktualisierenden Daten als 2D-Array \(z.B. \[\["Name", "Alter"\], \["Alice", 30\]\]\) oder Array von Objekten. |
|
||||
| `range` | string | Nein | Der Bereich der zu aktualisierenden Zellen |
|
||||
| `values` | array | Ja | Die Daten, die in der Tabelle aktualisiert werden sollen |
|
||||
| `valueInputOption` | string | Nein | Das Format der zu aktualisierenden Daten |
|
||||
| `includeValuesInResponse` | boolean | Nein | Ob die aktualisierten Werte in der Antwort enthalten sein sollen |
|
||||
|
||||
@@ -155,10 +155,10 @@ Daten am Ende einer Google Sheets-Tabelle anhängen
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | Ja | Die ID der Tabelle, an die angehängt werden soll |
|
||||
| `range` | string | Nein | Der A1-Notationsbereich, an den angehängt werden soll (z.B. "Sheet1", "Sheet1!A:D") |
|
||||
| `values` | array | Ja | Die anzuhängenden Daten als 2D-Array (z.B. [["Alice", 30], ["Bob", 25]]) oder Array von Objekten |
|
||||
| `range` | string | Nein | Der Bereich der Zellen, nach dem angehängt werden soll |
|
||||
| `values` | array | Ja | Die Daten, die an die Tabelle angehängt werden sollen |
|
||||
| `valueInputOption` | string | Nein | Das Format der anzuhängenden Daten |
|
||||
| `insertDataOption` | string | Nein | Wie die Daten eingefügt werden sollen (OVERWRITE oder INSERT_ROWS) |
|
||||
| `insertDataOption` | string | Nein | Wie die Daten eingefügt werden sollen \(OVERWRITE oder INSERT_ROWS\) |
|
||||
| `includeValuesInResponse` | boolean | Nein | Ob die angehängten Werte in der Antwort enthalten sein sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
title: Google Slides
|
||||
description: Präsentationen lesen, schreiben und erstellen
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="google_slides"
|
||||
color="#E0E0E0"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Google Slides](https://slides.google.com) ist eine dynamische cloudbasierte Präsentationsanwendung, mit der Benutzer in Echtzeit Präsentationen erstellen, bearbeiten, gemeinsam daran arbeiten und präsentieren können. Als Teil der Google-Produktivitätssuite bietet Google Slides eine flexible Plattform zum Gestalten ansprechender Präsentationen, zur Zusammenarbeit mit anderen und zum nahtlosen Teilen von Inhalten über die Cloud.
|
||||
|
||||
Erfahren Sie, wie Sie die Google Slides-Tools in Sim integrieren können, um Präsentationen mühelos als Teil Ihrer automatisierten Workflows zu verwalten. Mit Sim können Sie Google Slides-Präsentationen direkt über Ihre Agenten und automatisierten Prozesse lesen, schreiben, erstellen und aktualisieren, wodurch es einfach wird, aktuelle Informationen zu liefern, benutzerdefinierte Berichte zu generieren oder Marken-Präsentationen programmatisch zu erstellen.
|
||||
|
||||
Mit Google Slides können Sie:
|
||||
|
||||
- **Präsentationen erstellen und bearbeiten**: Gestalten Sie visuell ansprechende Folien mit Designs, Layouts und Multimedia-Inhalten
|
||||
- **In Echtzeit zusammenarbeiten**: Arbeiten Sie gleichzeitig mit Teammitgliedern, kommentieren Sie, weisen Sie Aufgaben zu und erhalten Sie Live-Feedback zu Präsentationen
|
||||
- **Überall präsentieren**: Zeigen Sie Präsentationen online oder offline an, teilen Sie Links oder veröffentlichen Sie im Web
|
||||
- **Bilder und umfangreiche Inhalte hinzufügen**: Fügen Sie Bilder, Grafiken, Diagramme und Videos ein, um Ihre Präsentationen ansprechend zu gestalten
|
||||
- **Mit anderen Diensten integrieren**: Verbinden Sie sich nahtlos mit Google Drive, Docs, Sheets und anderen Drittanbieter-Tools
|
||||
- **Von jedem Gerät aus zugreifen**: Nutzen Sie Google Slides auf Desktop-Computern, Laptops, Tablets und mobilen Geräten für maximale Flexibilität
|
||||
|
||||
In Sim ermöglicht die Google Slides-Integration Ihren Agenten, direkt programmatisch mit Präsentationsdateien zu interagieren. Automatisieren Sie Aufgaben wie das Lesen von Folieninhalten, das Einfügen neuer Folien oder Bilder, das Ersetzen von Text in einer gesamten Präsentation, das Erstellen neuer Präsentationen und das Abrufen von Folien-Miniaturansichten. Dies ermöglicht Ihnen, die Inhaltserstellung zu skalieren, Präsentationen aktuell zu halten und sie in automatisierte Dokumenten-Workflows einzubetten. Durch die Verbindung von Sim mit Google Slides ermöglichen Sie KI-gestütztes Präsentationsmanagement – so wird es einfach, Präsentationen ohne manuellen Aufwand zu generieren, zu aktualisieren oder Informationen daraus zu extrahieren.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## Nutzungsanleitung
|
||||
|
||||
Integrieren Sie Google Slides in den Workflow. Kann Präsentationen lesen, schreiben, erstellen, Text ersetzen, Folien hinzufügen, Bilder einfügen und Vorschaubilder abrufen.
|
||||
|
||||
## Tools
|
||||
|
||||
### `google_slides_read`
|
||||
|
||||
Inhalte aus einer Google Slides-Präsentation lesen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Ja | Die ID der zu lesenden Präsentation |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slides` | json | Array von Folien mit ihren Inhalten |
|
||||
| `metadata` | json | Präsentationsmetadaten einschließlich ID, Titel und URL |
|
||||
|
||||
### `google_slides_write`
|
||||
|
||||
Inhalte in einer Google Slides-Präsentation schreiben oder aktualisieren
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Ja | Die ID der Präsentation, in die geschrieben werden soll |
|
||||
| `content` | string | Ja | Der Inhalt, der in die Folie geschrieben werden soll |
|
||||
| `slideIndex` | number | Nein | Der Index der Folie, in die geschrieben werden soll \(standardmäßig die erste Folie\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `updatedContent` | boolean | Gibt an, ob der Präsentationsinhalt erfolgreich aktualisiert wurde |
|
||||
| `metadata` | json | Aktualisierte Präsentationsmetadaten einschließlich ID, Titel und URL |
|
||||
|
||||
### `google_slides_create`
|
||||
|
||||
Eine neue Google Slides-Präsentation erstellen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | Ja | Der Titel der zu erstellenden Präsentation |
|
||||
| `content` | string | Nein | Der Inhalt, der zur ersten Folie hinzugefügt werden soll |
|
||||
| `folderSelector` | string | Nein | Wählen Sie den Ordner aus, in dem die Präsentation erstellt werden soll |
|
||||
| `folderId` | string | Nein | Die ID des Ordners, in dem die Präsentation erstellt werden soll \(interne Verwendung\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `metadata` | json | Metadaten der erstellten Präsentation einschließlich ID, Titel und URL |
|
||||
|
||||
### `google_slides_replace_all_text`
|
||||
|
||||
Suchen und ersetzen aller Textvorkommen in einer Google Slides-Präsentation
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Ja | Die ID der Präsentation |
|
||||
| `findText` | string | Ja | Der zu suchende Text (z.B. \{\{placeholder\}\}) |
|
||||
| `replaceText` | string | Ja | Der Text, durch den ersetzt werden soll |
|
||||
| `matchCase` | boolean | Nein | Ob die Suche Groß-/Kleinschreibung berücksichtigen soll (Standard: true) |
|
||||
| `pageObjectIds` | string | Nein | Kommagetrennte Liste von Folienobjekt-IDs, um Ersetzungen auf bestimmte Folien zu beschränken (leer lassen für alle Folien) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `occurrencesChanged` | number | Anzahl der Textvorkommen, die ersetzt wurden |
|
||||
| `metadata` | json | Operationsmetadaten einschließlich Präsentations-ID und URL |
|
||||
|
||||
### `google_slides_add_slide`
|
||||
|
||||
Eine neue Folie mit einem bestimmten Layout zu einer Google Slides-Präsentation hinzufügen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Ja | Die ID der Präsentation |
|
||||
| `layout` | string | Nein | Das vordefinierte Layout für die Folie (BLANK, TITLE, TITLE_AND_BODY, TITLE_ONLY, SECTION_HEADER, usw.). Standard ist BLANK. |
|
||||
| `insertionIndex` | number | Nein | Der optionale nullbasierte Index, der angibt, wo die Folie eingefügt werden soll. Wenn nicht angegeben, wird die Folie am Ende hinzugefügt. |
|
||||
| `placeholderIdMappings` | string | Nein | JSON-Array von Platzhalter-Zuordnungen, um Platzhaltern benutzerdefinierte Objekt-IDs zuzuweisen. Format: \[\{"layoutPlaceholder":\{"type":"TITLE"\},"objectId":"custom_title_id"\}\] |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slideId` | string | Die Objekt-ID der neu erstellten Folie |
|
||||
| `metadata` | json | Operationsmetadaten einschließlich Präsentations-ID, Layout und URL |
|
||||
|
||||
### `google_slides_add_image`
|
||||
|
||||
Ein Bild in eine bestimmte Folie einer Google Slides-Präsentation einfügen
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Ja | Die ID der Präsentation |
|
||||
| `pageObjectId` | string | Ja | Die Objekt-ID der Folie/Seite, zu der das Bild hinzugefügt werden soll |
|
||||
| `imageUrl` | string | Ja | Die öffentlich zugängliche URL des Bildes \(muss PNG, JPEG oder GIF sein, max. 50MB\) |
|
||||
| `width` | number | Nein | Breite des Bildes in Punkten \(Standard: 300\) |
|
||||
| `height` | number | Nein | Höhe des Bildes in Punkten \(Standard: 200\) |
|
||||
| `positionX` | number | Nein | X-Position vom linken Rand in Punkten \(Standard: 100\) |
|
||||
| `positionY` | number | Nein | Y-Position vom oberen Rand in Punkten \(Standard: 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `imageId` | string | Die Objekt-ID des neu erstellten Bildes |
|
||||
| `metadata` | json | Operationsmetadaten einschließlich Präsentations-ID und Bild-URL |
|
||||
|
||||
### `google_slides_get_thumbnail`
|
||||
|
||||
Ein Vorschaubild einer bestimmten Folie in einer Google Slides-Präsentation generieren
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Ja | Die ID der Präsentation |
|
||||
| `pageObjectId` | string | Ja | Die Objekt-ID der Folie/Seite, für die ein Thumbnail erstellt werden soll |
|
||||
| `thumbnailSize` | string | Nein | Die Größe des Thumbnails: SMALL \(200px\), MEDIUM \(800px\) oder LARGE \(1600px\). Standardmäßig MEDIUM. |
|
||||
| `mimeType` | string | Nein | Der MIME-Typ des Thumbnail-Bildes: PNG oder GIF. Standardmäßig PNG. |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `contentUrl` | string | URL zum Thumbnail-Bild \(gültig für 30 Minuten\) |
|
||||
| `width` | number | Breite des Thumbnails in Pixeln |
|
||||
| `height` | number | Höhe des Thumbnails in Pixeln |
|
||||
| `metadata` | json | Operationsmetadaten einschließlich Präsentations-ID und Seitenobjekt-ID |
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Kategorie: `tools`
|
||||
- Typ: `google_slides`
|
||||
@@ -361,8 +361,6 @@ Einen bestehenden Zeitplan in incident.io aktualisieren
|
||||
| `id` | string | Ja | Die ID des zu aktualisierenden Zeitplans |
|
||||
| `name` | string | Nein | Neuer Name für den Zeitplan |
|
||||
| `timezone` | string | Nein | Neue Zeitzone für den Zeitplan \(z.B. America/New_York\) |
|
||||
| `config` | string | Nein | Zeitplankonfiguration als JSON-String mit Rotationen. Beispiel: \{"rotations": \[\{"name": "Primary", "users": \[\{"id": "user_id"\}\], "handover_start_at": "2024-01-01T09:00:00Z", "handovers": \[\{"interval": 1, "interval_type": "weekly"\}\]\}\]\} |
|
||||
| `Example` | string | Nein | Keine Beschreibung |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ Ein Unternehmen in Intercom erstellen oder aktualisieren
|
||||
| `plan` | string | Nein | Der Unternehmensplan |
|
||||
| `size` | number | Nein | Die Anzahl der Mitarbeiter im Unternehmen |
|
||||
| `industry` | string | Nein | Die Branche, in der das Unternehmen tätig ist |
|
||||
| `monthly_spend` | number | Nein | Wie viel Umsatz das Unternehmen für Ihr Geschäft generiert. Hinweis: Dieses Feld kürzt Dezimalzahlen auf ganze Zahlen \(z.B. wird aus 155,98 die Zahl 155\) |
|
||||
| `monthly_spend` | number | Nein | Wie viel Umsatz das Unternehmen für Ihr Geschäft generiert |
|
||||
| `custom_attributes` | string | Nein | Benutzerdefinierte Attribute als JSON-Objekt |
|
||||
|
||||
#### Ausgabe
|
||||
@@ -196,7 +196,7 @@ Ein einzelnes Unternehmen anhand der ID von Intercom abrufen
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
Listet alle Unternehmen von Intercom mit Paginierungsunterstützung auf. Hinweis: Dieser Endpunkt hat ein Limit von 10.000 Unternehmen, die über Paginierung zurückgegeben werden können. Für Datensätze mit mehr als 10.000 Unternehmen verwenden Sie stattdessen die Scroll-API.
|
||||
Alle Unternehmen von Intercom mit Paginierungsunterstützung auflisten
|
||||
|
||||
#### Eingabe
|
||||
|
||||
@@ -259,8 +259,8 @@ Als Administrator auf eine Konversation in Intercom antworten
|
||||
| `conversationId` | string | Ja | Konversations-ID, auf die geantwortet werden soll |
|
||||
| `message_type` | string | Ja | Nachrichtentyp: "comment" oder "note" |
|
||||
| `body` | string | Ja | Der Textinhalt der Antwort |
|
||||
| `admin_id` | string | Nein | Die ID des Administrators, der die Antwort verfasst. Wenn nicht angegeben, wird ein Standard-Administrator \(Operator/Fin\) verwendet. |
|
||||
| `attachment_urls` | string | Nein | Kommagetrennte Liste von Bild-URLs \(max. 10\) |
|
||||
| `admin_id` | string | Ja | Die ID des Administrators, der die Antwort verfasst |
|
||||
| `attachment_urls` | string | Nein | Kommagetrennte Liste von Bild-URLs (max. 10) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Kalshi
|
||||
description: Zugriff auf Prognosemärkte und Handel auf Kalshi
|
||||
description: Zugriff auf Prognosemarktdaten von Kalshi
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -27,7 +27,7 @@ Durch die Nutzung dieser einheitlichen Tools und Endpunkte können Sie Kalshis P
|
||||
|
||||
## Nutzungsanleitung
|
||||
|
||||
Integrieren Sie Kalshi-Prognosemärkte in den Workflow. Kann Märkte, Markt, Ereignisse, Ereignis, Kontostand, Positionen, Aufträge, Orderbuch, Trades, Candlesticks, Ausführungen, Serien, Börsenstatus abrufen und Trades platzieren/stornieren/ändern.
|
||||
Integrieren Sie Kalshi-Prognosemärkte in den Workflow. Kann Märkte, Markt, Ereignisse, Ereignis, Guthaben, Positionen, Aufträge, Orderbuch, Trades, Candlesticks, Ausführungen, Serien und Börsenstatus abrufen.
|
||||
|
||||
## Tools
|
||||
|
||||
@@ -172,34 +172,16 @@ Rufen Sie Ihre Bestellungen von Kalshi mit optionaler Filterung ab
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Bestelldaten und Metadaten |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
Rufen Sie Details zu einem bestimmten Auftrag anhand der ID von Kalshi ab
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Ja | Ihre Kalshi API-Schlüssel-ID |
|
||||
| `privateKey` | string | Ja | Ihr RSA Private Key \(PEM-Format\) |
|
||||
| `orderId` | string | Ja | Die abzurufende Auftrags-ID |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Auftragsdaten |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
Rufen Sie das Orderbuch (Ja- und Nein-Gebote) für einen bestimmten Markt ab
|
||||
Rufen Sie das Orderbuch (Gebote und Anfragen) für einen bestimmten Markt ab
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | Ja | Markt-Ticker \(z.B. KXBTC-24DEC31\) |
|
||||
| `depth` | number | Nein | Anzahl der Preisstufen, die pro Seite zurückgegeben werden sollen |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -210,46 +192,49 @@ Rufen Sie das Orderbuch (Ja- und Nein-Gebote) für einen bestimmten Markt ab
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
Rufen Sie aktuelle Trades über alle Märkte hinweg ab
|
||||
Ruft aktuelle Trades über alle Märkte oder für einen bestimmten Markt ab
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse \(1-1000, Standard: 100\) |
|
||||
| `ticker` | string | Nein | Nach Markt-Ticker filtern |
|
||||
| `minTs` | number | Nein | Minimaler Zeitstempel (Unix-Millisekunden) |
|
||||
| `maxTs` | number | Nein | Maximaler Zeitstempel (Unix-Millisekunden) |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse (1-1000, Standard: 100) |
|
||||
| `cursor` | string | Nein | Paginierungscursor für die nächste Seite |
|
||||
|
||||
#### Output
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Handelsdaten und Metadaten |
|
||||
| `output` | object | Trade-Daten und Metadaten |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
OHLC-Kerzendaten für einen bestimmten Markt abrufen
|
||||
Ruft OHLC-Kerzendaten für einen bestimmten Markt ab
|
||||
|
||||
#### Input
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `seriesTicker` | string | Ja | Serien-Ticker |
|
||||
| `ticker` | string | Ja | Markt-Ticker \(z.B. KXBTC-24DEC31\) |
|
||||
| `startTs` | number | Ja | Startzeitstempel \(Unix-Sekunden\) |
|
||||
| `endTs` | number | Ja | Endzeitstempel \(Unix-Sekunden\) |
|
||||
| `periodInterval` | number | Ja | Periodenintervall: 1 \(1min\), 60 \(1Stunde\) oder 1440 \(1Tag\) |
|
||||
| `ticker` | string | Ja | Markt-Ticker (z.B. KXBTC-24DEC31) |
|
||||
| `startTs` | number | Nein | Startzeitstempel (Unix-Millisekunden) |
|
||||
| `endTs` | number | Nein | Endzeitstempel (Unix-Millisekunden) |
|
||||
| `periodInterval` | number | Nein | Periodenintervall: 1 (1min), 60 (1std), oder 1440 (1tag) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Kerzendaten und Metadaten |
|
||||
| `output` | object | Candlestick-Daten und Metadaten |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
Ihr Portfolio abrufen
|
||||
Rufen Sie Ihr Portfolio ab
|
||||
|
||||
#### Input
|
||||
|
||||
@@ -258,7 +243,7 @@ Ihr Portfolio abrufen
|
||||
| `keyId` | string | Ja | Ihre Kalshi API-Schlüssel-ID |
|
||||
| `privateKey` | string | Ja | Ihr RSA Private Key \(PEM-Format\) |
|
||||
| `ticker` | string | Nein | Nach Markt-Ticker filtern |
|
||||
| `orderId` | string | Nein | Nach Bestell-ID filtern |
|
||||
| `orderId` | string | Nein | Nach Auftrags-ID filtern |
|
||||
| `minTs` | number | Nein | Minimaler Zeitstempel \(Unix-Millisekunden\) |
|
||||
| `maxTs` | number | Nein | Maximaler Zeitstempel \(Unix-Millisekunden\) |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse \(1-1000, Standard: 100\) |
|
||||
@@ -273,15 +258,15 @@ Ihr Portfolio abrufen
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
Details einer bestimmten Marktserie nach Ticker abrufen
|
||||
Rufen Sie Details einer bestimmten Marktserie nach Ticker ab
|
||||
|
||||
#### Eingabe
|
||||
#### Input
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `seriesTicker` | string | Ja | Serien-Ticker |
|
||||
|
||||
#### Ausgabe
|
||||
#### Output
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
@@ -290,7 +275,7 @@ Details einer bestimmten Marktserie nach Ticker abrufen
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
Den aktuellen Status der Kalshi-Börse abrufen (Handel und Börsenaktivität)
|
||||
Ruft den aktuellen Status der Kalshi-Börse ab (Handels- und Börsenaktivität)
|
||||
|
||||
#### Eingabe
|
||||
|
||||
@@ -304,89 +289,6 @@ Den aktuellen Status der Kalshi-Börse abrufen (Handel und Börsenaktivität)
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Börsenstatus-Daten und Metadaten |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
Eine neue Order auf einem Kalshi-Prognosemarkt erstellen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Ja | Ihre Kalshi API-Schlüssel-ID |
|
||||
| `privateKey` | string | Ja | Ihr RSA Private Key \(PEM-Format\) |
|
||||
| `ticker` | string | Ja | Markt-Ticker \(z.B. KXBTC-24DEC31\) |
|
||||
| `side` | string | Ja | Seite der Order: 'yes' oder 'no' |
|
||||
| `action` | string | Ja | Aktionstyp: 'buy' oder 'sell' |
|
||||
| `count` | string | Ja | Anzahl der Kontrakte \(mindestens 1\) |
|
||||
| `type` | string | Nein | Ordertyp: 'limit' oder 'market' \(Standard: limit\) |
|
||||
| `yesPrice` | string | Nein | Yes-Preis in Cent \(1-99\) |
|
||||
| `noPrice` | string | Nein | No-Preis in Cent \(1-99\) |
|
||||
| `yesPriceDollars` | string | Nein | Yes-Preis in Dollar \(z.B. "0.56"\) |
|
||||
| `noPriceDollars` | string | Nein | No-Preis in Dollar \(z.B. "0.56"\) |
|
||||
| `clientOrderId` | string | Nein | Benutzerdefinierte Order-ID |
|
||||
| `expirationTs` | string | Nein | Unix-Zeitstempel für Order-Ablauf |
|
||||
| `timeInForce` | string | Nein | Gültigkeitsdauer: 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel' |
|
||||
| `buyMaxCost` | string | Nein | Maximale Kosten in Cent \(aktiviert automatisch fill_or_kill\) |
|
||||
| `postOnly` | string | Nein | Auf 'true' setzen für Maker-Only-Orders |
|
||||
| `reduceOnly` | string | Nein | Auf 'true' setzen für ausschließliche Positionsreduzierung |
|
||||
| `selfTradePreventionType` | string | Nein | Selbsthandel-Prävention: 'taker_at_cross' oder 'maker' |
|
||||
| `orderGroupId` | string | Nein | Zugehörige Ordergruppen-ID |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Erstellte Auftragsdaten |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
Einen bestehenden Auftrag auf Kalshi stornieren
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Ja | Ihre Kalshi API-Schlüssel-ID |
|
||||
| `privateKey` | string | Ja | Ihr RSA Private Key \(PEM-Format\) |
|
||||
| `orderId` | string | Ja | Die zu stornierende Auftrags-ID |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Stornierte Auftragsdaten |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
Preis oder Menge eines bestehenden Auftrags auf Kalshi ändern
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Ja | Ihre Kalshi API-Schlüssel-ID |
|
||||
| `privateKey` | string | Ja | Ihr RSA Private Key \(PEM-Format\) |
|
||||
| `orderId` | string | Ja | Die zu ändernde Auftrags-ID |
|
||||
| `ticker` | string | Ja | Markt-Ticker |
|
||||
| `side` | string | Ja | Seite des Auftrags: 'yes' oder 'no' |
|
||||
| `action` | string | Ja | Aktionstyp: 'buy' oder 'sell' |
|
||||
| `clientOrderId` | string | Ja | Die ursprüngliche vom Kunden angegebene Auftrags-ID |
|
||||
| `updatedClientOrderId` | string | Ja | Die neue vom Kunden angegebene Auftrags-ID nach der Änderung |
|
||||
| `count` | string | Nein | Aktualisierte Menge für den Auftrag |
|
||||
| `yesPrice` | string | Nein | Aktualisierter Ja-Preis in Cent \(1-99\) |
|
||||
| `noPrice` | string | Nein | Aktualisierter Nein-Preis in Cent \(1-99\) |
|
||||
| `yesPriceDollars` | string | Nein | Aktualisierter Ja-Preis in Dollar \(z.B. "0.56"\) |
|
||||
| `noPriceDollars` | string | Nein | Aktualisierter Nein-Preis in Dollar \(z.B. "0.56"\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Geänderte Auftragsdaten |
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Kategorie: `tools`
|
||||
|
||||
@@ -41,11 +41,11 @@ Rufen Sie eine Liste von Prognosemärkten von Polymarket mit optionaler Filterun
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | Nein | Nach geschlossenem Status filtern \(true/false\). Verwenden Sie false für nur aktive Märkte. |
|
||||
| `order` | string | Nein | Sortierfeld \(z.B. volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `order` | string | Nein | Sortierfeld \(z.B. id, volume, liquidity\) |
|
||||
| `ascending` | string | Nein | Sortierrichtung \(true für aufsteigend, false für absteigend\) |
|
||||
| `tagId` | string | Nein | Nach Tag-ID filtern |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(empfohlen: 25-50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe diese Anzahl an Ergebnissen\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe so viele Ergebnisse\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
@@ -80,12 +80,12 @@ Ruft eine Liste von Events von Polymarket mit optionaler Filterung ab
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | Nein | Nach geschlossenem Status filtern \(true/false\). Verwenden Sie false für nur aktive Events. |
|
||||
| `order` | string | Nein | Sortierfeld \(z.B. volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `closed` | string | Nein | Filtern nach geschlossenem Status \(true/false\). Verwenden Sie false für nur aktive Events. |
|
||||
| `order` | string | Nein | Sortierfeld \(z.B. id, volume\) |
|
||||
| `ascending` | string | Nein | Sortierrichtung \(true für aufsteigend, false für absteigend\) |
|
||||
| `tagId` | string | Nein | Nach Tag-ID filtern |
|
||||
| `tagId` | string | Nein | Filtern nach Tag-ID |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(empfohlen: 25-50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe diese Anzahl an Ergebnissen\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe so viele Ergebnisse\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
|
||||
@@ -120,82 +120,6 @@ Lesen Sie die neuesten Nachrichten aus Slack-Kanälen. Rufen Sie den Konversatio
|
||||
| --------- | ---- | ----------- |
|
||||
| `messages` | array | Array von Nachrichtenobjekten aus dem Kanal |
|
||||
|
||||
### `slack_list_channels`
|
||||
|
||||
Listet alle Kanäle in einem Slack-Workspace auf. Gibt öffentliche und private Kanäle zurück, auf die der Bot Zugriff hat.
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | Nein | Authentifizierungsmethode: oauth oder bot_token |
|
||||
| `botToken` | string | Nein | Bot-Token für benutzerdefinierten Bot |
|
||||
| `includePrivate` | boolean | Nein | Private Kanäle einschließen, in denen der Bot Mitglied ist \(Standard: true\) |
|
||||
| `excludeArchived` | boolean | Nein | Archivierte Kanäle ausschließen \(Standard: true\) |
|
||||
| `limit` | number | Nein | Maximale Anzahl der zurückzugebenden Kanäle \(Standard: 100, max: 200\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `channels` | array | Array von Kanalobjekten aus dem Workspace |
|
||||
|
||||
### `slack_list_members`
|
||||
|
||||
Listet alle Mitglieder (Benutzer-IDs) in einem Slack-Kanal auf. Verwenden Sie diese Funktion mit Get User Info, um IDs in Namen aufzulösen.
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | Nein | Authentifizierungsmethode: oauth oder bot_token |
|
||||
| `botToken` | string | Nein | Bot-Token für benutzerdefinierten Bot |
|
||||
| `channel` | string | Ja | Kanal-ID, aus der Mitglieder aufgelistet werden sollen |
|
||||
| `limit` | number | Nein | Maximale Anzahl der zurückzugebenden Mitglieder \(Standard: 100, max: 200\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `members` | array | Array von Benutzer-IDs, die Mitglieder des Kanals sind \(z.B. U1234567890\) |
|
||||
|
||||
### `slack_list_users`
|
||||
|
||||
Listet alle Benutzer in einem Slack-Workspace auf. Gibt Benutzerprofile mit Namen und Avataren zurück.
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | Nein | Authentifizierungsmethode: oauth oder bot_token |
|
||||
| `botToken` | string | Nein | Bot-Token für benutzerdefinierten Bot |
|
||||
| `includeDeleted` | boolean | Nein | Deaktivierte/gelöschte Benutzer einbeziehen \(Standard: false\) |
|
||||
| `limit` | number | Nein | Maximale Anzahl der zurückzugebenden Benutzer \(Standard: 100, max: 200\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | Array von Benutzerobjekten aus dem Workspace |
|
||||
|
||||
### `slack_get_user`
|
||||
|
||||
Ruft detaillierte Informationen über einen bestimmten Slack-Benutzer anhand seiner Benutzer-ID ab.
|
||||
|
||||
#### Eingabe
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | Nein | Authentifizierungsmethode: oauth oder bot_token |
|
||||
| `botToken` | string | Nein | Bot-Token für benutzerdefinierten Bot |
|
||||
| `userId` | string | Ja | Zu suchende Benutzer-ID \(z.B. U1234567890\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `user` | object | Detaillierte Benutzerinformationen |
|
||||
|
||||
### `slack_download`
|
||||
|
||||
Eine Datei von Slack herunterladen
|
||||
@@ -224,7 +148,7 @@ Eine zuvor vom Bot in Slack gesendete Nachricht aktualisieren
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | Nein | Authentifizierungsmethode: oauth oder bot_token |
|
||||
| `botToken` | string | Nein | Bot-Token für benutzerdefinierten Bot |
|
||||
| `botToken` | string | Nein | Bot-Token für Custom Bot |
|
||||
| `channel` | string | Ja | Kanal-ID, in dem die Nachricht gepostet wurde \(z.B. C1234567890\) |
|
||||
| `timestamp` | string | Ja | Zeitstempel der zu aktualisierenden Nachricht \(z.B. 1405894322.002768\) |
|
||||
| `text` | string | Ja | Neuer Nachrichtentext \(unterstützt Slack mrkdwn-Formatierung\) |
|
||||
@@ -235,7 +159,7 @@ Eine zuvor vom Bot in Slack gesendete Nachricht aktualisieren
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | object | Vollständiges aktualisiertes Nachrichtenobjekt mit allen von Slack zurückgegebenen Eigenschaften |
|
||||
| `content` | string | Erfolgsmeldung |
|
||||
| `metadata` | object | Aktualisierte Nachrichtenmetadaten |
|
||||
| `metadata` | object | Metadaten der aktualisierten Nachricht |
|
||||
|
||||
### `slack_delete_message`
|
||||
|
||||
@@ -246,7 +170,7 @@ Eine zuvor vom Bot in Slack gesendete Nachricht löschen
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | Nein | Authentifizierungsmethode: oauth oder bot_token |
|
||||
| `botToken` | string | Nein | Bot-Token für benutzerdefinierten Bot |
|
||||
| `botToken` | string | Nein | Bot-Token für Custom Bot |
|
||||
| `channel` | string | Ja | Kanal-ID, in dem die Nachricht gepostet wurde \(z.B. C1234567890\) |
|
||||
| `timestamp` | string | Ja | Zeitstempel der zu löschenden Nachricht \(z.B. 1405894322.002768\) |
|
||||
|
||||
@@ -259,7 +183,7 @@ Eine zuvor vom Bot in Slack gesendete Nachricht löschen
|
||||
|
||||
### `slack_add_reaction`
|
||||
|
||||
Eine Emoji-Reaktion zu einer Slack-Nachricht hinzufügen
|
||||
Emoji-Reaktion zu einer Slack-Nachricht hinzufügen
|
||||
|
||||
#### Eingabe
|
||||
|
||||
|
||||
@@ -149,7 +149,6 @@ Get the top pages of a target domain sorted by organic traffic. Returns page URL
|
||||
| `date` | string | No | Date for historical data in YYYY-MM-DD format \(defaults to today\) |
|
||||
| `limit` | number | No | Maximum number of results to return \(default: 100\) |
|
||||
| `offset` | number | No | Number of results to skip for pagination |
|
||||
| `select` | string | No | Comma-separated list of fields to return \(e.g., url,traffic,keywords,top_keyword,value\). Default: url,traffic,keywords,top_keyword,value |
|
||||
| `apiKey` | string | Yes | Ahrefs API Key |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
---
|
||||
title: Google Slides
|
||||
description: Read, write, and create presentations
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="google_slides"
|
||||
color="#E0E0E0"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Google Slides](https://slides.google.com) is a dynamic cloud-based presentation application that allows users to create, edit, collaborate on, and present slideshows in real-time. As part of Google's productivity suite, Google Slides offers a flexible platform for designing engaging presentations, collaborating with others, and sharing content seamlessly through the cloud.
|
||||
|
||||
Learn how to integrate the Google Slides tools in Sim to effortlessly manage presentations as part of your automated workflows. With Sim, you can read, write, create, and update Google Slides presentations directly through your agents and automated processes, making it easy to deliver up-to-date information, generate custom reports, or produce branded decks programmatically.
|
||||
|
||||
With Google Slides, you can:
|
||||
|
||||
- **Create and edit presentations**: Design visually appealing slides with themes, layouts, and multimedia content
|
||||
- **Collaborate in real-time**: Work simultaneously with teammates, comment, assign tasks, and receive live feedback on presentations
|
||||
- **Present anywhere**: Display presentations online or offline, share links, or publish to the web
|
||||
- **Add images and rich content**: Insert images, graphics, charts, and videos to make your presentations engaging
|
||||
- **Integrate with other services**: Connect seamlessly with Google Drive, Docs, Sheets, and other third-party tools
|
||||
- **Access from any device**: Use Google Slides on desktops, laptops, tablets, and mobile devices for maximum flexibility
|
||||
|
||||
In Sim, the Google Slides integration enables your agents to interact directly with presentation files programmatically. Automate tasks like reading slide content, inserting new slides or images, replacing text throughout a deck, generating new presentations, and retrieving slide thumbnails. This empowers you to scale content creation, keep presentations up-to-date, and embed them into automated document workflows. By connecting Sim with Google Slides, you facilitate AI-driven presentation management—making it easy to generate, update, or extract information from presentations without manual effort.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Google Slides into the workflow. Can read, write, create presentations, replace text, add slides, add images, and get thumbnails.
|
||||
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
### `google_slides_read`
|
||||
|
||||
Read content from a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation to read |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slides` | json | Array of slides with their content |
|
||||
| `metadata` | json | Presentation metadata including ID, title, and URL |
|
||||
|
||||
### `google_slides_write`
|
||||
|
||||
Write or update content in a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation to write to |
|
||||
| `content` | string | Yes | The content to write to the slide |
|
||||
| `slideIndex` | number | No | The index of the slide to write to \(defaults to first slide\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `updatedContent` | boolean | Indicates if presentation content was updated successfully |
|
||||
| `metadata` | json | Updated presentation metadata including ID, title, and URL |
|
||||
|
||||
### `google_slides_create`
|
||||
|
||||
Create a new Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | Yes | The title of the presentation to create |
|
||||
| `content` | string | No | The content to add to the first slide |
|
||||
| `folderSelector` | string | No | Select the folder to create the presentation in |
|
||||
| `folderId` | string | No | The ID of the folder to create the presentation in \(internal use\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `metadata` | json | Created presentation metadata including ID, title, and URL |
|
||||
|
||||
### `google_slides_replace_all_text`
|
||||
|
||||
Find and replace all occurrences of text throughout a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation |
|
||||
| `findText` | string | Yes | The text to find \(e.g., \{\{placeholder\}\}\) |
|
||||
| `replaceText` | string | Yes | The text to replace with |
|
||||
| `matchCase` | boolean | No | Whether the search should be case-sensitive \(default: true\) |
|
||||
| `pageObjectIds` | string | No | Comma-separated list of slide object IDs to limit replacements to specific slides \(leave empty for all slides\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `occurrencesChanged` | number | Number of text occurrences that were replaced |
|
||||
| `metadata` | json | Operation metadata including presentation ID and URL |
|
||||
|
||||
### `google_slides_add_slide`
|
||||
|
||||
Add a new slide to a Google Slides presentation with a specified layout
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation |
|
||||
| `layout` | string | No | The predefined layout for the slide \(BLANK, TITLE, TITLE_AND_BODY, TITLE_ONLY, SECTION_HEADER, etc.\). Defaults to BLANK. |
|
||||
| `insertionIndex` | number | No | The optional zero-based index indicating where to insert the slide. If not specified, the slide is added at the end. |
|
||||
| `placeholderIdMappings` | string | No | JSON array of placeholder mappings to assign custom object IDs to placeholders. Format: \[\{"layoutPlaceholder":\{"type":"TITLE"\},"objectId":"custom_title_id"\}\] |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slideId` | string | The object ID of the newly created slide |
|
||||
| `metadata` | json | Operation metadata including presentation ID, layout, and URL |
|
||||
|
||||
### `google_slides_add_image`
|
||||
|
||||
Insert an image into a specific slide in a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation |
|
||||
| `pageObjectId` | string | Yes | The object ID of the slide/page to add the image to |
|
||||
| `imageUrl` | string | Yes | The publicly accessible URL of the image \(must be PNG, JPEG, or GIF, max 50MB\) |
|
||||
| `width` | number | No | Width of the image in points \(default: 300\) |
|
||||
| `height` | number | No | Height of the image in points \(default: 200\) |
|
||||
| `positionX` | number | No | X position from the left edge in points \(default: 100\) |
|
||||
| `positionY` | number | No | Y position from the top edge in points \(default: 100\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `imageId` | string | The object ID of the newly created image |
|
||||
| `metadata` | json | Operation metadata including presentation ID and image URL |
|
||||
|
||||
### `google_slides_get_thumbnail`
|
||||
|
||||
Generate a thumbnail image of a specific slide in a Google Slides presentation
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Yes | The ID of the presentation |
|
||||
| `pageObjectId` | string | Yes | The object ID of the slide/page to get a thumbnail for |
|
||||
| `thumbnailSize` | string | No | The size of the thumbnail: SMALL \(200px\), MEDIUM \(800px\), or LARGE \(1600px\). Defaults to MEDIUM. |
|
||||
| `mimeType` | string | No | The MIME type of the thumbnail image: PNG or GIF. Defaults to PNG. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `contentUrl` | string | URL to the thumbnail image \(valid for 30 minutes\) |
|
||||
| `width` | number | Width of the thumbnail in pixels |
|
||||
| `height` | number | Height of the thumbnail in pixels |
|
||||
| `metadata` | json | Operation metadata including presentation ID and page object ID |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
- Category: `tools`
|
||||
- Type: `google_slides`
|
||||
@@ -364,8 +364,6 @@ Update an existing schedule in incident.io
|
||||
| `id` | string | Yes | The ID of the schedule to update |
|
||||
| `name` | string | No | New name for the schedule |
|
||||
| `timezone` | string | No | New timezone for the schedule \(e.g., America/New_York\) |
|
||||
| `config` | string | No | Schedule configuration as JSON string with rotations. Example: \{"rotations": \[\{"name": "Primary", "users": \[\{"id": "user_id"\}\], "handover_start_at": "2024-01-01T09:00:00Z", "handovers": \[\{"interval": 1, "interval_type": "weekly"\}\]\}\]\} |
|
||||
| `Example` | string | No | No description |
|
||||
|
||||
#### Output
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ Create or update a company in Intercom
|
||||
| `plan` | string | No | The company plan name |
|
||||
| `size` | number | No | The number of employees in the company |
|
||||
| `industry` | string | No | The industry the company operates in |
|
||||
| `monthly_spend` | number | No | How much revenue the company generates for your business. Note: This field truncates floats to whole integers \(e.g., 155.98 becomes 155\) |
|
||||
| `monthly_spend` | number | No | How much revenue the company generates for your business |
|
||||
| `custom_attributes` | string | No | Custom attributes as JSON object |
|
||||
|
||||
#### Output
|
||||
@@ -199,7 +199,7 @@ Retrieve a single company by ID from Intercom
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
List all companies from Intercom with pagination support. Note: This endpoint has a limit of 10,000 companies that can be returned using pagination. For datasets larger than 10,000 companies, use the Scroll API instead.
|
||||
List all companies from Intercom with pagination support
|
||||
|
||||
#### Input
|
||||
|
||||
@@ -262,7 +262,7 @@ Reply to a conversation as an admin in Intercom
|
||||
| `conversationId` | string | Yes | Conversation ID to reply to |
|
||||
| `message_type` | string | Yes | Message type: "comment" or "note" |
|
||||
| `body` | string | Yes | The text body of the reply |
|
||||
| `admin_id` | string | No | The ID of the admin authoring the reply. If not provided, a default admin \(Operator/Fin\) will be used. |
|
||||
| `admin_id` | string | Yes | The ID of the admin authoring the reply |
|
||||
| `attachment_urls` | string | No | Comma-separated list of image URLs \(max 10\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Kalshi
|
||||
description: Access prediction markets and trade on Kalshi
|
||||
description: Access prediction markets data from Kalshi
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -28,7 +28,7 @@ By using these unified tools and endpoints, you can seamlessly incorporate Kalsh
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, exchange status, and place/cancel/amend trades.
|
||||
Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, and exchange status.
|
||||
|
||||
|
||||
|
||||
@@ -175,34 +175,16 @@ Retrieve your orders from Kalshi with optional filtering
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Orders data and metadata |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
Retrieve details of a specific order by ID from Kalshi
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Yes | Your Kalshi API Key ID |
|
||||
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
|
||||
| `orderId` | string | Yes | The order ID to retrieve |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Order data |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
Retrieve the orderbook (yes and no bids) for a specific market
|
||||
Retrieve the orderbook (bids and asks) for a specific market
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | Yes | Market ticker \(e.g., KXBTC-24DEC31\) |
|
||||
| `depth` | number | No | Number of price levels to return per side |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -213,12 +195,15 @@ Retrieve the orderbook (yes and no bids) for a specific market
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
Retrieve recent trades across all markets
|
||||
Retrieve recent trades across all markets or for a specific market
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | No | Filter by market ticker |
|
||||
| `minTs` | number | No | Minimum timestamp \(Unix milliseconds\) |
|
||||
| `maxTs` | number | No | Maximum timestamp \(Unix milliseconds\) |
|
||||
| `limit` | string | No | Number of results \(1-1000, default: 100\) |
|
||||
| `cursor` | string | No | Pagination cursor for next page |
|
||||
|
||||
@@ -239,9 +224,9 @@ Retrieve OHLC candlestick data for a specific market
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `seriesTicker` | string | Yes | Series ticker |
|
||||
| `ticker` | string | Yes | Market ticker \(e.g., KXBTC-24DEC31\) |
|
||||
| `startTs` | number | Yes | Start timestamp \(Unix seconds\) |
|
||||
| `endTs` | number | Yes | End timestamp \(Unix seconds\) |
|
||||
| `periodInterval` | number | Yes | Period interval: 1 \(1min\), 60 \(1hour\), or 1440 \(1day\) |
|
||||
| `startTs` | number | No | Start timestamp \(Unix milliseconds\) |
|
||||
| `endTs` | number | No | End timestamp \(Unix milliseconds\) |
|
||||
| `periodInterval` | number | No | Period interval: 1 \(1min\), 60 \(1hour\), or 1440 \(1day\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -307,89 +292,6 @@ Retrieve the current status of the Kalshi exchange (trading and exchange activit
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Exchange status data and metadata |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
Create a new order on a Kalshi prediction market
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Yes | Your Kalshi API Key ID |
|
||||
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
|
||||
| `ticker` | string | Yes | Market ticker \(e.g., KXBTC-24DEC31\) |
|
||||
| `side` | string | Yes | Side of the order: 'yes' or 'no' |
|
||||
| `action` | string | Yes | Action type: 'buy' or 'sell' |
|
||||
| `count` | string | Yes | Number of contracts \(minimum 1\) |
|
||||
| `type` | string | No | Order type: 'limit' or 'market' \(default: limit\) |
|
||||
| `yesPrice` | string | No | Yes price in cents \(1-99\) |
|
||||
| `noPrice` | string | No | No price in cents \(1-99\) |
|
||||
| `yesPriceDollars` | string | No | Yes price in dollars \(e.g., "0.56"\) |
|
||||
| `noPriceDollars` | string | No | No price in dollars \(e.g., "0.56"\) |
|
||||
| `clientOrderId` | string | No | Custom order identifier |
|
||||
| `expirationTs` | string | No | Unix timestamp for order expiration |
|
||||
| `timeInForce` | string | No | Time in force: 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel' |
|
||||
| `buyMaxCost` | string | No | Maximum cost in cents \(auto-enables fill_or_kill\) |
|
||||
| `postOnly` | string | No | Set to 'true' for maker-only orders |
|
||||
| `reduceOnly` | string | No | Set to 'true' for position reduction only |
|
||||
| `selfTradePreventionType` | string | No | Self-trade prevention: 'taker_at_cross' or 'maker' |
|
||||
| `orderGroupId` | string | No | Associated order group ID |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created order data |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
Cancel an existing order on Kalshi
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Yes | Your Kalshi API Key ID |
|
||||
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
|
||||
| `orderId` | string | Yes | The order ID to cancel |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Canceled order data |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
Modify the price or quantity of an existing order on Kalshi
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Yes | Your Kalshi API Key ID |
|
||||
| `privateKey` | string | Yes | Your RSA Private Key \(PEM format\) |
|
||||
| `orderId` | string | Yes | The order ID to amend |
|
||||
| `ticker` | string | Yes | Market ticker |
|
||||
| `side` | string | Yes | Side of the order: 'yes' or 'no' |
|
||||
| `action` | string | Yes | Action type: 'buy' or 'sell' |
|
||||
| `clientOrderId` | string | Yes | The original client-specified order ID |
|
||||
| `updatedClientOrderId` | string | Yes | The new client-specified order ID after amendment |
|
||||
| `count` | string | No | Updated quantity for the order |
|
||||
| `yesPrice` | string | No | Updated yes price in cents \(1-99\) |
|
||||
| `noPrice` | string | No | Updated no price in cents \(1-99\) |
|
||||
| `yesPriceDollars` | string | No | Updated yes price in dollars \(e.g., "0.56"\) |
|
||||
| `noPriceDollars` | string | No | Updated no price in dollars \(e.g., "0.56"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Amended order data |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"google_forms",
|
||||
"google_search",
|
||||
"google_sheets",
|
||||
"google_slides",
|
||||
"google_vault",
|
||||
"grafana",
|
||||
"hubspot",
|
||||
|
||||
@@ -44,7 +44,7 @@ Retrieve a list of prediction markets from Polymarket with optional filtering
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | No | Filter by closed status \(true/false\). Use false for active markets only. |
|
||||
| `order` | string | No | Sort field \(e.g., volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `order` | string | No | Sort field \(e.g., id, volume, liquidity\) |
|
||||
| `ascending` | string | No | Sort direction \(true for ascending, false for descending\) |
|
||||
| `tagId` | string | No | Filter by tag ID |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
@@ -84,7 +84,7 @@ Retrieve a list of events from Polymarket with optional filtering
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | No | Filter by closed status \(true/false\). Use false for active events only. |
|
||||
| `order` | string | No | Sort field \(e.g., volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `order` | string | No | Sort field \(e.g., id, volume\) |
|
||||
| `ascending` | string | No | Sort direction \(true for ascending, false for descending\) |
|
||||
| `tagId` | string | No | Filter by tag ID |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
|
||||
@@ -140,13 +140,12 @@ Obtén las páginas principales de un dominio objetivo ordenadas por tráfico or
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `target` | string | Sí | El dominio objetivo a analizar |
|
||||
| `target` | string | Sí | El dominio objetivo para analizar |
|
||||
| `country` | string | No | Código de país para datos de tráfico \(p. ej., us, gb, de\). Predeterminado: us |
|
||||
| `mode` | string | No | Modo de análisis: domain \(dominio completo\), prefix \(prefijo URL\), subdomains \(incluir todos los subdominios\) |
|
||||
| `date` | string | No | Fecha para datos históricos en formato AAAA-MM-DD \(por defecto es hoy\) |
|
||||
| `date` | string | No | Fecha para datos históricos en formato AAAA-MM-DD \(predeterminado: hoy\) |
|
||||
| `limit` | number | No | Número máximo de resultados a devolver \(predeterminado: 100\) |
|
||||
| `offset` | number | No | Número de resultados a omitir para paginación |
|
||||
| `select` | string | No | Lista separada por comas de campos a devolver \(p. ej., url,traffic,keywords,top_keyword,value\). Predeterminado: url,traffic,keywords,top_keyword,value |
|
||||
| `apiKey` | string | Sí | Clave API de Ahrefs |
|
||||
|
||||
#### Salida
|
||||
|
||||
@@ -42,9 +42,9 @@ Crear un nuevo evento en Google Calendar
|
||||
| `summary` | string | Sí | Título/resumen del evento |
|
||||
| `description` | string | No | Descripción del evento |
|
||||
| `location` | string | No | Ubicación del evento |
|
||||
| `startDateTime` | string | Sí | Fecha y hora de inicio. DEBE incluir el desplazamiento de zona horaria \(p. ej., 2025-06-03T10:00:00-08:00\) O proporcionar el parámetro timeZone |
|
||||
| `endDateTime` | string | Sí | Fecha y hora de finalización. DEBE incluir el desplazamiento de zona horaria \(p. ej., 2025-06-03T11:00:00-08:00\) O proporcionar el parámetro timeZone |
|
||||
| `timeZone` | string | No | Zona horaria \(p. ej., America/Los_Angeles\). Obligatorio si la fecha y hora no incluye desplazamiento. Por defecto es America/Los_Angeles si no se proporciona. |
|
||||
| `startDateTime` | string | Sí | Fecha y hora de inicio \(formato RFC3339, p. ej., 2025-06-03T10:00:00-08:00\) |
|
||||
| `endDateTime` | string | Sí | Fecha y hora de finalización \(formato RFC3339, p. ej., 2025-06-03T11:00:00-08:00\) |
|
||||
| `timeZone` | string | No | Zona horaria \(p. ej., America/Los_Angeles\) |
|
||||
| `attendees` | array | No | Array de direcciones de correo electrónico de los asistentes |
|
||||
| `sendUpdates` | string | No | Cómo enviar actualizaciones a los asistentes: all, externalOnly o none |
|
||||
|
||||
|
||||
@@ -108,10 +108,10 @@ Listar archivos y carpetas en Google Drive
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `folderSelector` | string | No | Selecciona la carpeta desde la que listar archivos |
|
||||
| `folderSelector` | string | No | Seleccionar la carpeta desde la que listar archivos |
|
||||
| `folderId` | string | No | El ID de la carpeta desde la que listar archivos \(uso interno\) |
|
||||
| `query` | string | No | Término de búsqueda para filtrar archivos por nombre \(p. ej. "presupuesto" encuentra archivos con "presupuesto" en el nombre\). NO uses la sintaxis de consulta de Google Drive aquí - solo proporciona un término de búsqueda simple. |
|
||||
| `pageSize` | number | No | El número máximo de archivos a devolver \(predeterminado: 100\) |
|
||||
| `query` | string | No | Una consulta para filtrar los archivos |
|
||||
| `pageSize` | number | No | El número de archivos a devolver |
|
||||
| `pageToken` | string | No | El token de página para usar en la paginación |
|
||||
|
||||
#### Salida
|
||||
|
||||
@@ -87,9 +87,9 @@ Leer datos de una hoja de cálculo de Google Sheets
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | Sí | El ID de la hoja de cálculo \(se encuentra en la URL: docs.google.com/spreadsheets/d/\{SPREADSHEET_ID\}/edit\). |
|
||||
| `range` | string | No | El rango en notación A1 para leer \(por ejemplo, "Sheet1!A1:D10", "A1:B5"\). Si no se especifica, por defecto es la primera hoja A1:Z1000. |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `spreadsheetId` | string | Sí | El ID de la hoja de cálculo de la que leer |
|
||||
| `range` | string | No | El rango de celdas del que leer |
|
||||
|
||||
#### Salida
|
||||
|
||||
@@ -105,10 +105,10 @@ Escribir datos en una hoja de cálculo de Google Sheets
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | Sí | El ID de la hoja de cálculo |
|
||||
| `range` | string | No | El rango en notación A1 donde escribir \(por ejemplo, "Sheet1!A1:D10", "A1:B5"\) |
|
||||
| `values` | array | Sí | Los datos a escribir como un array 2D \(por ejemplo, \[\["Nombre", "Edad"\], \["Alice", 30\], \["Bob", 25\]\]\) o array de objetos. |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `spreadsheetId` | string | Sí | El ID de la hoja de cálculo en la que escribir |
|
||||
| `range` | string | No | El rango de celdas en el que escribir |
|
||||
| `values` | array | Sí | Los datos a escribir en la hoja de cálculo |
|
||||
| `valueInputOption` | string | No | El formato de los datos a escribir |
|
||||
| `includeValuesInResponse` | boolean | No | Si se deben incluir los valores escritos en la respuesta |
|
||||
|
||||
@@ -129,10 +129,10 @@ Actualizar datos en una hoja de cálculo de Google Sheets
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `spreadsheetId` | string | Sí | El ID de la hoja de cálculo a actualizar |
|
||||
| `range` | string | No | El rango en notación A1 para actualizar \(por ejemplo, "Sheet1!A1:D10", "A1:B5"\) |
|
||||
| `values` | array | Sí | Los datos para actualizar como un array 2D \(por ejemplo, \[\["Nombre", "Edad"\], \["Alice", 30\]\]\) o array de objetos. |
|
||||
| `range` | string | No | El rango de celdas a actualizar |
|
||||
| `values` | array | Sí | Los datos para actualizar en la hoja de cálculo |
|
||||
| `valueInputOption` | string | No | El formato de los datos a actualizar |
|
||||
| `includeValuesInResponse` | boolean | No | Si se deben incluir los valores actualizados en la respuesta |
|
||||
|
||||
@@ -153,12 +153,12 @@ Añadir datos al final de una hoja de cálculo de Google Sheets
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | Sí | El ID de la hoja de cálculo a la que añadir datos |
|
||||
| `range` | string | No | El rango en notación A1 después del cual añadir datos (ej. "Hoja1", "Hoja1!A:D") |
|
||||
| `values` | array | Sí | Los datos a añadir como un array 2D (ej. [["Alice", 30], ["Bob", 25]]) o array de objetos. |
|
||||
| `range` | string | No | El rango de celdas después del cual añadir datos |
|
||||
| `values` | array | Sí | Los datos a añadir a la hoja de cálculo |
|
||||
| `valueInputOption` | string | No | El formato de los datos a añadir |
|
||||
| `insertDataOption` | string | No | Cómo insertar los datos (OVERWRITE o INSERT_ROWS) |
|
||||
| `insertDataOption` | string | No | Cómo insertar los datos \(OVERWRITE o INSERT_ROWS\) |
|
||||
| `includeValuesInResponse` | boolean | No | Si se deben incluir los valores añadidos en la respuesta |
|
||||
|
||||
#### Salida
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
title: Google Slides
|
||||
description: Lee, escribe y crea presentaciones
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="google_slides"
|
||||
color="#E0E0E0"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Google Slides](https://slides.google.com) es una aplicación dinámica de presentaciones basada en la nube que permite a los usuarios crear, editar, colaborar y presentar diapositivas en tiempo real. Como parte del conjunto de productividad de Google, Google Slides ofrece una plataforma flexible para diseñar presentaciones atractivas, colaborar con otros y compartir contenido sin problemas a través de la nube.
|
||||
|
||||
Aprende cómo integrar las herramientas de Google Slides en Sim para gestionar presentaciones sin esfuerzo como parte de tus flujos de trabajo automatizados. Con Sim, puedes leer, escribir, crear y actualizar presentaciones de Google Slides directamente a través de tus agentes y procesos automatizados, facilitando la entrega de información actualizada, la generación de informes personalizados o la producción de presentaciones corporativas de forma programática.
|
||||
|
||||
Con Google Slides, puedes:
|
||||
|
||||
- **Crear y editar presentaciones**: Diseña diapositivas visualmente atractivas con temas, diseños y contenido multimedia
|
||||
- **Colaborar en tiempo real**: Trabaja simultáneamente con compañeros, comenta, asigna tareas y recibe comentarios en vivo sobre las presentaciones
|
||||
- **Presentar en cualquier lugar**: Muestra presentaciones en línea o sin conexión, comparte enlaces o publica en la web
|
||||
- **Añadir imágenes y contenido enriquecido**: Inserta imágenes, gráficos, diagramas y videos para hacer tus presentaciones más atractivas
|
||||
- **Integrar con otros servicios**: Conéctate sin problemas con Google Drive, Docs, Sheets y otras herramientas de terceros
|
||||
- **Acceder desde cualquier dispositivo**: Usa Google Slides en ordenadores de escritorio, portátiles, tabletas y dispositivos móviles para máxima flexibilidad
|
||||
|
||||
En Sim, la integración con Google Slides permite a tus agentes interactuar directamente con archivos de presentación de forma programática. Automatiza tareas como leer el contenido de diapositivas, insertar nuevas diapositivas o imágenes, reemplazar texto en toda una presentación, generar nuevas presentaciones y recuperar miniaturas de diapositivas. Esto te permite escalar la creación de contenido, mantener las presentaciones actualizadas e incorporarlas en flujos de trabajo de documentos automatizados. Al conectar Sim con Google Slides, facilitas la gestión de presentaciones impulsada por IA, haciendo que sea fácil generar, actualizar o extraer información de presentaciones sin esfuerzo manual.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## Instrucciones de uso
|
||||
|
||||
Integra Google Slides en el flujo de trabajo. Puede leer, escribir, crear presentaciones, reemplazar texto, añadir diapositivas, añadir imágenes y obtener miniaturas.
|
||||
|
||||
## Herramientas
|
||||
|
||||
### `google_slides_read`
|
||||
|
||||
Leer contenido de una presentación de Google Slides
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Sí | El ID de la presentación a leer |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slides` | json | Array de diapositivas con su contenido |
|
||||
| `metadata` | json | Metadatos de la presentación incluyendo ID, título y URL |
|
||||
|
||||
### `google_slides_write`
|
||||
|
||||
Escribir o actualizar contenido en una presentación de Google Slides
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Sí | El ID de la presentación en la que escribir |
|
||||
| `content` | string | Sí | El contenido a escribir en la diapositiva |
|
||||
| `slideIndex` | number | No | El índice de la diapositiva en la que escribir \(por defecto, primera diapositiva\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `updatedContent` | boolean | Indica si el contenido de la presentación se actualizó correctamente |
|
||||
| `metadata` | json | Metadatos de la presentación actualizada incluyendo ID, título y URL |
|
||||
|
||||
### `google_slides_create`
|
||||
|
||||
Crear una nueva presentación de Google Slides
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | Sí | El título de la presentación a crear |
|
||||
| `content` | string | No | El contenido a añadir a la primera diapositiva |
|
||||
| `folderSelector` | string | No | Seleccionar la carpeta donde crear la presentación |
|
||||
| `folderId` | string | No | El ID de la carpeta donde crear la presentación \(uso interno\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `metadata` | json | Metadatos de la presentación creada, incluyendo ID, título y URL |
|
||||
|
||||
### `google_slides_replace_all_text`
|
||||
|
||||
Buscar y reemplazar todas las ocurrencias de texto en una presentación de Google Slides
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Sí | El ID de la presentación |
|
||||
| `findText` | string | Sí | El texto a buscar \(p. ej., \{\{placeholder\}\}\) |
|
||||
| `replaceText` | string | Sí | El texto con el que reemplazar |
|
||||
| `matchCase` | boolean | No | Si la búsqueda debe distinguir entre mayúsculas y minúsculas \(predeterminado: true\) |
|
||||
| `pageObjectIds` | string | No | Lista separada por comas de IDs de objetos de diapositivas para limitar los reemplazos a diapositivas específicas \(dejar vacío para todas las diapositivas\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `occurrencesChanged` | number | Número de ocurrencias de texto que fueron reemplazadas |
|
||||
| `metadata` | json | Metadatos de la operación, incluyendo ID de la presentación y URL |
|
||||
|
||||
### `google_slides_add_slide`
|
||||
|
||||
Añadir una nueva diapositiva a una presentación de Google Slides con un diseño específico
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Sí | El ID de la presentación |
|
||||
| `layout` | string | No | El diseño predefinido para la diapositiva \(BLANK, TITLE, TITLE_AND_BODY, TITLE_ONLY, SECTION_HEADER, etc.\). Por defecto es BLANK. |
|
||||
| `insertionIndex` | number | No | El índice opcional basado en cero que indica dónde insertar la diapositiva. Si no se especifica, la diapositiva se añade al final. |
|
||||
| `placeholderIdMappings` | string | No | Array JSON de mapeos de marcadores de posición para asignar IDs de objeto personalizados a los marcadores. Formato: \[\{"layoutPlaceholder":\{"type":"TITLE"\},"objectId":"custom_title_id"\}\] |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slideId` | string | El ID del objeto de la diapositiva recién creada |
|
||||
| `metadata` | json | Metadatos de la operación incluyendo ID de la presentación, diseño y URL |
|
||||
|
||||
### `google_slides_add_image`
|
||||
|
||||
Insertar una imagen en una diapositiva específica de una presentación de Google Slides
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Sí | El ID de la presentación |
|
||||
| `pageObjectId` | string | Sí | El ID del objeto de la diapositiva/página donde se añadirá la imagen |
|
||||
| `imageUrl` | string | Sí | La URL de acceso público de la imagen \(debe ser PNG, JPEG o GIF, máximo 50MB\) |
|
||||
| `width` | number | No | Ancho de la imagen en puntos \(predeterminado: 300\) |
|
||||
| `height` | number | No | Altura de la imagen en puntos \(predeterminado: 200\) |
|
||||
| `positionX` | number | No | Posición X desde el borde izquierdo en puntos \(predeterminado: 100\) |
|
||||
| `positionY` | number | No | Posición Y desde el borde superior en puntos \(predeterminado: 100\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `imageId` | string | El ID del objeto de la imagen recién creada |
|
||||
| `metadata` | json | Metadatos de la operación incluyendo ID de la presentación y URL de la imagen |
|
||||
|
||||
### `google_slides_get_thumbnail`
|
||||
|
||||
Generar una imagen en miniatura de una diapositiva específica en una presentación de Google Slides
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Sí | El ID de la presentación |
|
||||
| `pageObjectId` | string | Sí | El ID del objeto de la diapositiva/página para obtener una miniatura |
|
||||
| `thumbnailSize` | string | No | El tamaño de la miniatura: SMALL \(200px\), MEDIUM \(800px\), o LARGE \(1600px\). Por defecto es MEDIUM. |
|
||||
| `mimeType` | string | No | El tipo MIME de la imagen en miniatura: PNG o GIF. Por defecto es PNG. |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `contentUrl` | string | URL de la imagen en miniatura \(válida durante 30 minutos\) |
|
||||
| `width` | number | Ancho de la miniatura en píxeles |
|
||||
| `height` | number | Alto de la miniatura en píxeles |
|
||||
| `metadata` | json | Metadatos de la operación incluyendo el ID de la presentación y el ID del objeto de la página |
|
||||
|
||||
## Notas
|
||||
|
||||
- Categoría: `tools`
|
||||
- Tipo: `google_slides`
|
||||
@@ -357,12 +357,10 @@ Actualizar un horario existente en incident.io
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Sí | Clave API de incident.io |
|
||||
| `id` | string | Sí | El ID del horario a actualizar |
|
||||
| `name` | string | No | Nuevo nombre para el horario |
|
||||
| `timezone` | string | No | Nueva zona horaria para el horario \(p. ej., America/New_York\) |
|
||||
| `config` | string | No | Configuración del horario como cadena JSON con rotaciones. Ejemplo: \{"rotations": \[\{"name": "Primary", "users": \[\{"id": "user_id"\}\], "handover_start_at": "2024-01-01T09:00:00Z", "handovers": \[\{"interval": 1, "interval_type": "weekly"\}\]\}\]\} |
|
||||
| `Example` | string | No | Sin descripción |
|
||||
| `apiKey` | cadena | Sí | Clave API de incident.io |
|
||||
| `id` | cadena | Sí | El ID del horario a actualizar |
|
||||
| `name` | cadena | No | Nuevo nombre para el horario |
|
||||
| `timezone` | cadena | No | Nueva zona horaria para el horario \(p. ej., America/New_York\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ Crear o actualizar una empresa en Intercom
|
||||
| `plan` | string | No | El nombre del plan de la empresa |
|
||||
| `size` | number | No | El número de empleados en la empresa |
|
||||
| `industry` | string | No | El sector en el que opera la empresa |
|
||||
| `monthly_spend` | number | No | Cuántos ingresos genera la empresa para tu negocio. Nota: Este campo trunca los decimales a números enteros \(por ejemplo, 155.98 se convierte en 155\) |
|
||||
| `monthly_spend` | number | No | Cuántos ingresos genera la empresa para tu negocio |
|
||||
| `custom_attributes` | string | No | Atributos personalizados como objeto JSON |
|
||||
|
||||
#### Salida
|
||||
@@ -196,7 +196,7 @@ Recuperar una única empresa por ID desde Intercom
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
Lista todas las empresas de Intercom con soporte de paginación. Nota: Este endpoint tiene un límite de 10,000 empresas que pueden ser devueltas usando paginación. Para conjuntos de datos mayores a 10,000 empresas, usa la API Scroll en su lugar.
|
||||
Listar todas las empresas de Intercom con soporte de paginación
|
||||
|
||||
#### Entrada
|
||||
|
||||
@@ -259,8 +259,8 @@ Responder a una conversación como administrador en Intercom
|
||||
| `conversationId` | string | Sí | ID de la conversación a la que responder |
|
||||
| `message_type` | string | Sí | Tipo de mensaje: "comment" o "note" |
|
||||
| `body` | string | Sí | El texto del cuerpo de la respuesta |
|
||||
| `admin_id` | string | No | El ID del administrador que escribe la respuesta. Si no se proporciona, se utilizará un administrador predeterminado \(Operator/Fin\). |
|
||||
| `attachment_urls` | string | No | Lista separada por comas de URLs de imágenes \(máximo 10\) |
|
||||
| `admin_id` | string | Sí | El ID del administrador que escribe la respuesta |
|
||||
| `attachment_urls` | string | No | Lista separada por comas de URLs de imágenes (máx. 10) |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Kalshi
|
||||
description: Accede a mercados de predicción y opera en Kalshi
|
||||
description: Accede a datos de mercados de predicción de Kalshi
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -27,7 +27,7 @@ Al utilizar estas herramientas y puntos de acceso unificados, puedes incorporar
|
||||
|
||||
## Instrucciones de uso
|
||||
|
||||
Integra los mercados de predicción de Kalshi en el flujo de trabajo. Puede obtener mercados, mercado específico, eventos, evento específico, saldo, posiciones, órdenes, libro de órdenes, operaciones, gráficos de velas, ejecuciones, series, estado del intercambio y realizar/cancelar/modificar operaciones.
|
||||
Integra los mercados de predicción de Kalshi en el flujo de trabajo. Puede obtener mercados, mercado, eventos, evento, saldo, posiciones, órdenes, libro de órdenes, operaciones, velas, ejecuciones, series y estado del mercado.
|
||||
|
||||
## Herramientas
|
||||
|
||||
@@ -172,50 +172,35 @@ Recupera tus órdenes de Kalshi con filtrado opcional
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de órdenes y metadatos |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
Recupera detalles de una orden específica por ID desde Kalshi
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Sí | Tu ID de clave API de Kalshi |
|
||||
| `privateKey` | string | Sí | Tu clave privada RSA \(formato PEM\) |
|
||||
| `orderId` | string | Sí | El ID de la orden a recuperar |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la orden |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
Recupera el libro de órdenes (ofertas de sí y no) para un mercado específico
|
||||
Recupera el libro de órdenes (ofertas de compra y venta) para un mercado específico
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | Sí | Ticker del mercado \(p. ej., KXBTC-24DEC31\) |
|
||||
| `depth` | number | No | Número de niveles de precio a devolver por lado |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del libro de órdenes y metadatos |
|
||||
| `output` | object | Datos y metadatos del libro de órdenes |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
Recupera operaciones recientes de todos los mercados
|
||||
Recuperar operaciones recientes en todos los mercados o para un mercado específico
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | No | Filtrar por ticker de mercado |
|
||||
| `minTs` | number | No | Marca de tiempo mínima \(Unix en milisegundos\) |
|
||||
| `maxTs` | number | No | Marca de tiempo máxima \(Unix en milisegundos\) |
|
||||
| `limit` | string | No | Número de resultados \(1-1000, predeterminado: 100\) |
|
||||
| `cursor` | string | No | Cursor de paginación para la siguiente página |
|
||||
|
||||
@@ -224,44 +209,44 @@ Recupera operaciones recientes de todos los mercados
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de operaciones y metadatos |
|
||||
| `output` | object | Datos y metadatos de operaciones |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
Obtener datos de velas OHLC para un mercado específico
|
||||
Recuperar datos de velas OHLC para un mercado específico
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `seriesTicker` | string | Sí | Ticker de serie |
|
||||
| `ticker` | string | Sí | Ticker del mercado \(p. ej., KXBTC-24DEC31\) |
|
||||
| `startTs` | number | Sí | Marca de tiempo inicial \(segundos Unix\) |
|
||||
| `endTs` | number | Sí | Marca de tiempo final \(segundos Unix\) |
|
||||
| `periodInterval` | number | Sí | Intervalo de período: 1 \(1min\), 60 \(1hora\), o 1440 \(1día\) |
|
||||
| `ticker` | string | Sí | Ticker de mercado \(p. ej., KXBTC-24DEC31\) |
|
||||
| `startTs` | number | No | Marca de tiempo de inicio \(Unix en milisegundos\) |
|
||||
| `endTs` | number | No | Marca de tiempo de fin \(Unix en milisegundos\) |
|
||||
| `periodInterval` | number | No | Intervalo de período: 1 \(1min\), 60 \(1hora\), o 1440 \(1día\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de velas y metadatos |
|
||||
| `output` | object | Datos de velas e información adicional |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
Recuperar tu portafolio
|
||||
Recuperar tu cartera
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Sí | Tu ID de clave API de Kalshi |
|
||||
| `privateKey` | string | Sí | Tu clave privada RSA \(formato PEM\) |
|
||||
| `privateKey` | string | Sí | Tu clave privada RSA (formato PEM) |
|
||||
| `ticker` | string | No | Filtrar por ticker de mercado |
|
||||
| `orderId` | string | No | Filtrar por ID de orden |
|
||||
| `minTs` | number | No | Marca de tiempo mínima \(milisegundos Unix\) |
|
||||
| `maxTs` | number | No | Marca de tiempo máxima \(milisegundos Unix\) |
|
||||
| `limit` | string | No | Número de resultados \(1-1000, predeterminado: 100\) |
|
||||
| `minTs` | number | No | Marca de tiempo mínima (milisegundos Unix) |
|
||||
| `maxTs` | number | No | Marca de tiempo máxima (milisegundos Unix) |
|
||||
| `limit` | string | No | Número de resultados (1-1000, predeterminado: 100) |
|
||||
| `cursor` | string | No | Cursor de paginación para la siguiente página |
|
||||
|
||||
#### Salida
|
||||
@@ -269,11 +254,11 @@ Recuperar tu portafolio
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de ejecuciones y metadatos |
|
||||
| `output` | object | Datos de ejecuciones e información adicional |
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
Obtener detalles de una serie de mercado específica por ticker
|
||||
Recuperar detalles de una serie de mercado específica por ticker
|
||||
|
||||
#### Entrada
|
||||
|
||||
@@ -286,15 +271,15 @@ Obtener detalles de una serie de mercado específica por ticker
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la serie y metadatos |
|
||||
| `output` | object | Datos de la serie e información adicional |
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
Obtener el estado actual del intercambio Kalshi (actividad de trading y del intercambio)
|
||||
Recupera el estado actual del intercambio Kalshi (actividad de trading y del intercambio)
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
|
||||
#### Salida
|
||||
@@ -302,90 +287,7 @@ Obtener el estado actual del intercambio Kalshi (actividad de trading y del inte
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del estado del intercambio y metadatos |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
Crear una nueva orden en un mercado de predicción de Kalshi
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Sí | Tu ID de clave API de Kalshi |
|
||||
| `privateKey` | string | Sí | Tu clave privada RSA \(formato PEM\) |
|
||||
| `ticker` | string | Sí | Ticker del mercado \(p. ej., KXBTC-24DEC31\) |
|
||||
| `side` | string | Sí | Lado de la orden: 'yes' o 'no' |
|
||||
| `action` | string | Sí | Tipo de acción: 'buy' o 'sell' |
|
||||
| `count` | string | Sí | Número de contratos \(mínimo 1\) |
|
||||
| `type` | string | No | Tipo de orden: 'limit' o 'market' \(predeterminado: limit\) |
|
||||
| `yesPrice` | string | No | Precio de 'yes' en centavos \(1-99\) |
|
||||
| `noPrice` | string | No | Precio de 'no' en centavos \(1-99\) |
|
||||
| `yesPriceDollars` | string | No | Precio de 'yes' en dólares \(p. ej., "0.56"\) |
|
||||
| `noPriceDollars` | string | No | Precio de 'no' en dólares \(p. ej., "0.56"\) |
|
||||
| `clientOrderId` | string | No | Identificador personalizado de la orden |
|
||||
| `expirationTs` | string | No | Marca de tiempo Unix para la expiración de la orden |
|
||||
| `timeInForce` | string | No | Tiempo en vigor: 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel' |
|
||||
| `buyMaxCost` | string | No | Costo máximo en centavos \(habilita automáticamente fill_or_kill\) |
|
||||
| `postOnly` | string | No | Establecer como 'true' para órdenes solo maker |
|
||||
| `reduceOnly` | string | No | Establecer como 'true' solo para reducción de posición |
|
||||
| `selfTradePreventionType` | string | No | Prevención de auto-negociación: 'taker_at_cross' o 'maker' |
|
||||
| `orderGroupId` | string | No | ID de grupo de órdenes asociado |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la orden creada |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
Cancelar una orden existente en Kalshi
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Sí | Tu ID de clave API de Kalshi |
|
||||
| `privateKey` | string | Sí | Tu clave privada RSA \(formato PEM\) |
|
||||
| `orderId` | string | Sí | El ID de la orden a cancelar |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la orden cancelada |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
Modificar el precio o la cantidad de una orden existente en Kalshi
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Sí | Tu ID de clave API de Kalshi |
|
||||
| `privateKey` | string | Sí | Tu clave privada RSA \(formato PEM\) |
|
||||
| `orderId` | string | Sí | El ID de la orden a modificar |
|
||||
| `ticker` | string | Sí | Ticker del mercado |
|
||||
| `side` | string | Sí | Lado de la orden: 'yes' o 'no' |
|
||||
| `action` | string | Sí | Tipo de acción: 'buy' o 'sell' |
|
||||
| `clientOrderId` | string | Sí | El ID de orden original especificado por el cliente |
|
||||
| `updatedClientOrderId` | string | Sí | El nuevo ID de orden especificado por el cliente después de la modificación |
|
||||
| `count` | string | No | Cantidad actualizada para la orden |
|
||||
| `yesPrice` | string | No | Precio actualizado para 'yes' en centavos \(1-99\) |
|
||||
| `noPrice` | string | No | Precio actualizado para 'no' en centavos \(1-99\) |
|
||||
| `yesPriceDollars` | string | No | Precio actualizado para 'yes' en dólares \(p. ej., "0.56"\) |
|
||||
| `noPriceDollars` | string | No | Precio actualizado para 'no' en dólares \(p. ej., "0.56"\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la orden modificada |
|
||||
| `output` | object | Datos de estado del intercambio y metadatos |
|
||||
|
||||
## Notas
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@ Obtener una lista de mercados de predicción de Polymarket con filtrado opcional
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | No | Filtrar por estado cerrado \(true/false\). Use false para mostrar solo mercados activos. |
|
||||
| `order` | string | No | Campo de ordenación \(p. ej., volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `closed` | string | No | Filtrar por estado cerrado \(true/false\). Usa false para mercados activos solamente. |
|
||||
| `order` | string | No | Campo de ordenación \(p. ej., id, volume, liquidity\) |
|
||||
| `ascending` | string | No | Dirección de ordenación \(true para ascendente, false para descendente\) |
|
||||
| `tagId` | string | No | Filtrar por ID de etiqueta |
|
||||
| `limit` | string | No | Número de resultados por página \(recomendado: 25-50\) |
|
||||
@@ -78,14 +78,14 @@ Obtener una lista de eventos de Polymarket con filtrado opcional
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | No | Filtrar por estado cerrado \(true/false\). Use false para mostrar solo eventos activos. |
|
||||
| `order` | string | No | Campo de ordenación \(p. ej., volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | No | Dirección de ordenación \(true para ascendente, false para descendente\) |
|
||||
| `closed` | string | No | Filtrar por estado cerrado (true/false). Usar false para eventos activos solamente. |
|
||||
| `order` | string | No | Campo de ordenación (ej., id, volume) |
|
||||
| `ascending` | string | No | Dirección de ordenación (true para ascendente, false para descendente) |
|
||||
| `tagId` | string | No | Filtrar por ID de etiqueta |
|
||||
| `limit` | string | No | Número de resultados por página \(recomendado: 25-50\) |
|
||||
| `offset` | string | No | Desplazamiento de paginación \(omitir esta cantidad de resultados\) |
|
||||
| `limit` | string | No | Número de resultados por página (recomendado: 25-50) |
|
||||
| `offset` | string | No | Desplazamiento de paginación (omitir esta cantidad de resultados) |
|
||||
|
||||
#### Salida
|
||||
|
||||
|
||||
@@ -120,82 +120,6 @@ Lee los últimos mensajes de los canales de Slack. Recupera el historial de conv
|
||||
| --------- | ---- | ----------- |
|
||||
| `messages` | array | Array de objetos de mensaje del canal |
|
||||
|
||||
### `slack_list_channels`
|
||||
|
||||
Lista todos los canales en un espacio de trabajo de Slack. Devuelve los canales públicos y privados a los que el bot tiene acceso.
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | No | Método de autenticación: oauth o bot_token |
|
||||
| `botToken` | string | No | Token del bot para Bot personalizado |
|
||||
| `includePrivate` | boolean | No | Incluir canales privados de los que el bot es miembro (predeterminado: true) |
|
||||
| `excludeArchived` | boolean | No | Excluir canales archivados (predeterminado: true) |
|
||||
| `limit` | number | No | Número máximo de canales a devolver (predeterminado: 100, máx: 200) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `channels` | array | Array de objetos de canal del espacio de trabajo |
|
||||
|
||||
### `slack_list_members`
|
||||
|
||||
Lista todos los miembros (IDs de usuario) en un canal de Slack. Úsalo con Obtener Información de Usuario para resolver IDs a nombres.
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | No | Método de autenticación: oauth o bot_token |
|
||||
| `botToken` | string | No | Token del bot para Bot personalizado |
|
||||
| `channel` | string | Sí | ID del canal del que listar miembros |
|
||||
| `limit` | number | No | Número máximo de miembros a devolver (predeterminado: 100, máx: 200) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `members` | array | Array de IDs de usuario que son miembros del canal (p. ej., U1234567890) |
|
||||
|
||||
### `slack_list_users`
|
||||
|
||||
Lista todos los usuarios en un espacio de trabajo de Slack. Devuelve perfiles de usuario con nombres y avatares.
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | No | Método de autenticación: oauth o bot_token |
|
||||
| `botToken` | string | No | Token del bot para Bot personalizado |
|
||||
| `includeDeleted` | boolean | No | Incluir usuarios desactivados/eliminados \(predeterminado: false\) |
|
||||
| `limit` | number | No | Número máximo de usuarios a devolver \(predeterminado: 100, máx: 200\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | Array de objetos de usuario del espacio de trabajo |
|
||||
|
||||
### `slack_get_user`
|
||||
|
||||
Obtiene información detallada sobre un usuario específico de Slack mediante su ID de usuario.
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | No | Método de autenticación: oauth o bot_token |
|
||||
| `botToken` | string | No | Token del bot para Bot personalizado |
|
||||
| `userId` | string | Sí | ID de usuario a buscar \(p. ej., U1234567890\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `user` | object | Información detallada del usuario |
|
||||
|
||||
### `slack_download`
|
||||
|
||||
Descargar un archivo de Slack
|
||||
@@ -259,7 +183,7 @@ Eliminar un mensaje enviado previamente por el bot en Slack
|
||||
|
||||
### `slack_add_reaction`
|
||||
|
||||
Añadir una reacción emoji a un mensaje de Slack
|
||||
Añadir una reacción con emoji a un mensaje de Slack
|
||||
|
||||
#### Entrada
|
||||
|
||||
|
||||
@@ -139,14 +139,13 @@ Obtenez les meilleures pages d'un domaine cible triées par trafic organique. Re
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `target` | string | Oui | Le domaine cible à analyser |
|
||||
| `country` | string | Non | Code pays pour les données de trafic \(ex. : us, gb, de\). Par défaut : us |
|
||||
| `country` | string | Non | Code pays pour les données de trafic \(ex., us, gb, de\). Par défaut : us |
|
||||
| `mode` | string | Non | Mode d'analyse : domain \(domaine entier\), prefix \(préfixe d'URL\), subdomains \(inclure tous les sous-domaines\) |
|
||||
| `date` | string | Non | Date pour les données historiques au format AAAA-MM-JJ \(par défaut : aujourd'hui\) |
|
||||
| `limit` | number | Non | Nombre maximum de résultats à renvoyer \(par défaut : 100\) |
|
||||
| `limit` | number | Non | Nombre maximum de résultats à retourner \(par défaut : 100\) |
|
||||
| `offset` | number | Non | Nombre de résultats à ignorer pour la pagination |
|
||||
| `select` | string | Non | Liste de champs à renvoyer, séparés par des virgules \(ex. : url,traffic,keywords,top_keyword,value\). Par défaut : url,traffic,keywords,top_keyword,value |
|
||||
| `apiKey` | string | Oui | Clé API Ahrefs |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -37,16 +37,16 @@ Créer un nouvel événement dans Google Agenda
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `calendarId` | chaîne | Non | ID du calendrier (par défaut : primaire) |
|
||||
| `summary` | chaîne | Oui | Titre/résumé de l'événement |
|
||||
| `description` | chaîne | Non | Description de l'événement |
|
||||
| `location` | chaîne | Non | Lieu de l'événement |
|
||||
| `startDateTime` | chaîne | Oui | Date et heure de début. DOIT inclure le décalage horaire (ex. : 2025-06-03T10:00:00-08:00) OU fournir le paramètre timeZone |
|
||||
| `endDateTime` | chaîne | Oui | Date et heure de fin. DOIT inclure le décalage horaire (ex. : 2025-06-03T11:00:00-08:00) OU fournir le paramètre timeZone |
|
||||
| `timeZone` | chaîne | Non | Fuseau horaire (ex. : America/Los_Angeles). Obligatoire si la date/heure n'inclut pas de décalage. Par défaut : America/Los_Angeles si non fourni. |
|
||||
| `attendees` | tableau | Non | Tableau d'adresses e-mail des participants |
|
||||
| `sendUpdates` | chaîne | Non | Comment envoyer les mises à jour aux participants : all, externalOnly, ou none |
|
||||
| `startDateTime` | chaîne | Oui | Date et heure de début (format RFC3339, ex. : 2025-06-03T10:00:00-08:00) |
|
||||
| `endDateTime` | chaîne | Oui | Date et heure de fin (format RFC3339, ex. : 2025-06-03T11:00:00-08:00) |
|
||||
| `timeZone` | chaîne | Non | Fuseau horaire (ex. : America/Los_Angeles) |
|
||||
| `attendees` | tableau | Non | Tableau des adresses e-mail des participants |
|
||||
| `sendUpdates` | chaîne | Non | Comment envoyer les mises à jour aux participants : all, externalOnly ou none |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -107,11 +107,11 @@ Lister les fichiers et dossiers dans Google Drive
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `folderSelector` | string | Non | Sélectionner le dossier à partir duquel lister les fichiers |
|
||||
| `folderId` | string | Non | L'ID du dossier à partir duquel lister les fichiers (usage interne) |
|
||||
| `query` | string | Non | Terme de recherche pour filtrer les fichiers par nom (ex. "budget" trouve les fichiers avec "budget" dans le nom). N'utilisez PAS la syntaxe de requête Google Drive ici - fournissez simplement un terme de recherche ordinaire. |
|
||||
| `pageSize` | number | Non | Le nombre maximum de fichiers à retourner (par défaut : 100) |
|
||||
| `folderId` | string | Non | L'ID du dossier à partir duquel lister les fichiers \(usage interne\) |
|
||||
| `query` | string | Non | Une requête pour filtrer les fichiers |
|
||||
| `pageSize` | number | Non | Le nombre de fichiers à retourner |
|
||||
| `pageToken` | string | Non | Le jeton de page à utiliser pour la pagination |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -88,8 +88,8 @@ Lire des données d'une feuille de calcul Google Sheets
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `spreadsheetId` | chaîne | Oui | L'identifiant de la feuille de calcul \(trouvé dans l'URL : docs.google.com/spreadsheets/d/\{SPREADSHEET_ID\}/edit\). |
|
||||
| `range` | chaîne | Non | La plage en notation A1 à lire \(par exemple "Sheet1!A1:D10", "A1:B5"\). Par défaut, première feuille A1:Z1000 si non spécifié. |
|
||||
| `spreadsheetId` | chaîne | Oui | L'identifiant de la feuille de calcul à lire |
|
||||
| `range` | chaîne | Non | La plage de cellules à lire |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -106,9 +106,9 @@ Lire des données d'une feuille de calcul Google Sheets
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `spreadsheetId` | chaîne | Oui | L'identifiant de la feuille de calcul |
|
||||
| `range` | chaîne | Non | La plage en notation A1 où écrire \(par exemple "Sheet1!A1:D10", "A1:B5"\) |
|
||||
| `values` | tableau | Oui | Les données à écrire sous forme de tableau 2D \(par exemple \[\["Nom", "Âge"\], \["Alice", 30\], \["Bob", 25\]\]\) ou tableau d'objets. |
|
||||
| `spreadsheetId` | chaîne | Oui | L'identifiant de la feuille de calcul dans laquelle écrire |
|
||||
| `range` | chaîne | Non | La plage de cellules dans laquelle écrire |
|
||||
| `values` | tableau | Oui | Les données à écrire dans la feuille de calcul |
|
||||
| `valueInputOption` | chaîne | Non | Le format des données à écrire |
|
||||
| `includeValuesInResponse` | booléen | Non | Indique si les valeurs écrites doivent être incluses dans la réponse |
|
||||
|
||||
@@ -129,12 +129,12 @@ Mettre à jour des données dans une feuille de calcul Google Sheets
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `spreadsheetId` | chaîne | Oui | L'identifiant de la feuille de calcul à mettre à jour |
|
||||
| `range` | chaîne | Non | La plage en notation A1 à mettre à jour \(par exemple "Sheet1!A1:D10", "A1:B5"\) |
|
||||
| `values` | tableau | Oui | Les données à mettre à jour sous forme de tableau 2D \(par exemple \[\["Nom", "Âge"\], \["Alice", 30\]\]\) ou tableau d'objets. |
|
||||
| `valueInputOption` | chaîne | Non | Le format des données à mettre à jour |
|
||||
| `includeValuesInResponse` | booléen | Non | Indique si les valeurs mises à jour doivent être incluses dans la réponse |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `spreadsheetId` | string | Oui | L'ID de la feuille de calcul à mettre à jour |
|
||||
| `range` | string | Non | La plage de cellules à mettre à jour |
|
||||
| `values` | array | Oui | Les données à mettre à jour dans la feuille de calcul |
|
||||
| `valueInputOption` | string | Non | Le format des données à mettre à jour |
|
||||
| `includeValuesInResponse` | boolean | Non | Indique s'il faut inclure les valeurs mises à jour dans la réponse |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -155,11 +155,11 @@ Ajouter des données à la fin d'une feuille de calcul Google Sheets
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `spreadsheetId` | chaîne | Oui | L'identifiant de la feuille de calcul à laquelle ajouter des données |
|
||||
| `range` | chaîne | Non | La plage de notation A1 après laquelle ajouter des données \(ex. "Feuille1", "Feuille1!A:D"\) |
|
||||
| `values` | tableau | Oui | Les données à ajouter sous forme de tableau 2D \(ex. \[\["Alice", 30\], \["Bob", 25\]\]\) ou tableau d'objets. |
|
||||
| `range` | chaîne | Non | La plage de cellules après laquelle ajouter des données |
|
||||
| `values` | tableau | Oui | Les données à ajouter à la feuille de calcul |
|
||||
| `valueInputOption` | chaîne | Non | Le format des données à ajouter |
|
||||
| `insertDataOption` | chaîne | Non | Comment insérer les données \(OVERWRITE ou INSERT_ROWS\) |
|
||||
| `includeValuesInResponse` | booléen | Non | Indique s'il faut inclure les valeurs ajoutées dans la réponse |
|
||||
| `includeValuesInResponse` | booléen | Non | Indique si les valeurs ajoutées doivent être incluses dans la réponse |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
title: Google Slides
|
||||
description: Lire, écrire et créer des présentations
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="google_slides"
|
||||
color="#E0E0E0"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Google Slides](https://slides.google.com) est une application de présentation dynamique basée sur le cloud qui permet aux utilisateurs de créer, modifier, collaborer et présenter des diaporamas en temps réel. En tant que partie de la suite de productivité de Google, Google Slides offre une plateforme flexible pour concevoir des présentations attrayantes, collaborer avec d'autres et partager du contenu de manière transparente via le cloud.
|
||||
|
||||
Découvrez comment intégrer les outils Google Slides dans Sim pour gérer sans effort les présentations dans le cadre de vos flux de travail automatisés. Avec Sim, vous pouvez lire, écrire, créer et mettre à jour des présentations Google Slides directement via vos agents et processus automatisés, ce qui facilite la diffusion d'informations actualisées, la génération de rapports personnalisés ou la production programmatique de présentations à l'image de votre marque.
|
||||
|
||||
Avec Google Slides, vous pouvez :
|
||||
|
||||
- **Créer et modifier des présentations** : concevoir des diapositives visuellement attrayantes avec des thèmes, des mises en page et du contenu multimédia
|
||||
- **Collaborer en temps réel** : travailler simultanément avec des coéquipiers, commenter, attribuer des tâches et recevoir des commentaires en direct sur les présentations
|
||||
- **Présenter n'importe où** : afficher des présentations en ligne ou hors ligne, partager des liens ou publier sur le web
|
||||
- **Ajouter des images et du contenu enrichi** : insérer des images, des graphiques, des diagrammes et des vidéos pour rendre vos présentations attrayantes
|
||||
- **S'intégrer à d'autres services** : se connecter de manière transparente avec Google Drive, Docs, Sheets et d'autres outils tiers
|
||||
- **Accéder depuis n'importe quel appareil** : utiliser Google Slides sur ordinateurs de bureau, portables, tablettes et appareils mobiles pour une flexibilité maximale
|
||||
|
||||
Dans Sim, l'intégration de Google Slides permet à vos agents d'interagir directement avec les fichiers de présentation de manière programmatique. Automatisez des tâches comme la lecture du contenu des diapositives, l'insertion de nouvelles diapositives ou images, le remplacement de texte dans toute une présentation, la génération de nouvelles présentations et la récupération de miniatures de diapositives. Cela vous permet de développer la création de contenu, de maintenir les présentations à jour et de les intégrer dans des flux de travail de documents automatisés. En connectant Sim avec Google Slides, vous facilitez la gestion des présentations pilotée par l'IA, ce qui permet de générer, mettre à jour ou extraire facilement des informations des présentations sans effort manuel.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## Instructions d'utilisation
|
||||
|
||||
Intégrez Google Slides dans le flux de travail. Peut lire, écrire, créer des présentations, remplacer du texte, ajouter des diapositives, ajouter des images et obtenir des miniatures.
|
||||
|
||||
## Outils
|
||||
|
||||
### `google_slides_read`
|
||||
|
||||
Lire le contenu d'une présentation Google Slides
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Oui | L'ID de la présentation à lire |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slides` | json | Tableau des diapositives avec leur contenu |
|
||||
| `metadata` | json | Métadonnées de la présentation incluant ID, titre et URL |
|
||||
|
||||
### `google_slides_write`
|
||||
|
||||
Écrire ou mettre à jour le contenu dans une présentation Google Slides
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Oui | L'ID de la présentation dans laquelle écrire |
|
||||
| `content` | string | Oui | Le contenu à écrire dans la diapositive |
|
||||
| `slideIndex` | number | Non | L'index de la diapositive dans laquelle écrire \(par défaut, première diapositive\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `updatedContent` | boolean | Indique si le contenu de la présentation a été mis à jour avec succès |
|
||||
| `metadata` | json | Métadonnées de la présentation mise à jour incluant ID, titre et URL |
|
||||
|
||||
### `google_slides_create`
|
||||
|
||||
Créer une nouvelle présentation Google Slides
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | Oui | Le titre de la présentation à créer |
|
||||
| `content` | string | Non | Le contenu à ajouter à la première diapositive |
|
||||
| `folderSelector` | string | Non | Sélectionner le dossier dans lequel créer la présentation |
|
||||
| `folderId` | string | Non | L'ID du dossier dans lequel créer la présentation \(usage interne\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `metadata` | json | Métadonnées de la présentation créée, y compris l'ID, le titre et l'URL |
|
||||
|
||||
### `google_slides_replace_all_text`
|
||||
|
||||
Rechercher et remplacer toutes les occurrences de texte dans une présentation Google Slides
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Oui | L'ID de la présentation |
|
||||
| `findText` | string | Oui | Le texte à rechercher \(par exemple, \{\{placeholder\}\}\) |
|
||||
| `replaceText` | string | Oui | Le texte de remplacement |
|
||||
| `matchCase` | boolean | Non | Si la recherche doit être sensible à la casse \(par défaut : true\) |
|
||||
| `pageObjectIds` | string | Non | Liste séparée par des virgules des ID d'objets de diapositive pour limiter les remplacements à des diapositives spécifiques \(laisser vide pour toutes les diapositives\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `occurrencesChanged` | number | Nombre d'occurrences de texte qui ont été remplacées |
|
||||
| `metadata` | json | Métadonnées de l'opération, y compris l'ID et l'URL de la présentation |
|
||||
|
||||
### `google_slides_add_slide`
|
||||
|
||||
Ajouter une nouvelle diapositive à une présentation Google Slides avec une mise en page spécifiée
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | Oui | L'ID de la présentation |
|
||||
| `layout` | string | Non | La mise en page prédéfinie pour la diapositive \(BLANK, TITLE, TITLE_AND_BODY, TITLE_ONLY, SECTION_HEADER, etc.\). Par défaut : BLANK. |
|
||||
| `insertionIndex` | number | Non | L'index facultatif basé sur zéro indiquant où insérer la diapositive. Si non spécifié, la diapositive est ajoutée à la fin. |
|
||||
| `placeholderIdMappings` | string | Non | Tableau JSON de mappages d'espaces réservés pour attribuer des ID d'objets personnalisés aux espaces réservés. Format : \[\{"layoutPlaceholder":\{"type":"TITLE"\},"objectId":"custom_title_id"\}\] |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slideId` | string | L'ID d'objet de la diapositive nouvellement créée |
|
||||
| `metadata` | json | Métadonnées de l'opération incluant l'ID de la présentation, la mise en page et l'URL |
|
||||
|
||||
### `google_slides_add_image`
|
||||
|
||||
Insérer une image dans une diapositive spécifique d'une présentation Google Slides
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `presentationId` | string | Oui | L'ID de la présentation |
|
||||
| `pageObjectId` | string | Oui | L'ID d'objet de la diapositive/page où ajouter l'image |
|
||||
| `imageUrl` | string | Oui | L'URL accessible publiquement de l'image \(doit être PNG, JPEG ou GIF, max 50 Mo\) |
|
||||
| `width` | number | Non | Largeur de l'image en points \(par défaut : 300\) |
|
||||
| `height` | number | Non | Hauteur de l'image en points \(par défaut : 200\) |
|
||||
| `positionX` | number | Non | Position X depuis le bord gauche en points \(par défaut : 100\) |
|
||||
| `positionY` | number | Non | Position Y depuis le bord supérieur en points \(par défaut : 100\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `imageId` | string | L'ID d'objet de l'image nouvellement créée |
|
||||
| `metadata` | json | Métadonnées de l'opération incluant l'ID de la présentation et l'URL de l'image |
|
||||
|
||||
### `google_slides_get_thumbnail`
|
||||
|
||||
Générer une image miniature d'une diapositive spécifique dans une présentation Google Slides
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `presentationId` | chaîne | Oui | L'identifiant de la présentation |
|
||||
| `pageObjectId` | chaîne | Oui | L'identifiant d'objet de la diapositive/page pour laquelle obtenir une vignette |
|
||||
| `thumbnailSize` | chaîne | Non | La taille de la vignette : SMALL \(200px\), MEDIUM \(800px\), ou LARGE \(1600px\). Par défaut MEDIUM. |
|
||||
| `mimeType` | chaîne | Non | Le type MIME de l'image vignette : PNG ou GIF. Par défaut PNG. |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `contentUrl` | chaîne | URL vers l'image vignette \(valide pendant 30 minutes\) |
|
||||
| `width` | nombre | Largeur de la vignette en pixels |
|
||||
| `height` | nombre | Hauteur de la vignette en pixels |
|
||||
| `metadata` | json | Métadonnées de l'opération incluant l'identifiant de la présentation et l'identifiant d'objet de la page |
|
||||
|
||||
## Notes
|
||||
|
||||
- Catégorie : `tools`
|
||||
- Type : `google_slides`
|
||||
@@ -360,9 +360,7 @@ Mettre à jour un planning existant dans incident.io
|
||||
| `apiKey` | chaîne | Oui | Clé API incident.io |
|
||||
| `id` | chaîne | Oui | L'ID du planning à mettre à jour |
|
||||
| `name` | chaîne | Non | Nouveau nom pour le planning |
|
||||
| `timezone` | chaîne | Non | Nouveau fuseau horaire pour le planning \(ex. : America/New_York\) |
|
||||
| `config` | chaîne | Non | Configuration du planning au format JSON avec rotations. Exemple : \{"rotations": \[\{"name": "Primary", "users": \[\{"id": "user_id"\}\], "handover_start_at": "2024-01-01T09:00:00Z", "handovers": \[\{"interval": 1, "interval_type": "weekly"\}\]\}\]\} |
|
||||
| `Example` | chaîne | Non | Pas de description |
|
||||
| `timezone` | chaîne | Non | Nouveau fuseau horaire pour le planning \(ex., America/New_York\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
|
||||
@@ -162,14 +162,14 @@ Créer ou mettre à jour une entreprise dans Intercom
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `company_id` | string | Oui | Votre identifiant unique pour l'entreprise |
|
||||
| `name` | string | Non | Le nom de l'entreprise |
|
||||
| `website` | string | Non | Le site web de l'entreprise |
|
||||
| `plan` | string | Non | Le nom du forfait de l'entreprise |
|
||||
| `size` | number | Non | Le nombre d'employés dans l'entreprise |
|
||||
| `industry` | string | Non | Le secteur d'activité de l'entreprise |
|
||||
| `monthly_spend` | number | Non | Le montant des revenus que l'entreprise génère pour votre activité. Remarque : ce champ tronque les décimales en nombres entiers (par exemple, 155,98 devient 155) |
|
||||
| `custom_attributes` | string | Non | Attributs personnalisés sous forme d'objet JSON |
|
||||
| `company_id` | chaîne | Oui | Votre identifiant unique pour l'entreprise |
|
||||
| `name` | chaîne | Non | Le nom de l'entreprise |
|
||||
| `website` | chaîne | Non | Le site web de l'entreprise |
|
||||
| `plan` | chaîne | Non | Le nom du forfait de l'entreprise |
|
||||
| `size` | nombre | Non | Le nombre d'employés dans l'entreprise |
|
||||
| `industry` | chaîne | Non | Le secteur d'activité de l'entreprise |
|
||||
| `monthly_spend` | nombre | Non | Le montant des revenus que l'entreprise génère pour votre activité |
|
||||
| `custom_attributes` | chaîne | Non | Attributs personnalisés sous forme d'objet JSON |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -197,7 +197,7 @@ Récupérer une seule entreprise par ID depuis Intercom
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
Liste toutes les entreprises d'Intercom avec prise en charge de la pagination. Remarque : cet endpoint a une limite de 10 000 entreprises qui peuvent être renvoyées en utilisant la pagination. Pour les ensembles de données de plus de 10 000 entreprises, utilisez plutôt l'API Scroll.
|
||||
Lister toutes les entreprises depuis Intercom avec prise en charge de la pagination
|
||||
|
||||
#### Entrée
|
||||
|
||||
@@ -256,11 +256,11 @@ Répondre à une conversation en tant qu'administrateur dans Intercom
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `conversationId` | string | Oui | ID de la conversation à laquelle répondre |
|
||||
| `message_type` | string | Oui | Type de message : "comment" ou "note" |
|
||||
| `body` | string | Oui | Le corps du texte de la réponse |
|
||||
| `admin_id` | string | Non | L'ID de l'administrateur qui rédige la réponse. Si non fourni, un administrateur par défaut (Operator/Fin) sera utilisé. |
|
||||
| `admin_id` | string | Oui | L'ID de l'administrateur qui rédige la réponse |
|
||||
| `attachment_urls` | string | Non | Liste d'URLs d'images séparées par des virgules (max 10) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Kalshi
|
||||
description: Accédez aux marchés prédictifs et tradez sur Kalshi
|
||||
description: Accédez aux données des marchés prédictifs de Kalshi
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -27,7 +27,7 @@ En utilisant ces outils et points d'accès unifiés, vous pouvez intégrer de ma
|
||||
|
||||
## Instructions d'utilisation
|
||||
|
||||
Intégrez les marchés prédictifs Kalshi dans votre flux de travail. Permet d'obtenir les marchés, un marché spécifique, les événements, un événement spécifique, le solde, les positions, les ordres, le carnet d'ordres, les transactions, les chandeliers, les exécutions, les séries, l'état de l'échange, et de placer/annuler/modifier des transactions.
|
||||
Intégrez les marchés prédictifs de Kalshi dans le flux de travail. Peut obtenir les marchés, le marché, les événements, l'événement, le solde, les positions, les ordres, le carnet d'ordres, les transactions, les chandeliers, les exécutions, les séries et le statut de la bourse.
|
||||
|
||||
## Outils
|
||||
|
||||
@@ -172,51 +172,36 @@ Récupérez vos ordres depuis Kalshi avec filtrage optionnel
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données des ordres et métadonnées |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
Récupérer les détails d'un ordre spécifique par ID depuis Kalshi
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Oui | Votre ID de clé API Kalshi |
|
||||
| `privateKey` | string | Oui | Votre clé privée RSA \(format PEM\) |
|
||||
| `orderId` | string | Oui | L'ID de l'ordre à récupérer |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de l'ordre |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
Récupérer le carnet d'ordres (offres oui et non) pour un marché spécifique
|
||||
Récupérez le carnet d'ordres (offres d'achat et de vente) pour un marché spécifique
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | Oui | Ticker du marché \(ex. : KXBTC-24DEC31\) |
|
||||
| `depth` | number | Non | Nombre de niveaux de prix à retourner par côté |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données du carnet d'ordres et métadonnées |
|
||||
| `output` | object | Données et métadonnées du carnet d'ordres |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
Récupérer les transactions récentes sur tous les marchés
|
||||
Récupérer les transactions récentes sur tous les marchés ou pour un marché spécifique
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | Non | Nombre de résultats \(1-1000, par défaut : 100\) |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `ticker` | string | Non | Filtrer par symbole de marché |
|
||||
| `minTs` | number | Non | Horodatage minimum (millisecondes Unix) |
|
||||
| `maxTs` | number | Non | Horodatage maximum (millisecondes Unix) |
|
||||
| `limit` | string | Non | Nombre de résultats (1-1000, par défaut : 100) |
|
||||
| `cursor` | string | Non | Curseur de pagination pour la page suivante |
|
||||
|
||||
#### Sortie
|
||||
@@ -224,7 +209,7 @@ Récupérer les transactions récentes sur tous les marchés
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données des transactions et métadonnées |
|
||||
| `output` | object | Données et métadonnées des transactions |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
@@ -233,19 +218,19 @@ Récupérer les données de chandeliers OHLC pour un marché spécifique
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `seriesTicker` | string | Oui | Ticker de série |
|
||||
| `ticker` | string | Oui | Ticker du marché \(ex. : KXBTC-24DEC31\) |
|
||||
| `startTs` | number | Oui | Horodatage de début \(secondes Unix\) |
|
||||
| `endTs` | number | Oui | Horodatage de fin \(secondes Unix\) |
|
||||
| `periodInterval` | number | Oui | Intervalle de période : 1 \(1min\), 60 \(1heure\), ou 1440 \(1jour\) |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `seriesTicker` | string | Oui | Symbole de la série |
|
||||
| `ticker` | string | Oui | Symbole du marché (ex. : KXBTC-24DEC31) |
|
||||
| `startTs` | number | Non | Horodatage de début (millisecondes Unix) |
|
||||
| `endTs` | number | Non | Horodatage de fin (millisecondes Unix) |
|
||||
| `periodInterval` | number | Non | Intervalle de période : 1 (1min), 60 (1heure), ou 1440 (1jour) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de chandeliers et métadonnées |
|
||||
| `output` | object | Données et métadonnées du chandelier |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
@@ -258,7 +243,7 @@ Récupérer votre portefeuille
|
||||
| `keyId` | string | Oui | Votre ID de clé API Kalshi |
|
||||
| `privateKey` | string | Oui | Votre clé privée RSA \(format PEM\) |
|
||||
| `ticker` | string | Non | Filtrer par ticker de marché |
|
||||
| `orderId` | string | Non | Filtrer par ID d'ordre |
|
||||
| `orderId` | string | Non | Filtrer par ID de commande |
|
||||
| `minTs` | number | Non | Horodatage minimum \(millisecondes Unix\) |
|
||||
| `maxTs` | number | Non | Horodatage maximum \(millisecondes Unix\) |
|
||||
| `limit` | string | Non | Nombre de résultats \(1-1000, par défaut : 100\) |
|
||||
@@ -290,102 +275,19 @@ Récupérer les détails d'une série de marché spécifique par ticker
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
Récupérer le statut actuel de la plateforme d'échange Kalshi (activité de trading et d'échange)
|
||||
Récupérer l'état actuel de l'échange Kalshi (activité de trading et d'échange)
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées du statut de l'échange |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
Créer un nouvel ordre sur un marché de prédiction Kalshi
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Oui | Votre ID de clé API Kalshi |
|
||||
| `privateKey` | string | Oui | Votre clé privée RSA \(format PEM\) |
|
||||
| `ticker` | string | Oui | Ticker du marché \(ex., KXBTC-24DEC31\) |
|
||||
| `side` | string | Oui | Côté de l'ordre : 'yes' ou 'no' |
|
||||
| `action` | string | Oui | Type d'action : 'buy' ou 'sell' |
|
||||
| `count` | string | Oui | Nombre de contrats \(minimum 1\) |
|
||||
| `type` | string | Non | Type d'ordre : 'limit' ou 'market' \(par défaut : limit\) |
|
||||
| `yesPrice` | string | Non | Prix du 'yes' en centimes \(1-99\) |
|
||||
| `noPrice` | string | Non | Prix du 'no' en centimes \(1-99\) |
|
||||
| `yesPriceDollars` | string | Non | Prix du 'yes' en dollars \(ex., "0.56"\) |
|
||||
| `noPriceDollars` | string | Non | Prix du 'no' en dollars \(ex., "0.56"\) |
|
||||
| `clientOrderId` | string | Non | Identifiant personnalisé de l'ordre |
|
||||
| `expirationTs` | string | Non | Horodatage Unix pour l'expiration de l'ordre |
|
||||
| `timeInForce` | string | Non | Durée de validité : 'fill_or_kill', 'good_till_canceled', 'immediate_or_cancel' |
|
||||
| `buyMaxCost` | string | Non | Coût maximum en centimes \(active automatiquement fill_or_kill\) |
|
||||
| `postOnly` | string | Non | Définir à 'true' pour les ordres maker uniquement |
|
||||
| `reduceOnly` | string | Non | Définir à 'true' pour la réduction de position uniquement |
|
||||
| `selfTradePreventionType` | string | Non | Prévention d'auto-négociation : 'taker_at_cross' ou 'maker' |
|
||||
| `orderGroupId` | string | Non | ID de groupe d'ordres associé |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de l'ordre créé |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
Annuler un ordre existant sur Kalshi
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Oui | Votre ID de clé API Kalshi |
|
||||
| `privateKey` | string | Oui | Votre clé privée RSA \(format PEM\) |
|
||||
| `orderId` | string | Oui | L'ID de l'ordre à annuler |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de l'ordre annulé |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
Modifier le prix ou la quantité d'un ordre existant sur Kalshi
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | Oui | Votre ID de clé API Kalshi |
|
||||
| `privateKey` | string | Oui | Votre clé privée RSA \(format PEM\) |
|
||||
| `orderId` | string | Oui | L'ID de l'ordre à modifier |
|
||||
| `ticker` | string | Oui | Ticker du marché |
|
||||
| `side` | string | Oui | Côté de l'ordre : 'yes' ou 'no' |
|
||||
| `action` | string | Oui | Type d'action : 'buy' ou 'sell' |
|
||||
| `clientOrderId` | string | Oui | L'ID d'ordre original spécifié par le client |
|
||||
| `updatedClientOrderId` | string | Oui | Le nouvel ID d'ordre spécifié par le client après modification |
|
||||
| `count` | string | Non | Quantité mise à jour pour l'ordre |
|
||||
| `yesPrice` | string | Non | Prix 'yes' mis à jour en centimes \(1-99\) |
|
||||
| `noPrice` | string | Non | Prix 'no' mis à jour en centimes \(1-99\) |
|
||||
| `yesPriceDollars` | string | Non | Prix 'yes' mis à jour en dollars \(ex. : "0.56"\) |
|
||||
| `noPriceDollars` | string | Non | Prix 'no' mis à jour en dollars \(ex. : "0.56"\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de l'ordre modifié |
|
||||
| `output` | object | Données d'état de l'échange et métadonnées |
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@ Récupérer une liste des marchés prédictifs de Polymarket avec filtrage optio
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `closed` | string | Non | Filtrer par statut fermé \(true/false\). Utilisez false pour les marchés actifs uniquement. |
|
||||
| `order` | string | Non | Champ de tri \(par exemple, volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | Non | Direction de tri \(true pour ascendant, false pour descendant\) |
|
||||
| `tagId` | string | Non | Filtrer par ID de tag |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
| `offset` | string | Non | Décalage de pagination \(ignorer ce nombre de résultats\) |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | chaîne | Non | Filtrer par statut fermé \(true/false\). Utilisez false pour les marchés actifs uniquement. |
|
||||
| `order` | chaîne | Non | Champ de tri \(ex. id, volume, liquidity\) |
|
||||
| `ascending` | chaîne | Non | Direction de tri \(true pour ascendant, false pour descendant\) |
|
||||
| `tagId` | chaîne | Non | Filtrer par ID de tag |
|
||||
| `limit` | chaîne | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
| `offset` | chaîne | Non | Décalage de pagination \(ignorer ce nombre de résultats\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
@@ -79,9 +79,9 @@ Récupérer une liste d'événements de Polymarket avec filtrage optionnel
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | Non | Filtrer par statut fermé \(true/false\). Utilisez false pour les événements actifs uniquement. |
|
||||
| `order` | string | Non | Champ de tri \(par exemple, volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `order` | string | Non | Champ de tri \(ex. : id, volume\) |
|
||||
| `ascending` | string | Non | Direction de tri \(true pour ascendant, false pour descendant\) |
|
||||
| `tagId` | string | Non | Filtrer par ID de tag |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
|
||||
@@ -120,82 +120,6 @@ Lisez les derniers messages des canaux Slack. Récupérez l'historique des conve
|
||||
| --------- | ---- | ----------- |
|
||||
| `messages` | tableau | Tableau d'objets de messages du canal |
|
||||
|
||||
### `slack_list_channels`
|
||||
|
||||
Liste tous les canaux dans un espace de travail Slack. Renvoie les canaux publics et privés auxquels le bot a accès.
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `authMethod` | chaîne | Non | Méthode d'authentification : oauth ou bot_token |
|
||||
| `botToken` | chaîne | Non | Jeton du bot pour Bot personnalisé |
|
||||
| `includePrivate` | booléen | Non | Inclure les canaux privés dont le bot est membre \(par défaut : true\) |
|
||||
| `excludeArchived` | booléen | Non | Exclure les canaux archivés \(par défaut : true\) |
|
||||
| `limit` | nombre | Non | Nombre maximum de canaux à renvoyer \(par défaut : 100, max : 200\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `channels` | tableau | Tableau d'objets de canaux de l'espace de travail |
|
||||
|
||||
### `slack_list_members`
|
||||
|
||||
Liste tous les membres (identifiants d'utilisateurs) dans un canal Slack. À utiliser avec Get User Info pour convertir les identifiants en noms.
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `authMethod` | chaîne | Non | Méthode d'authentification : oauth ou bot_token |
|
||||
| `botToken` | chaîne | Non | Jeton du bot pour Bot personnalisé |
|
||||
| `channel` | chaîne | Oui | ID du canal dont il faut lister les membres |
|
||||
| `limit` | nombre | Non | Nombre maximum de membres à renvoyer \(par défaut : 100, max : 200\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `members` | tableau | Tableau d'identifiants d'utilisateurs qui sont membres du canal \(par ex., U1234567890\) |
|
||||
|
||||
### `slack_list_users`
|
||||
|
||||
Liste tous les utilisateurs dans un espace de travail Slack. Renvoie les profils d'utilisateurs avec noms et avatars.
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `authMethod` | chaîne | Non | Méthode d'authentification : oauth ou bot_token |
|
||||
| `botToken` | chaîne | Non | Jeton du bot pour le Bot personnalisé |
|
||||
| `includeDeleted` | booléen | Non | Inclure les utilisateurs désactivés/supprimés \(par défaut : false\) |
|
||||
| `limit` | nombre | Non | Nombre maximum d'utilisateurs à renvoyer \(par défaut : 100, max : 200\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | tableau | Tableau d'objets utilisateur de l'espace de travail |
|
||||
|
||||
### `slack_get_user`
|
||||
|
||||
Obtenir des informations détaillées sur un utilisateur Slack spécifique à partir de son ID utilisateur.
|
||||
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `authMethod` | chaîne | Non | Méthode d'authentification : oauth ou bot_token |
|
||||
| `botToken` | chaîne | Non | Jeton du bot pour le Bot personnalisé |
|
||||
| `userId` | chaîne | Oui | ID utilisateur à rechercher \(par ex., U1234567890\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `user` | objet | Informations détaillées sur l'utilisateur |
|
||||
|
||||
### `slack_download`
|
||||
|
||||
Télécharger un fichier depuis Slack
|
||||
|
||||
@@ -142,11 +142,10 @@ AhrefsのSEOツールをワークフローに統合します。ドメインレ
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `target` | string | はい | 分析対象のドメイン |
|
||||
| `country` | string | いいえ | トラフィックデータの国コード(例:us、gb、de)。デフォルト:us |
|
||||
| `mode` | string | いいえ | 分析モード:domain(ドメイン全体)、prefix(URLプレフィックス)、subdomains(すべてのサブドメインを含む) |
|
||||
| `date` | string | いいえ | 履歴データの日付(YYYY-MM-DD形式、デフォルトは今日) |
|
||||
| `mode` | string | いいえ | 分析モード:domain(ドメイン全体)、prefix(URL接頭辞)、subdomains(すべてのサブドメインを含む) |
|
||||
| `date` | string | いいえ | 過去データの日付(YYYY-MM-DD形式)(デフォルト:今日) |
|
||||
| `limit` | number | いいえ | 返す結果の最大数(デフォルト:100) |
|
||||
| `offset` | number | いいえ | ページネーション用にスキップする結果の数 |
|
||||
| `select` | string | いいえ | 返すフィールドのカンマ区切りリスト(例:url,traffic,keywords,top_keyword,value)。デフォルト:url,traffic,keywords,top_keyword,value |
|
||||
| `apiKey` | string | はい | Ahrefs APIキー |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -38,15 +38,15 @@ Googleカレンダーに新しいイベントを作成する
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `calendarId` | string | いいえ | カレンダーID(デフォルトはプライマリ) |
|
||||
| `calendarId` | string | いいえ | カレンダーID(デフォルトはprimary) |
|
||||
| `summary` | string | はい | イベントのタイトル/概要 |
|
||||
| `description` | string | いいえ | イベントの説明 |
|
||||
| `location` | string | いいえ | イベントの場所 |
|
||||
| `startDateTime` | string | はい | 開始日時。タイムゾーンオフセットを含める必要があります(例:2025-06-03T10:00:00-08:00)または、timeZoneパラメータを提供する |
|
||||
| `endDateTime` | string | はい | 終了日時。タイムゾーンオフセットを含める必要があります(例:2025-06-03T11:00:00-08:00)または、timeZoneパラメータを提供する |
|
||||
| `timeZone` | string | いいえ | タイムゾーン(例:America/Los_Angeles)。日時にオフセットが含まれていない場合は必須。提供されない場合はAmerica/Los_Angelesがデフォルト。 |
|
||||
| `startDateTime` | string | はい | 開始日時(RFC3339形式、例:2025-06-03T10:00:00-08:00) |
|
||||
| `endDateTime` | string | はい | 終了日時(RFC3339形式、例:2025-06-03T11:00:00-08:00) |
|
||||
| `timeZone` | string | いいえ | タイムゾーン(例:America/Los_Angeles) |
|
||||
| `attendees` | array | いいえ | 参加者のメールアドレスの配列 |
|
||||
| `sendUpdates` | string | いいえ | 参加者への更新通知方法:all(全員)、externalOnly(外部のみ)、またはnone(なし) |
|
||||
| `sendUpdates` | string | いいえ | 参加者への更新通知方法:all、externalOnly、またはnone |
|
||||
|
||||
#### 出力
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ Google Drive内のファイルとフォルダを一覧表示する
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `folderSelector` | string | いいえ | ファイルを一覧表示するフォルダを選択 |
|
||||
| `folderId` | string | いいえ | ファイルを一覧表示するフォルダのID(内部使用) |
|
||||
| `query` | string | いいえ | ファイル名でフィルタリングする検索語(例:「budget」は名前に「budget」を含むファイルを検索)。ここではGoogle Driveのクエリ構文を使用しないでください - 単純な検索語を提供してください。 |
|
||||
| `pageSize` | number | いいえ | 返すファイルの最大数(デフォルト:100) |
|
||||
| `query` | string | いいえ | ファイルをフィルタリングするためのクエリ |
|
||||
| `pageSize` | number | いいえ | 返すファイルの数 |
|
||||
| `pageToken` | string | いいえ | ページネーションに使用するページトークン |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -88,8 +88,8 @@ Google Sheetsスプレッドシートからデータを読み取る
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | はい | スプレッドシートのID(URLで確認できます: docs.google.com/spreadsheets/d/\{SPREADSHEET_ID\}/edit) |
|
||||
| `range` | string | いいえ | 読み取るA1表記の範囲(例: "Sheet1!A1:D10"、"A1:B5")。指定しない場合、デフォルトで最初のシートのA1:Z1000が使用されます。 |
|
||||
| `spreadsheetId` | string | はい | 読み取り元のスプレッドシートID |
|
||||
| `range` | string | いいえ | 読み取るセル範囲 |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -106,9 +106,9 @@ Google Sheetsスプレッドシートにデータを書き込む
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | はい | スプレッドシートのID |
|
||||
| `range` | string | いいえ | 書き込むA1表記の範囲(例: "Sheet1!A1:D10"、"A1:B5") |
|
||||
| `values` | array | はい | 2次元配列(例: \[\["名前", "年齢"\], \["Alice", 30\], \["Bob", 25\]\])またはオブジェクトの配列として書き込むデータ |
|
||||
| `spreadsheetId` | string | はい | 書き込み先のスプレッドシートID |
|
||||
| `range` | string | いいえ | 書き込むセル範囲 |
|
||||
| `values` | array | はい | スプレッドシートに書き込むデータ |
|
||||
| `valueInputOption` | string | いいえ | 書き込むデータの形式 |
|
||||
| `includeValuesInResponse` | boolean | いいえ | レスポンスに書き込まれた値を含めるかどうか |
|
||||
|
||||
@@ -131,8 +131,8 @@ Google Sheetsスプレッドシートのデータを更新する
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | はい | 更新するスプレッドシートのID |
|
||||
| `range` | string | いいえ | 更新するA1表記の範囲(例: "Sheet1!A1:D10"、"A1:B5") |
|
||||
| `values` | array | はい | 2次元配列(例: \[\["名前", "年齢"\], \["Alice", 30\]\])またはオブジェクトの配列として更新するデータ |
|
||||
| `range` | string | いいえ | 更新するセルの範囲 |
|
||||
| `values` | array | はい | スプレッドシートに更新するデータ |
|
||||
| `valueInputOption` | string | いいえ | 更新するデータの形式 |
|
||||
| `includeValuesInResponse` | boolean | いいえ | レスポンスに更新された値を含めるかどうか |
|
||||
|
||||
@@ -154,11 +154,11 @@ Google Sheetsスプレッドシートの末尾にデータを追加する
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | はい | 追加先のスプレッドシートID |
|
||||
| `range` | string | いいえ | 追加するセル範囲 |
|
||||
| `values` | array | はい | 追加するデータ |
|
||||
| `spreadsheetId` | string | はい | 追加先のスプレッドシートのID |
|
||||
| `range` | string | いいえ | 追加する位置の後のセル範囲 |
|
||||
| `values` | array | はい | スプレッドシートに追加するデータ |
|
||||
| `valueInputOption` | string | いいえ | 追加するデータの形式 |
|
||||
| `insertDataOption` | string | いいえ | データの挿入方法(上書きまたは行の挿入) |
|
||||
| `insertDataOption` | string | いいえ | データの挿入方法(OVERWRITE または INSERT_ROWS) |
|
||||
| `includeValuesInResponse` | boolean | いいえ | レスポンスに追加された値を含めるかどうか |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
title: Google スライド
|
||||
description: プレゼンテーションの読み取り、作成、編集
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="google_slides"
|
||||
color="#E0E0E0"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Google スライド](https://slides.google.com)は、ユーザーがリアルタイムでスライドショーを作成、編集、共同作業、プレゼンテーションできるダイナミッククラウドベースのプレゼンテーションアプリケーションです。Googleの生産性スイートの一部として、Google スライドは魅力的なプレゼンテーションの設計、他者との共同作業、クラウドを通じたコンテンツのシームレスな共有のための柔軟なプラットフォームを提供します。
|
||||
|
||||
Simで Google スライドツールを統合して、自動化されたワークフローの一部としてプレゼンテーションを簡単に管理する方法を学びましょう。Simを使用すると、エージェントや自動化されたプロセスを通じて直接Google スライドプレゼンテーションの読み取り、書き込み、作成、更新ができ、最新情報の配信、カスタムレポートの生成、またはプログラムによるブランド化されたデッキの作成が容易になります。
|
||||
|
||||
Google スライドでは、次のことができます:
|
||||
|
||||
- **プレゼンテーションの作成と編集**:テーマ、レイアウト、マルチメディアコンテンツを使用して視覚的に魅力的なスライドをデザイン
|
||||
- **リアルタイムでの共同作業**:チームメイトと同時に作業し、コメントを残し、タスクを割り当て、プレゼンテーションに関するライブフィードバックを受け取る
|
||||
- **どこでもプレゼンテーション**:オンラインまたはオフラインでプレゼンテーションを表示し、リンクを共有したり、ウェブに公開したりする
|
||||
- **画像やリッチコンテンツの追加**:画像、グラフィック、チャート、動画を挿入してプレゼンテーションを魅力的にする
|
||||
- **他のサービスとの統合**:Google ドライブ、ドキュメント、スプレッドシート、その他のサードパーティツールとシームレスに接続
|
||||
- **あらゆるデバイスからアクセス**:最大限の柔軟性を得るために、デスクトップ、ラップトップ、タブレット、モバイルデバイスでGoogle スライドを使用
|
||||
|
||||
Simでは、Google スライド統合によりエージェントがプログラムでプレゼンテーションファイルと直接やり取りできるようになります。スライドコンテンツの読み取り、新しいスライドや画像の挿入、デッキ全体のテキスト置換、新しいプレゼンテーションの生成、スライドサムネイルの取得などのタスクを自動化します。これにより、コンテンツ作成のスケーリング、プレゼンテーションの最新状態の維持、自動化されたドキュメントワークフローへの組み込みが可能になります。SimとGoogle スライドを接続することで、AIによるプレゼンテーション管理を促進し、手動の労力なしにプレゼンテーションの生成、更新、または情報の抽出を簡単に行うことができます。
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## 使用方法
|
||||
|
||||
Google スライドをワークフローに統合します。プレゼンテーションの読み取り、書き込み、作成、テキストの置換、スライドの追加、画像の追加、サムネイルの取得が可能です。
|
||||
|
||||
## ツール
|
||||
|
||||
### `google_slides_read`
|
||||
|
||||
Google スライドプレゼンテーションからコンテンツを読み取る
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | はい | 読み取るプレゼンテーションのID |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slides` | json | コンテンツを含むスライドの配列 |
|
||||
| `metadata` | json | ID、タイトル、URLを含むプレゼンテーションのメタデータ |
|
||||
|
||||
### `google_slides_write`
|
||||
|
||||
Google スライドプレゼンテーションにコンテンツを書き込みまたは更新する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | はい | 書き込み先のプレゼンテーションのID |
|
||||
| `content` | string | はい | スライドに書き込むコンテンツ |
|
||||
| `slideIndex` | number | いいえ | 書き込み先のスライドのインデックス(デフォルトは最初のスライド) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `updatedContent` | boolean | プレゼンテーションのコンテンツが正常に更新されたかどうかを示す |
|
||||
| `metadata` | json | ID、タイトル、URLを含む更新されたプレゼンテーションのメタデータ |
|
||||
|
||||
### `google_slides_create`
|
||||
|
||||
新しいGoogle スライドプレゼンテーションを作成する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | はい | 作成するプレゼンテーションのタイトル |
|
||||
| `content` | string | いいえ | 最初のスライドに追加するコンテンツ |
|
||||
| `folderSelector` | string | いいえ | プレゼンテーションを作成するフォルダを選択 |
|
||||
| `folderId` | string | いいえ | プレゼンテーションを作成するフォルダのID(内部使用) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `metadata` | json | 作成されたプレゼンテーションのメタデータ(ID、タイトル、URLを含む) |
|
||||
|
||||
### `google_slides_replace_all_text`
|
||||
|
||||
Google Slidesプレゼンテーション全体でテキストのすべての出現箇所を検索して置換する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | はい | プレゼンテーションのID |
|
||||
| `findText` | string | はい | 検索するテキスト(例:\{\{placeholder\}\}) |
|
||||
| `replaceText` | string | はい | 置換するテキスト |
|
||||
| `matchCase` | boolean | いいえ | 検索で大文字と小文字を区別するかどうか(デフォルト:true) |
|
||||
| `pageObjectIds` | string | いいえ | 特定のスライドに置換を制限するスライドオブジェクトIDのカンマ区切りリスト(すべてのスライドの場合は空のままにする) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `occurrencesChanged` | number | 置換されたテキストの出現回数 |
|
||||
| `metadata` | json | プレゼンテーションIDとURLを含む操作メタデータ |
|
||||
|
||||
### `google_slides_add_slide`
|
||||
|
||||
指定されたレイアウトでGoogle Slidesプレゼンテーションに新しいスライドを追加する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | はい | プレゼンテーションのID |
|
||||
| `layout` | string | いいえ | スライドの事前定義されたレイアウト(BLANK、TITLE、TITLE_AND_BODY、TITLE_ONLY、SECTION_HEADERなど)。デフォルトはBLANK。 |
|
||||
| `insertionIndex` | number | いいえ | スライドを挿入する場所を示すオプションのゼロベースのインデックス。指定されていない場合、スライドは最後に追加されます。 |
|
||||
| `placeholderIdMappings` | string | いいえ | プレースホルダーにカスタムオブジェクトIDを割り当てるプレースホルダーマッピングのJSON配列。形式:\[\{"layoutPlaceholder":\{"type":"TITLE"\},"objectId":"custom_title_id"\}\] |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slideId` | string | 新しく作成されたスライドのオブジェクトID |
|
||||
| `metadata` | json | プレゼンテーションID、レイアウト、URLを含む操作メタデータ |
|
||||
|
||||
### `google_slides_add_image`
|
||||
|
||||
Google Slidesプレゼンテーションの特定のスライドに画像を挿入する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | はい | プレゼンテーションのID |
|
||||
| `pageObjectId` | string | はい | 画像を追加するスライド/ページのオブジェクトID |
|
||||
| `imageUrl` | string | はい | 画像の公開アクセス可能なURL(PNG、JPEG、またはGIF形式、最大50MB) |
|
||||
| `width` | number | いいえ | ポイント単位の画像の幅(デフォルト:300) |
|
||||
| `height` | number | いいえ | ポイント単位の画像の高さ(デフォルト:200) |
|
||||
| `positionX` | number | いいえ | 左端からのX位置(ポイント単位)(デフォルト:100) |
|
||||
| `positionY` | number | いいえ | 上端からのY位置(ポイント単位)(デフォルト:100) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `imageId` | string | 新しく作成された画像のオブジェクトID |
|
||||
| `metadata` | json | プレゼンテーションIDと画像URLを含む操作メタデータ |
|
||||
|
||||
### `google_slides_get_thumbnail`
|
||||
|
||||
Google Slidesプレゼンテーションの特定のスライドのサムネイル画像を生成する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | はい | プレゼンテーションのID |
|
||||
| `pageObjectId` | string | はい | サムネイルを取得するスライド/ページのオブジェクトID |
|
||||
| `thumbnailSize` | string | いいえ | サムネイルのサイズ:SMALL(200px)、MEDIUM(800px)、またはLARGE(1600px)。デフォルトはMEDIUM。 |
|
||||
| `mimeType` | string | いいえ | サムネイル画像のMIMEタイプ:PNGまたはGIF。デフォルトはPNG。 |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `contentUrl` | string | サムネイル画像へのURL(30分間有効) |
|
||||
| `width` | number | サムネイルの幅(ピクセル単位) |
|
||||
| `height` | number | サムネイルの高さ(ピクセル単位) |
|
||||
| `metadata` | json | プレゼンテーションIDとページオブジェクトIDを含む操作メタデータ |
|
||||
|
||||
## 注意事項
|
||||
|
||||
- カテゴリ: `tools`
|
||||
- タイプ: `google_slides`
|
||||
@@ -361,8 +361,6 @@ incident.io内の既存のスケジュールを更新する
|
||||
| `id` | string | はい | 更新するスケジュールのID |
|
||||
| `name` | string | いいえ | スケジュールの新しい名前 |
|
||||
| `timezone` | string | いいえ | スケジュールの新しいタイムゾーン(例:America/New_York) |
|
||||
| `config` | string | いいえ | ローテーションを含むJSONフォーマットのスケジュール設定。例:\{"rotations": \[\{"name": "Primary", "users": \[\{"id": "user_id"\}\], "handover_start_at": "2024-01-01T09:00:00Z", "handovers": \[\{"interval": 1, "interval_type": "weekly"\}\]\}\]\} |
|
||||
| `Example` | string | いいえ | 説明なし |
|
||||
|
||||
#### 出力
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ Intercomで企業を作成または更新する
|
||||
| `plan` | string | いいえ | 企業のプラン名 |
|
||||
| `size` | number | いいえ | 企業の従業員数 |
|
||||
| `industry` | string | いいえ | 企業が事業を展開している業界 |
|
||||
| `monthly_spend` | number | いいえ | 企業があなたのビジネスにもたらす収益額。注:このフィールドは小数点以下を切り捨てて整数にします(例:155.98は155になります) |
|
||||
| `monthly_spend` | number | いいえ | 企業があなたのビジネスにもたらす収益額 |
|
||||
| `custom_attributes` | string | いいえ | JSONオブジェクトとしてのカスタム属性 |
|
||||
|
||||
#### 出力
|
||||
@@ -196,7 +196,7 @@ IDによってIntercomから単一の企業を取得する
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
ページネーションをサポートしてIntercomからすべての企業を一覧表示します。注:このエンドポイントはページネーションを使用して返すことができる企業数が10,000社に制限されています。10,000社を超えるデータセットの場合は、代わりにスクロールAPIを使用してください。
|
||||
ページネーションサポート付きでIntercomからすべての企業を一覧表示
|
||||
|
||||
#### 入力
|
||||
|
||||
@@ -259,8 +259,8 @@ IDによりIntercomから単一の会話を取得
|
||||
| `conversationId` | string | はい | 返信する会話ID |
|
||||
| `message_type` | string | はい | メッセージタイプ:「comment」または「note」 |
|
||||
| `body` | string | はい | 返信の本文テキスト |
|
||||
| `admin_id` | string | いいえ | 返信を作成する管理者のID。提供されない場合、デフォルトの管理者(オペレーター/Fin)が使用されます。 |
|
||||
| `attachment_urls` | string | いいえ | 画像URLのカンマ区切りリスト(最大10件) |
|
||||
| `admin_id` | string | はい | 返信を作成する管理者のID |
|
||||
| `attachment_urls` | string | いいえ | カンマ区切りの画像URL一覧(最大10件) |
|
||||
|
||||
#### 出力
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Kalshi
|
||||
description: Kalshiの予測市場にアクセスして取引する
|
||||
description: Kalshiの予測市場データにアクセス
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -27,7 +27,7 @@ SimにおけるKalshiの統合により、以下のことが可能になりま
|
||||
|
||||
## 使用方法
|
||||
|
||||
Kalshiの予測市場をワークフローに統合します。市場一覧、個別市場、イベント一覧、個別イベント、残高、ポジション、注文、注文板、取引履歴、ローソク足チャート、約定履歴、シリーズ、取引所ステータスの取得、および取引の発注/キャンセル/変更が可能です。
|
||||
Kalshi予測市場をワークフローに統合します。市場一覧、特定の市場、イベント一覧、特定のイベント、残高、ポジション、注文、注文板、取引、ローソク足チャート、約定、シリーズ、取引所のステータスを取得できます。
|
||||
|
||||
## ツール
|
||||
|
||||
@@ -172,52 +172,37 @@ Kalshiからオープンポジションを取得
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 注文データとメタデータ |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | はい | あなたのKalshi APIキーID |
|
||||
| `privateKey` | string | はい | あなたのRSA秘密鍵(PEM形式) |
|
||||
| `orderId` | string | はい | 取得する注文ID |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 注文データ |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
特定の市場の注文板(yesとnoの注文)を取得する
|
||||
特定のマーケットの注文板(買い注文と売り注文)を取得する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | はい | 市場ティッカー(例:KXBTC-24DEC31) |
|
||||
| `ticker` | string | はい | マーケットティッカー(例:KXBTC-24DEC31) |
|
||||
| `depth` | number | いいえ | 各サイドで返す価格レベルの数 |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 注文板データとメタデータ |
|
||||
| `output` | object | オーダーブックデータとメタデータ |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
すべての市場の最近の取引を取得する
|
||||
すべての市場または特定の市場における最近の取引を取得する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | いいえ | 結果の数(1-1000、デフォルト:100) |
|
||||
| `cursor` | string | いいえ | 次のページのページネーションカーソル |
|
||||
| `ticker` | string | いいえ | 市場ティッカーでフィルタリング |
|
||||
| `minTs` | number | いいえ | 最小タイムスタンプ(Unixミリ秒) |
|
||||
| `maxTs` | number | いいえ | 最大タイムスタンプ(Unixミリ秒) |
|
||||
| `limit` | string | いいえ | 結果数(1-1000、デフォルト:100) |
|
||||
| `cursor` | string | いいえ | 次ページのページネーションカーソル |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -236,9 +221,9 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `seriesTicker` | string | はい | シリーズティッカー |
|
||||
| `ticker` | string | はい | 市場ティッカー(例:KXBTC-24DEC31) |
|
||||
| `startTs` | number | はい | 開始タイムスタンプ(Unix秒) |
|
||||
| `endTs` | number | はい | 終了タイムスタンプ(Unix秒) |
|
||||
| `periodInterval` | number | はい | 期間間隔:1(1分)、60(1時間)、または1440(1日) |
|
||||
| `startTs` | number | いいえ | 開始タイムスタンプ(Unixミリ秒) |
|
||||
| `endTs` | number | いいえ | 終了タイムスタンプ(Unixミリ秒) |
|
||||
| `periodInterval` | number | いいえ | 期間間隔:1(1分)、60(1時間)、または1440(1日) |
|
||||
|
||||
#### 出力
|
||||
|
||||
@@ -249,19 +234,19 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
あなたのポートフォリオを取得する
|
||||
ポートフォリオを取得する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | はい | あなたのKalshi APIキーID |
|
||||
| `privateKey` | string | はい | あなたのRSA秘密鍵(PEM形式) |
|
||||
| `ticker` | string | いいえ | 市場ティッカーでフィルタリング |
|
||||
| `keyId` | string | はい | Kalshi API キーID |
|
||||
| `privateKey` | string | はい | RSA秘密鍵(PEM形式) |
|
||||
| `ticker` | string | いいえ | マーケットティッカーでフィルタリング |
|
||||
| `orderId` | string | いいえ | 注文IDでフィルタリング |
|
||||
| `minTs` | number | いいえ | 最小タイムスタンプ(Unixミリ秒) |
|
||||
| `maxTs` | number | いいえ | 最大タイムスタンプ(Unixミリ秒) |
|
||||
| `limit` | string | いいえ | 結果の数(1-1000、デフォルト:100) |
|
||||
| `limit` | string | いいえ | 結果数(1-1000、デフォルト:100) |
|
||||
| `cursor` | string | いいえ | 次ページのページネーションカーソル |
|
||||
|
||||
#### 出力
|
||||
@@ -273,7 +258,7 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
ティッカーで特定の市場シリーズの詳細を取得する
|
||||
ティッカーで特定のマーケットシリーズの詳細を取得する
|
||||
|
||||
#### 入力
|
||||
|
||||
@@ -304,89 +289,6 @@ Kalshi取引所の現在のステータス(取引と取引所のアクティ
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 取引所ステータスデータとメタデータ |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
Kalshi予測市場に新しい注文を作成する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | はい | あなたのKalshi APIキーID |
|
||||
| `privateKey` | string | はい | あなたのRSA秘密鍵(PEM形式) |
|
||||
| `ticker` | string | はい | 市場ティッカー(例:KXBTC-24DEC31) |
|
||||
| `side` | string | はい | 注文のサイド:'yes'または'no' |
|
||||
| `action` | string | はい | アクションタイプ:'buy'または'sell' |
|
||||
| `count` | string | はい | 契約数(最小1) |
|
||||
| `type` | string | いいえ | 注文タイプ:'limit'または'market'(デフォルト:limit) |
|
||||
| `yesPrice` | string | いいえ | Yesの価格(セント単位、1-99) |
|
||||
| `noPrice` | string | いいえ | Noの価格(セント単位、1-99) |
|
||||
| `yesPriceDollars` | string | いいえ | Yesの価格(ドル単位、例:"0.56") |
|
||||
| `noPriceDollars` | string | いいえ | Noの価格(ドル単位、例:"0.56") |
|
||||
| `clientOrderId` | string | いいえ | カスタム注文識別子 |
|
||||
| `expirationTs` | string | いいえ | 注文有効期限のUnixタイムスタンプ |
|
||||
| `timeInForce` | string | いいえ | 有効期間:'fill_or_kill'、'good_till_canceled'、'immediate_or_cancel' |
|
||||
| `buyMaxCost` | string | いいえ | 最大コスト(セント単位、自動的にfill_or_killを有効にする) |
|
||||
| `postOnly` | string | いいえ | メーカーオンリー注文の場合は'true'に設定 |
|
||||
| `reduceOnly` | string | いいえ | ポジション削減のみの場合は'true'に設定 |
|
||||
| `selfTradePreventionType` | string | いいえ | 自己取引防止:'taker_at_cross'または'maker' |
|
||||
| `orderGroupId` | string | いいえ | 関連する注文グループID |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 作成された注文データ |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
Kalshiで既存の注文をキャンセルする
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | はい | あなたのKalshi APIキーID |
|
||||
| `privateKey` | string | はい | あなたのRSA秘密鍵(PEM形式) |
|
||||
| `orderId` | string | はい | キャンセルする注文ID |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | キャンセルされた注文データ |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
Kalshiで既存の注文の価格または数量を変更する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | はい | あなたのKalshi APIキーID |
|
||||
| `privateKey` | string | はい | あなたのRSA秘密鍵(PEM形式) |
|
||||
| `orderId` | string | はい | 変更する注文ID |
|
||||
| `ticker` | string | はい | マーケットティッカー |
|
||||
| `side` | string | はい | 注文のサイド:'yes'または'no' |
|
||||
| `action` | string | はい | アクションタイプ:'buy'または'sell' |
|
||||
| `clientOrderId` | string | はい | クライアントが指定した元の注文ID |
|
||||
| `updatedClientOrderId` | string | はい | 変更後のクライアントが指定した新しい注文ID |
|
||||
| `count` | string | いいえ | 注文の更新された数量 |
|
||||
| `yesPrice` | string | いいえ | 更新されたyes価格(セント単位、1-99) |
|
||||
| `noPrice` | string | いいえ | 更新されたno価格(セント単位、1-99) |
|
||||
| `yesPriceDollars` | string | いいえ | 更新されたyes価格(ドル単位、例:"0.56") |
|
||||
| `noPriceDollars` | string | いいえ | 更新されたno価格(ドル単位、例:"0.56") |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 変更された注文データ |
|
||||
|
||||
## 注意事項
|
||||
|
||||
- カテゴリー: `tools`
|
||||
|
||||
@@ -40,8 +40,8 @@ Polymarketから予測市場のリストをオプションのフィルタリン
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | いいえ | クローズ状態でフィルタリング(true/false)。アクティブなマーケットのみの場合はfalseを使用。 |
|
||||
| `order` | string | いいえ | ソートフィールド(例:volumeNum、liquidityNum、startDate、endDate、createdAt) |
|
||||
| `closed` | string | いいえ | クローズ状態でフィルタリング(true/false)。アクティブな市場のみの場合はfalseを使用。 |
|
||||
| `order` | string | いいえ | ソートフィールド(例:id、volume、liquidity) |
|
||||
| `ascending` | string | いいえ | ソート方向(昇順の場合はtrue、降順の場合はfalse) |
|
||||
| `tagId` | string | いいえ | タグIDでフィルタリング |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(推奨:25-50) |
|
||||
@@ -81,7 +81,7 @@ Polymarketからイベントのリストを取得し、オプションでフィ
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | いいえ | クローズ状態でフィルタリング(true/false)。アクティブなイベントのみの場合はfalseを使用。 |
|
||||
| `order` | string | いいえ | ソートフィールド(例:volume、liquidity、startDate、endDate、createdAt) |
|
||||
| `order` | string | いいえ | ソートフィールド(例:id、volume) |
|
||||
| `ascending` | string | いいえ | ソート方向(昇順の場合はtrue、降順の場合はfalse) |
|
||||
| `tagId` | string | いいえ | タグIDでフィルタリング |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(推奨:25-50) |
|
||||
|
||||
@@ -119,82 +119,6 @@ Slackチャンネルから最新のメッセージを読み取ります。フィ
|
||||
| --------- | ---- | ----------- |
|
||||
| `messages` | array | チャンネルからのメッセージオブジェクトの配列 |
|
||||
|
||||
### `slack_list_channels`
|
||||
|
||||
Slackワークスペース内のすべてのチャンネルを一覧表示します。ボットがアクセスできるパブリックチャンネルとプライベートチャンネルを返します。
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauthまたはbot_token |
|
||||
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
|
||||
| `includePrivate` | boolean | いいえ | ボットがメンバーであるプライベートチャンネルを含める(デフォルト:true) |
|
||||
| `excludeArchived` | boolean | いいえ | アーカイブされたチャンネルを除外する(デフォルト:true) |
|
||||
| `limit` | number | いいえ | 返すチャンネルの最大数(デフォルト:100、最大:200) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `channels` | array | ワークスペースからのチャンネルオブジェクトの配列 |
|
||||
|
||||
### `slack_list_members`
|
||||
|
||||
Slackチャンネル内のすべてのメンバー(ユーザーID)を一覧表示します。IDを名前に解決するには「ユーザー情報を取得」と併用してください。
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauthまたはbot_token |
|
||||
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
|
||||
| `channel` | string | はい | メンバーを一覧表示するチャンネルID |
|
||||
| `limit` | number | いいえ | 返すメンバーの最大数(デフォルト:100、最大:200) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `members` | array | チャンネルのメンバーであるユーザーIDの配列(例:U1234567890) |
|
||||
|
||||
### `slack_list_users`
|
||||
|
||||
Slackワークスペース内のすべてのユーザーを一覧表示します。名前とアバターを含むユーザープロファイルを返します。
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauthまたはbot_token |
|
||||
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
|
||||
| `includeDeleted` | boolean | いいえ | 無効化/削除されたユーザーを含める(デフォルト:false) |
|
||||
| `limit` | number | いいえ | 返すユーザーの最大数(デフォルト:100、最大:200) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | ワークスペースからのユーザーオブジェクトの配列 |
|
||||
|
||||
### `slack_get_user`
|
||||
|
||||
ユーザーIDを使用して特定のSlackユーザーに関する詳細情報を取得します。
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauthまたはbot_token |
|
||||
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
|
||||
| `userId` | string | はい | 検索するユーザーID(例:U1234567890) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `user` | object | 詳細なユーザー情報 |
|
||||
|
||||
### `slack_download`
|
||||
|
||||
Slackからファイルをダウンロードする
|
||||
@@ -216,13 +140,13 @@ Slackからファイルをダウンロードする
|
||||
|
||||
### `slack_update_message`
|
||||
|
||||
Slackでボットが以前に送信したメッセージを更新する
|
||||
Slackでボットが以前送信したメッセージを更新する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauthまたはbot_token |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauth または bot_token |
|
||||
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
|
||||
| `channel` | string | はい | メッセージが投稿されたチャンネルID(例:C1234567890) |
|
||||
| `timestamp` | string | はい | 更新するメッセージのタイムスタンプ(例:1405894322.002768) |
|
||||
@@ -232,19 +156,19 @@ Slackでボットが以前に送信したメッセージを更新する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `message` | object | Slackから返されたすべてのプロパティを含む完全な更新メッセージオブジェクト |
|
||||
| `message` | object | Slackから返されたすべてのプロパティを含む完全に更新されたメッセージオブジェクト |
|
||||
| `content` | string | 成功メッセージ |
|
||||
| `metadata` | object | 更新されたメッセージのメタデータ |
|
||||
|
||||
### `slack_delete_message`
|
||||
|
||||
Slackでボットが以前に送信したメッセージを削除する
|
||||
Slackでボットが以前送信したメッセージを削除する
|
||||
|
||||
#### 入力
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauthまたはbot_token |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauth または bot_token |
|
||||
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
|
||||
| `channel` | string | はい | メッセージが投稿されたチャンネルID(例:C1234567890) |
|
||||
| `timestamp` | string | はい | 削除するメッセージのタイムスタンプ(例:1405894322.002768) |
|
||||
@@ -264,7 +188,7 @@ Slackメッセージに絵文字リアクションを追加する
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauthまたはbot_token |
|
||||
| `authMethod` | string | いいえ | 認証方法:oauth または bot_token |
|
||||
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
|
||||
| `channel` | string | はい | メッセージが投稿されたチャンネルID(例:C1234567890) |
|
||||
| `timestamp` | string | はい | リアクションするメッセージのタイムスタンプ(例:1405894322.002768) |
|
||||
|
||||
@@ -140,14 +140,13 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `target` | 字符串 | 是 | 要分析的目标域名 |
|
||||
| `country` | 字符串 | 否 | 流量数据的国家代码 \(例如:us, gb, de\)。默认值:us |
|
||||
| `mode` | 字符串 | 否 | 分析模式:domain \(整个域名\), prefix \(URL 前缀\), subdomains \(包含所有子域名\) |
|
||||
| `date` | 字符串 | 否 | 历史数据的日期,格式为 YYYY-MM-DD \(默认为今天\) |
|
||||
| `limit` | 数字 | 否 | 返回结果的最大数量 \(默认值:100\) |
|
||||
| `offset` | 数字 | 否 | 分页时跳过的结果数量 |
|
||||
| `select` | 字符串 | 否 | 要返回的字段的逗号分隔列表 \(例如:url,traffic,keywords,top_keyword,value\)。默认值:url,traffic,keywords,top_keyword,value |
|
||||
| `apiKey` | 字符串 | 是 | Ahrefs API 密钥 |
|
||||
| `target` | string | 是 | 要分析的目标域名 |
|
||||
| `country` | string | 否 | 流量数据的国家代码 \(例如:us, gb, de\)。默认值:us |
|
||||
| `mode` | string | 否 | 分析模式:domain \(整个域名\), prefix \(URL 前缀\), subdomains \(包含所有子域名\) |
|
||||
| `date` | string | 否 | 历史数据的日期,格式为 YYYY-MM-DD \(默认为今天\) |
|
||||
| `limit` | number | 否 | 返回结果的最大数量 \(默认值:100\) |
|
||||
| `offset` | number | 否 | 分页时跳过的结果数量 |
|
||||
| `apiKey` | string | 是 | Ahrefs API 密钥 |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -38,15 +38,15 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `calendarId` | string | 否 | 日历 ID \(默认为主日历\) |
|
||||
| `summary` | string | 是 | 活动标题/摘要 |
|
||||
| `description` | string | 否 | 活动描述 |
|
||||
| `location` | string | 否 | 活动地点 |
|
||||
| `startDateTime` | string | 是 | 开始日期和时间。必须包含时区偏移 \(例如:2025-06-03T10:00:00-08:00\) 或提供 timeZone 参数 |
|
||||
| `endDateTime` | string | 是 | 结束日期和时间。必须包含时区偏移 \(例如:2025-06-03T11:00:00-08:00\) 或提供 timeZone 参数 |
|
||||
| `timeZone` | string | 否 | 时区 \(例如:America/Los_Angeles\)。如果日期时间未包含偏移,则必需提供。如果未提供,默认为 America/Los_Angeles。 |
|
||||
| `calendarId` | string | 否 | 日历 ID(默认为主日历) |
|
||||
| `summary` | string | 是 | 事件标题/摘要 |
|
||||
| `description` | string | 否 | 事件描述 |
|
||||
| `location` | string | 否 | 事件地点 |
|
||||
| `startDateTime` | string | 是 | 开始日期和时间(RFC3339 格式,例如:2025-06-03T10:00:00-08:00) |
|
||||
| `endDateTime` | string | 是 | 结束日期和时间(RFC3339 格式,例如:2025-06-03T11:00:00-08:00) |
|
||||
| `timeZone` | string | 否 | 时区(例如:America/Los_Angeles) |
|
||||
| `attendees` | array | 否 | 参与者电子邮件地址数组 |
|
||||
| `sendUpdates` | string | 否 | 如何向参与者发送更新:all、externalOnly 或 none |
|
||||
| `sendUpdates` | string | 否 | 向参与者发送更新的方式:all、externalOnly 或 none |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `folderSelector` | string | 否 | 选择要列出文件的文件夹 |
|
||||
| `folderId` | string | 否 | 要列出文件的文件夹 ID(内部使用) |
|
||||
| `query` | string | 否 | 用于按名称过滤文件的搜索词(例如,"budget" 会找到名称中包含 "budget" 的文件)。不要在此处使用 Google Drive 查询语法 - 只需提供一个普通的搜索词即可。 |
|
||||
| `pageSize` | number | 否 | 要返回的最大文件数(默认值:100) |
|
||||
| `query` | string | 否 | 用于过滤文件的查询 |
|
||||
| `pageSize` | number | 否 | 要返回的文件数量 |
|
||||
| `pageToken` | string | 否 | 用于分页的页面令牌 |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -88,8 +88,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | 是 | 电子表格的 ID \(可在 URL 中找到:docs.google.com/spreadsheets/d/\{SPREADSHEET_ID\}/edit\)。 |
|
||||
| `range` | string | 否 | 要读取的 A1 表示法范围 \(例如 "Sheet1!A1:D10", "A1:B5"\)。如果未指定,默认为第一个工作表 A1:Z1000。 |
|
||||
| `spreadsheetId` | string | 是 | 要读取的电子表格的 ID |
|
||||
| `range` | string | 否 | 要读取的单元格范围 |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -106,9 +106,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | 是 | 电子表格的 ID |
|
||||
| `range` | string | 否 | 要写入的 A1 表示法范围 \(例如 "Sheet1!A1:D10", "A1:B5"\) |
|
||||
| `values` | array | 是 | 要写入的数据,格式为二维数组 \(例如 \[\["Name", "Age"\], \["Alice", 30\], \["Bob", 25\]\]\) 或对象数组。 |
|
||||
| `spreadsheetId` | string | 是 | 要写入的电子表格的 ID |
|
||||
| `range` | string | 否 | 要写入的单元格范围 |
|
||||
| `values` | array | 是 | 要写入电子表格的数据 |
|
||||
| `valueInputOption` | string | 否 | 要写入数据的格式 |
|
||||
| `includeValuesInResponse` | boolean | 否 | 是否在响应中包含写入的值 |
|
||||
|
||||
@@ -131,10 +131,10 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | 是 | 要更新的电子表格的 ID |
|
||||
| `range` | string | 否 | 要更新的 A1 表示法范围 \(例如 "Sheet1!A1:D10", "A1:B5"\) |
|
||||
| `values` | array | 是 | 要更新的数据,格式为二维数组 \(例如 \[\["Name", "Age"\], \["Alice", 30\]\]\) 或对象数组。 |
|
||||
| `range` | string | 否 | 要更新的单元格范围 |
|
||||
| `values` | array | 是 | 要更新到电子表格中的数据 |
|
||||
| `valueInputOption` | string | 否 | 要更新数据的格式 |
|
||||
| `includeValuesInResponse` | boolean | 否 | 是否在响应中包含更新的值 |
|
||||
| `includeValuesInResponse` | boolean | 否 | 是否在响应中包含更新后的值 |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -154,12 +154,12 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `spreadsheetId` | string | 是 | 要追加数据的电子表格的 ID |
|
||||
| `range` | string | 否 | 要追加数据的单元格范围 \(例如:"Sheet1", "Sheet1!A:D"\) |
|
||||
| `values` | array | 是 | 要追加的数据,格式为二维数组 \(例如:\[\["Alice", 30\], \["Bob", 25\]\]\) 或对象数组。 |
|
||||
| `spreadsheetId` | string | 是 | 要追加的电子表格的 ID |
|
||||
| `range` | string | 否 | 要在其后追加的单元格范围 |
|
||||
| `values` | array | 是 | 要追加到电子表格的数据 |
|
||||
| `valueInputOption` | string | 否 | 要追加数据的格式 |
|
||||
| `insertDataOption` | string | 否 | 数据插入方式 \(OVERWRITE 或 INSERT_ROWS\) |
|
||||
| `includeValuesInResponse` | boolean | 否 | 是否在响应中包含追加后的值 |
|
||||
| `insertDataOption` | string | 否 | 如何插入数据 \(OVERWRITE 或 INSERT_ROWS\) |
|
||||
| `includeValuesInResponse` | boolean | 否 | 是否在响应中包含追加的值 |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
title: Google Slides
|
||||
description: 阅读、编写和创建演示文稿
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="google_slides"
|
||||
color="#E0E0E0"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[Google Slides](https://slides.google.com) 是一个动态的基于云的演示文稿应用程序,允许用户实时创建、编辑、协作和展示幻灯片。作为 Google 生产力套件的一部分,Google Slides 提供了一个灵活的平台,用于设计引人入胜的演示文稿,与他人协作,并通过云无缝共享内容。
|
||||
|
||||
了解如何在 Sim 中集成 Google Slides 工具,以轻松管理作为自动化工作流程一部分的演示文稿。通过 Sim,您可以直接通过代理和自动化流程读取、编写、创建和更新 Google Slides 演示文稿,从而轻松传递最新信息、生成自定义报告或以编程方式制作品牌幻灯片。
|
||||
|
||||
使用 Google Slides,您可以:
|
||||
|
||||
- **创建和编辑演示文稿**:使用主题、布局和多媒体内容设计视觉吸引力的幻灯片
|
||||
- **实时协作**:与团队成员同时工作,评论、分配任务并实时接收演示文稿的反馈
|
||||
- **随时随地展示**:在线或离线展示演示文稿,分享链接或发布到网络
|
||||
- **添加图片和丰富内容**:插入图片、图形、图表和视频,使您的演示文稿更具吸引力
|
||||
- **与其他服务集成**:与 Google Drive、Docs、Sheets 和其他第三方工具无缝连接
|
||||
- **从任何设备访问**:在台式机、笔记本电脑、平板电脑和移动设备上使用 Google Slides,最大限度地提高灵活性
|
||||
|
||||
在 Sim 中,Google Slides 集成使您的代理能够以编程方式直接与演示文稿文件交互。自动化任务如读取幻灯片内容、插入新幻灯片或图片、替换整个幻灯片中的文本、生成新演示文稿以及检索幻灯片缩略图。这使您能够扩展内容创建,保持演示文稿的最新状态,并将其嵌入到自动化文档工作流程中。通过将 Sim 与 Google Slides 连接,您可以实现 AI 驱动的演示文稿管理——轻松生成、更新或从演示文稿中提取信息,而无需手动操作。
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## 使用说明
|
||||
|
||||
将 Google 幻灯片集成到工作流程中。可以读取、写入、创建演示文稿,替换文本,添加幻灯片,添加图片,以及获取缩略图。
|
||||
|
||||
## 工具
|
||||
|
||||
### `google_slides_read`
|
||||
|
||||
从 Google 幻灯片演示文稿中读取内容
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | 是 | 要读取的演示文稿的 ID |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slides` | json | 包含内容的幻灯片数组 |
|
||||
| `metadata` | json | 包括 ID、标题和 URL 的演示文稿元数据 |
|
||||
|
||||
### `google_slides_write`
|
||||
|
||||
在 Google 幻灯片演示文稿中写入或更新内容
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | 是 | 要写入的演示文稿的 ID |
|
||||
| `content` | string | 是 | 要写入幻灯片的内容 |
|
||||
| `slideIndex` | number | 否 | 要写入的幻灯片索引 \(默认为第一张幻灯片\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `updatedContent` | boolean | 表示演示文稿内容是否成功更新 |
|
||||
| `metadata` | json | 更新后的演示文稿元数据,包括 ID、标题和 URL |
|
||||
|
||||
### `google_slides_create`
|
||||
|
||||
创建一个新的 Google 幻灯片演示文稿
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `title` | string | 是 | 要创建的演示文稿的标题 |
|
||||
| `content` | string | 否 | 要添加到第一张幻灯片的内容 |
|
||||
| `folderSelector` | string | 否 | 选择创建演示文稿的文件夹 |
|
||||
| `folderId` | string | 否 | 用于创建演示文稿的文件夹 ID \(内部使用\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `metadata` | json | 创建的演示文稿元数据,包括 ID、标题和 URL |
|
||||
|
||||
### `google_slides_replace_all_text`
|
||||
|
||||
在 Google 幻灯片演示文稿中查找并替换所有文本
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | 是 | 演示文稿的 ID |
|
||||
| `findText` | string | 是 | 要查找的文本 \(例如,\{\{placeholder\}\}\) |
|
||||
| `replaceText` | string | 是 | 要替换的文本 |
|
||||
| `matchCase` | boolean | 否 | 是否区分大小写 \(默认值:true\) |
|
||||
| `pageObjectIds` | string | 否 | 用逗号分隔的幻灯片对象 ID 列表,用于限制替换到特定幻灯片 \(留空表示所有幻灯片\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `occurrencesChanged` | number | 替换的文本出现次数 |
|
||||
| `metadata` | json | 操作元数据,包括演示文稿 ID 和 URL |
|
||||
|
||||
### `google_slides_add_slide`
|
||||
|
||||
向 Google 幻灯片演示文稿添加具有指定布局的新幻灯片
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | 是 | 演示文稿的 ID |
|
||||
| `layout` | string | 否 | 幻灯片的预定义布局 \(BLANK, TITLE, TITLE_AND_BODY, TITLE_ONLY, SECTION_HEADER 等\)。默认为 BLANK。 |
|
||||
| `insertionIndex` | number | 否 | 可选的从零开始的索引,指示插入幻灯片的位置。如果未指定,则幻灯片添加到末尾。 |
|
||||
| `placeholderIdMappings` | string | 否 | JSON 数组的占位符映射,用于为占位符分配自定义对象 ID。格式:\[\{"layoutPlaceholder":\{"type":"TITLE"\},"objectId":"custom_title_id"\}\] |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `slideId` | string | 新创建幻灯片的对象 ID |
|
||||
| `metadata` | json | 操作元数据,包括演示文稿 ID、布局和 URL |
|
||||
|
||||
### `google_slides_add_image`
|
||||
|
||||
在 Google 幻灯片演示文稿中的特定幻灯片中插入图片
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | 是 | 演示文稿的 ID |
|
||||
| `pageObjectId` | string | 是 | 要添加图片的幻灯片/页面的对象 ID |
|
||||
| `imageUrl` | string | 是 | 图片的公开可访问 URL(必须是 PNG、JPEG 或 GIF,最大 50MB) |
|
||||
| `width` | number | 否 | 图片的宽度(单位:点,默认值:300) |
|
||||
| `height` | number | 否 | 图片的高度(单位:点,默认值:200) |
|
||||
| `positionX` | number | 否 | 距离左边缘的 X 位置(单位:点,默认值:100) |
|
||||
| `positionY` | number | 否 | 距离顶部边缘的 Y 位置(单位:点,默认值:100) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `imageId` | string | 新创建图片的对象 ID |
|
||||
| `metadata` | json | 操作元数据,包括演示文稿 ID 和图片 URL |
|
||||
|
||||
### `google_slides_get_thumbnail`
|
||||
|
||||
生成 Google 幻灯片演示文稿中特定幻灯片的缩略图
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `presentationId` | string | 是 | 演示文稿的 ID |
|
||||
| `pageObjectId` | string | 是 | 要获取缩略图的幻灯片/页面的对象 ID |
|
||||
| `thumbnailSize` | string | 否 | 缩略图的大小:SMALL(200px)、MEDIUM(800px)或 LARGE(1600px)。默认为 MEDIUM。 |
|
||||
| `mimeType` | string | 否 | 缩略图图像的 MIME 类型:PNG 或 GIF。默认为 PNG。 |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `contentUrl` | string | 缩略图图像的 URL(有效期为 30 分钟) |
|
||||
| `width` | number | 缩略图的宽度(以像素为单位) |
|
||||
| `height` | number | 缩略图的高度(以像素为单位) |
|
||||
| `metadata` | json | 操作元数据,包括演示文稿 ID 和页面对象 ID |
|
||||
|
||||
## 注意
|
||||
|
||||
- 类别:`tools`
|
||||
- 类型:`google_slides`
|
||||
@@ -360,9 +360,7 @@ incident.io 赋能现代组织更快响应、协调团队并捕获经验教训
|
||||
| `apiKey` | string | 是 | incident.io API 密钥 |
|
||||
| `id` | string | 是 | 要更新的日程 ID |
|
||||
| `name` | string | 否 | 日程的新名称 |
|
||||
| `timezone` | string | 否 | 日程的新时区 \(例如: America/New_York\) |
|
||||
| `config` | string | 否 | 以 JSON 字符串形式表示的日程配置,包括轮换。例如: \{"rotations": \[\{"name": "Primary", "users": \[\{"id": "user_id"\}\], "handover_start_at": "2024-01-01T09:00:00Z", "handovers": \[\{"interval": 1, "interval_type": "weekly"\}\]\}\]\} |
|
||||
| `Example` | string | 否 | 无描述 |
|
||||
| `timezone` | string | 否 | 日程的新时区 \(例如,America/New_York\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| `plan` | string | 否 | 公司计划名称 |
|
||||
| `size` | number | 否 | 公司员工数量 |
|
||||
| `industry` | string | 否 | 公司所属行业 |
|
||||
| `monthly_spend` | number | 否 | 公司为您的业务创造的收入。注意:此字段会将浮点数截断为整数(例如,155.98 会变为 155) |
|
||||
| `monthly_spend` | number | 否 | 公司为您的业务创造的收入 |
|
||||
| `custom_attributes` | string | 否 | 作为 JSON 对象的自定义属性 |
|
||||
|
||||
#### 输出
|
||||
@@ -196,7 +196,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
### `intercom_list_companies`
|
||||
|
||||
列出来自 Intercom 的所有公司,支持分页。注意:此端点限制为通过分页返回最多 10,000 家公司。对于超过 10,000 家公司的数据集,请改用 Scroll API。
|
||||
列出 Intercom 中的所有公司,并支持分页
|
||||
|
||||
#### 输入
|
||||
|
||||
@@ -254,12 +254,12 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| 参数 | 类型 | 必填 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `conversationId` | string | 是 | 要回复的会话 ID |
|
||||
| `conversationId` | string | 是 | 要回复的对话 ID |
|
||||
| `message_type` | string | 是 | 消息类型:"comment" 或 "note" |
|
||||
| `body` | string | 是 | 回复的正文文本 |
|
||||
| `admin_id` | string | 否 | 撰写回复的管理员 ID。如果未提供,将使用默认管理员(Operator/Fin)。 |
|
||||
| `body` | string | 是 | 回复的文本内容 |
|
||||
| `admin_id` | string | 是 | 撰写回复的管理员 ID |
|
||||
| `attachment_urls` | string | 否 | 逗号分隔的图片 URL 列表(最多 10 个) |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Kalshi
|
||||
description: 访问预测市场并在 Kalshi 上进行交易
|
||||
description: 访问 Kalshi 的预测市场数据
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -27,7 +27,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
## 使用说明
|
||||
|
||||
将 Kalshi 预测市场集成到工作流程中。可以获取市场、单个市场、事件、单个事件、余额、头寸、订单、订单簿、交易、K线图、成交、系列、交易所状态,并进行下单/取消/修改交易。
|
||||
将 Kalshi 预测市场集成到工作流中。可以获取市场、单个市场、事件、单个事件、余额、头寸、订单、订单簿、交易、K 线、成交、系列和交易所状态。
|
||||
|
||||
## 工具
|
||||
|
||||
@@ -172,34 +172,16 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 订单数据和元数据 |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
通过 ID 从 Kalshi 检索特定订单的详细信息
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | 是 | 您的 Kalshi API 密钥 ID |
|
||||
| `privateKey` | string | 是 | 您的 RSA 私钥 \(PEM 格式\) |
|
||||
| `orderId` | string | 是 | 要检索的订单 ID |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 订单数据 |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
检索特定市场的订单簿(买入和卖出报价)
|
||||
检索特定市场的订单簿(买入和卖出)
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | 是 | 市场代码 \(例如:KXBTC-24DEC31\) |
|
||||
| `ticker` | string | 是 | 市场代码 \(例如,KXBTC-24DEC31\) |
|
||||
| `depth` | number | 否 | 每侧返回的价格级别数量 |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -210,12 +192,15 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
检索所有市场的最近交易
|
||||
获取所有市场或特定市场的最近交易记录
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `ticker` | string | 否 | 按市场代码过滤 |
|
||||
| `minTs` | number | 否 | 最小时间戳 \(Unix 毫秒\) |
|
||||
| `maxTs` | number | 否 | 最大时间戳 \(Unix 毫秒\) |
|
||||
| `limit` | string | 否 | 结果数量 \(1-1000,默认值:100\) |
|
||||
| `cursor` | string | 否 | 下一页的分页游标 |
|
||||
|
||||
@@ -228,7 +213,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
检索特定市场的 OHLC 蜡烛图数据
|
||||
获取特定市场的 OHLC 蜡烛图数据
|
||||
|
||||
#### 输入
|
||||
|
||||
@@ -236,16 +221,16 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `seriesTicker` | string | 是 | 系列代码 |
|
||||
| `ticker` | string | 是 | 市场代码 \(例如:KXBTC-24DEC31\) |
|
||||
| `startTs` | number | 是 | 开始时间戳 \(Unix 秒\) |
|
||||
| `endTs` | number | 是 | 结束时间戳 \(Unix 秒\) |
|
||||
| `periodInterval` | number | 是 | 时间间隔:1 \(1分钟\), 60 \(1小时\), 或 1440 \(1天\) |
|
||||
| `startTs` | number | 否 | 开始时间戳 \(Unix 毫秒\) |
|
||||
| `endTs` | number | 否 | 结束时间戳 \(Unix 毫秒\) |
|
||||
| `periodInterval` | number | 否 | 时间间隔:1 \(1分钟\),60 \(1小时\),或 1440 \(1天\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 蜡烛图数据和元数据 |
|
||||
| `output` | object | K线数据和元数据 |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
@@ -256,12 +241,12 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | 是 | 您的 Kalshi API 密钥 ID |
|
||||
| `privateKey` | string | 是 | 您的 RSA 私钥 \(PEM 格式\) |
|
||||
| `ticker` | string | 否 | 按市场代码过滤 |
|
||||
| `orderId` | string | 否 | 按订单 ID 过滤 |
|
||||
| `minTs` | number | 否 | 最小时间戳 \(Unix 毫秒\) |
|
||||
| `maxTs` | number | 否 | 最大时间戳 \(Unix 毫秒\) |
|
||||
| `limit` | string | 否 | 结果数量 \(1-1000,默认值:100\) |
|
||||
| `privateKey` | string | 是 | 您的 RSA 私钥(PEM 格式)|
|
||||
| `ticker` | string | 否 | 按市场代码筛选 |
|
||||
| `orderId` | string | 否 | 按订单 ID 筛选 |
|
||||
| `minTs` | number | 否 | 最小时间戳(Unix 毫秒)|
|
||||
| `maxTs` | number | 否 | 最大时间戳(Unix 毫秒)|
|
||||
| `limit` | string | 否 | 结果数量(1-1000,默认值:100)|
|
||||
| `cursor` | string | 否 | 下一页的分页游标 |
|
||||
|
||||
#### 输出
|
||||
@@ -269,7 +254,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 填充数据和元数据 |
|
||||
| `output` | object | 成交数据和元数据 |
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
@@ -290,7 +275,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
检索 Kalshi 交易所的当前状态(交易和交易所活动)
|
||||
检索 Kalshi 交易所的当前状态(交易和交易活动)
|
||||
|
||||
#### 输入
|
||||
|
||||
@@ -304,90 +289,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 交易所状态数据和元数据 |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
在 Kalshi 预测市场创建新订单
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | 是 | 您的 Kalshi API 密钥 ID |
|
||||
| `privateKey` | string | 是 | 您的 RSA 私钥(PEM 格式)|
|
||||
| `ticker` | string | 是 | 市场代码 \(例如:KXBTC-24DEC31\) |
|
||||
| `side` | string | 是 | 订单方向:'yes' 或 'no' |
|
||||
| `action` | string | 是 | 操作类型:'buy' 或 'sell' |
|
||||
| `count` | string | 是 | 合约数量(最少 1)|
|
||||
| `type` | string | 否 | 订单类型:'limit' 或 'market'(默认:limit)|
|
||||
| `yesPrice` | string | 否 | Yes 价格(以美分为单位,1-99)|
|
||||
| `noPrice` | string | 否 | No 价格(以美分为单位,1-99)|
|
||||
| `yesPriceDollars` | string | 否 | Yes 价格(以美元为单位,例如:"0.56")|
|
||||
| `noPriceDollars` | string | 否 | No 价格(以美元为单位,例如:"0.56")|
|
||||
| `clientOrderId` | string | 否 | 自定义订单标识符 |
|
||||
| `expirationTs` | string | 否 | 订单过期的 Unix 时间戳 |
|
||||
| `timeInForce` | string | 否 | 有效时间:'fill_or_kill','good_till_canceled','immediate_or_cancel' |
|
||||
| `buyMaxCost` | string | 否 | 最大成本(以美分为单位,自动启用 fill_or_kill)|
|
||||
| `postOnly` | string | 否 | 设置为 'true' 以仅限做市订单 |
|
||||
| `reduceOnly` | string | 否 | 设置为 'true' 以仅限减少头寸 |
|
||||
| `selfTradePreventionType` | string | 否 | 自交易预防:'taker_at_cross' 或 'maker' |
|
||||
| `orderGroupId` | string | 否 | 关联的订单组 ID |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 创建的订单数据 |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
取消 Kalshi 上的现有订单
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | 是 | 您的 Kalshi API 密钥 ID |
|
||||
| `privateKey` | string | 是 | 您的 RSA 私钥 \(PEM 格式\) |
|
||||
| `orderId` | string | 是 | 要取消的订单 ID |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 已取消的订单数据 |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
修改 Kalshi 上现有订单的价格或数量
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `keyId` | string | 是 | 您的 Kalshi API 密钥 ID |
|
||||
| `privateKey` | string | 是 | 您的 RSA 私钥 \(PEM 格式\) |
|
||||
| `orderId` | string | 是 | 要修改的订单 ID |
|
||||
| `ticker` | string | 是 | 市场代码 |
|
||||
| `side` | string | 是 | 订单方向:'yes' 或 'no' |
|
||||
| `action` | string | 是 | 操作类型:'buy' 或 'sell' |
|
||||
| `clientOrderId` | string | 是 | 原始客户指定的订单 ID |
|
||||
| `updatedClientOrderId` | string | 是 | 修改后的客户指定订单 ID |
|
||||
| `count` | string | 否 | 更新后的订单数量 |
|
||||
| `yesPrice` | string | 否 | 更新后的 yes 价格(以分为单位 \(1-99\)) |
|
||||
| `noPrice` | string | 否 | 更新后的 no 价格(以分为单位 \(1-99\)) |
|
||||
| `yesPriceDollars` | string | 否 | 更新后的 yes 价格(以美元为单位 \(例如,"0.56"\)) |
|
||||
| `noPriceDollars` | string | 否 | 更新后的 no 价格(以美元为单位 \(例如,"0.56"\)) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 修改后的订单数据 |
|
||||
|
||||
## 注意
|
||||
|
||||
- 类别:`tools`
|
||||
- 类型:`kalshi`
|
||||
- 类别: `tools`
|
||||
- 类型: `kalshi`
|
||||
|
||||
@@ -38,14 +38,14 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| 参数 | 类型 | 是否必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | 否 | 按关闭状态筛选 \(true/false\)。使用 false 仅显示活跃市场。 |
|
||||
| `order` | string | 否 | 排序字段 \(例如:volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | 否 | 排序方向 \(true 表示升序,false 表示降序\) |
|
||||
| `tagId` | string | 否 | 按标签 ID 筛选 |
|
||||
| `limit` | string | 否 | 每页结果数量 \(推荐:25-50\) |
|
||||
| `offset` | string | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
| `closed` | 字符串 | 否 | 按关闭状态筛选 \(true/false\)。使用 false 仅显示活跃市场。 |
|
||||
| `order` | 字符串 | 否 | 排序字段 \(例如,id、volume、liquidity\) |
|
||||
| `ascending` | 字符串 | 否 | 排序方向 \(true 表示升序,false 表示降序\) |
|
||||
| `tagId` | 字符串 | 否 | 按标签 ID 筛选 |
|
||||
| `limit` | 字符串 | 否 | 每页结果数量 \(建议:25-50\) |
|
||||
| `offset` | 字符串 | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
@@ -80,10 +80,10 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | 否 | 按关闭状态筛选 \(true/false\)。使用 false 仅显示活跃事件。 |
|
||||
| `order` | string | 否 | 排序字段 \(例如:volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | 否 | 排序方向 \(true 表示升序,false 表示降序\) |
|
||||
| `tagId` | string | 否 | 按标签 ID 筛选 |
|
||||
| `closed` | string | 否 | 按关闭状态过滤 \(true/false\)。使用 false 仅显示活跃事件。 |
|
||||
| `order` | string | 否 | 排序字段 \(例如,id, volume\) |
|
||||
| `ascending` | string | 否 | 排序方向 \(true 为升序,false 为降序\) |
|
||||
| `tagId` | string | 否 | 按标签 ID 过滤 |
|
||||
| `limit` | string | 否 | 每页结果数量 \(推荐:25-50\) |
|
||||
| `offset` | string | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
|
||||
|
||||
@@ -118,82 +118,6 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| --------- | ---- | ----------- |
|
||||
| `messages` | array | 频道中的消息对象数组 |
|
||||
|
||||
### `slack_list_channels`
|
||||
|
||||
列出 Slack 工作区中的所有频道。返回机器人有权限访问的公共和私人频道。
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 是否必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | 否 | 认证方法:oauth 或 bot_token |
|
||||
| `botToken` | string | 否 | 自定义 Bot 的 Bot token |
|
||||
| `includePrivate` | boolean | 否 | 包括机器人是成员的私人频道(默认:true) |
|
||||
| `excludeArchived` | boolean | 否 | 排除已归档的频道(默认:true) |
|
||||
| `limit` | number | 否 | 返回的最大频道数量(默认:100,最大:200) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `channels` | array | 工作区中的频道对象数组 |
|
||||
|
||||
### `slack_list_members`
|
||||
|
||||
列出 Slack 频道中的所有成员(用户 ID)。可与获取用户信息功能结合使用,将 ID 解析为名称。
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 是否必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | 否 | 认证方法:oauth 或 bot_token |
|
||||
| `botToken` | string | 否 | 自定义 Bot 的 Bot token |
|
||||
| `channel` | string | 是 | 要列出成员的频道 ID |
|
||||
| `limit` | number | 否 | 返回的最大成员数量(默认:100,最大:200) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `members` | array | 频道成员的用户 ID 数组(例如,U1234567890) |
|
||||
|
||||
### `slack_list_users`
|
||||
|
||||
列出 Slack 工作区中的所有用户。返回包含名称和头像的用户资料。
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 是否必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | 否 | 认证方法:oauth 或 bot_token |
|
||||
| `botToken` | string | 否 | 自定义 Bot 的令牌 |
|
||||
| `includeDeleted` | boolean | 否 | 是否包含已停用/已删除的用户(默认值:false) |
|
||||
| `limit` | number | 否 | 返回的最大用户数量(默认值:100,最大值:200) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `users` | array | 工作区中的用户对象数组 |
|
||||
|
||||
### `slack_get_user`
|
||||
|
||||
通过用户 ID 获取特定 Slack 用户的详细信息。
|
||||
|
||||
#### 输入
|
||||
|
||||
| 参数 | 类型 | 是否必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | 否 | 认证方法:oauth 或 bot_token |
|
||||
| `botToken` | string | 否 | 自定义 Bot 的令牌 |
|
||||
| `userId` | string | 是 | 要查询的用户 ID(例如,U1234567890) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `user` | object | 用户的详细信息 |
|
||||
|
||||
### `slack_download`
|
||||
|
||||
从 Slack 下载文件
|
||||
@@ -215,7 +139,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
### `slack_update_message`
|
||||
|
||||
更新由机器人在 Slack 中之前发送的消息
|
||||
更新由机器人在 Slack 中先前发送的消息
|
||||
|
||||
#### 输入
|
||||
|
||||
@@ -237,7 +161,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
### `slack_delete_message`
|
||||
|
||||
删除由机器人在 Slack 中之前发送的消息
|
||||
删除由机器人在 Slack 中先前发送的消息
|
||||
|
||||
#### 输入
|
||||
|
||||
@@ -253,7 +177,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `content` | string | 成功消息 |
|
||||
| `metadata` | object | 已删除消息的元数据 |
|
||||
| `metadata` | object | 删除的消息元数据 |
|
||||
|
||||
### `slack_add_reaction`
|
||||
|
||||
|
||||
@@ -907,56 +907,32 @@ checksums:
|
||||
content/27: 170ccdc4ce7ee086e9c6b5073efca582
|
||||
content/28: bcadfc362b69078beee0088e5936c98b
|
||||
content/29: b82def7d82657f941fbe60df3924eeeb
|
||||
content/30: 1ca7ee3856805fa1718031c5f75b6ffb
|
||||
content/31: 949801e405d87718b6c2b9059cae502e
|
||||
content/30: bcc307ab47d781529e1809281c84f6e1
|
||||
content/31: f3b1717da25bab99b686761a60f53842
|
||||
content/32: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/33: 6f837577eee86c44737b01077235690c
|
||||
content/33: a3a3035f55f9475958a212a317202cf7
|
||||
content/34: bcadfc362b69078beee0088e5936c98b
|
||||
content/35: d07ba6e7e81a6c14f4c755c5d4e1feb7
|
||||
content/36: 9885b40af8a69f3209842c6b1999e2f0
|
||||
content/37: dd1630773e623a22170424b9a9fcd8dc
|
||||
content/35: f87e976ce212bb9c7bcd83a26156de2a
|
||||
content/36: df64b205315168a0264574d76f7776ad
|
||||
content/37: 263aabafa94113ea3e562655cb92dbac
|
||||
content/38: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/39: cad87a46758853b623344e8eb6e736a9
|
||||
content/39: 5319bf5409aced353e6b9d67f597ffef
|
||||
content/40: bcadfc362b69078beee0088e5936c98b
|
||||
content/41: 55cce6895bb236266b0012554c8f9421
|
||||
content/42: a5364fa7f3eccd9d409d7a55b80ee29d
|
||||
content/43: 17e3fecb26d6cf65b93d08cc455d82c3
|
||||
content/41: d67f49be147c5ea63f88554f3b8eaed1
|
||||
content/42: e10ecb501eb65fd1a59501a40b707c7a
|
||||
content/43: d829a82e9bcbcfb6239ca2ed9e10ba77
|
||||
content/44: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/45: 77493a293ee95216ef36fe6d4aece3d1
|
||||
content/45: 33e38761b95cbf57093ae18ee93753b3
|
||||
content/46: bcadfc362b69078beee0088e5936c98b
|
||||
content/47: a9471f623e6956bed2ba9934d04129fb
|
||||
content/48: 56cfa01acbab775b52ea70fa53aa172b
|
||||
content/49: d2f31d68fc899d5806a92122600a4e80
|
||||
content/47: de2a47d11b7a6bb3bd6a2c76902c7ef2
|
||||
content/48: 359c44f8d3f1bf46e03d20d51b8a028f
|
||||
content/49: e7b1cc0780768ccbf9876e9ce76e984f
|
||||
content/50: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/51: d690cfede23e591a52765ead94ee7103
|
||||
content/51: c2843b4d30bf09aca759e5b2a064aba8
|
||||
content/52: bcadfc362b69078beee0088e5936c98b
|
||||
content/53: ffc6403a8169d47eeef5dd8674e413df
|
||||
content/54: bcc307ab47d781529e1809281c84f6e1
|
||||
content/55: f3b1717da25bab99b686761a60f53842
|
||||
content/56: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/57: a3a3035f55f9475958a212a317202cf7
|
||||
content/58: bcadfc362b69078beee0088e5936c98b
|
||||
content/59: f87e976ce212bb9c7bcd83a26156de2a
|
||||
content/60: df64b205315168a0264574d76f7776ad
|
||||
content/61: 263aabafa94113ea3e562655cb92dbac
|
||||
content/62: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/63: 5319bf5409aced353e6b9d67f597ffef
|
||||
content/64: bcadfc362b69078beee0088e5936c98b
|
||||
content/65: d67f49be147c5ea63f88554f3b8eaed1
|
||||
content/66: e10ecb501eb65fd1a59501a40b707c7a
|
||||
content/67: d829a82e9bcbcfb6239ca2ed9e10ba77
|
||||
content/68: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/69: 33e38761b95cbf57093ae18ee93753b3
|
||||
content/70: bcadfc362b69078beee0088e5936c98b
|
||||
content/71: de2a47d11b7a6bb3bd6a2c76902c7ef2
|
||||
content/72: 359c44f8d3f1bf46e03d20d51b8a028f
|
||||
content/73: e7b1cc0780768ccbf9876e9ce76e984f
|
||||
content/74: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/75: c2843b4d30bf09aca759e5b2a064aba8
|
||||
content/76: bcadfc362b69078beee0088e5936c98b
|
||||
content/77: 883dfd99c21232c8569bbdf8939f0e7e
|
||||
content/78: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/79: f80857bbc3489ef18fcaadab197e2e77
|
||||
content/53: 883dfd99c21232c8569bbdf8939f0e7e
|
||||
content/54: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/55: f80857bbc3489ef18fcaadab197e2e77
|
||||
3b259d9553c506dab1915efe6df32fc4:
|
||||
meta/title: bffce99cf04c6651f6f5e82b731c6093
|
||||
meta/description: 6ed9ca8738fc05b54b373173c32a1dea
|
||||
@@ -2802,25 +2778,25 @@ checksums:
|
||||
content/17: ea3d2fb612b08ffe583e92ecd5b3025c
|
||||
content/18: a78936adb114037a8dee267d88abb603
|
||||
content/19: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/20: b9b0012cb532c77dda6dba57fd5fb943
|
||||
content/20: 7ca1d93dc14ae39cd3b4d56ac56f93d4
|
||||
content/21: bcadfc362b69078beee0088e5936c98b
|
||||
content/22: 11a612ef04dadc8a3d55cab9afa1e4df
|
||||
content/23: ab15a93aaed1f9cef3847c4ead310fa2
|
||||
content/24: 2ebb96901a65dd1736f9dbaae7b744e6
|
||||
content/25: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/26: c83d5d790dd881ee43938bb93522e77a
|
||||
content/26: 3baeb386e740b984a8df4561397c4cfb
|
||||
content/27: bcadfc362b69078beee0088e5936c98b
|
||||
content/28: 47edc139bf3ff54edf851af50aec7c60
|
||||
content/29: 456511a19c775d0f6f7be905765f41d7
|
||||
content/30: f47eab9d86d77728d9934b080758c58b
|
||||
content/31: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/32: e76c892ef9374151ed506737008e5f18
|
||||
content/32: dfb83115b39e7d573161be0230d5bea3
|
||||
content/33: bcadfc362b69078beee0088e5936c98b
|
||||
content/34: 47edc139bf3ff54edf851af50aec7c60
|
||||
content/35: 6ec9639048ecca2d156c7be05a893934
|
||||
content/36: 247cb9d4bf4ec359896a78f037088319
|
||||
content/37: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/38: ca57a9ef1818257f362ad930e949fae3
|
||||
content/38: a32ed8d3852d56e8a6ce706e44f319ea
|
||||
content/39: bcadfc362b69078beee0088e5936c98b
|
||||
content/40: 79400769ec9ac9106489b520c4752077
|
||||
content/41: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
@@ -2882,7 +2858,7 @@ checksums:
|
||||
content/29: ea522cae48853c07c8d5d8b378769653
|
||||
content/30: e260dc1e43fda57cedb8ff50fd9ff8b0
|
||||
content/31: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/32: 9a2bf9be39ff41b735bc13e10e7920fb
|
||||
content/32: 0ec9739376bf4d1607f508a9ccd5135d
|
||||
content/33: bcadfc362b69078beee0088e5936c98b
|
||||
content/34: b949599a24b5450a297ffdbcb5bc573f
|
||||
content/35: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
@@ -2940,7 +2916,7 @@ checksums:
|
||||
content/9: 4094290e2d3e5a386eeb80bbfffdcb66
|
||||
content/10: 1cf287e27ca91288eb3ddf06d67c1bbc
|
||||
content/11: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/12: 5982e8a9d918fbf02a484fb1fa1c4022
|
||||
content/12: d06a683f0109e4cc78ca5ed12970a3ae
|
||||
content/13: bcadfc362b69078beee0088e5936c98b
|
||||
content/14: d21e4931f38f03eaafb618e0ff4734c0
|
||||
content/15: d18ba36c8cdd7aab15fa909f84a76a73
|
||||
@@ -46008,7 +45984,7 @@ checksums:
|
||||
content/113: 61f62451582748e0ed3dbc1e2fe5a85e
|
||||
content/114: 03da6d5c0e86cf220a45e3618628ed32
|
||||
content/115: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/116: e18187db483ea88bb15d78e31c705e51
|
||||
content/116: f3f04e72c93bb67b9d324c881485a501
|
||||
content/117: bcadfc362b69078beee0088e5936c98b
|
||||
content/118: 0c3b89bd00a8d061646b17910809500d
|
||||
content/119: eecf897a6786b775557f2682cc7b1c9e
|
||||
@@ -47093,7 +47069,7 @@ checksums:
|
||||
content/45: c76943404f9c8d34a85e6315359ed0c4
|
||||
content/46: b5e111e430aa1c929fb07d5844bf65eb
|
||||
content/47: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/48: 3e3ced1f6eb6c0ef39098531beb12598
|
||||
content/48: 4f3e33afca0089a4c38e600ddcf987e7
|
||||
content/49: bcadfc362b69078beee0088e5936c98b
|
||||
content/50: dbc08cce26f9565e719891bbbf4632a9
|
||||
content/51: d0ce65f5420745c45ab42b7edd135bf4
|
||||
@@ -47103,7 +47079,7 @@ checksums:
|
||||
content/55: bcadfc362b69078beee0088e5936c98b
|
||||
content/56: a7e001e39652db8eeb4d32968bda102b
|
||||
content/57: 440f2732ad006bee8cccc975fdbf673a
|
||||
content/58: 7a7048c54763b0109643f37e583381ce
|
||||
content/58: f3cc387aa35265e27be2134def8d9e23
|
||||
content/59: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/60: 5672a02f72bdb165f91b43e4ad24c4a9
|
||||
content/61: bcadfc362b69078beee0088e5936c98b
|
||||
@@ -47123,7 +47099,7 @@ checksums:
|
||||
content/75: 935f1a713d05f32d3d826434a7e715ee
|
||||
content/76: e505d8f656fb6e3b65a98cb73d744598
|
||||
content/77: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/78: e218c4f319e6a6a50c0535d3ee5e8fcf
|
||||
content/78: c963aaaebebb4ed420f776f10471a033
|
||||
content/79: bcadfc362b69078beee0088e5936c98b
|
||||
content/80: 22bd99d5b844817b808b9d0d3baddac4
|
||||
content/81: e959b48af94a559e9c46cbd7653d2dd2
|
||||
@@ -48017,7 +47993,7 @@ checksums:
|
||||
content/10: bb5d0521074c58da3f6b997faef887ae
|
||||
content/11: 147f49b8a7a56cb60c881117480a71fb
|
||||
content/12: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/13: 64096864823f19e5e62d794315454652
|
||||
content/13: d88f3b55a0e0c21884b1666d3d431d06
|
||||
content/14: bcadfc362b69078beee0088e5936c98b
|
||||
content/15: 7311861512fa50f2c937d080692151e8
|
||||
content/16: fbb677a4902291738c3a1f8b9303fd4a
|
||||
@@ -48029,7 +48005,7 @@ checksums:
|
||||
content/22: 25e7fef7953155abe4219e03058c2f94
|
||||
content/23: 1ba7d90c16ff260798c13836a74aad6a
|
||||
content/24: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/25: 0f201f6957d27eaf9d1c4b4eab6ec8f7
|
||||
content/25: 937261561e7fbca25bfa59e1761a77be
|
||||
content/26: bcadfc362b69078beee0088e5936c98b
|
||||
content/27: a88bd6e0708b4c3017be42eabc480947
|
||||
content/28: 528f164231d5bf39fcd8849734efebde
|
||||
@@ -48120,7 +48096,7 @@ checksums:
|
||||
content/113: 29208f859f7c25898a8bb435d3e744d0
|
||||
54ec89df9800159df913e0955d2775e1:
|
||||
meta/title: 6e51f3ea638199ea7733ea81e4512317
|
||||
meta/description: 0e3e222a5003cb3a73b6a37cbb3a6cbc
|
||||
meta/description: 5084d459d228b33203d57521e97a15aa
|
||||
content/0: 1b031fb0c62c46b177aeed5c3d3f8f80
|
||||
content/1: 367fa4b034d02d9123dca3dc0dcfdadf
|
||||
content/2: 9eb85e9cb9e915facdce6a4c626757a2
|
||||
@@ -48128,7 +48104,7 @@ checksums:
|
||||
content/4: 5cc47b6bd553ef5ae4e6f2a5fba3a4b1
|
||||
content/5: df92c120b414bd5409e0b2f5d12fa191
|
||||
content/6: 821e6394b0a953e2b0842b04ae8f3105
|
||||
content/7: 65ab1981394e333b75e33b02140e3931
|
||||
content/7: a9d3e8e671a399b716a03dbac5747dd3
|
||||
content/8: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
|
||||
content/9: 1249d97c2c2c96fb0dc483cdf764c103
|
||||
content/10: 14e0d19fa4c092a2db93edcc870dcf67
|
||||
@@ -48172,68 +48148,44 @@ checksums:
|
||||
content/48: 93c01d42da765b2862a1c90e5c207f3a
|
||||
content/49: bcadfc362b69078beee0088e5936c98b
|
||||
content/50: cb25800f3f5e9a5688769ba1f1d101fb
|
||||
content/51: 0440d7abaca1a48b2ce63b48c97444a1
|
||||
content/52: 4cb6ac8a34a8cc0d87a85a45dd9382b9
|
||||
content/51: 0c321044b21a59eec7b04f91d6ad521f
|
||||
content/52: e98e73fb76f28bb6c2fbc2d1aaab21f8
|
||||
content/53: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/54: df651aea48c483f6163ae49e4f1fda5a
|
||||
content/54: f130069ac3fcde2a745a1cca5dbb54b5
|
||||
content/55: bcadfc362b69078beee0088e5936c98b
|
||||
content/56: 90bd63383b8ba686aa71a192a945344a
|
||||
content/57: 0c321044b21a59eec7b04f91d6ad521f
|
||||
content/58: d8a0a8ae22b5c2a0664f1172adf2c1d1
|
||||
content/56: d6ec19129a153bc0667e76edb9c2953d
|
||||
content/57: ce48c0560ca3ddaad402f2bdd9d459f3
|
||||
content/58: b6c9dbaa63995d6b3ee642321e1a9144
|
||||
content/59: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/60: fcef3a88d9de7bfa5e308e4cece9136e
|
||||
content/60: 0eedad01ffe39ef19b5e6728aed5d2f4
|
||||
content/61: bcadfc362b69078beee0088e5936c98b
|
||||
content/62: d6ec19129a153bc0667e76edb9c2953d
|
||||
content/63: ce48c0560ca3ddaad402f2bdd9d459f3
|
||||
content/64: d96d5d108333391af5bc3ab6425c9f29
|
||||
content/62: 77916aef5f86264c96b85ab874c3c447
|
||||
content/63: 85eba1d62b7cdd083d0a4991169d7162
|
||||
content/64: d4dd284c2b688770d5c732a7e6dff3f0
|
||||
content/65: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/66: be92911dda52b245d2fda5b0c54527dc
|
||||
content/66: a76f4df74674266dabf365775e462623
|
||||
content/67: bcadfc362b69078beee0088e5936c98b
|
||||
content/68: 77916aef5f86264c96b85ab874c3c447
|
||||
content/69: 85eba1d62b7cdd083d0a4991169d7162
|
||||
content/70: d4dd284c2b688770d5c732a7e6dff3f0
|
||||
content/68: 3f6664c231057ceadd9ac8cba5332b3e
|
||||
content/69: 4936debbcf6ba547fa7c9265f7dcf40f
|
||||
content/70: 17a43ca41485e2f78ef7987c78a52e8b
|
||||
content/71: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/72: ad3910637d3e53fef449e8a5aa74b1ef
|
||||
content/72: b87a8359c3277447f29050ef280f4ceb
|
||||
content/73: bcadfc362b69078beee0088e5936c98b
|
||||
content/74: 3f6664c231057ceadd9ac8cba5332b3e
|
||||
content/75: 4936debbcf6ba547fa7c9265f7dcf40f
|
||||
content/76: 17a43ca41485e2f78ef7987c78a52e8b
|
||||
content/74: be30e8adfbab208f4b7b6fedc0559c19
|
||||
content/75: 72de310caecaf16e8e74ff37a7fa46b0
|
||||
content/76: 4738fdcfd44aa60f5bfd7f484be36360
|
||||
content/77: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/78: b87a8359c3277447f29050ef280f4ceb
|
||||
content/78: 3ff5cc143dcf2514e730b4f68757a6cb
|
||||
content/79: bcadfc362b69078beee0088e5936c98b
|
||||
content/80: be30e8adfbab208f4b7b6fedc0559c19
|
||||
content/81: 72de310caecaf16e8e74ff37a7fa46b0
|
||||
content/82: 4738fdcfd44aa60f5bfd7f484be36360
|
||||
content/80: 08aaa5e1b8c8e8c146f68228e6d53792
|
||||
content/81: a72a19e1aff8ea71d24df98f312d9ada
|
||||
content/82: 0a0fecc6d1a70497410160ee0ea9e757
|
||||
content/83: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/84: 3ff5cc143dcf2514e730b4f68757a6cb
|
||||
content/84: d71b6bb8e2dd6ce98101aec6a1dd77f2
|
||||
content/85: bcadfc362b69078beee0088e5936c98b
|
||||
content/86: 08aaa5e1b8c8e8c146f68228e6d53792
|
||||
content/87: a72a19e1aff8ea71d24df98f312d9ada
|
||||
content/88: 0a0fecc6d1a70497410160ee0ea9e757
|
||||
content/89: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/90: d71b6bb8e2dd6ce98101aec6a1dd77f2
|
||||
content/91: bcadfc362b69078beee0088e5936c98b
|
||||
content/92: 2e1b6ed9ba71ee98540f582bf5decd41
|
||||
content/93: b37060e61c0433052dd02939aa60e412
|
||||
content/94: c2184e718b1015489f82854fe48e6a37
|
||||
content/95: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/96: 574cc410d9df4f37e7dc291491e13fe6
|
||||
content/97: bcadfc362b69078beee0088e5936c98b
|
||||
content/98: b06f1d576f491777f39a47401c77f268
|
||||
content/99: 5070ca6b1d2cbd4f8450b15e8f46e446
|
||||
content/100: 3edfc5e9d7138730bb73863616f3435e
|
||||
content/101: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/102: 8d5f23e56e4606bacc7ac1f3b1cbe86b
|
||||
content/103: bcadfc362b69078beee0088e5936c98b
|
||||
content/104: 52f075cb7459e5cf8daf1f3e37ecd36b
|
||||
content/105: 82c709054f9e80781baa7d678ff03b24
|
||||
content/106: 5a38d9ff2a65641f8f13e91db263cc9e
|
||||
content/107: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/108: 71b47f694d04e8f1a8fa22723539fe47
|
||||
content/109: bcadfc362b69078beee0088e5936c98b
|
||||
content/110: 45b9563e3bcc50f89b39b015a9d46114
|
||||
content/111: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/112: cbeffb4f92b38461ac320bc6fe7f7ef0
|
||||
content/86: 2e1b6ed9ba71ee98540f582bf5decd41
|
||||
content/87: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/88: cbeffb4f92b38461ac320bc6fe7f7ef0
|
||||
d58d2e8b125a994bcfb20ff98cfef0ad:
|
||||
meta/title: 4dd6404ecdca5a70a577f481171c67d6
|
||||
meta/description: 13a0731523233bca340f1e28be2d11b3
|
||||
@@ -48783,7 +48735,7 @@ checksums:
|
||||
content/39: 671fd4e9cf20bbe08cfb3732c8b57f4c
|
||||
content/40: 74f3efc50333dc5d828aa7ae69f5c4e5
|
||||
content/41: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/42: d2244dafd79365ced4a0df1a56975ad9
|
||||
content/42: bb049223fb68bf44717cee08a563f292
|
||||
content/43: bcadfc362b69078beee0088e5936c98b
|
||||
content/44: 4ff8530d52904479669c897bce94c95f
|
||||
content/45: 7520d0a12665f8095be14087d2161e8c
|
||||
@@ -48800,60 +48752,3 @@ checksums:
|
||||
content/56: eafb7339fb5be61d3984c611d4a42e0d
|
||||
content/57: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/58: 50d2e01e0d0b0baac560a1329e628da0
|
||||
09ccda87bd3aa44885e6d15056cd082a:
|
||||
meta/title: 373ed3498c87f799ef07ffde0ea1d265
|
||||
meta/description: 36b55b049d6761b91eca1cfec3728d97
|
||||
content/0: 1b031fb0c62c46b177aeed5c3d3f8f80
|
||||
content/1: 7b05071991aa2683214ff01e1d237fd6
|
||||
content/2: 53c416611cbf8a5e2941db4958d847b2
|
||||
content/3: c19eedc77dfe7959c7a5cdba872d5dd4
|
||||
content/4: 68a9557198da5e46a16662f9bb7c0626
|
||||
content/5: 82324e9833120509db51c0630510b058
|
||||
content/6: bb72f9bd5ed494daedbba52e987f83d1
|
||||
content/7: 821e6394b0a953e2b0842b04ae8f3105
|
||||
content/8: 76805f5755b94a715d3fc5b395a77a8e
|
||||
content/9: 9c8aa3f09c9b2bd50ea4cdff3598ea4e
|
||||
content/10: 62ce37c2502d7777328da5677584797c
|
||||
content/11: 3d2e2a51ba0c5426194cb55d4da2e151
|
||||
content/12: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/13: 2d6c6be43578df4f92ad9584ae55a3b8
|
||||
content/14: bcadfc362b69078beee0088e5936c98b
|
||||
content/15: 47970143e4e6c785fd685b2636389ca7
|
||||
content/16: a36beec71cdd23691b51257f60b3c58c
|
||||
content/17: ac772dcad079ad5f9ad2b06acb54fec7
|
||||
content/18: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/19: 44d35b60f0d5b31205814c4c56b1dd3e
|
||||
content/20: bcadfc362b69078beee0088e5936c98b
|
||||
content/21: 74263e6c3dc857c8cb93a622936a8691
|
||||
content/22: 8ac211bb1cb741500d13cf250594307c
|
||||
content/23: 9aa42d507a938780e9a2fd414587f782
|
||||
content/24: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/25: eb9977313568849d57fe90b91876bda6
|
||||
content/26: bcadfc362b69078beee0088e5936c98b
|
||||
content/27: 955a86ad4d727a003152069d2ee7812d
|
||||
content/28: 1e4586fc956a22f40bcb150b194fd71d
|
||||
content/29: 3585aca0b652b03d5676cdb90ca5853b
|
||||
content/30: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/31: 0a8055f53c4c6a7c5a7531727b7c553d
|
||||
content/32: bcadfc362b69078beee0088e5936c98b
|
||||
content/33: 8a1edfff2dfbc40324b6aa1d273b6d61
|
||||
content/34: 07785703ef06035c1beb02899a167233
|
||||
content/35: bbeda043ea8bd727559a883885764a92
|
||||
content/36: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/37: 1ad77742ffd96a0290d3966fd7f4da7c
|
||||
content/38: bcadfc362b69078beee0088e5936c98b
|
||||
content/39: b342b9fbd5b7f29ab635c8d84b310aee
|
||||
content/40: c35d0fb34a4440d8059bb2b530abcb0b
|
||||
content/41: da9a94744520cda2a28f21fee724cc76
|
||||
content/42: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/43: e17e13843e3f670e50a84316e495fbbe
|
||||
content/44: bcadfc362b69078beee0088e5936c98b
|
||||
content/45: fdd2b073dd551d4550f3057bbc9b40c9
|
||||
content/46: 4ca9fbd9a079b50e3720fcd493d6b1bd
|
||||
content/47: 09fbc3004cceaec5af57e0daab0faab0
|
||||
content/48: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/49: 64b7205c95437eea16e3998609a0e478
|
||||
content/50: bcadfc362b69078beee0088e5936c98b
|
||||
content/51: f23cc6d685827f8880df368d65c9ee88
|
||||
content/52: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/53: dce0795eb6bcefcb20aa65529826adab
|
||||
|
||||
@@ -163,7 +163,7 @@ describe('OAuth Utils', () => {
|
||||
|
||||
const result = await refreshTokenIfNeeded('request-id', mockCredential, 'credential-id')
|
||||
|
||||
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token', undefined)
|
||||
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token')
|
||||
expect(mockDb.update).toHaveBeenCalled()
|
||||
expect(mockDb.set).toHaveBeenCalled()
|
||||
expect(result).toEqual({ accessToken: 'new-token', refreshed: true })
|
||||
@@ -251,7 +251,7 @@ describe('OAuth Utils', () => {
|
||||
|
||||
const token = await refreshAccessTokenIfNeeded('credential-id', 'test-user-id', 'request-id')
|
||||
|
||||
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token', undefined)
|
||||
expect(mockRefreshOAuthToken).toHaveBeenCalledWith('google', 'refresh-token')
|
||||
expect(mockDb.update).toHaveBeenCalled()
|
||||
expect(mockDb.set).toHaveBeenCalled()
|
||||
expect(token).toBe('new-token')
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { db } from '@sim/db'
|
||||
import { member, user, userStats } from '@sim/db/schema'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import {
|
||||
member,
|
||||
organization,
|
||||
subscription as subscriptionTable,
|
||||
user,
|
||||
userStats,
|
||||
} from '@sim/db/schema'
|
||||
import { and, eq, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getUserUsageData } from '@/lib/billing/core/usage'
|
||||
import { removeUserFromOrganization } from '@/lib/billing/organizations/membership'
|
||||
import { requireStripeClient } from '@/lib/billing/stripe-client'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('OrganizationMemberAPI')
|
||||
@@ -35,6 +41,7 @@ export async function GET(
|
||||
const url = new URL(request.url)
|
||||
const includeUsage = url.searchParams.get('include') === 'usage'
|
||||
|
||||
// Verify user has access to this organization
|
||||
const userMember = await db
|
||||
.select()
|
||||
.from(member)
|
||||
@@ -51,6 +58,7 @@ export async function GET(
|
||||
const userRole = userMember[0].role
|
||||
const hasAdminAccess = ['owner', 'admin'].includes(userRole)
|
||||
|
||||
// Get target member details
|
||||
const memberQuery = db
|
||||
.select({
|
||||
id: member.id,
|
||||
@@ -72,6 +80,7 @@ export async function GET(
|
||||
return NextResponse.json({ error: 'Member not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Check if user can view this member's details
|
||||
const canViewDetails = hasAdminAccess || session.user.id === memberId
|
||||
|
||||
if (!canViewDetails) {
|
||||
@@ -80,6 +89,7 @@ export async function GET(
|
||||
|
||||
let memberData = memberEntry[0]
|
||||
|
||||
// Include usage data if requested and user has permission
|
||||
if (includeUsage && hasAdminAccess) {
|
||||
const usageData = await db
|
||||
.select({
|
||||
@@ -171,6 +181,7 @@ export async function PUT(
|
||||
return NextResponse.json({ error: 'Forbidden - Admin access required' }, { status: 403 })
|
||||
}
|
||||
|
||||
// Check if target member exists
|
||||
const targetMember = await db
|
||||
.select()
|
||||
.from(member)
|
||||
@@ -181,10 +192,12 @@ export async function PUT(
|
||||
return NextResponse.json({ error: 'Member not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Prevent changing owner role
|
||||
if (targetMember[0].role === 'owner') {
|
||||
return NextResponse.json({ error: 'Cannot change owner role' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Prevent non-owners from promoting to admin
|
||||
if (role === 'admin' && userMember[0].role !== 'owner') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Only owners can promote members to admin' },
|
||||
@@ -192,10 +205,12 @@ export async function PUT(
|
||||
)
|
||||
}
|
||||
|
||||
// Prevent admins from changing other admins' roles - only owners can modify admin roles
|
||||
if (targetMember[0].role === 'admin' && userMember[0].role !== 'owner') {
|
||||
return NextResponse.json({ error: 'Only owners can change admin roles' }, { status: 403 })
|
||||
}
|
||||
|
||||
// Update member role
|
||||
const updatedMember = await db
|
||||
.update(member)
|
||||
.set({ role })
|
||||
@@ -249,8 +264,9 @@ export async function DELETE(
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id: organizationId, memberId: targetUserId } = await params
|
||||
const { id: organizationId, memberId } = await params
|
||||
|
||||
// Verify user has admin access
|
||||
const userMember = await db
|
||||
.select()
|
||||
.from(member)
|
||||
@@ -265,54 +281,209 @@ export async function DELETE(
|
||||
}
|
||||
|
||||
const canRemoveMembers =
|
||||
['owner', 'admin'].includes(userMember[0].role) || session.user.id === targetUserId
|
||||
['owner', 'admin'].includes(userMember[0].role) || session.user.id === memberId
|
||||
|
||||
if (!canRemoveMembers) {
|
||||
return NextResponse.json({ error: 'Forbidden - Insufficient permissions' }, { status: 403 })
|
||||
}
|
||||
|
||||
// Check if target member exists
|
||||
const targetMember = await db
|
||||
.select({ id: member.id, role: member.role })
|
||||
.select()
|
||||
.from(member)
|
||||
.where(and(eq(member.organizationId, organizationId), eq(member.userId, targetUserId)))
|
||||
.where(and(eq(member.organizationId, organizationId), eq(member.userId, memberId)))
|
||||
.limit(1)
|
||||
|
||||
if (targetMember.length === 0) {
|
||||
return NextResponse.json({ error: 'Member not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const result = await removeUserFromOrganization({
|
||||
userId: targetUserId,
|
||||
organizationId,
|
||||
memberId: targetMember[0].id,
|
||||
})
|
||||
// Prevent removing the owner
|
||||
if (targetMember[0].role === 'owner') {
|
||||
return NextResponse.json({ error: 'Cannot remove organization owner' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
if (result.error === 'Cannot remove organization owner') {
|
||||
return NextResponse.json({ error: result.error }, { status: 400 })
|
||||
// Capture departed member's usage and reset their cost to prevent double billing
|
||||
try {
|
||||
const departingUserStats = await db
|
||||
.select({ currentPeriodCost: userStats.currentPeriodCost })
|
||||
.from(userStats)
|
||||
.where(eq(userStats.userId, memberId))
|
||||
.limit(1)
|
||||
|
||||
if (departingUserStats.length > 0 && departingUserStats[0].currentPeriodCost) {
|
||||
const usage = Number.parseFloat(departingUserStats[0].currentPeriodCost)
|
||||
if (usage > 0) {
|
||||
await db
|
||||
.update(organization)
|
||||
.set({
|
||||
departedMemberUsage: sql`${organization.departedMemberUsage} + ${usage}`,
|
||||
})
|
||||
.where(eq(organization.id, organizationId))
|
||||
|
||||
await db
|
||||
.update(userStats)
|
||||
.set({ currentPeriodCost: '0' })
|
||||
.where(eq(userStats.userId, memberId))
|
||||
|
||||
logger.info('Captured departed member usage and reset user cost', {
|
||||
organizationId,
|
||||
memberId,
|
||||
usage,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (result.error === 'Member not found') {
|
||||
return NextResponse.json({ error: result.error }, { status: 404 })
|
||||
}
|
||||
return NextResponse.json({ error: result.error }, { status: 500 })
|
||||
} catch (usageCaptureError) {
|
||||
logger.error('Failed to capture departed member usage', {
|
||||
organizationId,
|
||||
memberId,
|
||||
error: usageCaptureError,
|
||||
})
|
||||
}
|
||||
|
||||
// Remove member
|
||||
const removedMember = await db
|
||||
.delete(member)
|
||||
.where(and(eq(member.organizationId, organizationId), eq(member.userId, memberId)))
|
||||
.returning()
|
||||
|
||||
if (removedMember.length === 0) {
|
||||
return NextResponse.json({ error: 'Failed to remove member' }, { status: 500 })
|
||||
}
|
||||
|
||||
logger.info('Organization member removed', {
|
||||
organizationId,
|
||||
removedMemberId: targetUserId,
|
||||
removedMemberId: memberId,
|
||||
removedBy: session.user.id,
|
||||
wasSelfRemoval: session.user.id === targetUserId,
|
||||
billingActions: result.billingActions,
|
||||
wasSelfRemoval: session.user.id === memberId,
|
||||
})
|
||||
|
||||
// If the removed user left their last paid team and has a personal Pro set to cancel_at_period_end, restore it
|
||||
try {
|
||||
const remainingPaidTeams = await db
|
||||
.select({ orgId: member.organizationId })
|
||||
.from(member)
|
||||
.where(eq(member.userId, memberId))
|
||||
|
||||
let hasAnyPaidTeam = false
|
||||
if (remainingPaidTeams.length > 0) {
|
||||
const orgIds = remainingPaidTeams.map((m) => m.orgId)
|
||||
const orgPaidSubs = await db
|
||||
.select()
|
||||
.from(subscriptionTable)
|
||||
.where(and(eq(subscriptionTable.status, 'active'), eq(subscriptionTable.plan, 'team')))
|
||||
|
||||
hasAnyPaidTeam = orgPaidSubs.some((s) => orgIds.includes(s.referenceId))
|
||||
}
|
||||
|
||||
if (!hasAnyPaidTeam) {
|
||||
const personalProRows = await db
|
||||
.select()
|
||||
.from(subscriptionTable)
|
||||
.where(
|
||||
and(
|
||||
eq(subscriptionTable.referenceId, memberId),
|
||||
eq(subscriptionTable.status, 'active'),
|
||||
eq(subscriptionTable.plan, 'pro')
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
const personalPro = personalProRows[0]
|
||||
if (
|
||||
personalPro &&
|
||||
personalPro.cancelAtPeriodEnd === true &&
|
||||
personalPro.stripeSubscriptionId
|
||||
) {
|
||||
try {
|
||||
const stripe = requireStripeClient()
|
||||
await stripe.subscriptions.update(personalPro.stripeSubscriptionId, {
|
||||
cancel_at_period_end: false,
|
||||
})
|
||||
} catch (stripeError) {
|
||||
logger.error('Stripe restore cancel_at_period_end failed for personal Pro', {
|
||||
userId: memberId,
|
||||
stripeSubscriptionId: personalPro.stripeSubscriptionId,
|
||||
error: stripeError,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await db
|
||||
.update(subscriptionTable)
|
||||
.set({ cancelAtPeriodEnd: false })
|
||||
.where(eq(subscriptionTable.id, personalPro.id))
|
||||
|
||||
logger.info('Restored personal Pro after leaving last paid team', {
|
||||
userId: memberId,
|
||||
personalSubscriptionId: personalPro.id,
|
||||
})
|
||||
} catch (dbError) {
|
||||
logger.error('DB update failed when restoring personal Pro', {
|
||||
userId: memberId,
|
||||
subscriptionId: personalPro.id,
|
||||
error: dbError,
|
||||
})
|
||||
}
|
||||
|
||||
// Also restore the snapshotted Pro usage back to currentPeriodCost
|
||||
try {
|
||||
const userStatsRows = await db
|
||||
.select({
|
||||
currentPeriodCost: userStats.currentPeriodCost,
|
||||
proPeriodCostSnapshot: userStats.proPeriodCostSnapshot,
|
||||
})
|
||||
.from(userStats)
|
||||
.where(eq(userStats.userId, memberId))
|
||||
.limit(1)
|
||||
|
||||
if (userStatsRows.length > 0) {
|
||||
const currentUsage = userStatsRows[0].currentPeriodCost || '0'
|
||||
const snapshotUsage = userStatsRows[0].proPeriodCostSnapshot || '0'
|
||||
|
||||
const currentNum = Number.parseFloat(currentUsage)
|
||||
const snapshotNum = Number.parseFloat(snapshotUsage)
|
||||
const restoredUsage = (currentNum + snapshotNum).toString()
|
||||
|
||||
await db
|
||||
.update(userStats)
|
||||
.set({
|
||||
currentPeriodCost: restoredUsage,
|
||||
proPeriodCostSnapshot: '0', // Clear the snapshot
|
||||
})
|
||||
.where(eq(userStats.userId, memberId))
|
||||
|
||||
logger.info('Restored Pro usage after leaving team', {
|
||||
userId: memberId,
|
||||
previousUsage: currentUsage,
|
||||
snapshotUsage: snapshotUsage,
|
||||
restoredUsage: restoredUsage,
|
||||
})
|
||||
}
|
||||
} catch (usageRestoreError) {
|
||||
logger.error('Failed to restore Pro usage after leaving team', {
|
||||
userId: memberId,
|
||||
error: usageRestoreError,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (postRemoveError) {
|
||||
logger.error('Post-removal personal Pro restore check failed', {
|
||||
organizationId,
|
||||
memberId,
|
||||
error: postRemoveError,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message:
|
||||
session.user.id === targetUserId
|
||||
session.user.id === memberId
|
||||
? 'You have left the organization'
|
||||
: 'Member removed successfully',
|
||||
data: {
|
||||
removedMemberId: targetUserId,
|
||||
removedMemberId: memberId,
|
||||
removedBy: session.user.id,
|
||||
removedAt: new Date().toISOString(),
|
||||
},
|
||||
|
||||
@@ -7,48 +7,20 @@
|
||||
* Set ADMIN_API_KEY environment variable and use x-admin-key header.
|
||||
*
|
||||
* Endpoints:
|
||||
*
|
||||
* Users:
|
||||
* GET /api/v1/admin/users - List all users
|
||||
* GET /api/v1/admin/users/:id - Get user details
|
||||
* GET /api/v1/admin/users/:id/billing - Get user billing info
|
||||
* PATCH /api/v1/admin/users/:id/billing - Update user billing (limit, blocked)
|
||||
* POST /api/v1/admin/users/:id/billing/move-to-org - Move user to organization
|
||||
*
|
||||
* Workspaces:
|
||||
* GET /api/v1/admin/workspaces - List all workspaces
|
||||
* GET /api/v1/admin/workspaces/:id - Get workspace details
|
||||
* GET /api/v1/admin/workspaces/:id/workflows - List workspace workflows
|
||||
* DELETE /api/v1/admin/workspaces/:id/workflows - Delete all workspace workflows
|
||||
* GET /api/v1/admin/workspaces/:id/folders - List workspace folders
|
||||
* GET /api/v1/admin/workspaces/:id/export - Export workspace (ZIP/JSON)
|
||||
* POST /api/v1/admin/workspaces/:id/import - Import into workspace
|
||||
*
|
||||
* Workflows:
|
||||
* GET /api/v1/admin/workflows - List all workflows
|
||||
* GET /api/v1/admin/workflows/:id - Get workflow details
|
||||
* DELETE /api/v1/admin/workflows/:id - Delete workflow
|
||||
* GET /api/v1/admin/workflows/:id/export - Export workflow (JSON)
|
||||
* POST /api/v1/admin/workflows/import - Import single workflow
|
||||
*
|
||||
* Organizations:
|
||||
* GET /api/v1/admin/organizations - List all organizations
|
||||
* GET /api/v1/admin/organizations/:id - Get organization details
|
||||
* PATCH /api/v1/admin/organizations/:id - Update organization
|
||||
* GET /api/v1/admin/organizations/:id/members - List organization members
|
||||
* POST /api/v1/admin/organizations/:id/members - Add member to organization
|
||||
* GET /api/v1/admin/organizations/:id/members/:mid - Get member details
|
||||
* PATCH /api/v1/admin/organizations/:id/members/:mid - Update member role
|
||||
* DELETE /api/v1/admin/organizations/:id/members/:mid - Remove member
|
||||
* GET /api/v1/admin/organizations/:id/billing - Get org billing summary
|
||||
* PATCH /api/v1/admin/organizations/:id/billing - Update org usage limit
|
||||
* GET /api/v1/admin/organizations/:id/seats - Get seat analytics
|
||||
* PATCH /api/v1/admin/organizations/:id/seats - Update seat count
|
||||
*
|
||||
* Subscriptions:
|
||||
* GET /api/v1/admin/subscriptions - List all subscriptions
|
||||
* GET /api/v1/admin/subscriptions/:id - Get subscription details
|
||||
* PATCH /api/v1/admin/subscriptions/:id - Update subscription
|
||||
* GET /api/v1/admin/users - List all users
|
||||
* GET /api/v1/admin/users/:id - Get user details
|
||||
* GET /api/v1/admin/workspaces - List all workspaces
|
||||
* GET /api/v1/admin/workspaces/:id - Get workspace details
|
||||
* GET /api/v1/admin/workspaces/:id/workflows - List workspace workflows
|
||||
* DELETE /api/v1/admin/workspaces/:id/workflows - Delete all workspace workflows
|
||||
* GET /api/v1/admin/workspaces/:id/folders - List workspace folders
|
||||
* GET /api/v1/admin/workspaces/:id/export - Export workspace (ZIP/JSON)
|
||||
* POST /api/v1/admin/workspaces/:id/import - Import into workspace
|
||||
* GET /api/v1/admin/workflows - List all workflows
|
||||
* GET /api/v1/admin/workflows/:id - Get workflow details
|
||||
* DELETE /api/v1/admin/workflows/:id - Delete workflow
|
||||
* GET /api/v1/admin/workflows/:id/export - Export workflow (JSON)
|
||||
* POST /api/v1/admin/workflows/import - Import single workflow
|
||||
*/
|
||||
|
||||
export type { AdminAuthFailure, AdminAuthResult, AdminAuthSuccess } from '@/app/api/v1/admin/auth'
|
||||
@@ -70,26 +42,13 @@ export type {
|
||||
AdminErrorResponse,
|
||||
AdminFolder,
|
||||
AdminListResponse,
|
||||
AdminMember,
|
||||
AdminMemberDetail,
|
||||
AdminOrganization,
|
||||
AdminOrganizationBillingSummary,
|
||||
AdminOrganizationDetail,
|
||||
AdminSeatAnalytics,
|
||||
AdminSingleResponse,
|
||||
AdminSubscription,
|
||||
AdminUser,
|
||||
AdminUserBilling,
|
||||
AdminUserBillingWithSubscription,
|
||||
AdminWorkflow,
|
||||
AdminWorkflowDetail,
|
||||
AdminWorkspace,
|
||||
AdminWorkspaceDetail,
|
||||
DbMember,
|
||||
DbOrganization,
|
||||
DbSubscription,
|
||||
DbUser,
|
||||
DbUserStats,
|
||||
DbWorkflow,
|
||||
DbWorkflowFolder,
|
||||
DbWorkspace,
|
||||
@@ -114,8 +73,6 @@ export {
|
||||
parsePaginationParams,
|
||||
parseWorkflowVariables,
|
||||
toAdminFolder,
|
||||
toAdminOrganization,
|
||||
toAdminSubscription,
|
||||
toAdminUser,
|
||||
toAdminWorkflow,
|
||||
toAdminWorkspace,
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/organizations/[id]/billing
|
||||
*
|
||||
* Get organization billing summary including usage, seats, and member data.
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminOrganizationBillingSummary>
|
||||
*
|
||||
* PATCH /api/v1/admin/organizations/[id]/billing
|
||||
*
|
||||
* Update organization billing settings.
|
||||
*
|
||||
* Body:
|
||||
* - orgUsageLimit?: number - New usage limit (null to clear)
|
||||
*
|
||||
* Response: AdminSingleResponse<{ success: true, orgUsageLimit: string | null }>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { organization } from '@sim/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { getOrganizationBillingData } from '@/lib/billing/core/organization'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
|
||||
import {
|
||||
badRequestResponse,
|
||||
internalErrorResponse,
|
||||
notFoundResponse,
|
||||
singleResponse,
|
||||
} from '@/app/api/v1/admin/responses'
|
||||
import type { AdminOrganizationBillingSummary } from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminOrganizationBillingAPI')
|
||||
|
||||
interface RouteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const GET = withAdminAuthParams<RouteParams>(async (_, context) => {
|
||||
const { id: organizationId } = await context.params
|
||||
|
||||
try {
|
||||
const billingData = await getOrganizationBillingData(organizationId)
|
||||
|
||||
if (!billingData) {
|
||||
return notFoundResponse('Organization or subscription')
|
||||
}
|
||||
|
||||
const membersOverLimit = billingData.members.filter((m) => m.isOverLimit).length
|
||||
const membersNearLimit = billingData.members.filter(
|
||||
(m) => !m.isOverLimit && m.percentUsed >= 80
|
||||
).length
|
||||
const usagePercentage =
|
||||
billingData.totalUsageLimit > 0
|
||||
? Math.round((billingData.totalCurrentUsage / billingData.totalUsageLimit) * 10000) / 100
|
||||
: 0
|
||||
|
||||
const data: AdminOrganizationBillingSummary = {
|
||||
organizationId: billingData.organizationId,
|
||||
organizationName: billingData.organizationName,
|
||||
subscriptionPlan: billingData.subscriptionPlan,
|
||||
subscriptionStatus: billingData.subscriptionStatus,
|
||||
totalSeats: billingData.totalSeats,
|
||||
usedSeats: billingData.usedSeats,
|
||||
availableSeats: billingData.totalSeats - billingData.usedSeats,
|
||||
totalCurrentUsage: billingData.totalCurrentUsage,
|
||||
totalUsageLimit: billingData.totalUsageLimit,
|
||||
minimumBillingAmount: billingData.minimumBillingAmount,
|
||||
averageUsagePerMember: billingData.averageUsagePerMember,
|
||||
usagePercentage,
|
||||
billingPeriodStart: billingData.billingPeriodStart?.toISOString() ?? null,
|
||||
billingPeriodEnd: billingData.billingPeriodEnd?.toISOString() ?? null,
|
||||
membersOverLimit,
|
||||
membersNearLimit,
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Retrieved billing summary for organization ${organizationId}`)
|
||||
|
||||
return singleResponse(data)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to get organization billing', { error, organizationId })
|
||||
return internalErrorResponse('Failed to get organization billing')
|
||||
}
|
||||
})
|
||||
|
||||
export const PATCH = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: organizationId } = await context.params
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
const [orgData] = await db
|
||||
.select()
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
if (body.orgUsageLimit !== undefined) {
|
||||
let newLimit: string | null = null
|
||||
|
||||
if (body.orgUsageLimit === null) {
|
||||
newLimit = null
|
||||
} else if (typeof body.orgUsageLimit === 'number' && body.orgUsageLimit >= 0) {
|
||||
newLimit = body.orgUsageLimit.toFixed(2)
|
||||
} else {
|
||||
return badRequestResponse('orgUsageLimit must be a non-negative number or null')
|
||||
}
|
||||
|
||||
await db
|
||||
.update(organization)
|
||||
.set({
|
||||
orgUsageLimit: newLimit,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(organization.id, organizationId))
|
||||
|
||||
logger.info(`Admin API: Updated usage limit for organization ${organizationId}`, {
|
||||
newLimit,
|
||||
})
|
||||
|
||||
return singleResponse({
|
||||
success: true,
|
||||
orgUsageLimit: newLimit,
|
||||
})
|
||||
}
|
||||
|
||||
return badRequestResponse('No valid fields to update')
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to update organization billing', { error, organizationId })
|
||||
return internalErrorResponse('Failed to update organization billing')
|
||||
}
|
||||
})
|
||||
@@ -1,251 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/organizations/[id]/members/[memberId]
|
||||
*
|
||||
* Get member details.
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminMemberDetail>
|
||||
*
|
||||
* PATCH /api/v1/admin/organizations/[id]/members/[memberId]
|
||||
*
|
||||
* Update member role.
|
||||
*
|
||||
* Body:
|
||||
* - role: string - New role ('admin' | 'member')
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminMember>
|
||||
*
|
||||
* DELETE /api/v1/admin/organizations/[id]/members/[memberId]
|
||||
*
|
||||
* Remove member from organization with full billing logic.
|
||||
* Handles departed usage capture and Pro restoration like the regular flow.
|
||||
*
|
||||
* Query Parameters:
|
||||
* - skipBillingLogic: boolean - Skip billing logic (default: false)
|
||||
*
|
||||
* Response: { success: true, memberId: string, billingActions: {...} }
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { member, organization, user, userStats } from '@sim/db/schema'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { removeUserFromOrganization } from '@/lib/billing/organizations/membership'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
|
||||
import {
|
||||
badRequestResponse,
|
||||
internalErrorResponse,
|
||||
notFoundResponse,
|
||||
singleResponse,
|
||||
} from '@/app/api/v1/admin/responses'
|
||||
import type { AdminMember, AdminMemberDetail } from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminOrganizationMemberDetailAPI')
|
||||
|
||||
interface RouteParams {
|
||||
id: string
|
||||
memberId: string
|
||||
}
|
||||
|
||||
export const GET = withAdminAuthParams<RouteParams>(async (_, context) => {
|
||||
const { id: organizationId, memberId } = await context.params
|
||||
|
||||
try {
|
||||
const [orgData] = await db
|
||||
.select({ id: organization.id })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const [memberData] = await db
|
||||
.select({
|
||||
id: member.id,
|
||||
userId: member.userId,
|
||||
organizationId: member.organizationId,
|
||||
role: member.role,
|
||||
createdAt: member.createdAt,
|
||||
userName: user.name,
|
||||
userEmail: user.email,
|
||||
currentPeriodCost: userStats.currentPeriodCost,
|
||||
currentUsageLimit: userStats.currentUsageLimit,
|
||||
lastActive: userStats.lastActive,
|
||||
billingBlocked: userStats.billingBlocked,
|
||||
})
|
||||
.from(member)
|
||||
.innerJoin(user, eq(member.userId, user.id))
|
||||
.leftJoin(userStats, eq(member.userId, userStats.userId))
|
||||
.where(and(eq(member.id, memberId), eq(member.organizationId, organizationId)))
|
||||
.limit(1)
|
||||
|
||||
if (!memberData) {
|
||||
return notFoundResponse('Member')
|
||||
}
|
||||
|
||||
const data: AdminMemberDetail = {
|
||||
id: memberData.id,
|
||||
userId: memberData.userId,
|
||||
organizationId: memberData.organizationId,
|
||||
role: memberData.role,
|
||||
createdAt: memberData.createdAt.toISOString(),
|
||||
userName: memberData.userName,
|
||||
userEmail: memberData.userEmail,
|
||||
currentPeriodCost: memberData.currentPeriodCost ?? '0',
|
||||
currentUsageLimit: memberData.currentUsageLimit,
|
||||
lastActive: memberData.lastActive?.toISOString() ?? null,
|
||||
billingBlocked: memberData.billingBlocked ?? false,
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Retrieved member ${memberId} from organization ${organizationId}`)
|
||||
|
||||
return singleResponse(data)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to get member', { error, organizationId, memberId })
|
||||
return internalErrorResponse('Failed to get member')
|
||||
}
|
||||
})
|
||||
|
||||
export const PATCH = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: organizationId, memberId } = await context.params
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
if (!body.role || !['admin', 'member'].includes(body.role)) {
|
||||
return badRequestResponse('role must be "admin" or "member"')
|
||||
}
|
||||
|
||||
const [orgData] = await db
|
||||
.select({ id: organization.id })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const [existingMember] = await db
|
||||
.select({
|
||||
id: member.id,
|
||||
userId: member.userId,
|
||||
role: member.role,
|
||||
})
|
||||
.from(member)
|
||||
.where(and(eq(member.id, memberId), eq(member.organizationId, organizationId)))
|
||||
.limit(1)
|
||||
|
||||
if (!existingMember) {
|
||||
return notFoundResponse('Member')
|
||||
}
|
||||
|
||||
if (existingMember.role === 'owner') {
|
||||
return badRequestResponse('Cannot change owner role')
|
||||
}
|
||||
|
||||
const [updated] = await db
|
||||
.update(member)
|
||||
.set({ role: body.role })
|
||||
.where(eq(member.id, memberId))
|
||||
.returning()
|
||||
|
||||
const [userData] = await db
|
||||
.select({ name: user.name, email: user.email })
|
||||
.from(user)
|
||||
.where(eq(user.id, updated.userId))
|
||||
.limit(1)
|
||||
|
||||
const data: AdminMember = {
|
||||
id: updated.id,
|
||||
userId: updated.userId,
|
||||
organizationId: updated.organizationId,
|
||||
role: updated.role,
|
||||
createdAt: updated.createdAt.toISOString(),
|
||||
userName: userData?.name ?? '',
|
||||
userEmail: userData?.email ?? '',
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Updated member ${memberId} role to ${body.role}`, {
|
||||
organizationId,
|
||||
previousRole: existingMember.role,
|
||||
})
|
||||
|
||||
return singleResponse(data)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to update member', { error, organizationId, memberId })
|
||||
return internalErrorResponse('Failed to update member')
|
||||
}
|
||||
})
|
||||
|
||||
export const DELETE = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: organizationId, memberId } = await context.params
|
||||
const url = new URL(request.url)
|
||||
const skipBillingLogic = url.searchParams.get('skipBillingLogic') === 'true'
|
||||
|
||||
try {
|
||||
const [orgData] = await db
|
||||
.select({ id: organization.id })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const [existingMember] = await db
|
||||
.select({
|
||||
id: member.id,
|
||||
userId: member.userId,
|
||||
role: member.role,
|
||||
})
|
||||
.from(member)
|
||||
.where(and(eq(member.id, memberId), eq(member.organizationId, organizationId)))
|
||||
.limit(1)
|
||||
|
||||
if (!existingMember) {
|
||||
return notFoundResponse('Member')
|
||||
}
|
||||
|
||||
const userId = existingMember.userId
|
||||
|
||||
const result = await removeUserFromOrganization({
|
||||
userId,
|
||||
organizationId,
|
||||
memberId,
|
||||
skipBillingLogic,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
if (result.error === 'Cannot remove organization owner') {
|
||||
return badRequestResponse(result.error)
|
||||
}
|
||||
if (result.error === 'Member not found') {
|
||||
return notFoundResponse('Member')
|
||||
}
|
||||
return internalErrorResponse(result.error || 'Failed to remove member')
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Removed member ${memberId} from organization ${organizationId}`, {
|
||||
userId,
|
||||
billingActions: result.billingActions,
|
||||
})
|
||||
|
||||
return singleResponse({
|
||||
success: true,
|
||||
memberId,
|
||||
userId,
|
||||
billingActions: {
|
||||
usageCaptured: result.billingActions.usageCaptured,
|
||||
proRestored: result.billingActions.proRestored,
|
||||
usageRestored: result.billingActions.usageRestored,
|
||||
skipBillingLogic,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to remove member', { error, organizationId, memberId })
|
||||
return internalErrorResponse('Failed to remove member')
|
||||
}
|
||||
})
|
||||
@@ -1,193 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/organizations/[id]/members
|
||||
*
|
||||
* List all members of an organization with their billing info.
|
||||
*
|
||||
* Query Parameters:
|
||||
* - limit: number (default: 50, max: 250)
|
||||
* - offset: number (default: 0)
|
||||
*
|
||||
* Response: AdminListResponse<AdminMemberDetail>
|
||||
*
|
||||
* POST /api/v1/admin/organizations/[id]/members
|
||||
*
|
||||
* Add a user to an organization with full billing logic.
|
||||
* Handles Pro usage snapshot and subscription cancellation like the invitation flow.
|
||||
*
|
||||
* Body:
|
||||
* - userId: string - User ID to add
|
||||
* - role: string - Role ('admin' | 'member')
|
||||
* - skipBillingLogic?: boolean - Skip Pro cancellation (default: false)
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminMember>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { member, organization, user, userStats } from '@sim/db/schema'
|
||||
import { count, eq } from 'drizzle-orm'
|
||||
import { addUserToOrganization } from '@/lib/billing/organizations/membership'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
|
||||
import {
|
||||
badRequestResponse,
|
||||
internalErrorResponse,
|
||||
listResponse,
|
||||
notFoundResponse,
|
||||
singleResponse,
|
||||
} from '@/app/api/v1/admin/responses'
|
||||
import {
|
||||
type AdminMember,
|
||||
type AdminMemberDetail,
|
||||
createPaginationMeta,
|
||||
parsePaginationParams,
|
||||
} from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminOrganizationMembersAPI')
|
||||
|
||||
interface RouteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const GET = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: organizationId } = await context.params
|
||||
const url = new URL(request.url)
|
||||
const { limit, offset } = parsePaginationParams(url)
|
||||
|
||||
try {
|
||||
const [orgData] = await db
|
||||
.select({ id: organization.id })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const [countResult, membersData] = await Promise.all([
|
||||
db.select({ count: count() }).from(member).where(eq(member.organizationId, organizationId)),
|
||||
db
|
||||
.select({
|
||||
id: member.id,
|
||||
userId: member.userId,
|
||||
organizationId: member.organizationId,
|
||||
role: member.role,
|
||||
createdAt: member.createdAt,
|
||||
userName: user.name,
|
||||
userEmail: user.email,
|
||||
currentPeriodCost: userStats.currentPeriodCost,
|
||||
currentUsageLimit: userStats.currentUsageLimit,
|
||||
lastActive: userStats.lastActive,
|
||||
billingBlocked: userStats.billingBlocked,
|
||||
})
|
||||
.from(member)
|
||||
.innerJoin(user, eq(member.userId, user.id))
|
||||
.leftJoin(userStats, eq(member.userId, userStats.userId))
|
||||
.where(eq(member.organizationId, organizationId))
|
||||
.orderBy(member.createdAt)
|
||||
.limit(limit)
|
||||
.offset(offset),
|
||||
])
|
||||
|
||||
const total = countResult[0].count
|
||||
const data: AdminMemberDetail[] = membersData.map((m) => ({
|
||||
id: m.id,
|
||||
userId: m.userId,
|
||||
organizationId: m.organizationId,
|
||||
role: m.role,
|
||||
createdAt: m.createdAt.toISOString(),
|
||||
userName: m.userName,
|
||||
userEmail: m.userEmail,
|
||||
currentPeriodCost: m.currentPeriodCost ?? '0',
|
||||
currentUsageLimit: m.currentUsageLimit,
|
||||
lastActive: m.lastActive?.toISOString() ?? null,
|
||||
billingBlocked: m.billingBlocked ?? false,
|
||||
}))
|
||||
|
||||
const pagination = createPaginationMeta(total, limit, offset)
|
||||
|
||||
logger.info(`Admin API: Listed ${data.length} members for organization ${organizationId}`)
|
||||
|
||||
return listResponse(data, pagination)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to list organization members', { error, organizationId })
|
||||
return internalErrorResponse('Failed to list organization members')
|
||||
}
|
||||
})
|
||||
|
||||
export const POST = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: organizationId } = await context.params
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
if (!body.userId || typeof body.userId !== 'string') {
|
||||
return badRequestResponse('userId is required')
|
||||
}
|
||||
|
||||
if (!body.role || !['admin', 'member'].includes(body.role)) {
|
||||
return badRequestResponse('role must be "admin" or "member"')
|
||||
}
|
||||
|
||||
const skipBillingLogic = body.skipBillingLogic === true
|
||||
|
||||
const [orgData] = await db
|
||||
.select({ id: organization.id, name: organization.name })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const [userData] = await db
|
||||
.select({ id: user.id, name: user.name, email: user.email })
|
||||
.from(user)
|
||||
.where(eq(user.id, body.userId))
|
||||
.limit(1)
|
||||
|
||||
if (!userData) {
|
||||
return notFoundResponse('User')
|
||||
}
|
||||
|
||||
const result = await addUserToOrganization({
|
||||
userId: body.userId,
|
||||
organizationId,
|
||||
role: body.role,
|
||||
skipBillingLogic,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
return badRequestResponse(result.error || 'Failed to add member')
|
||||
}
|
||||
|
||||
const data: AdminMember = {
|
||||
id: result.memberId!,
|
||||
userId: body.userId,
|
||||
organizationId,
|
||||
role: body.role,
|
||||
createdAt: new Date().toISOString(),
|
||||
userName: userData.name,
|
||||
userEmail: userData.email,
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Added user ${body.userId} to organization ${organizationId}`, {
|
||||
role: body.role,
|
||||
memberId: result.memberId,
|
||||
billingActions: result.billingActions,
|
||||
skipBillingLogic,
|
||||
})
|
||||
|
||||
return singleResponse({
|
||||
...data,
|
||||
billingActions: {
|
||||
proUsageSnapshotted: result.billingActions.proUsageSnapshotted,
|
||||
proCancelledAtPeriodEnd: result.billingActions.proCancelledAtPeriodEnd,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to add organization member', { error, organizationId })
|
||||
return internalErrorResponse('Failed to add organization member')
|
||||
}
|
||||
})
|
||||
@@ -1,140 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/organizations/[id]
|
||||
*
|
||||
* Get organization details including member count and subscription.
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminOrganizationDetail>
|
||||
*
|
||||
* PATCH /api/v1/admin/organizations/[id]
|
||||
*
|
||||
* Update organization details.
|
||||
*
|
||||
* Body:
|
||||
* - name?: string - Organization name
|
||||
* - slug?: string - Organization slug
|
||||
* - orgUsageLimit?: number - Usage limit (null to clear)
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminOrganization>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { member, organization, subscription } from '@sim/db/schema'
|
||||
import { and, count, eq } from 'drizzle-orm'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
|
||||
import {
|
||||
badRequestResponse,
|
||||
internalErrorResponse,
|
||||
notFoundResponse,
|
||||
singleResponse,
|
||||
} from '@/app/api/v1/admin/responses'
|
||||
import {
|
||||
type AdminOrganizationDetail,
|
||||
toAdminOrganization,
|
||||
toAdminSubscription,
|
||||
} from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminOrganizationDetailAPI')
|
||||
|
||||
interface RouteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const GET = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: organizationId } = await context.params
|
||||
|
||||
try {
|
||||
const [orgData] = await db
|
||||
.select()
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const [memberCountResult, subscriptionData] = await Promise.all([
|
||||
db.select({ count: count() }).from(member).where(eq(member.organizationId, organizationId)),
|
||||
db
|
||||
.select()
|
||||
.from(subscription)
|
||||
.where(and(eq(subscription.referenceId, organizationId), eq(subscription.status, 'active')))
|
||||
.limit(1),
|
||||
])
|
||||
|
||||
const data: AdminOrganizationDetail = {
|
||||
...toAdminOrganization(orgData),
|
||||
memberCount: memberCountResult[0].count,
|
||||
subscription: subscriptionData[0] ? toAdminSubscription(subscriptionData[0]) : null,
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Retrieved organization ${organizationId}`)
|
||||
|
||||
return singleResponse(data)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to get organization', { error, organizationId })
|
||||
return internalErrorResponse('Failed to get organization')
|
||||
}
|
||||
})
|
||||
|
||||
export const PATCH = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: organizationId } = await context.params
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!existing) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const updateData: Record<string, unknown> = {
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
|
||||
if (body.name !== undefined) {
|
||||
if (typeof body.name !== 'string' || body.name.trim().length === 0) {
|
||||
return badRequestResponse('name must be a non-empty string')
|
||||
}
|
||||
updateData.name = body.name.trim()
|
||||
}
|
||||
|
||||
if (body.slug !== undefined) {
|
||||
if (typeof body.slug !== 'string' || body.slug.trim().length === 0) {
|
||||
return badRequestResponse('slug must be a non-empty string')
|
||||
}
|
||||
updateData.slug = body.slug.trim()
|
||||
}
|
||||
|
||||
if (body.orgUsageLimit !== undefined) {
|
||||
if (body.orgUsageLimit === null) {
|
||||
updateData.orgUsageLimit = null
|
||||
} else if (typeof body.orgUsageLimit === 'number' && body.orgUsageLimit >= 0) {
|
||||
updateData.orgUsageLimit = body.orgUsageLimit.toFixed(2)
|
||||
} else {
|
||||
return badRequestResponse('orgUsageLimit must be a non-negative number or null')
|
||||
}
|
||||
}
|
||||
|
||||
const [updated] = await db
|
||||
.update(organization)
|
||||
.set(updateData)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.returning()
|
||||
|
||||
logger.info(`Admin API: Updated organization ${organizationId}`, {
|
||||
fields: Object.keys(updateData).filter((k) => k !== 'updatedAt'),
|
||||
})
|
||||
|
||||
return singleResponse(toAdminOrganization(updated))
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to update organization', { error, organizationId })
|
||||
return internalErrorResponse('Failed to update organization')
|
||||
}
|
||||
})
|
||||
@@ -1,143 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/organizations/[id]/seats
|
||||
*
|
||||
* Get organization seat analytics including member activity.
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminSeatAnalytics>
|
||||
*
|
||||
* PATCH /api/v1/admin/organizations/[id]/seats
|
||||
*
|
||||
* Update organization seat count (for admin override of enterprise seats).
|
||||
*
|
||||
* Body:
|
||||
* - seats: number - New seat count (for enterprise metadata.seats)
|
||||
*
|
||||
* Response: AdminSingleResponse<{ success: true, seats: number }>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { organization, subscription } from '@sim/db/schema'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { getOrganizationSeatAnalytics } from '@/lib/billing/validation/seat-management'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
|
||||
import {
|
||||
badRequestResponse,
|
||||
internalErrorResponse,
|
||||
notFoundResponse,
|
||||
singleResponse,
|
||||
} from '@/app/api/v1/admin/responses'
|
||||
import type { AdminSeatAnalytics } from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminOrganizationSeatsAPI')
|
||||
|
||||
interface RouteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const GET = withAdminAuthParams<RouteParams>(async (_, context) => {
|
||||
const { id: organizationId } = await context.params
|
||||
|
||||
try {
|
||||
const analytics = await getOrganizationSeatAnalytics(organizationId)
|
||||
|
||||
if (!analytics) {
|
||||
return notFoundResponse('Organization or subscription')
|
||||
}
|
||||
|
||||
const data: AdminSeatAnalytics = {
|
||||
organizationId: analytics.organizationId,
|
||||
organizationName: analytics.organizationName,
|
||||
currentSeats: analytics.currentSeats,
|
||||
maxSeats: analytics.maxSeats,
|
||||
availableSeats: analytics.availableSeats,
|
||||
subscriptionPlan: analytics.subscriptionPlan,
|
||||
canAddSeats: analytics.canAddSeats,
|
||||
utilizationRate: analytics.utilizationRate,
|
||||
activeMembers: analytics.activeMembers,
|
||||
inactiveMembers: analytics.inactiveMembers,
|
||||
memberActivity: analytics.memberActivity.map((m) => ({
|
||||
userId: m.userId,
|
||||
userName: m.userName,
|
||||
userEmail: m.userEmail,
|
||||
role: m.role,
|
||||
joinedAt: m.joinedAt.toISOString(),
|
||||
lastActive: m.lastActive?.toISOString() ?? null,
|
||||
})),
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Retrieved seat analytics for organization ${organizationId}`)
|
||||
|
||||
return singleResponse(data)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to get organization seats', { error, organizationId })
|
||||
return internalErrorResponse('Failed to get organization seats')
|
||||
}
|
||||
})
|
||||
|
||||
export const PATCH = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: organizationId } = await context.params
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
if (typeof body.seats !== 'number' || body.seats < 1 || !Number.isInteger(body.seats)) {
|
||||
return badRequestResponse('seats must be a positive integer')
|
||||
}
|
||||
|
||||
const [orgData] = await db
|
||||
.select({ id: organization.id })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const [subData] = await db
|
||||
.select()
|
||||
.from(subscription)
|
||||
.where(and(eq(subscription.referenceId, organizationId), eq(subscription.status, 'active')))
|
||||
.limit(1)
|
||||
|
||||
if (!subData) {
|
||||
return notFoundResponse('Subscription')
|
||||
}
|
||||
|
||||
if (subData.plan === 'enterprise') {
|
||||
const currentMetadata = (subData.metadata as Record<string, unknown>) || {}
|
||||
const newMetadata = {
|
||||
...currentMetadata,
|
||||
seats: body.seats,
|
||||
}
|
||||
|
||||
await db
|
||||
.update(subscription)
|
||||
.set({ metadata: newMetadata })
|
||||
.where(eq(subscription.id, subData.id))
|
||||
|
||||
logger.info(`Admin API: Updated enterprise seats for organization ${organizationId}`, {
|
||||
seats: body.seats,
|
||||
})
|
||||
} else {
|
||||
await db
|
||||
.update(subscription)
|
||||
.set({ seats: body.seats })
|
||||
.where(eq(subscription.id, subData.id))
|
||||
|
||||
logger.info(`Admin API: Updated team seats for organization ${organizationId}`, {
|
||||
seats: body.seats,
|
||||
})
|
||||
}
|
||||
|
||||
return singleResponse({
|
||||
success: true,
|
||||
seats: body.seats,
|
||||
plan: subData.plan,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to update organization seats', { error, organizationId })
|
||||
return internalErrorResponse('Failed to update organization seats')
|
||||
}
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/organizations
|
||||
*
|
||||
* List all organizations with pagination.
|
||||
*
|
||||
* Query Parameters:
|
||||
* - limit: number (default: 50, max: 250)
|
||||
* - offset: number (default: 0)
|
||||
*
|
||||
* Response: AdminListResponse<AdminOrganization>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { organization } from '@sim/db/schema'
|
||||
import { count } from 'drizzle-orm'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuth } from '@/app/api/v1/admin/middleware'
|
||||
import { internalErrorResponse, listResponse } from '@/app/api/v1/admin/responses'
|
||||
import {
|
||||
type AdminOrganization,
|
||||
createPaginationMeta,
|
||||
parsePaginationParams,
|
||||
toAdminOrganization,
|
||||
} from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminOrganizationsAPI')
|
||||
|
||||
export const GET = withAdminAuth(async (request) => {
|
||||
const url = new URL(request.url)
|
||||
const { limit, offset } = parsePaginationParams(url)
|
||||
|
||||
try {
|
||||
const [countResult, organizations] = await Promise.all([
|
||||
db.select({ total: count() }).from(organization),
|
||||
db.select().from(organization).orderBy(organization.name).limit(limit).offset(offset),
|
||||
])
|
||||
|
||||
const total = countResult[0].total
|
||||
const data: AdminOrganization[] = organizations.map(toAdminOrganization)
|
||||
const pagination = createPaginationMeta(total, limit, offset)
|
||||
|
||||
logger.info(`Admin API: Listed ${data.length} organizations (total: ${total})`)
|
||||
|
||||
return listResponse(data, pagination)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to list organizations', { error })
|
||||
return internalErrorResponse('Failed to list organizations')
|
||||
}
|
||||
})
|
||||
@@ -1,236 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/subscriptions/[id]
|
||||
*
|
||||
* Get subscription details.
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminSubscription>
|
||||
*
|
||||
* PATCH /api/v1/admin/subscriptions/[id]
|
||||
*
|
||||
* Update subscription details with optional side effects.
|
||||
*
|
||||
* Body:
|
||||
* - plan?: string - New plan (free, pro, team, enterprise)
|
||||
* - status?: string - New status (active, canceled, etc.)
|
||||
* - seats?: number - Seat count (for team plans)
|
||||
* - metadata?: object - Subscription metadata (for enterprise)
|
||||
* - periodStart?: string - Period start (ISO date)
|
||||
* - periodEnd?: string - Period end (ISO date)
|
||||
* - cancelAtPeriodEnd?: boolean - Cancel at period end flag
|
||||
* - syncLimits?: boolean - Sync usage limits for affected users (default: false)
|
||||
* - reason?: string - Reason for the change (for audit logging)
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminSubscription & { sideEffects }>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { member, subscription } from '@sim/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { syncUsageLimitsFromSubscription } from '@/lib/billing/core/usage'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
|
||||
import {
|
||||
badRequestResponse,
|
||||
internalErrorResponse,
|
||||
notFoundResponse,
|
||||
singleResponse,
|
||||
} from '@/app/api/v1/admin/responses'
|
||||
import { toAdminSubscription } from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminSubscriptionDetailAPI')
|
||||
|
||||
interface RouteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
const VALID_PLANS = ['free', 'pro', 'team', 'enterprise']
|
||||
const VALID_STATUSES = ['active', 'canceled', 'past_due', 'unpaid', 'trialing', 'incomplete']
|
||||
|
||||
export const GET = withAdminAuthParams<RouteParams>(async (_, context) => {
|
||||
const { id: subscriptionId } = await context.params
|
||||
|
||||
try {
|
||||
const [subData] = await db
|
||||
.select()
|
||||
.from(subscription)
|
||||
.where(eq(subscription.id, subscriptionId))
|
||||
.limit(1)
|
||||
|
||||
if (!subData) {
|
||||
return notFoundResponse('Subscription')
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Retrieved subscription ${subscriptionId}`)
|
||||
|
||||
return singleResponse(toAdminSubscription(subData))
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to get subscription', { error, subscriptionId })
|
||||
return internalErrorResponse('Failed to get subscription')
|
||||
}
|
||||
})
|
||||
|
||||
export const PATCH = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: subscriptionId } = await context.params
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const syncLimits = body.syncLimits === true
|
||||
const reason = body.reason || 'Admin update (no reason provided)'
|
||||
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(subscription)
|
||||
.where(eq(subscription.id, subscriptionId))
|
||||
.limit(1)
|
||||
|
||||
if (!existing) {
|
||||
return notFoundResponse('Subscription')
|
||||
}
|
||||
|
||||
const updateData: Record<string, unknown> = {}
|
||||
const warnings: string[] = []
|
||||
|
||||
if (body.plan !== undefined) {
|
||||
if (!VALID_PLANS.includes(body.plan)) {
|
||||
return badRequestResponse(`plan must be one of: ${VALID_PLANS.join(', ')}`)
|
||||
}
|
||||
if (body.plan !== existing.plan) {
|
||||
warnings.push(
|
||||
`Plan change from ${existing.plan} to ${body.plan}. This does NOT update Stripe - manual sync required.`
|
||||
)
|
||||
}
|
||||
updateData.plan = body.plan
|
||||
}
|
||||
|
||||
if (body.status !== undefined) {
|
||||
if (!VALID_STATUSES.includes(body.status)) {
|
||||
return badRequestResponse(`status must be one of: ${VALID_STATUSES.join(', ')}`)
|
||||
}
|
||||
if (body.status !== existing.status) {
|
||||
warnings.push(
|
||||
`Status change from ${existing.status} to ${body.status}. This does NOT update Stripe - manual sync required.`
|
||||
)
|
||||
}
|
||||
updateData.status = body.status
|
||||
}
|
||||
|
||||
if (body.seats !== undefined) {
|
||||
if (typeof body.seats !== 'number' || body.seats < 1 || !Number.isInteger(body.seats)) {
|
||||
return badRequestResponse('seats must be a positive integer')
|
||||
}
|
||||
updateData.seats = body.seats
|
||||
}
|
||||
|
||||
if (body.metadata !== undefined) {
|
||||
if (typeof body.metadata !== 'object' || body.metadata === null) {
|
||||
return badRequestResponse('metadata must be an object')
|
||||
}
|
||||
updateData.metadata = {
|
||||
...((existing.metadata as Record<string, unknown>) || {}),
|
||||
...body.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
if (body.periodStart !== undefined) {
|
||||
const date = new Date(body.periodStart)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return badRequestResponse('periodStart must be a valid ISO date')
|
||||
}
|
||||
updateData.periodStart = date
|
||||
}
|
||||
|
||||
if (body.periodEnd !== undefined) {
|
||||
const date = new Date(body.periodEnd)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return badRequestResponse('periodEnd must be a valid ISO date')
|
||||
}
|
||||
updateData.periodEnd = date
|
||||
}
|
||||
|
||||
if (body.cancelAtPeriodEnd !== undefined) {
|
||||
if (typeof body.cancelAtPeriodEnd !== 'boolean') {
|
||||
return badRequestResponse('cancelAtPeriodEnd must be a boolean')
|
||||
}
|
||||
updateData.cancelAtPeriodEnd = body.cancelAtPeriodEnd
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return badRequestResponse('No valid fields to update')
|
||||
}
|
||||
|
||||
const [updated] = await db
|
||||
.update(subscription)
|
||||
.set(updateData)
|
||||
.where(eq(subscription.id, subscriptionId))
|
||||
.returning()
|
||||
|
||||
const sideEffects: {
|
||||
limitsSynced: boolean
|
||||
usersAffected: string[]
|
||||
errors: string[]
|
||||
} = {
|
||||
limitsSynced: false,
|
||||
usersAffected: [],
|
||||
errors: [],
|
||||
}
|
||||
|
||||
if (syncLimits) {
|
||||
try {
|
||||
const referenceId = updated.referenceId
|
||||
|
||||
if (['free', 'pro'].includes(updated.plan)) {
|
||||
await syncUsageLimitsFromSubscription(referenceId)
|
||||
sideEffects.usersAffected.push(referenceId)
|
||||
sideEffects.limitsSynced = true
|
||||
} else if (['team', 'enterprise'].includes(updated.plan)) {
|
||||
const members = await db
|
||||
.select({ userId: member.userId })
|
||||
.from(member)
|
||||
.where(eq(member.organizationId, referenceId))
|
||||
|
||||
for (const m of members) {
|
||||
try {
|
||||
await syncUsageLimitsFromSubscription(m.userId)
|
||||
sideEffects.usersAffected.push(m.userId)
|
||||
} catch (memberError) {
|
||||
sideEffects.errors.push(`Failed to sync limits for user ${m.userId}`)
|
||||
logger.error('Admin API: Failed to sync limits for member', {
|
||||
userId: m.userId,
|
||||
error: memberError,
|
||||
})
|
||||
}
|
||||
}
|
||||
sideEffects.limitsSynced = members.length > 0
|
||||
}
|
||||
|
||||
logger.info('Admin API: Synced usage limits after subscription update', {
|
||||
subscriptionId,
|
||||
usersAffected: sideEffects.usersAffected.length,
|
||||
})
|
||||
} catch (syncError) {
|
||||
sideEffects.errors.push('Failed to sync usage limits')
|
||||
logger.error('Admin API: Failed to sync usage limits', {
|
||||
subscriptionId,
|
||||
error: syncError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Updated subscription ${subscriptionId}`, {
|
||||
fields: Object.keys(updateData),
|
||||
previousPlan: existing.plan,
|
||||
previousStatus: existing.status,
|
||||
syncLimits,
|
||||
reason,
|
||||
})
|
||||
|
||||
return singleResponse({
|
||||
...toAdminSubscription(updated),
|
||||
sideEffects,
|
||||
warnings,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to update subscription', { error, subscriptionId })
|
||||
return internalErrorResponse('Failed to update subscription')
|
||||
}
|
||||
})
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/subscriptions
|
||||
*
|
||||
* List all subscriptions with pagination.
|
||||
*
|
||||
* Query Parameters:
|
||||
* - limit: number (default: 50, max: 250)
|
||||
* - offset: number (default: 0)
|
||||
* - plan: string (optional) - Filter by plan (free, pro, team, enterprise)
|
||||
* - status: string (optional) - Filter by status (active, canceled, etc.)
|
||||
*
|
||||
* Response: AdminListResponse<AdminSubscription>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { subscription } from '@sim/db/schema'
|
||||
import { and, count, eq, type SQL } from 'drizzle-orm'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuth } from '@/app/api/v1/admin/middleware'
|
||||
import { internalErrorResponse, listResponse } from '@/app/api/v1/admin/responses'
|
||||
import {
|
||||
type AdminSubscription,
|
||||
createPaginationMeta,
|
||||
parsePaginationParams,
|
||||
toAdminSubscription,
|
||||
} from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminSubscriptionsAPI')
|
||||
|
||||
export const GET = withAdminAuth(async (request) => {
|
||||
const url = new URL(request.url)
|
||||
const { limit, offset } = parsePaginationParams(url)
|
||||
const planFilter = url.searchParams.get('plan')
|
||||
const statusFilter = url.searchParams.get('status')
|
||||
|
||||
try {
|
||||
const conditions: SQL<unknown>[] = []
|
||||
if (planFilter) {
|
||||
conditions.push(eq(subscription.plan, planFilter))
|
||||
}
|
||||
if (statusFilter) {
|
||||
conditions.push(eq(subscription.status, statusFilter))
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? and(...conditions) : undefined
|
||||
|
||||
const [countResult, subscriptions] = await Promise.all([
|
||||
db.select({ total: count() }).from(subscription).where(whereClause),
|
||||
db
|
||||
.select()
|
||||
.from(subscription)
|
||||
.where(whereClause)
|
||||
.orderBy(subscription.plan)
|
||||
.limit(limit)
|
||||
.offset(offset),
|
||||
])
|
||||
|
||||
const total = countResult[0].total
|
||||
const data: AdminSubscription[] = subscriptions.map(toAdminSubscription)
|
||||
const pagination = createPaginationMeta(total, limit, offset)
|
||||
|
||||
logger.info(`Admin API: Listed ${data.length} subscriptions (total: ${total})`)
|
||||
|
||||
return listResponse(data, pagination)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to list subscriptions', { error })
|
||||
return internalErrorResponse('Failed to list subscriptions')
|
||||
}
|
||||
})
|
||||
@@ -5,16 +5,7 @@
|
||||
* All responses follow a consistent structure for predictability.
|
||||
*/
|
||||
|
||||
import type {
|
||||
member,
|
||||
organization,
|
||||
subscription,
|
||||
user,
|
||||
userStats,
|
||||
workflow,
|
||||
workflowFolder,
|
||||
workspace,
|
||||
} from '@sim/db/schema'
|
||||
import type { user, workflow, workflowFolder, workspace } from '@sim/db/schema'
|
||||
import type { InferSelectModel } from 'drizzle-orm'
|
||||
import type { Edge } from 'reactflow'
|
||||
import type { BlockState, Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||
@@ -27,10 +18,6 @@ export type DbUser = InferSelectModel<typeof user>
|
||||
export type DbWorkspace = InferSelectModel<typeof workspace>
|
||||
export type DbWorkflow = InferSelectModel<typeof workflow>
|
||||
export type DbWorkflowFolder = InferSelectModel<typeof workflowFolder>
|
||||
export type DbOrganization = InferSelectModel<typeof organization>
|
||||
export type DbSubscription = InferSelectModel<typeof subscription>
|
||||
export type DbMember = InferSelectModel<typeof member>
|
||||
export type DbUserStats = InferSelectModel<typeof userStats>
|
||||
|
||||
// =============================================================================
|
||||
// Pagination
|
||||
@@ -413,189 +400,3 @@ function getNestedString(obj: Record<string, unknown>, path: string): string | u
|
||||
|
||||
return typeof current === 'string' ? current : undefined
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Organization Types
|
||||
// =============================================================================
|
||||
|
||||
export interface AdminOrganization {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
logo: string | null
|
||||
orgUsageLimit: string | null
|
||||
storageUsedBytes: number
|
||||
departedMemberUsage: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface AdminOrganizationDetail extends AdminOrganization {
|
||||
memberCount: number
|
||||
subscription: AdminSubscription | null
|
||||
}
|
||||
|
||||
export function toAdminOrganization(dbOrg: DbOrganization): AdminOrganization {
|
||||
return {
|
||||
id: dbOrg.id,
|
||||
name: dbOrg.name,
|
||||
slug: dbOrg.slug,
|
||||
logo: dbOrg.logo,
|
||||
orgUsageLimit: dbOrg.orgUsageLimit,
|
||||
storageUsedBytes: dbOrg.storageUsedBytes,
|
||||
departedMemberUsage: dbOrg.departedMemberUsage,
|
||||
createdAt: dbOrg.createdAt.toISOString(),
|
||||
updatedAt: dbOrg.updatedAt.toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Subscription Types
|
||||
// =============================================================================
|
||||
|
||||
export interface AdminSubscription {
|
||||
id: string
|
||||
plan: string
|
||||
referenceId: string
|
||||
stripeCustomerId: string | null
|
||||
stripeSubscriptionId: string | null
|
||||
status: string | null
|
||||
periodStart: string | null
|
||||
periodEnd: string | null
|
||||
cancelAtPeriodEnd: boolean | null
|
||||
seats: number | null
|
||||
trialStart: string | null
|
||||
trialEnd: string | null
|
||||
metadata: unknown
|
||||
}
|
||||
|
||||
export function toAdminSubscription(dbSub: DbSubscription): AdminSubscription {
|
||||
return {
|
||||
id: dbSub.id,
|
||||
plan: dbSub.plan,
|
||||
referenceId: dbSub.referenceId,
|
||||
stripeCustomerId: dbSub.stripeCustomerId,
|
||||
stripeSubscriptionId: dbSub.stripeSubscriptionId,
|
||||
status: dbSub.status,
|
||||
periodStart: dbSub.periodStart?.toISOString() ?? null,
|
||||
periodEnd: dbSub.periodEnd?.toISOString() ?? null,
|
||||
cancelAtPeriodEnd: dbSub.cancelAtPeriodEnd,
|
||||
seats: dbSub.seats,
|
||||
trialStart: dbSub.trialStart?.toISOString() ?? null,
|
||||
trialEnd: dbSub.trialEnd?.toISOString() ?? null,
|
||||
metadata: dbSub.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Member Types
|
||||
// =============================================================================
|
||||
|
||||
export interface AdminMember {
|
||||
id: string
|
||||
userId: string
|
||||
organizationId: string
|
||||
role: string
|
||||
createdAt: string
|
||||
// Joined user info
|
||||
userName: string
|
||||
userEmail: string
|
||||
}
|
||||
|
||||
export interface AdminMemberDetail extends AdminMember {
|
||||
// Billing/usage info from userStats
|
||||
currentPeriodCost: string
|
||||
currentUsageLimit: string | null
|
||||
lastActive: string | null
|
||||
billingBlocked: boolean
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// User Billing Types
|
||||
// =============================================================================
|
||||
|
||||
export interface AdminUserBilling {
|
||||
userId: string
|
||||
// User info
|
||||
userName: string
|
||||
userEmail: string
|
||||
stripeCustomerId: string | null
|
||||
// Usage stats
|
||||
totalManualExecutions: number
|
||||
totalApiCalls: number
|
||||
totalWebhookTriggers: number
|
||||
totalScheduledExecutions: number
|
||||
totalChatExecutions: number
|
||||
totalTokensUsed: number
|
||||
totalCost: string
|
||||
currentUsageLimit: string | null
|
||||
currentPeriodCost: string
|
||||
lastPeriodCost: string | null
|
||||
billedOverageThisPeriod: string
|
||||
storageUsedBytes: number
|
||||
lastActive: string | null
|
||||
billingBlocked: boolean
|
||||
// Copilot usage
|
||||
totalCopilotCost: string
|
||||
currentPeriodCopilotCost: string
|
||||
lastPeriodCopilotCost: string | null
|
||||
totalCopilotTokens: number
|
||||
totalCopilotCalls: number
|
||||
}
|
||||
|
||||
export interface AdminUserBillingWithSubscription extends AdminUserBilling {
|
||||
subscriptions: AdminSubscription[]
|
||||
organizationMemberships: Array<{
|
||||
organizationId: string
|
||||
organizationName: string
|
||||
role: string
|
||||
}>
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Organization Billing Summary Types
|
||||
// =============================================================================
|
||||
|
||||
export interface AdminOrganizationBillingSummary {
|
||||
organizationId: string
|
||||
organizationName: string
|
||||
subscriptionPlan: string
|
||||
subscriptionStatus: string
|
||||
// Seats
|
||||
totalSeats: number
|
||||
usedSeats: number
|
||||
availableSeats: number
|
||||
// Usage
|
||||
totalCurrentUsage: number
|
||||
totalUsageLimit: number
|
||||
minimumBillingAmount: number
|
||||
averageUsagePerMember: number
|
||||
usagePercentage: number
|
||||
// Billing period
|
||||
billingPeriodStart: string | null
|
||||
billingPeriodEnd: string | null
|
||||
// Alerts
|
||||
membersOverLimit: number
|
||||
membersNearLimit: number
|
||||
}
|
||||
|
||||
export interface AdminSeatAnalytics {
|
||||
organizationId: string
|
||||
organizationName: string
|
||||
currentSeats: number
|
||||
maxSeats: number
|
||||
availableSeats: number
|
||||
subscriptionPlan: string
|
||||
canAddSeats: boolean
|
||||
utilizationRate: number
|
||||
activeMembers: number
|
||||
inactiveMembers: number
|
||||
memberActivity: Array<{
|
||||
userId: string
|
||||
userName: string
|
||||
userEmail: string
|
||||
role: string
|
||||
joinedAt: string
|
||||
lastActive: string | null
|
||||
}>
|
||||
}
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
/**
|
||||
* POST /api/v1/admin/users/[id]/billing/move-to-org
|
||||
*
|
||||
* Move a user to an organization with full billing logic.
|
||||
* Enforces single-org constraint, handles Pro snapshot/cancellation.
|
||||
*
|
||||
* Body:
|
||||
* - organizationId: string - Target organization ID
|
||||
* - role?: string - Role in organization ('admin' | 'member'), defaults to 'member'
|
||||
* - skipBillingLogic?: boolean - Skip Pro handling (default: false)
|
||||
*
|
||||
* Response: AdminSingleResponse<{
|
||||
* success: true,
|
||||
* memberId: string,
|
||||
* organizationId: string,
|
||||
* role: string,
|
||||
* action: 'created' | 'updated' | 'already_member',
|
||||
* billingActions: { proUsageSnapshotted, proCancelledAtPeriodEnd }
|
||||
* }>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { member, organization, user } from '@sim/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { addUserToOrganization } from '@/lib/billing/organizations/membership'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
|
||||
import {
|
||||
badRequestResponse,
|
||||
internalErrorResponse,
|
||||
notFoundResponse,
|
||||
singleResponse,
|
||||
} from '@/app/api/v1/admin/responses'
|
||||
|
||||
const logger = createLogger('AdminUserMoveToOrgAPI')
|
||||
|
||||
interface RouteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const POST = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: userId } = await context.params
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
|
||||
if (!body.organizationId || typeof body.organizationId !== 'string') {
|
||||
return badRequestResponse('organizationId is required')
|
||||
}
|
||||
|
||||
const role = body.role || 'member'
|
||||
if (!['admin', 'member'].includes(role)) {
|
||||
return badRequestResponse('role must be "admin" or "member"')
|
||||
}
|
||||
|
||||
const skipBillingLogic = body.skipBillingLogic === true
|
||||
|
||||
const [userData] = await db
|
||||
.select({ id: user.id })
|
||||
.from(user)
|
||||
.where(eq(user.id, userId))
|
||||
.limit(1)
|
||||
|
||||
if (!userData) {
|
||||
return notFoundResponse('User')
|
||||
}
|
||||
|
||||
const [orgData] = await db
|
||||
.select({ id: organization.id, name: organization.name })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, body.organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return notFoundResponse('Organization')
|
||||
}
|
||||
|
||||
const existingMemberships = await db
|
||||
.select({ id: member.id, organizationId: member.organizationId, role: member.role })
|
||||
.from(member)
|
||||
.where(eq(member.userId, userId))
|
||||
|
||||
const existingInThisOrg = existingMemberships.find(
|
||||
(m) => m.organizationId === body.organizationId
|
||||
)
|
||||
if (existingInThisOrg) {
|
||||
if (existingInThisOrg.role !== role) {
|
||||
await db.update(member).set({ role }).where(eq(member.id, existingInThisOrg.id))
|
||||
|
||||
logger.info(
|
||||
`Admin API: Updated user ${userId} role in organization ${body.organizationId}`,
|
||||
{
|
||||
previousRole: existingInThisOrg.role,
|
||||
newRole: role,
|
||||
}
|
||||
)
|
||||
|
||||
return singleResponse({
|
||||
success: true,
|
||||
memberId: existingInThisOrg.id,
|
||||
organizationId: body.organizationId,
|
||||
organizationName: orgData.name,
|
||||
role,
|
||||
action: 'updated',
|
||||
billingActions: {
|
||||
proUsageSnapshotted: false,
|
||||
proCancelledAtPeriodEnd: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return singleResponse({
|
||||
success: true,
|
||||
memberId: existingInThisOrg.id,
|
||||
organizationId: body.organizationId,
|
||||
organizationName: orgData.name,
|
||||
role: existingInThisOrg.role,
|
||||
action: 'already_member',
|
||||
billingActions: {
|
||||
proUsageSnapshotted: false,
|
||||
proCancelledAtPeriodEnd: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const result = await addUserToOrganization({
|
||||
userId,
|
||||
organizationId: body.organizationId,
|
||||
role,
|
||||
skipBillingLogic,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
return badRequestResponse(result.error || 'Failed to move user to organization')
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Moved user ${userId} to organization ${body.organizationId}`, {
|
||||
role,
|
||||
memberId: result.memberId,
|
||||
billingActions: result.billingActions,
|
||||
skipBillingLogic,
|
||||
})
|
||||
|
||||
return singleResponse({
|
||||
success: true,
|
||||
memberId: result.memberId,
|
||||
organizationId: body.organizationId,
|
||||
organizationName: orgData.name,
|
||||
role,
|
||||
action: 'created',
|
||||
billingActions: {
|
||||
proUsageSnapshotted: result.billingActions.proUsageSnapshotted,
|
||||
proCancelledAtPeriodEnd: result.billingActions.proCancelledAtPeriodEnd,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to move user to organization', { error, userId })
|
||||
return internalErrorResponse('Failed to move user to organization')
|
||||
}
|
||||
})
|
||||
@@ -1,260 +0,0 @@
|
||||
/**
|
||||
* GET /api/v1/admin/users/[id]/billing
|
||||
*
|
||||
* Get user billing information including usage stats, subscriptions, and org memberships.
|
||||
*
|
||||
* Response: AdminSingleResponse<AdminUserBillingWithSubscription>
|
||||
*
|
||||
* PATCH /api/v1/admin/users/[id]/billing
|
||||
*
|
||||
* Update user billing settings with proper validation.
|
||||
*
|
||||
* Body:
|
||||
* - currentUsageLimit?: number | null - Usage limit (null to use default)
|
||||
* - billingBlocked?: boolean - Block/unblock billing
|
||||
* - currentPeriodCost?: number - Reset/adjust current period cost (use with caution)
|
||||
* - reason?: string - Reason for the change (for audit logging)
|
||||
*
|
||||
* Response: AdminSingleResponse<{ success: true, updated: string[], warnings: string[] }>
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { member, organization, subscription, user, userStats } from '@sim/db/schema'
|
||||
import { eq, or } from 'drizzle-orm'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
|
||||
import {
|
||||
badRequestResponse,
|
||||
internalErrorResponse,
|
||||
notFoundResponse,
|
||||
singleResponse,
|
||||
} from '@/app/api/v1/admin/responses'
|
||||
import {
|
||||
type AdminUserBillingWithSubscription,
|
||||
toAdminSubscription,
|
||||
} from '@/app/api/v1/admin/types'
|
||||
|
||||
const logger = createLogger('AdminUserBillingAPI')
|
||||
|
||||
interface RouteParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const GET = withAdminAuthParams<RouteParams>(async (_, context) => {
|
||||
const { id: userId } = await context.params
|
||||
|
||||
try {
|
||||
const [userData] = await db
|
||||
.select({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
stripeCustomerId: user.stripeCustomerId,
|
||||
})
|
||||
.from(user)
|
||||
.where(eq(user.id, userId))
|
||||
.limit(1)
|
||||
|
||||
if (!userData) {
|
||||
return notFoundResponse('User')
|
||||
}
|
||||
|
||||
const [stats] = await db.select().from(userStats).where(eq(userStats.userId, userId)).limit(1)
|
||||
|
||||
const memberOrgs = await db
|
||||
.select({
|
||||
organizationId: member.organizationId,
|
||||
organizationName: organization.name,
|
||||
role: member.role,
|
||||
})
|
||||
.from(member)
|
||||
.innerJoin(organization, eq(member.organizationId, organization.id))
|
||||
.where(eq(member.userId, userId))
|
||||
|
||||
const orgIds = memberOrgs.map((m) => m.organizationId)
|
||||
|
||||
const subscriptions = await db
|
||||
.select()
|
||||
.from(subscription)
|
||||
.where(
|
||||
orgIds.length > 0
|
||||
? or(
|
||||
eq(subscription.referenceId, userId),
|
||||
...orgIds.map((orgId) => eq(subscription.referenceId, orgId))
|
||||
)
|
||||
: eq(subscription.referenceId, userId)
|
||||
)
|
||||
|
||||
const data: AdminUserBillingWithSubscription = {
|
||||
userId: userData.id,
|
||||
userName: userData.name,
|
||||
userEmail: userData.email,
|
||||
stripeCustomerId: userData.stripeCustomerId,
|
||||
totalManualExecutions: stats?.totalManualExecutions ?? 0,
|
||||
totalApiCalls: stats?.totalApiCalls ?? 0,
|
||||
totalWebhookTriggers: stats?.totalWebhookTriggers ?? 0,
|
||||
totalScheduledExecutions: stats?.totalScheduledExecutions ?? 0,
|
||||
totalChatExecutions: stats?.totalChatExecutions ?? 0,
|
||||
totalTokensUsed: stats?.totalTokensUsed ?? 0,
|
||||
totalCost: stats?.totalCost ?? '0',
|
||||
currentUsageLimit: stats?.currentUsageLimit ?? null,
|
||||
currentPeriodCost: stats?.currentPeriodCost ?? '0',
|
||||
lastPeriodCost: stats?.lastPeriodCost ?? null,
|
||||
billedOverageThisPeriod: stats?.billedOverageThisPeriod ?? '0',
|
||||
storageUsedBytes: stats?.storageUsedBytes ?? 0,
|
||||
lastActive: stats?.lastActive?.toISOString() ?? null,
|
||||
billingBlocked: stats?.billingBlocked ?? false,
|
||||
totalCopilotCost: stats?.totalCopilotCost ?? '0',
|
||||
currentPeriodCopilotCost: stats?.currentPeriodCopilotCost ?? '0',
|
||||
lastPeriodCopilotCost: stats?.lastPeriodCopilotCost ?? null,
|
||||
totalCopilotTokens: stats?.totalCopilotTokens ?? 0,
|
||||
totalCopilotCalls: stats?.totalCopilotCalls ?? 0,
|
||||
subscriptions: subscriptions.map(toAdminSubscription),
|
||||
organizationMemberships: memberOrgs.map((m) => ({
|
||||
organizationId: m.organizationId,
|
||||
organizationName: m.organizationName,
|
||||
role: m.role,
|
||||
})),
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Retrieved billing for user ${userId}`)
|
||||
|
||||
return singleResponse(data)
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to get user billing', { error, userId })
|
||||
return internalErrorResponse('Failed to get user billing')
|
||||
}
|
||||
})
|
||||
|
||||
export const PATCH = withAdminAuthParams<RouteParams>(async (request, context) => {
|
||||
const { id: userId } = await context.params
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const reason = body.reason || 'Admin update (no reason provided)'
|
||||
|
||||
const [userData] = await db
|
||||
.select({ id: user.id })
|
||||
.from(user)
|
||||
.where(eq(user.id, userId))
|
||||
.limit(1)
|
||||
|
||||
if (!userData) {
|
||||
return notFoundResponse('User')
|
||||
}
|
||||
|
||||
const [existingStats] = await db
|
||||
.select()
|
||||
.from(userStats)
|
||||
.where(eq(userStats.userId, userId))
|
||||
.limit(1)
|
||||
|
||||
const userSubscription = await getHighestPrioritySubscription(userId)
|
||||
const isTeamOrEnterpriseMember =
|
||||
userSubscription && ['team', 'enterprise'].includes(userSubscription.plan)
|
||||
|
||||
const [orgMembership] = await db
|
||||
.select({ organizationId: member.organizationId })
|
||||
.from(member)
|
||||
.where(eq(member.userId, userId))
|
||||
.limit(1)
|
||||
|
||||
const updateData: Record<string, unknown> = {}
|
||||
const updated: string[] = []
|
||||
const warnings: string[] = []
|
||||
|
||||
if (body.currentUsageLimit !== undefined) {
|
||||
if (isTeamOrEnterpriseMember && orgMembership) {
|
||||
warnings.push(
|
||||
'User is a team/enterprise member. Individual limits may be ignored in favor of organization limits.'
|
||||
)
|
||||
}
|
||||
|
||||
if (body.currentUsageLimit === null) {
|
||||
updateData.currentUsageLimit = null
|
||||
} else if (typeof body.currentUsageLimit === 'number' && body.currentUsageLimit >= 0) {
|
||||
const currentCost = Number.parseFloat(existingStats?.currentPeriodCost || '0')
|
||||
if (body.currentUsageLimit < currentCost) {
|
||||
warnings.push(
|
||||
`New limit ($${body.currentUsageLimit.toFixed(2)}) is below current usage ($${currentCost.toFixed(2)}). User may be immediately blocked.`
|
||||
)
|
||||
}
|
||||
updateData.currentUsageLimit = body.currentUsageLimit.toFixed(2)
|
||||
} else {
|
||||
return badRequestResponse('currentUsageLimit must be a non-negative number or null')
|
||||
}
|
||||
updateData.usageLimitUpdatedAt = new Date()
|
||||
updated.push('currentUsageLimit')
|
||||
}
|
||||
|
||||
if (body.billingBlocked !== undefined) {
|
||||
if (typeof body.billingBlocked !== 'boolean') {
|
||||
return badRequestResponse('billingBlocked must be a boolean')
|
||||
}
|
||||
|
||||
if (body.billingBlocked === false && existingStats?.billingBlocked === true) {
|
||||
warnings.push(
|
||||
'Unblocking user. Ensure payment issues are resolved to prevent re-blocking on next invoice.'
|
||||
)
|
||||
}
|
||||
|
||||
updateData.billingBlocked = body.billingBlocked
|
||||
updated.push('billingBlocked')
|
||||
}
|
||||
|
||||
if (body.currentPeriodCost !== undefined) {
|
||||
if (typeof body.currentPeriodCost !== 'number' || body.currentPeriodCost < 0) {
|
||||
return badRequestResponse('currentPeriodCost must be a non-negative number')
|
||||
}
|
||||
|
||||
const previousCost = existingStats?.currentPeriodCost || '0'
|
||||
warnings.push(
|
||||
`Manually adjusting currentPeriodCost from $${previousCost} to $${body.currentPeriodCost.toFixed(2)}. This may affect billing accuracy.`
|
||||
)
|
||||
|
||||
updateData.currentPeriodCost = body.currentPeriodCost.toFixed(2)
|
||||
updated.push('currentPeriodCost')
|
||||
}
|
||||
|
||||
if (updated.length === 0) {
|
||||
return badRequestResponse('No valid fields to update')
|
||||
}
|
||||
|
||||
if (existingStats) {
|
||||
await db.update(userStats).set(updateData).where(eq(userStats.userId, userId))
|
||||
} else {
|
||||
await db.insert(userStats).values({
|
||||
id: nanoid(),
|
||||
userId,
|
||||
...updateData,
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`Admin API: Updated billing for user ${userId}`, {
|
||||
updated,
|
||||
warnings,
|
||||
reason,
|
||||
previousValues: existingStats
|
||||
? {
|
||||
currentUsageLimit: existingStats.currentUsageLimit,
|
||||
billingBlocked: existingStats.billingBlocked,
|
||||
currentPeriodCost: existingStats.currentPeriodCost,
|
||||
}
|
||||
: null,
|
||||
newValues: updateData,
|
||||
isTeamMember: !!orgMembership,
|
||||
})
|
||||
|
||||
return singleResponse({
|
||||
success: true,
|
||||
updated,
|
||||
warnings,
|
||||
reason,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Admin API: Failed to update user billing', { error, userId })
|
||||
return internalErrorResponse('Failed to update user billing')
|
||||
}
|
||||
})
|
||||
@@ -84,6 +84,7 @@ export async function GET(request: NextRequest) {
|
||||
},
|
||||
})
|
||||
|
||||
// Build filter conditions
|
||||
const filters = {
|
||||
workspaceId: params.workspaceId,
|
||||
workflowIds: params.workflowIds?.split(',').filter(Boolean),
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTerminalStore } from '@/stores/terminal'
|
||||
|
||||
/**
|
||||
* Constants for output panel sizing
|
||||
* Must match MIN_OUTPUT_PANEL_WIDTH_PX and BLOCK_COLUMN_WIDTH_PX in terminal.tsx
|
||||
*/
|
||||
const MIN_WIDTH = 440
|
||||
const BLOCK_COLUMN_WIDTH = 240
|
||||
|
||||
/**
|
||||
* Custom hook to handle output panel horizontal resize functionality.
|
||||
* Manages mouse events for resizing and enforces min/max width constraints.
|
||||
*
|
||||
* @returns Resize state and handlers
|
||||
*/
|
||||
export function useOutputPanelResize() {
|
||||
const setOutputPanelWidth = useTerminalStore((state) => state.setOutputPanelWidth)
|
||||
const { setOutputPanelWidth } = useTerminalStore()
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
|
||||
/**
|
||||
* Handles mouse down on resize handle
|
||||
*/
|
||||
const handleMouseDown = useCallback(() => {
|
||||
setIsResizing(true)
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Setup resize event listeners and body styles when resizing.
|
||||
* Cleanup is handled automatically by the effect's return function.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isResizing) return
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
// Calculate width from the right edge of the viewport
|
||||
// Account for panel width on the right side
|
||||
const panelWidth = Number.parseInt(
|
||||
getComputedStyle(document.documentElement).getPropertyValue('--panel-width') || '0'
|
||||
)
|
||||
@@ -24,10 +43,13 @@ export function useOutputPanelResize() {
|
||||
)
|
||||
|
||||
const newWidth = window.innerWidth - e.clientX - panelWidth
|
||||
|
||||
// Calculate max width: total terminal width minus block column width
|
||||
const terminalWidth = window.innerWidth - sidebarWidth - panelWidth
|
||||
const maxWidth = terminalWidth - BLOCK_COLUMN_WIDTH
|
||||
const clampedWidth = Math.max(MIN_WIDTH, Math.min(newWidth, maxWidth))
|
||||
|
||||
// Clamp between min and max width
|
||||
const clampedWidth = Math.max(MIN_WIDTH, Math.min(newWidth, maxWidth))
|
||||
setOutputPanelWidth(clampedWidth)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useTerminalStore } from '@/stores/terminal'
|
||||
|
||||
/**
|
||||
* Constants for terminal sizing
|
||||
*/
|
||||
const MIN_HEIGHT = 30
|
||||
const MAX_HEIGHT_PERCENTAGE = 0.7
|
||||
const MAX_HEIGHT_PERCENTAGE = 0.7 // 70% of viewport height
|
||||
|
||||
/**
|
||||
* Custom hook to handle terminal resize functionality.
|
||||
* Manages mouse events for resizing and enforces min/max height constraints.
|
||||
* Maximum height is capped at 70% of the viewport height for optimal layout.
|
||||
*
|
||||
* @returns Resize state and handlers
|
||||
*/
|
||||
export function useTerminalResize() {
|
||||
const setTerminalHeight = useTerminalStore((state) => state.setTerminalHeight)
|
||||
const isResizing = useTerminalStore((state) => state.isResizing)
|
||||
const setIsResizing = useTerminalStore((state) => state.setIsResizing)
|
||||
const { setTerminalHeight, isResizing, setIsResizing } = useTerminalStore()
|
||||
|
||||
/**
|
||||
* Handles mouse down on resize handle
|
||||
*/
|
||||
const handleMouseDown = useCallback(() => {
|
||||
setIsResizing(true)
|
||||
}, [setIsResizing])
|
||||
|
||||
/**
|
||||
* Setup resize event listeners and body styles when resizing
|
||||
* Cleanup is handled automatically by the effect's return function
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isResizing) return
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
// Calculate height from the bottom edge of the viewport
|
||||
const newHeight = window.innerHeight - e.clientY
|
||||
const maxHeight = window.innerHeight * MAX_HEIGHT_PERCENTAGE
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
ArrowDown,
|
||||
@@ -18,9 +18,9 @@ import {
|
||||
Trash2,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import {
|
||||
Button,
|
||||
Code,
|
||||
Input,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
PopoverScrollArea,
|
||||
PopoverTrigger,
|
||||
Tooltip,
|
||||
VirtualizedCodeViewer,
|
||||
} from '@/components/emcn'
|
||||
import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
|
||||
import { createCommands } from '@/app/workspace/[workspaceId]/utils/commands-utils'
|
||||
@@ -238,42 +237,6 @@ const isEventFromEditableElement = (e: KeyboardEvent): boolean => {
|
||||
return false
|
||||
}
|
||||
|
||||
interface OutputCodeContentProps {
|
||||
code: string
|
||||
language: 'javascript' | 'json'
|
||||
wrapText: boolean
|
||||
searchQuery: string | undefined
|
||||
currentMatchIndex: number
|
||||
onMatchCountChange: (count: number) => void
|
||||
contentRef: React.RefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
const OutputCodeContent = React.memo(function OutputCodeContent({
|
||||
code,
|
||||
language,
|
||||
wrapText,
|
||||
searchQuery,
|
||||
currentMatchIndex,
|
||||
onMatchCountChange,
|
||||
contentRef,
|
||||
}: OutputCodeContentProps) {
|
||||
return (
|
||||
<VirtualizedCodeViewer
|
||||
code={code}
|
||||
showGutter
|
||||
language={language}
|
||||
className='m-0 min-h-full rounded-none border-0 bg-[var(--surface-1)]'
|
||||
paddingLeft={8}
|
||||
gutterStyle={{ backgroundColor: 'transparent' }}
|
||||
wrapText={wrapText}
|
||||
searchQuery={searchQuery}
|
||||
currentMatchIndex={currentMatchIndex}
|
||||
onMatchCountChange={onMatchCountChange}
|
||||
contentRef={contentRef}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* Terminal component with resizable height that persists across page refreshes.
|
||||
*
|
||||
@@ -291,6 +254,7 @@ export function Terminal() {
|
||||
const prevEntriesLengthRef = useRef(0)
|
||||
const prevWorkflowEntriesLengthRef = useRef(0)
|
||||
const {
|
||||
terminalHeight,
|
||||
setTerminalHeight,
|
||||
lastExpandedHeight,
|
||||
outputPanelWidth,
|
||||
@@ -299,16 +263,10 @@ export function Terminal() {
|
||||
setOpenOnRun,
|
||||
setHasHydrated,
|
||||
} = useTerminalStore()
|
||||
const isExpanded = useTerminalStore((state) => state.terminalHeight > NEAR_MIN_THRESHOLD)
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
const workflowEntriesSelector = useCallback(
|
||||
(state: { entries: ConsoleEntry[] }) =>
|
||||
state.entries.filter((entry) => entry.workflowId === activeWorkflowId),
|
||||
[activeWorkflowId]
|
||||
)
|
||||
const entries = useTerminalConsoleStore(useShallow(workflowEntriesSelector))
|
||||
const entries = useTerminalConsoleStore((state) => state.entries)
|
||||
const clearWorkflowConsole = useTerminalConsoleStore((state) => state.clearWorkflowConsole)
|
||||
const exportConsoleCSV = useTerminalConsoleStore((state) => state.exportConsoleCSV)
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
const [selectedEntry, setSelectedEntry] = useState<ConsoleEntry | null>(null)
|
||||
const [isToggling, setIsToggling] = useState(false)
|
||||
const [wrapText, setWrapText] = useState(true)
|
||||
@@ -346,6 +304,8 @@ export function Terminal() {
|
||||
hasActiveFilters,
|
||||
} = useTerminalFilters()
|
||||
|
||||
const isExpanded = terminalHeight > NEAR_MIN_THRESHOLD
|
||||
|
||||
/**
|
||||
* Expands the terminal to its last meaningful height, with safeguards:
|
||||
* - Never expands below {@link DEFAULT_EXPANDED_HEIGHT}.
|
||||
@@ -362,7 +322,13 @@ export function Terminal() {
|
||||
setTerminalHeight(targetHeight)
|
||||
}, [lastExpandedHeight, setTerminalHeight])
|
||||
|
||||
const allWorkflowEntries = entries
|
||||
/**
|
||||
* Get all entries for current workflow (before filtering) for filter options
|
||||
*/
|
||||
const allWorkflowEntries = useMemo(() => {
|
||||
if (!activeWorkflowId) return []
|
||||
return entries.filter((entry) => entry.workflowId === activeWorkflowId)
|
||||
}, [entries, activeWorkflowId])
|
||||
|
||||
/**
|
||||
* Filter entries for current workflow and apply filters
|
||||
@@ -459,11 +425,6 @@ export function Terminal() {
|
||||
return selectedEntry.output
|
||||
}, [selectedEntry, showInput])
|
||||
|
||||
const outputDataStringified = useMemo(() => {
|
||||
if (outputData === null || outputData === undefined) return ''
|
||||
return JSON.stringify(outputData, null, 2)
|
||||
}, [outputData])
|
||||
|
||||
/**
|
||||
* Auto-open the terminal on new entries when "Open on run" is enabled.
|
||||
* This mirrors the header toggle behavior by using expandToLastHeight,
|
||||
@@ -478,12 +439,13 @@ export function Terminal() {
|
||||
const previousLength = prevWorkflowEntriesLengthRef.current
|
||||
const currentLength = allWorkflowEntries.length
|
||||
|
||||
if (currentLength > previousLength && !isExpanded) {
|
||||
// Only react when new entries are added for the active workflow
|
||||
if (currentLength > previousLength && terminalHeight <= MIN_HEIGHT) {
|
||||
expandToLastHeight()
|
||||
}
|
||||
|
||||
prevWorkflowEntriesLengthRef.current = currentLength
|
||||
}, [allWorkflowEntries.length, expandToLastHeight, openOnRun, isExpanded])
|
||||
}, [allWorkflowEntries.length, expandToLastHeight, openOnRun, terminalHeight])
|
||||
|
||||
/**
|
||||
* Handle row click - toggle if clicking same entry
|
||||
@@ -523,11 +485,13 @@ export function Terminal() {
|
||||
const handleCopy = useCallback(() => {
|
||||
if (!selectedEntry) return
|
||||
|
||||
const textToCopy = shouldShowCodeDisplay ? selectedEntry.input.code : outputDataStringified
|
||||
const textToCopy = shouldShowCodeDisplay
|
||||
? selectedEntry.input.code
|
||||
: JSON.stringify(outputData, null, 2)
|
||||
|
||||
navigator.clipboard.writeText(textToCopy)
|
||||
setShowCopySuccess(true)
|
||||
}, [selectedEntry, outputDataStringified, shouldShowCodeDisplay])
|
||||
}, [selectedEntry, outputData, shouldShowCodeDisplay])
|
||||
|
||||
/**
|
||||
* Clears the console for the active workflow.
|
||||
@@ -578,7 +542,7 @@ export function Terminal() {
|
||||
}, [matchCount])
|
||||
|
||||
/**
|
||||
* Handles match count change from VirtualizedCodeViewer.
|
||||
* Handles match count change from Code.Viewer.
|
||||
*/
|
||||
const handleMatchCountChange = useCallback((count: number) => {
|
||||
setMatchCount(count)
|
||||
@@ -1612,11 +1576,15 @@ export function Terminal() {
|
||||
{/* Content */}
|
||||
<div className='flex-1 overflow-x-auto overflow-y-auto'>
|
||||
{shouldShowCodeDisplay ? (
|
||||
<OutputCodeContent
|
||||
<Code.Viewer
|
||||
code={selectedEntry.input.code}
|
||||
showGutter
|
||||
language={
|
||||
(selectedEntry.input.language as 'javascript' | 'json') || 'javascript'
|
||||
}
|
||||
className='m-0 min-h-full rounded-none border-0 bg-[var(--surface-1)]'
|
||||
paddingLeft={8}
|
||||
gutterStyle={{ backgroundColor: 'transparent' }}
|
||||
wrapText={wrapText}
|
||||
searchQuery={isOutputSearchActive ? outputSearchQuery : undefined}
|
||||
currentMatchIndex={currentMatchIndex}
|
||||
@@ -1624,9 +1592,13 @@ export function Terminal() {
|
||||
contentRef={outputContentRef}
|
||||
/>
|
||||
) : (
|
||||
<OutputCodeContent
|
||||
code={outputDataStringified}
|
||||
<Code.Viewer
|
||||
code={JSON.stringify(outputData, null, 2)}
|
||||
showGutter
|
||||
language='json'
|
||||
className='m-0 min-h-full rounded-none border-0 bg-[var(--surface-1)]'
|
||||
paddingLeft={8}
|
||||
gutterStyle={{ backgroundColor: 'transparent' }}
|
||||
wrapText={wrapText}
|
||||
searchQuery={isOutputSearchActive ? outputSearchQuery : undefined}
|
||||
currentMatchIndex={currentMatchIndex}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,433 +0,0 @@
|
||||
import { GoogleSlidesIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { GoogleSlidesResponse } from '@/tools/google_slides/types'
|
||||
|
||||
export const GoogleSlidesBlock: BlockConfig<GoogleSlidesResponse> = {
|
||||
type: 'google_slides',
|
||||
name: 'Google Slides',
|
||||
description: 'Read, write, and create presentations',
|
||||
authMode: AuthMode.OAuth,
|
||||
longDescription:
|
||||
'Integrate Google Slides into the workflow. Can read, write, create presentations, replace text, add slides, add images, and get thumbnails.',
|
||||
docsLink: 'https://docs.sim.ai/tools/google_slides',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: GoogleSlidesIcon,
|
||||
subBlocks: [
|
||||
// Operation selector
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Read Presentation', id: 'read' },
|
||||
{ label: 'Write to Presentation', id: 'write' },
|
||||
{ label: 'Create Presentation', id: 'create' },
|
||||
{ label: 'Replace All Text', id: 'replace_all_text' },
|
||||
{ label: 'Add Slide', id: 'add_slide' },
|
||||
{ label: 'Add Image', id: 'add_image' },
|
||||
{ label: 'Get Thumbnail', id: 'get_thumbnail' },
|
||||
],
|
||||
value: () => 'read',
|
||||
},
|
||||
// Google Slides Credentials
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Google Account',
|
||||
type: 'oauth-input',
|
||||
required: true,
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
],
|
||||
placeholder: 'Select Google account',
|
||||
},
|
||||
// Presentation selector (basic mode) - for operations that need an existing presentation
|
||||
{
|
||||
id: 'presentationId',
|
||||
title: 'Select Presentation',
|
||||
type: 'file-selector',
|
||||
canonicalParamId: 'presentationId',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [],
|
||||
mimeType: 'application/vnd.google-apps.presentation',
|
||||
placeholder: 'Select a presentation',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'basic',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['read', 'write', 'replace_all_text', 'add_slide', 'add_image', 'get_thumbnail'],
|
||||
},
|
||||
},
|
||||
// Manual presentation ID input (advanced mode)
|
||||
{
|
||||
id: 'manualPresentationId',
|
||||
title: 'Presentation ID',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'presentationId',
|
||||
placeholder: 'Enter presentation ID',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['read', 'write', 'replace_all_text', 'add_slide', 'add_image', 'get_thumbnail'],
|
||||
},
|
||||
},
|
||||
|
||||
// ========== Write Operation Fields ==========
|
||||
{
|
||||
id: 'slideIndex',
|
||||
title: 'Slide Index',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter slide index (0 for first slide)',
|
||||
condition: { field: 'operation', value: 'write' },
|
||||
},
|
||||
{
|
||||
id: 'content',
|
||||
title: 'Content',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter slide content',
|
||||
condition: { field: 'operation', value: 'write' },
|
||||
required: true,
|
||||
},
|
||||
|
||||
// ========== Create Operation Fields ==========
|
||||
{
|
||||
id: 'title',
|
||||
title: 'Presentation Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter title for the new presentation',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
required: true,
|
||||
},
|
||||
// Folder selector (basic mode)
|
||||
{
|
||||
id: 'folderSelector',
|
||||
title: 'Select Parent Folder',
|
||||
type: 'file-selector',
|
||||
canonicalParamId: 'folderId',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [],
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
placeholder: 'Select a parent folder',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'basic',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
},
|
||||
// Manual folder ID input (advanced mode)
|
||||
{
|
||||
id: 'folderId',
|
||||
title: 'Parent Folder ID',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'folderId',
|
||||
placeholder: 'Enter parent folder ID (leave empty for root folder)',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
},
|
||||
// Content Field for create operation
|
||||
{
|
||||
id: 'createContent',
|
||||
title: 'Initial Content',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter initial slide content (optional)',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
},
|
||||
|
||||
// ========== Replace All Text Operation Fields ==========
|
||||
{
|
||||
id: 'findText',
|
||||
title: 'Find Text',
|
||||
type: 'short-input',
|
||||
placeholder: 'Text to find (e.g., {{placeholder}})',
|
||||
condition: { field: 'operation', value: 'replace_all_text' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'replaceText',
|
||||
title: 'Replace With',
|
||||
type: 'short-input',
|
||||
placeholder: 'Text to replace with',
|
||||
condition: { field: 'operation', value: 'replace_all_text' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'matchCase',
|
||||
title: 'Match Case',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'replace_all_text' },
|
||||
},
|
||||
{
|
||||
id: 'pageObjectIds',
|
||||
title: 'Limit to Slides (IDs)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated slide IDs (leave empty for all)',
|
||||
condition: { field: 'operation', value: 'replace_all_text' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// ========== Add Slide Operation Fields ==========
|
||||
{
|
||||
id: 'layout',
|
||||
title: 'Slide Layout',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Blank', id: 'BLANK' },
|
||||
{ label: 'Title', id: 'TITLE' },
|
||||
{ label: 'Title and Body', id: 'TITLE_AND_BODY' },
|
||||
{ label: 'Title Only', id: 'TITLE_ONLY' },
|
||||
{ label: 'Title and Two Columns', id: 'TITLE_AND_TWO_COLUMNS' },
|
||||
{ label: 'Section Header', id: 'SECTION_HEADER' },
|
||||
{ label: 'Caption Only', id: 'CAPTION_ONLY' },
|
||||
{ label: 'Main Point', id: 'MAIN_POINT' },
|
||||
{ label: 'Big Number', id: 'BIG_NUMBER' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'add_slide' },
|
||||
value: () => 'BLANK',
|
||||
},
|
||||
{
|
||||
id: 'insertionIndex',
|
||||
title: 'Insertion Position',
|
||||
type: 'short-input',
|
||||
placeholder: 'Position to insert slide (leave empty for end)',
|
||||
condition: { field: 'operation', value: 'add_slide' },
|
||||
},
|
||||
{
|
||||
id: 'placeholderIdMappings',
|
||||
title: 'Placeholder ID Mappings',
|
||||
type: 'long-input',
|
||||
placeholder: 'JSON array: [{"layoutPlaceholder":{"type":"TITLE"},"objectId":"my_title"}]',
|
||||
condition: { field: 'operation', value: 'add_slide' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
// ========== Add Image Operation Fields ==========
|
||||
{
|
||||
id: 'pageObjectId',
|
||||
title: 'Slide ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Object ID of the slide to add image to',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'imageUrl',
|
||||
title: 'Image URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'Public URL of the image (PNG, JPEG, or GIF)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'imageWidth',
|
||||
title: 'Width (points)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Image width in points (default: 300)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
},
|
||||
{
|
||||
id: 'imageHeight',
|
||||
title: 'Height (points)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Image height in points (default: 200)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
},
|
||||
{
|
||||
id: 'positionX',
|
||||
title: 'X Position (points)',
|
||||
type: 'short-input',
|
||||
placeholder: 'X position from left (default: 100)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
},
|
||||
{
|
||||
id: 'positionY',
|
||||
title: 'Y Position (points)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Y position from top (default: 100)',
|
||||
condition: { field: 'operation', value: 'add_image' },
|
||||
},
|
||||
|
||||
// ========== Get Thumbnail Operation Fields ==========
|
||||
{
|
||||
id: 'thumbnailPageId',
|
||||
title: 'Slide ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Object ID of the slide to get thumbnail for',
|
||||
condition: { field: 'operation', value: 'get_thumbnail' },
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'thumbnailSize',
|
||||
title: 'Thumbnail Size',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Small (200px)', id: 'SMALL' },
|
||||
{ label: 'Medium (800px)', id: 'MEDIUM' },
|
||||
{ label: 'Large (1600px)', id: 'LARGE' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'get_thumbnail' },
|
||||
value: () => 'MEDIUM',
|
||||
},
|
||||
{
|
||||
id: 'mimeType',
|
||||
title: 'Image Format',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'PNG', id: 'PNG' },
|
||||
{ label: 'GIF', id: 'GIF' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'get_thumbnail' },
|
||||
value: () => 'PNG',
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'google_slides_read',
|
||||
'google_slides_write',
|
||||
'google_slides_create',
|
||||
'google_slides_replace_all_text',
|
||||
'google_slides_add_slide',
|
||||
'google_slides_add_image',
|
||||
'google_slides_get_thumbnail',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'read':
|
||||
return 'google_slides_read'
|
||||
case 'write':
|
||||
return 'google_slides_write'
|
||||
case 'create':
|
||||
return 'google_slides_create'
|
||||
case 'replace_all_text':
|
||||
return 'google_slides_replace_all_text'
|
||||
case 'add_slide':
|
||||
return 'google_slides_add_slide'
|
||||
case 'add_image':
|
||||
return 'google_slides_add_image'
|
||||
case 'get_thumbnail':
|
||||
return 'google_slides_get_thumbnail'
|
||||
default:
|
||||
throw new Error(`Invalid Google Slides operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const {
|
||||
credential,
|
||||
presentationId,
|
||||
manualPresentationId,
|
||||
folderSelector,
|
||||
folderId,
|
||||
slideIndex,
|
||||
createContent,
|
||||
thumbnailPageId,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
...rest
|
||||
} = params
|
||||
|
||||
const effectivePresentationId = (presentationId || manualPresentationId || '').trim()
|
||||
const effectiveFolderId = (folderSelector || folderId || '').trim()
|
||||
|
||||
const result: Record<string, any> = {
|
||||
...rest,
|
||||
presentationId: effectivePresentationId || undefined,
|
||||
credential,
|
||||
}
|
||||
|
||||
// Handle operation-specific params
|
||||
if (params.operation === 'write' && slideIndex) {
|
||||
result.slideIndex = Number.parseInt(slideIndex as string, 10)
|
||||
}
|
||||
|
||||
if (params.operation === 'create') {
|
||||
result.folderId = effectiveFolderId || undefined
|
||||
if (createContent) {
|
||||
result.content = createContent
|
||||
}
|
||||
}
|
||||
|
||||
if (params.operation === 'add_slide' && params.insertionIndex) {
|
||||
result.insertionIndex = Number.parseInt(params.insertionIndex as string, 10)
|
||||
}
|
||||
|
||||
if (params.operation === 'add_image') {
|
||||
if (imageWidth) {
|
||||
result.width = Number.parseInt(imageWidth as string, 10)
|
||||
}
|
||||
if (imageHeight) {
|
||||
result.height = Number.parseInt(imageHeight as string, 10)
|
||||
}
|
||||
if (params.positionX) {
|
||||
result.positionX = Number.parseInt(params.positionX as string, 10)
|
||||
}
|
||||
if (params.positionY) {
|
||||
result.positionY = Number.parseInt(params.positionY as string, 10)
|
||||
}
|
||||
}
|
||||
|
||||
if (params.operation === 'get_thumbnail') {
|
||||
result.pageObjectId = thumbnailPageId
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
credential: { type: 'string', description: 'Google Slides access token' },
|
||||
presentationId: { type: 'string', description: 'Presentation identifier' },
|
||||
manualPresentationId: { type: 'string', description: 'Manual presentation identifier' },
|
||||
// Write operation
|
||||
slideIndex: { type: 'number', description: 'Slide index to write to' },
|
||||
content: { type: 'string', description: 'Slide content' },
|
||||
// Create operation
|
||||
title: { type: 'string', description: 'Presentation title' },
|
||||
folderSelector: { type: 'string', description: 'Selected folder' },
|
||||
folderId: { type: 'string', description: 'Folder identifier' },
|
||||
createContent: { type: 'string', description: 'Initial slide content' },
|
||||
// Replace all text operation
|
||||
findText: { type: 'string', description: 'Text to find' },
|
||||
replaceText: { type: 'string', description: 'Text to replace with' },
|
||||
matchCase: { type: 'boolean', description: 'Whether to match case' },
|
||||
pageObjectIds: {
|
||||
type: 'string',
|
||||
description: 'Comma-separated slide IDs to limit replacements',
|
||||
},
|
||||
// Add slide operation
|
||||
layout: { type: 'string', description: 'Slide layout' },
|
||||
insertionIndex: { type: 'number', description: 'Position to insert slide' },
|
||||
placeholderIdMappings: { type: 'string', description: 'JSON array of placeholder ID mappings' },
|
||||
// Add image operation
|
||||
pageObjectId: { type: 'string', description: 'Slide object ID for image' },
|
||||
imageUrl: { type: 'string', description: 'Image URL' },
|
||||
imageWidth: { type: 'number', description: 'Image width in points' },
|
||||
imageHeight: { type: 'number', description: 'Image height in points' },
|
||||
positionX: { type: 'number', description: 'X position in points' },
|
||||
positionY: { type: 'number', description: 'Y position in points' },
|
||||
// Get thumbnail operation
|
||||
thumbnailPageId: { type: 'string', description: 'Slide object ID for thumbnail' },
|
||||
thumbnailSize: { type: 'string', description: 'Thumbnail size' },
|
||||
mimeType: { type: 'string', description: 'Image format (PNG or GIF)' },
|
||||
},
|
||||
outputs: {
|
||||
// Read operation
|
||||
slides: { type: 'json', description: 'Presentation slides' },
|
||||
metadata: { type: 'json', description: 'Presentation metadata' },
|
||||
// Write operation
|
||||
updatedContent: { type: 'boolean', description: 'Content update status' },
|
||||
// Replace all text operation
|
||||
occurrencesChanged: { type: 'number', description: 'Number of text occurrences replaced' },
|
||||
// Add slide operation
|
||||
slideId: { type: 'string', description: 'Object ID of newly created slide' },
|
||||
// Add image operation
|
||||
imageId: { type: 'string', description: 'Object ID of newly created image' },
|
||||
// Get thumbnail operation
|
||||
contentUrl: { type: 'string', description: 'URL to the thumbnail image' },
|
||||
width: { type: 'number', description: 'Thumbnail width in pixels' },
|
||||
height: { type: 'number', description: 'Thumbnail height in pixels' },
|
||||
},
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import { AuthMode } from '@/blocks/types'
|
||||
export const KalshiBlock: BlockConfig = {
|
||||
type: 'kalshi',
|
||||
name: 'Kalshi',
|
||||
description: 'Access prediction markets and trade on Kalshi',
|
||||
description: 'Access prediction markets data from Kalshi',
|
||||
longDescription:
|
||||
'Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, exchange status, and place/cancel/amend trades.',
|
||||
'Integrate Kalshi prediction markets into the workflow. Can get markets, market, events, event, balance, positions, orders, orderbook, trades, candlesticks, fills, series, and exchange status.',
|
||||
docsLink: 'https://docs.sim.ai/tools/kalshi',
|
||||
authMode: AuthMode.ApiKey,
|
||||
category: 'tools',
|
||||
@@ -26,16 +26,12 @@ export const KalshiBlock: BlockConfig = {
|
||||
{ label: 'Get Balance', id: 'get_balance' },
|
||||
{ label: 'Get Positions', id: 'get_positions' },
|
||||
{ label: 'Get Orders', id: 'get_orders' },
|
||||
{ label: 'Get Order', id: 'get_order' },
|
||||
{ label: 'Get Orderbook', id: 'get_orderbook' },
|
||||
{ label: 'Get Trades', id: 'get_trades' },
|
||||
{ label: 'Get Candlesticks', id: 'get_candlesticks' },
|
||||
{ label: 'Get Fills', id: 'get_fills' },
|
||||
{ label: 'Get Series by Ticker', id: 'get_series_by_ticker' },
|
||||
{ label: 'Get Exchange Status', id: 'get_exchange_status' },
|
||||
{ label: 'Create Order', id: 'create_order' },
|
||||
{ label: 'Cancel Order', id: 'cancel_order' },
|
||||
{ label: 'Amend Order', id: 'amend_order' },
|
||||
],
|
||||
value: () => 'get_markets',
|
||||
},
|
||||
@@ -47,16 +43,7 @@ export const KalshiBlock: BlockConfig = {
|
||||
placeholder: 'Your Kalshi API Key ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_balance',
|
||||
'get_positions',
|
||||
'get_orders',
|
||||
'get_order',
|
||||
'get_fills',
|
||||
'create_order',
|
||||
'cancel_order',
|
||||
'amend_order',
|
||||
],
|
||||
value: ['get_balance', 'get_positions', 'get_orders', 'get_fills'],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
@@ -68,16 +55,7 @@ export const KalshiBlock: BlockConfig = {
|
||||
placeholder: 'Your RSA Private Key (PEM format)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_balance',
|
||||
'get_positions',
|
||||
'get_orders',
|
||||
'get_order',
|
||||
'get_fills',
|
||||
'create_order',
|
||||
'cancel_order',
|
||||
'amend_order',
|
||||
],
|
||||
value: ['get_balance', 'get_positions', 'get_orders', 'get_fills'],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
@@ -169,20 +147,35 @@ export const KalshiBlock: BlockConfig = {
|
||||
],
|
||||
condition: { field: 'operation', value: ['get_orders'] },
|
||||
},
|
||||
// Get Fills timestamp filters
|
||||
// Get Orderbook fields
|
||||
{
|
||||
id: 'depth',
|
||||
title: 'Depth',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of price levels per side',
|
||||
condition: { field: 'operation', value: ['get_orderbook'] },
|
||||
},
|
||||
// Get Trades fields
|
||||
{
|
||||
id: 'tickerTrades',
|
||||
title: 'Market Ticker',
|
||||
type: 'short-input',
|
||||
placeholder: 'Filter by market ticker (optional)',
|
||||
condition: { field: 'operation', value: ['get_trades'] },
|
||||
},
|
||||
{
|
||||
id: 'minTs',
|
||||
title: 'Min Timestamp',
|
||||
type: 'short-input',
|
||||
placeholder: 'Minimum timestamp (Unix milliseconds)',
|
||||
condition: { field: 'operation', value: ['get_fills'] },
|
||||
condition: { field: 'operation', value: ['get_trades', 'get_fills'] },
|
||||
},
|
||||
{
|
||||
id: 'maxTs',
|
||||
title: 'Max Timestamp',
|
||||
type: 'short-input',
|
||||
placeholder: 'Maximum timestamp (Unix milliseconds)',
|
||||
condition: { field: 'operation', value: ['get_fills'] },
|
||||
condition: { field: 'operation', value: ['get_trades', 'get_fills'] },
|
||||
},
|
||||
// Get Candlesticks fields
|
||||
{
|
||||
@@ -205,16 +198,14 @@ export const KalshiBlock: BlockConfig = {
|
||||
id: 'startTs',
|
||||
title: 'Start Timestamp',
|
||||
type: 'short-input',
|
||||
placeholder: 'Start timestamp (Unix seconds)',
|
||||
required: true,
|
||||
placeholder: 'Start timestamp (Unix milliseconds)',
|
||||
condition: { field: 'operation', value: ['get_candlesticks'] },
|
||||
},
|
||||
{
|
||||
id: 'endTs',
|
||||
title: 'End Timestamp',
|
||||
type: 'short-input',
|
||||
placeholder: 'End timestamp (Unix seconds)',
|
||||
required: true,
|
||||
placeholder: 'End timestamp (Unix milliseconds)',
|
||||
condition: { field: 'operation', value: ['get_candlesticks'] },
|
||||
},
|
||||
{
|
||||
@@ -222,11 +213,11 @@ export const KalshiBlock: BlockConfig = {
|
||||
title: 'Period Interval',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'All', id: '' },
|
||||
{ label: '1 minute', id: '1' },
|
||||
{ label: '1 hour', id: '60' },
|
||||
{ label: '1 day', id: '1440' },
|
||||
],
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_candlesticks'] },
|
||||
},
|
||||
// Get Fills fields
|
||||
@@ -253,146 +244,6 @@ export const KalshiBlock: BlockConfig = {
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_series_by_ticker'] },
|
||||
},
|
||||
// Order ID for get_order, cancel_order, amend_order
|
||||
{
|
||||
id: 'orderIdParam',
|
||||
title: 'Order ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Order ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['get_order', 'cancel_order', 'amend_order'] },
|
||||
},
|
||||
// Create Order fields
|
||||
{
|
||||
id: 'tickerOrder',
|
||||
title: 'Market Ticker',
|
||||
type: 'short-input',
|
||||
placeholder: 'Market ticker (e.g., KXBTC-24DEC31)',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'side',
|
||||
title: 'Side',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Yes', id: 'yes' },
|
||||
{ label: 'No', id: 'no' },
|
||||
],
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'action',
|
||||
title: 'Action',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Buy', id: 'buy' },
|
||||
{ label: 'Sell', id: 'sell' },
|
||||
],
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'count',
|
||||
title: 'Contracts',
|
||||
type: 'short-input',
|
||||
placeholder: 'Number of contracts',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'countAmend',
|
||||
title: 'Contracts',
|
||||
type: 'short-input',
|
||||
placeholder: 'Updated number of contracts (optional)',
|
||||
condition: { field: 'operation', value: ['amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'orderType',
|
||||
title: 'Order Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Limit', id: 'limit' },
|
||||
{ label: 'Market', id: 'market' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'yesPrice',
|
||||
title: 'Yes Price (cents)',
|
||||
type: 'short-input',
|
||||
placeholder: 'Yes price in cents (1-99)',
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'noPrice',
|
||||
title: 'No Price (cents)',
|
||||
type: 'short-input',
|
||||
placeholder: 'No price in cents (1-99)',
|
||||
condition: { field: 'operation', value: ['create_order', 'amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'clientOrderId',
|
||||
title: 'Client Order ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Custom order identifier (optional)',
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'clientOrderIdAmend',
|
||||
title: 'Client Order ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Original client order ID',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'updatedClientOrderId',
|
||||
title: 'New Client Order ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'New client order ID after amendment',
|
||||
required: true,
|
||||
condition: { field: 'operation', value: ['amend_order'] },
|
||||
},
|
||||
{
|
||||
id: 'timeInForce',
|
||||
title: 'Time in Force',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Good Till Canceled', id: 'good_till_canceled' },
|
||||
{ label: 'Fill or Kill', id: 'fill_or_kill' },
|
||||
{ label: 'Immediate or Cancel', id: 'immediate_or_cancel' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'expirationTs',
|
||||
title: 'Expiration',
|
||||
type: 'short-input',
|
||||
placeholder: 'Unix timestamp for order expiration',
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'postOnly',
|
||||
title: 'Post Only',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: '' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
{
|
||||
id: 'reduceOnly',
|
||||
title: 'Reduce Only',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: '' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['create_order'] },
|
||||
},
|
||||
// Pagination fields
|
||||
{
|
||||
id: 'limit',
|
||||
@@ -438,16 +289,12 @@ export const KalshiBlock: BlockConfig = {
|
||||
'kalshi_get_balance',
|
||||
'kalshi_get_positions',
|
||||
'kalshi_get_orders',
|
||||
'kalshi_get_order',
|
||||
'kalshi_get_orderbook',
|
||||
'kalshi_get_trades',
|
||||
'kalshi_get_candlesticks',
|
||||
'kalshi_get_fills',
|
||||
'kalshi_get_series_by_ticker',
|
||||
'kalshi_get_exchange_status',
|
||||
'kalshi_create_order',
|
||||
'kalshi_cancel_order',
|
||||
'kalshi_amend_order',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
@@ -466,8 +313,6 @@ export const KalshiBlock: BlockConfig = {
|
||||
return 'kalshi_get_positions'
|
||||
case 'get_orders':
|
||||
return 'kalshi_get_orders'
|
||||
case 'get_order':
|
||||
return 'kalshi_get_order'
|
||||
case 'get_orderbook':
|
||||
return 'kalshi_get_orderbook'
|
||||
case 'get_trades':
|
||||
@@ -480,12 +325,6 @@ export const KalshiBlock: BlockConfig = {
|
||||
return 'kalshi_get_series_by_ticker'
|
||||
case 'get_exchange_status':
|
||||
return 'kalshi_get_exchange_status'
|
||||
case 'create_order':
|
||||
return 'kalshi_create_order'
|
||||
case 'cancel_order':
|
||||
return 'kalshi_cancel_order'
|
||||
case 'amend_order':
|
||||
return 'kalshi_amend_order'
|
||||
default:
|
||||
return 'kalshi_get_markets'
|
||||
}
|
||||
@@ -495,15 +334,11 @@ export const KalshiBlock: BlockConfig = {
|
||||
operation,
|
||||
orderStatus,
|
||||
tickerFilter,
|
||||
tickerTrades,
|
||||
tickerFills,
|
||||
tickerCandlesticks,
|
||||
seriesTickerCandlesticks,
|
||||
seriesTickerGet,
|
||||
orderIdParam,
|
||||
tickerOrder,
|
||||
orderType,
|
||||
countAmend,
|
||||
clientOrderIdAmend,
|
||||
...rest
|
||||
} = params
|
||||
const cleanParams: Record<string, any> = {}
|
||||
@@ -518,6 +353,11 @@ export const KalshiBlock: BlockConfig = {
|
||||
cleanParams.ticker = tickerFilter
|
||||
}
|
||||
|
||||
// Map tickerTrades to ticker for get_trades
|
||||
if (operation === 'get_trades' && tickerTrades) {
|
||||
cleanParams.ticker = tickerTrades
|
||||
}
|
||||
|
||||
// Map tickerFills to ticker for get_fills
|
||||
if (operation === 'get_fills' && tickerFills) {
|
||||
cleanParams.ticker = tickerFills
|
||||
@@ -534,36 +374,6 @@ export const KalshiBlock: BlockConfig = {
|
||||
cleanParams.seriesTicker = seriesTickerGet
|
||||
}
|
||||
|
||||
// Map orderIdParam to orderId for get_order, cancel_order, amend_order
|
||||
if (
|
||||
(operation === 'get_order' ||
|
||||
operation === 'cancel_order' ||
|
||||
operation === 'amend_order') &&
|
||||
orderIdParam
|
||||
) {
|
||||
cleanParams.orderId = orderIdParam
|
||||
}
|
||||
|
||||
// Map tickerOrder to ticker for create_order, amend_order
|
||||
if ((operation === 'create_order' || operation === 'amend_order') && tickerOrder) {
|
||||
cleanParams.ticker = tickerOrder
|
||||
}
|
||||
|
||||
// Map orderType to type for create_order
|
||||
if (operation === 'create_order' && orderType) {
|
||||
cleanParams.type = orderType
|
||||
}
|
||||
|
||||
// Map countAmend to count for amend_order
|
||||
if (operation === 'amend_order' && countAmend) {
|
||||
cleanParams.count = countAmend
|
||||
}
|
||||
|
||||
// Map clientOrderIdAmend to clientOrderId for amend_order
|
||||
if (operation === 'amend_order' && clientOrderIdAmend) {
|
||||
cleanParams.clientOrderId = clientOrderIdAmend
|
||||
}
|
||||
|
||||
Object.entries(rest).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
cleanParams[key] = value
|
||||
|
||||
@@ -193,40 +193,17 @@ export const PolymarketBlock: BlockConfig = {
|
||||
{
|
||||
id: 'order',
|
||||
title: 'Sort By',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Default', id: '' },
|
||||
{ label: 'Volume', id: 'volumeNum' },
|
||||
{ label: 'Liquidity', id: 'liquidityNum' },
|
||||
{ label: 'Start Date', id: 'startDate' },
|
||||
{ label: 'End Date', id: 'endDate' },
|
||||
{ label: 'Created At', id: 'createdAt' },
|
||||
{ label: 'Updated At', id: 'updatedAt' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['get_markets'] },
|
||||
},
|
||||
{
|
||||
id: 'orderEvents',
|
||||
title: 'Sort By',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Default', id: '' },
|
||||
{ label: 'Volume', id: 'volume' },
|
||||
{ label: 'Liquidity', id: 'liquidity' },
|
||||
{ label: 'Start Date', id: 'startDate' },
|
||||
{ label: 'End Date', id: 'endDate' },
|
||||
{ label: 'Created At', id: 'createdAt' },
|
||||
{ label: 'Updated At', id: 'updatedAt' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['get_events'] },
|
||||
type: 'short-input',
|
||||
placeholder: 'Sort field (e.g., id, volume, liquidity)',
|
||||
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
|
||||
},
|
||||
{
|
||||
id: 'ascending',
|
||||
title: 'Sort Order',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Descending', id: 'false' },
|
||||
{ label: 'Ascending', id: 'true' },
|
||||
{ label: 'Descending (newest first)', id: 'false' },
|
||||
{ label: 'Ascending (oldest first)', id: 'true' },
|
||||
],
|
||||
condition: { field: 'operation', value: ['get_markets', 'get_events'] },
|
||||
},
|
||||
@@ -321,7 +298,7 @@ export const PolymarketBlock: BlockConfig = {
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { operation, marketSlug, eventSlug, orderEvents, order, ...rest } = params
|
||||
const { operation, marketSlug, eventSlug, ...rest } = params
|
||||
const cleanParams: Record<string, any> = {}
|
||||
|
||||
// Map marketSlug to slug for get_market
|
||||
@@ -334,13 +311,6 @@ export const PolymarketBlock: BlockConfig = {
|
||||
cleanParams.slug = eventSlug
|
||||
}
|
||||
|
||||
// Map order field based on operation (markets use volumeNum/liquidityNum, events use volume/liquidity)
|
||||
if (operation === 'get_markets' && order) {
|
||||
cleanParams.order = order
|
||||
} else if (operation === 'get_events' && orderEvents) {
|
||||
cleanParams.order = orderEvents
|
||||
}
|
||||
|
||||
// Convert numeric fields from string to number for get_price_history
|
||||
if (operation === 'get_price_history') {
|
||||
if (rest.fidelity) cleanParams.fidelity = Number(rest.fidelity)
|
||||
|
||||
@@ -1,594 +0,0 @@
|
||||
import { SnowflakeIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { AuthMode } from '@/blocks/types'
|
||||
import type { SnowflakeResponse } from '@/tools/snowflake/types'
|
||||
|
||||
export const SnowflakeBlock: BlockConfig<SnowflakeResponse> = {
|
||||
type: 'snowflake',
|
||||
name: 'Snowflake',
|
||||
description: 'Execute queries on Snowflake data warehouse',
|
||||
authMode: AuthMode.ApiKey,
|
||||
longDescription:
|
||||
'Integrate Snowflake into your workflow. Execute SQL queries, insert, update, and delete rows, list databases, schemas, and tables, and describe table structures in your Snowflake data warehouse.',
|
||||
docsLink: 'https://docs.sim.ai/tools/snowflake',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: SnowflakeIcon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Execute Query', id: 'execute_query' },
|
||||
{ label: 'Insert Rows', id: 'insert_rows' },
|
||||
{ label: 'Update Rows', id: 'update_rows' },
|
||||
{ label: 'Delete Rows', id: 'delete_rows' },
|
||||
{ label: 'List Databases', id: 'list_databases' },
|
||||
{ label: 'List Schemas', id: 'list_schemas' },
|
||||
{ label: 'List Tables', id: 'list_tables' },
|
||||
{ label: 'List Views', id: 'list_views' },
|
||||
{ label: 'List Warehouses', id: 'list_warehouses' },
|
||||
{ label: 'List File Formats', id: 'list_file_formats' },
|
||||
{ label: 'List Stages', id: 'list_stages' },
|
||||
{ label: 'Describe Table', id: 'describe_table' },
|
||||
],
|
||||
value: () => 'execute_query',
|
||||
},
|
||||
{
|
||||
id: 'accountUrl',
|
||||
title: 'Account URL',
|
||||
type: 'short-input',
|
||||
placeholder: 'your-account.snowflakecomputing.com',
|
||||
description: 'Your Snowflake account URL (e.g., xy12345.us-east-1.snowflakecomputing.com)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'accessToken',
|
||||
title: 'Personal Access Token',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter your Snowflake PAT',
|
||||
description: 'Generate a PAT in Snowflake Snowsight',
|
||||
password: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'warehouse',
|
||||
title: 'Warehouse',
|
||||
type: 'short-input',
|
||||
placeholder: 'Warehouse name',
|
||||
},
|
||||
{
|
||||
id: 'role',
|
||||
title: 'Role',
|
||||
type: 'short-input',
|
||||
placeholder: 'Role name',
|
||||
},
|
||||
{
|
||||
id: 'query',
|
||||
title: 'SQL Query',
|
||||
type: 'long-input',
|
||||
required: true,
|
||||
placeholder: 'Enter SQL query (e.g., SELECT * FROM database.schema.table LIMIT 10)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'execute_query',
|
||||
},
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
maintainHistory: true,
|
||||
prompt: `You are an expert Snowflake SQL developer. Generate Snowflake SQL queries based on the user's natural language request.
|
||||
|
||||
### CONTEXT
|
||||
{context}
|
||||
|
||||
### CRITICAL INSTRUCTION
|
||||
Return ONLY the SQL query. Do not include any explanations, markdown formatting, comments, or additional text. Just the raw SQL query that can be executed directly in Snowflake.
|
||||
|
||||
### SNOWFLAKE SQL GUIDELINES
|
||||
1. **Syntax**: Use standard Snowflake SQL syntax and functions
|
||||
2. **Fully Qualified Names**: Use database.schema.table format when possible
|
||||
3. **Case Sensitivity**: Identifiers are case-insensitive unless quoted
|
||||
4. **Performance**: Consider using LIMIT clauses for large datasets
|
||||
5. **Data Types**: Use appropriate Snowflake data types (VARIANT for JSON, TIMESTAMP_NTZ, etc.)
|
||||
|
||||
### COMMON SNOWFLAKE SQL PATTERNS
|
||||
|
||||
**Basic SELECT**:
|
||||
SELECT * FROM database.schema.table LIMIT 100;
|
||||
|
||||
**Filtered Query**:
|
||||
SELECT column1, column2
|
||||
FROM database.schema.table
|
||||
WHERE status = 'active'
|
||||
AND created_at > DATEADD(day, -7, CURRENT_DATE())
|
||||
LIMIT 100;
|
||||
|
||||
**Aggregate Functions**:
|
||||
SELECT
|
||||
category,
|
||||
COUNT(*) as total_count,
|
||||
AVG(amount) as avg_amount,
|
||||
SUM(amount) as total_amount
|
||||
FROM database.schema.sales
|
||||
GROUP BY category
|
||||
ORDER BY total_amount DESC;
|
||||
|
||||
**JOIN Operations**:
|
||||
SELECT
|
||||
u.user_id,
|
||||
u.name,
|
||||
o.order_id,
|
||||
o.total
|
||||
FROM database.schema.users u
|
||||
INNER JOIN database.schema.orders o
|
||||
ON u.user_id = o.user_id
|
||||
WHERE o.created_at > CURRENT_DATE() - 30;
|
||||
|
||||
**Window Functions**:
|
||||
SELECT
|
||||
user_id,
|
||||
order_date,
|
||||
amount,
|
||||
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) as row_num
|
||||
FROM database.schema.orders;
|
||||
|
||||
**JSON/VARIANT Queries**:
|
||||
SELECT
|
||||
id,
|
||||
json_data:field::STRING as field_value,
|
||||
json_data:nested.value::NUMBER as nested_value
|
||||
FROM database.schema.json_table
|
||||
WHERE json_data:status::STRING = 'active';
|
||||
|
||||
**FLATTEN for Arrays**:
|
||||
SELECT
|
||||
id,
|
||||
f.value::STRING as array_item
|
||||
FROM database.schema.table,
|
||||
LATERAL FLATTEN(input => array_column) f;
|
||||
|
||||
**CTE (Common Table Expression)**:
|
||||
WITH active_users AS (
|
||||
SELECT user_id, name
|
||||
FROM database.schema.users
|
||||
WHERE status = 'active'
|
||||
)
|
||||
SELECT
|
||||
au.name,
|
||||
COUNT(o.order_id) as order_count
|
||||
FROM active_users au
|
||||
LEFT JOIN database.schema.orders o ON au.user_id = o.user_id
|
||||
GROUP BY au.name;
|
||||
|
||||
**Date/Time Functions**:
|
||||
SELECT
|
||||
DATE_TRUNC('month', order_date) as month,
|
||||
COUNT(*) as orders
|
||||
FROM database.schema.orders
|
||||
WHERE order_date >= DATEADD(year, -1, CURRENT_DATE())
|
||||
GROUP BY month
|
||||
ORDER BY month DESC;
|
||||
|
||||
**INSERT Statement**:
|
||||
INSERT INTO database.schema.table (column1, column2, column3)
|
||||
VALUES ('value1', 123, CURRENT_TIMESTAMP());
|
||||
|
||||
**UPDATE Statement**:
|
||||
UPDATE database.schema.table
|
||||
SET status = 'processed', updated_at = CURRENT_TIMESTAMP()
|
||||
WHERE id = 123;
|
||||
|
||||
**DELETE Statement**:
|
||||
DELETE FROM database.schema.table
|
||||
WHERE created_at < DATEADD(year, -2, CURRENT_DATE());
|
||||
|
||||
**MERGE Statement (Upsert)**:
|
||||
MERGE INTO database.schema.target t
|
||||
USING database.schema.source s
|
||||
ON t.id = s.id
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET t.value = s.value, t.updated_at = CURRENT_TIMESTAMP()
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (id, value, created_at) VALUES (s.id, s.value, CURRENT_TIMESTAMP());
|
||||
|
||||
### SNOWFLAKE SPECIFIC FEATURES
|
||||
|
||||
**SAMPLE Clause** (for testing with large tables):
|
||||
SELECT * FROM database.schema.large_table SAMPLE (1000 ROWS);
|
||||
|
||||
**QUALIFY Clause** (filter window functions):
|
||||
SELECT
|
||||
user_id,
|
||||
order_date,
|
||||
amount
|
||||
FROM database.schema.orders
|
||||
QUALIFY ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) = 1;
|
||||
|
||||
**Time Travel**:
|
||||
SELECT * FROM database.schema.table AT (TIMESTAMP => '2024-01-01 00:00:00'::TIMESTAMP);
|
||||
|
||||
### BEST PRACTICES
|
||||
1. Always use LIMIT when exploring data
|
||||
2. Use WHERE clauses to filter data efficiently
|
||||
3. Index commonly queried columns
|
||||
4. Use appropriate date functions (DATEADD, DATE_TRUNC, DATEDIFF)
|
||||
5. For JSON data, use proper casting (::STRING, ::NUMBER, etc.)
|
||||
6. Use CTEs for complex queries to improve readability
|
||||
|
||||
### REMEMBER
|
||||
Return ONLY the SQL query - no explanations, no markdown code blocks, no extra text. The query should be ready to execute.`,
|
||||
placeholder:
|
||||
'Describe the SQL query you need (e.g., "Get all orders from the last 7 days with customer names")...',
|
||||
generationType: 'sql-query',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'database',
|
||||
title: 'Database',
|
||||
type: 'short-input',
|
||||
placeholder: 'Database name',
|
||||
required: true,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'list_schemas',
|
||||
'list_tables',
|
||||
'list_views',
|
||||
'list_file_formats',
|
||||
'list_stages',
|
||||
'describe_table',
|
||||
'insert_rows',
|
||||
'update_rows',
|
||||
'delete_rows',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'schema',
|
||||
title: 'Schema',
|
||||
type: 'short-input',
|
||||
placeholder: 'Schema name',
|
||||
required: true,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'list_tables',
|
||||
'list_views',
|
||||
'list_file_formats',
|
||||
'list_stages',
|
||||
'describe_table',
|
||||
'insert_rows',
|
||||
'update_rows',
|
||||
'delete_rows',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'table',
|
||||
title: 'Table',
|
||||
type: 'short-input',
|
||||
placeholder: 'Table name',
|
||||
required: true,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['describe_table', 'insert_rows', 'update_rows', 'delete_rows'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'columns',
|
||||
title: 'Columns',
|
||||
type: 'long-input',
|
||||
placeholder: '["column1", "column2", "column3"]',
|
||||
required: true,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'insert_rows',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'values',
|
||||
title: 'Values',
|
||||
type: 'long-input',
|
||||
placeholder: '[["value1", "value2", "value3"], ["value4", "value5", "value6"]]',
|
||||
required: true,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'insert_rows',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'updates',
|
||||
title: 'Updates',
|
||||
type: 'long-input',
|
||||
placeholder: '{"column1": "new_value", "column2": 123, "updated_at": "2024-01-01"}',
|
||||
required: true,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'update_rows',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'whereClause',
|
||||
title: 'WHERE Clause',
|
||||
type: 'long-input',
|
||||
placeholder: 'id = 123 (leave empty to update/delete ALL rows)',
|
||||
required: false,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['update_rows', 'delete_rows'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'timeout',
|
||||
title: 'Timeout (seconds)',
|
||||
type: 'short-input',
|
||||
placeholder: '60',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'execute_query',
|
||||
},
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'snowflake_execute_query',
|
||||
'snowflake_insert_rows',
|
||||
'snowflake_update_rows',
|
||||
'snowflake_delete_rows',
|
||||
'snowflake_list_databases',
|
||||
'snowflake_list_schemas',
|
||||
'snowflake_list_tables',
|
||||
'snowflake_list_views',
|
||||
'snowflake_list_warehouses',
|
||||
'snowflake_list_file_formats',
|
||||
'snowflake_list_stages',
|
||||
'snowflake_describe_table',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
case 'execute_query':
|
||||
return 'snowflake_execute_query'
|
||||
case 'insert_rows':
|
||||
return 'snowflake_insert_rows'
|
||||
case 'update_rows':
|
||||
return 'snowflake_update_rows'
|
||||
case 'delete_rows':
|
||||
return 'snowflake_delete_rows'
|
||||
case 'list_databases':
|
||||
return 'snowflake_list_databases'
|
||||
case 'list_schemas':
|
||||
return 'snowflake_list_schemas'
|
||||
case 'list_tables':
|
||||
return 'snowflake_list_tables'
|
||||
case 'list_views':
|
||||
return 'snowflake_list_views'
|
||||
case 'list_warehouses':
|
||||
return 'snowflake_list_warehouses'
|
||||
case 'list_file_formats':
|
||||
return 'snowflake_list_file_formats'
|
||||
case 'list_stages':
|
||||
return 'snowflake_list_stages'
|
||||
case 'describe_table':
|
||||
return 'snowflake_describe_table'
|
||||
default:
|
||||
throw new Error(`Unknown operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { operation, ...rest } = params
|
||||
|
||||
// Build base params - use PAT directly as accessToken
|
||||
const baseParams: Record<string, any> = {
|
||||
accessToken: params.accessToken,
|
||||
accountUrl: params.accountUrl,
|
||||
}
|
||||
|
||||
// Add optional warehouse and role if provided
|
||||
if (params.warehouse) {
|
||||
baseParams.warehouse = params.warehouse
|
||||
}
|
||||
|
||||
if (params.role) {
|
||||
baseParams.role = params.role
|
||||
}
|
||||
|
||||
// Operation-specific params
|
||||
switch (operation) {
|
||||
case 'execute_query': {
|
||||
if (!params.query) {
|
||||
throw new Error('Query is required for execute_query operation')
|
||||
}
|
||||
baseParams.query = params.query
|
||||
if (params.database) baseParams.database = params.database
|
||||
if (params.schema) baseParams.schema = params.schema
|
||||
if (params.timeout) baseParams.timeout = Number.parseInt(params.timeout)
|
||||
break
|
||||
}
|
||||
|
||||
case 'list_databases': {
|
||||
// No additional params needed
|
||||
break
|
||||
}
|
||||
|
||||
case 'list_schemas': {
|
||||
if (!params.database) {
|
||||
throw new Error('Database is required for list_schemas operation')
|
||||
}
|
||||
baseParams.database = params.database
|
||||
break
|
||||
}
|
||||
|
||||
case 'list_tables': {
|
||||
if (!params.database || !params.schema) {
|
||||
throw new Error('Database and Schema are required for list_tables operation')
|
||||
}
|
||||
baseParams.database = params.database
|
||||
baseParams.schema = params.schema
|
||||
break
|
||||
}
|
||||
|
||||
case 'list_views': {
|
||||
if (!params.database || !params.schema) {
|
||||
throw new Error('Database and Schema are required for list_views operation')
|
||||
}
|
||||
baseParams.database = params.database
|
||||
baseParams.schema = params.schema
|
||||
break
|
||||
}
|
||||
|
||||
case 'list_warehouses': {
|
||||
// No additional params needed
|
||||
break
|
||||
}
|
||||
|
||||
case 'list_file_formats': {
|
||||
if (!params.database || !params.schema) {
|
||||
throw new Error('Database and Schema are required for list_file_formats operation')
|
||||
}
|
||||
baseParams.database = params.database
|
||||
baseParams.schema = params.schema
|
||||
break
|
||||
}
|
||||
|
||||
case 'list_stages': {
|
||||
if (!params.database || !params.schema) {
|
||||
throw new Error('Database and Schema are required for list_stages operation')
|
||||
}
|
||||
baseParams.database = params.database
|
||||
baseParams.schema = params.schema
|
||||
break
|
||||
}
|
||||
|
||||
case 'describe_table': {
|
||||
if (!params.database || !params.schema || !params.table) {
|
||||
throw new Error(
|
||||
'Database, Schema, and Table are required for describe_table operation'
|
||||
)
|
||||
}
|
||||
baseParams.database = params.database
|
||||
baseParams.schema = params.schema
|
||||
baseParams.table = params.table
|
||||
break
|
||||
}
|
||||
|
||||
case 'insert_rows': {
|
||||
if (!params.database || !params.schema || !params.table) {
|
||||
throw new Error('Database, Schema, and Table are required for insert_rows operation')
|
||||
}
|
||||
if (!params.columns || !params.values) {
|
||||
throw new Error('Columns and Values are required for insert_rows operation')
|
||||
}
|
||||
|
||||
// Parse columns and values if they are strings
|
||||
let columns = params.columns
|
||||
let values = params.values
|
||||
|
||||
if (typeof columns === 'string') {
|
||||
try {
|
||||
columns = JSON.parse(columns)
|
||||
} catch (e) {
|
||||
throw new Error('Columns must be a valid JSON array')
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof values === 'string') {
|
||||
try {
|
||||
values = JSON.parse(values)
|
||||
} catch (e) {
|
||||
throw new Error('Values must be a valid JSON array of arrays')
|
||||
}
|
||||
}
|
||||
|
||||
baseParams.database = params.database
|
||||
baseParams.schema = params.schema
|
||||
baseParams.table = params.table
|
||||
baseParams.columns = columns
|
||||
baseParams.values = values
|
||||
if (params.timeout) baseParams.timeout = Number.parseInt(params.timeout)
|
||||
break
|
||||
}
|
||||
|
||||
case 'update_rows': {
|
||||
if (!params.database || !params.schema || !params.table) {
|
||||
throw new Error('Database, Schema, and Table are required for update_rows operation')
|
||||
}
|
||||
if (!params.updates) {
|
||||
throw new Error('Updates object is required for update_rows operation')
|
||||
}
|
||||
|
||||
// Parse updates if it's a string
|
||||
let updates = params.updates
|
||||
if (typeof updates === 'string') {
|
||||
try {
|
||||
updates = JSON.parse(updates)
|
||||
} catch (e) {
|
||||
throw new Error('Updates must be a valid JSON object')
|
||||
}
|
||||
}
|
||||
|
||||
baseParams.database = params.database
|
||||
baseParams.schema = params.schema
|
||||
baseParams.table = params.table
|
||||
baseParams.updates = updates
|
||||
if (params.whereClause) baseParams.whereClause = params.whereClause
|
||||
if (params.timeout) baseParams.timeout = Number.parseInt(params.timeout)
|
||||
break
|
||||
}
|
||||
|
||||
case 'delete_rows': {
|
||||
if (!params.database || !params.schema || !params.table) {
|
||||
throw new Error('Database, Schema, and Table are required for delete_rows operation')
|
||||
}
|
||||
|
||||
baseParams.database = params.database
|
||||
baseParams.schema = params.schema
|
||||
baseParams.table = params.table
|
||||
if (params.whereClause) baseParams.whereClause = params.whereClause
|
||||
if (params.timeout) baseParams.timeout = Number.parseInt(params.timeout)
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown operation: ${operation}`)
|
||||
}
|
||||
|
||||
return baseParams
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
accountUrl: {
|
||||
type: 'string',
|
||||
description: 'Snowflake account URL (e.g., xy12345.us-east-1.snowflakecomputing.com)',
|
||||
},
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
description: 'Snowflake Personal Access Token (PAT)',
|
||||
},
|
||||
warehouse: { type: 'string', description: 'Warehouse name' },
|
||||
role: { type: 'string', description: 'Role name' },
|
||||
query: { type: 'string', description: 'SQL query to execute' },
|
||||
database: { type: 'string', description: 'Database name' },
|
||||
schema: { type: 'string', description: 'Schema name' },
|
||||
table: { type: 'string', description: 'Table name' },
|
||||
columns: { type: 'json', description: 'Array of column names for insert operation' },
|
||||
values: { type: 'json', description: 'Array of arrays containing values for insert operation' },
|
||||
updates: {
|
||||
type: 'json',
|
||||
description: 'Object containing column-value pairs for update operation',
|
||||
},
|
||||
whereClause: { type: 'string', description: 'WHERE clause for update/delete operations' },
|
||||
timeout: { type: 'string', description: 'Query timeout in seconds' },
|
||||
},
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
output: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Operation results containing query data, databases, schemas, tables, or column definitions based on the selected operation',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import { GoogleDocsBlock } from '@/blocks/blocks/google_docs'
|
||||
import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
|
||||
import { GoogleFormsBlock } from '@/blocks/blocks/google_form'
|
||||
import { GoogleSheetsBlock } from '@/blocks/blocks/google_sheets'
|
||||
import { GoogleSlidesBlock } from '@/blocks/blocks/google_slides'
|
||||
import { GoogleVaultBlock } from '@/blocks/blocks/google_vault'
|
||||
import { GrafanaBlock } from '@/blocks/blocks/grafana'
|
||||
import { GuardrailsBlock } from '@/blocks/blocks/guardrails'
|
||||
@@ -97,7 +96,6 @@ import { SharepointBlock } from '@/blocks/blocks/sharepoint'
|
||||
import { ShopifyBlock } from '@/blocks/blocks/shopify'
|
||||
import { SlackBlock } from '@/blocks/blocks/slack'
|
||||
import { SmtpBlock } from '@/blocks/blocks/smtp'
|
||||
import { SnowflakeBlock } from '@/blocks/blocks/snowflake'
|
||||
import { SSHBlock } from '@/blocks/blocks/ssh'
|
||||
import { StagehandBlock } from '@/blocks/blocks/stagehand'
|
||||
import { StagehandAgentBlock } from '@/blocks/blocks/stagehand_agent'
|
||||
@@ -174,7 +172,6 @@ export const registry: Record<string, BlockConfig> = {
|
||||
google_forms: GoogleFormsBlock,
|
||||
google_search: GoogleSearchBlock,
|
||||
google_sheets: GoogleSheetsBlock,
|
||||
google_slides: GoogleSlidesBlock,
|
||||
google_vault: GoogleVaultBlock,
|
||||
hubspot: HubSpotBlock,
|
||||
huggingface: HuggingFaceBlock,
|
||||
@@ -235,7 +232,6 @@ export const registry: Record<string, BlockConfig> = {
|
||||
shopify: ShopifyBlock,
|
||||
slack: SlackBlock,
|
||||
smtp: SmtpBlock,
|
||||
snowflake: SnowflakeBlock,
|
||||
ssh: SSHBlock,
|
||||
stagehand: StagehandBlock,
|
||||
stagehand_agent: StagehandAgentBlock,
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { highlight, languages } from 'prismjs'
|
||||
import { List, type RowComponentProps, useDynamicRowHeight, useListRef } from 'react-window'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-python'
|
||||
import 'prismjs/components/prism-json'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { CODE_LINE_HEIGHT_PX, calculateGutterWidth } from './code'
|
||||
|
||||
/**
|
||||
* Virtualized code viewer for large outputs.
|
||||
* Uses react-window to render only visible lines, keeping DOM minimal.
|
||||
* Supports Prism syntax highlighting, line numbers, text wrapping, and search.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <VirtualizedCodeViewer
|
||||
* code={JSON.stringify(data, null, 2)}
|
||||
* showGutter
|
||||
* language="json"
|
||||
* wrapText
|
||||
* searchQuery="error"
|
||||
* currentMatchIndex={0}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* Props for the VirtualizedCodeViewer component.
|
||||
*/
|
||||
interface VirtualizedCodeViewerProps {
|
||||
/** Code content to display */
|
||||
code: string
|
||||
/** Whether to show line numbers gutter */
|
||||
showGutter?: boolean
|
||||
/** Language for syntax highlighting */
|
||||
language?: 'javascript' | 'json' | 'python'
|
||||
/** Additional CSS classes for the container */
|
||||
className?: string
|
||||
/** Left padding offset */
|
||||
paddingLeft?: number
|
||||
/** Inline styles for the gutter */
|
||||
gutterStyle?: React.CSSProperties
|
||||
/** Whether to wrap text */
|
||||
wrapText?: boolean
|
||||
/** Search query to highlight in the code */
|
||||
searchQuery?: string
|
||||
/** Index of the currently active match */
|
||||
currentMatchIndex?: number
|
||||
/** Callback when match count changes */
|
||||
onMatchCountChange?: (count: number) => void
|
||||
/** Ref for the content container */
|
||||
contentRef?: React.RefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
interface HighlightedLine {
|
||||
lineNumber: number
|
||||
html: string
|
||||
}
|
||||
|
||||
interface CodeRowProps {
|
||||
lines: HighlightedLine[]
|
||||
gutterWidth: number
|
||||
showGutter: boolean
|
||||
gutterStyle?: React.CSSProperties
|
||||
leftOffset: number
|
||||
wrapText: boolean
|
||||
}
|
||||
|
||||
function escapeRegex(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
function countSearchMatches(code: string, searchQuery: string): number {
|
||||
if (!searchQuery.trim()) return 0
|
||||
const escaped = escapeRegex(searchQuery)
|
||||
const regex = new RegExp(escaped, 'gi')
|
||||
const matches = code.match(regex)
|
||||
return matches?.length ?? 0
|
||||
}
|
||||
|
||||
function applySearchHighlightingToLine(
|
||||
html: string,
|
||||
searchQuery: string,
|
||||
currentMatchIndex: number,
|
||||
globalMatchOffset: number
|
||||
): { html: string; matchesInLine: number } {
|
||||
if (!searchQuery.trim()) return { html, matchesInLine: 0 }
|
||||
|
||||
const escaped = escapeRegex(searchQuery)
|
||||
const regex = new RegExp(`(${escaped})`, 'gi')
|
||||
const parts = html.split(/(<[^>]+>)/g)
|
||||
let matchesInLine = 0
|
||||
|
||||
const result = parts
|
||||
.map((part) => {
|
||||
if (part.startsWith('<') && part.endsWith('>')) {
|
||||
return part
|
||||
}
|
||||
return part.replace(regex, (match) => {
|
||||
const globalIndex = globalMatchOffset + matchesInLine
|
||||
const isCurrentMatch = globalIndex === currentMatchIndex
|
||||
matchesInLine++
|
||||
|
||||
const bgClass = isCurrentMatch
|
||||
? 'bg-[#F6AD55] text-[#1a1a1a] dark:bg-[#F6AD55] dark:text-[#1a1a1a]'
|
||||
: 'bg-[#FCD34D]/40 dark:bg-[#FCD34D]/30'
|
||||
|
||||
return `<mark class="${bgClass} rounded-[2px]" data-search-match>${match}</mark>`
|
||||
})
|
||||
})
|
||||
.join('')
|
||||
|
||||
return { html: result, matchesInLine }
|
||||
}
|
||||
|
||||
function CodeRow({ index, style, ...props }: RowComponentProps<CodeRowProps>) {
|
||||
const { lines, gutterWidth, showGutter, gutterStyle, leftOffset, wrapText } = props
|
||||
const line = lines[index]
|
||||
|
||||
return (
|
||||
<div style={style} className='flex' data-row-index={index}>
|
||||
{showGutter && (
|
||||
<div
|
||||
className='flex-shrink-0 select-none pr-0.5 text-right text-[var(--text-muted)] text-xs tabular-nums leading-[21px] dark:text-[#a8a8a8]'
|
||||
style={{ width: gutterWidth, marginLeft: leftOffset, ...gutterStyle }}
|
||||
>
|
||||
{line.lineNumber}
|
||||
</div>
|
||||
)}
|
||||
<pre
|
||||
className={cn(
|
||||
'm-0 flex-1 pr-2 pl-2 font-mono text-[13px] text-[var(--text-primary)] leading-[21px] dark:text-[#eeeeee]',
|
||||
wrapText ? 'whitespace-pre-wrap break-words' : 'whitespace-pre'
|
||||
)}
|
||||
dangerouslySetInnerHTML={{ __html: line.html || ' ' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const VirtualizedCodeViewer = memo(function VirtualizedCodeViewer({
|
||||
code,
|
||||
showGutter = true,
|
||||
language = 'json',
|
||||
className,
|
||||
paddingLeft = 0,
|
||||
gutterStyle,
|
||||
wrapText = false,
|
||||
searchQuery,
|
||||
currentMatchIndex = 0,
|
||||
onMatchCountChange,
|
||||
contentRef,
|
||||
}: VirtualizedCodeViewerProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const listRef = useListRef(null)
|
||||
const [containerHeight, setContainerHeight] = useState(400)
|
||||
|
||||
const dynamicRowHeight = useDynamicRowHeight({
|
||||
defaultRowHeight: CODE_LINE_HEIGHT_PX,
|
||||
key: wrapText ? 'wrap' : 'nowrap',
|
||||
})
|
||||
|
||||
const matchCount = useMemo(() => countSearchMatches(code, searchQuery || ''), [code, searchQuery])
|
||||
|
||||
useEffect(() => {
|
||||
onMatchCountChange?.(matchCount)
|
||||
}, [matchCount, onMatchCountChange])
|
||||
|
||||
const lines = useMemo(() => code.split('\n'), [code])
|
||||
const lineCount = lines.length
|
||||
const gutterWidth = useMemo(() => calculateGutterWidth(lineCount), [lineCount])
|
||||
|
||||
const highlightedLines = useMemo(() => {
|
||||
const lang = languages[language] || languages.javascript
|
||||
return lines.map((line, idx) => ({
|
||||
lineNumber: idx + 1,
|
||||
html: highlight(line, lang, language),
|
||||
}))
|
||||
}, [lines, language])
|
||||
|
||||
const matchOffsets = useMemo(() => {
|
||||
if (!searchQuery?.trim()) return []
|
||||
const offsets: number[] = []
|
||||
let cumulative = 0
|
||||
const escaped = escapeRegex(searchQuery)
|
||||
const regex = new RegExp(escaped, 'gi')
|
||||
|
||||
for (const line of lines) {
|
||||
offsets.push(cumulative)
|
||||
const matches = line.match(regex)
|
||||
cumulative += matches?.length ?? 0
|
||||
}
|
||||
return offsets
|
||||
}, [lines, searchQuery])
|
||||
|
||||
const linesWithSearch = useMemo(() => {
|
||||
if (!searchQuery?.trim()) return highlightedLines
|
||||
|
||||
return highlightedLines.map((line, idx) => {
|
||||
const { html } = applySearchHighlightingToLine(
|
||||
line.html,
|
||||
searchQuery,
|
||||
currentMatchIndex,
|
||||
matchOffsets[idx]
|
||||
)
|
||||
return { ...line, html }
|
||||
})
|
||||
}, [highlightedLines, searchQuery, currentMatchIndex, matchOffsets])
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchQuery?.trim() || matchCount === 0 || !listRef.current) return
|
||||
|
||||
let accumulated = 0
|
||||
for (let i = 0; i < matchOffsets.length; i++) {
|
||||
const matchesInThisLine = (matchOffsets[i + 1] ?? matchCount) - matchOffsets[i]
|
||||
if (currentMatchIndex >= accumulated && currentMatchIndex < accumulated + matchesInThisLine) {
|
||||
listRef.current.scrollToRow({ index: i, align: 'center' })
|
||||
break
|
||||
}
|
||||
accumulated += matchesInThisLine
|
||||
}
|
||||
}, [currentMatchIndex, searchQuery, matchCount, matchOffsets, listRef])
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
const parent = container.parentElement
|
||||
if (!parent) return
|
||||
|
||||
const updateHeight = () => {
|
||||
setContainerHeight(parent.clientHeight)
|
||||
}
|
||||
|
||||
updateHeight()
|
||||
|
||||
const resizeObserver = new ResizeObserver(updateHeight)
|
||||
resizeObserver.observe(parent)
|
||||
|
||||
return () => resizeObserver.disconnect()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!wrapText) return
|
||||
|
||||
const container = containerRef.current
|
||||
if (!container) return
|
||||
|
||||
const rows = container.querySelectorAll('[data-row-index]')
|
||||
if (rows.length === 0) return
|
||||
|
||||
return dynamicRowHeight.observeRowElements(rows)
|
||||
}, [wrapText, dynamicRowHeight, linesWithSearch])
|
||||
|
||||
const setRefs = useCallback(
|
||||
(el: HTMLDivElement | null) => {
|
||||
containerRef.current = el
|
||||
if (contentRef && 'current' in contentRef) {
|
||||
contentRef.current = el
|
||||
}
|
||||
},
|
||||
[contentRef]
|
||||
)
|
||||
|
||||
const rowProps = useMemo(
|
||||
() => ({
|
||||
lines: linesWithSearch,
|
||||
gutterWidth,
|
||||
showGutter,
|
||||
gutterStyle,
|
||||
leftOffset: paddingLeft,
|
||||
wrapText,
|
||||
}),
|
||||
[linesWithSearch, gutterWidth, showGutter, gutterStyle, paddingLeft, wrapText]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setRefs}
|
||||
className={cn(
|
||||
'code-editor-theme relative rounded-[4px] border border-[var(--border-strong)]',
|
||||
'bg-[var(--surface-1)] font-medium font-mono text-sm',
|
||||
'dark:bg-[#1F1F1F]',
|
||||
className
|
||||
)}
|
||||
style={{ height: containerHeight }}
|
||||
>
|
||||
<List
|
||||
listRef={listRef}
|
||||
defaultHeight={containerHeight}
|
||||
rowCount={lineCount}
|
||||
rowHeight={wrapText ? dynamicRowHeight : CODE_LINE_HEIGHT_PX}
|
||||
rowComponent={CodeRow}
|
||||
rowProps={rowProps}
|
||||
overscanCount={5}
|
||||
className='overflow-x-auto'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -8,7 +8,6 @@ export {
|
||||
highlight,
|
||||
languages,
|
||||
} from './code/code'
|
||||
export { VirtualizedCodeViewer } from './code/code-optimized'
|
||||
export { Combobox, type ComboboxOption } from './combobox/combobox'
|
||||
export { Input } from './input/input'
|
||||
export { Label } from './label/label'
|
||||
|
||||
@@ -1084,27 +1084,6 @@ export function GoogleDocsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function GoogleSlidesIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 48 48'
|
||||
width='96px'
|
||||
height='96px'
|
||||
>
|
||||
<path
|
||||
fill='#FFC107'
|
||||
d='M37,45H11c-1.657,0-3-1.343-3-3V6c0-1.657,1.343-3,3-3h19l10,10v29C40,43.657,38.657,45,37,45z'
|
||||
/>
|
||||
<path fill='#FFECB3' d='M40 13L30 13 30 3z' />
|
||||
<path fill='#FFA000' d='M30 13L40 23 40 13z' />
|
||||
<path fill='#FFF8E1' d='M14 21H34V35H14z' />
|
||||
<path fill='#FFA000' d='M16 23H32V26H16zM16 28H28V30H16z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function GoogleCalendarIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
@@ -4089,18 +4068,3 @@ export function PolymarketIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function SnowflakeIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='64'
|
||||
height='64'
|
||||
viewBox='0 0 64 64'
|
||||
fill='#29b5e8'
|
||||
>
|
||||
<path d='M9.86 15.298l13.008 7.8a3.72 3.72 0 0 0 4.589-.601 4.01 4.01 0 0 0 1.227-2.908V3.956a3.81 3.81 0 0 0-1.861-3.42 3.81 3.81 0 0 0-3.893 0 3.81 3.81 0 0 0-1.861 3.42v8.896l-7.387-4.43a3.79 3.79 0 0 0-2.922-.4c-.986.265-1.818.94-2.3 1.844-1.057 1.9-.44 4.28 1.4 5.422m31.27 7.8l13.008-7.8c1.84-1.143 2.458-3.533 1.4-5.424a3.75 3.75 0 0 0-5.22-1.452l-7.3 4.37v-8.84a3.81 3.81 0 1 0-7.615 0v15.323a4.08 4.08 0 0 0 .494 2.367c.482.903 1.314 1.57 2.3 1.844a3.71 3.71 0 0 0 2.922-.4M29.552 31.97c.013-.25.108-.5.272-.68l1.52-1.58a1.06 1.06 0 0 1 .658-.282h.057a1.05 1.05 0 0 1 .656.282l1.52 1.58a1.12 1.12 0 0 1 .272.681v.06a1.13 1.13 0 0 1-.272.683l-1.52 1.58a1.04 1.04 0 0 1-.656.284h-.057c-.246-.014-.48-.115-.658-.284l-1.52-1.58a1.13 1.13 0 0 1-.272-.683zm-4.604-.65v1.364a1.54 1.54 0 0 0 .372.93l5.16 5.357a1.42 1.42 0 0 0 .895.386h1.312a1.42 1.42 0 0 0 .895-.386l5.16-5.357a1.54 1.54 0 0 0 .372-.93V31.32a1.54 1.54 0 0 0-.372-.93l-5.16-5.357a1.42 1.42 0 0 0-.895-.386h-1.312a1.42 1.42 0 0 0-.895.386L25.32 30.4a1.55 1.55 0 0 0-.372.93M3.13 27.62l7.365 4.417L3.13 36.45a4.06 4.06 0 0 0-1.399 5.424 3.75 3.75 0 0 0 2.3 1.844c.986.274 2.042.133 2.922-.392l13.008-7.8c1.2-.762 1.9-2.078 1.9-3.492a4.16 4.16 0 0 0-1.9-3.492l-13.008-7.8a3.79 3.79 0 0 0-2.922-.4c-.986.265-1.818.94-2.3 1.844-1.057 1.9-.44 4.278 1.4 5.422m38.995 4.442a4 4 0 0 0 1.91 3.477l13 7.8c.88.524 1.934.666 2.92.392s1.817-.94 2.3-1.843a4.05 4.05 0 0 0-1.4-5.424L53.5 32.038l7.365-4.417c1.84-1.143 2.457-3.53 1.4-5.422a3.74 3.74 0 0 0-2.3-1.844c-.987-.274-2.042-.134-2.92.4l-13 7.8a4 4 0 0 0-1.91 3.507M25.48 40.508a3.7 3.7 0 0 0-2.611.464l-13.008 7.8c-1.84 1.143-2.456 3.53-1.4 5.422.483.903 1.314 1.57 2.3 1.843a3.75 3.75 0 0 0 2.922-.392l7.387-4.43v8.83a3.81 3.81 0 1 0 7.614 0V44.4a3.91 3.91 0 0 0-3.205-3.903m28.66 8.276l-13.008-7.8a3.75 3.75 0 0 0-2.922-.392 3.74 3.74 0 0 0-2.3 1.843 4.09 4.09 0 0 0-.494 2.37v15.25a3.81 3.81 0 1 0 7.614 0V51.28l7.287 4.37a3.79 3.79 0 0 0 2.922.4c.986-.265 1.818-.94 2.3-1.844 1.057-1.9.44-4.28-1.4-5.422' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,8 +98,6 @@ function resolveFileSelector(
|
||||
return { key: 'google.drive', context, allowSearch: true }
|
||||
case 'google-docs':
|
||||
return { key: 'google.drive', context, allowSearch: true }
|
||||
case 'google-slides':
|
||||
return { key: 'google.drive', context, allowSearch: true }
|
||||
case 'onedrive': {
|
||||
const key: SelectorKey = subBlock.mimeType === 'file' ? 'onedrive.files' : 'onedrive.folders'
|
||||
return { key, context, allowSearch: true }
|
||||
|
||||
@@ -482,6 +482,7 @@ export const auth = betterAuth({
|
||||
prompt: 'consent',
|
||||
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/google-forms`,
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'google-vault',
|
||||
clientId: env.GOOGLE_CLIENT_ID as string,
|
||||
|
||||
@@ -1,588 +0,0 @@
|
||||
/**
|
||||
* Organization Membership Management
|
||||
*
|
||||
* Shared helpers for adding and removing users from organizations.
|
||||
* Used by both regular routes and admin routes to ensure consistent business logic.
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'crypto'
|
||||
import { db } from '@sim/db'
|
||||
import {
|
||||
member,
|
||||
organization,
|
||||
subscription as subscriptionTable,
|
||||
user,
|
||||
userStats,
|
||||
} from '@sim/db/schema'
|
||||
import { and, eq, sql } from 'drizzle-orm'
|
||||
import { requireStripeClient } from '@/lib/billing/stripe-client'
|
||||
import { validateSeatAvailability } from '@/lib/billing/validation/seat-management'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('OrganizationMembership')
|
||||
|
||||
export interface AddMemberParams {
|
||||
userId: string
|
||||
organizationId: string
|
||||
role: 'admin' | 'member' | 'owner'
|
||||
/** Skip Pro snapshot/cancellation logic (default: false) */
|
||||
skipBillingLogic?: boolean
|
||||
/** Skip seat validation (default: false) */
|
||||
skipSeatValidation?: boolean
|
||||
}
|
||||
|
||||
export interface AddMemberResult {
|
||||
success: boolean
|
||||
memberId?: string
|
||||
error?: string
|
||||
billingActions: {
|
||||
proUsageSnapshotted: boolean
|
||||
proCancelledAtPeriodEnd: boolean
|
||||
/** If Pro was cancelled, contains info for Stripe update (caller can optionally call Stripe) */
|
||||
proSubscriptionToCancel?: {
|
||||
subscriptionId: string
|
||||
stripeSubscriptionId: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface RemoveMemberParams {
|
||||
userId: string
|
||||
organizationId: string
|
||||
memberId: string
|
||||
/** Skip departed usage capture and Pro restoration (default: false) */
|
||||
skipBillingLogic?: boolean
|
||||
}
|
||||
|
||||
export interface RemoveMemberResult {
|
||||
success: boolean
|
||||
error?: string
|
||||
billingActions: {
|
||||
usageCaptured: number
|
||||
proRestored: boolean
|
||||
usageRestored: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface MembershipValidationResult {
|
||||
canAdd: boolean
|
||||
reason?: string
|
||||
existingOrgId?: string
|
||||
seatValidation?: {
|
||||
currentSeats: number
|
||||
maxSeats: number
|
||||
availableSeats: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if a user can be added to an organization.
|
||||
* Checks single-org constraint and seat availability.
|
||||
*/
|
||||
export async function validateMembershipAddition(
|
||||
userId: string,
|
||||
organizationId: string
|
||||
): Promise<MembershipValidationResult> {
|
||||
const [userData] = await db.select({ id: user.id }).from(user).where(eq(user.id, userId)).limit(1)
|
||||
|
||||
if (!userData) {
|
||||
return { canAdd: false, reason: 'User not found' }
|
||||
}
|
||||
|
||||
const [orgData] = await db
|
||||
.select({ id: organization.id })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
if (!orgData) {
|
||||
return { canAdd: false, reason: 'Organization not found' }
|
||||
}
|
||||
|
||||
const existingMemberships = await db
|
||||
.select({ organizationId: member.organizationId })
|
||||
.from(member)
|
||||
.where(eq(member.userId, userId))
|
||||
|
||||
if (existingMemberships.length > 0) {
|
||||
const isAlreadyMemberOfThisOrg = existingMemberships.some(
|
||||
(m) => m.organizationId === organizationId
|
||||
)
|
||||
|
||||
if (isAlreadyMemberOfThisOrg) {
|
||||
return { canAdd: false, reason: 'User is already a member of this organization' }
|
||||
}
|
||||
|
||||
return {
|
||||
canAdd: false,
|
||||
reason:
|
||||
'User is already a member of another organization. Users can only belong to one organization at a time.',
|
||||
existingOrgId: existingMemberships[0].organizationId,
|
||||
}
|
||||
}
|
||||
|
||||
const seatValidation = await validateSeatAvailability(organizationId, 1)
|
||||
if (!seatValidation.canInvite) {
|
||||
return {
|
||||
canAdd: false,
|
||||
reason: seatValidation.reason || 'No seats available',
|
||||
seatValidation: {
|
||||
currentSeats: seatValidation.currentSeats,
|
||||
maxSeats: seatValidation.maxSeats,
|
||||
availableSeats: seatValidation.availableSeats,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
canAdd: true,
|
||||
seatValidation: {
|
||||
currentSeats: seatValidation.currentSeats,
|
||||
maxSeats: seatValidation.maxSeats,
|
||||
availableSeats: seatValidation.availableSeats,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a user to an organization with full billing logic.
|
||||
*
|
||||
* Handles:
|
||||
* - Single organization constraint validation
|
||||
* - Seat availability validation
|
||||
* - Member record creation
|
||||
* - Pro usage snapshot when joining paid team
|
||||
* - Pro subscription cancellation at period end
|
||||
* - Usage limit sync
|
||||
*/
|
||||
export async function addUserToOrganization(params: AddMemberParams): Promise<AddMemberResult> {
|
||||
const {
|
||||
userId,
|
||||
organizationId,
|
||||
role,
|
||||
skipBillingLogic = false,
|
||||
skipSeatValidation = false,
|
||||
} = params
|
||||
|
||||
const billingActions: AddMemberResult['billingActions'] = {
|
||||
proUsageSnapshotted: false,
|
||||
proCancelledAtPeriodEnd: false,
|
||||
}
|
||||
|
||||
try {
|
||||
if (!skipSeatValidation) {
|
||||
const validation = await validateMembershipAddition(userId, organizationId)
|
||||
if (!validation.canAdd) {
|
||||
return { success: false, error: validation.reason, billingActions }
|
||||
}
|
||||
} else {
|
||||
const existingMemberships = await db
|
||||
.select({ organizationId: member.organizationId })
|
||||
.from(member)
|
||||
.where(eq(member.userId, userId))
|
||||
|
||||
if (existingMemberships.length > 0) {
|
||||
const isAlreadyMemberOfThisOrg = existingMemberships.some(
|
||||
(m) => m.organizationId === organizationId
|
||||
)
|
||||
|
||||
if (isAlreadyMemberOfThisOrg) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'User is already a member of this organization',
|
||||
billingActions,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
'User is already a member of another organization. Users can only belong to one organization at a time.',
|
||||
billingActions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [orgSub] = await db
|
||||
.select()
|
||||
.from(subscriptionTable)
|
||||
.where(
|
||||
and(
|
||||
eq(subscriptionTable.referenceId, organizationId),
|
||||
eq(subscriptionTable.status, 'active')
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
const orgIsPaid = orgSub && (orgSub.plan === 'team' || orgSub.plan === 'enterprise')
|
||||
|
||||
let memberId = ''
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
memberId = randomUUID()
|
||||
await tx.insert(member).values({
|
||||
id: memberId,
|
||||
userId,
|
||||
organizationId,
|
||||
role,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
|
||||
// Handle Pro subscription if org is paid and we're not skipping billing logic
|
||||
if (orgIsPaid && !skipBillingLogic) {
|
||||
// Find user's active personal Pro subscription
|
||||
const [personalPro] = await tx
|
||||
.select()
|
||||
.from(subscriptionTable)
|
||||
.where(
|
||||
and(
|
||||
eq(subscriptionTable.referenceId, userId),
|
||||
eq(subscriptionTable.status, 'active'),
|
||||
eq(subscriptionTable.plan, 'pro')
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (personalPro) {
|
||||
// Snapshot the current Pro usage before resetting
|
||||
const [userStatsRow] = await tx
|
||||
.select({ currentPeriodCost: userStats.currentPeriodCost })
|
||||
.from(userStats)
|
||||
.where(eq(userStats.userId, userId))
|
||||
.limit(1)
|
||||
|
||||
if (userStatsRow) {
|
||||
const currentProUsage = userStatsRow.currentPeriodCost || '0'
|
||||
|
||||
// Snapshot Pro usage and reset currentPeriodCost so new usage goes to team
|
||||
await tx
|
||||
.update(userStats)
|
||||
.set({
|
||||
proPeriodCostSnapshot: currentProUsage,
|
||||
currentPeriodCost: '0',
|
||||
currentPeriodCopilotCost: '0',
|
||||
})
|
||||
.where(eq(userStats.userId, userId))
|
||||
|
||||
billingActions.proUsageSnapshotted = true
|
||||
|
||||
logger.info('Snapshotted Pro usage when adding to team', {
|
||||
userId,
|
||||
proUsageSnapshot: currentProUsage,
|
||||
organizationId,
|
||||
})
|
||||
}
|
||||
|
||||
// Mark Pro for cancellation at period end
|
||||
if (!personalPro.cancelAtPeriodEnd) {
|
||||
await tx
|
||||
.update(subscriptionTable)
|
||||
.set({ cancelAtPeriodEnd: true })
|
||||
.where(eq(subscriptionTable.id, personalPro.id))
|
||||
|
||||
billingActions.proCancelledAtPeriodEnd = true
|
||||
billingActions.proSubscriptionToCancel = {
|
||||
subscriptionId: personalPro.id,
|
||||
stripeSubscriptionId: personalPro.stripeSubscriptionId,
|
||||
}
|
||||
|
||||
logger.info('Marked personal Pro for cancellation at period end', {
|
||||
userId,
|
||||
subscriptionId: personalPro.id,
|
||||
organizationId,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.info('Added user to organization', {
|
||||
userId,
|
||||
organizationId,
|
||||
role,
|
||||
memberId,
|
||||
billingActions,
|
||||
})
|
||||
|
||||
return { success: true, memberId, billingActions }
|
||||
} catch (error) {
|
||||
logger.error('Failed to add user to organization', { userId, organizationId, error })
|
||||
return { success: false, error: 'Failed to add user to organization', billingActions }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a user from an organization with full billing logic.
|
||||
*
|
||||
* Handles:
|
||||
* - Owner removal prevention
|
||||
* - Departed member usage capture
|
||||
* - Member record deletion
|
||||
* - Pro subscription restoration when leaving a paid team
|
||||
* - Pro usage restoration from snapshot
|
||||
*
|
||||
* Note: Users can only belong to one organization at a time.
|
||||
*/
|
||||
export async function removeUserFromOrganization(
|
||||
params: RemoveMemberParams
|
||||
): Promise<RemoveMemberResult> {
|
||||
const { userId, organizationId, memberId, skipBillingLogic = false } = params
|
||||
|
||||
const billingActions = {
|
||||
usageCaptured: 0,
|
||||
proRestored: false,
|
||||
usageRestored: false,
|
||||
}
|
||||
|
||||
try {
|
||||
// Check member exists and get their details
|
||||
const [existingMember] = await db
|
||||
.select({
|
||||
id: member.id,
|
||||
userId: member.userId,
|
||||
role: member.role,
|
||||
})
|
||||
.from(member)
|
||||
.where(and(eq(member.id, memberId), eq(member.organizationId, organizationId)))
|
||||
.limit(1)
|
||||
|
||||
if (!existingMember) {
|
||||
return { success: false, error: 'Member not found', billingActions }
|
||||
}
|
||||
|
||||
// Prevent removing owner
|
||||
if (existingMember.role === 'owner') {
|
||||
return { success: false, error: 'Cannot remove organization owner', billingActions }
|
||||
}
|
||||
|
||||
// STEP 1: Capture departed member's usage (add to org's departedMemberUsage)
|
||||
if (!skipBillingLogic) {
|
||||
try {
|
||||
const [departingUserStats] = await db
|
||||
.select({ currentPeriodCost: userStats.currentPeriodCost })
|
||||
.from(userStats)
|
||||
.where(eq(userStats.userId, userId))
|
||||
.limit(1)
|
||||
|
||||
if (departingUserStats?.currentPeriodCost) {
|
||||
const usage = Number.parseFloat(departingUserStats.currentPeriodCost)
|
||||
if (usage > 0) {
|
||||
await db
|
||||
.update(organization)
|
||||
.set({
|
||||
departedMemberUsage: sql`${organization.departedMemberUsage} + ${usage}`,
|
||||
})
|
||||
.where(eq(organization.id, organizationId))
|
||||
|
||||
await db
|
||||
.update(userStats)
|
||||
.set({ currentPeriodCost: '0' })
|
||||
.where(eq(userStats.userId, userId))
|
||||
|
||||
billingActions.usageCaptured = usage
|
||||
|
||||
logger.info('Captured departed member usage', {
|
||||
organizationId,
|
||||
userId,
|
||||
usage,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (usageCaptureError) {
|
||||
logger.error('Failed to capture departed member usage', {
|
||||
organizationId,
|
||||
userId,
|
||||
error: usageCaptureError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 2: Delete the member record
|
||||
await db.delete(member).where(eq(member.id, memberId))
|
||||
|
||||
logger.info('Removed member from organization', {
|
||||
organizationId,
|
||||
userId,
|
||||
memberId,
|
||||
})
|
||||
|
||||
// STEP 3: Restore personal Pro if user has no remaining paid team memberships
|
||||
if (!skipBillingLogic) {
|
||||
try {
|
||||
// Check for remaining paid team memberships
|
||||
const remainingPaidTeams = await db
|
||||
.select({ orgId: member.organizationId })
|
||||
.from(member)
|
||||
.where(eq(member.userId, userId))
|
||||
|
||||
let hasAnyPaidTeam = false
|
||||
if (remainingPaidTeams.length > 0) {
|
||||
const orgIds = remainingPaidTeams.map((m) => m.orgId)
|
||||
const orgPaidSubs = await db
|
||||
.select()
|
||||
.from(subscriptionTable)
|
||||
.where(eq(subscriptionTable.status, 'active'))
|
||||
|
||||
hasAnyPaidTeam = orgPaidSubs.some(
|
||||
(s) => orgIds.includes(s.referenceId) && ['team', 'enterprise'].includes(s.plan ?? '')
|
||||
)
|
||||
}
|
||||
|
||||
// If no remaining paid teams, try to restore personal Pro
|
||||
if (!hasAnyPaidTeam) {
|
||||
const [personalPro] = await db
|
||||
.select()
|
||||
.from(subscriptionTable)
|
||||
.where(
|
||||
and(
|
||||
eq(subscriptionTable.referenceId, userId),
|
||||
eq(subscriptionTable.status, 'active'),
|
||||
eq(subscriptionTable.plan, 'pro')
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
// Only restore if cancelAtPeriodEnd is true AND stripeSubscriptionId exists
|
||||
if (
|
||||
personalPro &&
|
||||
personalPro.cancelAtPeriodEnd === true &&
|
||||
personalPro.stripeSubscriptionId
|
||||
) {
|
||||
// Call Stripe API first (separate try/catch so failure doesn't prevent DB update)
|
||||
try {
|
||||
const stripe = requireStripeClient()
|
||||
await stripe.subscriptions.update(personalPro.stripeSubscriptionId, {
|
||||
cancel_at_period_end: false,
|
||||
})
|
||||
} catch (stripeError) {
|
||||
logger.error('Stripe restore cancel_at_period_end failed for personal Pro', {
|
||||
userId,
|
||||
stripeSubscriptionId: personalPro.stripeSubscriptionId,
|
||||
error: stripeError,
|
||||
})
|
||||
}
|
||||
|
||||
// Update DB (separate try/catch)
|
||||
try {
|
||||
await db
|
||||
.update(subscriptionTable)
|
||||
.set({ cancelAtPeriodEnd: false })
|
||||
.where(eq(subscriptionTable.id, personalPro.id))
|
||||
|
||||
billingActions.proRestored = true
|
||||
|
||||
logger.info('Restored personal Pro after leaving last paid team', {
|
||||
userId,
|
||||
personalSubscriptionId: personalPro.id,
|
||||
})
|
||||
} catch (dbError) {
|
||||
logger.error('DB update failed when restoring personal Pro', {
|
||||
userId,
|
||||
subscriptionId: personalPro.id,
|
||||
error: dbError,
|
||||
})
|
||||
}
|
||||
|
||||
// Restore snapshotted Pro usage (separate try/catch)
|
||||
try {
|
||||
const [stats] = await db
|
||||
.select({
|
||||
currentPeriodCost: userStats.currentPeriodCost,
|
||||
proPeriodCostSnapshot: userStats.proPeriodCostSnapshot,
|
||||
})
|
||||
.from(userStats)
|
||||
.where(eq(userStats.userId, userId))
|
||||
.limit(1)
|
||||
|
||||
if (stats) {
|
||||
const currentUsage = stats.currentPeriodCost || '0'
|
||||
const snapshotUsage = stats.proPeriodCostSnapshot || '0'
|
||||
|
||||
const currentNum = Number.parseFloat(currentUsage)
|
||||
const snapshotNum = Number.parseFloat(snapshotUsage)
|
||||
const restoredUsage = (currentNum + snapshotNum).toString()
|
||||
|
||||
await db
|
||||
.update(userStats)
|
||||
.set({
|
||||
currentPeriodCost: restoredUsage,
|
||||
proPeriodCostSnapshot: '0',
|
||||
})
|
||||
.where(eq(userStats.userId, userId))
|
||||
|
||||
billingActions.usageRestored = true
|
||||
|
||||
logger.info('Restored Pro usage after leaving team', {
|
||||
userId,
|
||||
previousUsage: currentUsage,
|
||||
snapshotUsage: snapshotUsage,
|
||||
restoredUsage: restoredUsage,
|
||||
})
|
||||
}
|
||||
} catch (usageRestoreError) {
|
||||
logger.error('Failed to restore Pro usage after leaving team', {
|
||||
userId,
|
||||
error: usageRestoreError,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (postRemoveError) {
|
||||
logger.error('Post-removal personal Pro restore check failed', {
|
||||
organizationId,
|
||||
userId,
|
||||
error: postRemoveError,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, billingActions }
|
||||
} catch (error) {
|
||||
logger.error('Failed to remove user from organization', {
|
||||
userId,
|
||||
organizationId,
|
||||
memberId,
|
||||
error,
|
||||
})
|
||||
return { success: false, error: 'Failed to remove user from organization', billingActions }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is a member of a specific organization.
|
||||
*/
|
||||
export async function isUserMemberOfOrganization(
|
||||
userId: string,
|
||||
organizationId: string
|
||||
): Promise<{ isMember: boolean; role?: string; memberId?: string }> {
|
||||
const [memberRecord] = await db
|
||||
.select({ id: member.id, role: member.role })
|
||||
.from(member)
|
||||
.where(and(eq(member.userId, userId), eq(member.organizationId, organizationId)))
|
||||
.limit(1)
|
||||
|
||||
if (memberRecord) {
|
||||
return { isMember: true, role: memberRecord.role, memberId: memberRecord.id }
|
||||
}
|
||||
|
||||
return { isMember: false }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's current organization membership (if any).
|
||||
*/
|
||||
export async function getUserOrganization(
|
||||
userId: string
|
||||
): Promise<{ organizationId: string; role: string; memberId: string } | null> {
|
||||
const [memberRecord] = await db
|
||||
.select({
|
||||
organizationId: member.organizationId,
|
||||
role: member.role,
|
||||
memberId: member.id,
|
||||
})
|
||||
.from(member)
|
||||
.where(eq(member.userId, userId))
|
||||
.limit(1)
|
||||
|
||||
return memberRecord || null
|
||||
}
|
||||
@@ -109,7 +109,6 @@ export type OAuthService =
|
||||
| 'shopify'
|
||||
| 'zoom'
|
||||
| 'wordpress'
|
||||
|
||||
export interface OAuthProviderConfig {
|
||||
id: OAuthProvider
|
||||
name: string
|
||||
|
||||
@@ -17,29 +17,13 @@ import type { BlockState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
const logger = createLogger('AutoLayout:Core')
|
||||
|
||||
/** Handle names that indicate edges from subflow end */
|
||||
const SUBFLOW_END_HANDLES = new Set(['loop-end-source', 'parallel-end-source'])
|
||||
|
||||
/**
|
||||
* Checks if an edge comes from a subflow end handle
|
||||
*/
|
||||
function isSubflowEndEdge(edge: Edge): boolean {
|
||||
return edge.sourceHandle != null && SUBFLOW_END_HANDLES.has(edge.sourceHandle)
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns layers (columns) to blocks using topological sort.
|
||||
* Blocks with no incoming edges are placed in layer 0.
|
||||
* When edges come from subflow end handles, the subflow's internal depth is added.
|
||||
*
|
||||
* @param blocks - The blocks to assign layers to
|
||||
* @param edges - The edges connecting blocks
|
||||
* @param subflowDepths - Optional map of container block IDs to their internal depth (max layers inside)
|
||||
*/
|
||||
export function assignLayers(
|
||||
blocks: Record<string, BlockState>,
|
||||
edges: Edge[],
|
||||
subflowDepths?: Map<string, number>
|
||||
edges: Edge[]
|
||||
): Map<string, GraphNode> {
|
||||
const nodes = new Map<string, GraphNode>()
|
||||
|
||||
@@ -56,15 +40,6 @@ export function assignLayers(
|
||||
})
|
||||
}
|
||||
|
||||
// Build a map of target node -> edges coming into it (to check sourceHandle later)
|
||||
const incomingEdgesMap = new Map<string, Edge[]>()
|
||||
for (const edge of edges) {
|
||||
if (!incomingEdgesMap.has(edge.target)) {
|
||||
incomingEdgesMap.set(edge.target, [])
|
||||
}
|
||||
incomingEdgesMap.get(edge.target)!.push(edge)
|
||||
}
|
||||
|
||||
// Build adjacency from edges
|
||||
for (const edge of edges) {
|
||||
const sourceNode = nodes.get(edge.source)
|
||||
@@ -104,33 +79,15 @@ export function assignLayers(
|
||||
processed.add(nodeId)
|
||||
|
||||
// Calculate layer based on max incoming layer + 1
|
||||
// For edges from subflow ends, add the subflow's internal depth (minus 1 to avoid double-counting)
|
||||
if (node.incoming.size > 0) {
|
||||
let maxEffectiveLayer = -1
|
||||
const incomingEdges = incomingEdgesMap.get(nodeId) || []
|
||||
|
||||
let maxIncomingLayer = -1
|
||||
for (const incomingId of node.incoming) {
|
||||
const incomingNode = nodes.get(incomingId)
|
||||
if (incomingNode) {
|
||||
// Find edges from this incoming node to check if it's a subflow end edge
|
||||
const edgesFromSource = incomingEdges.filter((e) => e.source === incomingId)
|
||||
let additionalDepth = 0
|
||||
|
||||
// Check if any edge from this source is a subflow end edge
|
||||
const hasSubflowEndEdge = edgesFromSource.some(isSubflowEndEdge)
|
||||
if (hasSubflowEndEdge && subflowDepths) {
|
||||
// Get the internal depth of the subflow
|
||||
// Subtract 1 because the +1 at the end of layer calculation already accounts for one layer
|
||||
// E.g., if subflow has 2 internal layers (depth=2), we add 1 extra so total offset is 2
|
||||
const depth = subflowDepths.get(incomingId) ?? 1
|
||||
additionalDepth = Math.max(0, depth - 1)
|
||||
}
|
||||
|
||||
const effectiveLayer = incomingNode.layer + additionalDepth
|
||||
maxEffectiveLayer = Math.max(maxEffectiveLayer, effectiveLayer)
|
||||
maxIncomingLayer = Math.max(maxIncomingLayer, incomingNode.layer)
|
||||
}
|
||||
}
|
||||
node.layer = maxEffectiveLayer + 1
|
||||
node.layer = maxIncomingLayer + 1
|
||||
}
|
||||
|
||||
// Add outgoing nodes when all dependencies processed
|
||||
@@ -297,19 +254,12 @@ export function calculatePositions(
|
||||
* 4. Calculate positions
|
||||
* 5. Normalize positions to start from padding
|
||||
*
|
||||
* @param blocks - The blocks to lay out
|
||||
* @param edges - The edges connecting blocks
|
||||
* @param options - Layout options including container flag and subflow depths
|
||||
* @returns The laid-out nodes with updated positions, and bounding dimensions
|
||||
*/
|
||||
export function layoutBlocksCore(
|
||||
blocks: Record<string, BlockState>,
|
||||
edges: Edge[],
|
||||
options: {
|
||||
isContainer: boolean
|
||||
layoutOptions?: LayoutOptions
|
||||
subflowDepths?: Map<string, number>
|
||||
}
|
||||
options: { isContainer: boolean; layoutOptions?: LayoutOptions }
|
||||
): { nodes: Map<string, GraphNode>; dimensions: { width: number; height: number } } {
|
||||
if (Object.keys(blocks).length === 0) {
|
||||
return { nodes: new Map(), dimensions: { width: 0, height: 0 } }
|
||||
@@ -319,8 +269,8 @@ export function layoutBlocksCore(
|
||||
options.layoutOptions ??
|
||||
(options.isContainer ? CONTAINER_LAYOUT_OPTIONS : DEFAULT_LAYOUT_OPTIONS)
|
||||
|
||||
// 1. Assign layers (with subflow depth adjustment for subflow end edges)
|
||||
const nodes = assignLayers(blocks, edges, options.subflowDepths)
|
||||
// 1. Assign layers
|
||||
const nodes = assignLayers(blocks, edges)
|
||||
|
||||
// 2. Prepare metrics
|
||||
prepareBlockMetrics(nodes)
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { layoutContainers } from '@/lib/workflows/autolayout/containers'
|
||||
import { assignLayers, layoutBlocksCore } from '@/lib/workflows/autolayout/core'
|
||||
import { layoutBlocksCore } from '@/lib/workflows/autolayout/core'
|
||||
import type { Edge, LayoutOptions, LayoutResult } from '@/lib/workflows/autolayout/types'
|
||||
import {
|
||||
calculateSubflowDepths,
|
||||
filterLayoutEligibleBlockIds,
|
||||
getBlocksByParent,
|
||||
} from '@/lib/workflows/autolayout/utils'
|
||||
import { filterLayoutEligibleBlockIds, getBlocksByParent } from '@/lib/workflows/autolayout/utils'
|
||||
import type { BlockState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
const logger = createLogger('AutoLayout')
|
||||
@@ -40,15 +36,10 @@ export function applyAutoLayout(
|
||||
(edge) => layoutRootIds.includes(edge.source) && layoutRootIds.includes(edge.target)
|
||||
)
|
||||
|
||||
// Calculate subflow depths before laying out root blocks
|
||||
// This ensures blocks connected to subflow ends are positioned correctly
|
||||
const subflowDepths = calculateSubflowDepths(blocksCopy, edges, assignLayers)
|
||||
|
||||
if (Object.keys(rootBlocks).length > 0) {
|
||||
const { nodes } = layoutBlocksCore(rootBlocks, rootEdges, {
|
||||
isContainer: false,
|
||||
layoutOptions: options,
|
||||
subflowDepths,
|
||||
})
|
||||
|
||||
for (const node of nodes.values()) {
|
||||
|
||||
@@ -4,10 +4,9 @@ import {
|
||||
DEFAULT_HORIZONTAL_SPACING,
|
||||
DEFAULT_VERTICAL_SPACING,
|
||||
} from '@/lib/workflows/autolayout/constants'
|
||||
import { assignLayers, layoutBlocksCore } from '@/lib/workflows/autolayout/core'
|
||||
import { layoutBlocksCore } from '@/lib/workflows/autolayout/core'
|
||||
import type { Edge, LayoutOptions } from '@/lib/workflows/autolayout/types'
|
||||
import {
|
||||
calculateSubflowDepths,
|
||||
filterLayoutEligibleBlockIds,
|
||||
getBlockMetrics,
|
||||
getBlocksByParent,
|
||||
@@ -49,19 +48,7 @@ export function applyTargetedLayout(
|
||||
|
||||
const groups = getBlocksByParent(blocksCopy)
|
||||
|
||||
// Calculate subflow depths before layout to properly position blocks after subflow ends
|
||||
const subflowDepths = calculateSubflowDepths(blocksCopy, edges, assignLayers)
|
||||
|
||||
layoutGroup(
|
||||
null,
|
||||
groups.root,
|
||||
blocksCopy,
|
||||
edges,
|
||||
changedSet,
|
||||
verticalSpacing,
|
||||
horizontalSpacing,
|
||||
subflowDepths
|
||||
)
|
||||
layoutGroup(null, groups.root, blocksCopy, edges, changedSet, verticalSpacing, horizontalSpacing)
|
||||
|
||||
for (const [parentId, childIds] of groups.children.entries()) {
|
||||
layoutGroup(
|
||||
@@ -71,8 +58,7 @@ export function applyTargetedLayout(
|
||||
edges,
|
||||
changedSet,
|
||||
verticalSpacing,
|
||||
horizontalSpacing,
|
||||
subflowDepths
|
||||
horizontalSpacing
|
||||
)
|
||||
}
|
||||
|
||||
@@ -89,8 +75,7 @@ function layoutGroup(
|
||||
edges: Edge[],
|
||||
changedSet: Set<string>,
|
||||
verticalSpacing: number,
|
||||
horizontalSpacing: number,
|
||||
subflowDepths: Map<string, number>
|
||||
horizontalSpacing: number
|
||||
): void {
|
||||
if (childIds.length === 0) return
|
||||
|
||||
@@ -138,15 +123,13 @@ function layoutGroup(
|
||||
}
|
||||
|
||||
// Compute layout positions using core function
|
||||
// Only pass subflowDepths for root-level layout (not inside containers)
|
||||
const layoutPositions = computeLayoutPositions(
|
||||
layoutEligibleChildIds,
|
||||
blocks,
|
||||
edges,
|
||||
parentBlock,
|
||||
horizontalSpacing,
|
||||
verticalSpacing,
|
||||
parentId === null ? subflowDepths : undefined
|
||||
verticalSpacing
|
||||
)
|
||||
|
||||
if (layoutPositions.size === 0) {
|
||||
@@ -194,8 +177,7 @@ function computeLayoutPositions(
|
||||
edges: Edge[],
|
||||
parentBlock: BlockState | undefined,
|
||||
horizontalSpacing: number,
|
||||
verticalSpacing: number,
|
||||
subflowDepths?: Map<string, number>
|
||||
verticalSpacing: number
|
||||
): Map<string, { x: number; y: number }> {
|
||||
const subsetBlocks: Record<string, BlockState> = {}
|
||||
for (const id of childIds) {
|
||||
@@ -218,7 +200,6 @@ function computeLayoutPositions(
|
||||
verticalSpacing,
|
||||
alignment: 'center',
|
||||
},
|
||||
subflowDepths,
|
||||
})
|
||||
|
||||
// Update parent container dimensions if applicable
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ROOT_PADDING_X,
|
||||
ROOT_PADDING_Y,
|
||||
} from '@/lib/workflows/autolayout/constants'
|
||||
import type { BlockMetrics, BoundingBox, Edge, GraphNode } from '@/lib/workflows/autolayout/types'
|
||||
import type { BlockMetrics, BoundingBox, GraphNode } from '@/lib/workflows/autolayout/types'
|
||||
import { BLOCK_DIMENSIONS, CONTAINER_DIMENSIONS } from '@/lib/workflows/blocks/block-dimensions'
|
||||
import type { BlockState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
@@ -265,53 +265,3 @@ export function transferBlockHeights(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the internal depth (max layer count) for each subflow container.
|
||||
* Used to properly position blocks that connect after a subflow ends.
|
||||
*
|
||||
* @param blocks - All blocks in the workflow
|
||||
* @param edges - All edges in the workflow
|
||||
* @param assignLayersFn - Function to assign layers to blocks
|
||||
* @returns Map of container block IDs to their internal layer depth
|
||||
*/
|
||||
export function calculateSubflowDepths(
|
||||
blocks: Record<string, BlockState>,
|
||||
edges: Edge[],
|
||||
assignLayersFn: (blocks: Record<string, BlockState>, edges: Edge[]) => Map<string, GraphNode>
|
||||
): Map<string, number> {
|
||||
const depths = new Map<string, number>()
|
||||
const { children } = getBlocksByParent(blocks)
|
||||
|
||||
for (const [containerId, childIds] of children.entries()) {
|
||||
if (childIds.length === 0) {
|
||||
depths.set(containerId, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
const childBlocks: Record<string, BlockState> = {}
|
||||
const layoutChildIds = filterLayoutEligibleBlockIds(childIds, blocks)
|
||||
for (const childId of layoutChildIds) {
|
||||
childBlocks[childId] = blocks[childId]
|
||||
}
|
||||
|
||||
const childEdges = edges.filter(
|
||||
(edge) => layoutChildIds.includes(edge.source) && layoutChildIds.includes(edge.target)
|
||||
)
|
||||
|
||||
if (Object.keys(childBlocks).length === 0) {
|
||||
depths.set(containerId, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
const childNodes = assignLayersFn(childBlocks, childEdges)
|
||||
let maxLayer = 0
|
||||
for (const node of childNodes.values()) {
|
||||
maxLayer = Math.max(maxLayer, node.layer)
|
||||
}
|
||||
|
||||
depths.set(containerId, Math.max(maxLayer + 1, 1))
|
||||
}
|
||||
|
||||
return depths
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
"@react-email/components": "^0.0.34",
|
||||
"@react-email/render": "2.0.0",
|
||||
"@trigger.dev/sdk": "4.1.2",
|
||||
"@types/react-window": "2.0.0",
|
||||
"@types/three": "0.177.0",
|
||||
"better-auth": "1.3.12",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
@@ -116,7 +115,6 @@
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"react-window": "2.2.3",
|
||||
"reactflow": "^11.11.4",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
MODELS_WITH_VERBOSITY,
|
||||
PROVIDERS_WITH_TOOL_USAGE_CONTROL,
|
||||
prepareToolsWithUsageControl,
|
||||
shouldBillModelUsage,
|
||||
supportsTemperature,
|
||||
supportsToolUsageControl,
|
||||
transformCustomTool,
|
||||
@@ -41,7 +40,6 @@ describe('getApiKey', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
// @ts-expect-error - mocking boolean with different value
|
||||
isHostedSpy.mockReturnValue(false)
|
||||
|
||||
module.require = vi.fn(() => ({
|
||||
@@ -55,7 +53,6 @@ describe('getApiKey', () => {
|
||||
})
|
||||
|
||||
it('should return user-provided key when not in hosted environment', () => {
|
||||
// @ts-expect-error - mocking boolean with different value
|
||||
isHostedSpy.mockReturnValue(false)
|
||||
|
||||
// For OpenAI
|
||||
@@ -68,7 +65,6 @@ describe('getApiKey', () => {
|
||||
})
|
||||
|
||||
it('should throw error if no key provided in non-hosted environment', () => {
|
||||
// @ts-expect-error - mocking boolean with different value
|
||||
isHostedSpy.mockReturnValue(false)
|
||||
|
||||
expect(() => getApiKey('openai', 'gpt-4')).toThrow('API key is required for openai gpt-4')
|
||||
@@ -84,8 +80,7 @@ describe('getApiKey', () => {
|
||||
throw new Error('Rotation failed')
|
||||
})
|
||||
|
||||
// Use gpt-4o which IS in the hosted models list
|
||||
const key = getApiKey('openai', 'gpt-4o', 'user-fallback-key')
|
||||
const key = getApiKey('openai', 'gpt-4', 'user-fallback-key')
|
||||
expect(key).toBe('user-fallback-key')
|
||||
})
|
||||
|
||||
@@ -96,8 +91,7 @@ describe('getApiKey', () => {
|
||||
throw new Error('Rotation failed')
|
||||
})
|
||||
|
||||
// Use gpt-4o which IS in the hosted models list
|
||||
expect(() => getApiKey('openai', 'gpt-4o')).toThrow('No API key available for openai gpt-4o')
|
||||
expect(() => getApiKey('openai', 'gpt-4')).toThrow('No API key available for openai gpt-4')
|
||||
})
|
||||
|
||||
it('should require user key for non-OpenAI/Anthropic providers even in hosted environment', () => {
|
||||
@@ -110,30 +104,6 @@ describe('getApiKey', () => {
|
||||
'API key is required for other-provider some-model'
|
||||
)
|
||||
})
|
||||
|
||||
it('should require user key for models NOT in hosted list even if provider matches', () => {
|
||||
isHostedSpy.mockReturnValue(true)
|
||||
|
||||
// Models with version suffixes that are NOT in the hosted list should require user API key
|
||||
// even though they're from anthropic/openai providers
|
||||
|
||||
// User provides their own key - should work
|
||||
const key1 = getApiKey('anthropic', 'claude-sonnet-4-20250514', 'user-key-anthropic')
|
||||
expect(key1).toBe('user-key-anthropic')
|
||||
|
||||
// No user key - should throw, NOT use server key
|
||||
expect(() => getApiKey('anthropic', 'claude-sonnet-4-20250514')).toThrow(
|
||||
'API key is required for anthropic claude-sonnet-4-20250514'
|
||||
)
|
||||
|
||||
// Same for OpenAI versioned models not in list
|
||||
const key2 = getApiKey('openai', 'gpt-4o-2024-08-06', 'user-key-openai')
|
||||
expect(key2).toBe('user-key-openai')
|
||||
|
||||
expect(() => getApiKey('openai', 'gpt-4o-2024-08-06')).toThrow(
|
||||
'API key is required for openai gpt-4o-2024-08-06'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Model Capabilities', () => {
|
||||
@@ -506,52 +476,6 @@ describe('getHostedModels', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldBillModelUsage', () => {
|
||||
it.concurrent('should return true for exact matches of hosted models', () => {
|
||||
// OpenAI models
|
||||
expect(shouldBillModelUsage('gpt-4o')).toBe(true)
|
||||
expect(shouldBillModelUsage('o1')).toBe(true)
|
||||
|
||||
// Anthropic models
|
||||
expect(shouldBillModelUsage('claude-sonnet-4-0')).toBe(true)
|
||||
expect(shouldBillModelUsage('claude-opus-4-0')).toBe(true)
|
||||
|
||||
// Google models
|
||||
expect(shouldBillModelUsage('gemini-2.5-pro')).toBe(true)
|
||||
expect(shouldBillModelUsage('gemini-2.5-flash')).toBe(true)
|
||||
})
|
||||
|
||||
it.concurrent('should return false for non-hosted models', () => {
|
||||
// Other providers
|
||||
expect(shouldBillModelUsage('deepseek-v3')).toBe(false)
|
||||
expect(shouldBillModelUsage('grok-4-latest')).toBe(false)
|
||||
|
||||
// Unknown models
|
||||
expect(shouldBillModelUsage('unknown-model')).toBe(false)
|
||||
})
|
||||
|
||||
it.concurrent('should return false for versioned model names not in hosted list', () => {
|
||||
// Versioned model names that are NOT in the hosted list
|
||||
// These should NOT be billed (user provides own API key)
|
||||
expect(shouldBillModelUsage('claude-sonnet-4-20250514')).toBe(false)
|
||||
expect(shouldBillModelUsage('gpt-4o-2024-08-06')).toBe(false)
|
||||
expect(shouldBillModelUsage('claude-3-5-sonnet-20241022')).toBe(false)
|
||||
})
|
||||
|
||||
it.concurrent('should be case insensitive', () => {
|
||||
expect(shouldBillModelUsage('GPT-4O')).toBe(true)
|
||||
expect(shouldBillModelUsage('Claude-Sonnet-4-0')).toBe(true)
|
||||
expect(shouldBillModelUsage('GEMINI-2.5-PRO')).toBe(true)
|
||||
})
|
||||
|
||||
it.concurrent('should not match partial model names', () => {
|
||||
// Should not match partial/prefix models
|
||||
expect(shouldBillModelUsage('gpt-4')).toBe(false) // gpt-4o is hosted, not gpt-4
|
||||
expect(shouldBillModelUsage('claude-sonnet')).toBe(false)
|
||||
expect(shouldBillModelUsage('gemini')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Provider Management', () => {
|
||||
describe('getProviderFromModel', () => {
|
||||
it.concurrent('should return correct provider for known models', () => {
|
||||
|
||||
@@ -619,7 +619,7 @@ export function getHostedModels(): string[] {
|
||||
*/
|
||||
export function shouldBillModelUsage(model: string): boolean {
|
||||
const hostedModels = getHostedModels()
|
||||
return hostedModels.some((hostedModel) => model.toLowerCase() === hostedModel.toLowerCase())
|
||||
return hostedModels.includes(model)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -643,22 +643,19 @@ export function getApiKey(provider: string, model: string, userProvidedKey?: str
|
||||
const isGeminiModel = provider === 'google'
|
||||
|
||||
if (isHosted && (isOpenAIModel || isClaudeModel || isGeminiModel)) {
|
||||
// Only use server key if model is explicitly in our hosted list
|
||||
const hostedModels = getHostedModels()
|
||||
const isModelHosted = hostedModels.some((m) => m.toLowerCase() === model.toLowerCase())
|
||||
|
||||
if (isModelHosted) {
|
||||
try {
|
||||
const { getRotatingApiKey } = require('@/lib/core/config/api-keys')
|
||||
const serverKey = getRotatingApiKey(isGeminiModel ? 'gemini' : provider)
|
||||
return serverKey
|
||||
} catch (_error) {
|
||||
if (hasUserKey) {
|
||||
return userProvidedKey!
|
||||
}
|
||||
|
||||
throw new Error(`No API key available for ${provider} ${model}`)
|
||||
try {
|
||||
// Import the key rotation function
|
||||
const { getRotatingApiKey } = require('@/lib/core/config/api-keys')
|
||||
const serverKey = getRotatingApiKey(isGeminiModel ? 'gemini' : provider)
|
||||
return serverKey
|
||||
} catch (_error) {
|
||||
// If server key fails and we have a user key, fallback to that
|
||||
if (hasUserKey) {
|
||||
return userProvidedKey!
|
||||
}
|
||||
|
||||
// Otherwise, throw an error
|
||||
throw new Error(`No API key available for ${provider} ${model}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,13 +46,6 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
|
||||
visibility: 'user-only',
|
||||
description: 'Number of results to skip for pagination',
|
||||
},
|
||||
select: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Comma-separated list of fields to return (e.g., url,traffic,keywords,top_keyword,value). Default: url,traffic,keywords,top_keyword,value',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
@@ -69,9 +62,6 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
// Select is required by API v3 - default to common fields if not provided
|
||||
const select = params.select || 'url,traffic,keywords,top_keyword,value'
|
||||
url.searchParams.set('select', select)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
|
||||
@@ -126,7 +126,6 @@ export interface AhrefsTopPagesParams extends AhrefsBaseParams {
|
||||
mode?: AhrefsTargetMode
|
||||
limit?: number
|
||||
offset?: number
|
||||
select?: string // Comma-separated list of fields to return (e.g., "url,traffic,keywords,top_keyword,value")
|
||||
}
|
||||
|
||||
export interface AhrefsTopPage {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user