@justmpm/ai-tool 0.6.0 → 0.7.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.
@@ -47,6 +47,16 @@ function detectCategory(filePath) {
47
47
  if (fileName.endsWith(".astro") && normalized.includes("/layouts/")) {
48
48
  return "layout";
49
49
  }
50
+ if (normalized.includes("functions/src/") && !normalized.includes("/__tests__/")) {
51
+ if (!fileName.includes("index") && !normalized.includes("/types/")) {
52
+ return "cloud-function";
53
+ }
54
+ }
55
+ if (normalized.includes("/triggers/") || normalized.includes("/callable/") || normalized.includes("/scheduled/")) {
56
+ if (normalized.includes("functions/") || normalized.includes("firebase/")) {
57
+ return "cloud-function";
58
+ }
59
+ }
50
60
  if (normalized.includes("/api/") || normalized.includes("/server/") || fileNameNoExt.endsWith(".server") || fileNameNoExt === "+server") {
51
61
  return "route";
52
62
  }
@@ -106,6 +116,7 @@ var categoryIcons = {
106
116
  type: "\u{1F4DD}",
107
117
  config: "\u2699\uFE0F",
108
118
  test: "\u{1F9EA}",
119
+ "cloud-function": "\u26A1",
109
120
  other: "\u{1F4C1}"
110
121
  };
111
122
  function isEntryPoint(filePath) {
@@ -149,6 +160,7 @@ function formatMapSummary(result, areasInfo) {
149
160
  "layout",
150
161
  "route",
151
162
  "store",
163
+ "cloud-function",
152
164
  "other"
153
165
  ];
154
166
  const catParts = [];
@@ -167,6 +179,16 @@ function formatMapSummary(result, areasInfo) {
167
179
  }
168
180
  out += `
169
181
  `;
182
+ const cloudFunctionCount = result.summary.categories["cloud-function"] || 0;
183
+ if (cloudFunctionCount > 0) {
184
+ out += `\u{1F525} Firebase Cloud Functions detectado
185
+ `;
186
+ out += ` ${cloudFunctionCount} function(s) em functions/src/
187
+ `;
188
+ out += ` \u2192 Use 'ai-tool functions' para listar triggers
189
+
190
+ `;
191
+ }
170
192
  if (result.circularDependencies.length > 0) {
171
193
  out += `\u26A0\uFE0F ${result.circularDependencies.length} depend\xEAncia(s) circular(es) detectada(s)
172
194
  `;
@@ -225,13 +247,14 @@ function formatMapText(result) {
225
247
  "type",
226
248
  "config",
227
249
  "test",
250
+ "cloud-function",
228
251
  "other"
229
252
  ];
230
253
  for (const cat of catOrder) {
231
254
  const count = result.summary.categories[cat];
232
255
  if (count) {
233
256
  const icon = categoryIcons[cat];
234
- out += ` ${icon} ${cat.padEnd(12)} ${count}
257
+ out += ` ${icon} ${cat.padEnd(14)} ${count}
235
258
  `;
236
259
  }
237
260
  }
@@ -799,6 +822,7 @@ function formatAreaDetailText(result, options = {}) {
799
822
  "type",
800
823
  "config",
801
824
  "test",
825
+ "cloud-function",
802
826
  "other"
803
827
  ];
804
828
  const catParts = [];
@@ -1057,6 +1081,26 @@ function formatAreaContextText(result) {
1057
1081
  `;
1058
1082
  out += ` state: ${st.state}
1059
1083
 
1084
+ `;
1085
+ }
1086
+ }
1087
+ if (result.triggers && result.triggers.length > 0) {
1088
+ out += `\u26A1 TRIGGERS (${result.triggers.length})
1089
+
1090
+ `;
1091
+ for (const t of result.triggers) {
1092
+ const filePart = t.file.split("/").pop() || t.file;
1093
+ out += ` ${t.name}`.padEnd(35) + `${t.triggerType.padEnd(25)}[${filePart}:${t.line}]
1094
+ `;
1095
+ if (t.triggerPath) {
1096
+ out += ` path: ${t.triggerPath}
1097
+ `;
1098
+ }
1099
+ if (t.triggerSchedule) {
1100
+ out += ` schedule: ${t.triggerSchedule}
1101
+ `;
1102
+ }
1103
+ out += `
1060
1104
  `;
1061
1105
  }
1062
1106
  }
@@ -1077,6 +1121,10 @@ function formatAreaContextText(result) {
1077
1121
  `;
1078
1122
  out += ` Stores: ${result.stores.length}
1079
1123
  `;
1124
+ if (result.triggers && result.triggers.length > 0) {
1125
+ out += ` Triggers: ${result.triggers.length}
1126
+ `;
1127
+ }
1080
1128
  return out;
1081
1129
  }
1082
1130
 
@@ -1559,9 +1607,17 @@ var FOLDER_PATTERNS = [
1559
1607
  { pattern: /store\/.*[Aa]uth/, area: "auth", priority: 70 },
1560
1608
  { pattern: /store\/.*[Uu]ser/, area: "user", priority: 70 },
1561
1609
  // ============================================================================
1562
- // CLOUD FUNCTIONS
1610
+ // FIREBASE CLOUD FUNCTIONS (expandido)
1563
1611
  // ============================================================================
1564
1612
  { pattern: /functions\/src\//, area: "cloud-functions", priority: 80 },
1613
+ { pattern: /functions\/src\/triggers\//, area: "triggers", priority: 95 },
1614
+ { pattern: /functions\/src\/callable\//, area: "callable", priority: 95 },
1615
+ { pattern: /functions\/src\/scheduled\//, area: "scheduled", priority: 95 },
1616
+ { pattern: /functions\/src\/firestore\//, area: "firestore-triggers", priority: 95 },
1617
+ { pattern: /functions\/src\/auth\//, area: "auth-triggers", priority: 95 },
1618
+ { pattern: /functions\/src\/storage\//, area: "storage-triggers", priority: 95 },
1619
+ { pattern: /functions\/src\/pubsub\//, area: "pubsub", priority: 95 },
1620
+ { pattern: /functions\/src\/https\//, area: "callable", priority: 95 },
1565
1621
  // ============================================================================
1566
1622
  // OUTROS
1567
1623
  // ============================================================================
@@ -1611,7 +1667,17 @@ var KEYWORD_PATTERNS = [
1611
1667
  { keyword: /[Mm]anifest/, area: "pwa", priority: 55 },
1612
1668
  // PDF (genérico)
1613
1669
  { keyword: /[Pp]df[Ee]xport/, area: "export", priority: 60 },
1614
- { keyword: /[Dd]ocx[Ee]xport/, area: "export", priority: 60 }
1670
+ { keyword: /[Dd]ocx[Ee]xport/, area: "export", priority: 60 },
1671
+ // Firebase Cloud Functions triggers
1672
+ { keyword: /[Oo]nCall/, area: "callable", priority: 70 },
1673
+ { keyword: /[Oo]nRequest/, area: "callable", priority: 70 },
1674
+ { keyword: /[Oo]nSchedule/, area: "scheduled", priority: 70 },
1675
+ { keyword: /[Oo]n[A-Z].*Created/, area: "triggers", priority: 70 },
1676
+ { keyword: /[Oo]n[A-Z].*Updated/, area: "triggers", priority: 70 },
1677
+ { keyword: /[Oo]n[A-Z].*Deleted/, area: "triggers", priority: 70 },
1678
+ { keyword: /[Oo]n[A-Z].*Written/, area: "triggers", priority: 70 },
1679
+ { keyword: /[Oo]nObject/, area: "storage-triggers", priority: 70 },
1680
+ { keyword: /[Oo]nMessage/, area: "pubsub", priority: 70 }
1615
1681
  ];
1616
1682
  var AREA_NAMES = {
1617
1683
  // Autenticação e usuário
@@ -1658,6 +1724,13 @@ var AREA_NAMES = {
1658
1724
  layout: "Layout",
1659
1725
  "shared-ui": "UI Compartilhada",
1660
1726
  "cloud-functions": "Cloud Functions",
1727
+ triggers: "Triggers Firebase",
1728
+ callable: "Callable Functions",
1729
+ scheduled: "Scheduled Functions",
1730
+ "firestore-triggers": "Firestore Triggers",
1731
+ "auth-triggers": "Auth Triggers",
1732
+ "storage-triggers": "Storage Triggers",
1733
+ pubsub: "Pub/Sub",
1661
1734
  assets: "Assets",
1662
1735
  scripts: "Scripts",
1663
1736
  // UI patterns
@@ -1711,7 +1784,14 @@ var AREA_DESCRIPTIONS = {
1711
1784
  core: "Providers e configura\xE7\xE3o principal",
1712
1785
  layout: "Layout e navega\xE7\xE3o",
1713
1786
  "shared-ui": "Componentes de UI compartilhados",
1714
- "cloud-functions": "Cloud Functions (serverless)",
1787
+ "cloud-functions": "Firebase Cloud Functions (serverless)",
1788
+ triggers: "Event-driven Firebase triggers",
1789
+ callable: "HTTPS callable functions (frontend SDK)",
1790
+ scheduled: "Scheduled/cron functions",
1791
+ "firestore-triggers": "Triggers para eventos do Firestore",
1792
+ "auth-triggers": "Triggers para eventos de autentica\xE7\xE3o",
1793
+ "storage-triggers": "Triggers para eventos do Storage",
1794
+ pubsub: "Triggers para mensagens Pub/Sub",
1715
1795
  assets: "Assets p\xFAblicos",
1716
1796
  scripts: "Scripts de automa\xE7\xE3o",
1717
1797
  // UI patterns
@@ -2748,6 +2828,12 @@ function collectSuggestions(targetPath, graph, allFiles, limit) {
2748
2828
  priority: "low"
2749
2829
  });
2750
2830
  }
2831
+ const cloudFunctionSuggestions = suggestFirebaseRules(targetPath, allFiles);
2832
+ for (const suggestion of cloudFunctionSuggestions) {
2833
+ if (addedPaths.has(suggestion.path)) continue;
2834
+ addedPaths.add(suggestion.path);
2835
+ suggestions.push(suggestion);
2836
+ }
2751
2837
  const priorityOrder = {
2752
2838
  critical: 0,
2753
2839
  high: 1,
@@ -2806,6 +2892,63 @@ function findTargetFile2(target, allFiles) {
2806
2892
  }
2807
2893
  return null;
2808
2894
  }
2895
+ function suggestFirebaseRules(targetPath, allFiles) {
2896
+ const suggestions = [];
2897
+ if (!targetPath.includes("functions/src/")) {
2898
+ return suggestions;
2899
+ }
2900
+ const isFirestoreTrigger = targetPath.toLowerCase().includes("firestore") || targetPath.toLowerCase().includes("document");
2901
+ const isStorageTrigger = targetPath.toLowerCase().includes("storage");
2902
+ for (const file of allFiles) {
2903
+ const fileName = file.split("/").pop()?.toLowerCase() || "";
2904
+ if (fileName === "firestore.rules" || file.endsWith("firestore.rules")) {
2905
+ if (isFirestoreTrigger) {
2906
+ suggestions.push({
2907
+ path: file,
2908
+ category: "config",
2909
+ reason: "Regras Firestore (trigger relacionado)",
2910
+ priority: "high"
2911
+ });
2912
+ } else {
2913
+ suggestions.push({
2914
+ path: file,
2915
+ category: "config",
2916
+ reason: "Regras Firestore (Cloud Function)",
2917
+ priority: "medium"
2918
+ });
2919
+ }
2920
+ }
2921
+ if (fileName === "storage.rules" || file.endsWith("storage.rules")) {
2922
+ if (isStorageTrigger) {
2923
+ suggestions.push({
2924
+ path: file,
2925
+ category: "config",
2926
+ reason: "Regras Storage (trigger relacionado)",
2927
+ priority: "high"
2928
+ });
2929
+ } else {
2930
+ suggestions.push({
2931
+ path: file,
2932
+ category: "config",
2933
+ reason: "Regras Storage (Cloud Function)",
2934
+ priority: "low"
2935
+ });
2936
+ }
2937
+ }
2938
+ }
2939
+ if (!targetPath.endsWith("index.ts")) {
2940
+ const indexFile = allFiles.find((f) => f.includes("functions/src/index"));
2941
+ if (indexFile) {
2942
+ suggestions.push({
2943
+ path: indexFile,
2944
+ category: "config",
2945
+ reason: "Exports de Cloud Functions",
2946
+ priority: "high"
2947
+ });
2948
+ }
2949
+ }
2950
+ return suggestions;
2951
+ }
2809
2952
  function formatNotFound2(target, allFiles) {
2810
2953
  return formatFileNotFound({ target, allFiles, command: "suggest" });
2811
2954
  }
@@ -2881,11 +3024,11 @@ function simplifyType(typeText) {
2881
3024
  return simplified;
2882
3025
  }
2883
3026
  function extractFunctions(sourceFile) {
2884
- const functions = [];
3027
+ const functions2 = [];
2885
3028
  for (const func of sourceFile.getFunctions()) {
2886
3029
  const name = func.getName();
2887
3030
  if (!name) continue;
2888
- functions.push({
3031
+ functions2.push({
2889
3032
  name,
2890
3033
  params: extractParams(func.getParameters()),
2891
3034
  returnType: simplifyType(func.getReturnType().getText()),
@@ -2903,7 +3046,7 @@ function extractFunctions(sourceFile) {
2903
3046
  if (init.getKind() === SyntaxKind.ArrowFunction) {
2904
3047
  const arrowFunc = init.asKind(SyntaxKind.ArrowFunction);
2905
3048
  if (!arrowFunc) continue;
2906
- functions.push({
3049
+ functions2.push({
2907
3050
  name: varDecl.getName(),
2908
3051
  params: extractParams(arrowFunc.getParameters()),
2909
3052
  returnType: simplifyType(arrowFunc.getReturnType().getText()),
@@ -2916,7 +3059,7 @@ function extractFunctions(sourceFile) {
2916
3059
  if (init.getKind() === SyntaxKind.FunctionExpression) {
2917
3060
  const funcExpr = init.asKind(SyntaxKind.FunctionExpression);
2918
3061
  if (!funcExpr) continue;
2919
- functions.push({
3062
+ functions2.push({
2920
3063
  name: varDecl.getName(),
2921
3064
  params: extractParams(funcExpr.getParameters()),
2922
3065
  returnType: simplifyType(funcExpr.getReturnType().getText()),
@@ -2928,7 +3071,7 @@ function extractFunctions(sourceFile) {
2928
3071
  }
2929
3072
  }
2930
3073
  }
2931
- return functions;
3074
+ return functions2;
2932
3075
  }
2933
3076
  function extractTypes(sourceFile) {
2934
3077
  const types = [];
@@ -3035,6 +3178,62 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
3035
3178
  ".vercel",
3036
3179
  ".analyze"
3037
3180
  ]);
3181
+ var FIREBASE_V2_TRIGGERS = /* @__PURE__ */ new Set([
3182
+ // HTTPS (firebase-functions/v2/https)
3183
+ "onCall",
3184
+ "onRequest",
3185
+ // Firestore (firebase-functions/v2/firestore)
3186
+ "onDocumentCreated",
3187
+ "onDocumentCreatedWithAuthContext",
3188
+ "onDocumentUpdated",
3189
+ "onDocumentUpdatedWithAuthContext",
3190
+ "onDocumentDeleted",
3191
+ "onDocumentDeletedWithAuthContext",
3192
+ "onDocumentWritten",
3193
+ "onDocumentWrittenWithAuthContext",
3194
+ // Realtime Database (firebase-functions/v2/database)
3195
+ "onValueCreated",
3196
+ "onValueUpdated",
3197
+ "onValueDeleted",
3198
+ "onValueWritten",
3199
+ // Scheduler (firebase-functions/v2/scheduler)
3200
+ "onSchedule",
3201
+ // Storage (firebase-functions/v2/storage)
3202
+ "onObjectFinalized",
3203
+ "onObjectArchived",
3204
+ "onObjectDeleted",
3205
+ "onMetadataUpdated",
3206
+ // Pub/Sub (firebase-functions/v2/pubsub)
3207
+ "onMessagePublished",
3208
+ // Identity (firebase-functions/v2/identity)
3209
+ "beforeUserCreated",
3210
+ "beforeUserSignedIn",
3211
+ "beforeEmailSent",
3212
+ "beforeSmsSent",
3213
+ // Alerts - Crashlytics (firebase-functions/v2/alerts/crashlytics)
3214
+ "onNewFatalIssuePublished",
3215
+ "onNewNonfatalIssuePublished",
3216
+ "onNewAnrIssuePublished",
3217
+ "onRegressionAlertPublished",
3218
+ "onStabilityDigestPublished",
3219
+ "onVelocityAlertPublished",
3220
+ // Alerts - App Distribution (firebase-functions/v2/alerts/appDistribution)
3221
+ "onNewTesterIosDevicePublished",
3222
+ "onInAppFeedbackPublished",
3223
+ // Alerts - Performance (firebase-functions/v2/alerts/performance)
3224
+ "onThresholdAlertPublished",
3225
+ // Alerts - Billing (firebase-functions/v2/alerts/billing)
3226
+ "onPlanUpdatePublished",
3227
+ "onPlanAutomatedUpdatePublished",
3228
+ // Remote Config (firebase-functions/v2/remoteConfig)
3229
+ "onConfigUpdated",
3230
+ // Eventarc (firebase-functions/v2/eventarc)
3231
+ "onCustomEventPublished",
3232
+ // Tasks (firebase-functions/v2/tasks)
3233
+ "onTaskDispatched",
3234
+ // Test Lab (firebase-functions/v2/testLab)
3235
+ "onTestMatrixCompleted"
3236
+ ]);
3038
3237
  function indexProject(cwd) {
3039
3238
  const allFiles = getAllCodeFiles(cwd);
3040
3239
  const project = createProject2(cwd);
@@ -3084,7 +3283,7 @@ function indexProject(cwd) {
3084
3283
  if (!name) continue;
3085
3284
  const isExported = func.isExported();
3086
3285
  const params = func.getParameters().map((p) => p.getName());
3087
- const returnType = simplifyType2(func.getReturnType().getText());
3286
+ const returnType = simplifyType2(safeGetReturnType(func));
3088
3287
  const kind = inferSymbolKind(name, "function");
3089
3288
  const symbol = {
3090
3289
  name,
@@ -3116,7 +3315,7 @@ function indexProject(cwd) {
3116
3315
  const funcLike = init.asKind(SyntaxKind2.ArrowFunction) || init.asKind(SyntaxKind2.FunctionExpression);
3117
3316
  if (!funcLike) continue;
3118
3317
  const params = funcLike.getParameters().map((p) => p.getName());
3119
- const returnType = simplifyType2(funcLike.getReturnType().getText());
3318
+ const returnType = simplifyType2(safeGetReturnType(funcLike));
3120
3319
  const kind = inferSymbolKind(name, "function");
3121
3320
  const symbol = {
3122
3321
  name,
@@ -3136,6 +3335,48 @@ function indexProject(cwd) {
3136
3335
  if (isExported) {
3137
3336
  exports.push(name);
3138
3337
  }
3338
+ } else if (initKind === SyntaxKind2.CallExpression) {
3339
+ const triggerName = extractFirebaseTriggerName(init);
3340
+ if (triggerName && FIREBASE_V2_TRIGGERS.has(triggerName)) {
3341
+ const triggerInfo = extractTriggerInfo(init, triggerName);
3342
+ const symbol = {
3343
+ name,
3344
+ file: filePath,
3345
+ line: varDecl.getStartLineNumber(),
3346
+ kind: "trigger",
3347
+ signature: `${isExported ? "export " : ""}const ${name} = ${triggerName}(...)`,
3348
+ isExported,
3349
+ triggerInfo
3350
+ };
3351
+ symbols.push(symbol);
3352
+ if (!symbolsByName[name]) {
3353
+ symbolsByName[name] = [];
3354
+ }
3355
+ symbolsByName[name].push(symbol);
3356
+ if (isExported) {
3357
+ exports.push(name);
3358
+ }
3359
+ } else {
3360
+ const declKind = varStatement.getDeclarationKind();
3361
+ if (declKind.toString() === "const") {
3362
+ const symbol = {
3363
+ name,
3364
+ file: filePath,
3365
+ line: varDecl.getStartLineNumber(),
3366
+ kind: "const",
3367
+ signature: `${isExported ? "export " : ""}const ${name} = ${truncateCode(init.getText(), 40)}`,
3368
+ isExported
3369
+ };
3370
+ symbols.push(symbol);
3371
+ if (!symbolsByName[name]) {
3372
+ symbolsByName[name] = [];
3373
+ }
3374
+ symbolsByName[name].push(symbol);
3375
+ if (isExported) {
3376
+ exports.push(name);
3377
+ }
3378
+ }
3379
+ }
3139
3380
  } else {
3140
3381
  const declKind = varStatement.getDeclarationKind();
3141
3382
  if (declKind.toString() !== "const") continue;
@@ -3189,7 +3430,7 @@ function indexProject(cwd) {
3189
3430
  kind: "type",
3190
3431
  signature: `${isExported ? "export " : ""}type ${name}`,
3191
3432
  isExported,
3192
- definition: simplifyType2(typeAlias.getType().getText())
3433
+ definition: simplifyType2(safeGetTypeText(() => typeAlias.getType()))
3193
3434
  };
3194
3435
  symbols.push(symbol);
3195
3436
  if (!symbolsByName[name]) {
@@ -3290,13 +3531,70 @@ function inferSymbolKind(name, context2) {
3290
3531
  }
3291
3532
  return context2 === "function" ? "function" : "const";
3292
3533
  }
3534
+ function extractFirebaseTriggerName(init) {
3535
+ const text = init.getText();
3536
+ for (const trigger of FIREBASE_V2_TRIGGERS) {
3537
+ const pattern = new RegExp(`(?:^|\\.)${trigger}\\s*\\(`);
3538
+ if (pattern.test(text)) {
3539
+ return trigger;
3540
+ }
3541
+ }
3542
+ return null;
3543
+ }
3544
+ function extractTriggerInfo(init, triggerName) {
3545
+ const text = init.getText();
3546
+ const info = { triggerType: triggerName };
3547
+ if (triggerName.startsWith("onDocument") || triggerName.startsWith("onValue")) {
3548
+ const pathMatch = text.match(/\(\s*["'`]([^"'`]+)["'`]/);
3549
+ if (pathMatch) {
3550
+ info.triggerPath = pathMatch[1];
3551
+ }
3552
+ }
3553
+ if (triggerName === "onSchedule") {
3554
+ const scheduleMatch = text.match(/onSchedule\s*\(\s*["'`]([^"'`]+)["'`]/);
3555
+ if (scheduleMatch) {
3556
+ info.triggerSchedule = scheduleMatch[1];
3557
+ } else {
3558
+ const objectScheduleMatch = text.match(/schedule\s*:\s*["'`]([^"'`]+)["'`]/);
3559
+ if (objectScheduleMatch) {
3560
+ info.triggerSchedule = objectScheduleMatch[1];
3561
+ }
3562
+ }
3563
+ }
3564
+ if (triggerName.startsWith("onObject") || triggerName === "onMetadataUpdated") {
3565
+ const bucketMatch = text.match(/bucket\s*:\s*["'`]([^"'`]+)["'`]/);
3566
+ if (bucketMatch) {
3567
+ info.triggerPath = bucketMatch[1];
3568
+ }
3569
+ }
3570
+ return info;
3571
+ }
3293
3572
  function simplifyType2(typeText) {
3573
+ if (!typeText) return "unknown";
3294
3574
  let simplified = typeText.replace(/import\([^)]+\)\./g, "");
3295
3575
  if (simplified.length > 80) {
3296
3576
  simplified = simplified.slice(0, 77) + "...";
3297
3577
  }
3298
3578
  return simplified;
3299
3579
  }
3580
+ function safeGetTypeText(getTypeFn) {
3581
+ try {
3582
+ const type = getTypeFn();
3583
+ if (!type) return "unknown";
3584
+ return type.getText();
3585
+ } catch {
3586
+ return "unknown";
3587
+ }
3588
+ }
3589
+ function safeGetReturnType(node) {
3590
+ try {
3591
+ const returnType = node.getReturnType();
3592
+ if (!returnType) return "unknown";
3593
+ return returnType.getText();
3594
+ } catch {
3595
+ return "unknown";
3596
+ }
3597
+ }
3300
3598
  function truncateCode(code, maxLen) {
3301
3599
  const oneLine = code.replace(/\s+/g, " ").trim();
3302
3600
  if (oneLine.length <= maxLen) return oneLine;
@@ -3310,7 +3608,7 @@ function formatInterfaceDefinition2(iface) {
3310
3608
  }
3311
3609
  const props = iface.getProperties();
3312
3610
  for (const prop of props.slice(0, 10)) {
3313
- const propType = simplifyType2(prop.getType().getText());
3611
+ const propType = simplifyType2(safeGetTypeText(() => prop.getType()));
3314
3612
  parts.push(`${prop.getName()}: ${propType}`);
3315
3613
  }
3316
3614
  if (props.length > 10) {
@@ -3335,7 +3633,7 @@ async function context(target, options = {}) {
3335
3633
  const absolutePath = resolve2(cwd, targetPath);
3336
3634
  const sourceFile = addSourceFile(project, absolutePath);
3337
3635
  const imports = extractImports(sourceFile);
3338
- const functions = extractFunctions(sourceFile);
3636
+ const functions2 = extractFunctions(sourceFile);
3339
3637
  const types = extractTypes(sourceFile);
3340
3638
  const exports = extractExports(sourceFile);
3341
3639
  const result = {
@@ -3345,7 +3643,7 @@ async function context(target, options = {}) {
3345
3643
  category: detectCategory(targetPath),
3346
3644
  imports,
3347
3645
  exports,
3348
- functions,
3646
+ functions: functions2,
3349
3647
  types
3350
3648
  };
3351
3649
  if (format === "json") {
@@ -3478,10 +3776,11 @@ async function areaContext(areaName, options = {}) {
3478
3776
  }
3479
3777
  const types = [];
3480
3778
  const hooks = [];
3481
- const functions = [];
3779
+ const functions2 = [];
3482
3780
  const components = [];
3483
3781
  const services = [];
3484
3782
  const stores = [];
3783
+ const triggers = [];
3485
3784
  for (const filePath of areaFiles) {
3486
3785
  const fileData = index.files[filePath];
3487
3786
  if (!fileData) continue;
@@ -3526,7 +3825,7 @@ async function areaContext(areaName, options = {}) {
3526
3825
  returns: symbol.returnType || "unknown"
3527
3826
  });
3528
3827
  } else {
3529
- functions.push({
3828
+ functions2.push({
3530
3829
  name: symbol.name,
3531
3830
  file: filePath,
3532
3831
  line: symbol.line,
@@ -3545,6 +3844,16 @@ async function areaContext(areaName, options = {}) {
3545
3844
  });
3546
3845
  }
3547
3846
  break;
3847
+ case "trigger":
3848
+ triggers.push({
3849
+ name: symbol.name,
3850
+ file: filePath,
3851
+ line: symbol.line,
3852
+ triggerType: symbol.triggerInfo?.triggerType || "unknown",
3853
+ triggerPath: symbol.triggerInfo?.triggerPath,
3854
+ triggerSchedule: symbol.triggerInfo?.triggerSchedule
3855
+ });
3856
+ break;
3548
3857
  }
3549
3858
  }
3550
3859
  }
@@ -3560,10 +3869,11 @@ async function areaContext(areaName, options = {}) {
3560
3869
  },
3561
3870
  types,
3562
3871
  hooks,
3563
- functions,
3872
+ functions: functions2,
3564
3873
  components,
3565
3874
  services,
3566
- stores
3875
+ stores,
3876
+ triggers
3567
3877
  };
3568
3878
  if (format === "json") {
3569
3879
  return JSON.stringify(result, null, 2);
@@ -4012,6 +4322,406 @@ function getAllCodeFiles5(dir, files = [], baseDir = dir) {
4012
4322
  return files;
4013
4323
  }
4014
4324
 
4325
+ // src/commands/functions.ts
4326
+ async function functions(options = {}) {
4327
+ const cwd = options.cwd || process.cwd();
4328
+ const format = options.format || "text";
4329
+ const useCache = options.cache !== false;
4330
+ const filterTrigger = options.trigger;
4331
+ if (!isFirebaseProject(cwd)) {
4332
+ const errorMsg = "Este n\xE3o \xE9 um projeto Firebase.\nN\xE3o foi encontrado .firebaserc ou firebase.json";
4333
+ return format === "json" ? JSON.stringify({ error: errorMsg }) : `\u274C ${errorMsg}`;
4334
+ }
4335
+ if (!hasFirebaseFunctions(cwd)) {
4336
+ const errorMsg = "Projeto Firebase sem Cloud Functions.\nN\xE3o foi encontrado functions/src/index.ts";
4337
+ return format === "json" ? JSON.stringify({ error: errorMsg }) : `\u274C ${errorMsg}`;
4338
+ }
4339
+ try {
4340
+ let index;
4341
+ if (useCache && isCacheValid(cwd)) {
4342
+ const cached = getCachedSymbolsIndex(cwd);
4343
+ if (cached && cached.symbolsByName) {
4344
+ index = cached;
4345
+ } else {
4346
+ index = indexProject(cwd);
4347
+ cacheSymbolsIndex(cwd, index);
4348
+ updateCacheMeta(cwd);
4349
+ }
4350
+ } else {
4351
+ index = indexProject(cwd);
4352
+ if (useCache) {
4353
+ cacheSymbolsIndex(cwd, index);
4354
+ updateCacheMeta(cwd);
4355
+ }
4356
+ }
4357
+ const funcs = [];
4358
+ for (const fileData of Object.values(index.files)) {
4359
+ if (!fileData.path.includes("functions/src/")) continue;
4360
+ for (const symbol of fileData.symbols) {
4361
+ if (symbol.kind === "trigger") {
4362
+ funcs.push({
4363
+ name: symbol.name,
4364
+ file: symbol.file,
4365
+ line: symbol.line,
4366
+ triggerType: symbol.triggerInfo?.triggerType || "unknown",
4367
+ triggerPath: symbol.triggerInfo?.triggerPath,
4368
+ triggerSchedule: symbol.triggerInfo?.triggerSchedule,
4369
+ isExported: symbol.isExported
4370
+ });
4371
+ }
4372
+ }
4373
+ }
4374
+ const filtered = filterTrigger ? funcs.filter(
4375
+ (f) => f.triggerType.toLowerCase().includes(filterTrigger.toLowerCase())
4376
+ ) : funcs;
4377
+ const byTrigger = {};
4378
+ const triggerCounts = {};
4379
+ for (const func of filtered) {
4380
+ if (!byTrigger[func.triggerType]) {
4381
+ byTrigger[func.triggerType] = [];
4382
+ triggerCounts[func.triggerType] = 0;
4383
+ }
4384
+ byTrigger[func.triggerType].push(func);
4385
+ triggerCounts[func.triggerType]++;
4386
+ }
4387
+ const result = {
4388
+ version: "1.0.0",
4389
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4390
+ cwd,
4391
+ isFirebaseProject: true,
4392
+ hasFunctions: true,
4393
+ functions: filtered,
4394
+ byTrigger,
4395
+ summary: {
4396
+ total: filtered.length,
4397
+ exported: filtered.filter((f) => f.isExported).length,
4398
+ byTrigger: triggerCounts
4399
+ }
4400
+ };
4401
+ if (format === "json") {
4402
+ return JSON.stringify(result, null, 2);
4403
+ }
4404
+ return formatFunctionsText(result);
4405
+ } catch (error) {
4406
+ const message = error instanceof Error ? error.message : String(error);
4407
+ throw new Error(`Erro ao executar functions: ${message}`);
4408
+ }
4409
+ }
4410
+ function formatFunctionsText(result) {
4411
+ let out = "";
4412
+ out += `
4413
+ `;
4414
+ out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
4415
+ `;
4416
+ out += `\u2551 \u26A1 CLOUD FUNCTIONS \u2551
4417
+ `;
4418
+ out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
4419
+
4420
+ `;
4421
+ out += `\u{1F4CA} RESUMO
4422
+ `;
4423
+ out += ` Total: ${result.summary.total} functions
4424
+ `;
4425
+ out += ` Exportadas: ${result.summary.exported}
4426
+
4427
+ `;
4428
+ if (result.summary.total === 0) {
4429
+ out += ` \u26A0\uFE0F Nenhuma Cloud Function detectada.
4430
+ `;
4431
+ out += ` Verifique se seus triggers usam padr\xE3o Firebase v2 (onCall, onDocumentCreated, etc).
4432
+ `;
4433
+ return out;
4434
+ }
4435
+ out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
4436
+
4437
+ `;
4438
+ const triggerOrder = [
4439
+ // HTTPS
4440
+ "onCall",
4441
+ "onRequest",
4442
+ // Firestore
4443
+ "onDocumentCreated",
4444
+ "onDocumentCreatedWithAuthContext",
4445
+ "onDocumentUpdated",
4446
+ "onDocumentUpdatedWithAuthContext",
4447
+ "onDocumentDeleted",
4448
+ "onDocumentDeletedWithAuthContext",
4449
+ "onDocumentWritten",
4450
+ "onDocumentWrittenWithAuthContext",
4451
+ // Realtime Database
4452
+ "onValueCreated",
4453
+ "onValueUpdated",
4454
+ "onValueDeleted",
4455
+ "onValueWritten",
4456
+ // Scheduler
4457
+ "onSchedule",
4458
+ // Storage
4459
+ "onObjectFinalized",
4460
+ "onObjectArchived",
4461
+ "onObjectDeleted",
4462
+ "onMetadataUpdated",
4463
+ // Pub/Sub
4464
+ "onMessagePublished",
4465
+ // Identity
4466
+ "beforeUserCreated",
4467
+ "beforeUserSignedIn"
4468
+ ];
4469
+ const sortedTriggers = Object.keys(result.byTrigger).sort((a, b) => {
4470
+ const aIdx = triggerOrder.indexOf(a);
4471
+ const bIdx = triggerOrder.indexOf(b);
4472
+ if (aIdx === -1 && bIdx === -1) return a.localeCompare(b);
4473
+ if (aIdx === -1) return 1;
4474
+ if (bIdx === -1) return -1;
4475
+ return aIdx - bIdx;
4476
+ });
4477
+ for (const trigger of sortedTriggers) {
4478
+ const funcs = result.byTrigger[trigger];
4479
+ const icon = getTriggerIcon(trigger);
4480
+ out += `${icon} ${trigger} (${funcs.length})
4481
+
4482
+ `;
4483
+ for (const func of funcs) {
4484
+ const exportTag = func.isExported ? "" : " [n\xE3o exportada]";
4485
+ out += ` ${func.name}${exportTag}
4486
+ `;
4487
+ out += ` \u{1F4C1} ${func.file}:${func.line}
4488
+ `;
4489
+ if (func.triggerPath) {
4490
+ out += ` \u{1F4CD} path: ${func.triggerPath}
4491
+ `;
4492
+ }
4493
+ if (func.triggerSchedule) {
4494
+ out += ` \u23F0 schedule: ${func.triggerSchedule}
4495
+ `;
4496
+ }
4497
+ }
4498
+ out += `
4499
+ `;
4500
+ }
4501
+ return out;
4502
+ }
4503
+ function getTriggerIcon(trigger) {
4504
+ if (trigger.includes("Call") || trigger.includes("Request")) return "\u{1F310}";
4505
+ if (trigger.includes("Document") || trigger.includes("Value")) return "\u{1F525}";
4506
+ if (trigger.includes("Schedule")) return "\u23F0";
4507
+ if (trigger.includes("Object") || trigger.includes("Metadata")) return "\u{1F4E6}";
4508
+ if (trigger.includes("Message") || trigger.includes("Pub")) return "\u{1F4E8}";
4509
+ if (trigger.includes("User") || trigger.includes("before")) return "\u{1F464}";
4510
+ if (trigger.includes("Alert") || trigger.includes("Issue")) return "\u{1F6A8}";
4511
+ if (trigger.includes("Config")) return "\u2699\uFE0F";
4512
+ if (trigger.includes("Task")) return "\u{1F4CB}";
4513
+ if (trigger.includes("Test")) return "\u{1F9EA}";
4514
+ return "\u26A1";
4515
+ }
4516
+
4517
+ // src/commands/find.ts
4518
+ async function find(query, options = {}) {
4519
+ const cwd = options.cwd || process.cwd();
4520
+ const format = options.format || "text";
4521
+ const filterType = options.type || "all";
4522
+ const filterArea = options.area;
4523
+ const defOnly = options.def ?? false;
4524
+ const refsOnly = options.refs ?? false;
4525
+ const useCache = options.cache !== false;
4526
+ if (!query || query.trim().length === 0) {
4527
+ throw new Error("Query \xE9 obrigat\xF3ria. Exemplo: ai-tool find useAuth");
4528
+ }
4529
+ try {
4530
+ let index;
4531
+ let fromCache = false;
4532
+ if (useCache && isCacheValid(cwd)) {
4533
+ const cached = getCachedSymbolsIndex(cwd);
4534
+ if (cached && cached.symbolsByName) {
4535
+ index = cached;
4536
+ fromCache = true;
4537
+ } else {
4538
+ index = indexProject(cwd);
4539
+ cacheSymbolsIndex(cwd, index);
4540
+ updateCacheMeta(cwd);
4541
+ }
4542
+ } else {
4543
+ index = indexProject(cwd);
4544
+ if (useCache) {
4545
+ cacheSymbolsIndex(cwd, index);
4546
+ updateCacheMeta(cwd);
4547
+ }
4548
+ }
4549
+ let allowedFiles = null;
4550
+ if (filterArea) {
4551
+ const config = readConfig(cwd);
4552
+ const areaLower = filterArea.toLowerCase();
4553
+ allowedFiles = /* @__PURE__ */ new Set();
4554
+ for (const filePath of Object.keys(index.files)) {
4555
+ if (isFileIgnored(filePath, config)) continue;
4556
+ const fileAreas = detectFileAreas(filePath, config);
4557
+ const belongsToArea = fileAreas.some(
4558
+ (a) => a.toLowerCase() === areaLower || a.toLowerCase().includes(areaLower)
4559
+ );
4560
+ if (belongsToArea) {
4561
+ allowedFiles.add(filePath);
4562
+ }
4563
+ }
4564
+ if (allowedFiles.size === 0) {
4565
+ return format === "json" ? JSON.stringify({ error: `Nenhum arquivo encontrado na \xE1rea "${filterArea}"` }) : `\u274C Nenhum arquivo encontrado na \xE1rea "${filterArea}"`;
4566
+ }
4567
+ }
4568
+ const matches = searchInIndex(index, query, filterType, allowedFiles);
4569
+ let definition = null;
4570
+ let references = [];
4571
+ for (const match of matches) {
4572
+ if (match.matchType === "definition") {
4573
+ if (!definition) {
4574
+ definition = match;
4575
+ }
4576
+ } else {
4577
+ references.push(match);
4578
+ }
4579
+ }
4580
+ if (defOnly) {
4581
+ references = [];
4582
+ }
4583
+ if (refsOnly) {
4584
+ definition = null;
4585
+ }
4586
+ const uniqueFiles = /* @__PURE__ */ new Set();
4587
+ if (definition) uniqueFiles.add(definition.file);
4588
+ for (const ref of references) {
4589
+ uniqueFiles.add(ref.file);
4590
+ }
4591
+ const result = {
4592
+ version: "1.0.0",
4593
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4594
+ query,
4595
+ filters: {
4596
+ type: filterType !== "all" ? filterType : void 0,
4597
+ area: filterArea,
4598
+ defOnly,
4599
+ refsOnly
4600
+ },
4601
+ definition,
4602
+ references,
4603
+ summary: {
4604
+ definitions: definition ? 1 : 0,
4605
+ references: references.length,
4606
+ files: uniqueFiles.size
4607
+ },
4608
+ fromCache
4609
+ };
4610
+ if (format === "json") {
4611
+ return JSON.stringify(result, null, 2);
4612
+ }
4613
+ return formatFindText(result);
4614
+ } catch (error) {
4615
+ const message = error instanceof Error ? error.message : String(error);
4616
+ throw new Error(`Erro ao executar find: ${message}`);
4617
+ }
4618
+ }
4619
+ function searchInIndex(index, query, filterType, allowedFiles) {
4620
+ const matches = [];
4621
+ const queryLower = query.toLowerCase();
4622
+ const processedSymbols = /* @__PURE__ */ new Set();
4623
+ for (const [name, symbols] of Object.entries(index.symbolsByName)) {
4624
+ const nameLower = name.toLowerCase();
4625
+ if (nameLower === queryLower || nameLower.includes(queryLower)) {
4626
+ for (const symbol of symbols) {
4627
+ if (allowedFiles && !allowedFiles.has(symbol.file)) continue;
4628
+ if (!matchesType(symbol.kind, filterType)) continue;
4629
+ const key = `${symbol.file}:${symbol.line}:${symbol.name}`;
4630
+ if (processedSymbols.has(key)) continue;
4631
+ processedSymbols.add(key);
4632
+ const fileData = index.files[symbol.file];
4633
+ matches.push({
4634
+ file: symbol.file,
4635
+ line: symbol.line,
4636
+ column: 0,
4637
+ code: symbol.signature,
4638
+ matchType: "definition",
4639
+ symbolType: mapKindToSymbolType(symbol.kind),
4640
+ category: fileData?.category || "other"
4641
+ });
4642
+ }
4643
+ }
4644
+ }
4645
+ for (const [filePath, fileData] of Object.entries(index.files)) {
4646
+ if (allowedFiles && !allowedFiles.has(filePath)) continue;
4647
+ for (const imp of fileData.imports) {
4648
+ for (const spec of imp.specifiers) {
4649
+ const specLower = spec.toLowerCase();
4650
+ if (specLower === queryLower || specLower.includes(queryLower)) {
4651
+ const key = `import:${filePath}:${spec}:${imp.source}`;
4652
+ if (processedSymbols.has(key)) continue;
4653
+ processedSymbols.add(key);
4654
+ matches.push({
4655
+ file: filePath,
4656
+ line: 1,
4657
+ // Imports geralmente estão no topo
4658
+ column: 0,
4659
+ code: `import { ${spec} } from '${imp.source}'`,
4660
+ matchType: "import",
4661
+ symbolType: inferSymbolTypeFromName(spec),
4662
+ category: fileData.category
4663
+ });
4664
+ }
4665
+ }
4666
+ }
4667
+ }
4668
+ matches.sort((a, b) => {
4669
+ const order = { definition: 0, import: 1, usage: 2 };
4670
+ const orderDiff = order[a.matchType] - order[b.matchType];
4671
+ if (orderDiff !== 0) return orderDiff;
4672
+ return a.file.localeCompare(b.file) || a.line - b.line;
4673
+ });
4674
+ return matches;
4675
+ }
4676
+ function matchesType(kind, filter) {
4677
+ if (filter === "all") return true;
4678
+ switch (filter) {
4679
+ case "function":
4680
+ return kind === "function" || kind === "trigger";
4681
+ case "type":
4682
+ return kind === "type" || kind === "interface" || kind === "enum";
4683
+ case "const":
4684
+ return kind === "const";
4685
+ case "component":
4686
+ return kind === "component";
4687
+ case "hook":
4688
+ return kind === "hook";
4689
+ case "trigger":
4690
+ return kind === "trigger";
4691
+ default:
4692
+ return true;
4693
+ }
4694
+ }
4695
+ function mapKindToSymbolType(kind) {
4696
+ switch (kind) {
4697
+ case "function":
4698
+ return "function";
4699
+ case "hook":
4700
+ return "hook";
4701
+ case "component":
4702
+ return "component";
4703
+ case "type":
4704
+ case "interface":
4705
+ case "enum":
4706
+ return "type";
4707
+ case "const":
4708
+ return "const";
4709
+ case "trigger":
4710
+ return "trigger";
4711
+ default:
4712
+ return "function";
4713
+ }
4714
+ }
4715
+ function inferSymbolTypeFromName(name) {
4716
+ if (name.startsWith("use") && name.length > 3 && name[3] === name[3].toUpperCase()) {
4717
+ return "hook";
4718
+ }
4719
+ if (name[0] === name[0].toUpperCase()) {
4720
+ return "type";
4721
+ }
4722
+ return "function";
4723
+ }
4724
+
4015
4725
  // src/index.ts
4016
4726
  import { createRequire } from "module";
4017
4727
  var require2 = createRequire(import.meta.url);
@@ -4023,7 +4733,6 @@ export {
4023
4733
  categoryIcons,
4024
4734
  isEntryPoint,
4025
4735
  isCodeFile,
4026
- formatFindText,
4027
4736
  isFirebaseProject,
4028
4737
  hasFirebaseFunctions,
4029
4738
  isExportedCloudFunction,
@@ -4031,10 +4740,7 @@ export {
4031
4740
  clearFirebaseCache,
4032
4741
  getCacheDir,
4033
4742
  isCacheValid,
4034
- updateCacheMeta,
4035
4743
  invalidateCache,
4036
- cacheSymbolsIndex,
4037
- getCachedSymbolsIndex,
4038
4744
  configExists,
4039
4745
  readConfig,
4040
4746
  writeConfig,
@@ -4046,7 +4752,6 @@ export {
4046
4752
  KEYWORD_PATTERNS,
4047
4753
  AREA_NAMES,
4048
4754
  AREA_DESCRIPTIONS,
4049
- isFileIgnored,
4050
4755
  detectFileAreas,
4051
4756
  getAreaName,
4052
4757
  getAreaDescription,
@@ -4065,11 +4770,12 @@ export {
4065
4770
  formatInvalidCommand,
4066
4771
  impact,
4067
4772
  suggest,
4068
- indexProject,
4069
4773
  context,
4070
4774
  areaContext,
4071
4775
  areas,
4072
4776
  area,
4073
4777
  areasInit,
4778
+ find,
4779
+ functions,
4074
4780
  VERSION
4075
4781
  };