Skip to content
Merged
97 changes: 95 additions & 2 deletions .claude/commands/add-block.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,41 @@ outputs: {
}
```

### Typed JSON Outputs

When using `type: 'json'` and you know the object shape in advance, **describe the inner fields in the description** so downstream blocks know what properties are available. For well-known, stable objects, use nested output definitions instead:

```typescript
outputs: {
// BAD: Opaque json with no info about what's inside
plan: { type: 'json', description: 'Zone plan information' },

// GOOD: Describe the known fields in the description
plan: {
type: 'json',
description: 'Zone plan information (id, name, price, currency, frequency, is_subscribed)',
},

// BEST: Use nested output definition when the shape is stable and well-known
plan: {
id: { type: 'string', description: 'Plan identifier' },
name: { type: 'string', description: 'Plan name' },
price: { type: 'number', description: 'Plan price' },
currency: { type: 'string', description: 'Price currency' },
},
}
```

Use the nested pattern when:
- The object has a small, stable set of fields (< 10)
- Downstream blocks will commonly access specific properties
- The API response shape is well-documented and unlikely to change

Use `type: 'json'` with a descriptive string when:
- The object has many fields or a dynamic shape
- It represents a list/array of items
- The shape varies by operation

## V2 Block Pattern

When creating V2 blocks (alongside legacy V1):
Expand Down Expand Up @@ -695,16 +730,74 @@ Please provide the SVG and I'll convert it to a React component.
You can usually find this in the service's brand/press kit page, or copy it from their website.
```

## Advanced Mode for Optional Fields

Optional fields that are rarely used should be set to `mode: 'advanced'` so they don't clutter the basic UI. This includes:
- Pagination tokens
- Time range filters (start/end time)
- Sort order options
- Reply settings
- Rarely used IDs (e.g., reply-to tweet ID, quote tweet ID)
- Max results / limits

```typescript
{
id: 'startTime',
title: 'Start Time',
type: 'short-input',
Comment thread
waleedlatif1 marked this conversation as resolved.
placeholder: 'ISO 8601 timestamp',
condition: { field: 'operation', value: ['search', 'list'] },
mode: 'advanced', // Rarely used, hide from basic view
}
```

## WandConfig for Complex Inputs

Use `wandConfig` for fields that are hard to fill out manually, such as timestamps, JSON arrays, and complex query strings. This gives users an AI-assisted input experience.

```typescript
// Timestamps - use generationType: 'timestamp' to inject current date context
{
id: 'startTime',
title: 'Start Time',
type: 'short-input',
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: 'Generate an ISO 8601 timestamp based on the user description. Return ONLY the timestamp string.',
generationType: 'timestamp',
},
}

// JSON arrays - use generationType: 'json-object'
{
id: 'mediaIds',
title: 'Media IDs',
type: 'short-input',
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: 'Generate a comma-separated list of media IDs. Return ONLY the comma-separated values.',
},
}
```

## Naming Convention

All tool IDs referenced in `tools.access` and returned by `tools.config.tool` MUST use `snake_case` (e.g., `x_create_tweet`, `slack_send_message`). Never use camelCase or PascalCase.

## Checklist Before Finishing

- [ ] All subBlocks have `id`, `title` (except switch), and `type`
- [ ] Conditions use correct syntax (field, value, not, and)
- [ ] DependsOn set for fields that need other values
- [ ] Required fields marked correctly (boolean or condition)
- [ ] OAuth inputs have correct `serviceId`
- [ ] Tools.access lists all tool IDs
- [ ] Tools.config.tool returns correct tool ID
- [ ] Tools.access lists all tool IDs (snake_case)
- [ ] Tools.config.tool returns correct tool ID (snake_case)
- [ ] Outputs match tool outputs
- [ ] Block registered in registry.ts
- [ ] If icon missing: asked user to provide SVG
- [ ] If triggers exist: `triggers` config set, trigger subBlocks spread
- [ ] Optional/rarely-used fields set to `mode: 'advanced'`
- [ ] Timestamps and complex inputs have `wandConfig` enabled
30 changes: 29 additions & 1 deletion .claude/commands/add-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export const {service}{Action}Tool: ToolConfig<Params, Response> = {
- Always use `?? []` for optional array fields
- Set `optional: true` for outputs that may not exist
- Never output raw JSON dumps - extract meaningful fields
- When using `type: 'json'` and you know the object shape, define `properties` with the inner fields so downstream consumers know the structure. Only use bare `type: 'json'` when the shape is truly dynamic

## Step 3: Create Block

Expand Down Expand Up @@ -685,13 +686,40 @@ return NextResponse.json({
| `isUserFile` | `@/lib/core/utils/user-file` | Type guard for UserFile objects |
| `FileInputSchema` | `@/lib/uploads/utils/file-schemas` | Zod schema for file validation |

### Advanced Mode for Optional Fields

Optional fields that are rarely used should be set to `mode: 'advanced'` so they don't clutter the basic UI. Examples: pagination tokens, time range filters, sort order, max results, reply settings.

### WandConfig for Complex Inputs

Use `wandConfig` for fields that are hard to fill out manually:
- **Timestamps**: Use `generationType: 'timestamp'` to inject current date context into the AI prompt
- **JSON arrays**: Use `generationType: 'json-object'` for structured data
- **Complex queries**: Use a descriptive prompt explaining the expected format

```typescript
{
id: 'startTime',
title: 'Start Time',
type: 'short-input',
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
generationType: 'timestamp',
},
}
```

### Common Gotchas

1. **OAuth serviceId must match** - The `serviceId` in oauth-input must match the OAuth provider configuration
2. **Tool IDs are snake_case** - `stripe_create_payment`, not `stripeCreatePayment`
2. **All tool IDs MUST be snake_case** - `stripe_create_payment`, not `stripeCreatePayment`. This applies to tool `id` fields, registry keys, `tools.access` arrays, and `tools.config.tool` return values
3. **Block type is snake_case** - `type: 'stripe'`, not `type: 'Stripe'`
4. **Alphabetical ordering** - Keep imports and registry entries alphabetically sorted
5. **Required can be conditional** - Use `required: { field: 'op', value: 'create' }` instead of always true
6. **DependsOn clears options** - When a dependency changes, selector options are refetched
7. **Never pass Buffer directly to fetch** - Convert to `new Uint8Array(buffer)` for TypeScript compatibility
8. **Always handle legacy file params** - Keep hidden `fileContent` params for backwards compatibility
9. **Optional fields use advanced mode** - Set `mode: 'advanced'` on rarely-used optional fields
10. **Complex inputs need wandConfig** - Timestamps, JSON arrays, and other hard-to-type values should have `wandConfig` enabled
24 changes: 21 additions & 3 deletions .claude/commands/add-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,18 @@ closedAt: {
},
```

### Nested Properties
For complex outputs, define nested structure:
### Typed JSON Outputs

When using `type: 'json'` and you know the object shape in advance, **always define the inner structure** using `properties` so downstream consumers know what fields are available:

```typescript
// BAD: Opaque json with no info about what's inside
metadata: {
type: 'json',
description: 'Response metadata',
},

// GOOD: Define the known properties
metadata: {
type: 'json',
description: 'Response metadata',
Expand All @@ -159,7 +168,10 @@ metadata: {
count: { type: 'number', description: 'Total count' },
},
},
```

For arrays of objects, define the item structure:
```typescript
items: {
type: 'array',
description: 'List of items',
Expand All @@ -173,6 +185,8 @@ items: {
},
```

Only use bare `type: 'json'` without `properties` when the shape is truly dynamic or unknown.

## Critical Rules for transformResponse

### Handle Nullable Fields
Expand Down Expand Up @@ -272,13 +286,17 @@ If creating V2 tools (API-aligned outputs), use `_v2` suffix:
- Version: `'2.0.0'`
- Outputs: Flat, API-aligned (no content/metadata wrapper)

## Naming Convention

All tool IDs MUST use `snake_case`: `{service}_{action}` (e.g., `x_create_tweet`, `slack_send_message`). Never use camelCase or PascalCase for tool IDs.

## Checklist Before Finishing

- [ ] All tool IDs use snake_case
- [ ] All params have explicit `required: true` or `required: false`
- [ ] All params have appropriate `visibility`
- [ ] All nullable response fields use `?? null`
- [ ] All optional outputs have `optional: true`
- [ ] No raw JSON dumps in outputs
- [ ] Types file has all interfaces
- [ ] Index.ts exports all tools
- [ ] Tool IDs use snake_case
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,19 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'read:user': 'Read public user information',
'user:email': 'Access email address',
'tweet.read': 'Read tweets and timeline',
'tweet.write': 'Post tweets',
'users.read': 'Read profile information',
'tweet.write': 'Post and delete tweets',
'tweet.moderate.write': 'Hide and unhide replies to tweets',
'users.read': 'Read user profiles and account information',
'follows.read': 'View followers and following lists',
'follows.write': 'Follow and unfollow users',
'bookmark.read': 'View bookmarked tweets',
'bookmark.write': 'Add and remove bookmarks',
'like.read': 'View liked tweets and liking users',
'like.write': 'Like and unlike tweets',
'block.read': 'View blocked users',
'block.write': 'Block and unblock users',
'mute.read': 'View muted users',
'mute.write': 'Mute and unmute users',
'offline.access': 'Access account when not using the application',
'data.records:read': 'Read records',
'data.records:write': 'Write to records',
Expand Down
13 changes: 13 additions & 0 deletions apps/sim/blocks/blocks/x.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,20 +108,23 @@ export const XBlock: BlockConfig = {
type: 'short-input',
placeholder: 'Enter tweet ID to reply to',
condition: { field: 'operation', value: 'x_create_tweet' },
mode: 'advanced',
},
{
id: 'quoteTweetId',
title: 'Quote Tweet ID',
type: 'short-input',
placeholder: 'Enter tweet ID to quote',
condition: { field: 'operation', value: 'x_create_tweet' },
mode: 'advanced',
},
{
id: 'mediaIds',
title: 'Media IDs',
type: 'short-input',
placeholder: 'Comma-separated media IDs (up to 4)',
condition: { field: 'operation', value: 'x_create_tweet' },
mode: 'advanced',
},
{
id: 'replySettings',
Expand All @@ -136,6 +139,7 @@ export const XBlock: BlockConfig = {
],
value: () => '',
condition: { field: 'operation', value: 'x_create_tweet' },
mode: 'advanced',
},
// --- Tweet ID field (shared by multiple operations) ---
{
Expand Down Expand Up @@ -212,6 +216,7 @@ export const XBlock: BlockConfig = {
],
value: () => 'recency',
condition: { field: 'operation', value: 'x_search_tweets' },
mode: 'advanced',
},
// --- User ID field (shared by many operations) ---
{
Expand Down Expand Up @@ -341,6 +346,7 @@ export const XBlock: BlockConfig = {
field: 'operation',
value: ['x_get_user_tweets', 'x_get_user_timeline'],
},
mode: 'advanced',
},
// --- Time range fields (shared by tweet search and user tweet operations) ---
{
Expand All @@ -357,6 +363,7 @@ export const XBlock: BlockConfig = {
'x_get_user_timeline',
],
},
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate an ISO 8601 timestamp based on the user's description.
Expand Down Expand Up @@ -386,6 +393,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
'x_get_user_timeline',
],
},
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate an ISO 8601 timestamp based on the user's description.
Expand Down Expand Up @@ -425,6 +433,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
'x_get_blocking',
],
},
mode: 'advanced',
},
// --- Pagination Token (shared by many operations) ---
{
Expand All @@ -448,6 +457,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
'x_get_blocking',
],
},
mode: 'advanced',
},
// --- Next Token (for search operations that use nextToken instead of paginationToken) ---
{
Expand All @@ -459,6 +469,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
field: 'operation',
value: ['x_search_tweets', 'x_search_users'],
},
mode: 'advanced',
},
// --- Trends fields ---
{
Expand All @@ -475,6 +486,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
type: 'short-input',
placeholder: '20',
condition: { field: 'operation', value: 'x_get_trends_by_woeid' },
mode: 'advanced',
},
// --- Usage fields ---
{
Expand All @@ -483,6 +495,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
type: 'short-input',
placeholder: '7 (1-90)',
condition: { field: 'operation', value: 'x_get_usage' },
mode: 'advanced',
},
],
tools: {
Expand Down
18 changes: 17 additions & 1 deletion apps/sim/lib/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,23 @@ export const auth = betterAuth({
tokenUrl: 'https://api.x.com/2/oauth2/token',
userInfoUrl: 'https://api.x.com/2/users/me',
accessType: 'offline',
scopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
scopes: [
'tweet.read',
'tweet.write',
'tweet.moderate.write',
'users.read',
'follows.read',
'follows.write',
'bookmark.read',
'bookmark.write',
'like.read',
'like.write',
'block.read',
'block.write',
'mute.read',
'mute.write',
'offline.access',
],
pkce: true,
responseType: 'code',
prompt: 'consent',
Expand Down