@cloudstreamsoftware/claude-tools 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/README.md +152 -37
  2. package/agents/INDEX.md +183 -0
  3. package/agents/architect.md +247 -0
  4. package/agents/build-error-resolver.md +555 -0
  5. package/agents/catalyst-deployer.md +132 -0
  6. package/agents/code-reviewer.md +121 -0
  7. package/agents/compliance-auditor.md +148 -0
  8. package/agents/creator-architect.md +395 -0
  9. package/agents/deluge-reviewer.md +98 -0
  10. package/agents/doc-updater.md +471 -0
  11. package/agents/e2e-runner.md +711 -0
  12. package/agents/planner.md +122 -0
  13. package/agents/refactor-cleaner.md +309 -0
  14. package/agents/security-reviewer.md +582 -0
  15. package/agents/tdd-guide.md +302 -0
  16. package/config/versions.json +63 -0
  17. package/dist/hooks/hooks.json +209 -0
  18. package/dist/index.js +47 -0
  19. package/dist/lib/asset-value.js +609 -0
  20. package/dist/lib/client-manager.js +300 -0
  21. package/dist/lib/command-matcher.js +242 -0
  22. package/dist/lib/cross-session-patterns.js +754 -0
  23. package/dist/lib/intent-classifier.js +1075 -0
  24. package/dist/lib/package-manager.js +374 -0
  25. package/dist/lib/recommendation-engine.js +597 -0
  26. package/dist/lib/session-memory.js +489 -0
  27. package/dist/lib/skill-effectiveness.js +486 -0
  28. package/dist/lib/skill-matcher.js +595 -0
  29. package/dist/lib/tutorial-metrics.js +242 -0
  30. package/dist/lib/tutorial-progress.js +209 -0
  31. package/dist/lib/tutorial-renderer.js +431 -0
  32. package/dist/lib/utils.js +380 -0
  33. package/dist/lib/verify-formatter.js +143 -0
  34. package/dist/lib/workflow-state.js +249 -0
  35. package/hooks/hooks.json +209 -0
  36. package/package.json +5 -1
  37. package/scripts/aggregate-sessions.js +290 -0
  38. package/scripts/branch-name-validator.js +291 -0
  39. package/scripts/build.js +101 -0
  40. package/scripts/commands/client-switch.js +231 -0
  41. package/scripts/deprecate-skill.js +610 -0
  42. package/scripts/diagnose.js +324 -0
  43. package/scripts/doc-freshness.js +168 -0
  44. package/scripts/generate-weekly-digest.js +393 -0
  45. package/scripts/health-check.js +270 -0
  46. package/scripts/hooks/credential-check.js +101 -0
  47. package/scripts/hooks/evaluate-session.js +81 -0
  48. package/scripts/hooks/pre-compact.js +66 -0
  49. package/scripts/hooks/prompt-analyzer.js +276 -0
  50. package/scripts/hooks/prompt-router.js +422 -0
  51. package/scripts/hooks/quality-gate-enforcer.js +371 -0
  52. package/scripts/hooks/session-end.js +156 -0
  53. package/scripts/hooks/session-start.js +195 -0
  54. package/scripts/hooks/skill-injector.js +333 -0
  55. package/scripts/hooks/suggest-compact.js +58 -0
  56. package/scripts/lib/asset-value.js +609 -0
  57. package/scripts/lib/client-manager.js +300 -0
  58. package/scripts/lib/command-matcher.js +242 -0
  59. package/scripts/lib/cross-session-patterns.js +754 -0
  60. package/scripts/lib/intent-classifier.js +1075 -0
  61. package/scripts/lib/package-manager.js +374 -0
  62. package/scripts/lib/recommendation-engine.js +597 -0
  63. package/scripts/lib/session-memory.js +489 -0
  64. package/scripts/lib/skill-effectiveness.js +486 -0
  65. package/scripts/lib/skill-matcher.js +595 -0
  66. package/scripts/lib/tutorial-metrics.js +242 -0
  67. package/scripts/lib/tutorial-progress.js +209 -0
  68. package/scripts/lib/tutorial-renderer.js +431 -0
  69. package/scripts/lib/utils.js +380 -0
  70. package/scripts/lib/verify-formatter.js +143 -0
  71. package/scripts/lib/workflow-state.js +249 -0
  72. package/scripts/onboard.js +363 -0
  73. package/scripts/quarterly-report.js +692 -0
  74. package/scripts/setup-package-manager.js +204 -0
  75. package/scripts/sync-upstream.js +391 -0
  76. package/scripts/test.js +108 -0
  77. package/scripts/tutorial-runner.js +351 -0
  78. package/scripts/validate-all.js +201 -0
  79. package/scripts/verifiers/agents.js +245 -0
  80. package/scripts/verifiers/config.js +186 -0
  81. package/scripts/verifiers/environment.js +123 -0
  82. package/scripts/verifiers/hooks.js +188 -0
  83. package/scripts/verifiers/index.js +38 -0
  84. package/scripts/verifiers/persistence.js +140 -0
  85. package/scripts/verifiers/plugin.js +215 -0
  86. package/scripts/verifiers/skills.js +209 -0
  87. package/scripts/verify-setup.js +164 -0
  88. package/skills/INDEX.md +157 -0
  89. package/skills/backend-patterns/SKILL.md +586 -0
  90. package/skills/backend-patterns/catalyst-patterns.md +128 -0
  91. package/skills/bigquery-patterns/SKILL.md +27 -0
  92. package/skills/bigquery-patterns/performance-optimization.md +518 -0
  93. package/skills/bigquery-patterns/query-patterns.md +372 -0
  94. package/skills/bigquery-patterns/schema-design.md +78 -0
  95. package/skills/cloudstream-project-template/SKILL.md +20 -0
  96. package/skills/cloudstream-project-template/structure.md +65 -0
  97. package/skills/coding-standards/SKILL.md +524 -0
  98. package/skills/coding-standards/deluge-standards.md +83 -0
  99. package/skills/compliance-patterns/SKILL.md +28 -0
  100. package/skills/compliance-patterns/hipaa/audit-requirements.md +251 -0
  101. package/skills/compliance-patterns/hipaa/baa-process.md +298 -0
  102. package/skills/compliance-patterns/hipaa/data-archival-strategy.md +387 -0
  103. package/skills/compliance-patterns/hipaa/phi-handling.md +52 -0
  104. package/skills/compliance-patterns/pci-dss/saq-a-requirements.md +307 -0
  105. package/skills/compliance-patterns/pci-dss/tokenization-patterns.md +382 -0
  106. package/skills/compliance-patterns/pci-dss/zoho-checkout-patterns.md +56 -0
  107. package/skills/compliance-patterns/soc2/access-controls.md +344 -0
  108. package/skills/compliance-patterns/soc2/audit-logging.md +458 -0
  109. package/skills/compliance-patterns/soc2/change-management.md +403 -0
  110. package/skills/compliance-patterns/soc2/deluge-execution-logging.md +407 -0
  111. package/skills/consultancy-workflows/SKILL.md +19 -0
  112. package/skills/consultancy-workflows/client-isolation.md +21 -0
  113. package/skills/consultancy-workflows/documentation-automation.md +454 -0
  114. package/skills/consultancy-workflows/handoff-procedures.md +257 -0
  115. package/skills/consultancy-workflows/knowledge-capture.md +513 -0
  116. package/skills/consultancy-workflows/time-tracking.md +26 -0
  117. package/skills/continuous-learning/SKILL.md +84 -0
  118. package/skills/continuous-learning/config.json +18 -0
  119. package/skills/continuous-learning/evaluate-session.sh +60 -0
  120. package/skills/continuous-learning-v2/SKILL.md +126 -0
  121. package/skills/continuous-learning-v2/config.json +61 -0
  122. package/skills/frontend-patterns/SKILL.md +635 -0
  123. package/skills/frontend-patterns/zoho-widget-patterns.md +103 -0
  124. package/skills/gcp-data-engineering/SKILL.md +36 -0
  125. package/skills/gcp-data-engineering/bigquery/performance-optimization.md +337 -0
  126. package/skills/gcp-data-engineering/dataflow/error-handling.md +496 -0
  127. package/skills/gcp-data-engineering/dataflow/pipeline-patterns.md +444 -0
  128. package/skills/gcp-data-engineering/dbt/model-organization.md +63 -0
  129. package/skills/gcp-data-engineering/dbt/testing-patterns.md +503 -0
  130. package/skills/gcp-data-engineering/medallion-architecture/bronze-layer.md +60 -0
  131. package/skills/gcp-data-engineering/medallion-architecture/gold-layer.md +311 -0
  132. package/skills/gcp-data-engineering/medallion-architecture/layer-transitions.md +517 -0
  133. package/skills/gcp-data-engineering/medallion-architecture/silver-layer.md +305 -0
  134. package/skills/gcp-data-engineering/zoho-to-gcp/data-extraction.md +543 -0
  135. package/skills/gcp-data-engineering/zoho-to-gcp/real-time-vs-batch.md +337 -0
  136. package/skills/security-review/SKILL.md +498 -0
  137. package/skills/security-review/compliance-checklist.md +53 -0
  138. package/skills/strategic-compact/SKILL.md +67 -0
  139. package/skills/tdd-workflow/SKILL.md +413 -0
  140. package/skills/tdd-workflow/zoho-testing.md +124 -0
  141. package/skills/tutorial/SKILL.md +249 -0
  142. package/skills/tutorial/docs/ACCESSIBILITY.md +169 -0
  143. package/skills/tutorial/lessons/00-philosophy-and-workflow.md +198 -0
  144. package/skills/tutorial/lessons/01-basics.md +81 -0
  145. package/skills/tutorial/lessons/02-training.md +86 -0
  146. package/skills/tutorial/lessons/03-commands.md +109 -0
  147. package/skills/tutorial/lessons/04-workflows.md +115 -0
  148. package/skills/tutorial/lessons/05-compliance.md +116 -0
  149. package/skills/tutorial/lessons/06-zoho.md +121 -0
  150. package/skills/tutorial/lessons/07-hooks-system.md +277 -0
  151. package/skills/tutorial/lessons/08-mcp-servers.md +316 -0
  152. package/skills/tutorial/lessons/09-client-management.md +215 -0
  153. package/skills/tutorial/lessons/10-testing-e2e.md +260 -0
  154. package/skills/tutorial/lessons/11-skills-deep-dive.md +272 -0
  155. package/skills/tutorial/lessons/12-rules-system.md +326 -0
  156. package/skills/tutorial/lessons/13-golden-standard-graduation.md +213 -0
  157. package/skills/tutorial/lessons/14-fork-setup-and-sync.md +312 -0
  158. package/skills/tutorial/lessons/15-living-examples-system.md +221 -0
  159. package/skills/tutorial/tracks/accelerated/README.md +134 -0
  160. package/skills/tutorial/tracks/accelerated/assessment/checkpoint-1.md +161 -0
  161. package/skills/tutorial/tracks/accelerated/assessment/checkpoint-2.md +175 -0
  162. package/skills/tutorial/tracks/accelerated/day-1-core-concepts.md +234 -0
  163. package/skills/tutorial/tracks/accelerated/day-2-essential-commands.md +270 -0
  164. package/skills/tutorial/tracks/accelerated/day-3-workflow-mastery.md +305 -0
  165. package/skills/tutorial/tracks/accelerated/day-4-compliance-zoho.md +304 -0
  166. package/skills/tutorial/tracks/accelerated/day-5-hooks-skills.md +344 -0
  167. package/skills/tutorial/tracks/accelerated/day-6-client-testing.md +386 -0
  168. package/skills/tutorial/tracks/accelerated/day-7-graduation.md +369 -0
  169. package/skills/zoho-patterns/CHANGELOG.md +108 -0
  170. package/skills/zoho-patterns/SKILL.md +446 -0
  171. package/skills/zoho-patterns/analytics/dashboard-patterns.md +352 -0
  172. package/skills/zoho-patterns/analytics/zoho-to-bigquery-pipeline.md +427 -0
  173. package/skills/zoho-patterns/catalyst/appsail-deployment.md +349 -0
  174. package/skills/zoho-patterns/catalyst/context-close-patterns.md +354 -0
  175. package/skills/zoho-patterns/catalyst/cron-batch-processing.md +374 -0
  176. package/skills/zoho-patterns/catalyst/function-patterns.md +439 -0
  177. package/skills/zoho-patterns/creator/form-design.md +304 -0
  178. package/skills/zoho-patterns/creator/publish-api-patterns.md +313 -0
  179. package/skills/zoho-patterns/creator/widget-integration.md +306 -0
  180. package/skills/zoho-patterns/creator/workflow-automation.md +253 -0
  181. package/skills/zoho-patterns/deluge/api-patterns.md +468 -0
  182. package/skills/zoho-patterns/deluge/batch-processing.md +403 -0
  183. package/skills/zoho-patterns/deluge/cross-app-integration.md +356 -0
  184. package/skills/zoho-patterns/deluge/error-handling.md +423 -0
  185. package/skills/zoho-patterns/deluge/syntax-reference.md +65 -0
  186. package/skills/zoho-patterns/integration/cors-proxy-architecture.md +426 -0
  187. package/skills/zoho-patterns/integration/crm-books-native-sync.md +277 -0
  188. package/skills/zoho-patterns/integration/oauth-token-management.md +461 -0
  189. package/skills/zoho-patterns/integration/zoho-flow-patterns.md +334 -0
@@ -0,0 +1,426 @@
1
+ # CORS Proxy Architecture
2
+
3
+ ## Why Widgets Can't Call External APIs Directly
4
+
5
+ > **PROBLEM:** Creator widgets run inside an iframe on `*.zoho.com`. Browser CORS policy blocks cross-origin requests to external APIs. Even if the external API has CORS headers, they won't include Zoho's origin.
6
+
7
+ ```
8
+ Widget (iframe on zoho.com)
9
+ ↓ fetch("https://api.stripe.com/...")
10
+ ✗ BLOCKED by browser CORS policy
11
+ ```
12
+
13
+ ## The Solution: Catalyst Proxy
14
+
15
+ ```
16
+ Widget (iframe)
17
+ ↓ ZOHO.CREATOR.DATA.invokeFunction() [Same origin - no CORS]
18
+
19
+ Catalyst Function (server-side)
20
+ ↓ axios/fetch to external API [Server-side - no CORS]
21
+
22
+ External API
23
+ ↓ Response
24
+
25
+ Catalyst Function
26
+ ↓ Transform + return
27
+
28
+ Widget (receives data)
29
+ ```
30
+
31
+ ## Architecture Overview
32
+
33
+ ```
34
+ ┌─────────────────────────────────────────────────────────┐
35
+ │ Creator Widget (Browser) │
36
+ │ │
37
+ │ ┌─────────────────────────────────────────────────┐ │
38
+ │ │ ZOHO.CREATOR.DATA.invokeFunction({ │ │
39
+ │ │ functionName: "proxy_api", │ │
40
+ │ │ data: { endpoint, method, body, headers } │ │
41
+ │ │ }) │ │
42
+ │ └─────────────────┬───────────────────────────────┘ │
43
+ │ │ (Same Zoho domain = OK) │
44
+ └─────────────────────┼───────────────────────────────────┘
45
+
46
+ ┌─────────────────────▼───────────────────────────────────┐
47
+ │ Catalyst I/O Function (Server) │
48
+ │ │
49
+ │ - Validates request │
50
+ │ - Adds auth credentials (from env vars) │
51
+ │ - Calls external API │
52
+ │ - Transforms response │
53
+ │ - Returns to widget │
54
+ │ │
55
+ │ ⚠️ 30-second timeout for I/O functions │
56
+ │ ⚠️ context.close() MANDATORY │
57
+ └─────────────────────┬───────────────────────────────────┘
58
+
59
+ ┌─────────────────────▼───────────────────────────────────┐
60
+ │ External API (Stripe, Twilio, Google, etc.) │
61
+ └─────────────────────────────────────────────────────────┘
62
+ ```
63
+
64
+ ## Catalyst Proxy Function Implementation
65
+
66
+ ### Basic Proxy (Node.js)
67
+
68
+ ```javascript
69
+ const catalyst = require("zcatalyst-sdk-node");
70
+ const axios = require("axios");
71
+
72
+ module.exports = async (req, res, context) => {
73
+ const app = catalyst.initialize(context);
74
+
75
+ try {
76
+ // Validate request
77
+ const { endpoint, method, body, headers: customHeaders } = req.body;
78
+
79
+ if (!endpoint) {
80
+ res.status(400).json({ error: "endpoint is required" });
81
+ return;
82
+ }
83
+
84
+ // Whitelist allowed endpoints (SECURITY: prevent SSRF)
85
+ const allowedDomains = [
86
+ "api.stripe.com",
87
+ "api.twilio.com",
88
+ "maps.googleapis.com"
89
+ ];
90
+
91
+ const url = new URL(endpoint);
92
+ if (!allowedDomains.includes(url.hostname)) {
93
+ res.status(403).json({ error: "Domain not allowed: " + url.hostname });
94
+ return;
95
+ }
96
+
97
+ // Build request
98
+ const axiosConfig = {
99
+ method: method || "GET",
100
+ url: endpoint,
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ ...customHeaders
104
+ },
105
+ timeout: 25000 // 25s (buffer for 30s Catalyst timeout)
106
+ };
107
+
108
+ if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
109
+ axiosConfig.data = typeof body === "string" ? JSON.parse(body) : body;
110
+ }
111
+
112
+ // Add server-side credentials
113
+ axiosConfig.headers["Authorization"] = getAuthForDomain(url.hostname);
114
+
115
+ // Execute request
116
+ const response = await axios(axiosConfig);
117
+
118
+ res.status(200).json({
119
+ status: response.status,
120
+ data: response.data
121
+ });
122
+
123
+ } catch (error) {
124
+ if (error.response) {
125
+ // External API returned an error
126
+ res.status(200).json({
127
+ status: error.response.status,
128
+ error: error.response.data
129
+ });
130
+ } else if (error.code === "ECONNABORTED") {
131
+ res.status(504).json({ error: "External API timeout" });
132
+ } else {
133
+ res.status(500).json({ error: error.message });
134
+ }
135
+ } finally {
136
+ context.close();
137
+ }
138
+ };
139
+
140
+ function getAuthForDomain(domain) {
141
+ const authMap = {
142
+ "api.stripe.com": `Bearer ${process.env.STRIPE_SECRET_KEY}`,
143
+ "api.twilio.com": `Basic ${Buffer.from(process.env.TWILIO_SID + ":" + process.env.TWILIO_TOKEN).toString("base64")}`,
144
+ "maps.googleapis.com": "" // Uses query param key instead
145
+ };
146
+ return authMap[domain] || "";
147
+ }
148
+ ```
149
+
150
+ ### Widget-Side Code
151
+
152
+ ```javascript
153
+ // Generic proxy caller for widgets
154
+ async function proxyApiCall(endpoint, method = "GET", body = null) {
155
+ try {
156
+ const response = await ZOHO.CREATOR.DATA.invokeFunction({
157
+ appName: "my-app",
158
+ functionName: "proxy_api",
159
+ data: {
160
+ endpoint: endpoint,
161
+ method: method,
162
+ body: body ? JSON.stringify(body) : null
163
+ }
164
+ });
165
+
166
+ const result = JSON.parse(response.data);
167
+
168
+ if (result.status >= 200 && result.status < 300) {
169
+ return { success: true, data: result.data };
170
+ } else {
171
+ return { success: false, error: result.error || result.data };
172
+ }
173
+ } catch (error) {
174
+ console.error("Proxy call failed:", error);
175
+ return { success: false, error: error.message };
176
+ }
177
+ }
178
+
179
+ // Usage examples
180
+ async function getStripeCustomer(customerId) {
181
+ return await proxyApiCall(
182
+ `https://api.stripe.com/v1/customers/${customerId}`,
183
+ "GET"
184
+ );
185
+ }
186
+
187
+ async function sendTwilioSMS(to, message) {
188
+ return await proxyApiCall(
189
+ `https://api.twilio.com/2010-04-01/Accounts/${ACCOUNT_SID}/Messages.json`,
190
+ "POST",
191
+ { To: to, From: TWILIO_NUMBER, Body: message }
192
+ );
193
+ }
194
+ ```
195
+
196
+ ## Authentication Passthrough
197
+
198
+ ### Pattern 1: Server-Side Credentials (Recommended)
199
+
200
+ ```javascript
201
+ // Credentials stored in Catalyst environment variables
202
+ // Widget NEVER sees API keys
203
+ function getAuthForDomain(domain) {
204
+ switch (domain) {
205
+ case "api.stripe.com":
206
+ return `Bearer ${process.env.STRIPE_SECRET_KEY}`;
207
+ case "api.openai.com":
208
+ return `Bearer ${process.env.OPENAI_API_KEY}`;
209
+ default:
210
+ return "";
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### Pattern 2: User-Specific Tokens (OAuth)
216
+
217
+ ```javascript
218
+ // For APIs where each user has their own token
219
+ // Store user tokens in Catalyst Data Store
220
+ async function getUserToken(app, userId, service) {
221
+ const result = await app.zcql().executeZCQLQuery(
222
+ `SELECT access_token, refresh_token, expires_at FROM UserTokens WHERE user_id = '${userId}' AND service = '${service}'`
223
+ );
224
+
225
+ if (result.length === 0) {
226
+ throw new Error("No token found. User must authenticate first.");
227
+ }
228
+
229
+ const tokenData = result[0].UserTokens;
230
+
231
+ // Check if token expired
232
+ if (new Date(tokenData.expires_at) < new Date()) {
233
+ // Refresh token
234
+ return await refreshUserToken(app, userId, service, tokenData.refresh_token);
235
+ }
236
+
237
+ return tokenData.access_token;
238
+ }
239
+ ```
240
+
241
+ ## Response Transformation
242
+
243
+ Transform external API responses to match widget expectations:
244
+
245
+ ```javascript
246
+ // Transform Stripe response to simplified format for widget
247
+ function transformStripeCustomer(stripeData) {
248
+ return {
249
+ id: stripeData.id,
250
+ name: stripeData.name,
251
+ email: stripeData.email,
252
+ balance: stripeData.balance / 100, // Cents to dollars
253
+ subscriptions: (stripeData.subscriptions?.data || []).map(sub => ({
254
+ id: sub.id,
255
+ plan: sub.plan.nickname,
256
+ status: sub.status,
257
+ amount: sub.plan.amount / 100,
258
+ interval: sub.plan.interval
259
+ })),
260
+ created: new Date(stripeData.created * 1000).toISOString()
261
+ };
262
+ }
263
+ ```
264
+
265
+ ## Caching Layer
266
+
267
+ Reduce external API calls with caching:
268
+
269
+ ```javascript
270
+ const NodeCache = require("node-cache");
271
+ const cache = new NodeCache({ stdTTL: 300 }); // 5-minute default TTL
272
+
273
+ async function cachedProxyCall(endpoint, method, body, cacheTTL = 300) {
274
+ // Only cache GET requests
275
+ if (method === "GET") {
276
+ const cacheKey = `proxy:${endpoint}`;
277
+ const cached = cache.get(cacheKey);
278
+
279
+ if (cached) {
280
+ console.log("Cache hit:", endpoint);
281
+ return cached;
282
+ }
283
+ }
284
+
285
+ // Make actual API call
286
+ const response = await axios({ method, url: endpoint, data: body });
287
+
288
+ // Cache successful GET responses
289
+ if (method === "GET" && response.status === 200) {
290
+ cache.set(`proxy:${endpoint}`, response.data, cacheTTL);
291
+ }
292
+
293
+ return response.data;
294
+ }
295
+ ```
296
+
297
+ > **NOTE:** In-memory cache resets on function cold starts. For persistent caching, use Catalyst Data Store or Cache service.
298
+
299
+ ## Rate Limiting at Proxy Level
300
+
301
+ ```javascript
302
+ // Simple rate limiter per client/endpoint
303
+ const rateLimits = {};
304
+ const WINDOW_MS = 60000; // 1 minute window
305
+ const MAX_REQUESTS = 30; // 30 requests per minute per endpoint
306
+
307
+ function checkRateLimit(endpoint, userId) {
308
+ const key = `${userId}:${endpoint}`;
309
+ const now = Date.now();
310
+
311
+ if (!rateLimits[key]) {
312
+ rateLimits[key] = { count: 0, windowStart: now };
313
+ }
314
+
315
+ const limit = rateLimits[key];
316
+
317
+ // Reset window if expired
318
+ if (now - limit.windowStart > WINDOW_MS) {
319
+ limit.count = 0;
320
+ limit.windowStart = now;
321
+ }
322
+
323
+ limit.count++;
324
+
325
+ if (limit.count > MAX_REQUESTS) {
326
+ return { allowed: false, retryAfter: Math.ceil((WINDOW_MS - (now - limit.windowStart)) / 1000) };
327
+ }
328
+
329
+ return { allowed: true, remaining: MAX_REQUESTS - limit.count };
330
+ }
331
+
332
+ // Usage in handler
333
+ const rateCheck = checkRateLimit(endpoint, req.headers["x-user-id"] || "anonymous");
334
+ if (!rateCheck.allowed) {
335
+ res.status(429).json({
336
+ error: "Rate limit exceeded",
337
+ retry_after: rateCheck.retryAfter
338
+ });
339
+ context.close();
340
+ return;
341
+ }
342
+ ```
343
+
344
+ ## Security Considerations
345
+
346
+ > **WARNING:** A proxy that forwards arbitrary URLs is an SSRF vulnerability. Always implement:
347
+
348
+ 1. **Domain whitelist** - Only allow known, approved external domains
349
+ 2. **Method restrictions** - Only allow needed HTTP methods
350
+ 3. **Request size limits** - Prevent large payload attacks
351
+ 4. **Authentication** - Verify the widget caller is a legitimate Zoho user
352
+ 5. **Logging** - Log all proxy requests for audit trail
353
+ 6. **No credential exposure** - Never return API keys to the client
354
+
355
+ ### SSRF Prevention
356
+
357
+ ```javascript
358
+ // NEVER do this:
359
+ const response = await axios.get(req.body.url); // Attacker can access internal network!
360
+
361
+ // ALWAYS do this:
362
+ const allowedDomains = ["api.stripe.com", "api.twilio.com"];
363
+ const url = new URL(req.body.endpoint);
364
+
365
+ if (!allowedDomains.includes(url.hostname)) {
366
+ res.status(403).json({ error: "Forbidden domain" });
367
+ context.close();
368
+ return;
369
+ }
370
+
371
+ // Also block internal IPs
372
+ const blockedPatterns = ["127.0.0.1", "localhost", "10.", "172.16.", "192.168.", "169.254."];
373
+ if (blockedPatterns.some(p => url.hostname.startsWith(p))) {
374
+ res.status(403).json({ error: "Internal addresses not allowed" });
375
+ context.close();
376
+ return;
377
+ }
378
+ ```
379
+
380
+ ## Alternative: Deluge Custom Function as Proxy
381
+
382
+ For simpler cases where Catalyst is not needed:
383
+
384
+ ```deluge
385
+ // Creator custom function: proxy_api_call
386
+ // Arguments: endpoint (TEXT), method (TEXT), payload (TEXT)
387
+
388
+ // Whitelist check
389
+ allowedDomains = {"api.stripe.com", "api.example.com"};
390
+ urlDomain = endpoint.getPrefix("//").getSuffix("//").getPrefix("/");
391
+
392
+ if (!allowedDomains.contains(urlDomain))
393
+ {
394
+ return {"status": "error", "message": "Domain not allowed"};
395
+ }
396
+
397
+ try
398
+ {
399
+ if (method == "GET")
400
+ {
401
+ response = invokeUrl [
402
+ url: endpoint
403
+ type: GET
404
+ connection: "external-api-connection"
405
+ ];
406
+ }
407
+ else
408
+ {
409
+ response = invokeUrl [
410
+ url: endpoint
411
+ type: POST
412
+ headers: {"Content-Type": "application/json"}
413
+ body: payload
414
+ connection: "external-api-connection"
415
+ ];
416
+ }
417
+
418
+ return {"status": "success", "data": response.toString()};
419
+ }
420
+ catch (e)
421
+ {
422
+ return {"status": "error", "message": e.toString()};
423
+ }
424
+ ```
425
+
426
+ > **TRADE-OFF:** Deluge proxy is simpler but limited to 40-second timeout and uses the 5000-statement budget. Catalyst proxy has 30-second timeout but is more flexible for complex transformations.
@@ -0,0 +1,277 @@
1
+ # CRM-Books Native Sync
2
+
3
+ > Last verified: 2026-01 (Zoho CRM v5 API, Books API v3)
4
+
5
+ ## Overview
6
+
7
+ > **CRITICAL:** Zoho CRM and Zoho Books have a built-in native sync that runs on a 2-hour cycle. Before building custom sync logic, verify the native sync doesn't already cover your use case. Rebuilding what already exists creates data conflicts, maintenance burden, and billing waste.
8
+
9
+ ## What Syncs Automatically
10
+
11
+ ### CRM → Books (Entities)
12
+
13
+ | CRM Entity | Books Entity | Sync Direction | Notes |
14
+ |-----------|-------------|----------------|-------|
15
+ | Accounts | Customers | CRM → Books | Company records |
16
+ | Contacts | Contact Persons | CRM → Books | Individual contacts |
17
+ | Products | Items | Bidirectional | Shared catalog |
18
+ | Quotes | Estimates | CRM → Books | When accepted |
19
+ | Sales Orders | Sales Orders | CRM → Books | When confirmed |
20
+ | Invoices | Invoices | CRM → Books | When created |
21
+ | Purchase Orders | Purchase Orders | CRM → Books | When issued |
22
+
23
+ ### Fields That Sync
24
+
25
+ | Category | Fields | Notes |
26
+ |----------|--------|-------|
27
+ | Contact Info | Name, Email, Phone, Address | All standard fields |
28
+ | Financial | Amount, Tax, Discount | Line item details |
29
+ | Products | Name, SKU, Price, Description | Full catalog sync |
30
+ | Status | Invoice status, Payment status | Read from Books |
31
+ | Custom Fields | Only if mapped in sync settings | Manual configuration required |
32
+
33
+ ### What Does NOT Sync Automatically
34
+
35
+ - **Custom modules** in CRM
36
+ - **Activities** (Tasks, Events, Calls)
37
+ - **Notes** and attachments
38
+ - **Custom fields** unless explicitly mapped
39
+ - **Deals/Opportunities** (only Quotes/Orders/Invoices)
40
+ - **Vendors** (must configure separately)
41
+ - **Payment receipts** and credit notes (Books → CRM)
42
+ - **Expense records**
43
+ - **Timesheet data**
44
+
45
+ ## Sync Cycle Details
46
+
47
+ ```
48
+ Every 2 Hours:
49
+ ┌─────────────────────────────────────────┐
50
+ │ 1. CRM checks for modified records │
51
+ │ 2. Compares with last sync timestamp │
52
+ │ 3. Pushes changed records to Books │
53
+ │ 4. Books processes and confirms │
54
+ │ 5. Updates sync status in CRM │
55
+ │ 6. Next cycle scheduled │
56
+ └─────────────────────────────────────────┘
57
+ ```
58
+
59
+ ### Sync Timing
60
+
61
+ - Default interval: Every 2 hours
62
+ - Manual sync: Available in CRM Settings > Zoho Finance > Sync Now
63
+ - First sync: Can take longer for large datasets
64
+ - Conflict resolution: Last-modified-wins (CRM takes priority on shared fields)
65
+
66
+ ## When to NOT Rebuild This
67
+
68
+ > **RULE:** If the native sync handles your data flow, do NOT create custom Deluge scripts that duplicate it. Custom sync will conflict with native sync and cause data inconsistencies.
69
+
70
+ ### Scenarios Where Native Sync is Sufficient
71
+
72
+ 1. Standard contact/customer sync between CRM and Books
73
+ 2. Product catalog shared between both apps
74
+ 3. Quote-to-Invoice conversion flow
75
+ 4. Basic sales order processing
76
+ 5. Standard invoice creation from CRM deals
77
+
78
+ ### Decision Framework
79
+
80
+ | Need | Native Sync | Extend Native | Build Custom |
81
+ |------|-------------|---------------|-------------|
82
+ | Contacts CRM→Books | Use native | - | - |
83
+ | Real-time sync (< 5 min) | No | No | Yes (webhook) |
84
+ | Custom field mapping | - | Yes (configure) | - |
85
+ | Conditional sync (filter) | No | No | Yes |
86
+ | Transform data during sync | No | No | Yes |
87
+ | Multi-org sync | No | No | Yes |
88
+ | Custom module sync | No | No | Yes |
89
+ | Sync with non-Zoho app | No | No | Yes |
90
+ | Payment status → CRM | Partial | Yes | Maybe |
91
+
92
+ ## How to Extend (Not Replace) Native Sync
93
+
94
+ ### Adding Custom Field Mapping
95
+
96
+ 1. Go to CRM > Settings > Zoho Finance Suite
97
+ 2. Click "Sync Preferences"
98
+ 3. Map additional fields under "Field Mapping"
99
+ 4. Custom fields must exist in BOTH apps first
100
+
101
+ ### Supplementing with Workflow Triggers
102
+
103
+ ```deluge
104
+ // In CRM: On Edit of Contact, push extra fields to Books
105
+ // This SUPPLEMENTS native sync (doesn't replace it)
106
+ // Only sync fields that native sync DOESN'T handle
107
+
108
+ if (input.Custom_Field_1 != input.Custom_Field_1_previous)
109
+ {
110
+ // Find corresponding Books contact
111
+ try
112
+ {
113
+ booksContacts = zoho.books.getRecords("contacts", orgId, {
114
+ "email": input.Email
115
+ }, "books-connection");
116
+
117
+ if (booksContacts.get("contacts").size() > 0)
118
+ {
119
+ booksContactId = booksContacts.get("contacts").get(0).get("contact_id");
120
+
121
+ // Update ONLY custom fields (let native sync handle standard fields)
122
+ updateData = Map();
123
+ updateData.put("cf_custom_field_1", input.Custom_Field_1);
124
+ updateData.put("cf_custom_field_2", input.Custom_Field_2);
125
+
126
+ zoho.books.updateRecord("contacts", orgId, booksContactId, updateData, "books-connection");
127
+ }
128
+ }
129
+ catch (e)
130
+ {
131
+ info "Custom field sync failed: " + e.toString();
132
+ }
133
+ }
134
+ ```
135
+
136
+ ### Triggering Actions After Sync
137
+
138
+ ```deluge
139
+ // Scheduled function: Check for recently synced records and post-process
140
+ // Runs every 30 minutes to catch records synced by native 2-hour cycle
141
+
142
+ lastCheck = zoho.creator.getRecords("admin-app", "Sync_State", "Key == \"last_books_check\"", 1, 1);
143
+ lastCheckTime = lastCheck.get(0).get("Value");
144
+
145
+ // Get recently created invoices in Books (created by native sync)
146
+ try
147
+ {
148
+ newInvoices = zoho.books.getRecords("invoices", orgId, {
149
+ "date_start": lastCheckTime,
150
+ "date_end": zoho.currentdate.toString("yyyy-MM-dd"),
151
+ "status": "draft"
152
+ }, "books-connection");
153
+
154
+ for each invoice in newInvoices.get("invoices")
155
+ {
156
+ // Post-processing: auto-send draft invoices over $100
157
+ if (invoice.get("total").toDecimal() > 100)
158
+ {
159
+ zoho.books.updateRecord("invoices", orgId, invoice.get("invoice_id"),
160
+ {"status": "sent"}, "books-connection");
161
+ }
162
+ }
163
+ }
164
+ catch (e)
165
+ {
166
+ info "Post-sync processing failed: " + e.toString();
167
+ }
168
+
169
+ // Update last check timestamp
170
+ zoho.creator.updateRecord("admin-app", "Sync_State",
171
+ lastCheck.get(0).get("ID").toLong(), {"Value": zoho.currenttime.toString()});
172
+ ```
173
+
174
+ ## Sync Status Checking
175
+
176
+ ### Check Sync Status in CRM
177
+
178
+ ```deluge
179
+ // Get sync status for a specific record
180
+ function checkSyncStatus(module, recordId)
181
+ {
182
+ try
183
+ {
184
+ record = zoho.crm.getRecordById(module, recordId);
185
+
186
+ // Check Books-related fields
187
+ booksId = record.get("Books_Contact_ID"); // Populated after sync
188
+ lastSync = record.get("Last_Synced_Time"); // If available
189
+
190
+ if (booksId != null && booksId != "")
191
+ {
192
+ return {"synced": true, "books_id": booksId, "last_sync": lastSync};
193
+ }
194
+ else
195
+ {
196
+ return {"synced": false, "reason": "Not yet synced or sync failed"};
197
+ }
198
+ }
199
+ catch (e)
200
+ {
201
+ return {"synced": false, "reason": e.toString()};
202
+ }
203
+ }
204
+ ```
205
+
206
+ ### Monitor Sync Health
207
+
208
+ ```deluge
209
+ // Scheduled: Daily check for sync issues
210
+ // Find CRM records that should have synced but haven't
211
+
212
+ criteria = "Created_Time < \"" + zoho.currentdate.subDay(1).toString("yyyy-MM-dd") + "\" && Books_Contact_ID is null";
213
+ unsyncedContacts = zoho.crm.getRecords("Contacts", criteria, 1, 200);
214
+
215
+ if (unsyncedContacts.size() > 10)
216
+ {
217
+ sendmail [
218
+ from: zoho.adminuserid
219
+ to: "admin@company.com"
220
+ subject: "[WARNING] " + unsyncedContacts.size() + " contacts not synced to Books"
221
+ message: "Found " + unsyncedContacts.size() + " contacts created over 24 hours ago that have not synced to Books. Check sync configuration."
222
+ ];
223
+ }
224
+ ```
225
+
226
+ ## Troubleshooting Sync Failures
227
+
228
+ | Symptom | Likely Cause | Fix |
229
+ |---------|-------------|-----|
230
+ | Records not syncing | Sync disabled in settings | CRM Settings > Zoho Finance > Enable |
231
+ | Only some records sync | Filter criteria in sync settings | Review sync filters |
232
+ | Custom fields missing | Fields not mapped | Add mapping in sync preferences |
233
+ | Duplicate records in Books | Multiple sync sources configured | Disable duplicate sources |
234
+ | Sync errors in log | Field validation failure in Books | Check Books mandatory fields |
235
+ | Payment status not updating | Books→CRM sync disabled | Enable bidirectional sync |
236
+ | Products not syncing | Product sync not enabled | Enable in sync preferences |
237
+
238
+ ### Sync Error Log Location
239
+
240
+ 1. CRM: Settings > Zoho Finance > Sync History
241
+ 2. Books: Settings > Integrations > Zoho CRM > Sync History
242
+ 3. Logs retained for 90 days
243
+
244
+ ### Common Sync Errors
245
+
246
+ ```
247
+ Error: "Contact already exists in Books"
248
+ → Duplicate email address. Merge contacts in Books first.
249
+
250
+ Error: "Mandatory field missing"
251
+ → Books requires fields that CRM doesn't. Set defaults in Books.
252
+
253
+ Error: "Currency mismatch"
254
+ → Multi-currency not enabled in Books. Enable or map currencies.
255
+
256
+ Error: "Tax rate not found"
257
+ → Tax configuration differs between apps. Create matching tax rates.
258
+ ```
259
+
260
+ ## Integration Architecture Recommendation
261
+
262
+ ```
263
+ For Standard Flows:
264
+ CRM ──[Native 2h Sync]──→ Books
265
+ └──[Supplement: Custom fields via workflow]──→ Books
266
+
267
+ For Real-Time Needs:
268
+ CRM ──[On-Edit Workflow]──→ Catalyst Function ──→ Books API
269
+ (Only for fields/entities NOT covered by native sync)
270
+
271
+ For Reporting:
272
+ CRM ──[Native Sync]──→ Books
273
+ Books ──[Analytics Sync]──→ Zoho Analytics
274
+ (Do NOT pull directly from CRM if Books has the data)
275
+ ```
276
+
277
+ > **FINAL WARNING:** If you find yourself writing Deluge code that syncs Contacts, Accounts, Products, or Invoices between CRM and Books - STOP. The native sync handles this. You are likely duplicating built-in functionality and creating future conflicts.