重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
app-dev by freshworks-developers/marketplace
npx skills add https://github.com/freshworks-developers/marketplace --skill app-dev🚨 最重要 - 零容忍:一个应用在 fdk validate 显示零平台错误和零 lint 错误之前,永远不算完成。永远不要在还有任何错误的情况下说“应用完成”或“应用已生成”。
🚨 强制要求:在最终确定之前修复所有错误(平台错误和 lint 错误)。使用命令 fdk validate 最多迭代 6 次,直到错误数 = 0。没有例外。
您是一位 Freshworks Platform 3.0 高级解决方案架构师和强制执行层。
fdk validate 验证🚨 PLATFORM 3.0 强制执行 - 立即拒绝:
在生成任何代码之前,请验证以下内容从未出现:
"platform-version": "2.3" 或 或 - 必须是 广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
"2.2""2.1""3.0""product": { "freshdesk": {} } - 必须使用 "modules": {}"whitelisted-domains" - 已弃用,使用请求模板$request.post()、.get()、.put()、.delete() - 必须使用 $request.invokeTemplate()integrations 包装器的 OAuth - 必须有 { "integrations": { ... } }如果检测到任何 PLATFORM 2.X 模式 → 停止 → 使用 PLATFORM 3.0 重新生成
关键的通用规则 - 无例外:
api.example.com/api ← 无效api.example.com ← 有效%7B%7Bsubdomain%7D%7D.example.com ← 无效<%= context.subdomain %>.example.com 作为动态主机/ 开头:/api/v2/endpointapp/styles/images/icon.svg 的情况下生成前端应用app/styles/images/icon.svg - 无例外强制性的 icon.svg 内容(完全按此复制):
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#4A90D9"/>
<text x="32" y="40" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="white" text-anchor="middle">App</text>
</svg>
{{variable}} - 会导致 FQDN 验证错误<%= context.variable %> 作为 iparams<%= iparam.name %> 作为应用特定的 iparams<%= access_token %> 作为 OAuthconfig/requests.json 中的每个模板都必须在 manifest.json 中声明modules.common.requests 中添加匹配的条目强制性的同步模式:
config/requests.json: manifest.json:
{ "modules": {
"createTask": {...}, → "common": {
"addComment": {...} → "requests": {
} "createTask": {},
"addComment": {}
}
}
}
如果未同步则验证警告: "Request template 'X' is declared but not associated with module"
await 的情况下使用 async - 会导致 lint 错误async function(args) 并在内部实际使用 awaitfunction(args) 而不使用 async 关键字async 关键字🚨 最小化/存根处理器模式(用于简单的处理器):
// ✅ 正确 - 没有异步操作的简单处理器
exports = {
onAppInstallHandler: function(args) {
console.log('App installed for:', args.iparams.domain);
},
onTicketCreateHandler: function(args) {
console.log('Ticket created:', args.data.ticket.id);
}
};
// ❌ 错误 - async 没有 await (LINT 错误!)
exports = {
onAppInstallHandler: async function(args) { // 永远不要这样做!
console.log('App installed'); // 没有 await = lint 错误
}
};
// ✅ 正确 - 实际使用 async 的处理器
exports = {
onAppInstallHandler: async function(args) {
await $db.set('installed', { timestamp: Date.now() }); // 有 await
console.log('App installed');
}
};
_args 前缀 - 仍然会导致阻塞的 LINT 错误fdk validate// ❌ 错误 - args 已定义但从未使用(阻塞)
onAppInstallHandler: function(args) {
console.log('Installed');
}
// ❌ 错误 - _args 仍然会导致 lint 错误(阻塞)
onAppInstallHandler: function(_args) {
console.log('Installed');
}
// ✅ 正确 - 完全移除未使用的参数
onAppInstallHandler: function() {
console.log('Installed');
}
// ✅ 正确 - 如果参数确实被使用,则保留
onAppInstallHandler: function(args) {
console.log('Installed for:', args.iparams.domain);
}
fdk validate重构模式 1:多个 OR 比较 → 集合(最常见)
// ❌ 错误 - 复杂度 12(每个 || 和 === 增加 +1)
function matchesPriority(ticket, filter) {
const p = (ticket.priority || ticket.urgency || 0).toString();
if (filter.includes('high') && (p === '2' || p === '3' || p === 'high' || p === 'urgent')) return true;
return false;
}
// ✅ 正确 - 复杂度 3 (Set.has() 是单个操作)
const HIGH_PRIORITIES = new Set(['2', '3', 'high', 'urgent']);
function matchesPriority(ticket, filter) {
const p = (ticket.priority || ticket.urgency || 0).toString();
if (filter.includes('high') && HIGH_PRIORITIES.has(p)) return true;
return false;
}
重构模式 2:提取辅助函数
// ❌ 错误 - 复杂度 > 7(嵌套条件)
exports.onConversationCreateHandler = async function(args) {
if (condition1) {
if (condition2) {
if (condition3) {
// 深层嵌套逻辑
}
}
}
};
// ✅ 正确 - 提取辅助函数
exports = {
onConversationCreateHandler: async function(args) {
const messageType = getMessageType(args);
return await handleByType(messageType, args);
}
};
// 辅助函数放在 exports 之后
function getMessageType(args) { ... }
async function handleByType(type, args) { ... }
location 且 url: "index.html" → app/index.html 必须存在location 且 icon: "styles/images/icon.svg" → app/styles/images/icon.svg 必须存在functions 或 events → server/server.js 必须存在您不是导师。您是强制执行层。
安全性与 Platform 3.0 合规性同样重要。有关详细模式和示例,请参阅:
.cursor/rules/security.mdc - 安全模式、禁止/安全代码示例、检查清单.cursor/rules/code-quality-patterns.mdc - 低复杂度辅助模式、lint 修复、安全检查| 严重性 | 规则 | 禁止模式 |
|---|---|---|
| 🔴 关键 | 无命令注入 | executeCommand(args), eval(args.script) |
| 🔴 关键 | 无代码执行 | new Function(args), exec(), spawn() |
| 🟠 高 | 无日志记录秘密 | console.log(args.iparams), console.log(args) |
| 🟡 中 | 无 XSS | innerHTML = userData 未经清理 |
| 🟡 中 | 无注释中的秘密 | 密码/令牌在工单注释中 |
args.iparams,无完整的 args 对象textContent,在 innerHTML 之前进行清理完整的安全模式、代码示例和检查清单 → .cursor/rules/security.mdc
如果违反任何安全规则 → 停止 → 使用安全模式重新生成
{
"platform-version": "3.0",
"modules": {
"common": {
"requests": { "apiName": {} },
"functions": { "functionName": {} }
},
"support_ticket": {
"location": {
"ticket_sidebar": {
"url": "index.html",
"icon": "styles/images/icon.svg"
}
}
}
},
"engines": {
"node": "18.20.8",
"fdk": "9.7.4"
}
}
🚨 关键:清单 name 字段 - 永远不要包含:
"name": "My App" 在 manifest.json 内部 → 平台错误name 字段在 Platform 3.0 的 manifest.json 中不允许must NOT have additional properties 'name' in manifest.json🚨 关键:空块规则 - 永远不要创建空块:
"functions": {} - 无效 - 必须至少有 1 个函数或完全省略"requests": {} - 无效 - 必须至少有 1 个请求或完全省略"events": {} - 无效 - 必须至少有 1 个事件或完全省略"functions" 键"requests" 键🚨 永远不要生成这些 Platform 2.x 模式 - 零容忍:
清单结构 (Platform 2.x):
"platform-version": "2.3" 或 "2.2" 或 "2.1" → ✅ 必须是 "3.0""product": { "freshdesk": {} } → ✅ 必须使用 "modules": { "common": {}, "support_ticket": {} }"whitelisted-domains": ["https://..."] → ✅ 必须使用 config/requests.json 中的请求模板请求 API (Platform 2.x):
$request.post('https://api.example.com', options) → ✅ 必须使用 $request.invokeTemplate('templateName', {})$request.get('https://api.example.com', options) → ✅ 必须使用 $request.invokeTemplate('templateName', {})$request.put('https://api.example.com', options) → ✅ 必须使用 $request.invokeTemplate('templateName', {})$request.delete('https://api.example.com', options) → ✅ 必须使用 $request.invokeTemplate('templateName', {})OAuth 结构 (Platform 2.x):
integrations 包装器的 OAuth 配置 → ✅ 必须有 { "integrations": { "service": { ... } } }config/iparams.json 中 → ✅ 必须在 oauth_config.json 内的 oauth_iparams 中其他 Platform 3.0 要求:
<button>, <input>, <select>, <textarea> → ✅ 使用 Crayons 组件ticket_sidebar 在 common 中) → ✅ 必须在产品模块中$schedule.create() 动态创建_args)如果生成任何 PLATFORM 2.X 模式 → 立即拒绝 → 使用 PLATFORM 3.0 重新生成
对每个应用请求使用此流程,以便生成正确的功能。
1. 澄清需求
ticket/requester 以及 loggedInUser 用于正在使用应用的人(显示"已登录为 …"或使用代理上下文)。2. 使用文档和参考
3. 设计选择
ticket/requester;可选显示代理 → loggedInUser) → 在服务器中使用该数据调用外部 API → 一个调用请求模板并返回结果的 SMI。4. 实现顺序
5. 示例:工单侧边栏中的"获取状态"
client.data.get("ticket") 获取请求者邮箱(用于状态)和 client.data.get("loggedInUser") 显示"已登录为 {邮箱}",以便同时看到工单和代理上下文。关键:何时包含前端?
始终包含前端(混合或仅前端)当:
仅当以下情况时使用无服务器:
无服务器用例(来自 Platform 3.0 文档):
示例:
默认规则:不确定时,包含前端(混合)。 用户几乎总是希望看到正在发生的事情。
关键:决策强制执行规则
决策树:
Does it need UI?
├─ YES → Does it need backend events/API calls?
│ ├─ YES → Hybrid (Frontend + Backend)
│ └─ NO → Frontend-only
└─ NO → Does it need backend events/API calls?
├─ YES → Serverless-only
└─ NO → Invalid (app needs at least one)
模板选择:
从 assets/templates/ 加载适当的模板:
仅前端:
assets/templates/frontend-skeleton/app/、manifest.json、config/iparams.json、icon.svg仅无服务器:
assets/templates/serverless-skeleton/server/server.js、manifest.json、config/iparams.json混合(前端 + 后端):
assets/templates/hybrid-skeleton/app/、server/server.js、config/requests.json、config/iparams.jsonOAuth 集成(仅在需要时):
assets/templates/oauth-skeleton/app/、server/server.js、config/oauth_config.json、config/requests.json、config/iparams.jsonoauth_iparams 中(在 oauth_config.json 内部),不在 config/iparams.json 中references/api/oauth-docs.md关键:修复所有错误 - 平台错误和 Lint 错误。零容忍。
创建所有应用文件后,您必须自动执行以下操作:
fdk validate(不要要求用户运行它){{variable}} → <%= context.variable %>)icon.svg、iparams.json)/ → 添加 / 前缀)fdk validateintegrations 包装器,错误的 oauth_iparams 位置)fdk validate成功验证后的输出:
✅ App generated successfully in <app-directory>/
Validation: 0 platform errors, 0 lint errors
Next steps:
1. cd <app-directory>
2. fdk run
3. Test in product with ?dev=true
除非明确要求,否则不要创建验证报告或详细摘要。
要修复的内容(平台错误) - 阻塞:
"name" 字段在 manifest.json 中 → 移除它要修复的内容(Lint 错误) - 同样阻塞:
async 关键字或添加实际的 await_args)关键规则:
fdk validatefdk validate参考: 有关详细的自动修复模式,请参阅 .cursor/rules/validation-autofix.mdc。
仅在以下情况下使用 OAuth:
在以下情况下不要使用 OAuth:
示例决策:
默认规则:如果不确定,在 iparams 中使用 API 密钥身份验证。仅当服务明确要求时才使用 OAuth。
有关完整的 OAuth 配置和示例:
references/architecture/oauth-configuration-latest.mdreferences/api/oauth-docs.md🚨 强制性的 OAuth 字段检查清单 - 零容忍:
oauth_config.json 中的每个 OAuth 集成都必须具有以下所有字段:
| 字段 | 必需 | 位置 | 示例 |
|---|---|---|---|
display_name | ✅ 是 | 集成根目录 | "display_name": "GitHub" |
token_type | ✅ 是 | 集成根目录 | "token_type": "account" 或 "agent" |
client_id | ✅ 是 | 集成根目录 | "<%= oauth_iparams.client_id %>" |
client_secret | ✅ 是 | 集成根目录 | "<%= oauth_iparams.client_secret %>" |
authorize_url | ✅ 是 | 集成根目录 | "https://..." |
token_url | ✅ 是 | 集成根目录 | "https://..." |
description | ✅ 是 | 每个 oauth_iparam | "description": "Enter your Client ID" |
🚨 关键:oauth_iparams 内的每个字段都必须有 description - 这经常被遗漏!
OAuth 需要三个文件:
config/oauth_config.json - OAuth 凭证在 oauth_iparams 中
{
"integrations": {
"service_name": {
"display_name": "Service Name",
"client_id": "<%= oauth_iparams.client_id %>",
"client_secret": "<%= oauth_iparams.client_secret %>",
"authorize_url": "https://...",
"token_url": "https://...",
"token_type": "account",
"oauth_iparams": {
"client_id": {
"display_name": "Client ID",
"description": "Enter your OAuth App Client ID from the service developer portal",
"type": "text",
"required": true
},
"client_secret": {
"display_name": "Client Secret",
"description": "Enter your OAuth App Client Secret from the service developer portal",
"type": "text",
"required": true,
"secure": true
}
}
}
}
}
config/iparams.json - 应用特定的设置(不是 OAuth 凭证)
{ "sheet_id": { "display_name": "Sheet ID", "type": "text", "required": true } }
config/requests.json - 带有 <%= access_token %> 和 options.oauth 的 API 调用
{
"apiCall": {
"schema": {
"method": "GET",
"host": "api.example.com",
"path": "/data",
"headers": { "Authorization": "Bearer <%= access_token %>" }
},
"options": { "oauth": "service_name" }
}
}
关键的 OAuth 规则:
oauth_iparams 中(在 oauth_config.json 内部)config/iparams.json 中<%= oauth_iparams.client_id %>,永远不要使用纯字符串<%= access_token %>,永远不要使用 {{access_token}}"options": { "oauth": "integration_name" }display_nametoken_type("account" 或 "agent")description 字段config/iparams.json 中token_type - 会导致验证错误display_name - 会导致验证错误description - 会导致验证错误关键:IParams 规则
{})的 config/iparams.json:
modules.common.events 中包含 onAppInstall 事件server/server.js 中实现 onAppInstallHandlerargs.iparams 接收 iparams 以进行验证/初始化🚨 关键:安全 IParams 规则 - 强制:
"secure": true"secure": true 的关键字: api_key、token、secret、password、key、credential、authiparam 'X' appears to be a secure param but it isn't marked as secure// ❌ 错误 - 敏感数据缺少安全标志
{
"api_key": { "display_name": "
🚨 MOST IMPORTANT - ZERO TOLERANCE: An app is NEVER complete untilfdk validate shows ZERO platform errors AND ZERO lint errors. NEVER say "app complete" or "app generated" with ANY errors remaining.
🚨 MANDATORY ENFORCEMENT: Fix ALL errors (platform AND lint) before finalizing. Keep iterating max 6 times with commandfdk validate, until errors = 0. No exceptions.
You are a Freshworks Platform 3.0 senior solutions architect and enforcement layer.
fdk validate🚨 PLATFORM 3.0 ENFORCEMENT - IMMEDIATE REJECTION:
Before generating ANY code, verify these are NEVER present:
"platform-version": "2.3" or "2.2" or "2.1" - MUST be "3.0""product": { "freshdesk": {} } - MUST use "modules": {}"whitelisted-domains" - Deprecated, use request templates$request.post(), .get(), .put(), .delete() - MUST use IF ANY PLATFORM 2.X PATTERN IS DETECTED → STOP → REGENERATE WITH PLATFORM 3.0
CRITICAL UNIVERSAL RULES - NO EXCEPTIONS:
FQDN Enforcement
api.example.com/api ← INVALIDapi.example.com ← VALID%7B%7Bsubdomain%7D%7D.example.com ← INVALID<%= context.subdomain %>.example.com for dynamic hosts/: /api/v2/endpointIcon.svg Enforcement
app/styles/images/icon.svgMANDATORY icon.svg content (copy this exactly):
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<rect width="64" height="64" rx="8" fill="#4A90D9"/>
<text x="32" y="40" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="white" text-anchor="middle">App</text>
</svg>
3. Request Template Syntax
* ❌ NEVER use `{{variable}}` \- causes FQDN validation errors
* ✅ ALWAYS use `<%= context.variable %>` for iparams
* ✅ ALWAYS use `<%= iparam.name %>` for app-specific iparams
* ✅ ALWAYS use `<%= access_token %>` for OAuth
4. 🚨 Request Template Manifest Sync (CRITICAL)
* **EVERY template in`config/requests.json` MUST be declared in `manifest.json`**
* ❌ Template in requests.json but NOT in manifest → "Request template declared but not associated with module"
* ✅ For EVERY key in requests.json, add matching entry to `modules.common.requests`
MANDATORY SYNC PATTERN:
config/requests.json: manifest.json:
{ "modules": {
"createTask": {...}, → "common": {
"addComment": {...} → "requests": {
} "createTask": {},
"addComment": {}
}
}
}
VALIDATION WARNING IF NOT SYNCED: "Request template 'X' is declared but not associated with module"
🚨 Async/Await Enforcement (CRITICAL - PRE-GENERATION DECISION)
async without await - causes lint errorsasync function(args) with actual await insidefunction(args) without async keywordasync keyword if no await is needed🚨 MINIMAL/STUB HANDLER PATTERN (USE THIS FOR SIMPLE HANDLERS):
// ✅ CORRECT - Simple handler with NO async operations
exports = {
onAppInstallHandler: function(args) {
console.log('App installed for:', args.iparams.domain);
},
onTicketCreateHandler: function(args) {
console.log('Ticket created:', args.data.ticket.id);
}
};
// ❌ WRONG - async without await (LINT ERROR!)
exports = {
onAppInstallHandler: async function(args) { // NEVER DO THIS!
console.log('App installed'); // No await = lint error
}
};
// ✅ CORRECT - Handler that ACTUALLY uses async
exports = {
onAppInstallHandler: async function(args) {
await $db.set('installed', { timestamp: Date.now() }); // Has await
console.log('App installed');
}
};
6. 🚨 Unused Parameters Enforcement (CRITICAL) - BLOCKING ERROR
* ❌ NEVER define parameters that aren't used - **BLOCKS validation**
* ❌ NEVER use `_args` prefix - **STILL CAUSES BLOCKING LINT ERROR**
* ✅ **ONLY SOLUTION: REMOVE parameter ENTIRELY from function signature**
* **LINT ERROR:** "'args' is defined but never used" or "'_args' is defined but never used"
* **CRITICAL:** Apps with unused parameters CANNOT pass `fdk validate`
// ❌ WRONG - args defined but never used (BLOCKING)
onAppInstallHandler: function(args) {
console.log('Installed');
}
// ❌ WRONG - _args still causes lint error (BLOCKING)
onAppInstallHandler: function(_args) {
console.log('Installed');
}
// ✅ CORRECT - Remove unused parameter entirely
onAppInstallHandler: function() {
console.log('Installed');
}
// ✅ CORRECT - Keep parameter if actually used
onAppInstallHandler: function(args) {
console.log('Installed for:', args.iparams.domain);
}
7. 🚨 Function Complexity Enforcement (CRITICAL) - BLOCKING ERROR
* ❌ NEVER generate functions with complexity > 7 - **BLOCKS validation**
* ✅ **PRIMARY FIX: Use Sets/Arrays for multiple OR comparisons** (reduces complexity 10+ → 3)
* ✅ Extract helper functions for nested logic blocks
* ✅ Use early returns instead of nested if-else
* **WARNING:** "Function has complexity X. Maximum allowed is 7."
* **CRITICAL:** Apps with complexity > 7 CANNOT pass `fdk validate`
REFACTORING PATTERN 1: Multiple OR comparisons → Sets (MOST COMMON)
// ❌ WRONG - complexity 12 (each || and === adds +1)
function matchesPriority(ticket, filter) {
const p = (ticket.priority || ticket.urgency || 0).toString();
if (filter.includes('high') && (p === '2' || p === '3' || p === 'high' || p === 'urgent')) return true;
return false;
}
// ✅ CORRECT - complexity 3 (Set.has() is single operation)
const HIGH_PRIORITIES = new Set(['2', '3', 'high', 'urgent']);
function matchesPriority(ticket, filter) {
const p = (ticket.priority || ticket.urgency || 0).toString();
if (filter.includes('high') && HIGH_PRIORITIES.has(p)) return true;
return false;
}
REFACTORING PATTERN 2: Extract helper functions
// ❌ WRONG - complexity > 7 (nested conditions)
exports.onConversationCreateHandler = async function(args) {
if (condition1) {
if (condition2) {
if (condition3) {
// deeply nested logic
}
}
}
};
// ✅ CORRECT - extract helpers
exports = {
onConversationCreateHandler: async function(args) {
const messageType = getMessageType(args);
return await handleByType(messageType, args);
}
};
// Helper functions AFTER exports
function getMessageType(args) { ... }
async function handleByType(type, args) { ... }
8. 🚨 Manifest-to-File Consistency (CRITICAL)
* **If manifest has`location` with `url: "index.html"` → `app/index.html` MUST exist**
* **If manifest has`location` with `icon: "styles/images/icon.svg"` → `app/styles/images/icon.svg` MUST exist**
* **If manifest has`functions` or `events` → `server/server.js` MUST exist**
* ❌ NEVER create manifest referencing files that don't exist
* ✅ ALWAYS create files BEFORE adding them to manifest
You are not a tutor. You are an enforcement layer.
Security is as critical as Platform 3.0 compliance. For detailed patterns and examples, see:
.cursor/rules/security.mdc - Security patterns, forbidden/safe code examples, checklists.cursor/rules/code-quality-patterns.mdc - Low-complexity helper patterns, lint fixes, security checks| Severity | Rule | Forbidden Pattern |
|---|---|---|
| 🔴 CRITICAL | No command injection | executeCommand(args), eval(args.script) |
| 🔴 CRITICAL | No code execution | new Function(args), exec(), spawn() |
| 🟠 HIGH | No logging secrets | console.log(args.iparams), console.log(args) |
args.iparams, no full args objectstextContent, sanitize before innerHTMLFull security patterns, code examples, and checklists →.cursor/rules/security.mdc
IF ANY SECURITY RULE IS VIOLATED → STOP → REGENERATE WITH SECURE PATTERNS
{
"platform-version": "3.0",
"modules": {
"common": {
"requests": { "apiName": {} },
"functions": { "functionName": {} }
},
"support_ticket": {
"location": {
"ticket_sidebar": {
"url": "index.html",
"icon": "styles/images/icon.svg"
}
}
}
},
"engines": {
"node": "18.20.8",
"fdk": "9.7.4"
}
}
🚨 CRITICAL: Manifestname Field - NEVER INCLUDE:
"name": "My App" inside manifest.json → PLATFORM ERRORname field is NOT allowed in Platform 3.0 manifest.jsonmust NOT have additional properties 'name' in manifest.json🚨 CRITICAL: Empty Block Rules - NEVER create empty blocks:
"functions": {} - INVALID - must have at least 1 function OR omit entirely"requests": {} - INVALID - must have at least 1 request OR omit entirely"events": {} - INVALID - must have at least 1 event OR omit entirely"functions" key at all"requests" key at all🚨 NEVER generate these Platform 2.x patterns - ZERO TOLERANCE:
Manifest Structure (Platform 2.x):
"platform-version": "2.3" or "2.2" or "2.1" → ✅ MUST be "3.0""product": { "freshdesk": {} } → ✅ MUST use "modules": { "common": {}, "support_ticket": {} }"whitelisted-domains": ["https://..."] → ✅ MUST use request templates in config/requests.jsonRequest API (Platform 2.x):
$request.post('https://api.example.com', options) → ✅ MUST use $request.invokeTemplate('templateName', {})$request.get('https://api.example.com', options) → ✅ MUST use $request.invokeTemplate('templateName', {})$request.put('https://api.example.com', options) → ✅ MUST use $request.invokeTemplate('templateName', {})$request.delete('https://api.example.com', options) → ✅ MUST use $request.invokeTemplate('templateName', {})OAuth Structure (Platform 2.x):
integrations wrapper → ✅ MUST have { "integrations": { "service": { ... } } }config/iparams.json → ✅ MUST be in oauth_iparams inside oauth_config.jsonOther Platform 3.0 Requirements:
<button>, <input>, <select>, <textarea> → ✅ Use Crayons componentsticket_sidebar in common) → ✅ Must be in product module$schedule.create()_args)IF ANY PLATFORM 2.X PATTERN IS GENERATED → IMMEDIATE REJECTION → REGENERATE WITH PLATFORM 3.0
Use this process for every app request so the right features are generated.
1. Clarifying the ask
ticket/requester for the action and loggedInUser for who is using the app (show "Logged in as …" or use agent context).2. Using docs and references
3. Design choices
ticket/requester; optionally show agent → loggedInUser) → call external API with that data in server → one SMI that invokes request template(s) and returns result.4. Implementation order
5. Example: "Get status" in ticket sidebar
client.data.get("ticket") for requester email (for presence) and client.data.get("loggedInUser") to show "Logged in as {email}" so both ticket and agent context are visible.CRITICAL: When to include frontend?
ALWAYS include frontend (Hybrid or Frontend-only) when:
Use serverless only when:
Serverless Use Cases (from Platform 3.0 docs):
Examples:
Default Rule: When in doubt, include frontend (Hybrid). Users almost always want to see what's happening.
CRITICAL: Decision Enforcement Rule
Decision Tree:
Does it need UI?
├─ YES → Does it need backend events/API calls?
│ ├─ YES → Hybrid (Frontend + Backend)
│ └─ NO → Frontend-only
└─ NO → Does it need backend events/API calls?
├─ YES → Serverless-only
└─ NO → Invalid (app needs at least one)
Template Selection:
Load the appropriate template from assets/templates/:
Frontend Only:
assets/templates/frontend-skeleton/app/, manifest.json, config/iparams.json, icon.svgServerless Only:
assets/templates/serverless-skeleton/server/server.js, manifest.json, config/iparams.jsonHybrid (Frontend + Backend):
assets/templates/hybrid-skeleton/app/, server/server.js, config/requests.json, config/iparams.jsonOAuth Integration (ONLY when required):
assets/templates/oauth-skeleton/app/, server/server.js, config/oauth_config.json, config/requests.json, config/iparams.jsonoauth_iparams (inside oauth_config.json), NOT in config/iparams.jsonreferences/api/oauth-docs.mdCRITICAL: Fix ALL errors - Platform errors AND Lint errors. ZERO TOLERANCE.
AFTER creating ALL app files, you MUST AUTOMATICALLY:
fdk validate in the app directory (DO NOT ask user to run it){{variable}} → <%= context.variable %>)icon.svg, iparams.json)/ → add / prefix)fdk validateOutput after successful validation:
✅ App generated successfully in <app-directory>/
Validation: 0 platform errors, 0 lint errors
Next steps:
1. cd <app-directory>
2. fdk run
3. Test in product with ?dev=true
DO NOT create validation reports or detailed summaries unless explicitly requested.
What to FIX (Platform Errors) - BLOCKING:
"name" field in manifest.json → REMOVE ITWhat to FIX (Lint Errors) - ALSO BLOCKING:
async keyword OR add actual await_args)CRITICAL RULES:
fdk validate manuallyfdk validate after each fix iterationReference: See .cursor/rules/validation-autofix.mdc for detailed autofix patterns.
Use OAuth ONLY when:
DO NOT use OAuth when:
Example Decisions:
Default Rule: If in doubt, use API key authentication in iparams. Only use OAuth if the service explicitly requires it.
For complete OAuth configuration with examples:
references/architecture/oauth-configuration-latest.mdreferences/api/oauth-docs.md🚨 MANDATORY OAuth Fields Checklist - ZERO TOLERANCE:
Every OAuth integration in oauth_config.json MUST have ALL of these fields:
| Field | Required | Location | Example |
|---|---|---|---|
display_name | ✅ YES | Integration root | "display_name": "GitHub" |
token_type | ✅ YES | Integration root | "token_type": "account" or "agent" |
client_id | ✅ YES | Integration root |
🚨 CRITICAL: Every field insideoauth_iparams MUST have description - This is frequently missed!
OAuth requires THREE files:
config/oauth_config.json - OAuth credentials in oauth_iparams
{
"integrations": {
"service_name": {
"display_name": "Service Name",
"client_id": "<%= oauth_iparams.client_id %>",
"client_secret": "<%= oauth_iparams.client_secret %>",
"authorize_url": "https://...",
"token_url": "https://...",
"token_type": "account",
"oauth_iparams": {
"client_id": {
"display_name": "Client ID",
"description": "Enter your OAuth App Client ID from the service developer portal",
"type": "text",
"required": true
},
"client_secret": {
"display_name": "Client Secret",
"description": "Enter your OAuth App Client Secret from the service developer portal",
"type": "text",
"required": true,
"secure": true
}
}
}
}
}
config/iparams.json - App-specific settings (NOT OAuth credentials)
CRITICAL OAuth Rules:
oauth_iparams (inside oauth_config.json)config/iparams.json<%= oauth_iparams.client_id %>, NEVER plain strings<%= access_token %> in requests, NEVER {{access_token}}"options": { "oauth": "integration_name" }display_name at integration root leveltoken_type ( or ) at integration root levelCRITICAL: IParams Rule
config/iparams.json with any parameters (not empty {}):
onAppInstall event in modules.common.eventsonAppInstallHandler in server/server.jsargs.iparams for validation/initialization🚨 CRITICAL: Secure IParams Rule - MANDATORY:
Any iparam containing sensitive data MUST have "secure": true
Keywords that REQUIRE"secure": true: api_key, token, secret, password, key, credential, auth
VALIDATION WARNING: iparam 'X' appears to be a secure param but it isn't marked as secure
CRITICAL: Cleanup Rule
onAppUninstall event in modules.common.eventsonAppUninstallHandler in server/server.js$schedule.create(), recurring syncs, webhook subscriptions, background jobsFrontend apps (frontend-skeleton, hybrid-skeleton, oauth-skeleton):
app/
├── index.html # MUST include Crayons CDN
├── scripts/app.js # Use IIFE pattern for async
└── styles/
├── style.css
└── images/
└── icon.svg # REQUIRED - FDK validation fails without it
config/
└── iparams.json # REQUIRED - even if empty {}
Serverless apps (serverless-skeleton):
server/
└── server.js # Use $request.invokeTemplate()
config/
└── iparams.json # REQUIRED - even if empty {}
Hybrid apps (hybrid-skeleton):
app/ + server/ + config/requests.json + config/iparams.json
OAuth apps (oauth-skeleton):
app/ + server/ + config/oauth_config.json + config/requests.json + config/iparams.json
Before presenting the app, validate against:
references/tests/golden.json - Should match correct patternsreferences/tests/refusal.json - Should NOT contain forbidden patternsreferences/tests/violations.json - Should avoid common mistakesreferences/architecture/modular_app_concepts.mdreferences/architecture/request-templates-latest.mdreferences/architecture/oauth-configuration-latest.mdreferences/architecture/*.md (59 files)references/api/server-method-invocation-docs.mdreferences/api/request-method-docs.mdreferences/api/oauth-docs.mdreferences/api/interface-method-docs.md, instance-method-docs.mdreferences/runtime/iparams-comparison.md (default vs custom)
references/runtime/installation-parameters-docs.mdCrayons component needed → references/ui/crayons-docs/{component}.md
Available components → 59 files: button, input, select, modal, spinner, toast, etc.
Always include Crayons CDN in HTML:
<script async type="module" src="https://cdn.jsdelivr.net/npm/@freshworks/crayons@v4/dist/crayons/crayons.esm.js"></script>
<script async nomodule src="https://cdn.jsdelivr.net/npm/@freshworks/crayons@v4/dist/crayons/crayons.js"></script>
references/errors/manifest-errors.mdreferences/errors/request-method-errors.mdreferences/errors/oauth-errors.mdreferences/errors/frontend-errors.mdreferences/errors/server-method-invocation-errors.mdreferences/errors/installation-parameters-errors.mdreferences/errors/keyvalue-store-errors.mdreferences/manifest/manifest-docs.mdreferences/errors/manifest-errors.mdreferences/cli/cli-docs.mdreferences/cli/fdk_create.mdapp/styles/images/icon.svg exists (FDK validation fails without it)manifest.json has engines block{})config/iparams.json (default - platform generates form) ORconfig/iparams.html + config/assets/iparams.js (custom Settings UI)"platform-version": "3.0""modules" structure (not "product")modules.common.requestsmodules.common.functionscommon)integrations wrapper if usedonAppInstall event handler declared in modules.common.events_args)await expressions$request.invokeTemplate(), never $request.post(){ success: false, error: message } on failure// loop through array)executeCommand, runScript, eval with user inputconsole.log(args.iparams) or full args objectstextContent or sanitize before innerHTML<fw-button> not <button><fw-input> not <input><fw-select> not <select><fw-textarea> not <textarea>references/ui/crayons-docs/ALWAYS create app in a new folder in the parent directory:
my-app/, zapier-sync-app/)Example:
# User workspace: /Users/dchatterjee/projects/
# Create app as: /Users/dchatterjee/projects/zapier-sync-app/
# NOT as: /Users/dchatterjee/projects/ (files scattered in root)
UNIVERSAL PRE-GENERATION CHECKLIST - MANDATORY:
"platform-version": "3.0", "modules" NOT "product", NO whitelisted-domainsapp/styles/images/icon.svg (NO EXCEPTIONS for frontend apps)config/iparams.json OR config/iparams.html (NOT BOTH)<%= variable %>, NEVER 🔒 SECURITY CHECKLIST - MANDATORY (See Security Enforcement Section):
executeCommand, runScript, eval, execconsole.log(args.iparams) or full args objectstextContent for dynamic data, sanitize before innerHTMLCRITICAL: #7 Async/Await Rule - ZERO TOLERANCE
async function MUST contain at least one await expressionawait is needed, REMOVE the async keywordAfter generation:
fdk validate to catch all errorsFor comprehensive error catalog with examples and fixes:
references/errors/error-catalog.mdreferences/errors/manifest-errors.md, references/errors/oauth-errors.md, references/errors/request-template-errors.mdTop 5 Most Common Errors:
app/styles/images/icon.svg - Frontend apps must have icon<%= context.variable %>await OR remove asyncexports blockBEFORE generating ANY app code, verify ALL of these:
app/styles/images/icon.svg - MUST EXIST - #1 validation failure causeapp/index.html - MUST include Crayons CDNapp/scripts/app.js - MUST use IIFE patternapp/styles/style.css - MUST existmanifest.json - MUST be Platform 3.0 structureconfig/iparams.json - MUST exist (can be empty {})/ - MUST begin with forward slash<%= context.variable %> - NEVER {{variable}}<%= iparam.name %> - For app-specific iparams<%= access_token %> - For OAuth authorizationmodules.common.requestsoauth_iparams in oauth_config.json - NOT in regular iparams.json<%= oauth_iparams.client_id %> - Correct syntaxoptions.oauth in request templates - MUST be presentintegrations wrapper - Platform 3.0 requirementasync keyword_args)executeCommand(args), eval(args.script)console.log(args.iparams) or console.log(args)textContent or sanitize before innerHTMLe.message only, not full error objectsmodules.common.functionsfull_page_app → MUST be in modules.common.locationcti_global_sidebar → MUST be in modules.common.locationticket_sidebar → MUST be in modules.support_ticket.location (NOT common)contact_sidebar → be in (NOT common)<button>, <input>, etc.fwClick, fwInput events - Not click, inputintegrations property ✅fdk validate to verifyAutofix Process:
fdk validate to identify JSON errorsfdk validate until it passesReference: See .cursor/rules/validation-autofix.mdc for detailed autofix patterns.
IF ANY ITEM FAILS → STOP AND FIX BEFORE PROCEEDING
CRITICAL: Fix ALL errors - Platform errors AND Lint errors. ZERO TOLERANCE.
After creating ALL app files, you MUST AUTOMATICALLY:
fdk validate - AUTOMATICALLY run validation (DO NOT ask user){{variable}} → <%= variable %>)/ prefix)async if no await, remove unused paramsfdk validateWhat to FIX (Platform Errors) - BLOCKING:
"name" field in manifest.json → REMOVE ITWhat to FIX (Lint Errors) - ALSO BLOCKING:
async OR add awaitCRITICAL: You MUST fix ALL errors (platform AND lint). Keep iterating (up to 6 times) until ZERO errors. An app with ANY errors is NOT complete.
Reference: See validation-autofix.mdc for detailed autofix patterns and examples.
Error: "Unexpected token { in JSON"
Example Fix (requests.json):
// WRONG - Multiple top-level objects
{ "request1": { ... } }
{ "request2": { ... } }
// CORRECT - Single object
{
"request1": { ... },
"request2": { ... }
}
Example Fix (iparams.json):
// WRONG - Multiple top-level objects
{ "param1": { ... } }
{ "param2": { ... } }
// CORRECT - Single object
{
"param1": { ... },
"param2": { ... }
}
🚨 ZERO TOLERANCE: An app is NEVER complete unless ALL gates pass.
manifest.json exists - APP CANNOT EXIST WITHOUT THISconfig/iparams.json exists (can be empty {})app/index.html, app/scripts/app.js, app/styles/images/icon.svgserver/server.jslocation.*.url: "index.html" → app/index.html MUST existlocation.*.icon: "styles/images/icon.svg" → app/styles/images/icon.svg MUST existmodules.*.events exists → server/server.js MUST exist with handlersmodules.common.functions exists → server/server.js MUST exist with functions"platform-version": "3.0"functions: {}, requests: {}, events: {})display_nametoken_typeoauth_iparam field has description_fdk validate returns 0 platform errorsfdk validate returns 0 lint errorsIF ANY GATE FAILS:
After successfully generating an app, provide a concise summary:
✅ App generated successfully in <app-directory>/
Validation: [0 platform errors, 0 lint errors]
Next steps:
1. cd <app-directory>
2. fdk run
3. Test in Freshworks product with ?dev=true
DO NOT automatically generate:
Only generate these when user explicitly requests:
Keep post-generation output minimal and focused on immediate next steps.
manifest.json - App manifestserver/server.js - Server code (if serverless/hybrid)app/ files - Frontend code (if frontend/hybrid)config/ files - Configuration (iparams, requests, oauth)README.md - Basic installation and usage guide.validation-report.md - Detailed validation reportAPPS-SUMMARY.md - Multi-app comparisonARCHITECTURE.md - Technical architecture docsCHANGELOG.md - Version history.gitignore - Git ignore filepackage.json - NPM package file (not needed for FDK apps)User must explicitly say:
Default behavior: Create only mandatory files + basic README.md
Install from this marketplace using the Agent Skills standard:
npx @anthropic-ai/add-skill https://github.com/freshworks-developers/freshworks-platform3/tree/main/skills/app-dev
npx skills add https://github.com/freshworks-developers/freshworks-platform3 --skill freshworks-app-dev-skill
# Install a full plugin
claude plugin install <plugin-path>
# Or add individual skills
npx @anthropic-ai/add-skill https://github.com/freshworks-developers/freshworks-platform3/tree/main/skills/app-dev
Use these references to validate generated apps:
references/tests/golden.json - 4 test cases:
Usage: Generated apps should match these structural patterns.
references/tests/refusal.json - 8 test cases:
whitelisted-domains → Reject$request.post() → Rejectengines → Rejectintegrations → Reject🔒 Security Refusal Tests (see .cursor/rules/security.mdc for details): 9. Command injection patterns → Reject 10. Code execution patterns → Reject 11. Credential logging → Reject 12. XSS patterns → Reject 13. Secrets in notes → Reject
Usage: Never generate these patterns.
references/tests/violations.json - 10 test cases:
🔒 Security Violation Tests (see .cursor/rules/security.mdc): 11-15. Input validation, logging, XSS, sensitive data violations
Usage: Check generated code against these violations.
Freshdesk Modules:
support_ticket - Ticket managementsupport_contact - Contact managementsupport_company - Company managementsupport_agent - Agent managementsupport_email - Email managementsupport_portal - Portal managementFreshservice Modules:
service_ticket - Service ticket managementservice_asset - Asset managementservice_change - Change managementservice_user - User/Requester managementFreshsales Modules:
deal - Deal managementcontact - Contact managementaccount (or sales_account) - Account managementlead - Lead managementappointment - Appointment managementtask - Task managementproduct - Product managementcpq_document - CPQ document managementphone - Phone managementFreshcaller Modules:
call - Call managementcaller_agent - Agent managementnotification - Notification managementFreshchat Modules:
chat_conversation - Conversation managementchat_user - User managementCommon Locations (configured at modules.common.location):
full_page_app - Full page applicationcti_global_sidebar - CTI global sidebar (Freshdesk/Freshservice only)Freshdesk support_ticket Locations (configured at modules.support_ticket.location):
ticket_sidebar - Ticket sidebarticket_requester_info - Requester info sectionticket_top_navigation - Top navigation barticket_background - Background apptime_entry_background - Time entry backgroundticket_attachment - Ticket attachment sectionticket_conversation_editor - Conversation editornew_ticket_requester_info - New ticket requester infonew_ticket_background - New ticket backgroundFreshservice service_ticket Locations (configured at modules.service_ticket.location):
ticket_sidebar - Ticket sidebarticket_requester_info - Requester info sectionticket_conversation_editor - Conversation editorticket_top_navigation - Top navigation barticket_background - Background appnew_ticket_background - New ticket backgroundnew_ticket_sidebar - New ticket sidebarnew_ticket_description_editor - New ticket description editorFreshservice service_asset Locations (configured at modules.service_asset.location):
asset_top_navigation - Asset top navigationasset_sidebar - Asset sidebarFreshservice service_change Locations (configured at modules.service_change.location):
change_sidebar - Change sidebarLocation Placement Rules:
full_page_app, cti_global_sidebar → modules.common.locationmodules.<product_module>.location| User Says | Module Name | Common Locations |
|---|---|---|
| "Freshdesk ticket sidebar" | support_ticket | ticket_sidebar, ticket_background |
| "Freshdesk contact" | support_contact | Contact-specific locations |
| "Freshdesk company" | support_company | Company-specific locations |
| "Freshservice ticket" | service_ticket |
console.log only in server/server.js, not frontendFor complete event list by product:
references/events/event-reference.mdKey events:
onAppInstall (MUST include if app uses iparams)onAppUninstall (MUST include if app has scheduled events/webhooks)onTicketCreate, onTicketUpdate (in product modules)$schedule.create() - NOT declared in manifestFor detailed request template syntax and OAuth configuration:
references/architecture/request-templates-latest.mdreferences/architecture/oauth-configuration-latest.mdreferences/api/request-method-docs.mdQuick Rules:
/<%= context.variable %> for iparams<%= access_token %> for OAuth"options": { "oauth": "integration_name" }For Jobs documentation:
references/runtime/jobs-docs.mdQuick pattern:
modules.common.jobs.jobNameclient.jobs.invoke("jobName", "tag", {data})exports.jobName = async function(args) { ... }This skill provides:
When uncertain about any Platform 3.0 behavior, load the relevant reference file from references/ before proceeding.
Weekly Installs
56
Repository
GitHub Stars
5
First Seen
Feb 25, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
opencode56
codex56
gemini-cli55
github-copilot55
amp55
kimi-cli55
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
125,600 周安装
$request.invokeTemplate()integrations wrapper - MUST have { "integrations": { ... } }app/styles/images/icon.svg - NO EXCEPTIONS| 🟡 MEDIUM | No XSS | innerHTML = userData without sanitization |
| 🟡 MEDIUM | No secrets in notes | Passwords/tokens in ticket notes |
integrations wrapper, wrong oauth_iparams location)fdk validate"<%= oauth_iparams.client_id %>" |
client_secret | ✅ YES | Integration root | "<%= oauth_iparams.client_secret %>" |
authorize_url | ✅ YES | Integration root | "https://..." |
token_url | ✅ YES | Integration root | "https://..." |
description | ✅ YES | Each oauth_iparam | "description": "Enter your Client ID" |
{ "sheet_id": { "display_name": "Sheet ID", "type": "text", "required": true } }
config/requests.json - API calls with <%= access_token %> and options.oauth
{
"apiCall": {
"schema": {
"method": "GET",
"host": "api.example.com",
"path": "/data",
"headers": { "Authorization": "Bearer <%= access_token %>" }
},
"options": { "oauth": "service_name" }
}
}
"account""agent"description field for EVERY oauth_iparamconfig/iparams.jsontoken_type - causes validation errordisplay_name - causes validation errordescription in oauth_iparams - causes validation error// ❌ WRONG - Missing secure flag for sensitive data { "api_key": { "display_name": "API Key", "type": "text", "required": true } }
// ✅ CORRECT - Secure flag added { "api_key": { "display_name": "API Key", "type": "text", "required": true, "secure": true }, "webhook_token": { "display_name": "Webhook Token", "type": "text", "required": true, "secure": true } }
references/runtime/custom-iparams-docs.mdreferences/runtime/keyvalue-store-docs.md, object-store-docs.mdreferences/runtime/jobs-docs.mdonAppUninstall event handler declared in modules.common.events{{variable}}/oauth_iparams in oauth_config.json with integrations wrapperasync, MUST have await - NO EXCEPTIONS - REMOVE async IF NO awaitfull_page_app → modules.common.location, product locations → product module$request.invokeTemplate(), NEVER $request.post()/.get()/.put()/.delete()modules.support_contact.locationasset_sidebar → MUST be in modules.service_asset.location (NOT common)full_page_app in product modules{}whitelisted-domains, no product$schedule.create()fdk validateconfig/requests.jsonmodules.common.requeststicket_sidebar, ticket_top_navigation |
| "Freshservice asset" | service_asset | asset_sidebar, asset_top_navigation |
| "Freshservice change" | service_change | change_sidebar |
| "Freshsales deal" | deal | deal_sidebar, deal_entity_menu |
| "Freshsales contact" | contact | contact_sidebar |
| "Freshsales account" | sales_account | Account-specific locations |