@dforge-core/dforge-mcp 0.1.0-rc.3 → 0.1.0-rc.5

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.
package/dist/server.js CHANGED
@@ -2981,7 +2981,7 @@ var require_compile = __commonJS({
2981
2981
  const schOrFunc = root.refs[ref];
2982
2982
  if (schOrFunc)
2983
2983
  return schOrFunc;
2984
- let _sch = resolve4.call(this, root, ref);
2984
+ let _sch = resolve5.call(this, root, ref);
2985
2985
  if (_sch === void 0) {
2986
2986
  const schema2 = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
2987
2987
  const { schemaId } = this.opts;
@@ -3008,7 +3008,7 @@ var require_compile = __commonJS({
3008
3008
  function sameSchemaEnv(s1, s2) {
3009
3009
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3010
3010
  }
3011
- function resolve4(root, ref) {
3011
+ function resolve5(root, ref) {
3012
3012
  let sch;
3013
3013
  while (typeof (sch = this.refs[ref]) == "string")
3014
3014
  ref = sch;
@@ -3226,8 +3226,8 @@ var require_utils = __commonJS({
3226
3226
  }
3227
3227
  return ind;
3228
3228
  }
3229
- function removeDotSegments(path4) {
3230
- let input = path4;
3229
+ function removeDotSegments(path8) {
3230
+ let input = path8;
3231
3231
  const output = [];
3232
3232
  let nextSlash = -1;
3233
3233
  let len = 0;
@@ -3479,8 +3479,8 @@ var require_schemes = __commonJS({
3479
3479
  wsComponent.secure = void 0;
3480
3480
  }
3481
3481
  if (wsComponent.resourceName) {
3482
- const [path4, query] = wsComponent.resourceName.split("?");
3483
- wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
3482
+ const [path8, query] = wsComponent.resourceName.split("?");
3483
+ wsComponent.path = path8 && path8 !== "/" ? path8 : void 0;
3484
3484
  wsComponent.query = query;
3485
3485
  wsComponent.resourceName = void 0;
3486
3486
  }
@@ -3639,55 +3639,55 @@ var require_fast_uri = __commonJS({
3639
3639
  }
3640
3640
  return uri;
3641
3641
  }
3642
- function resolve4(baseURI, relativeURI, options) {
3642
+ function resolve5(baseURI, relativeURI, options) {
3643
3643
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3644
3644
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3645
3645
  schemelessOptions.skipEscape = true;
3646
3646
  return serialize(resolved, schemelessOptions);
3647
3647
  }
3648
- function resolveComponent(base, relative, options, skipNormalization) {
3648
+ function resolveComponent(base, relative2, options, skipNormalization) {
3649
3649
  const target = {};
3650
3650
  if (!skipNormalization) {
3651
3651
  base = parse3(serialize(base, options), options);
3652
- relative = parse3(serialize(relative, options), options);
3652
+ relative2 = parse3(serialize(relative2, options), options);
3653
3653
  }
3654
3654
  options = options || {};
3655
- if (!options.tolerant && relative.scheme) {
3656
- target.scheme = relative.scheme;
3657
- target.userinfo = relative.userinfo;
3658
- target.host = relative.host;
3659
- target.port = relative.port;
3660
- target.path = removeDotSegments(relative.path || "");
3661
- target.query = relative.query;
3655
+ if (!options.tolerant && relative2.scheme) {
3656
+ target.scheme = relative2.scheme;
3657
+ target.userinfo = relative2.userinfo;
3658
+ target.host = relative2.host;
3659
+ target.port = relative2.port;
3660
+ target.path = removeDotSegments(relative2.path || "");
3661
+ target.query = relative2.query;
3662
3662
  } else {
3663
- if (relative.userinfo !== void 0 || relative.host !== void 0 || relative.port !== void 0) {
3664
- target.userinfo = relative.userinfo;
3665
- target.host = relative.host;
3666
- target.port = relative.port;
3667
- target.path = removeDotSegments(relative.path || "");
3668
- target.query = relative.query;
3663
+ if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
3664
+ target.userinfo = relative2.userinfo;
3665
+ target.host = relative2.host;
3666
+ target.port = relative2.port;
3667
+ target.path = removeDotSegments(relative2.path || "");
3668
+ target.query = relative2.query;
3669
3669
  } else {
3670
- if (!relative.path) {
3670
+ if (!relative2.path) {
3671
3671
  target.path = base.path;
3672
- if (relative.query !== void 0) {
3673
- target.query = relative.query;
3672
+ if (relative2.query !== void 0) {
3673
+ target.query = relative2.query;
3674
3674
  } else {
3675
3675
  target.query = base.query;
3676
3676
  }
3677
3677
  } else {
3678
- if (relative.path[0] === "/") {
3679
- target.path = removeDotSegments(relative.path);
3678
+ if (relative2.path[0] === "/") {
3679
+ target.path = removeDotSegments(relative2.path);
3680
3680
  } else {
3681
3681
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3682
- target.path = "/" + relative.path;
3682
+ target.path = "/" + relative2.path;
3683
3683
  } else if (!base.path) {
3684
- target.path = relative.path;
3684
+ target.path = relative2.path;
3685
3685
  } else {
3686
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
3686
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
3687
3687
  }
3688
3688
  target.path = removeDotSegments(target.path);
3689
3689
  }
3690
- target.query = relative.query;
3690
+ target.query = relative2.query;
3691
3691
  }
3692
3692
  target.userinfo = base.userinfo;
3693
3693
  target.host = base.host;
@@ -3695,7 +3695,7 @@ var require_fast_uri = __commonJS({
3695
3695
  }
3696
3696
  target.scheme = base.scheme;
3697
3697
  }
3698
- target.fragment = relative.fragment;
3698
+ target.fragment = relative2.fragment;
3699
3699
  return target;
3700
3700
  }
3701
3701
  function equal(uriA, uriB, options) {
@@ -3897,7 +3897,7 @@ var require_fast_uri = __commonJS({
3897
3897
  var fastUri = {
3898
3898
  SCHEMES,
3899
3899
  normalize,
3900
- resolve: resolve4,
3900
+ resolve: resolve5,
3901
3901
  resolveComponent,
3902
3902
  equal,
3903
3903
  serialize,
@@ -6873,12 +6873,12 @@ var require_dist = __commonJS({
6873
6873
  throw new Error(`Unknown format "${name}"`);
6874
6874
  return f;
6875
6875
  };
6876
- function addFormats(ajv, list, fs4, exportName) {
6876
+ function addFormats(ajv, list, fs6, exportName) {
6877
6877
  var _a3;
6878
6878
  var _b;
6879
6879
  (_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6880
6880
  for (const f of list)
6881
- ajv.addFormat(f, fs4[f]);
6881
+ ajv.addFormat(f, fs6[f]);
6882
6882
  }
6883
6883
  module2.exports = exports2 = formatsPlugin;
6884
6884
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -7245,8 +7245,8 @@ function getErrorMap() {
7245
7245
 
7246
7246
  // node_modules/zod/v3/helpers/parseUtil.js
7247
7247
  var makeIssue = (params) => {
7248
- const { data, path: path4, errorMaps, issueData } = params;
7249
- const fullPath = [...path4, ...issueData.path || []];
7248
+ const { data, path: path8, errorMaps, issueData } = params;
7249
+ const fullPath = [...path8, ...issueData.path || []];
7250
7250
  const fullIssue = {
7251
7251
  ...issueData,
7252
7252
  path: fullPath
@@ -7361,11 +7361,11 @@ var errorUtil;
7361
7361
 
7362
7362
  // node_modules/zod/v3/types.js
7363
7363
  var ParseInputLazyPath = class {
7364
- constructor(parent, value, path4, key) {
7364
+ constructor(parent, value, path8, key) {
7365
7365
  this._cachedPath = [];
7366
7366
  this.parent = parent;
7367
7367
  this.data = value;
7368
- this._path = path4;
7368
+ this._path = path8;
7369
7369
  this._key = key;
7370
7370
  }
7371
7371
  get path() {
@@ -9112,8 +9112,8 @@ function deepPartialify(schema2) {
9112
9112
  if (schema2 instanceof ZodObject) {
9113
9113
  const newShape = {};
9114
9114
  for (const key in schema2.shape) {
9115
- const fieldSchema = schema2.shape[key];
9116
- newShape[key] = ZodOptional.create(deepPartialify(fieldSchema));
9115
+ const fieldSchema2 = schema2.shape[key];
9116
+ newShape[key] = ZodOptional.create(deepPartialify(fieldSchema2));
9117
9117
  }
9118
9118
  return new ZodObject({
9119
9119
  ...schema2._def,
@@ -9410,11 +9410,11 @@ var ZodObject = class _ZodObject extends ZodType {
9410
9410
  partial(mask) {
9411
9411
  const newShape = {};
9412
9412
  for (const key of util.objectKeys(this.shape)) {
9413
- const fieldSchema = this.shape[key];
9413
+ const fieldSchema2 = this.shape[key];
9414
9414
  if (mask && !mask[key]) {
9415
- newShape[key] = fieldSchema;
9415
+ newShape[key] = fieldSchema2;
9416
9416
  } else {
9417
- newShape[key] = fieldSchema.optional();
9417
+ newShape[key] = fieldSchema2.optional();
9418
9418
  }
9419
9419
  }
9420
9420
  return new _ZodObject({
@@ -9428,8 +9428,8 @@ var ZodObject = class _ZodObject extends ZodType {
9428
9428
  if (mask && !mask[key]) {
9429
9429
  newShape[key] = this.shape[key];
9430
9430
  } else {
9431
- const fieldSchema = this.shape[key];
9432
- let newField = fieldSchema;
9431
+ const fieldSchema2 = this.shape[key];
9432
+ let newField = fieldSchema2;
9433
9433
  while (newField instanceof ZodOptional) {
9434
9434
  newField = newField._def.innerType;
9435
9435
  }
@@ -11285,10 +11285,10 @@ function mergeDefs(...defs) {
11285
11285
  function cloneDef(schema2) {
11286
11286
  return mergeDefs(schema2._zod.def);
11287
11287
  }
11288
- function getElementAtPath(obj, path4) {
11289
- if (!path4)
11288
+ function getElementAtPath(obj, path8) {
11289
+ if (!path8)
11290
11290
  return obj;
11291
- return path4.reduce((acc, key) => acc?.[key], obj);
11291
+ return path8.reduce((acc, key) => acc?.[key], obj);
11292
11292
  }
11293
11293
  function promiseAllObject(promisesObj) {
11294
11294
  const keys = Object.keys(promisesObj);
@@ -11697,11 +11697,11 @@ function explicitlyAborted(x, startIndex = 0) {
11697
11697
  }
11698
11698
  return false;
11699
11699
  }
11700
- function prefixIssues(path4, issues) {
11700
+ function prefixIssues(path8, issues) {
11701
11701
  return issues.map((iss) => {
11702
11702
  var _a3;
11703
11703
  (_a3 = iss).path ?? (_a3.path = []);
11704
- iss.path.unshift(path4);
11704
+ iss.path.unshift(path8);
11705
11705
  return iss;
11706
11706
  });
11707
11707
  }
@@ -11848,16 +11848,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
11848
11848
  }
11849
11849
  function formatError(error51, mapper = (issue2) => issue2.message) {
11850
11850
  const fieldErrors = { _errors: [] };
11851
- const processError = (error52, path4 = []) => {
11851
+ const processError = (error52, path8 = []) => {
11852
11852
  for (const issue2 of error52.issues) {
11853
11853
  if (issue2.code === "invalid_union" && issue2.errors.length) {
11854
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
11854
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
11855
11855
  } else if (issue2.code === "invalid_key") {
11856
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
11856
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
11857
11857
  } else if (issue2.code === "invalid_element") {
11858
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
11858
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
11859
11859
  } else {
11860
- const fullpath = [...path4, ...issue2.path];
11860
+ const fullpath = [...path8, ...issue2.path];
11861
11861
  if (fullpath.length === 0) {
11862
11862
  fieldErrors._errors.push(mapper(issue2));
11863
11863
  } else {
@@ -11884,17 +11884,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
11884
11884
  }
11885
11885
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
11886
11886
  const result = { errors: [] };
11887
- const processError = (error52, path4 = []) => {
11887
+ const processError = (error52, path8 = []) => {
11888
11888
  var _a3, _b;
11889
11889
  for (const issue2 of error52.issues) {
11890
11890
  if (issue2.code === "invalid_union" && issue2.errors.length) {
11891
- issue2.errors.map((issues) => processError({ issues }, [...path4, ...issue2.path]));
11891
+ issue2.errors.map((issues) => processError({ issues }, [...path8, ...issue2.path]));
11892
11892
  } else if (issue2.code === "invalid_key") {
11893
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
11893
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
11894
11894
  } else if (issue2.code === "invalid_element") {
11895
- processError({ issues: issue2.issues }, [...path4, ...issue2.path]);
11895
+ processError({ issues: issue2.issues }, [...path8, ...issue2.path]);
11896
11896
  } else {
11897
- const fullpath = [...path4, ...issue2.path];
11897
+ const fullpath = [...path8, ...issue2.path];
11898
11898
  if (fullpath.length === 0) {
11899
11899
  result.errors.push(mapper(issue2));
11900
11900
  continue;
@@ -11926,8 +11926,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
11926
11926
  }
11927
11927
  function toDotPath(_path) {
11928
11928
  const segs = [];
11929
- const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
11930
- for (const seg of path4) {
11929
+ const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
11930
+ for (const seg of path8) {
11931
11931
  if (typeof seg === "number")
11932
11932
  segs.push(`[${seg}]`);
11933
11933
  else if (typeof seg === "symbol")
@@ -25052,13 +25052,13 @@ function resolveRef(ref, ctx) {
25052
25052
  if (!ref.startsWith("#")) {
25053
25053
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
25054
25054
  }
25055
- const path4 = ref.slice(1).split("/").filter(Boolean);
25056
- if (path4.length === 0) {
25055
+ const path8 = ref.slice(1).split("/").filter(Boolean);
25056
+ if (path8.length === 0) {
25057
25057
  return ctx.rootSchema;
25058
25058
  }
25059
25059
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
25060
- if (path4[0] === defsKey) {
25061
- const key = path4[1];
25060
+ if (path8[0] === defsKey) {
25061
+ const key = path8[1];
25062
25062
  if (!key || !ctx.defs[key]) {
25063
25063
  throw new Error(`Reference not found: ${ref}`);
25064
25064
  }
@@ -28828,7 +28828,7 @@ var Protocol = class {
28828
28828
  return;
28829
28829
  }
28830
28830
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
28831
- await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
28831
+ await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
28832
28832
  options?.signal?.throwIfAborted();
28833
28833
  }
28834
28834
  } catch (error51) {
@@ -28845,7 +28845,7 @@ var Protocol = class {
28845
28845
  */
28846
28846
  request(request, resultSchema, options) {
28847
28847
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
28848
- return new Promise((resolve4, reject) => {
28848
+ return new Promise((resolve5, reject) => {
28849
28849
  const earlyReject = (error51) => {
28850
28850
  reject(error51);
28851
28851
  };
@@ -28923,7 +28923,7 @@ var Protocol = class {
28923
28923
  if (!parseResult.success) {
28924
28924
  reject(parseResult.error);
28925
28925
  } else {
28926
- resolve4(parseResult.data);
28926
+ resolve5(parseResult.data);
28927
28927
  }
28928
28928
  } catch (error51) {
28929
28929
  reject(error51);
@@ -29184,12 +29184,12 @@ var Protocol = class {
29184
29184
  }
29185
29185
  } catch {
29186
29186
  }
29187
- return new Promise((resolve4, reject) => {
29187
+ return new Promise((resolve5, reject) => {
29188
29188
  if (signal.aborted) {
29189
29189
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
29190
29190
  return;
29191
29191
  }
29192
- const timeoutId = setTimeout(resolve4, interval);
29192
+ const timeoutId = setTimeout(resolve5, interval);
29193
29193
  signal.addEventListener("abort", () => {
29194
29194
  clearTimeout(timeoutId);
29195
29195
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -30289,7 +30289,7 @@ var McpServer = class {
30289
30289
  let task = createTaskResult.task;
30290
30290
  const pollInterval = task.pollInterval ?? 5e3;
30291
30291
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
30292
- await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
30292
+ await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
30293
30293
  const updatedTask = await extra.taskStore.getTask(taskId);
30294
30294
  if (!updatedTask) {
30295
30295
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -30938,12 +30938,12 @@ var StdioServerTransport = class {
30938
30938
  this.onclose?.();
30939
30939
  }
30940
30940
  send(message) {
30941
- return new Promise((resolve4) => {
30941
+ return new Promise((resolve5) => {
30942
30942
  const json2 = serializeMessage(message);
30943
30943
  if (this._stdout.write(json2)) {
30944
- resolve4();
30944
+ resolve5();
30945
30945
  } else {
30946
- this._stdout.once("drain", resolve4);
30946
+ this._stdout.once("drain", resolve5);
30947
30947
  }
30948
30948
  });
30949
30949
  }
@@ -31006,18 +31006,16 @@ function buildDataViews(entities) {
31006
31006
  }
31007
31007
  function buildFolders(opts) {
31008
31008
  return {
31009
- [opts.code]: {
31010
- label: opts.displayName,
31011
- description: opts.description || opts.displayName,
31012
- icon: "bi-folder",
31013
- color: "#2196F3",
31014
- entities: Object.fromEntries(
31015
- opts.entities.map((e) => [
31016
- e.name,
31017
- { viewName: e.name, quickAdd: true }
31018
- ])
31019
- )
31020
- }
31009
+ label: opts.displayName,
31010
+ description: opts.description || opts.displayName,
31011
+ icon: "bi-folder",
31012
+ color: "#2196F3",
31013
+ entities: Object.fromEntries(
31014
+ opts.entities.map((e) => [
31015
+ e.name,
31016
+ { viewName: "default", quickAdd: true }
31017
+ ])
31018
+ )
31021
31019
  };
31022
31020
  }
31023
31021
  function buildMenus(opts) {
@@ -31151,11 +31149,11 @@ function createModuleFiles(args) {
31151
31149
  entities: args.entities
31152
31150
  };
31153
31151
  const files = {};
31154
- const write = (rel, obj) => {
31155
- files[rel] = JSON.stringify(obj, null, " ") + "\n";
31152
+ const write = (rel2, obj) => {
31153
+ files[rel2] = JSON.stringify(obj, null, " ") + "\n";
31156
31154
  };
31157
- const writeText = (rel, text) => {
31158
- files[rel] = text;
31155
+ const writeText = (rel2, text) => {
31156
+ files[rel2] = text;
31159
31157
  };
31160
31158
  write("manifest.json", buildManifest(opts, moduleId));
31161
31159
  for (const e of opts.entities) {
@@ -31361,10 +31359,583 @@ function dbmlImport(_args) {
31361
31359
  };
31362
31360
  }
31363
31361
 
31364
- // src/resources/index.ts
31362
+ // src/tools/entity-field.ts
31363
+ var path4 = __toESM(require("path"));
31364
+
31365
+ // src/tools/_helpers.ts
31365
31366
  var fs3 = __toESM(require("fs"));
31366
31367
  var path3 = __toESM(require("path"));
31367
- var RESOURCES_DIR = path3.resolve(__dirname, "..", "resources");
31368
+ function modulePaths(moduleDir) {
31369
+ const root = path3.resolve(moduleDir);
31370
+ return {
31371
+ root,
31372
+ manifest: path3.join(root, "manifest.json"),
31373
+ entitiesDir: path3.join(root, "entities"),
31374
+ uiDir: path3.join(root, "ui"),
31375
+ securityDir: path3.join(root, "security"),
31376
+ logicDir: path3.join(root, "logic"),
31377
+ seedDataDir: path3.join(root, "seed-data"),
31378
+ translationsDir: path3.join(root, "translations"),
31379
+ dataViews: path3.join(root, "ui", "data_views.json"),
31380
+ folders: path3.join(root, "ui", "folders.json"),
31381
+ menus: path3.join(root, "ui", "menus.json"),
31382
+ actions: path3.join(root, "ui", "actions.json"),
31383
+ reports: path3.join(root, "ui", "reports.json"),
31384
+ roles: path3.join(root, "security", "roles.json"),
31385
+ jobs: path3.join(root, "logic", "jobs.json"),
31386
+ settings: path3.join(root, "settings.json")
31387
+ };
31388
+ }
31389
+ function readJson(absPath) {
31390
+ if (!fs3.existsSync(absPath)) {
31391
+ throw new Error(`Not found: ${absPath}`);
31392
+ }
31393
+ try {
31394
+ return JSON.parse(fs3.readFileSync(absPath, "utf8"));
31395
+ } catch (e) {
31396
+ throw new Error(`${absPath}: ${e.message}`);
31397
+ }
31398
+ }
31399
+ function readJsonOrDefault(absPath, dflt) {
31400
+ if (!fs3.existsSync(absPath)) return dflt;
31401
+ try {
31402
+ return JSON.parse(fs3.readFileSync(absPath, "utf8"));
31403
+ } catch (e) {
31404
+ throw new Error(`${absPath}: ${e.message}`);
31405
+ }
31406
+ }
31407
+ function jsonText(obj) {
31408
+ return JSON.stringify(obj, null, " ") + "\n";
31409
+ }
31410
+ function rel(root, abs) {
31411
+ return path3.relative(root, abs);
31412
+ }
31413
+ function loadManifest(moduleDir) {
31414
+ const paths = modulePaths(moduleDir);
31415
+ if (!fs3.existsSync(paths.manifest)) {
31416
+ throw new Error(
31417
+ `No manifest.json at ${paths.manifest} \u2014 is this a dForge module directory?`
31418
+ );
31419
+ }
31420
+ const manifest = readJson(paths.manifest);
31421
+ if (!manifest.code) {
31422
+ throw new Error("manifest.json has no `code` field \u2014 corrupt module?");
31423
+ }
31424
+ return { manifest, paths };
31425
+ }
31426
+ function makeResult(summary, files, warning) {
31427
+ const out = { summary, files };
31428
+ if (warning) out.warning = warning;
31429
+ return out;
31430
+ }
31431
+ function withTodayStamp(manifest) {
31432
+ return { ...manifest, updated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) };
31433
+ }
31434
+
31435
+ // src/tools/entity-field.ts
31436
+ var fieldSchema = external_exports.object({
31437
+ dbDatatype: external_exports.string().optional(),
31438
+ fieldTypeCd: external_exports.string().optional(),
31439
+ baseDatatypeCd: external_exports.string().optional(),
31440
+ columnType: external_exports.string().optional(),
31441
+ flags: external_exports.string().optional(),
31442
+ isNullable: external_exports.boolean().optional(),
31443
+ maxLen: external_exports.number().int().optional(),
31444
+ orderNum: external_exports.number().int().optional(),
31445
+ description: external_exports.string().optional(),
31446
+ defaultValue: external_exports.unknown().optional(),
31447
+ formula: external_exports.string().optional(),
31448
+ link: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
31449
+ params: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
31450
+ }).passthrough().describe(
31451
+ "Field spec. Common keys: dbDatatype, fieldTypeCd, flags (VEMHI letters), isNullable, maxLen, orderNum, description, link ({entity, otherKey}) for refs, formula for computed columns. Pass through whatever the entity.schema.json allows."
31452
+ );
31453
+ var entityFieldAddSchema = {
31454
+ moduleDir: external_exports.string().describe("Path to the module root."),
31455
+ entityName: external_exports.string().regex(/^[a-z][a-z0-9_]*$/).describe("Entity code to add the field to."),
31456
+ fieldName: external_exports.string().regex(/^[a-z][a-z0-9_]*$/).describe("New field's code."),
31457
+ field: fieldSchema
31458
+ };
31459
+ function entityFieldAdd(args) {
31460
+ const { paths, manifest } = loadManifest(args.moduleDir);
31461
+ const entityPath = path4.join(paths.entitiesDir, `${args.entityName}.json`);
31462
+ const entity = readJson(entityPath);
31463
+ const fields = entity.fields ?? {};
31464
+ if (Object.prototype.hasOwnProperty.call(fields, args.fieldName)) {
31465
+ throw new Error(
31466
+ `Field '${args.fieldName}' already exists on entity '${args.entityName}'. Use entity_field_modify to change it.`
31467
+ );
31468
+ }
31469
+ entity.fields = { ...fields, [args.fieldName]: args.field };
31470
+ const files = {
31471
+ [rel(paths.root, entityPath)]: jsonText(entity),
31472
+ "manifest.json": jsonText(withTodayStamp(manifest))
31473
+ };
31474
+ return makeResult(
31475
+ `Added field '${args.fieldName}' to entity '${args.entityName}'.`,
31476
+ files
31477
+ );
31478
+ }
31479
+ var entityFieldModifySchema = {
31480
+ moduleDir: external_exports.string(),
31481
+ entityName: external_exports.string().regex(/^[a-z][a-z0-9_]*$/),
31482
+ fieldName: external_exports.string().regex(/^[a-z][a-z0-9_]*$/),
31483
+ field: fieldSchema.describe(
31484
+ "Replacement spec. Replaces the existing field entirely \u2014 pass the full desired shape, not a partial patch."
31485
+ )
31486
+ };
31487
+ function entityFieldModify(args) {
31488
+ const { paths, manifest } = loadManifest(args.moduleDir);
31489
+ const entityPath = path4.join(paths.entitiesDir, `${args.entityName}.json`);
31490
+ const entity = readJson(entityPath);
31491
+ const fields = entity.fields ?? {};
31492
+ if (!Object.prototype.hasOwnProperty.call(fields, args.fieldName)) {
31493
+ throw new Error(
31494
+ `Field '${args.fieldName}' not found on entity '${args.entityName}'.`
31495
+ );
31496
+ }
31497
+ entity.fields = { ...fields, [args.fieldName]: args.field };
31498
+ return makeResult(
31499
+ `Modified field '${args.fieldName}' on entity '${args.entityName}'.`,
31500
+ {
31501
+ [rel(paths.root, entityPath)]: jsonText(entity),
31502
+ "manifest.json": jsonText(withTodayStamp(manifest))
31503
+ }
31504
+ );
31505
+ }
31506
+ var entityFieldRemoveSchema = {
31507
+ moduleDir: external_exports.string(),
31508
+ entityName: external_exports.string().regex(/^[a-z][a-z0-9_]*$/),
31509
+ fieldName: external_exports.string().regex(/^[a-z][a-z0-9_]*$/)
31510
+ };
31511
+ function entityFieldRemove(args) {
31512
+ const { paths, manifest } = loadManifest(args.moduleDir);
31513
+ const entityPath = path4.join(paths.entitiesDir, `${args.entityName}.json`);
31514
+ const entity = readJson(entityPath);
31515
+ const fields = entity.fields ?? {};
31516
+ if (!Object.prototype.hasOwnProperty.call(fields, args.fieldName)) {
31517
+ throw new Error(
31518
+ `Field '${args.fieldName}' not found on entity '${args.entityName}'.`
31519
+ );
31520
+ }
31521
+ const { [args.fieldName]: _removed, ...rest } = fields;
31522
+ void _removed;
31523
+ entity.fields = rest;
31524
+ return makeResult(
31525
+ `Removed field '${args.fieldName}' from entity '${args.entityName}'. Note: dependent views / formulas referencing this field will break \u2014 review them.`,
31526
+ {
31527
+ [rel(paths.root, entityPath)]: jsonText(entity),
31528
+ "manifest.json": jsonText(withTodayStamp(manifest))
31529
+ },
31530
+ "Removing fields can break dependent views, role rights, formulas, action DSL, and seed data. Run `dforge_module_inspect` after writing to spot broken references."
31531
+ );
31532
+ }
31533
+
31534
+ // src/tools/action-add.ts
31535
+ var path5 = __toESM(require("path"));
31536
+ var actionAddSchema = {
31537
+ moduleDir: external_exports.string(),
31538
+ code: external_exports.string().regex(/^[a-z][a-z0-9_]*$/).describe("Action code (becomes the DSL filename and registry key)."),
31539
+ entityCode: external_exports.string().regex(/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)?$/).describe(
31540
+ "Entity this action targets. May be cross-module via dot notation ('fin.invoice')."
31541
+ ),
31542
+ label: external_exports.string().min(1).describe("Display label in the action menu."),
31543
+ mode: external_exports.enum(["single", "each", "batch"]).default("single").describe(
31544
+ "Execution mode. 'single' = whole selection at once, 'each' = per-record, 'batch' = explicit `for x in records` loop in DSL."
31545
+ ),
31546
+ icon: external_exports.string().optional().describe("Bootstrap icon class (e.g. 'play-fill')."),
31547
+ background: external_exports.boolean().default(false).describe("Run asynchronously (queues to background_action table)."),
31548
+ dslBody: external_exports.string().describe(
31549
+ "Full DSL source. Should contain `params:`, optional `canExecute:`, optional `onBeforeStart:` (async only), and required `execute:` blocks."
31550
+ )
31551
+ };
31552
+ function actionAdd(args) {
31553
+ const { paths, manifest } = loadManifest(args.moduleDir);
31554
+ const actionsJson = readJsonOrDefault(paths.actions, {});
31555
+ if (Object.prototype.hasOwnProperty.call(actionsJson, args.code)) {
31556
+ throw new Error(
31557
+ `Action '${args.code}' already exists in ui/actions.json. Use a different code or remove the existing entry first.`
31558
+ );
31559
+ }
31560
+ const actionEntry = {
31561
+ entity: args.entityCode,
31562
+ label: args.label,
31563
+ mode: args.mode,
31564
+ background: args.background,
31565
+ dsl: `./logic/actions/${args.code}.dsl`
31566
+ };
31567
+ if (args.icon) actionEntry.icon = args.icon;
31568
+ actionsJson[args.code] = actionEntry;
31569
+ const dslPath = path5.join(paths.logicDir, "actions", `${args.code}.dsl`);
31570
+ const dslBody = args.dslBody.endsWith("\n") ? args.dslBody : args.dslBody + "\n";
31571
+ return makeResult(
31572
+ `Added action '${args.code}' targeting entity '${args.entityCode}' (mode=${args.mode}${args.background ? ", background" : ""}).`,
31573
+ {
31574
+ [rel(paths.root, paths.actions)]: jsonText(actionsJson),
31575
+ [rel(paths.root, dslPath)]: dslBody,
31576
+ "manifest.json": jsonText(withTodayStamp(manifest))
31577
+ }
31578
+ );
31579
+ }
31580
+
31581
+ // src/tools/view.ts
31582
+ var dataSourceSchema = external_exports.object({
31583
+ entityCode: external_exports.string(),
31584
+ level: external_exports.number().int().min(0).optional(),
31585
+ label: external_exports.string().optional(),
31586
+ columns: external_exports.array(external_exports.unknown()).optional(),
31587
+ filter: external_exports.unknown().optional(),
31588
+ order: external_exports.unknown().optional(),
31589
+ parentSetField: external_exports.string().optional()
31590
+ }).passthrough();
31591
+ var dataViewSchema = external_exports.object({
31592
+ viewType: external_exports.enum([
31593
+ "grid",
31594
+ "list",
31595
+ "kanban",
31596
+ "calendar",
31597
+ "gallery",
31598
+ "tree-grid",
31599
+ "diagram",
31600
+ "master-detail",
31601
+ "library"
31602
+ ]),
31603
+ label: external_exports.string().optional(),
31604
+ description: external_exports.string().optional(),
31605
+ icon: external_exports.string().optional(),
31606
+ dataSources: external_exports.array(dataSourceSchema).min(1),
31607
+ filter: external_exports.unknown().optional(),
31608
+ order: external_exports.unknown().optional(),
31609
+ viewConfig: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
31610
+ }).passthrough();
31611
+ var viewAddSchema = {
31612
+ moduleDir: external_exports.string(),
31613
+ code: external_exports.string().regex(/^[a-z][a-z0-9_]*$/).describe("View code (keys ui/data_views.json + URL slug)."),
31614
+ view: dataViewSchema
31615
+ };
31616
+ function viewAdd(args) {
31617
+ const { paths, manifest } = loadManifest(args.moduleDir);
31618
+ const views = readJsonOrDefault(paths.dataViews, {});
31619
+ if (Object.prototype.hasOwnProperty.call(views, args.code)) {
31620
+ throw new Error(
31621
+ `View '${args.code}' already exists in ui/data_views.json. Use view_modify to change it.`
31622
+ );
31623
+ }
31624
+ views[args.code] = args.view;
31625
+ return makeResult(
31626
+ `Added ${args.view.viewType} view '${args.code}' over [${args.view.dataSources.map((d) => d.entityCode).join(", ")}].`,
31627
+ {
31628
+ [rel(paths.root, paths.dataViews)]: jsonText(views),
31629
+ "manifest.json": jsonText(withTodayStamp(manifest))
31630
+ }
31631
+ );
31632
+ }
31633
+ var viewModifySchema = {
31634
+ moduleDir: external_exports.string(),
31635
+ code: external_exports.string().regex(/^[a-z][a-z0-9_]*$/),
31636
+ view: dataViewSchema.describe(
31637
+ "Replacement view spec \u2014 full shape, not a partial patch."
31638
+ )
31639
+ };
31640
+ function viewModify(args) {
31641
+ const { paths, manifest } = loadManifest(args.moduleDir);
31642
+ const views = readJsonOrDefault(paths.dataViews, {});
31643
+ if (!Object.prototype.hasOwnProperty.call(views, args.code)) {
31644
+ throw new Error(
31645
+ `View '${args.code}' not found. Use view_add to create it.`
31646
+ );
31647
+ }
31648
+ views[args.code] = args.view;
31649
+ return makeResult(`Modified view '${args.code}'.`, {
31650
+ [rel(paths.root, paths.dataViews)]: jsonText(views),
31651
+ "manifest.json": jsonText(withTodayStamp(manifest))
31652
+ });
31653
+ }
31654
+
31655
+ // src/tools/adds.ts
31656
+ var reportAddSchema = {
31657
+ moduleDir: external_exports.string(),
31658
+ code: external_exports.string().regex(/^[a-z][a-z0-9_]*$/).describe("Report code (keys ui/reports.json)."),
31659
+ report: external_exports.object({
31660
+ description: external_exports.string(),
31661
+ layout: external_exports.record(external_exports.string(), external_exports.unknown()),
31662
+ datasets: external_exports.record(external_exports.string(), external_exports.unknown()),
31663
+ parameters: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
31664
+ }).passthrough().describe(
31665
+ "Full report spec per reports.schema.json: { description, layout: { panels: [...] }, datasets: { code: {...} }, parameters? }."
31666
+ )
31667
+ };
31668
+ function reportAdd(args) {
31669
+ const { paths, manifest } = loadManifest(args.moduleDir);
31670
+ const reports = readJsonOrDefault(paths.reports, {});
31671
+ if (Object.prototype.hasOwnProperty.call(reports, args.code)) {
31672
+ throw new Error(`Report '${args.code}' already exists in ui/reports.json.`);
31673
+ }
31674
+ reports[args.code] = args.report;
31675
+ return makeResult(`Added report '${args.code}'.`, {
31676
+ [rel(paths.root, paths.reports)]: jsonText(reports),
31677
+ "manifest.json": jsonText(withTodayStamp(manifest))
31678
+ });
31679
+ }
31680
+ var settingAddSchema = {
31681
+ moduleDir: external_exports.string(),
31682
+ code: external_exports.string().regex(/^[a-z][a-z0-9_]*$/).describe("Setting code (keys settings.json)."),
31683
+ setting: external_exports.object({
31684
+ fieldTypeCd: external_exports.string(),
31685
+ baseDatatypeCd: external_exports.string().optional(),
31686
+ label: external_exports.string().optional(),
31687
+ description: external_exports.string().optional(),
31688
+ defaultValue: external_exports.unknown().optional(),
31689
+ formula: external_exports.string().optional(),
31690
+ required: external_exports.boolean().optional(),
31691
+ params: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
31692
+ }).passthrough()
31693
+ };
31694
+ function settingAdd(args) {
31695
+ const { paths, manifest } = loadManifest(args.moduleDir);
31696
+ const settings = readJsonOrDefault(paths.settings, {});
31697
+ if (Object.prototype.hasOwnProperty.call(settings, args.code)) {
31698
+ throw new Error(`Setting '${args.code}' already exists.`);
31699
+ }
31700
+ settings[args.code] = args.setting;
31701
+ return makeResult(`Added setting '${args.code}'.`, {
31702
+ [rel(paths.root, paths.settings)]: jsonText(settings),
31703
+ "manifest.json": jsonText(withTodayStamp(manifest))
31704
+ });
31705
+ }
31706
+ var roleAddSchema = {
31707
+ moduleDir: external_exports.string(),
31708
+ code: external_exports.string().regex(/^[a-z][a-z0-9_-]*\.[a-z][a-z0-9_-]*$/).describe(
31709
+ "Role code, namespaced as `<module>.<role>` (e.g. 'crm.admin'). Conventional namespacing prevents collisions across modules."
31710
+ ),
31711
+ description: external_exports.string(),
31712
+ rights: external_exports.record(external_exports.string(), external_exports.string()).describe(
31713
+ "Map: entityCode (or actionCode/reportCode) \u2192 rights string. For entities: any combination of 'S' (Select), 'I' (Insert), 'U' (Update), 'D' (Delete), 'C' (Clone), e.g. 'SIUDC' for full. For actions/reports: 'E' (Execute)."
31714
+ )
31715
+ };
31716
+ function roleAdd(args) {
31717
+ const { paths, manifest } = loadManifest(args.moduleDir);
31718
+ const roles = readJsonOrDefault(paths.roles, {});
31719
+ if (Object.prototype.hasOwnProperty.call(roles, args.code)) {
31720
+ throw new Error(`Role '${args.code}' already exists.`);
31721
+ }
31722
+ roles[args.code] = { description: args.description, rights: args.rights };
31723
+ return makeResult(
31724
+ `Added role '${args.code}' with rights on ${Object.keys(args.rights).length} object(s).`,
31725
+ {
31726
+ [rel(paths.root, paths.roles)]: jsonText(roles),
31727
+ "manifest.json": jsonText(withTodayStamp(manifest))
31728
+ }
31729
+ );
31730
+ }
31731
+ var folderAddSchema = {
31732
+ moduleDir: external_exports.string(),
31733
+ parentPath: external_exports.string().default("").describe(
31734
+ "Slash-separated folder codes from root, e.g. 'central/east' nests under root \u2192 central \u2192 east. Empty = add directly under root."
31735
+ ),
31736
+ code: external_exports.string().regex(/^[a-z][a-z0-9_-]*$/).describe("New folder's code (key under parent.children)."),
31737
+ folder: external_exports.object({
31738
+ label: external_exports.string(),
31739
+ description: external_exports.string().optional(),
31740
+ color: external_exports.string().optional(),
31741
+ icon: external_exports.string().optional(),
31742
+ inheritSecurity: external_exports.boolean().optional(),
31743
+ entities: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
31744
+ children: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
31745
+ }).passthrough()
31746
+ };
31747
+ function folderAdd(args) {
31748
+ const { paths, manifest } = loadManifest(args.moduleDir);
31749
+ const root = readJsonOrDefault(paths.folders, {
31750
+ label: manifest.displayName
31751
+ });
31752
+ let cursor = root;
31753
+ const segments = args.parentPath.split("/").filter(Boolean);
31754
+ for (const seg of segments) {
31755
+ const children2 = cursor.children ?? {};
31756
+ if (!children2[seg]) {
31757
+ throw new Error(
31758
+ `Folder path segment '${seg}' not found under '${segments.slice(0, segments.indexOf(seg)).join("/") || "(root)"}'.`
31759
+ );
31760
+ }
31761
+ cursor = children2[seg];
31762
+ }
31763
+ const children = cursor.children ?? {};
31764
+ if (children[args.code]) {
31765
+ throw new Error(`Folder '${args.code}' already exists at '${args.parentPath || "(root)"}'.`);
31766
+ }
31767
+ children[args.code] = args.folder;
31768
+ cursor.children = children;
31769
+ return makeResult(
31770
+ `Added folder '${args.code}' under '${args.parentPath || "(root)"}'.`,
31771
+ {
31772
+ [rel(paths.root, paths.folders)]: jsonText(root),
31773
+ "manifest.json": jsonText(withTodayStamp(manifest))
31774
+ }
31775
+ );
31776
+ }
31777
+ var dependencyAddSchema = {
31778
+ moduleDir: external_exports.string(),
31779
+ moduleCode: external_exports.string().regex(/^[a-z][a-z0-9_-]*$/).describe("Module to depend on."),
31780
+ version: external_exports.string().default(">=0.1.0").describe("Semver range."),
31781
+ entities: external_exports.array(external_exports.string()).optional().describe(
31782
+ "If only specific entities from the dependency are used, list them \u2014 produces the object-style dep `{ version, entities }` for partial coupling. Omit to depend on the whole module."
31783
+ )
31784
+ };
31785
+ function dependencyAdd(args) {
31786
+ const { manifest } = loadManifest(args.moduleDir);
31787
+ if (manifest.code === args.moduleCode) {
31788
+ throw new Error("A module can't depend on itself.");
31789
+ }
31790
+ const deps = manifest.dependencies ?? {};
31791
+ if (Object.prototype.hasOwnProperty.call(deps, args.moduleCode)) {
31792
+ throw new Error(
31793
+ `Dependency on '${args.moduleCode}' already exists. Edit manifest.json directly to change the version.`
31794
+ );
31795
+ }
31796
+ const value = args.entities && args.entities.length > 0 ? { version: args.version, entities: args.entities } : args.version;
31797
+ const newManifest = withTodayStamp({
31798
+ ...manifest,
31799
+ dependencies: { ...deps, [args.moduleCode]: value }
31800
+ });
31801
+ return makeResult(
31802
+ `Added dependency on '${args.moduleCode}' (${args.version})${args.entities ? ` for entities [${args.entities.join(", ")}]` : ""}.`,
31803
+ { "manifest.json": jsonText(newManifest) }
31804
+ );
31805
+ }
31806
+
31807
+ // src/tools/role-right.ts
31808
+ var roleRightSetSchema = {
31809
+ moduleDir: external_exports.string(),
31810
+ roleCode: external_exports.string().describe("Role code as it appears in security/roles.json (e.g. 'crm.admin')."),
31811
+ object: external_exports.string().describe(
31812
+ "Entity / action / report code to grant rights on. For cross-module rights, use dotted form ('fin.invoice')."
31813
+ ),
31814
+ rights: external_exports.string().describe(
31815
+ "Rights string. Entities: any combination of S/I/U/D/C (use '' to revoke all). Actions/reports: 'E' or ''."
31816
+ )
31817
+ };
31818
+ function roleRightSet(args) {
31819
+ const { paths, manifest } = loadManifest(args.moduleDir);
31820
+ const roles = readJson(paths.roles);
31821
+ const role = roles[args.roleCode];
31822
+ if (!role) {
31823
+ throw new Error(
31824
+ `Role '${args.roleCode}' not found in security/roles.json. Use role_add to create it.`
31825
+ );
31826
+ }
31827
+ const rights = role.rights ?? {};
31828
+ if (args.rights === "") {
31829
+ const { [args.object]: _gone, ...rest } = rights;
31830
+ void _gone;
31831
+ role.rights = rest;
31832
+ } else {
31833
+ role.rights = { ...rights, [args.object]: args.rights };
31834
+ }
31835
+ return makeResult(
31836
+ args.rights === "" ? `Revoked all rights on '${args.object}' from role '${args.roleCode}'.` : `Set rights on '${args.object}' for role '${args.roleCode}' to '${args.rights}'.`,
31837
+ {
31838
+ [rel(paths.root, paths.roles)]: jsonText(roles),
31839
+ "manifest.json": jsonText(withTodayStamp(manifest))
31840
+ }
31841
+ );
31842
+ }
31843
+
31844
+ // src/tools/module-inspect.ts
31845
+ var fs4 = __toESM(require("fs"));
31846
+ var path6 = __toESM(require("path"));
31847
+ var moduleInspectSchema = {
31848
+ moduleDir: external_exports.string()
31849
+ };
31850
+ function moduleInspect(args) {
31851
+ const { paths, manifest } = loadManifest(args.moduleDir);
31852
+ const entities = manifest.entities ?? {};
31853
+ const entitySummaries = Object.entries(entities).map(([name, relPath]) => {
31854
+ const abs = path6.join(paths.root, relPath.replace(/^\.\//, ""));
31855
+ const e = readJsonOrDefault(abs, {});
31856
+ const fields = e.fields ?? {};
31857
+ return {
31858
+ name,
31859
+ isExtension: Boolean(e.extends),
31860
+ traits: e.traits ?? [],
31861
+ fieldCount: Object.keys(fields).length,
31862
+ fields: Object.keys(fields),
31863
+ hasNumberSequence: Boolean(e.numberSequence),
31864
+ toString: e.toString
31865
+ };
31866
+ });
31867
+ const views = readJsonOrDefault(paths.dataViews, {});
31868
+ const viewSummaries = Object.entries(views).map(([code, v]) => ({
31869
+ code,
31870
+ viewType: v.viewType ?? "?",
31871
+ sources: (v.dataSources ?? []).map(
31872
+ (s) => s.entityCode ?? "?"
31873
+ )
31874
+ }));
31875
+ const foldersTree = readJsonOrDefault(paths.folders, {});
31876
+ const folderDepth = computeDepth(foldersTree);
31877
+ const menus = readJsonOrDefault(paths.menus, {});
31878
+ const menuSummaries = Object.entries(menus).map(([code, m]) => ({
31879
+ code,
31880
+ itemCount: Object.keys(m.items ?? {}).length
31881
+ }));
31882
+ const rolesJson = readJsonOrDefault(paths.roles, {});
31883
+ const roleSummaries = Object.entries(rolesJson).map(([code, r]) => {
31884
+ const rights = r.rights ?? {};
31885
+ return { code, objectCount: Object.keys(rights).length, rights };
31886
+ });
31887
+ const actions = readJsonOrDefault(paths.actions, {});
31888
+ const actionSummaries = Object.entries(actions).map(([code, a]) => ({
31889
+ code,
31890
+ entity: a.entity ?? "?",
31891
+ mode: a.mode ?? "single",
31892
+ background: Boolean(a.background)
31893
+ }));
31894
+ const reports = Object.keys(readJsonOrDefault(paths.reports, {}));
31895
+ const settings = Object.keys(readJsonOrDefault(paths.settings, {}));
31896
+ const jobs = Object.keys(readJsonOrDefault(paths.jobs, {}));
31897
+ const seedFiles = fs4.existsSync(paths.seedDataDir) ? fs4.readdirSync(paths.seedDataDir).filter((f) => f.endsWith(".json")).sort() : [];
31898
+ const translations = fs4.existsSync(paths.translationsDir) ? fs4.readdirSync(paths.translationsDir).filter((f) => f.endsWith(".json")).sort() : [];
31899
+ const summary = {
31900
+ module: {
31901
+ code: manifest.code,
31902
+ displayName: manifest.displayName,
31903
+ version: manifest.version,
31904
+ dependencies: manifest.dependencies ?? {},
31905
+ auditHistory: manifest.auditHistory,
31906
+ kind: manifest.kind,
31907
+ tags: manifest.tags
31908
+ },
31909
+ entities: entitySummaries,
31910
+ views: viewSummaries,
31911
+ folders: { tree: foldersTree, depth: folderDepth },
31912
+ menus: menuSummaries,
31913
+ roles: roleSummaries,
31914
+ actions: actionSummaries,
31915
+ reports,
31916
+ settings,
31917
+ jobs,
31918
+ seedFiles,
31919
+ translations
31920
+ };
31921
+ return {
31922
+ summary: `Module '${manifest.code}' v${manifest.version}: ${entitySummaries.length} entities, ${viewSummaries.length} views, ${roleSummaries.length} roles, ${actionSummaries.length} actions, ${reports.length} reports.`,
31923
+ files: { "_inspect.json": JSON.stringify(summary, null, " ") + "\n" }
31924
+ };
31925
+ }
31926
+ function computeDepth(folder, current = 1) {
31927
+ const children = folder.children;
31928
+ if (!children) return current;
31929
+ const childDepths = Object.values(children).map(
31930
+ (c) => computeDepth(c, current + 1)
31931
+ );
31932
+ return childDepths.length === 0 ? current : Math.max(...childDepths);
31933
+ }
31934
+
31935
+ // src/resources/index.ts
31936
+ var fs5 = __toESM(require("fs"));
31937
+ var path7 = __toESM(require("path"));
31938
+ var RESOURCES_DIR = path7.resolve(__dirname, "..", "resources");
31368
31939
  function schema(name, label, description) {
31369
31940
  return {
31370
31941
  uri: `dforge://schema/${name}`,
@@ -31443,13 +32014,13 @@ var resources = [
31443
32014
  read: () => readVendored("docs/conventions.md")
31444
32015
  }
31445
32016
  ];
31446
- function readVendored(rel) {
31447
- const p = path3.join(RESOURCES_DIR, rel);
31448
- if (!fs3.existsSync(p)) {
31449
- return `// Resource missing at build time: ${rel}
32017
+ function readVendored(rel2) {
32018
+ const p = path7.join(RESOURCES_DIR, rel2);
32019
+ if (!fs5.existsSync(p)) {
32020
+ return `// Resource missing at build time: ${rel2}
31450
32021
  // Run scripts/vendor-resources.sh in dforge-mcp to refresh from dForge-core.`;
31451
32022
  }
31452
- return fs3.readFileSync(p, "utf8");
32023
+ return fs5.readFileSync(p, "utf8");
31453
32024
  }
31454
32025
 
31455
32026
  // src/server.ts
@@ -31457,9 +32028,26 @@ var server = new McpServer({
31457
32028
  name: "dforge-mcp",
31458
32029
  version: "0.1.0"
31459
32030
  });
32031
+ function envelope(fn) {
32032
+ return async (args) => {
32033
+ try {
32034
+ const r = fn(args);
32035
+ return {
32036
+ content: [{ type: "text", text: JSON.stringify(r, null, 2) }]
32037
+ };
32038
+ } catch (e) {
32039
+ return {
32040
+ content: [
32041
+ { type: "text", text: `Error: ${e.message}` }
32042
+ ],
32043
+ isError: true
32044
+ };
32045
+ }
32046
+ };
32047
+ }
31460
32048
  server.tool(
31461
32049
  "dforge_module_create",
31462
- "Build the file map for a brand-new dForge module. Returns { files: { '<relPath>': '<contents>' } } \u2014 the client decides whether to write them. Always preview the file map with the user before writing.",
32050
+ "PHASE 1: Build the file map for a brand-new dForge module. Returns { files: { '<relPath>': '<contents>' } } \u2014 the client decides whether to write them. Always preview the file map with the user before writing.",
31463
32051
  createModuleSchema,
31464
32052
  async (args) => {
31465
32053
  const files = createModuleFiles(args);
@@ -31480,9 +32068,36 @@ server.tool(
31480
32068
  };
31481
32069
  }
31482
32070
  );
32071
+ server.tool(
32072
+ "dforge_module_inspect",
32073
+ "Read the current state of an existing module from disk and return a structured summary. Call this BEFORE any patch tool so you know what entities/views/roles already exist and don't try to re-create them.",
32074
+ moduleInspectSchema,
32075
+ envelope(moduleInspect)
32076
+ );
32077
+ server.tool(
32078
+ "dforge_module_pack",
32079
+ "Pack a module directory into a .dforge tarball. Requires the dforge-cli native binary on PATH (or set DFORGE_CLI_BINARY).",
32080
+ packModuleSchema,
32081
+ async (args) => {
32082
+ const result = packModule(args);
32083
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
32084
+ }
32085
+ );
32086
+ server.tool(
32087
+ "dforge_module_install",
32088
+ "PHASE 6: Install a module (directory or .dforge tarball) to a running tenant. Runs the FULL server-side validator \u2014 the only real validator. Reads DFORGE_URL / DFORGE_TOKEN env if not passed as args.",
32089
+ installModuleSchema,
32090
+ async (args) => {
32091
+ const result = installModule(args);
32092
+ return {
32093
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
32094
+ isError: !result.ok
32095
+ };
32096
+ }
32097
+ );
31483
32098
  server.tool(
31484
32099
  "dforge_entity_add",
31485
- "Add an entity to an existing module. Reads manifest from disk, returns the updated file map (manifest + new entity + regenerated UI/security). Other files on disk are NOT touched.",
32100
+ "PHASE 2: Add an entity to an existing module. Reads manifest from disk, returns the updated file map (manifest + new entity + regenerated UI/security). Other files on disk are NOT touched.",
31486
32101
  addEntitySchema,
31487
32102
  async (args) => {
31488
32103
  const { files, warning } = addEntityFiles(args);
@@ -31492,7 +32107,7 @@ server.tool(
31492
32107
  type: "text",
31493
32108
  text: JSON.stringify(
31494
32109
  {
31495
- summary: `Adds entity '${args.entity.name}' to ${args.moduleDir}. ${Object.keys(files).length} files changed.`,
32110
+ summary: `Adds entity '${args.entity.name}' to ${args.moduleDir}.`,
31496
32111
  warning,
31497
32112
  files
31498
32113
  },
@@ -31505,27 +32120,76 @@ server.tool(
31505
32120
  }
31506
32121
  );
31507
32122
  server.tool(
31508
- "dforge_module_pack",
31509
- "Pack a module directory into a .dforge tarball. Requires the dforge-cli native binary on PATH (or set DFORGE_CLI_BINARY).",
31510
- packModuleSchema,
31511
- async (args) => {
31512
- const result = packModule(args);
31513
- return {
31514
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
31515
- };
31516
- }
32123
+ "dforge_entity_field_add",
32124
+ "PHASE 2 / backtrack: Add a single new field to an existing entity. Use this (not entity_add) when refining an existing entity \u2014 preserves the rest of the entity definition.",
32125
+ entityFieldAddSchema,
32126
+ envelope(entityFieldAdd)
31517
32127
  );
31518
32128
  server.tool(
31519
- "dforge_module_install",
31520
- "Install a module (directory or .dforge tarball) to a running tenant. Reads DFORGE_URL / DFORGE_TOKEN env if not passed as args.",
31521
- installModuleSchema,
31522
- async (args) => {
31523
- const result = installModule(args);
31524
- return {
31525
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
31526
- isError: !result.ok
31527
- };
31528
- }
32129
+ "dforge_entity_field_modify",
32130
+ "PHASE 2 / backtrack: Replace an existing field's spec on an entity. Pass the full desired field shape, not a partial diff.",
32131
+ entityFieldModifySchema,
32132
+ envelope(entityFieldModify)
32133
+ );
32134
+ server.tool(
32135
+ "dforge_entity_field_remove",
32136
+ "PHASE 2 / backtrack: Remove a field from an entity. WARNING: may break dependent views, role rights, formulas, action DSL, seed data \u2014 re-run dforge_module_inspect after.",
32137
+ entityFieldRemoveSchema,
32138
+ envelope(entityFieldRemove)
32139
+ );
32140
+ server.tool(
32141
+ "dforge_action_add",
32142
+ "PHASE 3: Add a DSL action targeting an entity. Writes logic/actions/<code>.dsl plus an entry in ui/actions.json. Phase 3 is optional \u2014 only call when business logic genuinely requires custom actions.",
32143
+ actionAddSchema,
32144
+ envelope(actionAdd)
32145
+ );
32146
+ server.tool(
32147
+ "dforge_view_add",
32148
+ "PHASE 4: Add a data view to ui/data_views.json. viewType-specific viewConfig is supplied verbatim \u2014 pull dforge://schema/data-views first to know the shape.",
32149
+ viewAddSchema,
32150
+ envelope(viewAdd)
32151
+ );
32152
+ server.tool(
32153
+ "dforge_view_modify",
32154
+ "PHASE 4 / backtrack: Replace an existing view spec. Pass the full desired shape.",
32155
+ viewModifySchema,
32156
+ envelope(viewModify)
32157
+ );
32158
+ server.tool(
32159
+ "dforge_report_add",
32160
+ "PHASE 4: Add a report to ui/reports.json. Read dforge://schema/reports for the layout/datasets/parameters shape.",
32161
+ reportAddSchema,
32162
+ envelope(reportAdd)
32163
+ );
32164
+ server.tool(
32165
+ "dforge_setting_add",
32166
+ "PHASE 4 (polish): Add a configurable module-level setting to settings.json. Settings are folder-scoped at runtime \u2014 values inherit through the folder tree.",
32167
+ settingAddSchema,
32168
+ envelope(settingAdd)
32169
+ );
32170
+ server.tool(
32171
+ "dforge_role_add",
32172
+ "PHASE 5: Add a role to security/roles.json. Roles are namespaced (e.g. 'crm.admin'). Rights are S/I/U/D/C for entities, E for actions/reports.",
32173
+ roleAddSchema,
32174
+ envelope(roleAdd)
32175
+ );
32176
+ server.tool(
32177
+ "dforge_role_right_set",
32178
+ "PHASE 5 / backtrack: Grant or revoke a single right on a single object for a role. Cheaper than role_add when iterating on rights matrix.",
32179
+ roleRightSetSchema,
32180
+ envelope(roleRightSet)
32181
+ );
32182
+ server.tool(
32183
+ "dforge_folder_add",
32184
+ "PHASE 5 (optional): Add a sub-folder to ui/folders.json. Folders are SECURITY boundaries (row-level filters + per-folder role mappings). Most modules don't need any beyond the root \u2014 only use when intake said data must be separated per folder.",
32185
+ folderAddSchema,
32186
+ envelope(folderAdd)
32187
+ );
32188
+ server.tool(
32189
+ "dforge_dependency_add",
32190
+ "Add a dependency on another dForge module to manifest.json. Use the entities list form when only specific entities are imported (smaller coupling).",
32191
+ dependencyAddSchema,
32192
+ envelope(dependencyAdd)
31529
32193
  );
31530
32194
  server.tool(
31531
32195
  "dforge_dbml_import",