feat(rippling): expand Rippling integration from to 86 tools, landing updates (#3886)

* feat(rippling): expand Rippling integration from 16 to 86 tools

* fix(rippling): add required constraints on name and data subBlocks for create operations

* fix(rippling): add subblock ID migrations for removed legacy fields

* fix(docs): add MANUAL-CONTENT markers to tailscale docs and regenerate

* fix(rippling): add missing response fields to tool transforms

Add fields found missing by validation agents:
- list_companies: physical_address
- list/get_supergroups: sub_group_type, read_only, parent, mutually_exclusive_key, cumulatively_exhaustive_default, include_terminated
- list/get/create/update_custom_object: native_category_id, managed_package_install_id, owner_id
- list/get/create/update_custom_app: icon, pages
- list/get/create/update_custom_object_field: managed_package_install_id

* fix(rippling): add missing block outputs and required data conditions

- Add 17 missing collection output keys (titles, workLocations, supergroups, etc.)
- Add delete/bulk/report output keys (deleted, results, report_id, etc.)
- Mark data subBlock required for create_business_partner, create_custom_app,
  and create_custom_object_field (all have required params via data JSON spread)
- Add optional: true to get_current_user work_email and company_id outputs

* fix(rippling): add missing supergroup fields and fix validation issues

- Add 5 missing supergroup fields (allow_non_employees, can_override_role_states, priority, is_invisible, ignore_prov_group_matching) to types, list, and get tools
- Fix ok fallback from true to false in supergroup inclusion/exclusion member update tools
- Fix truthy check to null check for description param in create_custom_object_field

* fix(rippling): add missing custom page fields and structured custom setting responses

- Add 5 missing CustomPage fields (components, actions, canvas_actions, variables, media) to types and all page tools
- Replace opaque data blob with structured field mapping in create/update custom setting transforms
- Fix secret_value type cast consistency in list_custom_settings

* fix(rippling): add missing response fields, fix truthy checks, and improve UX

- Add 9 missing Worker fields (location, gender, date_of_birth, race, ethnicity, citizenship, termination_details, custom_fields, country_fields)
- Add 5 missing User fields (name, emails, phone_numbers, addresses, photos)
- Add worker expandable field to GroupMember types and all 3 member list tools
- Add 5 optional params to trigger_report_run (includeObjectIds, includeTotalRows, formatDateFields, formatCurrencyFields, outputType)
- Fix truthy checks to null checks in create_department, create/update_work_location
- Fix customObjectId subBlock label to say "API Name" instead of "ID"

* update docs

* fix(rippling): fix truthy checks, add missing fields, and regenerate docs

- Replace all `if (params.x)` with `if (params.x != null)` across 30+ tool files to prevent empty string/false/zero suppression
- Add expandable `parent` and `department_hierarchy` fields to department tools
- Add expandable `parent` field to team tools
- Add `company` expandable field to get_current_user
- Add `addressType` param to create/update work location tools
- Fix `secret_value` output type from 'json' to 'string' in list_custom_settings
- Regenerate docs for all 86 tools from current definitions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): add all remaining spec fields and regenerate docs

- Add 6 advanced params to create_custom_object_field: required, rqlDefinition,
  formulaAttrMetas, section, derivedFieldFormula, derivedAggregatedField
- Add 6 advanced params to update_custom_object_field: required, rqlDefinition,
  formulaAttrMetas, section, derivedFieldFormula, nameFieldDetails
- Add 4 record output fields to all custom object record tools: created_by,
  last_modified_by, owner_role, system_updated_at
- Add cursor param to get_current_user
- Add __meta response field to get_report_run
- Regenerate docs for all 86 tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): align all tools with OpenAPI spec

- Add __meta to 14 GET-by-ID tools (MetaResponse pattern)
- Fix supergroup tools: add filter to list_supergroups, remove invalid
  cursor from 4 list endpoints, revert update members to PATCH with
  Operations body
- Fix query_custom_object_records: use query/limit/cursor body params,
  return cursor instead of nextLink
- Fix bulk_create: use rows_to_write per spec
- Fix create/update record body wrappers with externalId support
- Update types.ts param interfaces and block config mappings
- Add limit param mapping with Number() conversion in block config
- Regenerate docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): address PR review comments — add dedicated subBlocks, fix data duplication, expand externalId condition

- Add dedicated apiName, businessPartnerGroupId, workerId, dataType subBlocks so required params are no longer hidden behind opaque data JSON
- Narrow `data: item` in custom object record tools to only include dynamic fields, avoiding duplication of enumerated fields
- Expand externalId subBlock condition to include create/update custom object record operations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): remove data JSON required for ops with dedicated subBlocks

create_business_partner, create_custom_app, and create_custom_object_field
now have dedicated subBlocks for their required params, so the data JSON
field is supplementary (not required) for those operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): use rest-destructuring for all custom object record data output

The spec uses additionalProperties for custom fields at the top level,
not a nested `data` sub-object. Use the same rest-destructuring pattern
across all 6 custom object record tools so `data` only contains dynamic
fields, not duplicates of enumerated standard fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): make update_custom_object_record data param optional in type

Matches the tool's `required: false` — users may update only external_id
without changing data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): add dedicated streetAddress subBlock for create_work_location

streetAddress is required by the tool but had no dedicated subBlock —
users had to include it in the data JSON. Now has its own required
subBlock matching the pattern used by all other required params.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): add allOrNothing subBlock for bulk operations

The bulk create/update/delete tools accept an optional allOrNothing
boolean param, but it had no subBlock and no way to be passed through
the block UI. Added as an advanced-mode dropdown with boolean coercion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): derive spreadOps from DATA_OPS to prevent divergence

Replace the hardcoded spreadOps array with a derivation from the
file-level DATA_OPS constant minus non-spread operations. This ensures
new create/update operations added to DATA_OPS automatically get
spread behavior without needing a second manual update.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* updated

* fix(rippling): replace generic JSON outputs with specific fields per API spec

- Extract file_url, expires_at, output_type from report run result blob
- Rename bulk create/update outputs to createdRecords/updatedRecords
- Fix list_custom_settings output key mismatch (settings → customSettings)
- Make data optional for update_custom_object_record in block
- Update block outputs to match new tool output fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix landing

* restore FF

* fix(rippling): add wandConfig, clean titles, and migrate legacy operation values

- Remove "(JSON)" suffix from all subBlock titles
- Add wandConfig with AI prompts for filter, expand, orderBy, query, data, records, and dataType fields
- Add OPERATION_VALUE_MIGRATIONS to migrate old operation values (list_employees → list_workers, etc.) preventing runtime errors on saved workflows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rippling): fix grammar typos and revert unnecessary migration

- Fix "a object" → "an object" in update/delete object category descriptions
- Revert OPERATION_VALUE_MIGRATIONS (unnecessary for low-usage integration)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(landing): add interactive workspace preview tabs

Adds Tables, Files, Knowledge Base, Logs, and Scheduled Tasks preview
components to the landing hero, with sidebar nav items that switch to each view.

* test updates

* refactor(landing): clean up code quality issues in preview components

- Replace widthMultiplier with explicit width on PreviewColumn
- Replace key={i} with key={Icon.name} in connectorIcons
- Scope --c-active CSS variable to sidebar container, eliminating hardcoded #363636 duplication
- Replace '-  -  -' fallback with em dash
- Type onSelectNav as (id: SidebarView) removing the unsafe cast

* fix(landing): use stable index key in connectorIcons to avoid minification breakage

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-04-02 01:30:43 -07:00
committed by GitHub
parent fc6fe193fa
commit 080a0a6123
137 changed files with 12448 additions and 2695 deletions

File diff suppressed because one or more lines are too long

View File

@@ -131,7 +131,7 @@ Erkennt personenbezogene Daten mithilfe von Microsoft Presidio. Unterstützt üb
**Anwendungsfälle:**
- Blockieren von Inhalten mit sensiblen persönlichen Informationen
- Maskieren von personenbezogenen Daten vor der Protokollierung oder Speicherung
- Einhaltung der DSGVO, HIPAA und anderer Datenschutzbestimmungen
- Einhaltung der DSGVO und anderer Datenschutzbestimmungen
- Bereinigung von Benutzereingaben vor der Verarbeitung
## Konfiguration

View File

@@ -132,7 +132,7 @@ Detects personally identifiable information using Microsoft Presidio. Supports o
**Use Cases:**
- Block content containing sensitive personal information
- Mask PII before logging or storing data
- Compliance with GDPR, HIPAA, and other privacy regulations
- Compliance with GDPR and other privacy regulations
- Sanitize user inputs before processing
## Configuration

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#2E2D2D"
/>
{/* MANUAL-CONTENT-START:intro */}
## Overview
[Tailscale](https://tailscale.com) is a zero-config mesh VPN built on WireGuard that makes it easy to connect devices, services, and users across any network. The Tailscale block lets you automate network management tasks like device provisioning, access control, route management, and DNS configuration directly from your Sim workflows.
@@ -39,6 +40,14 @@ Every operation requires a **tailnet** parameter. This is typically your organiz
- **Key lifecycle**: Create, list, inspect, and revoke auth keys
- **User auditing**: List all users in the tailnet and their roles
- **Policy review**: Retrieve the current ACL policy for inspection or backup
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Interact with the Tailscale API to manage devices, DNS, ACLs, auth keys, users, and routes across your tailnet.
## Tools
@@ -100,8 +109,6 @@ Get details of a specific device by ID
| `blocksIncomingConnections` | boolean | Whether the device blocks incoming connections |
| `lastSeen` | string | Last seen timestamp |
| `created` | string | Creation timestamp |
| `enabledRoutes` | array | Approved subnet routes |
| `advertisedRoutes` | array | Requested subnet routes |
| `isExternal` | boolean | Whether the device is external |
| `updateAvailable` | boolean | Whether an update is available |
| `machineKey` | string | Machine key |
@@ -263,6 +270,7 @@ Set the DNS nameservers for the tailnet
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `dns` | array | Updated list of DNS nameserver addresses |
| `magicDNS` | boolean | Whether MagicDNS is enabled |
### `tailscale_get_dns_preferences`
@@ -375,7 +383,7 @@ Create a new auth key for the tailnet to pre-authorize devices
| `reusable` | boolean | No | Whether the key can be used more than once |
| `ephemeral` | boolean | No | Whether devices authenticated with this key are ephemeral |
| `preauthorized` | boolean | No | Whether devices are pre-authorized \(skip manual approval\) |
| `tags` | string | Yes | Comma-separated list of tags for devices using this key \(e.g., "tag:server,tag:prod"\) |
| `tags` | string | No | Comma-separated list of tags for devices using this key \(e.g., "tag:server,tag:prod"\) |
| `description` | string | No | Description for the auth key |
| `expirySeconds` | number | No | Key expiry time in seconds \(default: 90 days\) |

View File

@@ -131,7 +131,7 @@ Detecta información de identificación personal utilizando Microsoft Presidio.
**Casos de uso:**
- Bloquear contenido que contiene información personal sensible
- Enmascarar PII antes de registrar o almacenar datos
- Cumplimiento de GDPR, HIPAA y otras regulaciones de privacidad
- Cumplimiento de GDPR y otras regulaciones de privacidad
- Sanear entradas de usuario antes del procesamiento
## Configuración

View File

@@ -131,7 +131,7 @@ Détecte les informations personnelles identifiables à l'aide de Microsoft Pres
**Cas d'utilisation :**
- Bloquer le contenu contenant des informations personnelles sensibles
- Masquer les PII avant de journaliser ou stocker des données
- Conformité avec le RGPD, HIPAA et autres réglementations sur la confidentialité
- Conformité avec le RGPD et autres réglementations sur la confidentialité
- Assainir les entrées utilisateur avant traitement
## Configuration

View File

@@ -131,7 +131,7 @@ Microsoft Presidioを使用して個人を特定できる情報を検出しま
**ユースケース:**
- 機密性の高い個人情報を含むコンテンツをブロック
- データのログ記録や保存前にPIIをマスク
- GDPR、HIPAA、その他のプライバシー規制への準拠
- GDPR、その他のプライバシー規制への準拠
- 処理前のユーザー入力のサニタイズ
## 設定

View File

@@ -131,7 +131,7 @@ Guardrails 模块通过针对多种验证类型检查内容,验证并保护您
**使用场景:**
- 阻止包含敏感个人信息的内容
- 在记录或存储数据之前屏蔽 PII
- 符合 GDPR、HIPAA 和其他隐私法规
- 符合 GDPR 和其他隐私法规
- 在处理之前清理用户输入
## 配置

View File

@@ -4,11 +4,11 @@
* SEO:
* - `<section id="enterprise" aria-labelledby="enterprise-heading">`.
* - `<h2 id="enterprise-heading">` for the section title.
* - Compliance certs (SOC 2, HIPAA) as visible `<strong>` text.
* - Compliance cert (SOC 2) as visible `<strong>` text.
* - Enterprise CTA links to contact form via `<a>` with `rel="noopener noreferrer"`.
*
* GEO:
* - Entity-rich: "Sim is SOC 2 and HIPAA compliant" — not "We are compliant."
* - Entity-rich: "Sim is SOC 2 compliant" — not "We are compliant."
* - `<ul>` checklist of features (SSO, RBAC, audit logs, SLA, on-premise deployment)
* as an atomic answer block for "What enterprise features does Sim offer?".
*/
@@ -66,7 +66,7 @@ const FEATURE_TAGS = [
function TrustStrip() {
return (
<div className='mx-6 mt-4 grid grid-cols-1 overflow-hidden rounded-lg border border-[var(--landing-bg-elevated)] sm:grid-cols-3 md:mx-8'>
{/* SOC 2 + HIPAA combined */}
{/* SOC 2 */}
<Link
href='https://app.vanta.com/sim.ai/trust/v35ia0jil4l7dteqjgaktn'
target='_blank'
@@ -83,10 +83,10 @@ function TrustStrip() {
/>
<div className='flex flex-col gap-[3px]'>
<strong className='font-[430] font-season text-small text-white leading-none'>
SOC 2 & HIPAA
SOC 2
</strong>
<span className='font-[430] font-season text-[color-mix(in_srgb,var(--landing-text-subtle)_55%,transparent)] text-xs leading-none tracking-[0.02em] transition-colors group-hover:text-[color-mix(in_srgb,var(--landing-text-subtle)_75%,transparent)]'>
Type II · PHI protected
Type II
</span>
</div>
</Link>

View File

@@ -42,7 +42,7 @@ export default function Hero() {
1,000+ integrations and LLMs including OpenAI, Claude, Gemini, Mistral, and xAI to
deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables,
and docs. Trusted by over 100,000 builders at startups and Fortune 500 companies. SOC2 and
HIPAA compliant.
SOC2 compliant.
</p>
<div

View File

@@ -0,0 +1,157 @@
import { File } from '@/components/emcn/icons'
import { DocxIcon, PdfIcon } from '@/components/icons/document-icons'
import type {
PreviewColumn,
PreviewRow,
} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
import {
LandingPreviewResource,
ownerCell,
} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
/** Generic audio/zip icon using basic SVG since no dedicated component exists */
function AudioIcon({ className }: { className?: string }) {
return (
<svg
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
className={className}
>
<path d='M9 18V5l12-2v13' />
<circle cx='6' cy='18' r='3' />
<circle cx='18' cy='16' r='3' />
</svg>
)
}
function JsonlIcon({ className }: { className?: string }) {
return (
<svg
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
className={className}
>
<path d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z' />
<path d='M14 2v4a2 2 0 0 0 2 2h4' />
<path d='M10 9H8' />
<path d='M16 13H8' />
<path d='M16 17H8' />
</svg>
)
}
function ZipIcon({ className }: { className?: string }) {
return (
<svg
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
className={className}
>
<path d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z' />
<path d='M14 2v4a2 2 0 0 0 2 2h4' />
<path d='M10 6h1' />
<path d='M10 10h1' />
<path d='M10 14h1' />
<path d='M9 18h2v2h-2z' />
</svg>
)
}
const COLUMNS: PreviewColumn[] = [
{ id: 'name', header: 'Name' },
{ id: 'size', header: 'Size' },
{ id: 'type', header: 'Type' },
{ id: 'created', header: 'Created' },
{ id: 'owner', header: 'Owner' },
]
const ROWS: PreviewRow[] = [
{
id: '1',
cells: {
name: { icon: <PdfIcon className='h-[14px] w-[14px]' />, label: 'Q1 Performance Report.pdf' },
size: { label: '2.4 MB' },
type: { icon: <PdfIcon className='h-[14px] w-[14px]' />, label: 'PDF' },
created: { label: '3 hours ago' },
owner: ownerCell('T', 'Theo L.'),
},
},
{
id: '2',
cells: {
name: { icon: <ZipIcon className='h-[14px] w-[14px]' />, label: 'product-screenshots.zip' },
size: { label: '18.7 MB' },
type: { icon: <ZipIcon className='h-[14px] w-[14px]' />, label: 'ZIP' },
created: { label: '1 day ago' },
owner: ownerCell('A', 'Alex M.'),
},
},
{
id: '3',
cells: {
name: { icon: <JsonlIcon className='h-[14px] w-[14px]' />, label: 'training-dataset.jsonl' },
size: { label: '892 KB' },
type: { icon: <JsonlIcon className='h-[14px] w-[14px]' />, label: 'JSONL' },
created: { label: '3 days ago' },
owner: ownerCell('J', 'Jordan P.'),
},
},
{
id: '4',
cells: {
name: { icon: <PdfIcon className='h-[14px] w-[14px]' />, label: 'brand-guidelines.pdf' },
size: { label: '5.1 MB' },
type: { icon: <PdfIcon className='h-[14px] w-[14px]' />, label: 'PDF' },
created: { label: '1 week ago' },
owner: ownerCell('S', 'Sarah K.'),
},
},
{
id: '5',
cells: {
name: { icon: <AudioIcon className='h-[14px] w-[14px]' />, label: 'customer-interviews.mp3' },
size: { label: '45.2 MB' },
type: { icon: <AudioIcon className='h-[14px] w-[14px]' />, label: 'Audio' },
created: { label: 'March 20th, 2026' },
owner: ownerCell('V', 'Vik M.'),
},
},
{
id: '6',
cells: {
name: { icon: <DocxIcon className='h-[14px] w-[14px]' />, label: 'onboarding-playbook.docx' },
size: { label: '1.1 MB' },
type: { icon: <DocxIcon className='h-[14px] w-[14px]' />, label: 'DOCX' },
created: { label: 'March 14th, 2026' },
owner: ownerCell('S', 'Sarah K.'),
},
},
]
/**
* Static landing preview of the Files workspace page.
*/
export function LandingPreviewFiles() {
return (
<LandingPreviewResource
icon={File}
title='Files'
createLabel='Upload file'
searchPlaceholder='Search files...'
columns={COLUMNS}
rows={ROWS}
/>
)
}

View File

@@ -0,0 +1,105 @@
import { Database } from '@/components/emcn/icons'
import {
AirtableIcon,
AsanaIcon,
ConfluenceIcon,
GoogleDocsIcon,
GoogleDriveIcon,
JiraIcon,
SalesforceIcon,
SlackIcon,
ZendeskIcon,
} from '@/components/icons'
import type {
PreviewColumn,
PreviewRow,
} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
import { LandingPreviewResource } from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
const DB_ICON = <Database className='h-[14px] w-[14px]' />
function connectorIcons(icons: React.ComponentType<{ className?: string }>[]) {
return {
content: (
<div className='flex items-center gap-1'>
{icons.map((Icon, i) => (
<Icon key={i} className='h-3.5 w-3.5 flex-shrink-0' />
))}
</div>
),
}
}
const COLUMNS: PreviewColumn[] = [
{ id: 'name', header: 'Name' },
{ id: 'documents', header: 'Documents' },
{ id: 'tokens', header: 'Tokens' },
{ id: 'connectors', header: 'Connectors' },
{ id: 'created', header: 'Created' },
]
const ROWS: PreviewRow[] = [
{
id: '1',
cells: {
name: { icon: DB_ICON, label: 'Product Documentation' },
documents: { label: '847' },
tokens: { label: '1,284,392' },
connectors: connectorIcons([AsanaIcon, GoogleDocsIcon]),
created: { label: '2 days ago' },
},
},
{
id: '2',
cells: {
name: { icon: DB_ICON, label: 'Customer Support KB' },
documents: { label: '234' },
tokens: { label: '892,104' },
connectors: connectorIcons([ZendeskIcon, SlackIcon]),
created: { label: '1 week ago' },
},
},
{
id: '3',
cells: {
name: { icon: DB_ICON, label: 'Engineering Wiki' },
documents: { label: '1,203' },
tokens: { label: '2,847,293' },
connectors: connectorIcons([ConfluenceIcon, JiraIcon]),
created: { label: 'March 12th, 2026' },
},
},
{
id: '4',
cells: {
name: { icon: DB_ICON, label: 'Marketing Assets' },
documents: { label: '189' },
tokens: { label: '634,821' },
connectors: connectorIcons([GoogleDriveIcon, AirtableIcon]),
created: { label: 'March 5th, 2026' },
},
},
{
id: '5',
cells: {
name: { icon: DB_ICON, label: 'Sales Playbook' },
documents: { label: '92' },
tokens: { label: '418,570' },
connectors: connectorIcons([SalesforceIcon]),
created: { label: 'February 28th, 2026' },
},
},
]
export function LandingPreviewKnowledge() {
return (
<LandingPreviewResource
icon={Database}
title='Knowledge Base'
createLabel='New base'
searchPlaceholder='Search knowledge bases...'
columns={COLUMNS}
rows={ROWS}
/>
)
}

View File

@@ -0,0 +1,321 @@
'use client'
import { useMemo, useState } from 'react'
import { Download } from 'lucide-react'
import { ArrowUpDown, Badge, Library, ListFilter, Search } from '@/components/emcn'
import type { BadgeProps } from '@/components/emcn/components/badge/badge'
import { cn } from '@/lib/core/utils/cn'
interface LogRow {
id: string
workflowName: string
workflowColor: string
date: string
status: 'completed' | 'error' | 'running'
cost: string
trigger: 'webhook' | 'api' | 'schedule' | 'manual' | 'mcp' | 'chat'
triggerLabel: string
duration: string
}
type BadgeVariant = BadgeProps['variant']
const STATUS_VARIANT: Record<LogRow['status'], BadgeVariant> = {
completed: 'gray',
error: 'red',
running: 'amber',
}
const STATUS_LABELS: Record<LogRow['status'], string> = {
completed: 'Completed',
error: 'Error',
running: 'Running',
}
const TRIGGER_VARIANT: Record<LogRow['trigger'], BadgeVariant> = {
webhook: 'orange',
api: 'blue',
schedule: 'green',
manual: 'gray-secondary',
mcp: 'cyan',
chat: 'purple',
}
const MOCK_LOGS: LogRow[] = [
{
id: '1',
workflowName: 'Customer Onboarding',
workflowColor: '#4f8ef7',
date: 'Apr 1 10:42 AM',
status: 'running',
cost: '-',
trigger: 'webhook',
triggerLabel: 'Webhook',
duration: '-',
},
{
id: '2',
workflowName: 'Lead Enrichment',
workflowColor: '#33C482',
date: 'Apr 1 09:15 AM',
status: 'error',
cost: '318 credits',
trigger: 'api',
triggerLabel: 'API',
duration: '2.7s',
},
{
id: '3',
workflowName: 'Email Campaign',
workflowColor: '#a855f7',
date: 'Apr 1 08:30 AM',
status: 'completed',
cost: '89 credits',
trigger: 'schedule',
triggerLabel: 'Schedule',
duration: '0.8s',
},
{
id: '4',
workflowName: 'Data Pipeline',
workflowColor: '#f97316',
date: 'Mar 31 10:14 PM',
status: 'completed',
cost: '241 credits',
trigger: 'webhook',
triggerLabel: 'Webhook',
duration: '4.1s',
},
{
id: '5',
workflowName: 'Invoice Processing',
workflowColor: '#ec4899',
date: 'Mar 31 08:45 PM',
status: 'completed',
cost: '112 credits',
trigger: 'manual',
triggerLabel: 'Manual',
duration: '0.9s',
},
{
id: '6',
workflowName: 'Support Triage',
workflowColor: '#0ea5e9',
date: 'Mar 31 07:22 PM',
status: 'completed',
cost: '197 credits',
trigger: 'api',
triggerLabel: 'API',
duration: '1.6s',
},
{
id: '7',
workflowName: 'Content Moderator',
workflowColor: '#f59e0b',
date: 'Mar 31 06:11 PM',
status: 'error',
cost: '284 credits',
trigger: 'schedule',
triggerLabel: 'Schedule',
duration: '3.2s',
},
]
type SortKey = 'workflowName' | 'date' | 'status' | 'cost' | 'trigger' | 'duration'
const COL_HEADERS: { key: SortKey; label: string }[] = [
{ key: 'workflowName', label: 'Workflow' },
{ key: 'date', label: 'Date' },
{ key: 'status', label: 'Status' },
{ key: 'cost', label: 'Cost' },
{ key: 'trigger', label: 'Trigger' },
{ key: 'duration', label: 'Duration' },
]
export function LandingPreviewLogs() {
const [search, setSearch] = useState('')
const [sortKey, setSortKey] = useState<SortKey | null>(null)
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc')
const [activeTab, setActiveTab] = useState<'logs' | 'dashboard'>('logs')
function handleSort(key: SortKey) {
if (sortKey === key) {
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))
} else {
setSortKey(key)
setSortDir('asc')
}
}
const sorted = useMemo(() => {
const q = search.toLowerCase()
const filtered = q
? MOCK_LOGS.filter(
(log) =>
log.workflowName.toLowerCase().includes(q) ||
log.triggerLabel.toLowerCase().includes(q) ||
STATUS_LABELS[log.status].toLowerCase().includes(q)
)
: MOCK_LOGS
if (!sortKey) return filtered
return [...filtered].sort((a, b) => {
const av = sortKey === 'cost' ? a.cost.replace(/\D/g, '') : a[sortKey]
const bv = sortKey === 'cost' ? b.cost.replace(/\D/g, '') : b[sortKey]
const cmp = av.localeCompare(bv, undefined, { numeric: true, sensitivity: 'base' })
return sortDir === 'asc' ? cmp : -cmp
})
}, [search, sortKey, sortDir])
return (
<div className='flex h-full flex-1 flex-col overflow-hidden bg-[var(--bg)]'>
{/* Header */}
<div className='border-[var(--border)] border-b px-6 py-2.5'>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-3'>
<Library className='h-[14px] w-[14px] text-[var(--text-icon)]' />
<h1 className='font-medium text-[var(--text-body)] text-sm'>Logs</h1>
</div>
<div className='flex items-center gap-1'>
<div className='flex cursor-default items-center rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption'>
<Download className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
Export
</div>
<button
type='button'
onClick={() => setActiveTab('logs')}
className='rounded-md px-2 py-1 text-caption transition-colors'
style={{
backgroundColor: activeTab === 'logs' ? 'var(--surface-active)' : 'transparent',
color: activeTab === 'logs' ? 'var(--text-body)' : 'var(--text-secondary)',
}}
>
Logs
</button>
<button
type='button'
onClick={() => setActiveTab('dashboard')}
className='rounded-md px-2 py-1 text-caption transition-colors'
style={{
backgroundColor:
activeTab === 'dashboard' ? 'var(--surface-active)' : 'transparent',
color: activeTab === 'dashboard' ? 'var(--text-body)' : 'var(--text-secondary)',
}}
>
Dashboard
</button>
</div>
</div>
</div>
{/* Options bar */}
<div className='border-[var(--border)] border-b px-6 py-2.5'>
<div className='flex items-center justify-between'>
<div className='flex flex-1 items-center gap-2.5'>
<Search className='h-[14px] w-[14px] flex-shrink-0 text-[var(--text-icon)]' />
<input
type='text'
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder='Search logs...'
className='flex-1 bg-transparent text-[var(--text-body)] text-caption outline-none placeholder:text-[var(--text-subtle)]'
/>
</div>
<div className='flex items-center gap-1.5'>
<div className='flex cursor-default items-center rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption'>
<ListFilter className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
Filter
</div>
<button
type='button'
onClick={() => handleSort(sortKey ?? 'workflowName')}
className='flex cursor-default items-center rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption transition-colors hover-hover:bg-[var(--surface-3)]'
>
<ArrowUpDown className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
Sort
</button>
</div>
</div>
</div>
{/* Table — uses <table> for pixel-perfect column alignment with headers */}
<div className='min-h-0 flex-1 overflow-hidden'>
<table className='w-full table-fixed text-sm'>
<colgroup>
<col style={{ width: '22%' }} />
<col style={{ width: '18%' }} />
<col style={{ width: '13%' }} />
<col style={{ width: '15%' }} />
<col style={{ width: '14%' }} />
<col style={{ width: '18%' }} />
</colgroup>
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
<tr>
{COL_HEADERS.map(({ key, label }) => (
<th
key={key}
className='h-10 px-6 py-1.5 text-left align-middle font-normal text-caption'
>
<button
type='button'
onClick={() => handleSort(key)}
className={cn(
'flex items-center gap-1 transition-colors hover-hover:text-[var(--text-secondary)]',
sortKey === key ? 'text-[var(--text-secondary)]' : 'text-[var(--text-muted)]'
)}
>
{label}
{sortKey === key && <ArrowUpDown className='h-[10px] w-[10px] opacity-60' />}
</button>
</th>
))}
</tr>
</thead>
<tbody>
{sorted.map((log) => (
<tr
key={log.id}
className='h-[44px] cursor-default transition-colors hover-hover:bg-[var(--surface-3)]'
>
<td className='px-6 align-middle'>
<div className='flex items-center gap-2'>
<div
className='h-[10px] w-[10px] flex-shrink-0 rounded-[3px] border-[1.5px]'
style={{
backgroundColor: log.workflowColor,
borderColor: `${log.workflowColor}60`,
backgroundClip: 'padding-box',
}}
/>
<span className='min-w-0 truncate font-medium text-[var(--text-primary)] text-caption'>
{log.workflowName}
</span>
</div>
</td>
<td className='px-6 align-middle text-[var(--text-secondary)] text-caption'>
{log.date}
</td>
<td className='px-6 align-middle'>
<Badge variant={STATUS_VARIANT[log.status]} size='sm' dot>
{STATUS_LABELS[log.status]}
</Badge>
</td>
<td className='px-6 align-middle text-[var(--text-secondary)] text-caption'>
{log.cost}
</td>
<td className='px-6 align-middle'>
<Badge variant={TRIGGER_VARIANT[log.trigger]} size='sm'>
{log.triggerLabel}
</Badge>
</td>
<td className='px-6 align-middle text-[var(--text-secondary)] text-caption'>
{log.duration}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}

View File

@@ -0,0 +1,211 @@
'use client'
import type { ReactNode } from 'react'
import { useMemo, useState } from 'react'
import { ArrowUpDown, ListFilter, Plus, Search } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
export interface PreviewColumn {
id: string
header: string
width?: number
}
export interface PreviewCell {
icon?: ReactNode
label?: string
content?: ReactNode
}
export interface PreviewRow {
id: string
cells: Record<string, PreviewCell>
}
interface LandingPreviewResourceProps {
icon: React.ComponentType<{ className?: string }>
title: string
createLabel: string
searchPlaceholder: string
columns: PreviewColumn[]
rows: PreviewRow[]
onRowClick?: (id: string) => void
}
export function ownerCell(initial: string, name: string): PreviewCell {
return {
icon: (
<span className='flex h-[14px] w-[14px] flex-shrink-0 items-center justify-center rounded-full border border-[var(--border)] bg-[var(--surface-3)] font-medium text-[8px] text-[var(--text-secondary)]'>
{initial}
</span>
),
label: name,
}
}
export function LandingPreviewResource({
icon: Icon,
title,
createLabel,
searchPlaceholder,
columns,
rows,
onRowClick,
}: LandingPreviewResourceProps) {
const [search, setSearch] = useState('')
const [sortColId, setSortColId] = useState<string | null>(null)
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc')
function handleSortClick(colId: string) {
if (sortColId === colId) {
setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))
} else {
setSortColId(colId)
setSortDir('asc')
}
}
const sorted = useMemo(() => {
const q = search.toLowerCase()
const filtered = q
? rows.filter((row) =>
Object.values(row.cells).some((cell) => cell.label?.toLowerCase().includes(q))
)
: rows
if (!sortColId) return filtered
return [...filtered].sort((a, b) => {
const av = a.cells[sortColId]?.label ?? ''
const bv = b.cells[sortColId]?.label ?? ''
const cmp = av.localeCompare(bv, undefined, { numeric: true, sensitivity: 'base' })
return sortDir === 'asc' ? cmp : -cmp
})
}, [rows, search, sortColId, sortDir])
return (
<div className='flex h-full flex-1 flex-col overflow-hidden bg-[var(--bg)]'>
{/* Header */}
<div className='border-[var(--border)] border-b px-6 py-2.5'>
<div className='flex items-center justify-between'>
<div className='flex items-center gap-3'>
<Icon className='h-[14px] w-[14px] text-[var(--text-icon)]' />
<h1 className='font-medium text-[var(--text-body)] text-sm'>{title}</h1>
</div>
<div className='flex cursor-default items-center rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption'>
<Plus className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
{createLabel}
</div>
</div>
</div>
{/* Options bar */}
<div className='border-[var(--border)] border-b px-6 py-2.5'>
<div className='flex items-center justify-between'>
<div className='flex flex-1 items-center gap-2.5'>
<Search className='h-[14px] w-[14px] flex-shrink-0 text-[var(--text-icon)]' />
<input
type='text'
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={searchPlaceholder}
className='flex-1 bg-transparent text-[var(--text-body)] text-caption outline-none placeholder:text-[var(--text-subtle)]'
/>
</div>
<div className='flex items-center gap-1.5'>
<div className='flex cursor-default items-center rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption'>
<ListFilter className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
Filter
</div>
<button
type='button'
onClick={() => handleSortClick(sortColId ?? columns[0]?.id)}
className='flex cursor-default items-center rounded-md px-2 py-1 text-[var(--text-secondary)] text-caption transition-colors hover-hover:bg-[var(--surface-3)]'
>
<ArrowUpDown className='mr-1.5 h-[14px] w-[14px] text-[var(--text-icon)]' />
Sort
</button>
</div>
</div>
</div>
{/* Table */}
<div className='min-h-0 flex-1 overflow-hidden'>
<table className='w-full table-fixed text-sm'>
<colgroup>
{columns.map((col, i) => (
<col
key={col.id}
style={i === 0 ? { minWidth: col.width ?? 200 } : { width: col.width ?? 160 }}
/>
))}
</colgroup>
<thead className='shadow-[inset_0_-1px_0_var(--border)]'>
<tr>
{columns.map((col) => (
<th
key={col.id}
className='h-10 px-6 py-1.5 text-left align-middle font-normal text-caption'
>
<button
type='button'
onClick={() => handleSortClick(col.id)}
className={cn(
'flex items-center gap-1 transition-colors hover-hover:text-[var(--text-secondary)]',
sortColId === col.id
? 'text-[var(--text-secondary)]'
: 'text-[var(--text-muted)]'
)}
>
{col.header}
{sortColId === col.id && (
<ArrowUpDown className='h-[10px] w-[10px] opacity-60' />
)}
</button>
</th>
))}
</tr>
</thead>
<tbody>
{sorted.map((row) => (
<tr
key={row.id}
onClick={() => onRowClick?.(row.id)}
className={cn(
'transition-colors hover-hover:bg-[var(--surface-3)]',
onRowClick && 'cursor-pointer'
)}
>
{columns.map((col, colIdx) => {
const cell = row.cells[col.id]
return (
<td key={col.id} className='px-6 py-2.5 align-middle'>
{cell?.content ? (
cell.content
) : (
<span
className={cn(
'flex min-w-0 items-center gap-3 font-medium text-sm',
colIdx === 0
? 'text-[var(--text-body)]'
: 'text-[var(--text-secondary)]'
)}
>
{cell?.icon && (
<span className='flex-shrink-0 text-[var(--text-icon)]'>
{cell.icon}
</span>
)}
<span className='truncate'>{cell?.label ?? '—'}</span>
</span>
)}
</td>
)
})}
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}

View File

@@ -0,0 +1,88 @@
import { Calendar } from '@/components/emcn/icons'
import type {
PreviewColumn,
PreviewRow,
} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
import { LandingPreviewResource } from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
const CAL_ICON = <Calendar className='h-[14px] w-[14px]' />
const COLUMNS: PreviewColumn[] = [
{ id: 'task', header: 'Task' },
{ id: 'schedule', header: 'Schedule', width: 240 },
{ id: 'nextRun', header: 'Next Run' },
{ id: 'lastRun', header: 'Last Run' },
]
const ROWS: PreviewRow[] = [
{
id: '1',
cells: {
task: { icon: CAL_ICON, label: 'Sync CRM contacts' },
schedule: { label: 'Recurring, every day at 9:00 AM' },
nextRun: { label: 'Tomorrow' },
lastRun: { label: '2 hours ago' },
},
},
{
id: '2',
cells: {
task: { icon: CAL_ICON, label: 'Generate weekly report' },
schedule: { label: 'Recurring, every Monday at 8:00 AM' },
nextRun: { label: 'In 5 days' },
lastRun: { label: '6 days ago' },
},
},
{
id: '3',
cells: {
task: { icon: CAL_ICON, label: 'Clean up stale files' },
schedule: { label: 'Recurring, every Sunday at midnight' },
nextRun: { label: 'In 2 days' },
lastRun: { label: '6 days ago' },
},
},
{
id: '4',
cells: {
task: { icon: CAL_ICON, label: 'Send performance digest' },
schedule: { label: 'Recurring, every Friday at 5:00 PM' },
nextRun: { label: 'In 3 days' },
lastRun: { label: '3 days ago' },
},
},
{
id: '5',
cells: {
task: { icon: CAL_ICON, label: 'Backup production data' },
schedule: { label: 'Recurring, every 4 hours' },
nextRun: { label: 'In 2 hours' },
lastRun: { label: '2 hours ago' },
},
},
{
id: '6',
cells: {
task: { icon: CAL_ICON, label: 'Scrape competitor pricing' },
schedule: { label: 'Recurring, every Tuesday at 6:00 AM' },
nextRun: { label: 'In 6 days' },
lastRun: { label: '1 week ago' },
},
},
]
/**
* Static landing preview of the Scheduled Tasks workspace page.
*/
export function LandingPreviewScheduledTasks() {
return (
<LandingPreviewResource
icon={Calendar}
title='Scheduled Tasks'
createLabel='New scheduled task'
searchPlaceholder='Search scheduled tasks...'
columns={COLUMNS}
rows={ROWS}
/>
)
}

View File

@@ -10,14 +10,25 @@ import {
Settings,
Table,
} from '@/components/emcn/icons'
import { cn } from '@/lib/core/utils/cn'
import type { PreviewWorkflow } from '@/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data'
export type SidebarView =
| 'home'
| 'workflow'
| 'tables'
| 'files'
| 'knowledge'
| 'logs'
| 'scheduled-tasks'
interface LandingPreviewSidebarProps {
workflows: PreviewWorkflow[]
activeWorkflowId: string
activeView: 'home' | 'workflow'
activeView: SidebarView
onSelectWorkflow: (id: string) => void
onSelectHome: () => void
onSelectNav: (id: SidebarView) => void
}
/**
@@ -39,7 +50,7 @@ const C = {
const WORKSPACE_NAV = [
{ id: 'tables', label: 'Tables', icon: Table },
{ id: 'files', label: 'Files', icon: File },
{ id: 'knowledge-base', label: 'Knowledge Base', icon: Database },
{ id: 'knowledge', label: 'Knowledge Base', icon: Database },
{ id: 'scheduled-tasks', label: 'Scheduled Tasks', icon: Calendar },
{ id: 'logs', label: 'Logs', icon: Library },
] as const
@@ -49,20 +60,42 @@ const FOOTER_NAV = [
{ id: 'settings', label: 'Settings', icon: Settings },
] as const
function StaticNavItem({
function NavItem({
icon: Icon,
label,
isActive,
onClick,
}: {
icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>
label: string
isActive?: boolean
onClick?: () => void
}) {
if (!onClick) {
return (
<div className='pointer-events-none mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2'>
<Icon className='h-[14px] w-[14px] flex-shrink-0' style={{ color: C.TEXT_ICON }} />
<span className='truncate text-[13px]' style={{ color: C.TEXT_BODY, fontWeight: 450 }}>
{label}
</span>
</div>
)
}
return (
<div className='pointer-events-none mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2'>
<button
type='button'
onClick={onClick}
className={cn(
'mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2 transition-colors hover-hover:bg-[var(--c-active)]',
isActive && 'bg-[var(--c-active)]'
)}
>
<Icon className='h-[14px] w-[14px] flex-shrink-0' style={{ color: C.TEXT_ICON }} />
<span className='truncate text-[13px]' style={{ color: C.TEXT_BODY, fontWeight: 450 }}>
{label}
</span>
</div>
</button>
)
}
@@ -77,13 +110,16 @@ export function LandingPreviewSidebar({
activeView,
onSelectWorkflow,
onSelectHome,
onSelectNav,
}: LandingPreviewSidebarProps) {
const isHomeActive = activeView === 'home'
return (
<div
className='flex h-full w-[248px] flex-shrink-0 flex-col pt-3'
style={{ backgroundColor: C.SURFACE_1 }}
style={
{ backgroundColor: C.SURFACE_1, '--c-active': C.SURFACE_ACTIVE } as React.CSSProperties
}
>
{/* Workspace Header */}
<div className='flex-shrink-0 px-2.5'>
@@ -116,21 +152,17 @@ export function LandingPreviewSidebar({
<button
type='button'
onClick={onSelectHome}
className='mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2 transition-colors'
style={{ backgroundColor: isHomeActive ? C.SURFACE_ACTIVE : 'transparent' }}
onMouseEnter={(e) => {
if (!isHomeActive) e.currentTarget.style.backgroundColor = C.SURFACE_ACTIVE
}}
onMouseLeave={(e) => {
if (!isHomeActive) e.currentTarget.style.backgroundColor = 'transparent'
}}
className={cn(
'mx-0.5 flex h-[28px] items-center gap-2 rounded-[8px] px-2 transition-colors hover-hover:bg-[var(--c-active)]',
isHomeActive && 'bg-[var(--c-active)]'
)}
>
<Home className='h-[14px] w-[14px] flex-shrink-0' style={{ color: C.TEXT_ICON }} />
<span className='truncate text-[13px]' style={{ color: C.TEXT_BODY, fontWeight: 450 }}>
Home
</span>
</button>
<StaticNavItem icon={Search} label='Search' />
<NavItem icon={Search} label='Search' />
</div>
{/* Workspace */}
@@ -142,7 +174,13 @@ export function LandingPreviewSidebar({
</div>
<div className='flex flex-col gap-0.5 px-2'>
{WORKSPACE_NAV.map((item) => (
<StaticNavItem key={item.id} icon={item.icon} label={item.label} />
<NavItem
key={item.id}
icon={item.icon}
label={item.label}
isActive={activeView === item.id}
onClick={() => onSelectNav(item.id)}
/>
))}
</div>
</div>
@@ -164,14 +202,10 @@ export function LandingPreviewSidebar({
key={workflow.id}
type='button'
onClick={() => onSelectWorkflow(workflow.id)}
className='group mx-0.5 flex h-[28px] w-full items-center gap-2 rounded-[8px] px-2 transition-colors'
style={{ backgroundColor: isActive ? C.SURFACE_ACTIVE : 'transparent' }}
onMouseEnter={(e) => {
if (!isActive) e.currentTarget.style.backgroundColor = C.SURFACE_ACTIVE
}}
onMouseLeave={(e) => {
if (!isActive) e.currentTarget.style.backgroundColor = 'transparent'
}}
className={cn(
'mx-0.5 flex h-[28px] w-full items-center gap-2 rounded-[8px] px-2 transition-colors hover-hover:bg-[#363636]',
isActive && 'bg-[#363636]'
)}
>
<div
className='h-[14px] w-[14px] flex-shrink-0 rounded-[4px] border-[2.5px]'
@@ -197,7 +231,7 @@ export function LandingPreviewSidebar({
{/* Footer */}
<div className='flex flex-shrink-0 flex-col gap-0.5 px-2 pt-[9px] pb-2'>
{FOOTER_NAV.map((item) => (
<StaticNavItem key={item.id} icon={item.icon} label={item.label} />
<NavItem key={item.id} icon={item.icon} label={item.label} />
))}
</div>
</div>

View File

@@ -0,0 +1,552 @@
'use client'
import { useState } from 'react'
import { Checkbox } from '@/components/emcn'
import {
ChevronDown,
Columns3,
Rows3,
Table,
TypeBoolean,
TypeNumber,
TypeText,
} from '@/components/emcn/icons'
import { cn } from '@/lib/core/utils/cn'
import type {
PreviewColumn,
PreviewRow,
} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
import {
LandingPreviewResource,
ownerCell,
} from '@/app/(home)/components/landing-preview/components/landing-preview-resource/landing-preview-resource'
const CELL = 'border-[var(--border)] border-r border-b px-2 py-[7px] align-middle select-none'
const CELL_CHECKBOX =
'border-[var(--border)] border-r border-b px-1 py-[7px] align-middle select-none'
const CELL_HEADER =
'border-[var(--border)] border-r border-b bg-[var(--bg)] p-0 text-left align-middle'
const CELL_HEADER_CHECKBOX =
'border-[var(--border)] border-r border-b bg-[var(--bg)] px-1 py-[7px] text-center align-middle'
const CELL_CONTENT =
'relative min-h-[20px] min-w-0 overflow-clip text-ellipsis whitespace-nowrap text-small'
const SELECTION_OVERLAY =
'pointer-events-none absolute -top-px -right-px -bottom-px -left-px z-[5] border-[2px] border-[var(--selection)]'
const LIST_COLUMNS: PreviewColumn[] = [
{ id: 'name', header: 'Name' },
{ id: 'columns', header: 'Columns' },
{ id: 'rows', header: 'Rows' },
{ id: 'created', header: 'Created' },
{ id: 'owner', header: 'Owner' },
]
const TABLE_METAS: Record<string, string> = {
'1': 'Customer Leads',
'2': 'Product Catalog',
'3': 'Campaign Analytics',
'4': 'User Profiles',
'5': 'Invoice Records',
}
const TABLE_ICON = <Table className='h-[14px] w-[14px]' />
const COLUMNS_ICON = <Columns3 className='h-[14px] w-[14px]' />
const ROWS_ICON = <Rows3 className='h-[14px] w-[14px]' />
const LIST_ROWS: PreviewRow[] = [
{
id: '1',
cells: {
name: { icon: TABLE_ICON, label: 'Customer Leads' },
columns: { icon: COLUMNS_ICON, label: '8' },
rows: { icon: ROWS_ICON, label: '2,847' },
created: { label: '2 days ago' },
owner: ownerCell('S', 'Sarah K.'),
},
},
{
id: '2',
cells: {
name: { icon: TABLE_ICON, label: 'Product Catalog' },
columns: { icon: COLUMNS_ICON, label: '12' },
rows: { icon: ROWS_ICON, label: '1,203' },
created: { label: '5 days ago' },
owner: ownerCell('A', 'Alex M.'),
},
},
{
id: '3',
cells: {
name: { icon: TABLE_ICON, label: 'Campaign Analytics' },
columns: { icon: COLUMNS_ICON, label: '6' },
rows: { icon: ROWS_ICON, label: '534' },
created: { label: '1 week ago' },
owner: ownerCell('W', 'Emaan K.'),
},
},
{
id: '4',
cells: {
name: { icon: TABLE_ICON, label: 'User Profiles' },
columns: { icon: COLUMNS_ICON, label: '15' },
rows: { icon: ROWS_ICON, label: '18,492' },
created: { label: '2 weeks ago' },
owner: ownerCell('J', 'Jordan P.'),
},
},
{
id: '5',
cells: {
name: { icon: TABLE_ICON, label: 'Invoice Records' },
columns: { icon: COLUMNS_ICON, label: '9' },
rows: { icon: ROWS_ICON, label: '742' },
created: { label: 'March 15th, 2026' },
owner: ownerCell('S', 'Sarah K.'),
},
},
]
interface SpreadsheetColumn {
id: string
label: string
type: 'text' | 'number' | 'boolean'
width: number
}
interface SpreadsheetRow {
id: string
cells: Record<string, string>
}
const COLUMN_TYPE_ICONS = {
text: TypeText,
number: TypeNumber,
boolean: TypeBoolean,
} as const
const SPREADSHEET_DATA: Record<string, { columns: SpreadsheetColumn[]; rows: SpreadsheetRow[] }> = {
'1': {
columns: [
{ id: 'name', label: 'Name', type: 'text', width: 160 },
{ id: 'email', label: 'Email', type: 'text', width: 200 },
{ id: 'company', label: 'Company', type: 'text', width: 160 },
{ id: 'score', label: 'Score', type: 'number', width: 100 },
{ id: 'qualified', label: 'Qualified', type: 'boolean', width: 120 },
],
rows: [
{
id: '1',
cells: {
name: 'Alice Johnson',
email: 'alice@acme.com',
company: 'Acme Corp',
score: '87',
qualified: 'true',
},
},
{
id: '2',
cells: {
name: 'Bob Williams',
email: 'bob@techco.io',
company: 'TechCo',
score: '62',
qualified: 'false',
},
},
{
id: '3',
cells: {
name: 'Carol Davis',
email: 'carol@startup.co',
company: 'StartupCo',
score: '94',
qualified: 'true',
},
},
{
id: '4',
cells: {
name: 'Dan Miller',
email: 'dan@bigcorp.com',
company: 'BigCorp',
score: '71',
qualified: 'true',
},
},
{
id: '5',
cells: {
name: 'Eva Chen',
email: 'eva@design.io',
company: 'Design IO',
score: '45',
qualified: 'false',
},
},
{
id: '6',
cells: {
name: 'Frank Lee',
email: 'frank@ventures.co',
company: 'Ventures',
score: '88',
qualified: 'true',
},
},
],
},
'2': {
columns: [
{ id: 'sku', label: 'SKU', type: 'text', width: 120 },
{ id: 'name', label: 'Product Name', type: 'text', width: 200 },
{ id: 'price', label: 'Price', type: 'number', width: 100 },
{ id: 'stock', label: 'In Stock', type: 'number', width: 120 },
{ id: 'active', label: 'Active', type: 'boolean', width: 90 },
],
rows: [
{
id: '1',
cells: {
sku: 'PRD-001',
name: 'Wireless Headphones',
price: '79.99',
stock: '234',
active: 'true',
},
},
{
id: '2',
cells: { sku: 'PRD-002', name: 'USB-C Hub', price: '49.99', stock: '89', active: 'true' },
},
{
id: '3',
cells: {
sku: 'PRD-003',
name: 'Laptop Stand',
price: '39.99',
stock: '0',
active: 'false',
},
},
{
id: '4',
cells: {
sku: 'PRD-004',
name: 'Mechanical Keyboard',
price: '129.99',
stock: '52',
active: 'true',
},
},
{
id: '5',
cells: { sku: 'PRD-005', name: 'Webcam HD', price: '89.99', stock: '17', active: 'true' },
},
{
id: '6',
cells: {
sku: 'PRD-006',
name: 'Mouse Pad XL',
price: '24.99',
stock: '0',
active: 'false',
},
},
],
},
'3': {
columns: [
{ id: 'campaign', label: 'Campaign', type: 'text', width: 180 },
{ id: 'clicks', label: 'Clicks', type: 'number', width: 100 },
{ id: 'conversions', label: 'Conversions', type: 'number', width: 140 },
{ id: 'spend', label: 'Spend ($)', type: 'number', width: 130 },
{ id: 'active', label: 'Active', type: 'boolean', width: 90 },
],
rows: [
{
id: '1',
cells: {
campaign: 'Spring Sale 2026',
clicks: '12,847',
conversions: '384',
spend: '2,400',
active: 'true',
},
},
{
id: '2',
cells: {
campaign: 'Email Reactivation',
clicks: '3,201',
conversions: '97',
spend: '450',
active: 'false',
},
},
{
id: '3',
cells: {
campaign: 'Referral Program',
clicks: '8,923',
conversions: '210',
spend: '1,100',
active: 'true',
},
},
{
id: '4',
cells: {
campaign: 'Product Launch',
clicks: '24,503',
conversions: '891',
spend: '5,800',
active: 'true',
},
},
{
id: '5',
cells: {
campaign: 'Retargeting Q1',
clicks: '6,712',
conversions: '143',
spend: '980',
active: 'false',
},
},
],
},
'4': {
columns: [
{ id: 'username', label: 'Username', type: 'text', width: 140 },
{ id: 'email', label: 'Email', type: 'text', width: 200 },
{ id: 'plan', label: 'Plan', type: 'text', width: 120 },
{ id: 'seats', label: 'Seats', type: 'number', width: 100 },
{ id: 'active', label: 'Active', type: 'boolean', width: 100 },
],
rows: [
{
id: '1',
cells: {
username: 'alice_j',
email: 'alice@acme.com',
plan: 'Pro',
seats: '5',
active: 'true',
},
},
{
id: '2',
cells: {
username: 'bobw',
email: 'bob@techco.io',
plan: 'Starter',
seats: '1',
active: 'true',
},
},
{
id: '3',
cells: {
username: 'carol_d',
email: 'carol@startup.co',
plan: 'Enterprise',
seats: '25',
active: 'true',
},
},
{
id: '4',
cells: {
username: 'dan.m',
email: 'dan@bigcorp.com',
plan: 'Pro',
seats: '10',
active: 'false',
},
},
{
id: '5',
cells: {
username: 'eva_chen',
email: 'eva@design.io',
plan: 'Starter',
seats: '1',
active: 'true',
},
},
{
id: '6',
cells: {
username: 'frank_lee',
email: 'frank@ventures.co',
plan: 'Enterprise',
seats: '50',
active: 'true',
},
},
],
},
'5': {
columns: [
{ id: 'invoice', label: 'Invoice #', type: 'text', width: 140 },
{ id: 'client', label: 'Client', type: 'text', width: 160 },
{ id: 'amount', label: 'Amount ($)', type: 'number', width: 130 },
{ id: 'paid', label: 'Paid', type: 'boolean', width: 80 },
],
rows: [
{
id: '1',
cells: { invoice: 'INV-2026-001', client: 'Acme Corp', amount: '4,800.00', paid: 'true' },
},
{
id: '2',
cells: { invoice: 'INV-2026-002', client: 'TechCo', amount: '1,200.00', paid: 'true' },
},
{
id: '3',
cells: { invoice: 'INV-2026-003', client: 'StartupCo', amount: '750.00', paid: 'false' },
},
{
id: '4',
cells: { invoice: 'INV-2026-004', client: 'BigCorp', amount: '12,500.00', paid: 'true' },
},
{
id: '5',
cells: { invoice: 'INV-2026-005', client: 'Design IO', amount: '3,300.00', paid: 'false' },
},
],
},
}
interface SpreadsheetViewProps {
tableId: string
tableName: string
onBack: () => void
}
function SpreadsheetView({ tableId, tableName, onBack }: SpreadsheetViewProps) {
const data = SPREADSHEET_DATA[tableId] ?? SPREADSHEET_DATA['1']
const [selectedCell, setSelectedCell] = useState<{ row: string; col: string } | null>(null)
return (
<div className='flex h-full flex-1 flex-col overflow-hidden bg-[var(--bg)]'>
{/* Breadcrumb header — matches real ResourceHeader breadcrumb layout */}
<div className='border-[var(--border)] border-b px-4 py-[8.5px]'>
<div className='flex items-center gap-3'>
<button
type='button'
onClick={onBack}
className='inline-flex items-center px-2 py-1 font-medium text-[var(--text-secondary)] text-sm transition-colors hover-hover:text-[var(--text-body)]'
>
<Table className='mr-3 h-[14px] w-[14px] text-[var(--text-icon)]' />
Tables
</button>
<span className='select-none text-[var(--text-icon)] text-sm'>/</span>
<span className='inline-flex items-center px-2 py-1 font-medium text-[var(--text-body)] text-sm'>
{tableName}
<ChevronDown className='ml-2 h-[7px] w-[9px] shrink-0 text-[var(--text-muted)]' />
</span>
</div>
</div>
{/* Spreadsheet — matches exact real table editor structure */}
<div className='min-h-0 flex-1 overflow-auto overscroll-none'>
<table className='table-fixed border-separate border-spacing-0 text-small'>
<colgroup>
<col style={{ width: 40 }} />
{data.columns.map((col) => (
<col key={col.id} style={{ width: col.width }} />
))}
</colgroup>
<thead className='sticky top-0 z-10'>
<tr>
<th className={CELL_HEADER_CHECKBOX} />
{data.columns.map((col) => {
const Icon = COLUMN_TYPE_ICONS[col.type] ?? TypeText
return (
<th key={col.id} className={CELL_HEADER}>
<div className='flex h-full w-full min-w-0 items-center px-2 py-[7px]'>
<Icon className='h-3 w-3 shrink-0 text-[var(--text-icon)]' />
<span className='ml-1.5 min-w-0 overflow-clip text-ellipsis whitespace-nowrap font-medium text-[var(--text-primary)] text-small'>
{col.label}
</span>
<ChevronDown className='ml-auto h-[7px] w-[9px] shrink-0 text-[var(--text-muted)]' />
</div>
</th>
)
})}
</tr>
</thead>
<tbody>
{data.rows.map((row, rowIdx) => (
<tr key={row.id}>
<td className={cn(CELL_CHECKBOX, 'text-center')}>
<span className='text-[var(--text-tertiary)] text-xs tabular-nums'>
{rowIdx + 1}
</span>
</td>
{data.columns.map((col) => {
const isSelected = selectedCell?.row === row.id && selectedCell?.col === col.id
const cellValue = row.cells[col.id] ?? ''
return (
<td
key={col.id}
onClick={() => setSelectedCell({ row: row.id, col: col.id })}
className={cn(
CELL,
'relative cursor-default text-[var(--text-body)]',
isSelected && 'bg-[rgba(37,99,235,0.06)]'
)}
>
{isSelected && <div className={SELECTION_OVERLAY} />}
<div className={CELL_CONTENT}>
{col.type === 'boolean' ? (
<div className='flex min-h-[20px] items-center justify-center'>
<Checkbox
size='sm'
checked={cellValue === 'true'}
className='pointer-events-none'
/>
</div>
) : (
cellValue
)}
</div>
</td>
)
})}
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
export function LandingPreviewTables() {
const [openTableId, setOpenTableId] = useState<string | null>(null)
if (openTableId !== null) {
return (
<SpreadsheetView
tableId={openTableId}
tableName={TABLE_METAS[openTableId] ?? 'Table'}
onBack={() => setOpenTableId(null)}
/>
)
}
return (
<LandingPreviewResource
icon={Table}
title='Tables'
createLabel='New table'
searchPlaceholder='Search tables...'
columns={LIST_COLUMNS}
rows={LIST_ROWS}
onRowClick={(id) => setOpenTableId(id)}
/>
)
}

View File

@@ -228,13 +228,13 @@ export const PreviewBlockNode = memo(function PreviewBlockNode({
{tools && tools.length > 0 && (
<div className='flex items-center gap-2'>
<span className='flex-shrink-0 font-normal text-[#b3b3b3] text-[14px]'>Tools</span>
<div className='flex flex-1 flex-wrap items-center justify-end gap-2'>
<div className='flex flex-1 flex-wrap items-center justify-end gap-[5px]'>
{tools.map((tool) => {
const ToolIcon = BLOCK_ICONS[tool.type]
return (
<div
key={tool.type}
className='flex items-center gap-2 rounded-[5px] border border-[#3d3d3d] bg-[#2a2a2a] px-2 py-1'
className='flex items-center gap-[5px] rounded-[5px] border border-[#3d3d3d] bg-[#2a2a2a] px-[6px] py-[3px]'
>
<div
className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-[4px]'

View File

@@ -127,6 +127,60 @@ const SELF_HEALING_CRM_WORKFLOW: PreviewWorkflow = {
edges: [{ id: 'e-3', source: 'schedule-1', target: 'mothership-1' }],
}
/**
* Customer Support Agent workflow — Gmail Trigger -> Agent (KB + Notion tools) -> Slack
*/
const CUSTOMER_SUPPORT_WORKFLOW: PreviewWorkflow = {
id: 'wf-customer-support',
name: 'Customer Support Agent',
color: '#0EA5E9',
blocks: [
{
id: 'gmail-1',
name: 'Gmail',
type: 'gmail',
bgColor: '#E0E0E0',
rows: [
{ title: 'Event', value: 'New Email' },
{ title: 'Label', value: 'Support' },
],
position: { x: 80, y: 140 },
hideTargetHandle: true,
},
{
id: 'agent-3',
name: 'Support Agent',
type: 'agent',
bgColor: '#701ffc',
rows: [
{ title: 'Model', value: 'gpt-5.4' },
{ title: 'System Prompt', value: 'Resolve customer issues...' },
],
tools: [
{ name: 'Knowledge', type: 'knowledge_base', bgColor: '#10B981' },
{ name: 'Notion', type: 'notion', bgColor: '#181C1E' },
],
position: { x: 420, y: 40 },
},
{
id: 'slack-3',
name: 'Slack',
type: 'slack',
bgColor: '#611f69',
rows: [
{ title: 'Channel', value: '#support' },
{ title: 'Operation', value: 'Send Message' },
],
position: { x: 420, y: 260 },
hideSourceHandle: true,
},
],
edges: [
{ id: 'e-cs-1', source: 'gmail-1', target: 'agent-3' },
{ id: 'e-cs-2', source: 'gmail-1', target: 'slack-3' },
],
}
/**
* Empty "New Agent" workflow — a single note prompting the user to start building
*/
@@ -153,6 +207,7 @@ const NEW_AGENT_WORKFLOW: PreviewWorkflow = {
export const PREVIEW_WORKFLOWS: PreviewWorkflow[] = [
SELF_HEALING_CRM_WORKFLOW,
IT_SERVICE_WORKFLOW,
CUSTOMER_SUPPORT_WORKFLOW,
NEW_AGENT_WORKFLOW,
]

View File

@@ -2,9 +2,15 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { motion, type Variants } from 'framer-motion'
import { LandingPreviewFiles } from '@/app/(home)/components/landing-preview/components/landing-preview-files/landing-preview-files'
import { LandingPreviewHome } from '@/app/(home)/components/landing-preview/components/landing-preview-home/landing-preview-home'
import { LandingPreviewKnowledge } from '@/app/(home)/components/landing-preview/components/landing-preview-knowledge/landing-preview-knowledge'
import { LandingPreviewLogs } from '@/app/(home)/components/landing-preview/components/landing-preview-logs/landing-preview-logs'
import { LandingPreviewPanel } from '@/app/(home)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
import { LandingPreviewScheduledTasks } from '@/app/(home)/components/landing-preview/components/landing-preview-scheduled-tasks/landing-preview-scheduled-tasks'
import type { SidebarView } from '@/app/(home)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar'
import { LandingPreviewSidebar } from '@/app/(home)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar'
import { LandingPreviewTables } from '@/app/(home)/components/landing-preview/components/landing-preview-tables/landing-preview-tables'
import { LandingPreviewWorkflow } from '@/app/(home)/components/landing-preview/components/landing-preview-workflow/landing-preview-workflow'
import {
EASE_OUT,
@@ -46,18 +52,16 @@ const panelVariants: Variants = {
* Interactive workspace preview for the hero section.
*
* Renders a lightweight replica of the Sim workspace with:
* - A sidebar with two selectable workflows
* - A sidebar with selectable workflows and workspace nav items
* - A ReactFlow canvas showing the active workflow's blocks and edges
* - Static previews of Tables, Files, Knowledge Base, Logs, and Scheduled Tasks
* - A panel with a functional copilot input (stores prompt + redirects to /signup)
*
* Everything except the workflow items and the copilot input is non-interactive.
* On mount the sidebar slides from left and the panel from right. The canvas
* background stays fully opaque; individual block nodes animate in with a
* staggered fade. Edges draw left-to-right. Animations only fire on initial
* load — workflow switches render instantly.
* Only workflow items, the home button, workspace nav items, and the copilot input
* are interactive. Animations only fire on initial load.
*/
export function LandingPreview() {
const [activeView, setActiveView] = useState<'home' | 'workflow'>('workflow')
const [activeView, setActiveView] = useState<SidebarView>('workflow')
const [activeWorkflowId, setActiveWorkflowId] = useState(PREVIEW_WORKFLOWS[0].id)
const isInitialMount = useRef(true)
@@ -74,11 +78,34 @@ export function LandingPreview() {
setActiveView('home')
}, [])
const handleSelectNav = useCallback((id: SidebarView) => {
setActiveView(id)
}, [])
const activeWorkflow =
PREVIEW_WORKFLOWS.find((w) => w.id === activeWorkflowId) ?? PREVIEW_WORKFLOWS[0]
const isWorkflowView = activeView === 'workflow'
function renderContent() {
switch (activeView) {
case 'workflow':
return <LandingPreviewWorkflow workflow={activeWorkflow} animate={isInitialMount.current} />
case 'home':
return <LandingPreviewHome />
case 'tables':
return <LandingPreviewTables />
case 'files':
return <LandingPreviewFiles />
case 'knowledge':
return <LandingPreviewKnowledge />
case 'logs':
return <LandingPreviewLogs />
case 'scheduled-tasks':
return <LandingPreviewScheduledTasks />
}
}
return (
<motion.div
className='dark flex aspect-[1116/549] w-full overflow-hidden rounded bg-[var(--landing-bg-surface)] antialiased'
@@ -93,6 +120,7 @@ export function LandingPreview() {
activeView={activeView}
onSelectWorkflow={handleSelectWorkflow}
onSelectHome={handleSelectHome}
onSelectNav={handleSelectNav}
/>
</motion.div>
<div className='flex min-w-0 flex-1 flex-col py-2 pr-2 pl-2 lg:pl-0'>
@@ -104,11 +132,7 @@ export function LandingPreview() {
: 'relative flex min-w-0 flex-1 flex-col overflow-hidden'
}
>
{isWorkflowView ? (
<LandingPreviewWorkflow workflow={activeWorkflow} animate={isInitialMount.current} />
) : (
<LandingPreviewHome />
)}
{renderContent()}
</div>
<motion.div
className={isWorkflowView ? 'hidden lg:flex' : 'hidden'}

View File

@@ -80,7 +80,7 @@ const PRICING_TIERS: PricingTier[] = [
'Custom execution limits',
'Custom concurrency limits',
'Unlimited log retention',
'SSO & SCIM · SOC2 & HIPAA',
'SSO & SCIM · SOC2',
'Self hosting · Dedicated support',
],
cta: { label: 'Book a demo', action: 'demo-request' },

View File

@@ -93,7 +93,7 @@ export default function StructuredData() {
url: 'https://sim.ai',
name: 'Sim — Build AI Agents & Run Your Agentic Workforce',
description:
'Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders. SOC2 and HIPAA compliant.',
'Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders. SOC2 compliant.',
applicationCategory: 'DeveloperApplication',
operatingSystem: 'Web',
browserRequirements: 'Requires a modern browser with JavaScript enabled',
@@ -179,7 +179,7 @@ export default function StructuredData() {
name: 'What is Sim?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Sim is the open-source platform to build AI agents and run your agentic workforce. Teams connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders. SOC2 and HIPAA compliant.',
text: 'Sim is the open-source platform to build AI agents and run your agentic workforce. Teams connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders. SOC2 compliant.',
},
},
{
@@ -211,7 +211,7 @@ export default function StructuredData() {
name: 'What enterprise features does Sim offer?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Sim offers SOC2 and HIPAA compliance, SSO/SAML authentication, role-based access control, audit logs, dedicated support, custom SLAs, and on-premise deployment options for enterprise customers.',
text: 'Sim offers SOC2 compliance, SSO/SAML authentication, role-based access control, audit logs, dedicated support, custom SLAs, and on-premise deployment options for enterprise customers.',
},
},
],

View File

@@ -9293,90 +9293,358 @@
"type": "rippling",
"slug": "rippling",
"name": "Rippling",
"description": "Manage employees, leave, departments, and company data in Rippling",
"longDescription": "Integrate Rippling into your workflow. Manage employees, departments, teams, leave requests, work locations, groups, candidates, and company information.",
"description": "Manage workers, departments, custom objects, and company data in Rippling",
"longDescription": "Integrate Rippling Platform into your workflow. Manage workers, users, departments, teams, titles, work locations, business partners, supergroups, custom objects, custom apps, custom pages, custom settings, object categories, reports, and draft hires.",
"bgColor": "#FFCC1C",
"iconName": "RipplingIcon",
"docsUrl": "https://docs.sim.ai/tools/rippling",
"operations": [
{
"name": "List Employees",
"description": "List all employees in Rippling with optional pagination"
"name": "List Workers",
"description": "List all workers with optional filtering and pagination"
},
{
"name": "Get Employee",
"description": "Get details for a specific employee by ID"
"name": "Get Worker",
"description": "Get a specific worker by ID"
},
{
"name": "List Employees (Including Terminated)",
"description": "List all employees in Rippling including terminated employees with optional pagination"
"name": "List Users",
"description": "List all users with optional pagination"
},
{
"name": "List Departments",
"description": "List all departments in the Rippling organization"
"name": "Get User",
"description": "Get a specific user by ID"
},
{
"name": "List Teams",
"description": "List all teams in Rippling"
},
{
"name": "List Levels",
"description": "List all position levels in Rippling"
},
{
"name": "List Work Locations",
"description": "List all work locations in Rippling"
},
{
"name": "Get Company",
"description": "Get details for the current company in Rippling"
},
{
"name": "Get Company Activity",
"description": "Get activity events for the current company in Rippling"
},
{
"name": "List Custom Fields",
"description": "List all custom fields defined in Rippling"
"name": "List Companies",
"description": "List all companies"
},
{
"name": "Get Current User",
"description": "Get the current authenticated user details"
"description": "Get SSO information for the current user"
},
{
"name": "List Leave Requests",
"description": "List leave requests in Rippling with optional filtering by date range and status"
"name": "List Entitlements",
"description": "List all entitlements"
},
{
"name": "Approve/Decline Leave Request",
"description": "Approve or decline a leave request in Rippling"
"name": "List Departments",
"description": "List all departments"
},
{
"name": "List Leave Balances",
"description": "List leave balances for all employees in Rippling"
"name": "Get Department",
"description": "Get a specific department by ID"
},
{
"name": "Get Leave Balance",
"description": "Get leave balance for a specific employee by role ID"
"name": "Create Department",
"description": "Create a new department"
},
{
"name": "List Leave Types",
"description": "List company leave types configured in Rippling"
"name": "Update Department",
"description": "Update an existing department"
},
{
"name": "Create Group",
"description": "Create a new group in Rippling"
"name": "List Teams",
"description": "List all teams"
},
{
"name": "Update Group",
"description": "Update an existing group in Rippling"
"name": "Get Team",
"description": "Get a specific team by ID"
},
{
"name": "Push Candidate",
"description": "Push a candidate to onboarding in Rippling"
"name": "List Employment Types",
"description": "List all employment types"
},
{
"name": "Get Employment Type",
"description": "Get a specific employment type by ID"
},
{
"name": "List Titles",
"description": "List all titles"
},
{
"name": "Get Title",
"description": "Get a specific title by ID"
},
{
"name": "Create Title",
"description": "Create a new title"
},
{
"name": "Update Title",
"description": "Update an existing title"
},
{
"name": "Delete Title",
"description": "Delete a title"
},
{
"name": "List Custom Fields",
"description": "List all custom fields"
},
{
"name": "List Job Functions",
"description": "List all job functions"
},
{
"name": "Get Job Function",
"description": "Get a specific job function by ID"
},
{
"name": "List Work Locations",
"description": "List all work locations"
},
{
"name": "Get Work Location",
"description": "Get a specific work location by ID"
},
{
"name": "Create Work Location",
"description": "Create a new work location"
},
{
"name": "Update Work Location",
"description": "Update a work location"
},
{
"name": "Delete Work Location",
"description": "Delete a work location"
},
{
"name": "List Business Partners",
"description": "List all business partners"
},
{
"name": "Get Business Partner",
"description": "Get a specific business partner by ID"
},
{
"name": "Create Business Partner",
"description": "Create a new business partner"
},
{
"name": "Delete Business Partner",
"description": "Delete a business partner"
},
{
"name": "List Business Partner Groups",
"description": "List all business partner groups"
},
{
"name": "Get Business Partner Group",
"description": "Get a specific business partner group by ID"
},
{
"name": "Create Business Partner Group",
"description": "Create a new business partner group"
},
{
"name": "Delete Business Partner Group",
"description": "Delete a business partner group"
},
{
"name": "List Supergroups",
"description": "List all supergroups"
},
{
"name": "Get Supergroup",
"description": "Get a specific supergroup by ID"
},
{
"name": "List Supergroup Members",
"description": "List members of a supergroup"
},
{
"name": "List Supergroup Inclusion Members",
"description": "List inclusion members of a supergroup"
},
{
"name": "List Supergroup Exclusion Members",
"description": "List exclusion members of a supergroup"
},
{
"name": "Update Supergroup Inclusion Members",
"description": "Update inclusion members of a supergroup"
},
{
"name": "Update Supergroup Exclusion Members",
"description": "Update exclusion members of a supergroup"
},
{
"name": "List Custom Objects",
"description": "List all custom objects"
},
{
"name": "Get Custom Object",
"description": "Get a custom object by API name"
},
{
"name": "Create Custom Object",
"description": "Create a new custom object"
},
{
"name": "Update Custom Object",
"description": "Update a custom object"
},
{
"name": "Delete Custom Object",
"description": "Delete a custom object"
},
{
"name": "List Custom Object Fields",
"description": "List all fields for a custom object"
},
{
"name": "Get Custom Object Field",
"description": "Get a specific field of a custom object"
},
{
"name": "Create Custom Object Field",
"description": "Create a field on a custom object"
},
{
"name": "Update Custom Object Field",
"description": "Update a field on a custom object"
},
{
"name": "Delete Custom Object Field",
"description": "Delete a field from a custom object"
},
{
"name": "List Custom Object Records",
"description": "List all records for a custom object"
},
{
"name": "Get Custom Object Record",
"description": "Get a specific custom object record"
},
{
"name": "Get Custom Object Record by External ID",
"description": "Get a custom object record by external ID"
},
{
"name": "Query Custom Object Records",
"description": "Query custom object records with filters"
},
{
"name": "Create Custom Object Record",
"description": "Create a custom object record"
},
{
"name": "Update Custom Object Record",
"description": "Update a custom object record"
},
{
"name": "Delete Custom Object Record",
"description": "Delete a custom object record"
},
{
"name": "Bulk Create Custom Object Records",
"description": "Bulk create custom object records"
},
{
"name": "Bulk Update Custom Object Records",
"description": "Bulk update custom object records"
},
{
"name": "Bulk Delete Custom Object Records",
"description": "Bulk delete custom object records"
},
{
"name": "List Custom Apps",
"description": "List all custom apps"
},
{
"name": "Get Custom App",
"description": "Get a specific custom app"
},
{
"name": "Create Custom App",
"description": "Create a new custom app"
},
{
"name": "Update Custom App",
"description": "Update a custom app"
},
{
"name": "Delete Custom App",
"description": "Delete a custom app"
},
{
"name": "List Custom Pages",
"description": "List all custom pages"
},
{
"name": "Get Custom Page",
"description": "Get a specific custom page"
},
{
"name": "Create Custom Page",
"description": "Create a new custom page"
},
{
"name": "Update Custom Page",
"description": "Update a custom page"
},
{
"name": "Delete Custom Page",
"description": "Delete a custom page"
},
{
"name": "List Custom Settings",
"description": "List all custom settings"
},
{
"name": "Get Custom Setting",
"description": "Get a specific custom setting"
},
{
"name": "Create Custom Setting",
"description": "Create a new custom setting"
},
{
"name": "Update Custom Setting",
"description": "Update a custom setting"
},
{
"name": "Delete Custom Setting",
"description": "Delete a custom setting"
},
{
"name": "List Object Categories",
"description": "List all object categories"
},
{
"name": "Get Object Category",
"description": "Get a specific object category"
},
{
"name": "Create Object Category",
"description": "Create a new object category"
},
{
"name": "Update Object Category",
"description": "Update a object category"
},
{
"name": "Delete Object Category",
"description": "Delete a object category"
},
{
"name": "Get Report Run",
"description": "Get a report run by ID"
},
{
"name": "Trigger Report Run",
"description": "Trigger a new report run"
},
{
"name": "Create Draft Hires",
"description": "Create bulk draft hires"
}
],
"operationCount": 19,
"operationCount": 86,
"triggers": [],
"triggerCount": 0,
"authType": "api-key",

View File

@@ -342,7 +342,7 @@ describe('Function Execute API Route', () => {
code: 'return "Email sent to user"',
params: {
email: {
from: 'Waleed Latif <waleed@sim.ai>',
from: 'Dr. Shaw <shaw@high-flying.ai>',
to: 'User <user@example.com>',
},
},
@@ -378,7 +378,7 @@ describe('Function Execute API Route', () => {
async () => {
const emailData = {
id: '123',
from: 'Waleed Latif <waleed@sim.ai>',
from: 'Dr. Shaw <shaw@high-flying.ai>',
to: 'User <user@example.com>',
subject: 'Test Email',
bodyText: 'Hello world',

View File

@@ -9,7 +9,7 @@ export async function GET() {
## Overview
Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over 100,000 builders use Sim — from startups to Fortune 500 companies. Teams connect their tools and data, build agents that execute real workflows across systems, and manage them with full observability. SOC2 and HIPAA compliant.
Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over 100,000 builders use Sim — from startups to Fortune 500 companies. Teams connect their tools and data, build agents that execute real workflows across systems, and manage them with full observability. SOC2 compliant.
## Product Details
@@ -17,7 +17,7 @@ Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over
- **Category**: AI Agent Platform / Agentic Workflow Orchestration
- **Deployment**: Cloud (SaaS) and Self-hosted options
- **Pricing**: Free tier, Pro ($25/month, 6K credits), Max ($100/month, 25K credits), Team plans available, Enterprise (custom)
- **Compliance**: SOC2 Type II, HIPAA compliant
- **Compliance**: SOC2 Type II
## Core Concepts

View File

@@ -7,7 +7,7 @@ export async function GET() {
> Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.
Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over 100,000 builders use Sim — from startups to Fortune 500 companies. SOC2 and HIPAA compliant.
Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over 100,000 builders use Sim — from startups to Fortune 500 companies. SOC2 compliant.
## Core Pages

View File

@@ -14,7 +14,7 @@ export const metadata: Metadata = {
description:
'Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to orchestrate agentic workflows.',
keywords:
'AI agents, agentic workforce, open-source AI agent platform, agentic workflows, LLM orchestration, AI automation, knowledge base, workflow builder, AI integrations, SOC2 compliant, HIPAA compliant, enterprise AI',
'AI agents, agentic workforce, open-source AI agent platform, agentic workflows, LLM orchestration, AI automation, knowledge base, workflow builder, AI integrations, SOC2 compliant, enterprise AI',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -130,8 +130,6 @@ This is useful for internal platforms, customer-facing deployments, or any scena
Sim maintains **SOC 2 Type II** certification with annual audits covering security, availability, and confidentiality controls. We share our SOC 2 report directly with prospective customers under NDA.
**HIPAA** — Business Associate Agreements available for healthcare organizations. Requires self-hosted deployment or dedicated infrastructure.
**Data Retention** — Configure how long workflow execution traces, inputs, and outputs are stored before automatic deletion. We work with enterprise customers to set retention policies that match their compliance requirements.
We provide penetration test reports, architecture documentation, and completed security questionnaires (SIG, CAIQ, and custom formats) for your vendor review process.

View File

@@ -163,7 +163,7 @@ Beyond the 80+ built-in integrations, Sim supports the Model Context Protocol (M
#### Flexible Deployment Options
Sim offers both cloud-hosted and self-hosted deployment options. Organizations can run Sim on their own infrastructure for complete control, or use the managed cloud service for simplicity. The platform is SOC2 and HIPAA compliant, ensuring enterprise-level security.
Sim offers both cloud-hosted and self-hosted deployment options. Organizations can run Sim on their own infrastructure for complete control, or use the managed cloud service for simplicity. The platform is SOC2 compliant, ensuring enterprise-level security.
#### Production-Ready Infrastructure

View File

@@ -9,7 +9,7 @@ export function generateBrandedMetadata(override: Partial<Metadata> = {}): Metad
const brand = getBrandConfig()
const defaultTitle = brand.name
const summaryFull = `Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders — from startups to Fortune 500 companies. SOC2 and HIPAA compliant.`
const summaryFull = `Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders — from startups to Fortune 500 companies. SOC2 compliant.`
const summaryShort = `Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.`
return {
@@ -132,7 +132,7 @@ export function generateStructuredData() {
'@type': 'SoftwareApplication',
name: 'Sim',
description:
'Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders. SOC2 and HIPAA compliant.',
'Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows. Create agents, workflows, knowledge bases, tables, and docs. Trusted by over 100,000 builders. SOC2 compliant.',
url: getBaseUrl(),
applicationCategory: 'BusinessApplication',
operatingSystem: 'Web',

View File

@@ -27,6 +27,30 @@ export const SUBBLOCK_ID_MIGRATIONS: Record<string, Record<string, string>> = {
emailType: '_removed_emailType',
phoneType: '_removed_phoneType',
},
rippling: {
action: '_removed_action',
candidateDepartment: '_removed_candidateDepartment',
candidatePhone: '_removed_candidatePhone',
candidateStartDate: '_removed_candidateStartDate',
email: '_removed_email',
employeeId: '_removed_employeeId',
endDate: '_removed_endDate',
firstName: '_removed_firstName',
groupId: '_removed_groupId',
groupName: '_removed_groupName',
groupVersion: '_removed_groupVersion',
jobTitle: '_removed_jobTitle',
lastName: '_removed_lastName',
leaveRequestId: '_removed_leaveRequestId',
managedBy: '_removed_managedBy',
nextCursor: '_removed_nextCursor',
offset: '_removed_offset',
roleId: '_removed_roleId',
spokeId: '_removed_spokeId',
startDate: '_removed_startDate',
status: '_removed_status',
users: '_removed_users',
},
}
/**

View File

@@ -1898,25 +1898,92 @@ import {
revenuecatUpdateSubscriberAttributesTool,
} from '@/tools/revenuecat'
import {
ripplingCreateGroupTool,
ripplingGetCompanyActivityTool,
ripplingGetCompanyTool,
ripplingBulkCreateCustomObjectRecordsTool,
ripplingBulkDeleteCustomObjectRecordsTool,
ripplingBulkUpdateCustomObjectRecordsTool,
ripplingCreateBusinessPartnerGroupTool,
ripplingCreateBusinessPartnerTool,
ripplingCreateCustomAppTool,
ripplingCreateCustomObjectFieldTool,
ripplingCreateCustomObjectRecordTool,
ripplingCreateCustomObjectTool,
ripplingCreateCustomPageTool,
ripplingCreateCustomSettingTool,
ripplingCreateDepartmentTool,
ripplingCreateDraftHiresTool,
ripplingCreateObjectCategoryTool,
ripplingCreateTitleTool,
ripplingCreateWorkLocationTool,
ripplingDeleteBusinessPartnerGroupTool,
ripplingDeleteBusinessPartnerTool,
ripplingDeleteCustomAppTool,
ripplingDeleteCustomObjectFieldTool,
ripplingDeleteCustomObjectRecordTool,
ripplingDeleteCustomObjectTool,
ripplingDeleteCustomPageTool,
ripplingDeleteCustomSettingTool,
ripplingDeleteObjectCategoryTool,
ripplingDeleteTitleTool,
ripplingDeleteWorkLocationTool,
ripplingGetBusinessPartnerGroupTool,
ripplingGetBusinessPartnerTool,
ripplingGetCurrentUserTool,
ripplingGetEmployeeTool,
ripplingGetLeaveBalanceTool,
ripplingGetCustomAppTool,
ripplingGetCustomObjectFieldTool,
ripplingGetCustomObjectRecordByExternalIdTool,
ripplingGetCustomObjectRecordTool,
ripplingGetCustomObjectTool,
ripplingGetCustomPageTool,
ripplingGetCustomSettingTool,
ripplingGetDepartmentTool,
ripplingGetEmploymentTypeTool,
ripplingGetJobFunctionTool,
ripplingGetObjectCategoryTool,
ripplingGetReportRunTool,
ripplingGetSupergroupTool,
ripplingGetTeamTool,
ripplingGetTitleTool,
ripplingGetUserTool,
ripplingGetWorkerTool,
ripplingGetWorkLocationTool,
ripplingListBusinessPartnerGroupsTool,
ripplingListBusinessPartnersTool,
ripplingListCompaniesTool,
ripplingListCustomAppsTool,
ripplingListCustomFieldsTool,
ripplingListCustomObjectFieldsTool,
ripplingListCustomObjectRecordsTool,
ripplingListCustomObjectsTool,
ripplingListCustomPagesTool,
ripplingListCustomSettingsTool,
ripplingListDepartmentsTool,
ripplingListEmployeesTool,
ripplingListEmployeesWithTerminatedTool,
ripplingListLeaveBalancesTool,
ripplingListLeaveRequestsTool,
ripplingListLeaveTypesTool,
ripplingListLevelsTool,
ripplingListEmploymentTypesTool,
ripplingListEntitlementsTool,
ripplingListJobFunctionsTool,
ripplingListObjectCategoriesTool,
ripplingListSupergroupExclusionMembersTool,
ripplingListSupergroupInclusionMembersTool,
ripplingListSupergroupMembersTool,
ripplingListSupergroupsTool,
ripplingListTeamsTool,
ripplingListTitlesTool,
ripplingListUsersTool,
ripplingListWorkersTool,
ripplingListWorkLocationsTool,
ripplingProcessLeaveRequestTool,
ripplingPushCandidateTool,
ripplingUpdateGroupTool,
ripplingQueryCustomObjectRecordsTool,
ripplingTriggerReportRunTool,
ripplingUpdateCustomAppTool,
ripplingUpdateCustomObjectFieldTool,
ripplingUpdateCustomObjectRecordTool,
ripplingUpdateCustomObjectTool,
ripplingUpdateCustomPageTool,
ripplingUpdateCustomSettingTool,
ripplingUpdateDepartmentTool,
ripplingUpdateObjectCategoryTool,
ripplingUpdateSupergroupExclusionMembersTool,
ripplingUpdateSupergroupInclusionMembersTool,
ripplingUpdateTitleTool,
ripplingUpdateWorkLocationTool,
} from '@/tools/rippling'
import {
s3CopyObjectTool,
@@ -3621,25 +3688,92 @@ export const tools: Record<string, ToolConfig> = {
revenuecat_defer_google_subscription: revenuecatDeferGoogleSubscriptionTool,
revenuecat_refund_google_subscription: revenuecatRefundGoogleSubscriptionTool,
revenuecat_revoke_google_subscription: revenuecatRevokeGoogleSubscriptionTool,
rippling_create_group: ripplingCreateGroupTool,
rippling_get_company: ripplingGetCompanyTool,
rippling_get_company_activity: ripplingGetCompanyActivityTool,
rippling_bulk_create_custom_object_records: ripplingBulkCreateCustomObjectRecordsTool,
rippling_bulk_delete_custom_object_records: ripplingBulkDeleteCustomObjectRecordsTool,
rippling_bulk_update_custom_object_records: ripplingBulkUpdateCustomObjectRecordsTool,
rippling_create_business_partner: ripplingCreateBusinessPartnerTool,
rippling_create_business_partner_group: ripplingCreateBusinessPartnerGroupTool,
rippling_create_custom_app: ripplingCreateCustomAppTool,
rippling_create_custom_object: ripplingCreateCustomObjectTool,
rippling_create_custom_object_field: ripplingCreateCustomObjectFieldTool,
rippling_create_custom_object_record: ripplingCreateCustomObjectRecordTool,
rippling_create_custom_page: ripplingCreateCustomPageTool,
rippling_create_custom_setting: ripplingCreateCustomSettingTool,
rippling_create_department: ripplingCreateDepartmentTool,
rippling_create_draft_hires: ripplingCreateDraftHiresTool,
rippling_create_object_category: ripplingCreateObjectCategoryTool,
rippling_create_title: ripplingCreateTitleTool,
rippling_create_work_location: ripplingCreateWorkLocationTool,
rippling_delete_business_partner: ripplingDeleteBusinessPartnerTool,
rippling_delete_business_partner_group: ripplingDeleteBusinessPartnerGroupTool,
rippling_delete_custom_app: ripplingDeleteCustomAppTool,
rippling_delete_custom_object: ripplingDeleteCustomObjectTool,
rippling_delete_custom_object_field: ripplingDeleteCustomObjectFieldTool,
rippling_delete_custom_object_record: ripplingDeleteCustomObjectRecordTool,
rippling_delete_custom_page: ripplingDeleteCustomPageTool,
rippling_delete_custom_setting: ripplingDeleteCustomSettingTool,
rippling_delete_object_category: ripplingDeleteObjectCategoryTool,
rippling_delete_title: ripplingDeleteTitleTool,
rippling_delete_work_location: ripplingDeleteWorkLocationTool,
rippling_get_business_partner: ripplingGetBusinessPartnerTool,
rippling_get_business_partner_group: ripplingGetBusinessPartnerGroupTool,
rippling_get_current_user: ripplingGetCurrentUserTool,
rippling_get_employee: ripplingGetEmployeeTool,
rippling_get_leave_balance: ripplingGetLeaveBalanceTool,
rippling_get_custom_app: ripplingGetCustomAppTool,
rippling_get_custom_object: ripplingGetCustomObjectTool,
rippling_get_custom_object_field: ripplingGetCustomObjectFieldTool,
rippling_get_custom_object_record: ripplingGetCustomObjectRecordTool,
rippling_get_custom_object_record_by_external_id: ripplingGetCustomObjectRecordByExternalIdTool,
rippling_get_custom_page: ripplingGetCustomPageTool,
rippling_get_custom_setting: ripplingGetCustomSettingTool,
rippling_get_department: ripplingGetDepartmentTool,
rippling_get_employment_type: ripplingGetEmploymentTypeTool,
rippling_get_job_function: ripplingGetJobFunctionTool,
rippling_get_object_category: ripplingGetObjectCategoryTool,
rippling_get_report_run: ripplingGetReportRunTool,
rippling_get_supergroup: ripplingGetSupergroupTool,
rippling_get_team: ripplingGetTeamTool,
rippling_get_title: ripplingGetTitleTool,
rippling_get_user: ripplingGetUserTool,
rippling_get_work_location: ripplingGetWorkLocationTool,
rippling_get_worker: ripplingGetWorkerTool,
rippling_list_business_partner_groups: ripplingListBusinessPartnerGroupsTool,
rippling_list_business_partners: ripplingListBusinessPartnersTool,
rippling_list_companies: ripplingListCompaniesTool,
rippling_list_custom_apps: ripplingListCustomAppsTool,
rippling_list_custom_fields: ripplingListCustomFieldsTool,
rippling_list_custom_object_fields: ripplingListCustomObjectFieldsTool,
rippling_list_custom_object_records: ripplingListCustomObjectRecordsTool,
rippling_list_custom_objects: ripplingListCustomObjectsTool,
rippling_list_custom_pages: ripplingListCustomPagesTool,
rippling_list_custom_settings: ripplingListCustomSettingsTool,
rippling_list_departments: ripplingListDepartmentsTool,
rippling_list_employees: ripplingListEmployeesTool,
rippling_list_employees_with_terminated: ripplingListEmployeesWithTerminatedTool,
rippling_list_leave_balances: ripplingListLeaveBalancesTool,
rippling_list_leave_requests: ripplingListLeaveRequestsTool,
rippling_list_leave_types: ripplingListLeaveTypesTool,
rippling_list_levels: ripplingListLevelsTool,
rippling_list_employment_types: ripplingListEmploymentTypesTool,
rippling_list_entitlements: ripplingListEntitlementsTool,
rippling_list_job_functions: ripplingListJobFunctionsTool,
rippling_list_object_categories: ripplingListObjectCategoriesTool,
rippling_list_supergroup_exclusion_members: ripplingListSupergroupExclusionMembersTool,
rippling_list_supergroup_inclusion_members: ripplingListSupergroupInclusionMembersTool,
rippling_list_supergroup_members: ripplingListSupergroupMembersTool,
rippling_list_supergroups: ripplingListSupergroupsTool,
rippling_list_teams: ripplingListTeamsTool,
rippling_list_titles: ripplingListTitlesTool,
rippling_list_users: ripplingListUsersTool,
rippling_list_work_locations: ripplingListWorkLocationsTool,
rippling_process_leave_request: ripplingProcessLeaveRequestTool,
rippling_push_candidate: ripplingPushCandidateTool,
rippling_update_group: ripplingUpdateGroupTool,
rippling_list_workers: ripplingListWorkersTool,
rippling_query_custom_object_records: ripplingQueryCustomObjectRecordsTool,
rippling_trigger_report_run: ripplingTriggerReportRunTool,
rippling_update_custom_app: ripplingUpdateCustomAppTool,
rippling_update_custom_object: ripplingUpdateCustomObjectTool,
rippling_update_custom_object_field: ripplingUpdateCustomObjectFieldTool,
rippling_update_custom_object_record: ripplingUpdateCustomObjectRecordTool,
rippling_update_custom_page: ripplingUpdateCustomPageTool,
rippling_update_custom_setting: ripplingUpdateCustomSettingTool,
rippling_update_department: ripplingUpdateDepartmentTool,
rippling_update_object_category: ripplingUpdateObjectCategoryTool,
rippling_update_supergroup_exclusion_members: ripplingUpdateSupergroupExclusionMembersTool,
rippling_update_supergroup_inclusion_members: ripplingUpdateSupergroupInclusionMembersTool,
rippling_update_title: ripplingUpdateTitleTool,
rippling_update_work_location: ripplingUpdateWorkLocationTool,
google_drive_copy: googleDriveCopyTool,
google_drive_create_folder: googleDriveCreateFolderTool,
google_drive_delete: googleDriveDeleteTool,

View File

@@ -0,0 +1,70 @@
import type { RipplingBulkCreateCustomObjectRecordsParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingBulkCreateCustomObjectRecordsTool: ToolConfig<RipplingBulkCreateCustomObjectRecordsParams> =
{
id: 'rippling_bulk_create_custom_object_records',
name: 'Rippling Bulk Create Custom Object Records',
description: 'Bulk create custom object records',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
rowsToWrite: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Array of records to create [{external_id?, data}]',
},
allOrNothing: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'If true, fail entire batch on any error',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/records/bulk/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { rows_to_write: params.rowsToWrite }
if (params.allOrNothing != null) body.all_or_nothing = params.allOrNothing
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const createdRecords = data.data ?? []
return {
success: true,
output: {
createdRecords,
totalCount: Array.isArray(createdRecords) ? createdRecords.length : 0,
},
}
},
outputs: {
createdRecords: { type: 'array', description: 'Created custom object records' },
totalCount: { type: 'number', description: 'Number of records created' },
},
}

View File

@@ -0,0 +1,64 @@
import type { RipplingBulkDeleteCustomObjectRecordsParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingBulkDeleteCustomObjectRecordsTool: ToolConfig<RipplingBulkDeleteCustomObjectRecordsParams> =
{
id: 'rippling_bulk_delete_custom_object_records',
name: 'Rippling Bulk Delete Custom Object Records',
description: 'Bulk delete custom object records',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
rowsToDelete: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Array of records to delete',
},
allOrNothing: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'If true, fail entire batch on any error',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/records/bulk-delete/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { rows_to_delete: params.rowsToDelete }
if (params.allOrNothing != null) body.all_or_nothing = params.allOrNothing
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return {
success: true,
output: { deleted: true },
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the bulk delete succeeded' },
},
}

View File

@@ -0,0 +1,70 @@
import type { RipplingBulkUpdateCustomObjectRecordsParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingBulkUpdateCustomObjectRecordsTool: ToolConfig<RipplingBulkUpdateCustomObjectRecordsParams> =
{
id: 'rippling_bulk_update_custom_object_records',
name: 'Rippling Bulk Update Custom Object Records',
description: 'Bulk update custom object records',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
rowsToUpdate: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Array of records to update',
},
allOrNothing: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'If true, fail entire batch on any error',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/records/bulk/`,
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { rows_to_update: params.rowsToUpdate }
if (params.allOrNothing != null) body.all_or_nothing = params.allOrNothing
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const updatedRecords = data.data ?? []
return {
success: true,
output: {
updatedRecords,
totalCount: Array.isArray(updatedRecords) ? updatedRecords.length : 0,
},
}
},
outputs: {
updatedRecords: { type: 'array', description: 'Updated custom object records' },
totalCount: { type: 'number', description: 'Number of records updated' },
},
}

View File

@@ -0,0 +1,76 @@
import type { RipplingCreateBusinessPartnerParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateBusinessPartnerTool: ToolConfig<RipplingCreateBusinessPartnerParams> = {
id: 'rippling_create_business_partner',
name: 'Rippling Create Business Partner',
description: 'Create a new business partner',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
businessPartnerGroupId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Business partner group ID',
},
workerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Worker ID',
},
},
request: {
url: `https://rest.ripplingapis.com/business-partners/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
return {
business_partner_group_id: params.businessPartnerGroupId,
worker_id: params.workerId,
}
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
business_partner_group_id: (data.business_partner_group_id as string) ?? null,
worker_id: (data.worker_id as string) ?? null,
client_group_id: (data.client_group_id as string) ?? null,
client_group_member_count: (data.client_group_member_count as number) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
business_partner_group_id: { type: 'string', description: 'Group ID', optional: true },
worker_id: { type: 'string', description: 'Worker ID', optional: true },
client_group_id: { type: 'string', description: 'Client group ID', optional: true },
client_group_member_count: {
type: 'number',
description: 'Client group member count',
optional: true,
},
},
}

View File

@@ -0,0 +1,82 @@
import type { RipplingCreateBusinessPartnerGroupParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateBusinessPartnerGroupTool: ToolConfig<RipplingCreateBusinessPartnerGroupParams> =
{
id: 'rippling_create_business_partner_group',
name: 'Rippling Create Business Partner Group',
description: 'Create a new business partner group',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Group name',
},
domain: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Domain (HR, IT, FINANCE, RECRUITING, OTHER)',
},
defaultBusinessPartnerId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Default business partner ID',
},
},
request: {
url: `https://rest.ripplingapis.com/business-partner-groups/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { name: params.name }
if (params.domain != null) body.domain = params.domain
if (params.defaultBusinessPartnerId != null)
body.default_business_partner_id = params.defaultBusinessPartnerId
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
domain: (data.domain as string) ?? null,
default_business_partner_id: (data.default_business_partner_id as string) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
domain: { type: 'string', description: 'Domain', optional: true },
default_business_partner_id: {
type: 'string',
description: 'Default partner ID',
optional: true,
},
},
}

View File

@@ -0,0 +1,69 @@
import type { RipplingCreateCustomAppParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateCustomAppTool: ToolConfig<RipplingCreateCustomAppParams> = {
id: 'rippling_create_custom_app',
name: 'Rippling Create Custom App',
description: 'Create a new custom app',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: { type: 'string', required: true, visibility: 'user-or-llm', description: 'App name' },
apiName: { type: 'string', required: true, visibility: 'user-or-llm', description: 'API name' },
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description',
},
},
request: {
url: `https://rest.ripplingapis.com/custom-apps/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { name: params.name, api_name: params.apiName }
if (params.description != null) body.description = params.description
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
api_name: (data.api_name as string) ?? null,
description: (data.description as string) ?? null,
icon: (data.icon as string) ?? null,
pages: (data.pages as unknown[]) ?? [],
},
}
},
outputs: {
id: { type: 'string', description: 'App ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
api_name: { type: 'string', description: 'API name', optional: true },
description: { type: 'string', description: 'Description', optional: true },
icon: { type: 'string', description: 'Icon URL', optional: true },
pages: { type: 'json', description: 'Array of page summaries', optional: true },
},
}

View File

@@ -0,0 +1,87 @@
import type { RipplingCreateCustomObjectParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateCustomObjectTool: ToolConfig<RipplingCreateCustomObjectParams> = {
id: 'rippling_create_custom_object',
name: 'Rippling Create Custom Object',
description: 'Create a new custom object',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Object name' },
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description',
},
category: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Category',
},
},
request: {
url: `https://rest.ripplingapis.com/custom-objects/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { name: params.name }
if (params.description != null) body.description = params.description
if (params.category != null) body.category = params.category
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
description: (data.description as string) ?? null,
api_name: (data.api_name as string) ?? null,
plural_label: (data.plural_label as string) ?? null,
category_id: (data.category_id as string) ?? null,
enable_history: (data.enable_history as boolean) ?? null,
native_category_id: (data.native_category_id as string) ?? null,
managed_package_install_id: (data.managed_package_install_id as string) ?? null,
owner_id: (data.owner_id as string) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
description: { type: 'string', description: 'Description', optional: true },
api_name: { type: 'string', description: 'API name', optional: true },
plural_label: { type: 'string', description: 'Plural label', optional: true },
category_id: { type: 'string', description: 'Category ID', optional: true },
enable_history: { type: 'boolean', description: 'History enabled', optional: true },
native_category_id: { type: 'string', description: 'Native category ID', optional: true },
managed_package_install_id: {
type: 'string',
description: 'Package install ID',
optional: true,
},
owner_id: { type: 'string', description: 'Owner ID', optional: true },
},
}

View File

@@ -0,0 +1,159 @@
import type { RipplingCreateCustomObjectFieldParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateCustomObjectFieldTool: ToolConfig<RipplingCreateCustomObjectFieldParams> =
{
id: 'rippling_create_custom_object_field',
name: 'Rippling Create Custom Object Field',
description: 'Create a field on a custom object',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Field name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description',
},
dataType: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Data type configuration',
},
required: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the field is required',
},
rqlDefinition: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'RQL definition object',
},
isUnique: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether field is unique',
},
formulaAttrMetas: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Formula attribute metadata',
},
section: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Section configuration',
},
enableHistory: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Enable history tracking',
},
derivedFieldFormula: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Derived field formula expression',
},
derivedAggregatedField: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Derived aggregated field configuration',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/fields/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { name: params.name, data_type: params.dataType }
if (params.description != null) body.description = params.description
if (params.required != null) body.required = params.required
if (params.rqlDefinition != null) body.rql_definition = params.rqlDefinition
if (params.isUnique != null) body.is_unique = params.isUnique
if (params.formulaAttrMetas != null) body.formula_attr_metas = params.formulaAttrMetas
if (params.section != null) body.section = params.section
if (params.enableHistory != null) body.enable_history = params.enableHistory
if (params.derivedFieldFormula != null)
body.derived_field_formula = params.derivedFieldFormula
if (params.derivedAggregatedField != null)
body.derived_aggregated_field = params.derivedAggregatedField
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
custom_object: (data.custom_object as string) ?? null,
description: (data.description as string) ?? null,
api_name: (data.api_name as string) ?? null,
data_type: data.data_type ?? null,
is_unique: (data.is_unique as boolean) ?? null,
is_immutable: (data.is_immutable as boolean) ?? null,
is_standard: (data.is_standard as boolean) ?? null,
enable_history: (data.enable_history as boolean) ?? null,
managed_package_install_id: (data.managed_package_install_id as string) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Field ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
custom_object: { type: 'string', description: 'Custom object', optional: true },
description: { type: 'string', description: 'Description', optional: true },
api_name: { type: 'string', description: 'API name', optional: true },
data_type: { type: 'json', description: 'Data type configuration', optional: true },
is_unique: { type: 'boolean', description: 'Is unique', optional: true },
is_immutable: { type: 'boolean', description: 'Is immutable', optional: true },
is_standard: { type: 'boolean', description: 'Is standard', optional: true },
enable_history: { type: 'boolean', description: 'History enabled', optional: true },
managed_package_install_id: {
type: 'string',
description: 'Package install ID',
optional: true,
},
},
}

View File

@@ -0,0 +1,87 @@
import type { RipplingCreateCustomObjectRecordParams } from '@/tools/rippling/types'
import { CUSTOM_OBJECT_RECORD_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateCustomObjectRecordTool: ToolConfig<RipplingCreateCustomObjectRecordParams> =
{
id: 'rippling_create_custom_object_record',
name: 'Rippling Create Custom Object Record',
description: 'Create a custom object record',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
externalId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'External ID for the record',
},
data: { type: 'json', required: true, visibility: 'user-or-llm', description: 'Record data' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/records/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { data: params.data }
if (params.externalId != null && params.externalId !== '')
body.external_id = params.externalId
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const json = await response.json()
const record = (json.data ?? json) as Record<string, unknown>
const {
id,
created_at,
updated_at,
name,
external_id,
created_by,
last_modified_by,
owner_role,
system_updated_at,
...dynamicFields
} = record
return {
success: true,
output: {
id: (id as string) ?? '',
created_at: (created_at as string) ?? null,
updated_at: (updated_at as string) ?? null,
name: (name as string) ?? null,
external_id: (external_id as string) ?? null,
created_by: created_by ?? null,
last_modified_by: last_modified_by ?? null,
owner_role: owner_role ?? null,
system_updated_at: (system_updated_at as string) ?? null,
data: dynamicFields,
},
}
},
outputs: {
...CUSTOM_OBJECT_RECORD_OUTPUT_PROPERTIES,
data: { type: 'json', description: 'Full record data' },
},
}

View File

@@ -0,0 +1,62 @@
import type { RipplingCreateCustomPageParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateCustomPageTool: ToolConfig<RipplingCreateCustomPageParams> = {
id: 'rippling_create_custom_page',
name: 'Rippling Create CustomPage',
description: 'Create a new custom page',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Page name' },
},
request: {
url: `https://rest.ripplingapis.com/custom-pages/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
return { name: params.name }
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
components: data.components ?? [],
actions: data.actions ?? [],
canvas_actions: data.canvas_actions ?? [],
variables: data.variables ?? [],
media: data.media ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Page ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
components: { type: 'json', description: 'Page components', optional: true },
actions: { type: 'json', description: 'Page actions', optional: true },
canvas_actions: { type: 'json', description: 'Canvas actions', optional: true },
variables: { type: 'json', description: 'Page variables', optional: true },
media: { type: 'json', description: 'Page media', optional: true },
},
}

View File

@@ -0,0 +1,105 @@
import type { RipplingCreateCustomSettingParams } from '@/tools/rippling/types'
import { CUSTOM_SETTING_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateCustomSettingTool: ToolConfig<RipplingCreateCustomSettingParams> = {
id: 'rippling_create_custom_setting',
name: 'Rippling Create Custom Setting',
description: 'Create a new custom setting',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
displayName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Display name',
},
apiName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Unique API name',
},
dataType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Data type of the setting',
},
secretValue: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Secret value (for secret data type)',
},
stringValue: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'String value (for string data type)',
},
numberValue: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number value (for number data type)',
},
booleanValue: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Boolean value (for boolean data type)',
},
},
request: {
url: `https://rest.ripplingapis.com/custom-settings/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.displayName != null) body.display_name = params.displayName
if (params.apiName != null) body.api_name = params.apiName
if (params.dataType != null) body.data_type = params.dataType
if (params.secretValue != null) body.secret_value = params.secretValue
if (params.stringValue != null) body.string_value = params.stringValue
if (params.numberValue != null) body.number_value = params.numberValue
if (params.booleanValue != null) body.boolean_value = params.booleanValue
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
display_name: (data.display_name as string) ?? null,
api_name: (data.api_name as string) ?? null,
data_type: (data.data_type as string) ?? null,
secret_value: (data.secret_value as string) ?? null,
string_value: (data.string_value as string) ?? null,
number_value: data.number_value ?? null,
boolean_value: data.boolean_value ?? null,
},
}
},
outputs: {
...CUSTOM_SETTING_OUTPUT_PROPERTIES,
},
}

View File

@@ -0,0 +1,90 @@
import type { RipplingCreateDepartmentParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateDepartmentTool: ToolConfig<RipplingCreateDepartmentParams> = {
id: 'rippling_create_department',
name: 'Rippling Create Department',
description: 'Create a new department',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Department name',
},
parentId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Parent department ID',
},
referenceCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Reference code',
},
},
request: {
url: `https://rest.ripplingapis.com/departments/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { name: params.name }
if (params.parentId != null) body.parent_id = params.parentId
if (params.referenceCode != null) body.reference_code = params.referenceCode
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
parent_id: (data.parent_id as string) ?? null,
reference_code: (data.reference_code as string) ?? null,
department_hierarchy_id: (data.department_hierarchy_id as unknown[]) ?? [],
parent: data.parent ?? null,
department_hierarchy: data.department_hierarchy ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Department ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
parent_id: { type: 'string', description: 'Parent department ID', optional: true },
reference_code: { type: 'string', description: 'Reference code', optional: true },
department_hierarchy_id: {
type: 'json',
description: 'Department hierarchy IDs',
optional: true,
},
parent: { type: 'json', description: 'Expanded parent department', optional: true },
department_hierarchy: {
type: 'json',
description: 'Expanded department hierarchy',
optional: true,
},
},
}

View File

@@ -0,0 +1,57 @@
import type { RipplingCreateDraftHiresParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateDraftHiresTool: ToolConfig<RipplingCreateDraftHiresParams> = {
id: 'rippling_create_draft_hires',
name: 'Rippling Create Draft Hires',
description: 'Create bulk draft hires',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
draftHires: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description: 'Array of draft hire objects',
},
},
request: {
url: `https://rest.ripplingapis.com/draft-hires/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
return { draft_hires: params.draftHires }
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
invalidItems: data.invalid_items ?? [],
successfulResults: data.successful_results ?? [],
totalInvalid: (data.invalid_items ?? []).length,
totalSuccessful: (data.successful_results ?? []).length,
},
}
},
outputs: {
invalidItems: { type: 'json', description: 'Failed draft hires' },
successfulResults: { type: 'json', description: 'Successful draft hires' },
totalInvalid: { type: 'number', description: 'Number of failures' },
totalSuccessful: { type: 'number', description: 'Number of successes' },
},
}

View File

@@ -1,91 +0,0 @@
import type { RipplingCreateGroupParams, RipplingCreateGroupResponse } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateGroupTool: ToolConfig<
RipplingCreateGroupParams,
RipplingCreateGroupResponse
> = {
id: 'rippling_create_group',
name: 'Rippling Create Group',
description: 'Create a new group in Rippling',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the group',
},
spokeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Third-party app identifier',
},
users: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description: 'Array of user ID strings to add to the group',
},
},
request: {
url: 'https://api.rippling.com/platform/api/groups',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
name: params.name,
spokeId: params.spokeId,
}
if (params.users !== undefined) {
body.users = params.users
}
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
name: (data.name as string) ?? null,
spokeId: (data.spokeId as string) ?? null,
users: (data.users as string[]) ?? [],
version: (data.version as number) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Group ID' },
name: { type: 'string', description: 'Group name' },
spokeId: { type: 'string', description: 'Third-party app identifier' },
users: {
type: 'array',
description: 'Array of user IDs in the group',
items: { type: 'string' },
},
version: { type: 'number', description: 'Group version number' },
},
}

View File

@@ -0,0 +1,67 @@
import type { RipplingCreateObjectCategoryParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateObjectCategoryTool: ToolConfig<RipplingCreateObjectCategoryParams> = {
id: 'rippling_create_object_category',
name: 'Rippling Create ObjectCategory',
description: 'Create a new object category',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Category name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description',
},
},
request: {
url: `https://rest.ripplingapis.com/object-categories/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { name: params.name }
if (params.description != null) body.description = params.description
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
description: (data.description as string) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Category ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
description: { type: 'string', description: 'Description', optional: true },
},
}

View File

@@ -0,0 +1,52 @@
import type { RipplingCreateTitleParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateTitleTool: ToolConfig<RipplingCreateTitleParams> = {
id: 'rippling_create_title',
name: 'Rippling Create Title',
description: 'Create a new title',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Title name' },
},
request: {
url: `https://rest.ripplingapis.com/titles/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
return { name: params.name }
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Title ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Title name', optional: true },
},
}

View File

@@ -0,0 +1,98 @@
import type { RipplingCreateWorkLocationParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingCreateWorkLocationTool: ToolConfig<RipplingCreateWorkLocationParams> = {
id: 'rippling_create_work_location',
name: 'Rippling Create Work Location',
description: 'Create a new work location',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Location name',
},
streetAddress: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Street address',
},
locality: { type: 'string', required: false, visibility: 'user-or-llm', description: 'City' },
region: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State/region',
},
postalCode: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Postal code',
},
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Country code',
},
addressType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Address type (HOME, WORK, OTHER)',
},
},
request: {
url: `https://rest.ripplingapis.com/work-locations/`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
Accept: 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = { name: params.name }
const address: Record<string, string> = { street_address: params.streetAddress }
if (params.locality != null) address.locality = params.locality
if (params.region != null) address.region = params.region
if (params.postalCode != null) address.postal_code = params.postalCode
if (params.country != null) address.country = params.country
if (params.addressType != null) address.type = params.addressType
body.address = address
return body
},
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
address: data.address ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Location ID' },
created_at: { type: 'string', description: 'Created timestamp', optional: true },
updated_at: { type: 'string', description: 'Updated timestamp', optional: true },
name: { type: 'string', description: 'Name' },
address: { type: 'json', description: 'Address', optional: true },
},
}

View File

@@ -0,0 +1,39 @@
import type { RipplingDeleteBusinessPartnerParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteBusinessPartnerTool: ToolConfig<RipplingDeleteBusinessPartnerParams> = {
id: 'rippling_delete_business_partner',
name: 'Rippling Delete Business Partner',
description: 'Delete a business partner',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/business-partners/${encodeURIComponent(params.id.trim())}/`,
method: 'DELETE',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,43 @@
import type { RipplingDeleteBusinessPartnerGroupParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteBusinessPartnerGroupTool: ToolConfig<RipplingDeleteBusinessPartnerGroupParams> =
{
id: 'rippling_delete_business_partner_group',
name: 'Rippling Delete Business Partner Group',
description: 'Delete a business partner group',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/business-partner-groups/${encodeURIComponent(params.id.trim())}/`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,39 @@
import type { RipplingDeleteCustomAppParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteCustomAppTool: ToolConfig<RipplingDeleteCustomAppParams> = {
id: 'rippling_delete_custom_app',
name: 'Rippling Delete CustomApp',
description: 'Delete a custom app',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-apps/${encodeURIComponent(params.id.trim())}/`,
method: 'DELETE',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,39 @@
import type { RipplingDeleteCustomObjectParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteCustomObjectTool: ToolConfig<RipplingDeleteCustomObjectParams> = {
id: 'rippling_delete_custom_object',
name: 'Rippling Delete Custom Object',
description: 'Delete a custom object',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/`,
method: 'DELETE',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,47 @@
import type { RipplingDeleteCustomObjectFieldParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteCustomObjectFieldTool: ToolConfig<RipplingDeleteCustomObjectFieldParams> =
{
id: 'rippling_delete_custom_object_field',
name: 'Rippling Delete Custom Object Field',
description: 'Delete a field from a custom object',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
fieldApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Field API name',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/fields/${encodeURIComponent(params.fieldApiName.trim())}/`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: { deleted: { type: 'boolean', description: 'Whether the field was deleted' } },
}

View File

@@ -0,0 +1,47 @@
import type { RipplingDeleteCustomObjectRecordParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteCustomObjectRecordTool: ToolConfig<RipplingDeleteCustomObjectRecordParams> =
{
id: 'rippling_delete_custom_object_record',
name: 'Rippling Delete Custom Object Record',
description: 'Delete a custom object record',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
codrId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Record ID',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/records/${encodeURIComponent(params.codrId.trim())}/`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: { deleted: { type: 'boolean', description: 'Whether the record was deleted' } },
}

View File

@@ -0,0 +1,39 @@
import type { RipplingDeleteCustomPageParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteCustomPageTool: ToolConfig<RipplingDeleteCustomPageParams> = {
id: 'rippling_delete_custom_page',
name: 'Rippling Delete CustomPage',
description: 'Delete a custom page',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-pages/${encodeURIComponent(params.id.trim())}/`,
method: 'DELETE',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,39 @@
import type { RipplingDeleteCustomSettingParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteCustomSettingTool: ToolConfig<RipplingDeleteCustomSettingParams> = {
id: 'rippling_delete_custom_setting',
name: 'Rippling Delete Custom Setting',
description: 'Delete a custom setting',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-settings/${encodeURIComponent(params.id.trim())}/`,
method: 'DELETE',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,39 @@
import type { RipplingDeleteObjectCategoryParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteObjectCategoryTool: ToolConfig<RipplingDeleteObjectCategoryParams> = {
id: 'rippling_delete_object_category',
name: 'Rippling Delete ObjectCategory',
description: 'Delete an object category',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/object-categories/${encodeURIComponent(params.id.trim())}/`,
method: 'DELETE',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,39 @@
import type { RipplingDeleteTitleParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteTitleTool: ToolConfig<RipplingDeleteTitleParams> = {
id: 'rippling_delete_title',
name: 'Rippling Delete Title',
description: 'Delete a title',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/titles/${encodeURIComponent(params.id.trim())}/`,
method: 'DELETE',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,39 @@
import type { RipplingDeleteWorkLocationParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingDeleteWorkLocationTool: ToolConfig<RipplingDeleteWorkLocationParams> = {
id: 'rippling_delete_work_location',
name: 'Rippling Delete Work Location',
description: 'Delete a work location',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the resource to delete',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/work-locations/${encodeURIComponent(params.id.trim())}/`,
method: 'DELETE',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
return { success: true, output: { deleted: true } }
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the resource was deleted' },
},
}

View File

@@ -0,0 +1,67 @@
import type { RipplingGetBusinessPartnerParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetBusinessPartnerTool: ToolConfig<RipplingGetBusinessPartnerParams> = {
id: 'rippling_get_business_partner',
name: 'Rippling Get Business Partner',
description: 'Get a specific business partner by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
},
request: {
url: (params) => {
const base = `https://rest.ripplingapis.com/business-partners/${encodeURIComponent(params.id.trim())}/`
if (params.expand != null) return `${base}?expand=${encodeURIComponent(params.expand)}`
return base
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
business_partner_group_id: (data.business_partner_group_id as string) ?? null,
worker_id: (data.worker_id as string) ?? null,
client_group_id: (data.client_group_id as string) ?? null,
client_group_member_count: (data.client_group_member_count as number) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
business_partner_group_id: { type: 'string', description: 'Group ID', optional: true },
worker_id: { type: 'string', description: 'Worker ID', optional: true },
client_group_id: { type: 'string', description: 'Client group ID', optional: true },
client_group_member_count: {
type: 'number',
description: 'Client group member count',
optional: true,
},
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,69 @@
import type { RipplingGetBusinessPartnerGroupParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetBusinessPartnerGroupTool: ToolConfig<RipplingGetBusinessPartnerGroupParams> =
{
id: 'rippling_get_business_partner_group',
name: 'Rippling Get Business Partner Group',
description: 'Get a specific business partner group by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
},
request: {
url: (params) => {
const base = `https://rest.ripplingapis.com/business-partner-groups/${encodeURIComponent(params.id.trim())}/`
if (params.expand != null) return `${base}?expand=${encodeURIComponent(params.expand)}`
return base
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
domain: (data.domain as string) ?? null,
default_business_partner_id: (data.default_business_partner_id as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
domain: { type: 'string', description: 'Domain', optional: true },
default_business_partner_id: {
type: 'string',
description: 'Default partner ID',
optional: true,
},
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -1,75 +0,0 @@
import type { RipplingGetCompanyParams, RipplingGetCompanyResponse } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCompanyTool: ToolConfig<
RipplingGetCompanyParams,
RipplingGetCompanyResponse
> = {
id: 'rippling_get_company',
name: 'Rippling Get Company',
description: 'Get details for the current company in Rippling',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
},
request: {
url: 'https://api.rippling.com/platform/api/companies/current',
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const address = data.address ?? {}
return {
success: true,
output: {
id: data.id ?? '',
name: data.name ?? null,
address: {
street: address.street ?? null,
city: address.city ?? null,
state: address.state ?? null,
zip: address.zip ?? null,
country: address.country ?? null,
},
email: data.email ?? null,
phone: data.phone ?? null,
workLocations: data.workLocations ?? [],
},
}
},
outputs: {
id: { type: 'string', description: 'Company ID' },
name: { type: 'string', description: 'Company name', optional: true },
address: {
type: 'json',
description: 'Company address with street, city, state, zip, country',
},
email: { type: 'string', description: 'Company email address', optional: true },
phone: { type: 'string', description: 'Company phone number', optional: true },
workLocations: {
type: 'array',
description: 'List of work location IDs',
items: { type: 'string' },
},
},
}

View File

@@ -1,125 +0,0 @@
import type {
RipplingGetCompanyActivityParams,
RipplingGetCompanyActivityResponse,
} from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCompanyActivityTool: ToolConfig<
RipplingGetCompanyActivityParams,
RipplingGetCompanyActivityResponse
> = {
id: 'rippling_get_company_activity',
name: 'Rippling Get Company Activity',
description: 'Get activity events for the current company in Rippling',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
startDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Start date filter in ISO format (e.g. 2024-01-01)',
},
endDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'End date filter in ISO format (e.g. 2024-12-31)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of activity events to return',
},
next: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for fetching the next page of results',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.startDate) query.set('startDate', params.startDate)
if (params.endDate) query.set('endDate', params.endDate)
if (params.limit != null) query.set('limit', String(params.limit))
if (params.next) query.set('next', params.next)
const qs = query.toString()
return `https://api.rippling.com/platform/api/company_activity${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = Array.isArray(data) ? data : (data.results ?? [])
const nextCursor = Array.isArray(data) ? null : ((data.next as string) ?? null)
const events = results.map((event: Record<string, unknown>) => {
const actor = (event.actor as Record<string, unknown>) ?? {}
return {
id: (event.id as string) ?? '',
type: (event.type as string) ?? null,
description: (event.description as string) ?? null,
createdAt: (event.createdAt as string) ?? null,
actor: {
id: (actor.id as string) ?? null,
name: (actor.name as string) ?? null,
},
}
})
return {
success: true,
output: {
events,
totalCount: events.length,
nextCursor,
},
}
},
outputs: {
events: {
type: 'array',
description: 'List of company activity events',
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Event ID' },
type: { type: 'string', description: 'Event type' },
description: { type: 'string', description: 'Event description' },
createdAt: { type: 'string', description: 'Event creation timestamp' },
actor: { type: 'json', description: 'Actor who triggered the event (id, name)' },
},
},
},
totalCount: {
type: 'number',
description: 'Number of activity events returned on this page',
},
nextCursor: {
type: 'string',
description: 'Cursor for fetching the next page of results',
optional: true,
},
},
}

View File

@@ -1,18 +1,11 @@
import type {
RipplingGetCurrentUserParams,
RipplingGetCurrentUserResponse,
} from '@/tools/rippling/types'
import type { RipplingGetCurrentUserParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCurrentUserTool: ToolConfig<
RipplingGetCurrentUserParams,
RipplingGetCurrentUserResponse
> = {
export const ripplingGetCurrentUserTool: ToolConfig<RipplingGetCurrentUserParams> = {
id: 'rippling_get_current_user',
name: 'Rippling Get Current User',
description: 'Get the current authenticated user details',
description: 'Get SSO information for the current user',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
@@ -20,38 +13,47 @@ export const ripplingGetCurrentUserTool: ToolConfig<
visibility: 'user-only',
description: 'Rippling API key',
},
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
},
request: {
url: 'https://api.rippling.com/platform/api/me',
url: (params) => {
const query = new URLSearchParams()
if (params.expand != null) query.set('expand', params.expand)
const qs = query.toString()
return `https://rest.ripplingapis.com/sso-me/${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: data.id ?? '',
workEmail: data.workEmail ?? null,
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
work_email: (data.work_email as string) ?? null,
company_id: (data.company_id as string) ?? null,
company: data.company ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'User ID' },
workEmail: { type: 'string', description: 'Work email address', optional: true },
company: { type: 'string', description: 'Company ID', optional: true },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
work_email: { type: 'string', description: 'Work email', optional: true },
company_id: { type: 'string', description: 'Company ID', optional: true },
company: { type: 'json', description: 'Expanded company object', optional: true },
},
}

View File

@@ -0,0 +1,56 @@
import type { RipplingGetCustomAppParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCustomAppTool: ToolConfig<RipplingGetCustomAppParams> = {
id: 'rippling_get_custom_app',
name: 'Rippling Get CustomApp',
description: 'Get a specific custom app',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-apps/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
api_name: (data.api_name as string) ?? null,
description: (data.description as string) ?? null,
icon: (data.icon as string) ?? null,
pages: (data.pages as unknown[]) ?? [],
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'App ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
api_name: { type: 'string', description: 'API name', optional: true },
description: { type: 'string', description: 'Description', optional: true },
icon: { type: 'string', description: 'Icon URL', optional: true },
pages: { type: 'json', description: 'Array of page summaries', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,71 @@
import type { RipplingGetCustomObjectParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCustomObjectTool: ToolConfig<RipplingGetCustomObjectParams> = {
id: 'rippling_get_custom_object',
name: 'Rippling Get Custom Object',
description: 'Get a custom object by API name',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'custom object api name',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
description: (data.description as string) ?? null,
api_name: (data.api_name as string) ?? null,
plural_label: (data.plural_label as string) ?? null,
category_id: (data.category_id as string) ?? null,
enable_history: (data.enable_history as boolean) ?? null,
native_category_id: (data.native_category_id as string) ?? null,
managed_package_install_id: (data.managed_package_install_id as string) ?? null,
owner_id: (data.owner_id as string) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
description: { type: 'string', description: 'Description', optional: true },
api_name: { type: 'string', description: 'API name', optional: true },
plural_label: { type: 'string', description: 'Plural label', optional: true },
category_id: { type: 'string', description: 'Category ID', optional: true },
enable_history: { type: 'boolean', description: 'History enabled', optional: true },
native_category_id: { type: 'string', description: 'Native category ID', optional: true },
managed_package_install_id: {
type: 'string',
description: 'Package install ID',
optional: true,
},
owner_id: { type: 'string', description: 'Owner ID', optional: true },
},
}

View File

@@ -0,0 +1,79 @@
import type { RipplingGetCustomObjectFieldParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCustomObjectFieldTool: ToolConfig<RipplingGetCustomObjectFieldParams> = {
id: 'rippling_get_custom_object_field',
name: 'Rippling Get Custom Object Field',
description: 'Get a specific field of a custom object',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
fieldApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Field API name',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/fields/${encodeURIComponent(params.fieldApiName.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
custom_object: (data.custom_object as string) ?? null,
description: (data.description as string) ?? null,
api_name: (data.api_name as string) ?? null,
data_type: data.data_type ?? null,
is_unique: (data.is_unique as boolean) ?? null,
is_immutable: (data.is_immutable as boolean) ?? null,
is_standard: (data.is_standard as boolean) ?? null,
enable_history: (data.enable_history as boolean) ?? null,
managed_package_install_id: (data.managed_package_install_id as string) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Field ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
custom_object: { type: 'string', description: 'Custom object', optional: true },
description: { type: 'string', description: 'Description', optional: true },
api_name: { type: 'string', description: 'API name', optional: true },
data_type: { type: 'json', description: 'Data type configuration', optional: true },
is_unique: { type: 'boolean', description: 'Is unique', optional: true },
is_immutable: { type: 'boolean', description: 'Is immutable', optional: true },
is_standard: { type: 'boolean', description: 'Is standard', optional: true },
enable_history: { type: 'boolean', description: 'History enabled', optional: true },
managed_package_install_id: {
type: 'string',
description: 'Package install ID',
optional: true,
},
},
}

View File

@@ -0,0 +1,69 @@
import type { RipplingGetCustomObjectRecordParams } from '@/tools/rippling/types'
import { CUSTOM_OBJECT_RECORD_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCustomObjectRecordTool: ToolConfig<RipplingGetCustomObjectRecordParams> = {
id: 'rippling_get_custom_object_record',
name: 'Rippling Get Custom Object Record',
description: 'Get a specific custom object record',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
codrId: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Record ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/records/${encodeURIComponent(params.codrId.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const record = await response.json()
const {
id,
created_at,
updated_at,
name,
external_id,
created_by,
last_modified_by,
owner_role,
system_updated_at,
...dynamicFields
} = record
return {
success: true,
output: {
id: (id as string) ?? '',
created_at: (created_at as string) ?? null,
updated_at: (updated_at as string) ?? null,
name: (name as string) ?? null,
external_id: (external_id as string) ?? null,
created_by: created_by ?? null,
last_modified_by: last_modified_by ?? null,
owner_role: owner_role ?? null,
system_updated_at: (system_updated_at as string) ?? null,
data: dynamicFields,
},
}
},
outputs: {
...CUSTOM_OBJECT_RECORD_OUTPUT_PROPERTIES,
data: { type: 'json', description: 'Full record data' },
},
}

View File

@@ -0,0 +1,78 @@
import type { RipplingGetCustomObjectRecordByExternalIdParams } from '@/tools/rippling/types'
import { CUSTOM_OBJECT_RECORD_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCustomObjectRecordByExternalIdTool: ToolConfig<RipplingGetCustomObjectRecordByExternalIdParams> =
{
id: 'rippling_get_custom_object_record_by_external_id',
name: 'Rippling Get Record By External ID',
description: 'Get a custom object record by external ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
externalId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'External ID',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/records/external_id/${encodeURIComponent(params.externalId.trim())}/`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const record = await response.json()
const {
id,
created_at,
updated_at,
name,
external_id,
created_by,
last_modified_by,
owner_role,
system_updated_at,
...dynamicFields
} = record
return {
success: true,
output: {
id: (id as string) ?? '',
created_at: (created_at as string) ?? null,
updated_at: (updated_at as string) ?? null,
name: (name as string) ?? null,
external_id: (external_id as string) ?? null,
created_by: created_by ?? null,
last_modified_by: last_modified_by ?? null,
owner_role: owner_role ?? null,
system_updated_at: (system_updated_at as string) ?? null,
data: dynamicFields,
},
}
},
outputs: {
...CUSTOM_OBJECT_RECORD_OUTPUT_PROPERTIES,
data: { type: 'json', description: 'Full record data' },
},
}

View File

@@ -0,0 +1,58 @@
import type { RipplingGetCustomPageParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCustomPageTool: ToolConfig<RipplingGetCustomPageParams> = {
id: 'rippling_get_custom_page',
name: 'Rippling Get CustomPage',
description: 'Get a specific custom page',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-pages/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
components: data.components ?? [],
actions: data.actions ?? [],
canvas_actions: data.canvas_actions ?? [],
variables: data.variables ?? [],
media: data.media ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Page ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
components: { type: 'json', description: 'Page components', optional: true },
actions: { type: 'json', description: 'Page actions', optional: true },
canvas_actions: { type: 'json', description: 'Canvas actions', optional: true },
variables: { type: 'json', description: 'Page variables', optional: true },
media: { type: 'json', description: 'Page media', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,52 @@
import type { RipplingGetCustomSettingParams } from '@/tools/rippling/types'
import { CUSTOM_SETTING_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetCustomSettingTool: ToolConfig<RipplingGetCustomSettingParams> = {
id: 'rippling_get_custom_setting',
name: 'Rippling Get Custom Setting',
description: 'Get a specific custom setting',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-settings/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
display_name: (data.display_name as string) ?? null,
api_name: (data.api_name as string) ?? null,
data_type: (data.data_type as string) ?? null,
secret_value: (data.secret_value as string) ?? null,
string_value: (data.string_value as string) ?? null,
number_value: data.number_value ?? null,
boolean_value: data.boolean_value ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
...CUSTOM_SETTING_OUTPUT_PROPERTIES,
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,60 @@
import type { RipplingGetDepartmentParams } from '@/tools/rippling/types'
import { DEPARTMENT_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetDepartmentTool: ToolConfig<RipplingGetDepartmentParams> = {
id: 'rippling_get_department',
name: 'Rippling Get Department',
description: 'Get a specific department by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
},
request: {
url: (params) => {
const base = `https://rest.ripplingapis.com/departments/${encodeURIComponent(params.id.trim())}/`
if (params.expand != null) return `${base}?expand=${encodeURIComponent(params.expand)}`
return base
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
parent_id: (data.parent_id as string) ?? null,
reference_code: (data.reference_code as string) ?? null,
department_hierarchy_id: (data.department_hierarchy_id as unknown[]) ?? [],
parent: data.parent ?? null,
department_hierarchy: data.department_hierarchy ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
...DEPARTMENT_OUTPUT_PROPERTIES,
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -1,79 +0,0 @@
import type { RipplingGetEmployeeParams, RipplingGetEmployeeResponse } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetEmployeeTool: ToolConfig<
RipplingGetEmployeeParams,
RipplingGetEmployeeResponse
> = {
id: 'rippling_get_employee',
name: 'Rippling Get Employee',
description: 'Get details for a specific employee by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
employeeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the employee to retrieve',
},
},
request: {
url: (params) =>
`https://api.rippling.com/platform/api/employees/${encodeURIComponent(params.employeeId.trim())}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const emp = await response.json()
return {
success: true,
output: {
id: emp.id ?? '',
firstName: emp.firstName ?? null,
lastName: emp.lastName ?? null,
workEmail: emp.workEmail ?? null,
personalEmail: emp.personalEmail ?? null,
roleState: emp.roleState ?? null,
department: emp.department ?? null,
title: emp.title ?? null,
startDate: emp.startDate ?? null,
endDate: emp.endDate ?? null,
manager: emp.manager ?? null,
phone: emp.phone ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Employee ID' },
firstName: { type: 'string', description: 'First name', optional: true },
lastName: { type: 'string', description: 'Last name', optional: true },
workEmail: { type: 'string', description: 'Work email address', optional: true },
personalEmail: { type: 'string', description: 'Personal email address', optional: true },
roleState: { type: 'string', description: 'Employment status', optional: true },
department: { type: 'string', description: 'Department name or ID', optional: true },
title: { type: 'string', description: 'Job title', optional: true },
startDate: { type: 'string', description: 'Employment start date', optional: true },
endDate: { type: 'string', description: 'Employment end date', optional: true },
manager: { type: 'string', description: 'Manager ID or name', optional: true },
phone: { type: 'string', description: 'Phone number', optional: true },
},
}

View File

@@ -0,0 +1,67 @@
import type { RipplingGetEmploymentTypeParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetEmploymentTypeTool: ToolConfig<RipplingGetEmploymentTypeParams> = {
id: 'rippling_get_employment_type',
name: 'Rippling Get Employment Type',
description: 'Get a specific employment type by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/employment-types/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
label: (data.label as string) ?? null,
name: (data.name as string) ?? null,
type: (data.type as string) ?? null,
compensation_time_period: (data.compensation_time_period as string) ?? null,
amount_worked: (data.amount_worked as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Employment type ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
label: { type: 'string', description: 'Label', optional: true },
name: { type: 'string', description: 'Name', optional: true },
type: { type: 'string', description: 'Type (CONTRACTOR, EMPLOYEE)', optional: true },
compensation_time_period: {
type: 'string',
description: 'Compensation period (HOURLY, SALARIED)',
optional: true,
},
amount_worked: {
type: 'string',
description: 'Amount worked (PART-TIME, FULL-TIME, TEMPORARY)',
optional: true,
},
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,48 @@
import type { RipplingGetJobFunctionParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetJobFunctionTool: ToolConfig<RipplingGetJobFunctionParams> = {
id: 'rippling_get_job_function',
name: 'Rippling Get Job Function',
description: 'Get a specific job function by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/job-functions/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Job function ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -1,77 +0,0 @@
import type {
RipplingGetLeaveBalanceParams,
RipplingGetLeaveBalanceResponse,
} from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetLeaveBalanceTool: ToolConfig<
RipplingGetLeaveBalanceParams,
RipplingGetLeaveBalanceResponse
> = {
id: 'rippling_get_leave_balance',
name: 'Rippling Get Leave Balance',
description: 'Get leave balance for a specific employee by role ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
roleId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The employee/role ID to retrieve leave balance for',
},
},
request: {
url: (params) =>
`https://api.rippling.com/platform/api/leave_balances/${encodeURIComponent(params.roleId.trim())}`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
employeeId: data.employeeId ?? '',
balances: (Array.isArray(data.balances) ? data.balances : []).map(
(b: Record<string, unknown>) => ({
leaveType: (b.leaveType as string) ?? '',
minutesRemaining: (b.minutesRemaining as number) ?? 0,
})
),
},
}
},
outputs: {
employeeId: { type: 'string', description: 'Employee ID' },
balances: {
type: 'array',
description: 'Leave balance entries',
items: {
type: 'json',
properties: {
leaveType: { type: 'string', description: 'Type of leave' },
minutesRemaining: { type: 'number', description: 'Minutes of leave remaining' },
},
},
},
},
}

View File

@@ -0,0 +1,51 @@
import type { RipplingGetObjectCategoryParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetObjectCategoryTool: ToolConfig<RipplingGetObjectCategoryParams> = {
id: 'rippling_get_object_category',
name: 'Rippling Get ObjectCategory',
description: 'Get a specific object category',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/object-categories/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
description: (data.description as string) ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Category ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
description: { type: 'string', description: 'Description', optional: true },
},
}

View File

@@ -0,0 +1,61 @@
import type { RipplingGetReportRunParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetReportRunTool: ToolConfig<RipplingGetReportRunParams> = {
id: 'rippling_get_report_run',
name: 'Rippling Get Report Run',
description: 'Get a report run by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
runId: { type: 'string', required: true, visibility: 'user-or-llm', description: 'run id' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/report-runs/${encodeURIComponent(params.runId.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const result = data.result as Record<string, unknown> | null
return {
success: true,
output: {
id: (data.id as string) ?? '',
report_id: (data.report_id as string) ?? null,
status: (data.status as string) ?? null,
file_url: (result?.file_url as string) ?? null,
expires_at: (result?.expires_at as string) ?? null,
output_type: (result?.output_type as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Report run ID' },
report_id: { type: 'string', description: 'Report ID', optional: true },
status: { type: 'string', description: 'Run status', optional: true },
file_url: { type: 'string', description: 'URL to download the report file', optional: true },
expires_at: {
type: 'string',
description: 'Expiration timestamp for the file URL',
optional: true,
},
output_type: { type: 'string', description: 'Output format (JSON or CSV)', optional: true },
__meta: {
type: 'json',
description: 'Metadata including redacted_fields',
optional: true,
},
},
}

View File

@@ -0,0 +1,61 @@
import type { RipplingGetSupergroupParams } from '@/tools/rippling/types'
import { SUPERGROUP_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetSupergroupTool: ToolConfig<RipplingGetSupergroupParams> = {
id: 'rippling_get_supergroup',
name: 'Rippling Get Supergroup',
description: 'Get a specific supergroup by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/supergroups/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
display_name: (data.display_name as string) ?? null,
description: (data.description as string) ?? null,
app_owner_id: (data.app_owner_id as string) ?? null,
group_type: (data.group_type as string) ?? null,
name: (data.name as string) ?? null,
sub_group_type: (data.sub_group_type as string) ?? null,
read_only: (data.read_only as boolean) ?? null,
parent: (data.parent as string) ?? null,
mutually_exclusive_key: (data.mutually_exclusive_key as string) ?? null,
cumulatively_exhaustive_default: (data.cumulatively_exhaustive_default as boolean) ?? null,
include_terminated: (data.include_terminated as boolean) ?? null,
allow_non_employees: (data.allow_non_employees as boolean) ?? null,
can_override_role_states: (data.can_override_role_states as boolean) ?? null,
priority: (data.priority as number) ?? null,
is_invisible: (data.is_invisible as boolean) ?? null,
ignore_prov_group_matching: (data.ignore_prov_group_matching as boolean) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
...SUPERGROUP_OUTPUT_PROPERTIES,
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,61 @@
import type { RipplingGetTeamParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetTeamTool: ToolConfig<RipplingGetTeamParams> = {
id: 'rippling_get_team',
name: 'Rippling Get Team',
description: 'Get a specific team by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
},
request: {
url: (params) => {
const base = `https://rest.ripplingapis.com/teams/${encodeURIComponent(params.id.trim())}/`
if (params.expand != null) return `${base}?expand=${encodeURIComponent(params.expand)}`
return base
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
parent_id: (data.parent_id as string) ?? null,
parent: data.parent ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Team ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
parent_id: { type: 'string', description: 'Parent team ID', optional: true },
parent: { type: 'json', description: 'Expanded parent team', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,48 @@
import type { RipplingGetTitleParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetTitleTool: ToolConfig<RipplingGetTitleParams> = {
id: 'rippling_get_title',
name: 'Rippling Get Title',
description: 'Get a specific title by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/titles/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Title ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Title name', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,69 @@
import type { RipplingGetUserParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetUserTool: ToolConfig<RipplingGetUserParams> = {
id: 'rippling_get_user',
name: 'Rippling Get User',
description: 'Get a specific user by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) => `https://rest.ripplingapis.com/users/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
active: (data.active as boolean) ?? null,
username: (data.username as string) ?? null,
display_name: (data.display_name as string) ?? null,
preferred_language: (data.preferred_language as string) ?? null,
locale: (data.locale as string) ?? null,
timezone: (data.timezone as string) ?? null,
number: (data.number as string) ?? null,
name: data.name ?? null,
emails: data.emails ?? [],
phone_numbers: data.phone_numbers ?? [],
addresses: data.addresses ?? [],
photos: data.photos ?? [],
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'User ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
active: { type: 'boolean', description: 'Is active', optional: true },
username: { type: 'string', description: 'Username', optional: true },
display_name: { type: 'string', description: 'Display name', optional: true },
preferred_language: { type: 'string', description: 'Preferred language', optional: true },
locale: { type: 'string', description: 'Locale', optional: true },
timezone: { type: 'string', description: 'Timezone', optional: true },
number: { type: 'string', description: 'Profile number', optional: true },
name: { type: 'json', description: 'User name object', optional: true },
emails: { type: 'json', description: 'Email addresses', optional: true },
phone_numbers: { type: 'json', description: 'Phone numbers', optional: true },
addresses: { type: 'json', description: 'Addresses', optional: true },
photos: { type: 'json', description: 'Photos', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,50 @@
import type { RipplingGetWorkLocationParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetWorkLocationTool: ToolConfig<RipplingGetWorkLocationParams> = {
id: 'rippling_get_work_location',
name: 'Rippling Get Work Location',
description: 'Get a specific work location by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/work-locations/${encodeURIComponent(params.id.trim())}/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
name: (data.name as string) ?? null,
address: data.address ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Location ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
name: { type: 'string', description: 'Name', optional: true },
address: { type: 'json', description: 'Address object', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,113 @@
import type { RipplingGetWorkerParams } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingGetWorkerTool: ToolConfig<RipplingGetWorkerParams> = {
id: 'rippling_get_worker',
name: 'Rippling Get Worker',
description: 'Get a specific worker by ID',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
id: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Resource ID' },
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
},
request: {
url: (params) => {
const base = `https://rest.ripplingapis.com/workers/${encodeURIComponent(params.id.trim())}/`
if (params.expand != null) return `${base}?expand=${encodeURIComponent(params.expand)}`
return base
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
return {
success: true,
output: {
id: (data.id as string) ?? '',
created_at: (data.created_at as string) ?? null,
updated_at: (data.updated_at as string) ?? null,
user_id: (data.user_id as string) ?? null,
is_manager: (data.is_manager as boolean) ?? null,
manager_id: (data.manager_id as string) ?? null,
legal_entity_id: (data.legal_entity_id as string) ?? null,
country: (data.country as string) ?? null,
start_date: (data.start_date as string) ?? null,
end_date: (data.end_date as string) ?? null,
number: (data.number as number) ?? null,
work_email: (data.work_email as string) ?? null,
personal_email: (data.personal_email as string) ?? null,
status: (data.status as string) ?? null,
employment_type_id: (data.employment_type_id as string) ?? null,
department_id: (data.department_id as string) ?? null,
teams_id: (data.teams_id as unknown[]) ?? [],
title: (data.title as string) ?? null,
level_id: (data.level_id as string) ?? null,
compensation_id: (data.compensation_id as string) ?? null,
overtime_exemption: (data.overtime_exemption as string) ?? null,
title_effective_date: (data.title_effective_date as string) ?? null,
business_partners_id: (data.business_partners_id as unknown[]) ?? [],
location: data.location ?? null,
gender: (data.gender as string) ?? null,
date_of_birth: (data.date_of_birth as string) ?? null,
race: (data.race as string) ?? null,
ethnicity: (data.ethnicity as string) ?? null,
citizenship: (data.citizenship as string) ?? null,
termination_details: data.termination_details ?? null,
custom_fields: data.custom_fields ?? null,
country_fields: data.country_fields ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Worker ID' },
created_at: { type: 'string', description: 'Creation date', optional: true },
updated_at: { type: 'string', description: 'Update date', optional: true },
user_id: { type: 'string', description: 'User ID', optional: true },
is_manager: { type: 'boolean', description: 'Is manager', optional: true },
manager_id: { type: 'string', description: 'Manager ID', optional: true },
legal_entity_id: { type: 'string', description: 'Legal entity ID', optional: true },
country: { type: 'string', description: 'Country', optional: true },
start_date: { type: 'string', description: 'Start date', optional: true },
end_date: { type: 'string', description: 'End date', optional: true },
number: { type: 'number', description: 'Worker number', optional: true },
work_email: { type: 'string', description: 'Work email', optional: true },
personal_email: { type: 'string', description: 'Personal email', optional: true },
status: { type: 'string', description: 'Status', optional: true },
employment_type_id: { type: 'string', description: 'Employment type ID', optional: true },
department_id: { type: 'string', description: 'Department ID', optional: true },
teams_id: { type: 'json', description: 'Team IDs', optional: true },
title: { type: 'string', description: 'Job title', optional: true },
level_id: { type: 'string', description: 'Level ID', optional: true },
compensation_id: { type: 'string', description: 'Compensation ID', optional: true },
overtime_exemption: { type: 'string', description: 'Overtime exemption', optional: true },
title_effective_date: { type: 'string', description: 'Title effective date', optional: true },
business_partners_id: { type: 'json', description: 'Business partner IDs', optional: true },
location: { type: 'json', description: 'Worker location', optional: true },
gender: { type: 'string', description: 'Gender', optional: true },
date_of_birth: { type: 'string', description: 'Date of birth', optional: true },
race: { type: 'string', description: 'Race', optional: true },
ethnicity: { type: 'string', description: 'Ethnicity', optional: true },
citizenship: { type: 'string', description: 'Citizenship', optional: true },
termination_details: { type: 'json', description: 'Termination details', optional: true },
custom_fields: { type: 'json', description: 'Custom fields', optional: true },
country_fields: { type: 'json', description: 'Country-specific fields', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -1,20 +1,87 @@
export { ripplingCreateGroupTool } from '@/tools/rippling/create_group'
export { ripplingGetCompanyTool } from '@/tools/rippling/get_company'
export { ripplingGetCompanyActivityTool } from '@/tools/rippling/get_company_activity'
export { ripplingGetCurrentUserTool } from '@/tools/rippling/get_current_user'
export { ripplingGetEmployeeTool } from '@/tools/rippling/get_employee'
export { ripplingGetLeaveBalanceTool } from '@/tools/rippling/get_leave_balance'
export { ripplingListCustomFieldsTool } from '@/tools/rippling/list_custom_fields'
export { ripplingListDepartmentsTool } from '@/tools/rippling/list_departments'
export { ripplingListEmployeesTool } from '@/tools/rippling/list_employees'
export { ripplingListEmployeesWithTerminatedTool } from '@/tools/rippling/list_employees_with_terminated'
export { ripplingListLeaveBalancesTool } from '@/tools/rippling/list_leave_balances'
export { ripplingListLeaveRequestsTool } from '@/tools/rippling/list_leave_requests'
export { ripplingListLeaveTypesTool } from '@/tools/rippling/list_leave_types'
export { ripplingListLevelsTool } from '@/tools/rippling/list_levels'
export { ripplingListTeamsTool } from '@/tools/rippling/list_teams'
export { ripplingListWorkLocationsTool } from '@/tools/rippling/list_work_locations'
export { ripplingProcessLeaveRequestTool } from '@/tools/rippling/process_leave_request'
export { ripplingPushCandidateTool } from '@/tools/rippling/push_candidate'
export * from '@/tools/rippling/types'
export { ripplingUpdateGroupTool } from '@/tools/rippling/update_group'
export { ripplingBulkCreateCustomObjectRecordsTool } from './bulk_create_custom_object_records'
export { ripplingBulkDeleteCustomObjectRecordsTool } from './bulk_delete_custom_object_records'
export { ripplingBulkUpdateCustomObjectRecordsTool } from './bulk_update_custom_object_records'
export { ripplingCreateBusinessPartnerTool } from './create_business_partner'
export { ripplingCreateBusinessPartnerGroupTool } from './create_business_partner_group'
export { ripplingCreateCustomAppTool } from './create_custom_app'
export { ripplingCreateCustomObjectTool } from './create_custom_object'
export { ripplingCreateCustomObjectFieldTool } from './create_custom_object_field'
export { ripplingCreateCustomObjectRecordTool } from './create_custom_object_record'
export { ripplingCreateCustomPageTool } from './create_custom_page'
export { ripplingCreateCustomSettingTool } from './create_custom_setting'
export { ripplingCreateDepartmentTool } from './create_department'
export { ripplingCreateDraftHiresTool } from './create_draft_hires'
export { ripplingCreateObjectCategoryTool } from './create_object_category'
export { ripplingCreateTitleTool } from './create_title'
export { ripplingCreateWorkLocationTool } from './create_work_location'
export { ripplingDeleteBusinessPartnerTool } from './delete_business_partner'
export { ripplingDeleteBusinessPartnerGroupTool } from './delete_business_partner_group'
export { ripplingDeleteCustomAppTool } from './delete_custom_app'
export { ripplingDeleteCustomObjectTool } from './delete_custom_object'
export { ripplingDeleteCustomObjectFieldTool } from './delete_custom_object_field'
export { ripplingDeleteCustomObjectRecordTool } from './delete_custom_object_record'
export { ripplingDeleteCustomPageTool } from './delete_custom_page'
export { ripplingDeleteCustomSettingTool } from './delete_custom_setting'
export { ripplingDeleteObjectCategoryTool } from './delete_object_category'
export { ripplingDeleteTitleTool } from './delete_title'
export { ripplingDeleteWorkLocationTool } from './delete_work_location'
export { ripplingGetBusinessPartnerTool } from './get_business_partner'
export { ripplingGetBusinessPartnerGroupTool } from './get_business_partner_group'
export { ripplingGetCurrentUserTool } from './get_current_user'
export { ripplingGetCustomAppTool } from './get_custom_app'
export { ripplingGetCustomObjectTool } from './get_custom_object'
export { ripplingGetCustomObjectFieldTool } from './get_custom_object_field'
export { ripplingGetCustomObjectRecordTool } from './get_custom_object_record'
export { ripplingGetCustomObjectRecordByExternalIdTool } from './get_custom_object_record_by_external_id'
export { ripplingGetCustomPageTool } from './get_custom_page'
export { ripplingGetCustomSettingTool } from './get_custom_setting'
export { ripplingGetDepartmentTool } from './get_department'
export { ripplingGetEmploymentTypeTool } from './get_employment_type'
export { ripplingGetJobFunctionTool } from './get_job_function'
export { ripplingGetObjectCategoryTool } from './get_object_category'
export { ripplingGetReportRunTool } from './get_report_run'
export { ripplingGetSupergroupTool } from './get_supergroup'
export { ripplingGetTeamTool } from './get_team'
export { ripplingGetTitleTool } from './get_title'
export { ripplingGetUserTool } from './get_user'
export { ripplingGetWorkLocationTool } from './get_work_location'
export { ripplingGetWorkerTool } from './get_worker'
export { ripplingListBusinessPartnerGroupsTool } from './list_business_partner_groups'
export { ripplingListBusinessPartnersTool } from './list_business_partners'
export { ripplingListCompaniesTool } from './list_companies'
export { ripplingListCustomAppsTool } from './list_custom_apps'
export { ripplingListCustomFieldsTool } from './list_custom_fields'
export { ripplingListCustomObjectFieldsTool } from './list_custom_object_fields'
export { ripplingListCustomObjectRecordsTool } from './list_custom_object_records'
export { ripplingListCustomObjectsTool } from './list_custom_objects'
export { ripplingListCustomPagesTool } from './list_custom_pages'
export { ripplingListCustomSettingsTool } from './list_custom_settings'
export { ripplingListDepartmentsTool } from './list_departments'
export { ripplingListEmploymentTypesTool } from './list_employment_types'
export { ripplingListEntitlementsTool } from './list_entitlements'
export { ripplingListJobFunctionsTool } from './list_job_functions'
export { ripplingListObjectCategoriesTool } from './list_object_categories'
export { ripplingListSupergroupExclusionMembersTool } from './list_supergroup_exclusion_members'
export { ripplingListSupergroupInclusionMembersTool } from './list_supergroup_inclusion_members'
export { ripplingListSupergroupMembersTool } from './list_supergroup_members'
export { ripplingListSupergroupsTool } from './list_supergroups'
export { ripplingListTeamsTool } from './list_teams'
export { ripplingListTitlesTool } from './list_titles'
export { ripplingListUsersTool } from './list_users'
export { ripplingListWorkLocationsTool } from './list_work_locations'
export { ripplingListWorkersTool } from './list_workers'
export { ripplingQueryCustomObjectRecordsTool } from './query_custom_object_records'
export { ripplingTriggerReportRunTool } from './trigger_report_run'
export * from './types'
export { ripplingUpdateCustomAppTool } from './update_custom_app'
export { ripplingUpdateCustomObjectTool } from './update_custom_object'
export { ripplingUpdateCustomObjectFieldTool } from './update_custom_object_field'
export { ripplingUpdateCustomObjectRecordTool } from './update_custom_object_record'
export { ripplingUpdateCustomPageTool } from './update_custom_page'
export { ripplingUpdateCustomSettingTool } from './update_custom_setting'
export { ripplingUpdateDepartmentTool } from './update_department'
export { ripplingUpdateObjectCategoryTool } from './update_object_category'
export { ripplingUpdateSupergroupExclusionMembersTool } from './update_supergroup_exclusion_members'
export { ripplingUpdateSupergroupInclusionMembersTool } from './update_supergroup_inclusion_members'
export { ripplingUpdateTitleTool } from './update_title'
export { ripplingUpdateWorkLocationTool } from './update_work_location'

View File

@@ -0,0 +1,86 @@
import type { RipplingListBusinessPartnerGroupsParams } from '@/tools/rippling/types'
import { BUSINESS_PARTNER_GROUP_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListBusinessPartnerGroupsTool: ToolConfig<RipplingListBusinessPartnerGroupsParams> =
{
id: 'rippling_list_business_partner_groups',
name: 'Rippling List Business Partner Groups',
description: 'List all business partner groups',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field. Prefix with - for descending',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from previous response',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.expand != null) query.set('expand', params.expand)
if (params.orderBy != null) query.set('order_by', params.orderBy)
if (params.cursor != null) query.set('cursor', params.cursor)
const qs = query.toString()
return `https://rest.ripplingapis.com/business-partner-groups/${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
businessPartnerGroups: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
name: (item.name as string) ?? null,
domain: (item.domain as string) ?? null,
default_business_partner_id: (item.default_business_partner_id as string) ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
businessPartnerGroups: {
type: 'array',
description: 'List of businessPartnerGroups',
items: { type: 'object', properties: BUSINESS_PARTNER_GROUP_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,90 @@
import type { RipplingListBusinessPartnersParams } from '@/tools/rippling/types'
import { BUSINESS_PARTNER_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListBusinessPartnersTool: ToolConfig<RipplingListBusinessPartnersParams> = {
id: 'rippling_list_business_partners',
name: 'Rippling List Business Partners',
description: 'List all business partners',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
filter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter expression',
},
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field. Prefix with - for descending',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from previous response',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.filter != null) query.set('filter', params.filter)
if (params.expand != null) query.set('expand', params.expand)
if (params.orderBy != null) query.set('order_by', params.orderBy)
if (params.cursor != null) query.set('cursor', params.cursor)
const qs = query.toString()
return `https://rest.ripplingapis.com/business-partners/${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
businessPartners: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
business_partner_group_id: (item.business_partner_group_id as string) ?? null,
worker_id: (item.worker_id as string) ?? null,
client_group_id: (item.client_group_id as string) ?? null,
client_group_member_count: (item.client_group_member_count as number) ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
businessPartners: {
type: 'array',
description: 'List of businessPartners',
items: { type: 'object', properties: BUSINESS_PARTNER_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,87 @@
import type { RipplingListCompaniesParams } from '@/tools/rippling/types'
import { COMPANY_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListCompaniesTool: ToolConfig<RipplingListCompaniesParams> = {
id: 'rippling_list_companies',
name: 'Rippling List Companies',
description: 'List all companies',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated fields to expand',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field. Prefix with - for descending',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from previous response',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.expand != null) query.set('expand', params.expand)
if (params.orderBy != null) query.set('order_by', params.orderBy)
if (params.cursor != null) query.set('cursor', params.cursor)
const qs = query.toString()
return `https://rest.ripplingapis.com/companies/${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
companies: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
name: (item.name as string) ?? null,
legal_name: (item.legal_name as string) ?? null,
doing_business_as_name: (item.doing_business_as_name as string) ?? null,
phone: (item.phone as string) ?? null,
primary_email: (item.primary_email as string) ?? null,
parent_legal_entity_id: (item.parent_legal_entity_id as string) ?? null,
legal_entities_id: (item.legal_entities_id as unknown[]) ?? [],
physical_address: item.physical_address ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
companies: {
type: 'array',
description: 'List of companies',
items: { type: 'object', properties: COMPANY_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,59 @@
import type { RipplingListCustomAppsParams } from '@/tools/rippling/types'
import { CUSTOM_APP_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListCustomAppsTool: ToolConfig<RipplingListCustomAppsParams> = {
id: 'rippling_list_custom_apps',
name: 'Rippling List CustomApps',
description: 'List all custom apps',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
},
request: {
url: `https://rest.ripplingapis.com/custom-apps/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
customApps: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
name: (item.name as string) ?? null,
api_name: (item.api_name as string) ?? null,
description: (item.description as string) ?? null,
icon: (item.icon as string) ?? null,
pages: (item.pages as unknown[]) ?? [],
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
customApps: {
type: 'array',
description: 'List of customApps',
items: { type: 'object', properties: CUSTOM_APP_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -1,18 +1,12 @@
import type {
RipplingListCustomFieldsParams,
RipplingListCustomFieldsResponse,
} from '@/tools/rippling/types'
import type { RipplingListCustomFieldsParams } from '@/tools/rippling/types'
import { CUSTOM_FIELD_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListCustomFieldsTool: ToolConfig<
RipplingListCustomFieldsParams,
RipplingListCustomFieldsResponse
> = {
export const ripplingListCustomFieldsTool: ToolConfig<RipplingListCustomFieldsParams> = {
id: 'rippling_list_custom_fields',
name: 'Rippling List Custom Fields',
description: 'List all custom fields defined in Rippling',
description: 'List all custom fields',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
@@ -20,77 +14,63 @@ export const ripplingListCustomFieldsTool: ToolConfig<
visibility: 'user-only',
description: 'Rippling API key',
},
limit: {
type: 'number',
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of custom fields to return',
description: 'Sort field. Prefix with - for descending',
},
offset: {
type: 'number',
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Offset for pagination',
description: 'Pagination cursor from previous response',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.limit != null) query.set('limit', String(params.limit))
if (params.offset != null) query.set('offset', String(params.offset))
if (params.orderBy != null) query.set('order_by', params.orderBy)
if (params.cursor != null) query.set('cursor', params.cursor)
const qs = query.toString()
return `https://api.rippling.com/platform/api/custom_fields${qs ? `?${qs}` : ''}`
return `https://rest.ripplingapis.com/custom-fields/${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = Array.isArray(data) ? data : (data.results ?? [])
const customFields = results.map((field: Record<string, unknown>) => ({
id: (field.id as string) ?? '',
type: (field.type as string) ?? null,
title: (field.title as string) ?? null,
mandatory: Boolean(field.mandatory),
}))
const results = data.results ?? []
return {
success: true,
output: {
customFields,
totalCount: customFields.length,
customFields: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
name: (item.name as string) ?? null,
description: (item.description as string) ?? null,
required: (item.required as boolean) ?? null,
type: (item.type as string) ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
customFields: {
type: 'array',
description: 'List of custom fields',
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Custom field ID' },
type: { type: 'string', description: 'Field type' },
title: { type: 'string', description: 'Field title' },
mandatory: { type: 'boolean', description: 'Whether the field is mandatory' },
},
},
},
totalCount: {
type: 'number',
description: 'Number of custom fields returned on this page',
description: 'List of customFields',
items: { type: 'object', properties: CUSTOM_FIELD_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,73 @@
import type { RipplingListCustomObjectFieldsParams } from '@/tools/rippling/types'
import { CUSTOM_OBJECT_FIELD_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListCustomObjectFieldsTool: ToolConfig<RipplingListCustomObjectFieldsParams> =
{
id: 'rippling_list_custom_object_fields',
name: 'Rippling List Custom Object Fields',
description: 'List all fields for a custom object',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/fields/`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
fields: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
name: (item.name as string) ?? null,
custom_object: (item.custom_object as string) ?? null,
description: (item.description as string) ?? null,
api_name: (item.api_name as string) ?? null,
data_type: item.data_type ?? null,
is_unique: (item.is_unique as boolean) ?? null,
is_immutable: (item.is_immutable as boolean) ?? null,
is_standard: (item.is_standard as boolean) ?? null,
enable_history: (item.enable_history as boolean) ?? null,
managed_package_install_id: (item.managed_package_install_id as string) ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
},
}
},
outputs: {
fields: {
type: 'array',
description: 'List of fields',
items: { type: 'object', properties: CUSTOM_OBJECT_FIELD_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of fields returned' },
nextLink: { type: 'string', description: 'Next page link', optional: true },
},
}

View File

@@ -0,0 +1,90 @@
import type { RipplingListCustomObjectRecordsParams } from '@/tools/rippling/types'
import { CUSTOM_OBJECT_RECORD_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListCustomObjectRecordsTool: ToolConfig<RipplingListCustomObjectRecordsParams> =
{
id: 'rippling_list_custom_object_records',
name: 'Rippling List Custom Object Records',
description: 'List all records for a custom object',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
customObjectApiName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Custom object API name',
},
},
request: {
url: (params) =>
`https://rest.ripplingapis.com/custom-objects/${encodeURIComponent(params.customObjectApiName.trim())}/records/`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
records: results.map((item: Record<string, unknown>) => {
const {
id,
created_at,
updated_at,
name,
external_id,
created_by,
last_modified_by,
owner_role,
system_updated_at,
...dynamicFields
} = item
return {
id: (id as string) ?? '',
created_at: (created_at as string) ?? null,
updated_at: (updated_at as string) ?? null,
name: (name as string) ?? null,
external_id: (external_id as string) ?? null,
created_by: created_by ?? null,
last_modified_by: last_modified_by ?? null,
owner_role: owner_role ?? null,
system_updated_at: (system_updated_at as string) ?? null,
data: dynamicFields,
}
}),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
},
}
},
outputs: {
records: {
type: 'array',
description: 'List of records',
items: {
type: 'object',
properties: {
...CUSTOM_OBJECT_RECORD_OUTPUT_PROPERTIES,
data: { type: 'json', description: 'Full record data including dynamic fields' },
},
},
},
totalCount: { type: 'number', description: 'Number of records returned' },
nextLink: { type: 'string', description: 'Next page link', optional: true },
},
}

View File

@@ -0,0 +1,64 @@
import type { RipplingListCustomObjectsParams } from '@/tools/rippling/types'
import { CUSTOM_OBJECT_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListCustomObjectsTool: ToolConfig<RipplingListCustomObjectsParams> = {
id: 'rippling_list_custom_objects',
name: 'Rippling List Custom Objects',
description: 'List all custom objects',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
},
request: {
url: `https://rest.ripplingapis.com/custom-objects/`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
customObjects: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
name: (item.name as string) ?? null,
description: (item.description as string) ?? null,
api_name: (item.api_name as string) ?? null,
plural_label: (item.plural_label as string) ?? null,
category_id: (item.category_id as string) ?? null,
enable_history: (item.enable_history as boolean) ?? null,
native_category_id: (item.native_category_id as string) ?? null,
managed_package_install_id: (item.managed_package_install_id as string) ?? null,
owner_id: (item.owner_id as string) ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
},
}
},
outputs: {
customObjects: {
type: 'array',
description: 'List of customObjects',
items: { type: 'object', properties: CUSTOM_OBJECT_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
},
}

View File

@@ -0,0 +1,60 @@
import type { RipplingListCustomPagesParams } from '@/tools/rippling/types'
import { CUSTOM_PAGE_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListCustomPagesTool: ToolConfig<RipplingListCustomPagesParams> = {
id: 'rippling_list_custom_pages',
name: 'Rippling List CustomPages',
description: 'List all custom pages',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
},
request: {
url: `https://rest.ripplingapis.com/custom-pages/`,
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
customPages: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
name: (item.name as string) ?? null,
components: item.components ?? [],
actions: item.actions ?? [],
canvas_actions: item.canvas_actions ?? [],
variables: item.variables ?? [],
media: item.media ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
customPages: {
type: 'array',
description: 'List of customPages',
items: { type: 'object', properties: CUSTOM_PAGE_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -0,0 +1,79 @@
import type { RipplingListCustomSettingsParams } from '@/tools/rippling/types'
import { CUSTOM_SETTING_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListCustomSettingsTool: ToolConfig<RipplingListCustomSettingsParams> = {
id: 'rippling_list_custom_settings',
name: 'Rippling List Custom Settings',
description: 'List all custom settings',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Rippling API key',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort field. Prefix with - for descending',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from previous response',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.orderBy != null) query.set('order_by', params.orderBy)
if (params.cursor != null) query.set('cursor', params.cursor)
const qs = query.toString()
return `https://rest.ripplingapis.com/custom-settings/${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = data.results ?? []
return {
success: true,
output: {
customSettings: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
display_name: (item.display_name as string) ?? null,
api_name: (item.api_name as string) ?? null,
data_type: (item.data_type as string) ?? null,
secret_value: (item.secret_value as string) ?? null,
string_value: (item.string_value as string) ?? null,
number_value: (item.number_value as number) ?? null,
boolean_value: (item.boolean_value as boolean) ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
customSettings: {
type: 'array',
description: 'List of custom settings',
items: { type: 'object', properties: CUSTOM_SETTING_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

View File

@@ -1,18 +1,12 @@
import type {
RipplingListDepartmentsParams,
RipplingListDepartmentsResponse,
} from '@/tools/rippling/types'
import type { RipplingListDepartmentsParams } from '@/tools/rippling/types'
import { DEPARTMENT_OUTPUT_PROPERTIES } from '@/tools/rippling/types'
import type { ToolConfig } from '@/tools/types'
export const ripplingListDepartmentsTool: ToolConfig<
RipplingListDepartmentsParams,
RipplingListDepartmentsResponse
> = {
export const ripplingListDepartmentsTool: ToolConfig<RipplingListDepartmentsParams> = {
id: 'rippling_list_departments',
name: 'Rippling List Departments',
description: 'List all departments in the Rippling organization',
description: 'List all departments',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
@@ -20,75 +14,72 @@ export const ripplingListDepartmentsTool: ToolConfig<
visibility: 'user-only',
description: 'Rippling API key',
},
limit: {
type: 'number',
expand: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of departments to return',
description: 'Comma-separated fields to expand',
},
offset: {
type: 'number',
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Offset for pagination',
description: 'Sort field. Prefix with - for descending',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor from previous response',
},
},
request: {
url: (params) => {
const query = new URLSearchParams()
if (params.limit != null) query.set('limit', String(params.limit))
if (params.offset != null) query.set('offset', String(params.offset))
if (params.expand != null) query.set('expand', params.expand)
if (params.orderBy != null) query.set('order_by', params.orderBy)
if (params.cursor != null) query.set('cursor', params.cursor)
const qs = query.toString()
return `https://api.rippling.com/platform/api/departments${qs ? `?${qs}` : ''}`
return `https://rest.ripplingapis.com/departments/${qs ? `?${qs}` : ''}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
Accept: 'application/json',
}),
headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, Accept: 'application/json' }),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Rippling API error (${response.status}): ${errorText}`)
}
const data = await response.json()
const results = Array.isArray(data) ? data : (data.results ?? [])
const departments = results.map((dept: Record<string, unknown>) => ({
id: (dept.id as string) ?? '',
name: (dept.name as string) ?? null,
parent: (dept.parent as string) ?? null,
}))
const results = data.results ?? []
return {
success: true,
output: {
departments,
totalCount: departments.length,
departments: results.map((item: Record<string, unknown>) => ({
id: (item.id as string) ?? '',
created_at: (item.created_at as string) ?? null,
updated_at: (item.updated_at as string) ?? null,
name: (item.name as string) ?? null,
parent_id: (item.parent_id as string) ?? null,
reference_code: (item.reference_code as string) ?? null,
department_hierarchy_id: (item.department_hierarchy_id as unknown[]) ?? [],
parent: item.parent ?? null,
department_hierarchy: item.department_hierarchy ?? null,
})),
totalCount: results.length,
nextLink: (data.next_link as string) ?? null,
__meta: data.__meta ?? null,
},
}
},
outputs: {
departments: {
type: 'array',
description: 'List of departments',
items: {
type: 'json',
properties: {
id: { type: 'string', description: 'Department ID' },
name: { type: 'string', description: 'Department name' },
parent: { type: 'string', description: 'Parent department ID' },
},
},
},
totalCount: {
type: 'number',
description: 'Number of departments returned on this page',
items: { type: 'object', properties: DEPARTMENT_OUTPUT_PROPERTIES },
},
totalCount: { type: 'number', description: 'Number of items returned' },
nextLink: { type: 'string', description: 'Link to next page of results', optional: true },
__meta: { type: 'json', description: 'Metadata including redacted_fields', optional: true },
},
}

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