@atlashub/smartstack-cli 3.54.0 → 4.0.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.
@@ -471,8 +471,8 @@ var init_parseUtil = __esm({
471
471
  init_errors();
472
472
  init_en();
473
473
  makeIssue = (params) => {
474
- const { data, path: path31, errorMaps, issueData } = params;
475
- const fullPath = [...path31, ...issueData.path || []];
474
+ const { data, path: path32, errorMaps, issueData } = params;
475
+ const fullPath = [...path32, ...issueData.path || []];
476
476
  const fullIssue = {
477
477
  ...issueData,
478
478
  path: fullPath
@@ -786,11 +786,11 @@ var init_types = __esm({
786
786
  init_parseUtil();
787
787
  init_util();
788
788
  ParseInputLazyPath = class {
789
- constructor(parent, value, path31, key) {
789
+ constructor(parent, value, path32, key) {
790
790
  this._cachedPath = [];
791
791
  this.parent = parent;
792
792
  this.data = value;
793
- this._path = path31;
793
+ this._path = path32;
794
794
  this._key = key;
795
795
  }
796
796
  get path() {
@@ -4367,10 +4367,10 @@ function assignProp(target, prop, value) {
4367
4367
  configurable: true
4368
4368
  });
4369
4369
  }
4370
- function getElementAtPath(obj, path31) {
4371
- if (!path31)
4370
+ function getElementAtPath(obj, path32) {
4371
+ if (!path32)
4372
4372
  return obj;
4373
- return path31.reduce((acc, key) => acc?.[key], obj);
4373
+ return path32.reduce((acc, key) => acc?.[key], obj);
4374
4374
  }
4375
4375
  function promiseAllObject(promisesObj) {
4376
4376
  const keys = Object.keys(promisesObj);
@@ -4619,11 +4619,11 @@ function aborted(x, startIndex = 0) {
4619
4619
  }
4620
4620
  return false;
4621
4621
  }
4622
- function prefixIssues(path31, issues) {
4622
+ function prefixIssues(path32, issues) {
4623
4623
  return issues.map((iss) => {
4624
4624
  var _a;
4625
4625
  (_a = iss).path ?? (_a.path = []);
4626
- iss.path.unshift(path31);
4626
+ iss.path.unshift(path32);
4627
4627
  return iss;
4628
4628
  });
4629
4629
  }
@@ -14447,8 +14447,8 @@ var require_utils = __commonJS({
14447
14447
  }
14448
14448
  return ind;
14449
14449
  }
14450
- function removeDotSegments(path31) {
14451
- let input = path31;
14450
+ function removeDotSegments(path32) {
14451
+ let input = path32;
14452
14452
  const output = [];
14453
14453
  let nextSlash = -1;
14454
14454
  let len = 0;
@@ -14648,8 +14648,8 @@ var require_schemes = __commonJS({
14648
14648
  wsComponent.secure = void 0;
14649
14649
  }
14650
14650
  if (wsComponent.resourceName) {
14651
- const [path31, query] = wsComponent.resourceName.split("?");
14652
- wsComponent.path = path31 && path31 !== "/" ? path31 : void 0;
14651
+ const [path32, query] = wsComponent.resourceName.split("?");
14652
+ wsComponent.path = path32 && path32 !== "/" ? path32 : void 0;
14653
14653
  wsComponent.query = query;
14654
14654
  wsComponent.resourceName = void 0;
14655
14655
  }
@@ -23041,12 +23041,12 @@ var init_esm7 = __esm({
23041
23041
  /**
23042
23042
  * Get the Path object referenced by the string path, resolved from this Path
23043
23043
  */
23044
- resolve(path31) {
23045
- if (!path31) {
23044
+ resolve(path32) {
23045
+ if (!path32) {
23046
23046
  return this;
23047
23047
  }
23048
- const rootPath = this.getRootString(path31);
23049
- const dir = path31.substring(rootPath.length);
23048
+ const rootPath = this.getRootString(path32);
23049
+ const dir = path32.substring(rootPath.length);
23050
23050
  const dirParts = dir.split(this.splitSep);
23051
23051
  const result = rootPath ? this.getRoot(rootPath).#resolveParts(dirParts) : this.#resolveParts(dirParts);
23052
23052
  return result;
@@ -23798,8 +23798,8 @@ var init_esm7 = __esm({
23798
23798
  /**
23799
23799
  * @internal
23800
23800
  */
23801
- getRootString(path31) {
23802
- return win32.parse(path31).root;
23801
+ getRootString(path32) {
23802
+ return win32.parse(path32).root;
23803
23803
  }
23804
23804
  /**
23805
23805
  * @internal
@@ -23845,8 +23845,8 @@ var init_esm7 = __esm({
23845
23845
  /**
23846
23846
  * @internal
23847
23847
  */
23848
- getRootString(path31) {
23849
- return path31.startsWith("/") ? "/" : "";
23848
+ getRootString(path32) {
23849
+ return path32.startsWith("/") ? "/" : "";
23850
23850
  }
23851
23851
  /**
23852
23852
  * @internal
@@ -23935,11 +23935,11 @@ var init_esm7 = __esm({
23935
23935
  /**
23936
23936
  * Get the depth of a provided path, string, or the cwd
23937
23937
  */
23938
- depth(path31 = this.cwd) {
23939
- if (typeof path31 === "string") {
23940
- path31 = this.cwd.resolve(path31);
23938
+ depth(path32 = this.cwd) {
23939
+ if (typeof path32 === "string") {
23940
+ path32 = this.cwd.resolve(path32);
23941
23941
  }
23942
- return path31.depth();
23942
+ return path32.depth();
23943
23943
  }
23944
23944
  /**
23945
23945
  * Return the cache of child entries. Exposed so subclasses can create
@@ -24426,9 +24426,9 @@ var init_esm7 = __esm({
24426
24426
  process3();
24427
24427
  return results;
24428
24428
  }
24429
- chdir(path31 = this.cwd) {
24429
+ chdir(path32 = this.cwd) {
24430
24430
  const oldCwd = this.cwd;
24431
- this.cwd = typeof path31 === "string" ? this.cwd.resolve(path31) : path31;
24431
+ this.cwd = typeof path32 === "string" ? this.cwd.resolve(path32) : path32;
24432
24432
  this.cwd[setAsCwd](oldCwd);
24433
24433
  }
24434
24434
  };
@@ -24809,8 +24809,8 @@ var init_processor = __esm({
24809
24809
  }
24810
24810
  // match, absolute, ifdir
24811
24811
  entries() {
24812
- return [...this.store.entries()].map(([path31, n]) => [
24813
- path31,
24812
+ return [...this.store.entries()].map(([path32, n]) => [
24813
+ path32,
24814
24814
  !!(n & 2),
24815
24815
  !!(n & 1)
24816
24816
  ]);
@@ -25025,9 +25025,9 @@ var init_walker = __esm({
25025
25025
  signal;
25026
25026
  maxDepth;
25027
25027
  includeChildMatches;
25028
- constructor(patterns, path31, opts) {
25028
+ constructor(patterns, path32, opts) {
25029
25029
  this.patterns = patterns;
25030
- this.path = path31;
25030
+ this.path = path32;
25031
25031
  this.opts = opts;
25032
25032
  this.#sep = !opts.posix && opts.platform === "win32" ? "\\" : "/";
25033
25033
  this.includeChildMatches = opts.includeChildMatches !== false;
@@ -25046,11 +25046,11 @@ var init_walker = __esm({
25046
25046
  });
25047
25047
  }
25048
25048
  }
25049
- #ignored(path31) {
25050
- return this.seen.has(path31) || !!this.#ignore?.ignored?.(path31);
25049
+ #ignored(path32) {
25050
+ return this.seen.has(path32) || !!this.#ignore?.ignored?.(path32);
25051
25051
  }
25052
- #childrenIgnored(path31) {
25053
- return !!this.#ignore?.childrenIgnored?.(path31);
25052
+ #childrenIgnored(path32) {
25053
+ return !!this.#ignore?.childrenIgnored?.(path32);
25054
25054
  }
25055
25055
  // backpressure mechanism
25056
25056
  pause() {
@@ -25265,8 +25265,8 @@ var init_walker = __esm({
25265
25265
  };
25266
25266
  GlobWalker = class extends GlobUtil {
25267
25267
  matches = /* @__PURE__ */ new Set();
25268
- constructor(patterns, path31, opts) {
25269
- super(patterns, path31, opts);
25268
+ constructor(patterns, path32, opts) {
25269
+ super(patterns, path32, opts);
25270
25270
  }
25271
25271
  matchEmit(e) {
25272
25272
  this.matches.add(e);
@@ -25303,8 +25303,8 @@ var init_walker = __esm({
25303
25303
  };
25304
25304
  GlobStream = class extends GlobUtil {
25305
25305
  results;
25306
- constructor(patterns, path31, opts) {
25307
- super(patterns, path31, opts);
25306
+ constructor(patterns, path32, opts) {
25307
+ super(patterns, path32, opts);
25308
25308
  this.results = new Minipass({
25309
25309
  signal: this.signal,
25310
25310
  objectMode: true
@@ -25634,6 +25634,26 @@ var init_esm8 = __esm({
25634
25634
  });
25635
25635
 
25636
25636
  // src/mcp/utils/fs.ts
25637
+ var fs_exports = {};
25638
+ __export(fs_exports, {
25639
+ FileSystemError: () => FileSystemError,
25640
+ copyFile: () => copyFile,
25641
+ directoryExists: () => directoryExists,
25642
+ ensureDirectory: () => ensureDirectory,
25643
+ fileExists: () => fileExists,
25644
+ findDirectories: () => findDirectories,
25645
+ findFiles: () => findFiles,
25646
+ normalizePath: () => normalizePath,
25647
+ readJson: () => readJson,
25648
+ readText: () => readText,
25649
+ relativePath: () => relativePath,
25650
+ removeDirectory: () => removeDirectory,
25651
+ removeFile: () => removeFile,
25652
+ safeJoinPath: () => safeJoinPath,
25653
+ validatePathSecurity: () => validatePathSecurity,
25654
+ writeJson: () => writeJson,
25655
+ writeText: () => writeText
25656
+ });
25637
25657
  import { stat, mkdir, readFile, writeFile, cp, rm } from "fs/promises";
25638
25658
  import path3 from "path";
25639
25659
  function validatePathSecurity(targetPath, baseDir) {
@@ -25697,6 +25717,20 @@ async function readJson(filePath) {
25697
25717
  );
25698
25718
  }
25699
25719
  }
25720
+ async function writeJson(filePath, data) {
25721
+ try {
25722
+ await mkdir(path3.dirname(filePath), { recursive: true });
25723
+ await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
25724
+ } catch (error2) {
25725
+ const err = error2 instanceof Error ? error2 : new Error(String(error2));
25726
+ throw new FileSystemError(
25727
+ `Failed to write JSON file: ${filePath} - ${err.message}`,
25728
+ "writeJson",
25729
+ filePath,
25730
+ err
25731
+ );
25732
+ }
25733
+ }
25700
25734
  async function readText(filePath) {
25701
25735
  try {
25702
25736
  return await readFile(filePath, "utf-8");
@@ -25724,6 +25758,16 @@ async function writeText(filePath, content) {
25724
25758
  );
25725
25759
  }
25726
25760
  }
25761
+ async function copyFile(src, dest) {
25762
+ await mkdir(path3.dirname(dest), { recursive: true });
25763
+ await cp(src, dest, { recursive: true });
25764
+ }
25765
+ async function removeFile(filePath) {
25766
+ await rm(filePath, { force: true });
25767
+ }
25768
+ async function removeDirectory(dirPath) {
25769
+ await rm(dirPath, { recursive: true, force: true });
25770
+ }
25727
25771
  async function findFiles(pattern, options = {}) {
25728
25772
  const { cwd = process.cwd(), ignore = [] } = options;
25729
25773
  const files = await glob(pattern, {
@@ -25734,6 +25778,27 @@ async function findFiles(pattern, options = {}) {
25734
25778
  });
25735
25779
  return files;
25736
25780
  }
25781
+ async function findDirectories(pattern, options = {}) {
25782
+ const { cwd = process.cwd(), ignore = [] } = options;
25783
+ const dirs = await glob(pattern, {
25784
+ cwd,
25785
+ ignore: ["**/node_modules/**", "**/bin/**", "**/obj/**", ...ignore],
25786
+ absolute: true
25787
+ });
25788
+ const results = [];
25789
+ for (const dir of dirs) {
25790
+ if (await directoryExists(dir)) {
25791
+ results.push(dir);
25792
+ }
25793
+ }
25794
+ return results;
25795
+ }
25796
+ function relativePath(from, to) {
25797
+ return path3.relative(from, to).replace(/\\/g, "/");
25798
+ }
25799
+ function normalizePath(filePath) {
25800
+ return filePath.replace(/\\/g, "/");
25801
+ }
25737
25802
  var FileSystemError;
25738
25803
  var init_fs = __esm({
25739
25804
  "src/mcp/utils/fs.ts"() {
@@ -25741,10 +25806,10 @@ var init_fs = __esm({
25741
25806
  init_esm_shims();
25742
25807
  init_esm8();
25743
25808
  FileSystemError = class extends Error {
25744
- constructor(message, operation, path31, cause) {
25809
+ constructor(message, operation, path32, cause) {
25745
25810
  super(message);
25746
25811
  this.operation = operation;
25747
- this.path = path31;
25812
+ this.path = path32;
25748
25813
  this.cause = cause;
25749
25814
  this.name = "FileSystemError";
25750
25815
  }
@@ -25787,7 +25852,7 @@ function tenantModeToTemplateFlags(mode) {
25787
25852
  tenantMode: mode
25788
25853
  };
25789
25854
  }
25790
- var ProjectConfigSchema, SmartStackConfigSchema, ConventionsConfigSchema, EfCoreContextSchema, EfCoreConfigSchema, ScaffoldingConfigSchema, ConfigSchema, TenantModeSchema, ValidateConventionsInputSchema, CheckMigrationsInputSchema, EntityPropertySchema, ScaffoldExtensionInputSchema, ApiDocsInputSchema, SuggestMigrationInputSchema, GeneratePermissionsInputSchema, TestTypeSchema, TestTargetSchema, ScaffoldTestsInputSchema, AnalyzeTestCoverageInputSchema, ValidateTestConventionsInputSchema, SuggestTestScenariosInputSchema, SecurityCheckSchema, ValidateSecurityInputSchema, QualityMetricSchema, AnalyzeCodeQualityInputSchema, ScaffoldApiClientInputSchema, ScaffoldRoutesInputSchema, ValidateFrontendRoutesInputSchema, SlotDefinitionSchema, ScaffoldFrontendExtensionInputSchema, AnalyzeExtensionPointsInputSchema, ScaffoldDataExportInputSchema, AnalyzeHierarchyPatternsInputSchema, ReviewCodeCheckSchema, ReviewCodeInputSchema;
25855
+ var ProjectConfigSchema, SmartStackConfigSchema, ConventionsConfigSchema, EfCoreContextSchema, EfCoreConfigSchema, ScaffoldingConfigSchema, ConfigSchema, TenantModeSchema, ValidateConventionsInputSchema, CheckMigrationsInputSchema, EntityPropertySchema, ScaffoldExtensionInputSchema, ApiDocsInputSchema, SuggestMigrationInputSchema, GeneratePermissionsInputSchema, TestTypeSchema, TestTargetSchema, ScaffoldTestsInputSchema, AnalyzeTestCoverageInputSchema, ValidateTestConventionsInputSchema, SuggestTestScenariosInputSchema, SecurityCheckSchema, ValidateSecurityInputSchema, QualityMetricSchema, AnalyzeCodeQualityInputSchema, ScaffoldApiClientInputSchema, ScaffoldRoutesInputSchema, ValidateFrontendRoutesInputSchema, SlotDefinitionSchema, ScaffoldFrontendExtensionInputSchema, AnalyzeExtensionPointsInputSchema, ScaffoldDataExportInputSchema, PromptDataExportM2mInputSchema, AnalyzeHierarchyPatternsInputSchema, ReviewCodeCheckSchema, ReviewCodeInputSchema;
25791
25856
  var init_types3 = __esm({
25792
25857
  "src/mcp/types/index.ts"() {
25793
25858
  "use strict";
@@ -26079,6 +26144,14 @@ var init_types3 = __esm({
26079
26144
  dryRun: external_exports.boolean().default(false).describe("Preview without writing files (default: false)")
26080
26145
  }).optional()
26081
26146
  });
26147
+ PromptDataExportM2mInputSchema = external_exports.object({
26148
+ endpoints: external_exports.array(external_exports.string()).optional().describe('Endpoint codes to include (e.g., ["users", "tenants"]). If empty, discovers all available endpoints.'),
26149
+ clientId: external_exports.string().optional().describe("Optional clientId to pre-fill in the prompt"),
26150
+ options: external_exports.object({
26151
+ includeBasePrompt: external_exports.boolean().default(true).describe("Include authentication and common sections (default: true)"),
26152
+ format: external_exports.enum(["full", "endpoints-only"]).default("full").describe('Output format: "full" or "endpoints-only"')
26153
+ }).optional()
26154
+ });
26082
26155
  AnalyzeHierarchyPatternsInputSchema = external_exports.object({
26083
26156
  path: external_exports.string().optional().describe("Project path to analyze (default: SmartStack.app path)"),
26084
26157
  entityFilter: external_exports.string().optional().describe('Filter entities by name pattern (e.g., "User*", "*Group*")'),
@@ -27271,8 +27344,8 @@ async function validateControllerRoutes(structure, _config, result) {
27271
27344
  const routeAttrMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
27272
27345
  if (!routeAttrMatch) continue;
27273
27346
  const routeValue = routeAttrMatch[1];
27274
- const relativePath = path8.relative(structure.api, file);
27275
- const dirParts = path8.dirname(relativePath).split(path8.sep);
27347
+ const relativePath2 = path8.relative(structure.api, file);
27348
+ const dirParts = path8.dirname(relativePath2).split(path8.sep);
27276
27349
  const moduleDir = dirParts.slice(0, Math.min(dirParts.length, 3)).join("/");
27277
27350
  if (!routesByDirectory.has(moduleDir)) {
27278
27351
  routesByDirectory.set(moduleDir, []);
@@ -29876,13 +29949,13 @@ var require_ast = __commonJS({
29876
29949
  helperExpression: function helperExpression(node) {
29877
29950
  return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
29878
29951
  },
29879
- scopedId: function scopedId(path31) {
29880
- return /^\.|this\b/.test(path31.original);
29952
+ scopedId: function scopedId(path32) {
29953
+ return /^\.|this\b/.test(path32.original);
29881
29954
  },
29882
29955
  // an ID is simple if it only has one part, and that part is not
29883
29956
  // `..` or `this`.
29884
- simpleId: function simpleId(path31) {
29885
- return path31.parts.length === 1 && !AST2.helpers.scopedId(path31) && !path31.depth;
29957
+ simpleId: function simpleId(path32) {
29958
+ return path32.parts.length === 1 && !AST2.helpers.scopedId(path32) && !path32.depth;
29886
29959
  }
29887
29960
  }
29888
29961
  };
@@ -30956,12 +31029,12 @@ var require_helpers2 = __commonJS({
30956
31029
  loc
30957
31030
  };
30958
31031
  }
30959
- function prepareMustache(path31, params, hash, open, strip, locInfo) {
31032
+ function prepareMustache(path32, params, hash, open, strip, locInfo) {
30960
31033
  var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
30961
31034
  var decorator = /\*/.test(open);
30962
31035
  return {
30963
31036
  type: decorator ? "Decorator" : "MustacheStatement",
30964
- path: path31,
31037
+ path: path32,
30965
31038
  params,
30966
31039
  hash,
30967
31040
  escaped,
@@ -31233,9 +31306,9 @@ var require_compiler = __commonJS({
31233
31306
  },
31234
31307
  DecoratorBlock: function DecoratorBlock(decorator) {
31235
31308
  var program = decorator.program && this.compileProgram(decorator.program);
31236
- var params = this.setupFullMustacheParams(decorator, program, void 0), path31 = decorator.path;
31309
+ var params = this.setupFullMustacheParams(decorator, program, void 0), path32 = decorator.path;
31237
31310
  this.useDecorators = true;
31238
- this.opcode("registerDecorator", params.length, path31.original);
31311
+ this.opcode("registerDecorator", params.length, path32.original);
31239
31312
  },
31240
31313
  PartialStatement: function PartialStatement(partial2) {
31241
31314
  this.usePartial = true;
@@ -31299,46 +31372,46 @@ var require_compiler = __commonJS({
31299
31372
  }
31300
31373
  },
31301
31374
  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
31302
- var path31 = sexpr.path, name = path31.parts[0], isBlock = program != null || inverse != null;
31303
- this.opcode("getContext", path31.depth);
31375
+ var path32 = sexpr.path, name = path32.parts[0], isBlock = program != null || inverse != null;
31376
+ this.opcode("getContext", path32.depth);
31304
31377
  this.opcode("pushProgram", program);
31305
31378
  this.opcode("pushProgram", inverse);
31306
- path31.strict = true;
31307
- this.accept(path31);
31379
+ path32.strict = true;
31380
+ this.accept(path32);
31308
31381
  this.opcode("invokeAmbiguous", name, isBlock);
31309
31382
  },
31310
31383
  simpleSexpr: function simpleSexpr(sexpr) {
31311
- var path31 = sexpr.path;
31312
- path31.strict = true;
31313
- this.accept(path31);
31384
+ var path32 = sexpr.path;
31385
+ path32.strict = true;
31386
+ this.accept(path32);
31314
31387
  this.opcode("resolvePossibleLambda");
31315
31388
  },
31316
31389
  helperSexpr: function helperSexpr(sexpr, program, inverse) {
31317
- var params = this.setupFullMustacheParams(sexpr, program, inverse), path31 = sexpr.path, name = path31.parts[0];
31390
+ var params = this.setupFullMustacheParams(sexpr, program, inverse), path32 = sexpr.path, name = path32.parts[0];
31318
31391
  if (this.options.knownHelpers[name]) {
31319
31392
  this.opcode("invokeKnownHelper", params.length, name);
31320
31393
  } else if (this.options.knownHelpersOnly) {
31321
31394
  throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
31322
31395
  } else {
31323
- path31.strict = true;
31324
- path31.falsy = true;
31325
- this.accept(path31);
31326
- this.opcode("invokeHelper", params.length, path31.original, _ast2["default"].helpers.simpleId(path31));
31396
+ path32.strict = true;
31397
+ path32.falsy = true;
31398
+ this.accept(path32);
31399
+ this.opcode("invokeHelper", params.length, path32.original, _ast2["default"].helpers.simpleId(path32));
31327
31400
  }
31328
31401
  },
31329
- PathExpression: function PathExpression(path31) {
31330
- this.addDepth(path31.depth);
31331
- this.opcode("getContext", path31.depth);
31332
- var name = path31.parts[0], scoped = _ast2["default"].helpers.scopedId(path31), blockParamId = !path31.depth && !scoped && this.blockParamIndex(name);
31402
+ PathExpression: function PathExpression(path32) {
31403
+ this.addDepth(path32.depth);
31404
+ this.opcode("getContext", path32.depth);
31405
+ var name = path32.parts[0], scoped = _ast2["default"].helpers.scopedId(path32), blockParamId = !path32.depth && !scoped && this.blockParamIndex(name);
31333
31406
  if (blockParamId) {
31334
- this.opcode("lookupBlockParam", blockParamId, path31.parts);
31407
+ this.opcode("lookupBlockParam", blockParamId, path32.parts);
31335
31408
  } else if (!name) {
31336
31409
  this.opcode("pushContext");
31337
- } else if (path31.data) {
31410
+ } else if (path32.data) {
31338
31411
  this.options.data = true;
31339
- this.opcode("lookupData", path31.depth, path31.parts, path31.strict);
31412
+ this.opcode("lookupData", path32.depth, path32.parts, path32.strict);
31340
31413
  } else {
31341
- this.opcode("lookupOnContext", path31.parts, path31.falsy, path31.strict, scoped);
31414
+ this.opcode("lookupOnContext", path32.parts, path32.falsy, path32.strict, scoped);
31342
31415
  }
31343
31416
  },
31344
31417
  StringLiteral: function StringLiteral(string3) {
@@ -31694,16 +31767,16 @@ var require_util2 = __commonJS({
31694
31767
  }
31695
31768
  exports.urlGenerate = urlGenerate;
31696
31769
  function normalize2(aPath) {
31697
- var path31 = aPath;
31770
+ var path32 = aPath;
31698
31771
  var url2 = urlParse(aPath);
31699
31772
  if (url2) {
31700
31773
  if (!url2.path) {
31701
31774
  return aPath;
31702
31775
  }
31703
- path31 = url2.path;
31776
+ path32 = url2.path;
31704
31777
  }
31705
- var isAbsolute = exports.isAbsolute(path31);
31706
- var parts = path31.split(/\/+/);
31778
+ var isAbsolute = exports.isAbsolute(path32);
31779
+ var parts = path32.split(/\/+/);
31707
31780
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
31708
31781
  part = parts[i];
31709
31782
  if (part === ".") {
@@ -31720,15 +31793,15 @@ var require_util2 = __commonJS({
31720
31793
  }
31721
31794
  }
31722
31795
  }
31723
- path31 = parts.join("/");
31724
- if (path31 === "") {
31725
- path31 = isAbsolute ? "/" : ".";
31796
+ path32 = parts.join("/");
31797
+ if (path32 === "") {
31798
+ path32 = isAbsolute ? "/" : ".";
31726
31799
  }
31727
31800
  if (url2) {
31728
- url2.path = path31;
31801
+ url2.path = path32;
31729
31802
  return urlGenerate(url2);
31730
31803
  }
31731
- return path31;
31804
+ return path32;
31732
31805
  }
31733
31806
  exports.normalize = normalize2;
31734
31807
  function join2(aRoot, aPath) {
@@ -34529,8 +34602,8 @@ var require_printer = __commonJS({
34529
34602
  return this.accept(sexpr.path) + " " + params + hash;
34530
34603
  };
34531
34604
  PrintVisitor.prototype.PathExpression = function(id) {
34532
- var path31 = id.parts.join("/");
34533
- return (id.data ? "@" : "") + "PATH:" + path31;
34605
+ var path32 = id.parts.join("/");
34606
+ return (id.data ? "@" : "") + "PATH:" + path32;
34534
34607
  };
34535
34608
  PrintVisitor.prototype.StringLiteral = function(string3) {
34536
34609
  return '"' + string3.value + '"';
@@ -46127,11 +46200,11 @@ var require_mime_types = __commonJS({
46127
46200
  }
46128
46201
  return exts[0];
46129
46202
  }
46130
- function lookup(path31) {
46131
- if (!path31 || typeof path31 !== "string") {
46203
+ function lookup(path32) {
46204
+ if (!path32 || typeof path32 !== "string") {
46132
46205
  return false;
46133
46206
  }
46134
- var extension2 = extname("x." + path31).toLowerCase().substr(1);
46207
+ var extension2 = extname("x." + path32).toLowerCase().substr(1);
46135
46208
  if (!extension2) {
46136
46209
  return false;
46137
46210
  }
@@ -47294,7 +47367,7 @@ var require_form_data = __commonJS({
47294
47367
  init_esm_shims();
47295
47368
  var CombinedStream = require_combined_stream();
47296
47369
  var util4 = __require("util");
47297
- var path31 = __require("path");
47370
+ var path32 = __require("path");
47298
47371
  var http3 = __require("http");
47299
47372
  var https2 = __require("https");
47300
47373
  var parseUrl = __require("url").parse;
@@ -47422,11 +47495,11 @@ var require_form_data = __commonJS({
47422
47495
  FormData3.prototype._getContentDisposition = function(value, options) {
47423
47496
  var filename;
47424
47497
  if (typeof options.filepath === "string") {
47425
- filename = path31.normalize(options.filepath).replace(/\\/g, "/");
47498
+ filename = path32.normalize(options.filepath).replace(/\\/g, "/");
47426
47499
  } else if (options.filename || value && (value.name || value.path)) {
47427
- filename = path31.basename(options.filename || value && (value.name || value.path));
47500
+ filename = path32.basename(options.filename || value && (value.name || value.path));
47428
47501
  } else if (value && value.readable && hasOwn(value, "httpVersion")) {
47429
- filename = path31.basename(value.client._httpMessage.path || "");
47502
+ filename = path32.basename(value.client._httpMessage.path || "");
47430
47503
  }
47431
47504
  if (filename) {
47432
47505
  return 'filename="' + filename + '"';
@@ -47625,9 +47698,9 @@ function isVisitable(thing) {
47625
47698
  function removeBrackets(key) {
47626
47699
  return utils_default.endsWith(key, "[]") ? key.slice(0, -2) : key;
47627
47700
  }
47628
- function renderKey(path31, key, dots) {
47629
- if (!path31) return key;
47630
- return path31.concat(key).map(function each(token, i) {
47701
+ function renderKey(path32, key, dots) {
47702
+ if (!path32) return key;
47703
+ return path32.concat(key).map(function each(token, i) {
47631
47704
  token = removeBrackets(token);
47632
47705
  return !dots && i ? "[" + token + "]" : token;
47633
47706
  }).join(dots ? "." : "");
@@ -47672,9 +47745,9 @@ function toFormData(obj, formData, options) {
47672
47745
  }
47673
47746
  return value;
47674
47747
  }
47675
- function defaultVisitor(value, key, path31) {
47748
+ function defaultVisitor(value, key, path32) {
47676
47749
  let arr = value;
47677
- if (value && !path31 && typeof value === "object") {
47750
+ if (value && !path32 && typeof value === "object") {
47678
47751
  if (utils_default.endsWith(key, "{}")) {
47679
47752
  key = metaTokens ? key : key.slice(0, -2);
47680
47753
  value = JSON.stringify(value);
@@ -47693,7 +47766,7 @@ function toFormData(obj, formData, options) {
47693
47766
  if (isVisitable(value)) {
47694
47767
  return true;
47695
47768
  }
47696
- formData.append(renderKey(path31, key, dots), convertValue(value));
47769
+ formData.append(renderKey(path32, key, dots), convertValue(value));
47697
47770
  return false;
47698
47771
  }
47699
47772
  const stack = [];
@@ -47702,10 +47775,10 @@ function toFormData(obj, formData, options) {
47702
47775
  convertValue,
47703
47776
  isVisitable
47704
47777
  });
47705
- function build(value, path31) {
47778
+ function build(value, path32) {
47706
47779
  if (utils_default.isUndefined(value)) return;
47707
47780
  if (stack.indexOf(value) !== -1) {
47708
- throw Error("Circular reference detected in " + path31.join("."));
47781
+ throw Error("Circular reference detected in " + path32.join("."));
47709
47782
  }
47710
47783
  stack.push(value);
47711
47784
  utils_default.forEach(value, function each(el, key) {
@@ -47713,11 +47786,11 @@ function toFormData(obj, formData, options) {
47713
47786
  formData,
47714
47787
  el,
47715
47788
  utils_default.isString(key) ? key.trim() : key,
47716
- path31,
47789
+ path32,
47717
47790
  exposedHelpers
47718
47791
  );
47719
47792
  if (result === true) {
47720
- build(el, path31 ? path31.concat(key) : [key]);
47793
+ build(el, path32 ? path32.concat(key) : [key]);
47721
47794
  }
47722
47795
  });
47723
47796
  stack.pop();
@@ -48003,7 +48076,7 @@ var init_platform = __esm({
48003
48076
  // node_modules/axios/lib/helpers/toURLEncodedForm.js
48004
48077
  function toURLEncodedForm(data, options) {
48005
48078
  return toFormData_default(data, new platform_default.classes.URLSearchParams(), {
48006
- visitor: function(value, key, path31, helpers) {
48079
+ visitor: function(value, key, path32, helpers) {
48007
48080
  if (platform_default.isNode && utils_default.isBuffer(value)) {
48008
48081
  this.append(key, value.toString("base64"));
48009
48082
  return false;
@@ -48042,11 +48115,11 @@ function arrayToObject(arr) {
48042
48115
  return obj;
48043
48116
  }
48044
48117
  function formDataToJSON(formData) {
48045
- function buildPath(path31, value, target, index) {
48046
- let name = path31[index++];
48118
+ function buildPath(path32, value, target, index) {
48119
+ let name = path32[index++];
48047
48120
  if (name === "__proto__") return true;
48048
48121
  const isNumericKey = Number.isFinite(+name);
48049
- const isLast = index >= path31.length;
48122
+ const isLast = index >= path32.length;
48050
48123
  name = !name && utils_default.isArray(target) ? target.length : name;
48051
48124
  if (isLast) {
48052
48125
  if (utils_default.hasOwnProp(target, name)) {
@@ -48059,7 +48132,7 @@ function formDataToJSON(formData) {
48059
48132
  if (!target[name] || !utils_default.isObject(target[name])) {
48060
48133
  target[name] = [];
48061
48134
  }
48062
- const result = buildPath(path31, value, target[name], index);
48135
+ const result = buildPath(path32, value, target[name], index);
48063
48136
  if (result && utils_default.isArray(target[name])) {
48064
48137
  target[name] = arrayToObject(target[name]);
48065
48138
  }
@@ -50959,9 +51032,9 @@ var init_http = __esm({
50959
51032
  auth = urlUsername + ":" + urlPassword;
50960
51033
  }
50961
51034
  auth && headers.delete("authorization");
50962
- let path31;
51035
+ let path32;
50963
51036
  try {
50964
- path31 = buildURL(
51037
+ path32 = buildURL(
50965
51038
  parsed.pathname + parsed.search,
50966
51039
  config2.params,
50967
51040
  config2.paramsSerializer
@@ -50979,7 +51052,7 @@ var init_http = __esm({
50979
51052
  false
50980
51053
  );
50981
51054
  const options = {
50982
- path: path31,
51055
+ path: path32,
50983
51056
  method,
50984
51057
  headers: headers.toJSON(),
50985
51058
  agents: { http: config2.httpAgent, https: config2.httpsAgent },
@@ -51232,14 +51305,14 @@ var init_cookies = __esm({
51232
51305
  cookies_default = platform_default.hasStandardBrowserEnv ? (
51233
51306
  // Standard browser envs support document.cookie
51234
51307
  {
51235
- write(name, value, expires, path31, domain, secure, sameSite) {
51308
+ write(name, value, expires, path32, domain, secure, sameSite) {
51236
51309
  if (typeof document === "undefined") return;
51237
51310
  const cookie = [`${name}=${encodeURIComponent(value)}`];
51238
51311
  if (utils_default.isNumber(expires)) {
51239
51312
  cookie.push(`expires=${new Date(expires).toUTCString()}`);
51240
51313
  }
51241
- if (utils_default.isString(path31)) {
51242
- cookie.push(`path=${path31}`);
51314
+ if (utils_default.isString(path32)) {
51315
+ cookie.push(`path=${path32}`);
51243
51316
  }
51244
51317
  if (utils_default.isString(domain)) {
51245
51318
  cookie.push(`domain=${domain}`);
@@ -57600,8 +57673,8 @@ function formatResult4(result, input) {
57600
57673
  lines.push("## Generated Files");
57601
57674
  lines.push("");
57602
57675
  for (const file of result.files) {
57603
- const relativePath = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
57604
- lines.push(`### ${relativePath}`);
57676
+ const relativePath2 = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
57677
+ lines.push(`### ${relativePath2}`);
57605
57678
  lines.push("");
57606
57679
  lines.push("```typescript");
57607
57680
  lines.push(file.content.substring(0, 1500) + (file.content.length > 1500 ? "\n// ... (truncated)" : ""));
@@ -58486,8 +58559,8 @@ function formatResult5(result, input) {
58486
58559
  lines.push("## Generated Files");
58487
58560
  lines.push("");
58488
58561
  for (const file of result.files) {
58489
- const relativePath = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
58490
- lines.push(`### ${relativePath}`);
58562
+ const relativePath2 = file.path.replace(/\\/g, "/").split("/src/").pop() || file.path;
58563
+ lines.push(`### ${relativePath2}`);
58491
58564
  lines.push("");
58492
58565
  lines.push("```tsx");
58493
58566
  lines.push(file.content.substring(0, 2e3) + (file.content.length > 2e3 ? "\n// ... (truncated)" : ""));
@@ -58703,7 +58776,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
58703
58776
  for (const file of clientFiles) {
58704
58777
  try {
58705
58778
  const content = await readText(file);
58706
- const relativePath = path20.relative(webPath, file);
58779
+ const relativePath2 = path20.relative(webPath, file);
58707
58780
  const usesRegistry = content.includes("getRoute('") || content.includes('getRoute("');
58708
58781
  if (!usesRegistry) {
58709
58782
  const hardcodedMatch = content.match(/apiClient\.(get|post|put|delete)\s*[<(]\s*['"`]([^'"`]+)['"`]/);
@@ -58711,7 +58784,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
58711
58784
  result.apiClients.issues.push({
58712
58785
  type: "invalid-path",
58713
58786
  severity: "warning",
58714
- file: relativePath,
58787
+ file: relativePath2,
58715
58788
  message: `Hardcoded API path: ${hardcodedMatch[2]}`,
58716
58789
  suggestion: "Use getRoute() from navRoutes.generated.ts instead"
58717
58790
  });
@@ -58725,7 +58798,7 @@ async function validateApiClients(webPath, backendRoutes, result) {
58725
58798
  result.apiClients.issues.push({
58726
58799
  type: "missing-route",
58727
58800
  severity: "error",
58728
- file: relativePath,
58801
+ file: relativePath2,
58729
58802
  navRoute,
58730
58803
  message: `NavRoute "${navRoute}" not found in backend controllers`,
58731
58804
  suggestion: "Verify the NavRoute path or update the backend controller"
@@ -60106,11 +60179,11 @@ async function analyzeFile(filePath, srcPath, target) {
60106
60179
  try {
60107
60180
  const content = await readText(filePath);
60108
60181
  if (!content) return null;
60109
- const relativePath = path21.relative(srcPath, filePath).replace(/\\/g, "/");
60182
+ const relativePath2 = path21.relative(srcPath, filePath).replace(/\\/g, "/");
60110
60183
  const componentName = extractComponentName(content, filePath);
60111
60184
  const isPage = filePath.includes("/pages/") || filePath.includes("\\pages\\");
60112
60185
  const analysis = {
60113
- file: relativePath,
60186
+ file: relativePath2,
60114
60187
  componentName,
60115
60188
  type: isPage ? "page" : "component",
60116
60189
  suggestedSlots: [],
@@ -60120,7 +60193,7 @@ async function analyzeFile(filePath, srcPath, target) {
60120
60193
  imports: extractImports(content),
60121
60194
  lineCount: content.split("\n").length
60122
60195
  };
60123
- const slotPrefix = generateSlotPrefix(componentName, relativePath);
60196
+ const slotPrefix = generateSlotPrefix(componentName, relativePath2);
60124
60197
  if (target === "forms" && !analysis.hasForm) return null;
60125
60198
  if (target === "tables" && !analysis.hasTable) return null;
60126
60199
  if (isPage) {
@@ -62186,6 +62259,8 @@ async function scaffoldDataExport(input, config2) {
62186
62259
  result.instructions.push(`Add the export permission "${permissionPath}.export" to PermissionConfiguration.cs`);
62187
62260
  result.instructions.push(`Add the seed data to DataExportEndpointConfiguration or call the fragment from your seeder`);
62188
62261
  result.instructions.push(`Create an EF Core migration to apply the new seed data`);
62262
+ result.instructions.push(`Update the KNOWN_ENDPOINTS registry in prompt-data-export-m2m.ts (MCP) and ExternalAppPromptTab.tsx (frontend) with the new endpoint metadata`);
62263
+ result.instructions.push(`Use prompt_data_export_m2m to generate an integration prompt for this endpoint`);
62189
62264
  if (includeFrontendClient) {
62190
62265
  result.instructions.push(`Import the client: import { export${name}Api } from './services/api/export${name}';`);
62191
62266
  }
@@ -62205,6 +62280,7 @@ namespace ${baseNamespace}.Api.Controllers.DataExport.v1;
62205
62280
  /// <summary>
62206
62281
  /// Data Export endpoint for ${name} entities.
62207
62282
  /// Security: JWT authentication + RequirePermission + Rate limiting + DataExportAccessMiddleware
62283
+ /// Tenant isolation: Required tenantId query parameter
62208
62284
  /// </summary>
62209
62285
  [ApiController]
62210
62286
  [Route("api/v1/export")]
@@ -62220,20 +62296,34 @@ public class Export${name}Controller : ControllerBase
62220
62296
  }
62221
62297
 
62222
62298
  /// <summary>
62223
- /// Export ${name} data with pagination and optional modified-since filter.
62299
+ /// Export ${name} data with pagination, tenant isolation, and optional modified-since filter.
62224
62300
  /// </summary>
62225
62301
  [HttpGet("${namePlural}")]
62226
62302
  [RequirePermission(Permissions.${permissionPath.split(".").map((s) => toPascalCase2(s)).join(".")}.Export)]
62303
+ [ProducesResponseType(typeof(PaginatedResult<Export${name}Dto>), StatusCodes.Status200OK)]
62304
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
62305
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
62227
62306
  public async Task<ActionResult<PaginatedResult<Export${name}Dto>>> Export(
62307
+ [FromQuery] Guid tenantId,
62228
62308
  [FromQuery] int page = 1,
62229
62309
  [FromQuery] int pageSize = 100,
62230
62310
  [FromQuery] DateTime? modifiedSince = null,
62231
62311
  CancellationToken ct = default)
62232
62312
  {
62313
+ if (tenantId == Guid.Empty)
62314
+ {
62315
+ return BadRequest(new ProblemDetails
62316
+ {
62317
+ Title = "Invalid Tenant",
62318
+ Detail = "tenantId is required and must be a valid GUID",
62319
+ Status = StatusCodes.Status400BadRequest
62320
+ });
62321
+ }
62322
+
62233
62323
  pageSize = Math.Clamp(pageSize, 1, ${maxPageSize});
62234
62324
  page = Math.Max(1, page);
62235
62325
 
62236
- var result = await _exportService.ExportAsync(page, pageSize, modifiedSince, ct);
62326
+ var result = await _exportService.ExportAsync(tenantId, page, pageSize, modifiedSince, ct);
62237
62327
  return Ok(result);
62238
62328
  }
62239
62329
  }
@@ -62247,13 +62337,15 @@ namespace ${baseNamespace}.Application.Common.Interfaces;
62247
62337
 
62248
62338
  /// <summary>
62249
62339
  /// Service interface for ${name} data export.
62340
+ /// Enforces tenant isolation via tenantId parameter.
62250
62341
  /// </summary>
62251
62342
  public interface IExport${name}Service
62252
62343
  {
62253
62344
  /// <summary>
62254
- /// Export ${name} data with pagination and optional modified-since filter.
62345
+ /// Export ${name} data with tenant isolation, pagination, and optional modified-since filter.
62255
62346
  /// </summary>
62256
62347
  Task<PaginatedResult<Export${name}Dto>> ExportAsync(
62348
+ Guid tenantId,
62257
62349
  int page = 1,
62258
62350
  int pageSize = 100,
62259
62351
  DateTime? modifiedSince = null,
@@ -62282,35 +62374,35 @@ namespace ${baseNamespace}.Infrastructure.Services.DataExport;
62282
62374
 
62283
62375
  /// <summary>
62284
62376
  /// Data export service for ${name} entities.
62285
- /// Enforces tenant isolation via ICurrentTenantService.
62377
+ /// Enforces tenant isolation via tenantId parameter validation.
62286
62378
  /// </summary>
62287
62379
  public class Export${name}Service : IExport${name}Service
62288
62380
  {
62289
62381
  private readonly ApplicationDbContext _db;
62290
- private readonly ICurrentTenantService _currentTenant;
62291
62382
  private readonly ICurrentUserService _currentUser;
62292
62383
  private readonly ILogger<Export${name}Service> _logger;
62293
62384
 
62294
62385
  public Export${name}Service(
62295
62386
  ApplicationDbContext db,
62296
- ICurrentTenantService currentTenant,
62297
62387
  ICurrentUserService currentUser,
62298
62388
  ILogger<Export${name}Service> logger)
62299
62389
  {
62300
62390
  _db = db;
62301
- _currentTenant = currentTenant;
62302
62391
  _currentUser = currentUser;
62303
62392
  _logger = logger;
62304
62393
  }
62305
62394
 
62306
62395
  public async Task<PaginatedResult<Export${name}Dto>> ExportAsync(
62396
+ Guid tenantId,
62307
62397
  int page = 1,
62308
62398
  int pageSize = 100,
62309
62399
  DateTime? modifiedSince = null,
62310
62400
  CancellationToken ct = default)
62311
62401
  {
62312
- var tenantId = _currentTenant.TenantId
62313
- ?? throw new UnauthorizedAccessException("Tenant context is required");
62402
+ if (tenantId == Guid.Empty)
62403
+ {
62404
+ throw new ArgumentException("Tenant ID cannot be empty", nameof(tenantId));
62405
+ }
62314
62406
 
62315
62407
  _logger.LogInformation("Data export requested for ${name} by {UserId}, tenant {TenantId}, page {Page}",
62316
62408
  _currentUser.UserId, tenantId, page);
@@ -62401,6 +62493,7 @@ function generateFrontendClient(name, nameLower, namePlural) {
62401
62493
  *
62402
62494
  * Auto-generated by SmartStack MCP - DO NOT EDIT MANUALLY
62403
62495
  * Endpoint: /api/v1/export/${namePlural}
62496
+ * Note: tenantId is required and must be passed for all requests
62404
62497
  */
62405
62498
 
62406
62499
  import { apiClient } from '../lib/apiClient';
@@ -62413,6 +62506,7 @@ export interface Export${name}Dto {
62413
62506
  }
62414
62507
 
62415
62508
  export interface ExportParams {
62509
+ tenantId: string; // Required: tenant identifier
62416
62510
  page?: number;
62417
62511
  pageSize?: number;
62418
62512
  modifiedSince?: string;
@@ -62432,19 +62526,25 @@ const EXPORT_ENDPOINT = '/api/v1/export/${namePlural}';
62432
62526
 
62433
62527
  export const export${name}Api = {
62434
62528
  /**
62435
- * Fetch paginated export data
62529
+ * Fetch paginated export data for a specific tenant
62530
+ * @param tenantId - Required tenant identifier
62531
+ * @param params - Pagination and filter parameters
62436
62532
  */
62437
- async getPage(params?: ExportParams): Promise<PaginatedExportResult<Export${name}Dto>> {
62438
- const response = await apiClient.get<PaginatedExportResult<Export${name}Dto>>(EXPORT_ENDPOINT, { params });
62533
+ async getPage(tenantId: string, params?: Omit<ExportParams, 'tenantId'>): Promise<PaginatedExportResult<Export${name}Dto>> {
62534
+ const response = await apiClient.get<PaginatedExportResult<Export${name}Dto>>(EXPORT_ENDPOINT, {
62535
+ params: { tenantId, ...params }
62536
+ });
62439
62537
  return response.data;
62440
62538
  },
62441
62539
 
62442
62540
  /**
62443
- * Download all pages as a blob (CSV/JSON)
62541
+ * Download all pages as a blob (CSV/JSON) for a specific tenant
62542
+ * @param tenantId - Required tenant identifier
62543
+ * @param format - Output format (default: json)
62444
62544
  */
62445
- async downloadAll(format: 'json' | 'csv' = 'json'): Promise<Blob> {
62545
+ async downloadAll(tenantId: string, format: 'json' | 'csv' = 'json'): Promise<Blob> {
62446
62546
  const response = await apiClient.get(EXPORT_ENDPOINT, {
62447
- params: { pageSize: 1000, format },
62547
+ params: { tenantId, pageSize: 1000, format },
62448
62548
  responseType: 'blob',
62449
62549
  });
62450
62550
  return response.data;
@@ -62482,8 +62582,8 @@ function formatResult9(result, input) {
62482
62582
  lines.push("## Generated Files");
62483
62583
  lines.push("");
62484
62584
  for (const file of result.files) {
62485
- const relativePath = file.path.replace(/\\/g, "/");
62486
- const parts = relativePath.split("/");
62585
+ const relativePath2 = file.path.replace(/\\/g, "/");
62586
+ const parts = relativePath2.split("/");
62487
62587
  const srcIdx = parts.findIndex((p) => ["Controllers", "Application", "Infrastructure", "services", "Persistence"].includes(p));
62488
62588
  const displayPath = srcIdx >= 0 ? parts.slice(srcIdx).join("/") : parts.slice(-3).join("/");
62489
62589
  lines.push(`### ${displayPath}`);
@@ -62601,8 +62701,581 @@ documented in smartstack-api.md.`,
62601
62701
  }
62602
62702
  });
62603
62703
 
62604
- // src/mcp/tools/analyze-hierarchy-patterns.ts
62704
+ // src/mcp/tools/prompt-data-export-m2m.ts
62605
62705
  import path26 from "path";
62706
+ async function handlePromptDataExportM2m(args, config2) {
62707
+ const input = PromptDataExportM2mInputSchema.parse(args);
62708
+ logger.info("Generating M2M data export prompt", {
62709
+ endpoints: input.endpoints,
62710
+ clientId: input.clientId ? "***" : void 0,
62711
+ format: input.options?.format
62712
+ });
62713
+ let selectedEndpoints;
62714
+ if (input.endpoints && input.endpoints.length > 0) {
62715
+ selectedEndpoints = KNOWN_ENDPOINTS.filter((e) => input.endpoints.includes(e.code));
62716
+ const unknown2 = input.endpoints.filter((code) => !KNOWN_ENDPOINTS.find((e) => e.code === code));
62717
+ if (unknown2.length > 0) {
62718
+ const discovered = await discoverEndpointsFromProject(config2, unknown2);
62719
+ selectedEndpoints.push(...discovered);
62720
+ const stillUnknown = unknown2.filter((code) => !discovered.find((e) => e.code === code));
62721
+ if (stillUnknown.length > 0) {
62722
+ logger.warn("Unknown endpoint codes", { codes: stillUnknown });
62723
+ }
62724
+ }
62725
+ } else {
62726
+ selectedEndpoints = [...KNOWN_ENDPOINTS];
62727
+ const discovered = await discoverEndpointsFromProject(config2);
62728
+ for (const ep of discovered) {
62729
+ if (!selectedEndpoints.find((e) => e.code === ep.code)) {
62730
+ selectedEndpoints.push(ep);
62731
+ }
62732
+ }
62733
+ }
62734
+ if (selectedEndpoints.length === 0) {
62735
+ return "# No Endpoints Found\n\nNo data export endpoints were found. Use `scaffold_data_export` to create one.";
62736
+ }
62737
+ const format = input.options?.format ?? "full";
62738
+ const includeBase = input.options?.includeBasePrompt !== false;
62739
+ const clientId = input.clientId ?? "<your-client-id>";
62740
+ const prompt = buildPrompt(selectedEndpoints, clientId, format, includeBase);
62741
+ return `# M2M Data Export Integration Prompt
62742
+
62743
+ **Endpoints included**: ${selectedEndpoints.map((e) => e.code).join(", ")}
62744
+ **Format**: ${format}
62745
+
62746
+ ---
62747
+
62748
+ \`\`\`markdown
62749
+ ${prompt}
62750
+ \`\`\`
62751
+
62752
+ ---
62753
+
62754
+ > Copy the content above (inside the code block) and paste it into your VibeCoding session.
62755
+ > The AI will have all the context needed to implement the full M2M client.`;
62756
+ }
62757
+ function buildPrompt(endpoints, clientId, format, includeBase) {
62758
+ const lines = [];
62759
+ if (includeBase && format === "full") {
62760
+ lines.push("## Contexte");
62761
+ lines.push("");
62762
+ lines.push("Tu dois implementer un client HTTP qui consomme l'API DataExport de SmartStack en mode **machine-to-machine** (M2M). Il n'y a aucune interaction utilisateur : ton application s'authentifie avec un `clientId` et un `clientSecret`, obtient un token JWT, puis appelle les endpoints d'export de donnees.");
62763
+ lines.push("");
62764
+ lines.push("---");
62765
+ lines.push("");
62766
+ lines.push("## 1. Authentification (HMAC-JWT Assertion)");
62767
+ lines.push("");
62768
+ lines.push("L'authentification se fait en 2 etapes :");
62769
+ lines.push("");
62770
+ lines.push("### Etape 1 : Creer une assertion JWT signee HMAC-SHA256");
62771
+ lines.push("");
62772
+ lines.push("Ton application doit generer un JWT **signe avec le `clientSecret`** (HMAC-SHA256) :");
62773
+ lines.push("");
62774
+ lines.push("```");
62775
+ lines.push('Header: { "alg": "HS256", "typ": "JWT" }');
62776
+ lines.push("Payload: {");
62777
+ lines.push(` "sub": "${clientId}", // ton clientId`);
62778
+ lines.push(' "iat": <timestamp-unix-actuel>, // timestamp Unix actuel');
62779
+ lines.push(' "exp": <timestamp-unix + 300>, // expiration (max 5 min apres iat)');
62780
+ lines.push(' "jti": "<uuid-unique>" // nonce anti-replay');
62781
+ lines.push("}");
62782
+ lines.push("Signature: HMAC-SHA256(header.payload, Base64Decode(clientSecret))");
62783
+ lines.push("```");
62784
+ lines.push("");
62785
+ lines.push("**Important** : Le `clientSecret` est encode en Base64. Tu dois le **decoder** avant de l'utiliser comme cle HMAC.");
62786
+ lines.push("");
62787
+ lines.push("### Etape 2 : Echanger l'assertion contre un access token");
62788
+ lines.push("");
62789
+ lines.push("```http");
62790
+ lines.push("POST {baseUrl}/api/auth/external-app/token");
62791
+ lines.push("Content-Type: application/json");
62792
+ lines.push("");
62793
+ lines.push("{");
62794
+ lines.push(' "assertion": "<jwt-assertion-de-etape-1>"');
62795
+ lines.push("}");
62796
+ lines.push("```");
62797
+ lines.push("");
62798
+ lines.push("**Reponse succes (200)** :");
62799
+ lines.push("```json");
62800
+ lines.push("{");
62801
+ lines.push(' "accessToken": "eyJhbGciOiJIUz...",');
62802
+ lines.push(' "expiresAt": "2026-02-25T11:00:00Z",');
62803
+ lines.push(' "permissions": ["administration.users.read", ...]');
62804
+ lines.push("}");
62805
+ lines.push("```");
62806
+ lines.push("");
62807
+ lines.push("### Erreurs d'authentification");
62808
+ lines.push("");
62809
+ lines.push("| HTTP | errorCode | Cause |");
62810
+ lines.push("|------|-----------|-------|");
62811
+ lines.push("| 401 | `invalid_assertion` | JWT mal forme ou invalide |");
62812
+ lines.push("| 401 | `missing_client_id` | Claim `sub` absent du JWT |");
62813
+ lines.push("| 401 | `unknown_client` | ClientId inconnu |");
62814
+ lines.push("| 401 | `app_inactive` | Application desactivee |");
62815
+ lines.push("| 401 | `ip_blocked` | IP pas dans la whitelist |");
62816
+ lines.push("| 401 | `invalid_signature` | Signature HMAC invalide |");
62817
+ lines.push("");
62818
+ lines.push("### Regles de gestion du token");
62819
+ lines.push("");
62820
+ lines.push("- Cache le token et renouvelle-le 5 min avant `expiresAt`");
62821
+ lines.push("- N'appelle PAS `/token` a chaque requete");
62822
+ lines.push("");
62823
+ lines.push("---");
62824
+ lines.push("");
62825
+ }
62826
+ lines.push(`## ${format === "full" ? "2" : "1"}. Endpoints a implementer (${endpoints.length})`);
62827
+ lines.push("");
62828
+ for (const ep of endpoints) {
62829
+ lines.push(`### ${ep.name}`);
62830
+ lines.push("");
62831
+ lines.push("```http");
62832
+ lines.push(`${ep.method} {baseUrl}${ep.route}`);
62833
+ lines.push("Authorization: Bearer <accessToken>");
62834
+ lines.push("```");
62835
+ lines.push("");
62836
+ if (ep.params.length > 0) {
62837
+ lines.push("**Parametres (query string)** :");
62838
+ lines.push("");
62839
+ lines.push("| Parametre | Type | Defaut | Description |");
62840
+ lines.push("|-----------|------|--------|-------------|");
62841
+ for (const p of ep.params) {
62842
+ lines.push(`| \`${p.name}\` | ${p.type} | ${p.default} | ${p.description} |`);
62843
+ }
62844
+ lines.push("");
62845
+ } else {
62846
+ lines.push("**Aucun parametre** - appel direct sans query string.");
62847
+ lines.push("");
62848
+ }
62849
+ if (ep.paginated) {
62850
+ lines.push("**Reponse (paginee)** :");
62851
+ lines.push("```json");
62852
+ lines.push("{");
62853
+ lines.push(' "items": [');
62854
+ lines.push(" {");
62855
+ for (let i = 0; i < ep.responseFields.length; i++) {
62856
+ const f = ep.responseFields[i];
62857
+ const comma = i < ep.responseFields.length - 1 ? "," : "";
62858
+ lines.push(` "${f.name}": ${getJsonExample(f.type)}${comma} // ${f.type} - ${f.description}`);
62859
+ }
62860
+ lines.push(" }");
62861
+ lines.push(" ],");
62862
+ lines.push(' "page": 1,');
62863
+ lines.push(' "pageSize": 100,');
62864
+ lines.push(' "totalCount": 250,');
62865
+ lines.push(' "totalPages": 3,');
62866
+ lines.push(' "hasMore": true');
62867
+ lines.push("}");
62868
+ lines.push("```");
62869
+ } else {
62870
+ lines.push("**Reponse (tableau direct, NON pagine)** :");
62871
+ lines.push("```json");
62872
+ lines.push("[");
62873
+ lines.push(" {");
62874
+ for (let i = 0; i < ep.responseFields.length; i++) {
62875
+ const f = ep.responseFields[i];
62876
+ const comma = i < ep.responseFields.length - 1 ? "," : "";
62877
+ lines.push(` "${f.name}": ${getJsonExample(f.type)}${comma} // ${f.type} - ${f.description}`);
62878
+ }
62879
+ lines.push(" }");
62880
+ lines.push("]");
62881
+ lines.push("```");
62882
+ }
62883
+ lines.push("");
62884
+ lines.push(`**Rate limit** : ${ep.defaultRateLimit} requetes/minute | Page size max : ${ep.defaultMaxPageSize}`);
62885
+ if (ep.notes) {
62886
+ lines.push(`**Note** : ${ep.notes}`);
62887
+ }
62888
+ lines.push("");
62889
+ }
62890
+ if (format === "full") {
62891
+ lines.push("---");
62892
+ lines.push("");
62893
+ lines.push("## 3. Isolation par tenant");
62894
+ lines.push("");
62895
+ lines.push("**Important** : Tous les endpoints (sauf `navigation`) necessitent le parametre `tenantId` pour delimiter l'export a un tenant specifique. Cela assure la securite multi-tenant et evite les fuites de donnees.");
62896
+ lines.push("");
62897
+ lines.push("```");
62898
+ lines.push("GET /api/v1/export/users?tenantId=<uuid>&page=1");
62899
+ lines.push("GET /api/v1/export/roles?tenantId=<uuid>&page=1");
62900
+ lines.push("GET /api/v1/export/tickets?tenantId=<uuid>&page=1");
62901
+ lines.push("```");
62902
+ lines.push("");
62903
+ lines.push("Exemple avec boucle :");
62904
+ lines.push("```");
62905
+ lines.push("1. Recuprer la liste de tous les tenants via GET /api/v1/export/navigation");
62906
+ lines.push("2. Pour chaque tenant, exporter ses donnees : users, roles, tickets");
62907
+ lines.push("3. Concatener les resultats ou synchroniser par tenant");
62908
+ lines.push("```");
62909
+ lines.push("");
62910
+ lines.push("---");
62911
+ lines.push("");
62912
+ const hasPaginated = endpoints.some((e) => e.paginated);
62913
+ if (hasPaginated) {
62914
+ lines.push("## 4. Pagination");
62915
+ lines.push("");
62916
+ lines.push("Pour parcourir toutes les pages : boucle tant que `hasMore === true`, en incrementant `page`.");
62917
+ lines.push("Les resultats sont tries par `createdAt ASC` (ordre stable).");
62918
+ lines.push("");
62919
+ lines.push("---");
62920
+ lines.push("");
62921
+ lines.push("## 5. Synchronisation incrementale");
62922
+ lines.push("");
62923
+ lines.push("Utilise `modifiedSince` pour ne recuperer que les changements :");
62924
+ lines.push("1. Premiere synchro : appelle sans `modifiedSince` (recupere tout)");
62925
+ lines.push("2. Stocke `DateTime.UtcNow` du debut de la synchro");
62926
+ lines.push("3. Synchros suivantes : passe `modifiedSince=<timestamp-stocke>`");
62927
+ lines.push("");
62928
+ lines.push("Le filtre retourne les entites ou `UpdatedAt >= modifiedSince OR CreatedAt >= modifiedSince`.");
62929
+ lines.push("");
62930
+ lines.push("---");
62931
+ lines.push("");
62932
+ }
62933
+ lines.push("## 6. Rate Limiting");
62934
+ lines.push("");
62935
+ lines.push("| Aspect | Valeur |");
62936
+ lines.push("|--------|--------|");
62937
+ lines.push("| Algorithme | Fenetre fixe de 1 minute |");
62938
+ lines.push("| Cle de partition | {clientId}:{endpointCode} (independant par endpoint) |");
62939
+ lines.push("");
62940
+ lines.push("Quand tu depasses la limite :");
62941
+ lines.push("```");
62942
+ lines.push("HTTP/1.1 429 Too Many Requests");
62943
+ lines.push("Retry-After: 42");
62944
+ lines.push("```");
62945
+ lines.push("Lis le header `Retry-After` et attends. Implemente un backoff exponentiel.");
62946
+ lines.push("");
62947
+ lines.push("---");
62948
+ lines.push("");
62949
+ lines.push("## 7. Gestion des erreurs");
62950
+ lines.push("");
62951
+ lines.push("| HTTP | Action |");
62952
+ lines.push("|------|--------|");
62953
+ lines.push("| 200 | Traite les donnees |");
62954
+ lines.push("| 400 | TenantId manquant ou invalide - verifie le parametre |");
62955
+ lines.push("| 401 | Renouvelle le token et relance |");
62956
+ lines.push("| 403 | Acces export non accorde - contacte l'admin |");
62957
+ lines.push("| 404 | Tenant non trouve - verifie le tenantId |");
62958
+ lines.push("| 429 | Attends `Retry-After` secondes |");
62959
+ lines.push("| 500 | Retry avec backoff exponentiel |");
62960
+ lines.push("");
62961
+ lines.push("---");
62962
+ lines.push("");
62963
+ lines.push("## 8. Checklist d'implementation");
62964
+ lines.push("");
62965
+ lines.push("### 8.1 Configuration");
62966
+ lines.push("```");
62967
+ lines.push("SMARTSTACK_BASE_URL=https://smartstack.example.com");
62968
+ lines.push(`SMARTSTACK_CLIENT_ID=${clientId}`);
62969
+ lines.push("SMARTSTACK_CLIENT_SECRET=<base64-encoded-secret>");
62970
+ lines.push("TENANT_IDS=<comma-separated-uuids> # Ou recuperer dynamiquement via navigation");
62971
+ lines.push("```");
62972
+ lines.push("Ne hardcode JAMAIS le clientId/clientSecret. Utilise des variables d'environnement ou un vault.");
62973
+ lines.push("");
62974
+ lines.push("### 8.2 Service d'authentification");
62975
+ lines.push("```");
62976
+ lines.push("SmartStackAuthService:");
62977
+ lines.push(" - generateAssertion() \u2192 JWT HMAC-SHA256 signe avec le secret decode du Base64");
62978
+ lines.push(" - getAccessToken() \u2192 POST /api/auth/external-app/token");
62979
+ lines.push(" - cachedToken / expiresAt \u2192 cache le token, renouvelle 5 min avant expiration");
62980
+ lines.push("```");
62981
+ lines.push("");
62982
+ lines.push("### 8.3 Client HTTP generique");
62983
+ lines.push("```");
62984
+ lines.push("SmartStackExportClient:");
62985
+ lines.push(" - fetchExport(endpointCode, tenantId, page?, pageSize?, modifiedSince?, extraParams?)");
62986
+ lines.push(" \u2192 GET /api/v1/export/{endpointCode}?tenantId=<uuid>");
62987
+ lines.push(" - fetchAllPages(endpointCode, tenantId, pageSize?, modifiedSince?)");
62988
+ lines.push(" \u2192 boucle automatique sur toutes les pages avec tenantId");
62989
+ lines.push(" - Gere 400 (tenantId invalide), 401 (retry token), 404 (tenant non trouve), 429 (Retry-After), 5xx (backoff)");
62990
+ lines.push("```");
62991
+ lines.push("");
62992
+ lines.push("### 8.4 Synchroniseur incremental par tenant");
62993
+ lines.push("```");
62994
+ lines.push("SmartStackSyncService:");
62995
+ lines.push(` - endpoints: [${endpoints.map((e) => `"${e.code}"`).join(", ")}]`);
62996
+ lines.push(" - syncAllTenants() \u2192 pour chaque tenant, appelle syncTenant()");
62997
+ lines.push(" - syncTenant(tenantId):");
62998
+ lines.push(" 1. Pour chaque endpoint (sauf navigation):");
62999
+ lines.push(" a. Lit le dernier timestamp de synchro pour ce tenant+endpoint");
63000
+ lines.push(" b. Appelle fetchAllPages(endpoint, tenantId, modifiedSince)");
63001
+ lines.push(" c. Upsert les donnees (INSERT si nouveau, UPDATE si existant, par `id`)");
63002
+ lines.push(" d. Sauve le timestamp");
63003
+ lines.push("```");
63004
+ lines.push("");
63005
+ lines.push("### 8.5 Securite");
63006
+ lines.push("");
63007
+ lines.push("- Stocke le clientSecret dans un vault (Azure Key Vault, etc.)");
63008
+ lines.push("- L'assertion JWT a une duree courte (5 min max)");
63009
+ lines.push("- Ne log jamais le token d'acces");
63010
+ lines.push("- Toutes les requetes sont auditees cote SmartStack (IP, User-Agent)");
63011
+ }
63012
+ return lines.join("\n");
63013
+ }
63014
+ async function discoverEndpointsFromProject(config2, filterCodes) {
63015
+ const discovered = [];
63016
+ try {
63017
+ const structure = await findSmartStackStructure(config2.smartstack.projectPath);
63018
+ if (!structure.api) return discovered;
63019
+ const exportControllerDir = path26.join(structure.api, "Controllers", "DataExport", "v1");
63020
+ const { findFiles: findFiles2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
63021
+ const controllerFiles = await findFiles2(path26.join(exportControllerDir, "Export*Controller.cs"));
63022
+ for (const file of controllerFiles) {
63023
+ const content = await readText(file);
63024
+ if (!content) continue;
63025
+ const routeMatch = content.match(/\[HttpGet\("api\/v1\/export\/([^"]+)"\)\]/);
63026
+ if (!routeMatch) continue;
63027
+ const code = routeMatch[1];
63028
+ if (KNOWN_ENDPOINTS.find((e) => e.code === code)) continue;
63029
+ if (filterCodes && !filterCodes.includes(code)) continue;
63030
+ const classMatch = content.match(/class Export(\w+)Controller/);
63031
+ const entityName = classMatch ? classMatch[1] : code;
63032
+ discovered.push({
63033
+ code,
63034
+ name: `${entityName} Export`,
63035
+ route: `/api/v1/export/${code}`,
63036
+ method: "GET",
63037
+ paginated: content.includes("PaginatedExportResult"),
63038
+ params: [
63039
+ { name: "page", type: "int", default: "1", description: "Numero de page" },
63040
+ { name: "pageSize", type: "int", default: "100", description: "Elements par page" },
63041
+ { name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC" }
63042
+ ],
63043
+ responseFields: [
63044
+ { name: "id", type: "UUID", description: "Identifiant unique" },
63045
+ { name: "...", type: "...", description: `Voir Export${entityName}Dto pour la structure complete` }
63046
+ ],
63047
+ defaultRateLimit: 60,
63048
+ defaultMaxPageSize: 1e3,
63049
+ notes: `Endpoint decouvert automatiquement. Verifier le DTO Export${entityName}Dto pour la structure exacte.`
63050
+ });
63051
+ }
63052
+ } catch {
63053
+ logger.debug("Could not discover endpoints from project");
63054
+ }
63055
+ return discovered;
63056
+ }
63057
+ function getJsonExample(type) {
63058
+ switch (type) {
63059
+ case "UUID":
63060
+ return '"3fa85f64-5717-4562-b3fc-2c963f66afa6"';
63061
+ case "UUID?":
63062
+ return "null";
63063
+ case "string":
63064
+ return '"..."';
63065
+ case "string?":
63066
+ return '"..."';
63067
+ case "boolean":
63068
+ return "true";
63069
+ case "int":
63070
+ return "0";
63071
+ case "DateTime":
63072
+ return '"2026-01-15T10:30:00Z"';
63073
+ case "DateTime?":
63074
+ return "null";
63075
+ case "string[]":
63076
+ return '["item1", "item2"]';
63077
+ case "UUID[]":
63078
+ return '["uuid1", "uuid2"]';
63079
+ case "object[]":
63080
+ return '[{"id": "...", "code": "...", "label": "..."}]';
63081
+ default:
63082
+ return '"..."';
63083
+ }
63084
+ }
63085
+ var promptDataExportM2mTool, KNOWN_ENDPOINTS;
63086
+ var init_prompt_data_export_m2m = __esm({
63087
+ "src/mcp/tools/prompt-data-export-m2m.ts"() {
63088
+ "use strict";
63089
+ init_esm_shims();
63090
+ init_types3();
63091
+ init_logger();
63092
+ init_detector();
63093
+ init_fs();
63094
+ promptDataExportM2mTool = {
63095
+ name: "prompt_data_export_m2m",
63096
+ description: `Generate a ready-to-use M2M integration prompt for SmartStack Data Export API.
63097
+
63098
+ Creates a complete prompt containing:
63099
+ - HMAC-JWT authentication flow (assertion + token exchange)
63100
+ - Selected endpoint details (route, parameters, response structure)
63101
+ - Pagination and incremental sync patterns
63102
+ - Rate limiting and error handling
63103
+ - Implementation checklist (auth service, HTTP client, sync service)
63104
+
63105
+ The prompt is designed to be pasted into a VibeCoding session (Claude, Cursor, etc.)
63106
+ so the AI can implement the full client without additional context.
63107
+
63108
+ Example:
63109
+ prompt_data_export_m2m endpoints=["users","tenants","roles"]
63110
+ prompt_data_export_m2m endpoints=["users"] clientId="app_abc123"
63111
+
63112
+ If no endpoints specified, scans the project to discover all available export endpoints.`,
63113
+ inputSchema: {
63114
+ type: "object",
63115
+ properties: {
63116
+ endpoints: {
63117
+ type: "array",
63118
+ items: { type: "string" },
63119
+ description: 'Endpoint codes to include (e.g., ["users", "tenants", "roles"]). If empty, discovers all available endpoints.'
63120
+ },
63121
+ clientId: {
63122
+ type: "string",
63123
+ description: 'Optional clientId to pre-fill in the prompt (e.g., "app_abc123...")'
63124
+ },
63125
+ options: {
63126
+ type: "object",
63127
+ properties: {
63128
+ includeBasePrompt: {
63129
+ type: "boolean",
63130
+ default: true,
63131
+ description: "Include authentication and common sections (default: true)"
63132
+ },
63133
+ format: {
63134
+ type: "string",
63135
+ enum: ["full", "endpoints-only"],
63136
+ default: "full",
63137
+ description: 'Output format: "full" includes auth+endpoints+checklist, "endpoints-only" just endpoint details'
63138
+ }
63139
+ }
63140
+ }
63141
+ }
63142
+ }
63143
+ };
63144
+ KNOWN_ENDPOINTS = [
63145
+ {
63146
+ code: "users",
63147
+ name: "Users Export",
63148
+ route: "/api/v1/export/users",
63149
+ method: "GET",
63150
+ paginated: true,
63151
+ params: [
63152
+ { name: "tenantId", type: "UUID", default: "required", description: "ID du tenant pour limiter l'export a ses utilisateurs" },
63153
+ { name: "page", type: "int", default: "1", description: "Numero de page (commence a 1)" },
63154
+ { name: "pageSize", type: "int", default: "100", description: "Elements par page (max 1000)" },
63155
+ { name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC - entites creees ou modifiees apres cette date" },
63156
+ { name: "isActive", type: "bool?", default: "null", description: "Filtre par statut actif/inactif" }
63157
+ ],
63158
+ responseFields: [
63159
+ { name: "id", type: "UUID", description: "Identifiant unique" },
63160
+ { name: "email", type: "string", description: "Adresse email" },
63161
+ { name: "firstName", type: "string?", description: "Prenom" },
63162
+ { name: "lastName", type: "string?", description: "Nom de famille" },
63163
+ { name: "displayName", type: "string?", description: "Nom affiche" },
63164
+ { name: "isActive", type: "boolean", description: "Compte actif" },
63165
+ { name: "createdAt", type: "DateTime", description: "Date de creation (UTC)" },
63166
+ { name: "updatedAt", type: "DateTime?", description: "Date de derniere modification (UTC)" },
63167
+ { name: "roles", type: "string[]", description: "Noms des roles attribues" },
63168
+ { name: "tenantIds", type: "UUID[]", description: "IDs des tenants associes" }
63169
+ ],
63170
+ defaultRateLimit: 60,
63171
+ defaultMaxPageSize: 1e3
63172
+ },
63173
+ {
63174
+ code: "tenants",
63175
+ name: "Tenants Export",
63176
+ route: "/api/v1/export/tenants",
63177
+ method: "GET",
63178
+ paginated: true,
63179
+ params: [
63180
+ { name: "tenantId", type: "UUID", default: "required", description: "ID du tenant a exporter - retourne le tenant specifie et ses donnees associees" },
63181
+ { name: "page", type: "int", default: "1", description: "Numero de page (commence a 1)" },
63182
+ { name: "pageSize", type: "int", default: "100", description: "Elements par page (max 1000)" },
63183
+ { name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC" }
63184
+ ],
63185
+ responseFields: [
63186
+ { name: "id", type: "UUID", description: "Identifiant unique" },
63187
+ { name: "name", type: "string", description: "Nom du tenant" },
63188
+ { name: "slug", type: "string", description: "Slug URL-friendly" },
63189
+ { name: "type", type: "string", description: "Type de tenant" },
63190
+ { name: "status", type: "string", description: "Statut (Active, Suspended, etc.)" },
63191
+ { name: "description", type: "string?", description: "Description" },
63192
+ { name: "createdAt", type: "DateTime", description: "Date de creation (UTC)" },
63193
+ { name: "updatedAt", type: "DateTime?", description: "Date de derniere modification (UTC)" },
63194
+ { name: "memberCount", type: "int", description: "Nombre de membres" }
63195
+ ],
63196
+ defaultRateLimit: 60,
63197
+ defaultMaxPageSize: 1e3
63198
+ },
63199
+ {
63200
+ code: "roles",
63201
+ name: "Roles Export",
63202
+ route: "/api/v1/export/roles",
63203
+ method: "GET",
63204
+ paginated: true,
63205
+ params: [
63206
+ { name: "tenantId", type: "UUID", default: "required", description: "ID du tenant pour limiter l'export a ses roles" },
63207
+ { name: "page", type: "int", default: "1", description: "Numero de page (commence a 1)" },
63208
+ { name: "pageSize", type: "int", default: "100", description: "Elements par page (max 1000)" },
63209
+ { name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC" }
63210
+ ],
63211
+ responseFields: [
63212
+ { name: "id", type: "UUID", description: "Identifiant unique" },
63213
+ { name: "name", type: "string", description: "Nom du role" },
63214
+ { name: "shortName", type: "string?", description: "Nom court" },
63215
+ { name: "code", type: "string?", description: "Code technique" },
63216
+ { name: "category", type: "string", description: "Categorie (Global, Admin, Manager, Contributor, Viewer, Custom)" },
63217
+ { name: "description", type: "string?", description: "Description du role" },
63218
+ { name: "isSystem", type: "boolean", description: "Role systeme (non modifiable)" },
63219
+ { name: "createdAt", type: "DateTime", description: "Date de creation (UTC)" },
63220
+ { name: "updatedAt", type: "DateTime?", description: "Date de derniere modification (UTC)" },
63221
+ { name: "permissions", type: "string[]", description: "Codes des permissions attribuees" }
63222
+ ],
63223
+ defaultRateLimit: 60,
63224
+ defaultMaxPageSize: 1e3
63225
+ },
63226
+ {
63227
+ code: "tickets",
63228
+ name: "Tickets Export",
63229
+ route: "/api/v1/export/tickets",
63230
+ method: "GET",
63231
+ paginated: true,
63232
+ params: [
63233
+ { name: "tenantId", type: "UUID", default: "required", description: "ID du tenant pour limiter l'export a ses tickets" },
63234
+ { name: "page", type: "int", default: "1", description: "Numero de page (commence a 1)" },
63235
+ { name: "pageSize", type: "int", default: "100", description: "Elements par page (max 500)" },
63236
+ { name: "modifiedSince", type: "DateTime?", default: "null", description: "Filtre incremental UTC" }
63237
+ ],
63238
+ responseFields: [
63239
+ { name: "id", type: "UUID", description: "Identifiant unique" },
63240
+ { name: "title", type: "string", description: "Titre du ticket" },
63241
+ { name: "type", type: "string", description: "Type (Bug, Feature, Support, etc.)" },
63242
+ { name: "status", type: "string", description: "Statut (Open, InProgress, Resolved, Closed)" },
63243
+ { name: "priority", type: "string", description: "Priorite (Low, Medium, High, Critical)" },
63244
+ { name: "assignedToUserId", type: "UUID?", description: "ID utilisateur assigne" },
63245
+ { name: "createdByUserId", type: "UUID", description: "ID createur" },
63246
+ { name: "tenantId", type: "UUID?", description: "ID tenant associe" },
63247
+ { name: "createdAt", type: "DateTime", description: "Date de creation (UTC)" },
63248
+ { name: "updatedAt", type: "DateTime?", description: "Date de derniere modification (UTC)" },
63249
+ { name: "resolvedAt", type: "DateTime?", description: "Date de resolution (UTC)" },
63250
+ { name: "closedAt", type: "DateTime?", description: "Date de fermeture (UTC)" }
63251
+ ],
63252
+ defaultRateLimit: 30,
63253
+ defaultMaxPageSize: 500,
63254
+ notes: "Rate limit reduit a 30 req/min. Page size max 500."
63255
+ },
63256
+ {
63257
+ code: "navigation",
63258
+ name: "Navigation Export",
63259
+ route: "/api/v1/export/navigation",
63260
+ method: "GET",
63261
+ paginated: false,
63262
+ params: [],
63263
+ responseFields: [
63264
+ { name: "id", type: "UUID", description: "Identifiant de l'application" },
63265
+ { name: "code", type: "string", description: "Code technique de l'application" },
63266
+ { name: "label", type: "string", description: "Libelle affiche" },
63267
+ { name: "modules", type: "object[]", description: "Liste des modules (chacun: id, code, label)" }
63268
+ ],
63269
+ defaultRateLimit: 60,
63270
+ defaultMaxPageSize: 1e3,
63271
+ notes: "Endpoint NON pagine. Retourne directement un tableau JSON (pas d'envelope de pagination). Contient la structure de navigation : applications avec leurs modules."
63272
+ }
63273
+ ];
63274
+ }
63275
+ });
63276
+
63277
+ // src/mcp/tools/analyze-hierarchy-patterns.ts
63278
+ import path27 from "path";
62606
63279
  async function handleAnalyzeHierarchyPatterns(args, config2) {
62607
63280
  const input = AnalyzeHierarchyPatternsInputSchema.parse(args);
62608
63281
  const projectPath = input.path || config2.smartstack.projectPath;
@@ -62681,7 +63354,7 @@ async function buildEntityGraph(domainPath, filter3) {
62681
63354
  return entityMap;
62682
63355
  }
62683
63356
  function parseEntity(content, file, domainPath) {
62684
- const fileName = path26.basename(file, ".cs");
63357
+ const fileName = path27.basename(file, ".cs");
62685
63358
  if (fileName.endsWith("Dto") || fileName.endsWith("Command") || fileName.endsWith("Query") || fileName.endsWith("Handler") || fileName.endsWith("Validator") || fileName.endsWith("Exception") || fileName.startsWith("I")) {
62686
63359
  return null;
62687
63360
  }
@@ -62725,7 +63398,7 @@ function parseEntity(content, file, domainPath) {
62725
63398
  const parentEntity = foreignKeys.length > 0 ? foreignKeys[0].referencedEntity : void 0;
62726
63399
  return {
62727
63400
  name: entityName,
62728
- file: path26.relative(domainPath, file),
63401
+ file: path27.relative(domainPath, file),
62729
63402
  baseClass: hasSystemEntity ? "SystemEntity" : "BaseEntity",
62730
63403
  isTenantAware: hasITenantEntity,
62731
63404
  isSystemEntity: hasSystemEntity,
@@ -62756,24 +63429,24 @@ function detectCircularDependencies(entityMap) {
62756
63429
  const cycles = [];
62757
63430
  for (const [name] of entityMap) {
62758
63431
  let dfs2 = function(current) {
62759
- if (path31.includes(current)) {
62760
- const cycleStart = path31.indexOf(current);
62761
- cycles.push([...path31.slice(cycleStart), current]);
63432
+ if (path32.includes(current)) {
63433
+ const cycleStart = path32.indexOf(current);
63434
+ cycles.push([...path32.slice(cycleStart), current]);
62762
63435
  return true;
62763
63436
  }
62764
63437
  if (visited.has(current)) return false;
62765
63438
  visited.add(current);
62766
- path31.push(current);
63439
+ path32.push(current);
62767
63440
  const node = entityMap.get(current);
62768
63441
  if (node?.parent) {
62769
63442
  dfs2(node.parent);
62770
63443
  }
62771
- path31.pop();
63444
+ path32.pop();
62772
63445
  return false;
62773
63446
  };
62774
63447
  var dfs = dfs2;
62775
63448
  const visited = /* @__PURE__ */ new Set();
62776
- const path31 = [];
63449
+ const path32 = [];
62777
63450
  dfs2(name);
62778
63451
  }
62779
63452
  const unique = /* @__PURE__ */ new Map();
@@ -65385,13 +66058,13 @@ async function getFilesToAnalyze(projectPath, scope, _structure) {
65385
66058
  try {
65386
66059
  const fullPath = filePath.startsWith(projectPath) ? filePath : `${projectPath}/${filePath}`;
65387
66060
  const content = await readText(fullPath);
65388
- const relativePath = filePath.replace(projectPath, "").replace(/^[/\\]/, "");
66061
+ const relativePath2 = filePath.replace(projectPath, "").replace(/^[/\\]/, "");
65389
66062
  files.push({
65390
66063
  path: fullPath,
65391
- relativePath,
66064
+ relativePath: relativePath2,
65392
66065
  content,
65393
66066
  language: detectLanguage(filePath),
65394
- layer: detectLayer(relativePath),
66067
+ layer: detectLayer(relativePath2),
65395
66068
  lineCount: content.split("\n").length
65396
66069
  });
65397
66070
  } catch {
@@ -65421,8 +66094,8 @@ function detectLanguage(filePath) {
65421
66094
  return "other";
65422
66095
  }
65423
66096
  }
65424
- function detectLayer(relativePath) {
65425
- const lower = relativePath.toLowerCase();
66097
+ function detectLayer(relativePath2) {
66098
+ const lower = relativePath2.toLowerCase();
65426
66099
  if (lower.includes("/domain/") || lower.includes("\\domain\\")) return "domain";
65427
66100
  if (lower.includes("/application/") || lower.includes("\\application\\")) return "application";
65428
66101
  if (lower.includes("/infrastructure/") || lower.includes("\\infrastructure\\")) return "infrastructure";
@@ -66922,7 +67595,7 @@ var init_conventions = __esm({
66922
67595
  });
66923
67596
 
66924
67597
  // src/mcp/resources/project-info.ts
66925
- import path27 from "path";
67598
+ import path28 from "path";
66926
67599
  async function getProjectInfoResource(config2) {
66927
67600
  const projectPath = config2.smartstack.projectPath;
66928
67601
  const projectInfo = await detectProject(projectPath);
@@ -66953,16 +67626,16 @@ async function getProjectInfoResource(config2) {
66953
67626
  lines.push("```");
66954
67627
  lines.push(`${projectInfo.name}/`);
66955
67628
  if (structure.domain) {
66956
- lines.push(`\u251C\u2500\u2500 ${path27.basename(structure.domain)}/ # Domain layer (entities)`);
67629
+ lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.domain)}/ # Domain layer (entities)`);
66957
67630
  }
66958
67631
  if (structure.application) {
66959
- lines.push(`\u251C\u2500\u2500 ${path27.basename(structure.application)}/ # Application layer (services)`);
67632
+ lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.application)}/ # Application layer (services)`);
66960
67633
  }
66961
67634
  if (structure.infrastructure) {
66962
- lines.push(`\u251C\u2500\u2500 ${path27.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
67635
+ lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.infrastructure)}/ # Infrastructure (EF Core)`);
66963
67636
  }
66964
67637
  if (structure.api) {
66965
- lines.push(`\u251C\u2500\u2500 ${path27.basename(structure.api)}/ # API layer (controllers)`);
67638
+ lines.push(`\u251C\u2500\u2500 ${path28.basename(structure.api)}/ # API layer (controllers)`);
66966
67639
  }
66967
67640
  if (structure.web) {
66968
67641
  lines.push(`\u2514\u2500\u2500 web/smartstack-web/ # React frontend`);
@@ -66975,9 +67648,9 @@ async function getProjectInfoResource(config2) {
66975
67648
  lines.push("| Project | Path |");
66976
67649
  lines.push("|---------|------|");
66977
67650
  for (const csproj of projectInfo.csprojFiles) {
66978
- const name = path27.basename(csproj, ".csproj");
66979
- const relativePath = path27.relative(projectPath, csproj);
66980
- lines.push(`| ${name} | \`${relativePath}\` |`);
67651
+ const name = path28.basename(csproj, ".csproj");
67652
+ const relativePath2 = path28.relative(projectPath, csproj);
67653
+ lines.push(`| ${name} | \`${relativePath2}\` |`);
66981
67654
  }
66982
67655
  lines.push("");
66983
67656
  }
@@ -66986,10 +67659,10 @@ async function getProjectInfoResource(config2) {
66986
67659
  cwd: structure.migrations,
66987
67660
  ignore: ["*.Designer.cs"]
66988
67661
  });
66989
- const migrations = migrationFiles.map((f) => path27.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
67662
+ const migrations = migrationFiles.map((f) => path28.basename(f)).filter((f) => !f.includes("ModelSnapshot") && !f.includes(".Designer.")).sort();
66990
67663
  lines.push("## EF Core Migrations");
66991
67664
  lines.push("");
66992
- lines.push(`**Location**: \`${path27.relative(projectPath, structure.migrations)}\``);
67665
+ lines.push(`**Location**: \`${path28.relative(projectPath, structure.migrations)}\``);
66993
67666
  lines.push(`**Total Migrations**: ${migrations.length}`);
66994
67667
  lines.push("");
66995
67668
  if (migrations.length > 0) {
@@ -67024,11 +67697,11 @@ async function getProjectInfoResource(config2) {
67024
67697
  lines.push("dotnet build");
67025
67698
  lines.push("");
67026
67699
  lines.push("# Run API");
67027
- lines.push(`cd ${structure.api ? path27.relative(projectPath, structure.api) : "src/Api"}`);
67700
+ lines.push(`cd ${structure.api ? path28.relative(projectPath, structure.api) : "src/Api"}`);
67028
67701
  lines.push("dotnet run");
67029
67702
  lines.push("");
67030
67703
  lines.push("# Run frontend");
67031
- lines.push(`cd ${structure.web ? path27.relative(projectPath, structure.web) : "web"}`);
67704
+ lines.push(`cd ${structure.web ? path28.relative(projectPath, structure.web) : "web"}`);
67032
67705
  lines.push("npm run dev");
67033
67706
  lines.push("");
67034
67707
  lines.push("# Create migration");
@@ -67066,7 +67739,7 @@ var init_project_info = __esm({
67066
67739
  });
67067
67740
 
67068
67741
  // src/mcp/resources/api-endpoints.ts
67069
- import path28 from "path";
67742
+ import path29 from "path";
67070
67743
  async function getApiEndpointsResource(config2, endpointFilter) {
67071
67744
  const structure = await findSmartStackStructure(config2.smartstack.projectPath);
67072
67745
  if (!structure.api) {
@@ -67085,7 +67758,7 @@ async function getApiEndpointsResource(config2, endpointFilter) {
67085
67758
  }
67086
67759
  async function parseController(filePath, _rootPath) {
67087
67760
  const content = await readText(filePath);
67088
- const fileName = path28.basename(filePath, ".cs");
67761
+ const fileName = path29.basename(filePath, ".cs");
67089
67762
  const controllerName = fileName.replace("Controller", "");
67090
67763
  const endpoints = [];
67091
67764
  const routeMatch = content.match(/\[Route\s*\(\s*"([^"]+)"\s*\)\]/);
@@ -67247,7 +67920,7 @@ var init_api_endpoints = __esm({
67247
67920
  });
67248
67921
 
67249
67922
  // src/mcp/resources/db-schema.ts
67250
- import path29 from "path";
67923
+ import path30 from "path";
67251
67924
  async function getDbSchemaResource(config2, tableFilter) {
67252
67925
  const structure = await findSmartStackStructure(config2.smartstack.projectPath);
67253
67926
  if (!structure.domain && !structure.infrastructure) {
@@ -67331,7 +68004,7 @@ async function parseEntity2(filePath, rootPath, _config) {
67331
68004
  tableName,
67332
68005
  properties,
67333
68006
  relationships,
67334
- file: path29.relative(rootPath, filePath)
68007
+ file: path30.relative(rootPath, filePath)
67335
68008
  };
67336
68009
  }
67337
68010
  async function enrichFromConfigurations(entities, infrastructurePath, _config) {
@@ -67492,7 +68165,7 @@ var init_db_schema = __esm({
67492
68165
  });
67493
68166
 
67494
68167
  // src/mcp/resources/entities.ts
67495
- import path30 from "path";
68168
+ import path31 from "path";
67496
68169
  async function getEntitiesResource(config2, entityFilter) {
67497
68170
  const structure = await findSmartStackStructure(config2.smartstack.projectPath);
67498
68171
  if (!structure.domain) {
@@ -67546,7 +68219,7 @@ async function parseEntitySummary(filePath, rootPath, config2) {
67546
68219
  hasSoftDelete,
67547
68220
  hasRowVersion,
67548
68221
  file: filePath,
67549
- relativePath: path30.relative(rootPath, filePath)
68222
+ relativePath: path31.relative(rootPath, filePath)
67550
68223
  };
67551
68224
  }
67552
68225
  function inferTableInfo(entityName, config2) {
@@ -67764,8 +68437,9 @@ async function createServer() {
67764
68437
  // Frontend Extension Tools
67765
68438
  scaffoldFrontendExtensionTool,
67766
68439
  analyzeExtensionPointsTool,
67767
- // Data Export Tool
68440
+ // Data Export Tools
67768
68441
  scaffoldDataExportTool,
68442
+ promptDataExportM2mTool,
67769
68443
  // Security & Code Quality Tools
67770
68444
  validateSecurityTool,
67771
68445
  analyzeCodeQualityTool,
@@ -67835,10 +68509,13 @@ async function createServer() {
67835
68509
  case "analyze_extension_points":
67836
68510
  result = await handleAnalyzeExtensionPoints(args ?? {}, config2);
67837
68511
  break;
67838
- // Data Export Tool
68512
+ // Data Export Tools
67839
68513
  case "scaffold_data_export":
67840
68514
  result = await handleScaffoldDataExport(args ?? {}, config2);
67841
68515
  break;
68516
+ case "prompt_data_export_m2m":
68517
+ result = await handlePromptDataExportM2m(args ?? {}, config2);
68518
+ break;
67842
68519
  // Security & Code Quality Tools
67843
68520
  case "validate_security":
67844
68521
  result = await handleValidateSecurity(args ?? {}, config2);
@@ -67968,6 +68645,7 @@ var init_server3 = __esm({
67968
68645
  init_validate_security();
67969
68646
  init_analyze_code_quality();
67970
68647
  init_scaffold_data_export();
68648
+ init_prompt_data_export_m2m();
67971
68649
  init_analyze_hierarchy_patterns();
67972
68650
  init_review_code();
67973
68651
  init_conventions();