forgecad 0.10.5 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/assets/{AdminPage-raksfnNA.js → AdminPage-B1nIvqLS.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-DP3RxhPs.js → BenchmarkPage-YZJbw5nd.js} +1 -1
  3. package/dist/assets/{BlogPage-D7Dos-vl.js → BlogPage-DIWRApKS.js} +1 -1
  4. package/dist/assets/{DocsPage-DO1kvBns.js → DocsPage-ClL6X1hR.js} +2 -22
  5. package/dist/assets/{EditorApp-DQJmcmRT.js → EditorApp-CYBDvSyT.js} +575 -119
  6. package/dist/assets/{EmbedViewer-DFDUhOma.js → EmbedViewer-Dmfu_LIw.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-DbE_tp8-.js → LandingPageProofDriven-XYTiYxfM.js} +1 -1
  8. package/dist/assets/{LegalPage-CominSso.js → LegalPage-D5Z3CscF.js} +1 -1
  9. package/dist/assets/{PricingPage-CcVIN9yj.js → PricingPage-BP4lIGio.js} +1 -1
  10. package/dist/assets/{SettingsPage-DLWcP289.js → SettingsPage-D3bcPBsC.js} +1 -1
  11. package/dist/assets/{app-xW3hOdq9.js → app-BKjogwIZ.js} +2192 -231
  12. package/dist/assets/{backendInit-mDHk97u7.js → backendInit-6a9-ilom.js} +76448 -75066
  13. package/dist/assets/cli/{render--SIU27W_.js → render-CMNudGb0.js} +3 -3
  14. package/dist/assets/{constructionHistoryWorker-uEe_Q7Kg.js → constructionHistoryWorker-BuZgc606.js} +6985 -6706
  15. package/dist/assets/{evalWorker-BqyDHDcI.js → evalWorker-DQ82ueGu.js} +40862 -39497
  16. package/dist/assets/{inspectWorker-UXMxlcR8.js → inspectWorker-Cuby2qfT.js} +2078 -478
  17. package/dist/assets/{jointPose-bYMlwU3v.js → jointPose-CFql5I-u.js} +1 -1
  18. package/dist/assets/{manifold-CyOV5B9S.js → manifold-02pmr7O7.js} +2 -2
  19. package/dist/assets/{manifold-BR7UYI4P.js → manifold-C6KU0oII.js} +1 -1
  20. package/dist/assets/{manifold-D4d5NQst.js → manifold-P1yF3GKn.js} +1 -1
  21. package/dist/assets/{reportWorker-DsaICZsn.js → reportWorker-kg065BVL.js} +85183 -78309
  22. package/dist/cli/render.html +1 -1
  23. package/dist/docs/index.html +2 -2
  24. package/dist/docs-raw/AI/usage.md +6 -8
  25. package/dist/docs-raw/CLI.md +10 -10
  26. package/dist/docs-raw/component-model.md +28 -9
  27. package/dist/docs-raw/generated/concepts.md +13 -4
  28. package/dist/docs-raw/generated/core.md +244 -56
  29. package/dist/docs-raw/generated/curves.md +13 -0
  30. package/dist/docs-raw/generated/runtime-names.md +2 -2
  31. package/dist/docs-raw/guides/inspection-bundles.md +1 -1
  32. package/dist/docs-raw/guides/structural-fea.md +11 -0
  33. package/dist/docs-raw/skills/forgecad-build-model.md +70 -147
  34. package/dist/docs-raw/skills/forgecad-image-prompt.md +1 -1
  35. package/dist/docs-raw/skills/forgecad-project-sync.md +3 -3
  36. package/dist/docs-raw/skills/forgecad-reconstruct-cad-file.md +2 -2
  37. package/dist/docs-raw/skills/forgecad-reconstruct-from-images.md +4 -5
  38. package/dist/docs-raw/skills/forgecad.md +3 -1
  39. package/dist/docs-raw/skills/index.md +1 -5
  40. package/dist/docs-raw/welcome.md +3 -4
  41. package/dist/index.html +1 -1
  42. package/dist/llms.txt +1 -2
  43. package/dist/sitemap.xml +15 -15
  44. package/dist-cli/{check-compiler-7YAHVXYM.js → check-compiler-UJWUEIDC.js} +1 -1
  45. package/dist-cli/{check-query-propagation-ZRR6IOJW.js → check-query-propagation-O2EPDJSY.js} +1 -1
  46. package/dist-cli/{chunk-VNM67DIV.js → chunk-MNDROM7T.js} +77145 -75767
  47. package/dist-cli/forgecad.js +1145 -441
  48. package/dist-skill/CONTEXT.md +429 -64
  49. package/dist-skill/SKILL.md +3 -1
  50. package/dist-skill/docs/API/core/concepts.md +31 -4
  51. package/dist-skill/docs/CLI.md +10 -10
  52. package/dist-skill/docs/generated/core.md +240 -57
  53. package/dist-skill/docs/generated/curves.md +13 -0
  54. package/dist-skill/docs/generated/runtime-names.md +2 -2
  55. package/dist-skill/docs/guides/inspection-bundles.md +1 -1
  56. package/dist-skill/docs/guides/manual-parameters.md +130 -0
  57. package/dist-skill/docs/guides/structural-fea.md +11 -0
  58. package/dist-skill/library/README.md +0 -4
  59. package/dist-skill/library/forgecad-build-model/SKILL.md +57 -150
  60. package/dist-skill/library/forgecad-build-model/references/inspection-feedback.md +58 -0
  61. package/dist-skill/library/forgecad-build-model/references/module-contracts.md +53 -0
  62. package/dist-skill/library/forgecad-build-model/references/parameter-controls.md +22 -0
  63. package/dist-skill/library/forgecad-build-model/references/readiness-review.md +43 -0
  64. package/dist-skill/library/forgecad-build-model/references/simulation-feedback.md +49 -0
  65. package/dist-skill/library/forgecad-build-model/references/stage-1-design-intent.md +21 -0
  66. package/dist-skill/library/forgecad-build-model/references/stage-2-architecture-plan.md +23 -0
  67. package/dist-skill/library/forgecad-build-model/references/stage-3-build-slices.md +39 -0
  68. package/dist-skill/library/forgecad-build-model/references/stage-4-feedback-iteration.md +24 -0
  69. package/dist-skill/library/forgecad-build-model/references/stage-5-readiness-package.md +34 -0
  70. package/dist-skill/library/forgecad-image-prompt/SKILL.md +1 -1
  71. package/dist-skill/library/forgecad-project-sync/SKILL.md +3 -3
  72. package/dist-skill/library/forgecad-reconstruct-cad-file/SKILL.md +2 -2
  73. package/dist-skill/library/forgecad-reconstruct-from-images/SKILL.md +4 -5
  74. package/dist-skill/website/skills/forgecad-build-model.md +70 -147
  75. package/dist-skill/website/skills/forgecad-image-prompt.md +1 -1
  76. package/dist-skill/website/skills/forgecad-project-sync.md +3 -3
  77. package/dist-skill/website/skills/forgecad-reconstruct-cad-file.md +2 -2
  78. package/dist-skill/website/skills/forgecad-reconstruct-from-images.md +4 -5
  79. package/dist-skill/website/skills/forgecad.md +3 -1
  80. package/dist-skill/website/skills/index.md +1 -5
  81. package/examples/api/param-path2d.forge.js +65 -0
  82. package/examples/api/param-placement2d.forge.js +80 -0
  83. package/examples/api/param-spline2d-g-continuity.forge.js +57 -0
  84. package/examples/api/spoon-full-tang-handle.forge.js +57 -17
  85. package/examples/api/surface-variable-thickness-panel.forge.js +62 -0
  86. package/examples/mechanical/airplane-propeller.forge.js +81 -28
  87. package/package.json +2 -2
  88. package/dist/docs-raw/skills/forgecad-design-spec.md +0 -145
  89. package/dist/docs-raw/skills/forgecad-grade-model.md +0 -84
  90. package/dist/docs-raw/skills/forgecad-inspect-model.md +0 -80
  91. package/dist/docs-raw/skills/forgecad-verify-mujoco.md +0 -78
  92. package/dist-skill/library/forgecad-design-spec/SKILL.md +0 -132
  93. package/dist-skill/library/forgecad-design-spec/references/default-profiles.md +0 -99
  94. package/dist-skill/library/forgecad-design-spec/references/master-prompt.md +0 -73
  95. package/dist-skill/library/forgecad-grade-model/SKILL.md +0 -72
  96. package/dist-skill/library/forgecad-grade-model/agents/openai.yaml +0 -4
  97. package/dist-skill/library/forgecad-inspect-model/SKILL.md +0 -68
  98. package/dist-skill/library/forgecad-verify-mujoco/SKILL.md +0 -66
  99. package/dist-skill/website/skills/forgecad-design-spec.md +0 -145
  100. package/dist-skill/website/skills/forgecad-grade-model.md +0 -84
  101. package/dist-skill/website/skills/forgecad-inspect-model.md +0 -80
  102. package/dist-skill/website/skills/forgecad-verify-mujoco.md +0 -78
  103. /package/dist-skill/library/{forgecad-verify-mujoco → forgecad-build-model}/scripts/mujoco_verify.py +0 -0
  104. /package/dist-skill/library/{forgecad-inspect-model → forgecad-build-model/scripts}/summarize_manifest.py +0 -0
@@ -141,7 +141,7 @@ import {
141
141
  updateConstraintValue,
142
142
  validateSimulationModel,
143
143
  wrapOCCTShapeBackend
144
- } from "./chunk-VNM67DIV.js";
144
+ } from "./chunk-MNDROM7T.js";
145
145
 
146
146
  // cli/forgecad.ts
147
147
  import { Command, Flags, Help, flush as flushOclif, handle as handleOclif, run as runOclif } from "@oclif/core";
@@ -2896,9 +2896,9 @@ function captureSnapshot(sketch) {
2896
2896
  };
2897
2897
  }
2898
2898
  function loadSnapshots() {
2899
- const path5 = join2(SNAPSHOT_DIR, "constraint-snapshots.json");
2900
- if (!existsSync(path5)) return {};
2901
- return JSON.parse(readFileSync2(path5, "utf-8"));
2899
+ const path7 = join2(SNAPSHOT_DIR, "constraint-snapshots.json");
2900
+ if (!existsSync(path7)) return {};
2901
+ return JSON.parse(readFileSync2(path7, "utf-8"));
2902
2902
  }
2903
2903
  function saveSnapshots(data) {
2904
2904
  if (!existsSync(SNAPSHOT_DIR)) mkdirSync(SNAPSHOT_DIR, { recursive: true });
@@ -4076,9 +4076,9 @@ function exactRoute(note) {
4076
4076
  function facetedRoute(blocker, note) {
4077
4077
  return { kind: "faceted", blocker, note };
4078
4078
  }
4079
- function partExample(family, path5, route, note, primaryShapes) {
4079
+ function partExample(family, path7, route, note, primaryShapes) {
4080
4080
  return {
4081
- path: path5,
4081
+ path: path7,
4082
4082
  family,
4083
4083
  class: "part",
4084
4084
  validation: "part-runtime",
@@ -4087,9 +4087,9 @@ function partExample(family, path5, route, note, primaryShapes) {
4087
4087
  note
4088
4088
  };
4089
4089
  }
4090
- function assemblyExample(path5, note, expect4) {
4090
+ function assemblyExample(path7, note, expect4) {
4091
4091
  return {
4092
- path: path5,
4092
+ path: path7,
4093
4093
  family: "non-part",
4094
4094
  class: "assembly",
4095
4095
  validation: "assembly-runtime",
@@ -4097,9 +4097,9 @@ function assemblyExample(path5, note, expect4) {
4097
4097
  expect: expect4
4098
4098
  };
4099
4099
  }
4100
- function runtimeSceneExample(path5, note, expect4) {
4100
+ function runtimeSceneExample(path7, note, expect4) {
4101
4101
  return {
4102
- path: path5,
4102
+ path: path7,
4103
4103
  family: "non-part",
4104
4104
  class: "runtime-scene",
4105
4105
  validation: "runtime-scene",
@@ -4107,9 +4107,9 @@ function runtimeSceneExample(path5, note, expect4) {
4107
4107
  expect: expect4
4108
4108
  };
4109
4109
  }
4110
- function sketchExample(path5, note, expect4) {
4110
+ function sketchExample(path7, note, expect4) {
4111
4111
  return {
4112
- path: path5,
4112
+ path: path7,
4113
4113
  family: "non-part",
4114
4114
  class: "sketch",
4115
4115
  validation: "sketch-svg",
@@ -4117,9 +4117,9 @@ function sketchExample(path5, note, expect4) {
4117
4117
  expect: expect4
4118
4118
  };
4119
4119
  }
4120
- function experimentalExample(path5, blocker, taskRef, note) {
4120
+ function experimentalExample(path7, blocker, taskRef, note) {
4121
4121
  return {
4122
- path: path5,
4122
+ path: path7,
4123
4123
  family: "experimental",
4124
4124
  class: "experimental",
4125
4125
  validation: "experimental-runtime",
@@ -4139,6 +4139,8 @@ var API_EXACT_PART_PATHS = [
4139
4139
  "examples/api/folded-service-panel-cover.forge.js",
4140
4140
  "examples/api/group-vs-union.forge.js",
4141
4141
  "examples/api/patterns.forge.js",
4142
+ "examples/api/param-spline2d-g-continuity.forge.js",
4143
+ "examples/api/param-path2d.forge.js",
4142
4144
  "examples/api/text2d-basics.forge.js",
4143
4145
  "examples/api/text2d-font.forge.js",
4144
4146
  "examples/api/extrude-options.forge.js",
@@ -4157,6 +4159,11 @@ var API_FACETED_PARTS = [
4157
4159
  path: "examples/api/profile-2020-b-slot6.forge.js",
4158
4160
  blocker: "The direct 3D profile helper still lowers through segmented profile geometry, so the extrusion must stay on the faceted route for now.",
4159
4161
  note: "The sketch half of the example remains exact-capable; the 3D helper is the intentional blocker."
4162
+ },
4163
+ {
4164
+ path: "examples/api/surface-variable-thickness-panel.forge.js",
4165
+ blocker: "Variable-thickness surface shells lower through sampled UV normal offsets because exact B-rep variable offsets are not available yet.",
4166
+ note: "The example validates the new Thickness.grid() API and the sampled watertight shell path."
4160
4167
  }
4161
4168
  ];
4162
4169
  var API_RECOVERED_FACETED_PARTS = [
@@ -4173,7 +4180,7 @@ var COMPILER_CORPUS_PATHS = [
4173
4180
  ];
4174
4181
  var API_AND_CORPUS_EXAMPLE_MANIFEST = [
4175
4182
  ...API_EXACT_PART_PATHS.map(
4176
- (path5) => partExample("api-parts", path5, exactRoute("This API example now stays inside the defended exact-route subset."))
4183
+ (path7) => partExample("api-parts", path7, exactRoute("This API example now stays inside the defended exact-route subset."))
4177
4184
  ),
4178
4185
  ...API_FACETED_PARTS.map(
4179
4186
  (entry) => partExample("api-parts", entry.path, facetedRoute(entry.blocker, entry.note), void 0, entry.primaryShapes)
@@ -4182,7 +4189,7 @@ var API_AND_CORPUS_EXAMPLE_MANIFEST = [
4182
4189
  (entry) => partExample("api-parts", entry.path, facetedRoute(entry.blocker, entry.note), void 0, entry.primaryShapes)
4183
4190
  ),
4184
4191
  ...COMPILER_CORPUS_PATHS.map(
4185
- (path5) => partExample("compiler-corpus", path5, exactRoute("The compiler corpus is the defended ordinary-part exact subset and must stay exact."))
4192
+ (path7) => partExample("compiler-corpus", path7, exactRoute("The compiler corpus is the defended ordinary-part exact subset and must stay exact."))
4186
4193
  )
4187
4194
  ];
4188
4195
 
@@ -4936,8 +4943,8 @@ var VALIDATION_PATH_BY_CLASS = {
4936
4943
  sketch: "sketch-svg",
4937
4944
  experimental: "experimental-runtime"
4938
4945
  };
4939
- function normalizeManifestPath(path5) {
4940
- return path5.replaceAll("\\", "/");
4946
+ function normalizeManifestPath(path7) {
4947
+ return path7.replaceAll("\\", "/");
4941
4948
  }
4942
4949
  function parseArgs(argv) {
4943
4950
  let profile = "smoke";
@@ -4977,25 +4984,25 @@ function parseArgs(argv) {
4977
4984
  }
4978
4985
  function assertManifestCoverage(entries) {
4979
4986
  const manifestPaths = entries.map((entry) => entry.path);
4980
- const duplicates = manifestPaths.filter((path5, index) => manifestPaths.indexOf(path5) !== index);
4987
+ const duplicates = manifestPaths.filter((path7, index) => manifestPaths.indexOf(path7) !== index);
4981
4988
  const duplicatePaths = [...new Set(duplicates)].sort();
4982
4989
  const discovered = listExampleArtifacts();
4983
4990
  const _manifestSet = new Set(manifestPaths);
4984
4991
  const discoveredSet = new Set(discovered);
4985
4992
  const unclassified = [];
4986
- const missingFiles = manifestPaths.filter((path5) => !discoveredSet.has(path5));
4993
+ const missingFiles = manifestPaths.filter((path7) => !discoveredSet.has(path7));
4987
4994
  const issues = [];
4988
4995
  if (duplicatePaths.length > 0) {
4989
4996
  issues.push(`Duplicate manifest entries:
4990
- ${duplicatePaths.map((path5) => ` - ${path5}`).join("\n")}`);
4997
+ ${duplicatePaths.map((path7) => ` - ${path7}`).join("\n")}`);
4991
4998
  }
4992
4999
  if (unclassified.length > 0) {
4993
5000
  issues.push(`Unclassified example artifacts:
4994
- ${unclassified.map((path5) => ` - ${path5}`).join("\n")}`);
5001
+ ${unclassified.map((path7) => ` - ${path7}`).join("\n")}`);
4995
5002
  }
4996
5003
  if (missingFiles.length > 0) {
4997
5004
  issues.push(`Manifest entries that point at missing files:
4998
- ${missingFiles.map((path5) => ` - ${path5}`).join("\n")}`);
5005
+ ${missingFiles.map((path7) => ` - ${path7}`).join("\n")}`);
4999
5006
  }
5000
5007
  if (issues.length > 0) {
5001
5008
  throw new Error(`Example manifest coverage failed.
@@ -5031,7 +5038,7 @@ function selectEntries(entries, args) {
5031
5038
  }
5032
5039
  if (args.examples.length > 0) {
5033
5040
  const exampleSet = new Set(args.examples);
5034
- const missingExamples = args.examples.filter((path5) => !entries.some((entry) => entry.path === path5));
5041
+ const missingExamples = args.examples.filter((path7) => !entries.some((entry) => entry.path === path7));
5035
5042
  if (missingExamples.length > 0) {
5036
5043
  throw new Error(`Unknown manifest example path(s): ${missingExamples.join(", ")}`);
5037
5044
  }
@@ -5500,9 +5507,9 @@ import { basename as basename3 } from "path";
5500
5507
  var RENDERABLE_INPUT_EXTENSIONS = [".forge.js", ".js", ".stl", ".obj", ".3mf", ".step", ".stp"];
5501
5508
  var EXACT_EXPORT_INPUT_EXTENSIONS = [".forge.js", ".js", ".step", ".stp"];
5502
5509
  var SCRIPT_INPUT_EXTENSIONS = [".forge.js", ".js"];
5503
- function requireInputPaths(inputPaths, usage27) {
5510
+ function requireInputPaths(inputPaths, usage28) {
5504
5511
  if (inputPaths.length === 0) {
5505
- throw new Error(usage27);
5512
+ throw new Error(usage28);
5506
5513
  }
5507
5514
  return inputPaths;
5508
5515
  }
@@ -5554,16 +5561,16 @@ function hasSupportedExtension(inputPath, extensions) {
5554
5561
  import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
5555
5562
  import { homedir } from "os";
5556
5563
  import { resolve as resolve5 } from "path";
5557
- function expandUserPath(path5) {
5558
- if (path5 === "~") return homedir();
5559
- if (path5.startsWith("~/")) return resolve5(homedir(), path5.slice(2));
5560
- return resolve5(path5);
5564
+ function expandUserPath(path7) {
5565
+ if (path7 === "~") return homedir();
5566
+ if (path7.startsWith("~/")) return resolve5(homedir(), path7.slice(2));
5567
+ return resolve5(path7);
5561
5568
  }
5562
5569
  function looksLikePath(value) {
5563
5570
  return value.endsWith(".json") || value.includes("/") || value.includes("\\") || value.startsWith(".") || value.startsWith("~");
5564
5571
  }
5565
- function readJsonFile(path5, flag) {
5566
- const resolved = expandUserPath(path5);
5572
+ function readJsonFile(path7, flag) {
5573
+ const resolved = expandUserPath(path7);
5567
5574
  if (!existsSync3(resolved)) {
5568
5575
  throw new Error(`${flag} file not found: ${resolved}`);
5569
5576
  }
@@ -5576,8 +5583,8 @@ function readJsonFile(path5, flag) {
5576
5583
  }
5577
5584
  return content;
5578
5585
  }
5579
- function readCliJsonFile(path5, flag) {
5580
- return readJsonFile(path5, flag);
5586
+ function readCliJsonFile(path7, flag) {
5587
+ return readJsonFile(path7, flag);
5581
5588
  }
5582
5589
  function readCliJsonOrFile(value, flag) {
5583
5590
  const trimmed = value.trim();
@@ -5702,21 +5709,21 @@ function extractImports(code) {
5702
5709
  let m;
5703
5710
  const forgeRe = new RegExp(FORGE_IMPORT_RE.source, FORGE_IMPORT_RE.flags);
5704
5711
  while ((m = forgeRe.exec(code)) !== null) {
5705
- const path5 = m[1] ?? m[2];
5712
+ const path7 = m[1] ?? m[2];
5706
5713
  const fn = m[0].match(
5707
5714
  /\b(importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.image|Import\.svgSketch|Import\.dxfSketch|compareWith)/
5708
5715
  )?.[1];
5709
- add3(path5, kindMap[fn] ?? "forgeMesh");
5716
+ add3(path7, kindMap[fn] ?? "forgeMesh");
5710
5717
  }
5711
5718
  const reqRe = new RegExp(REQUIRE_RE.source, REQUIRE_RE.flags);
5712
5719
  while ((m = reqRe.exec(code)) !== null) {
5713
- const path5 = m[1] ?? m[2];
5714
- add3(path5, "require");
5720
+ const path7 = m[1] ?? m[2];
5721
+ add3(path7, "require");
5715
5722
  }
5716
5723
  const esRe = new RegExp(ES_IMPORT_RE.source, ES_IMPORT_RE.flags);
5717
5724
  while ((m = esRe.exec(code)) !== null) {
5718
- const path5 = m[1] ?? m[2];
5719
- add3(path5, "esImport");
5725
+ const path7 = m[1] ?? m[2];
5726
+ add3(path7, "esImport");
5720
5727
  }
5721
5728
  return refs;
5722
5729
  }
@@ -5742,8 +5749,8 @@ function resolveRelative(fromFile, importPath) {
5742
5749
  }
5743
5750
  return resolved.join("/");
5744
5751
  }
5745
- function isCompareBinaryPath(path5) {
5746
- return /\.(?:stl|obj|3mf|step|stp)$/i.test(path5);
5752
+ function isCompareBinaryPath(path7) {
5753
+ return /\.(?:stl|obj|3mf|step|stp)$/i.test(path7);
5747
5754
  }
5748
5755
  function collectDependencies(entryFile, allFiles) {
5749
5756
  const codeFiles = {};
@@ -5801,18 +5808,18 @@ function collectDependencies(entryFile, allFiles) {
5801
5808
  var DIRECT_MESH_EXTS = /* @__PURE__ */ new Set([".stl", ".obj", ".3mf"]);
5802
5809
  var DIRECT_STEP_EXTS = /* @__PURE__ */ new Set([".step", ".stp"]);
5803
5810
  var DIRECT_CAD_EXTS = /* @__PURE__ */ new Set([...DIRECT_MESH_EXTS, ...DIRECT_STEP_EXTS]);
5804
- function directCadInputKind(path5) {
5805
- const ext = extname(path5).toLowerCase();
5811
+ function directCadInputKind(path7) {
5812
+ const ext = extname(path7).toLowerCase();
5806
5813
  if (DIRECT_MESH_EXTS.has(ext)) return "mesh";
5807
5814
  if (DIRECT_STEP_EXTS.has(ext)) return "step";
5808
5815
  return null;
5809
5816
  }
5810
- function replaceCliInputExtension(path5, newExt) {
5811
- const lower2 = path5.toLowerCase();
5817
+ function replaceCliInputExtension(path7, newExt) {
5818
+ const lower2 = path7.toLowerCase();
5812
5819
  for (const ext of [".forge.js", ".js", ".svg", ".dxf", ...DIRECT_CAD_EXTS]) {
5813
- if (lower2.endsWith(ext)) return path5.slice(0, -ext.length) + newExt;
5820
+ if (lower2.endsWith(ext)) return path7.slice(0, -ext.length) + newExt;
5814
5821
  }
5815
- return path5 + newExt;
5822
+ return path7 + newExt;
5816
5823
  }
5817
5824
  function buildDirectCadImportScript(fileName, kind) {
5818
5825
  const importFn = kind === "step" ? "Import.step" : "Import.mesh";
@@ -5823,10 +5830,10 @@ function buildDirectCadImportScript(fileName, kind) {
5823
5830
  `return [{ name: ${JSON.stringify(objectName)}, shape: imported }];`
5824
5831
  ].join("\n");
5825
5832
  }
5826
- function loadCliScriptInput(inputPath) {
5833
+ function loadCliScriptInput(inputPath, options = {}) {
5827
5834
  const sourcePath = resolve6(inputPath);
5828
5835
  const directCadKind = directCadInputKind(sourcePath);
5829
- const { projectRoot, allFiles, fileName, readBinaryFile } = collectProjectFiles(inputPath);
5836
+ const { projectRoot, allFiles, fileName, readBinaryFile } = collectProjectFiles(inputPath, options);
5830
5837
  const directThreeMfAnalysis = directCadKind === "mesh" && extname(fileName).toLowerCase() === ".3mf" ? analyze3mf(readBinaryFile(fileName)) : void 0;
5831
5838
  if (!directCadKind) {
5832
5839
  return {
@@ -7787,8 +7794,8 @@ function parseHideArg(argv, idx) {
7787
7794
  if (values.length === 0) throw new Error("--hide requires at least one object name.");
7788
7795
  return { value: values, nextIndex: idx + 1 };
7789
7796
  }
7790
- function replaceRenderableInputExtension(path5, newExt) {
7791
- return replaceCliInputExtension(path5, newExt);
7797
+ function replaceRenderableInputExtension(path7, newExt) {
7798
+ return replaceCliInputExtension(path7, newExt);
7792
7799
  }
7793
7800
  function parseRenderCliOptions(argv) {
7794
7801
  if (argv.length === 0 || argv.includes("-h") || argv.includes("--help")) {
@@ -8756,8 +8763,8 @@ function collectRenderProjectFiles(scriptPath) {
8756
8763
  fileName: pathRelativeTo(projectRoot, absScript)
8757
8764
  };
8758
8765
  }
8759
- function toPortablePath(path5) {
8760
- return path5.split("\\").join("/");
8766
+ function toPortablePath(path7) {
8767
+ return path7.split("\\").join("/");
8761
8768
  }
8762
8769
  function pathRelativeTo(from, to) {
8763
8770
  const rel = relative3(from, to);
@@ -8824,10 +8831,10 @@ function readPackageVersion() {
8824
8831
  }
8825
8832
  function buildViewPathEntries(pathsByView) {
8826
8833
  return Object.fromEntries(
8827
- Object.entries(pathsByView ?? {}).map(([view, path5]) => [
8834
+ Object.entries(pathsByView ?? {}).map(([view, path7]) => [
8828
8835
  view,
8829
8836
  {
8830
- path: path5
8837
+ path: path7
8831
8838
  }
8832
8839
  ])
8833
8840
  );
@@ -10916,8 +10923,8 @@ function projectDesignHistory(trace, options = {}) {
10916
10923
  function plural(count, singular) {
10917
10924
  return `${count} ${singular}${count === 1 ? "" : "s"}`;
10918
10925
  }
10919
- function pathLabel(path5, fallback) {
10920
- return path5.length > 0 ? path5.join(" > ") : fallback;
10926
+ function pathLabel(path7, fallback) {
10927
+ return path7.length > 0 ? path7.join(" > ") : fallback;
10921
10928
  }
10922
10929
  function sourceLabel2(step) {
10923
10930
  if (!step.sourceSpan) return "";
@@ -11222,13 +11229,13 @@ function childPath(parent, childName, index) {
11222
11229
  const label = childName ?? `child ${index + 1}`;
11223
11230
  return parent ? `${parent}.${label}` : label;
11224
11231
  }
11225
- function partShapeLeaves(part, path5 = "") {
11232
+ function partShapeLeaves(part, path7 = "") {
11226
11233
  if (part instanceof Shape) {
11227
11234
  const bbox = partBoundingBox(part);
11228
11235
  if (!bbox) return [];
11229
11236
  return [
11230
11237
  {
11231
- name: path5 || "shape",
11238
+ name: path7 || "shape",
11232
11239
  shape: part,
11233
11240
  bbox,
11234
11241
  bodyCount: bodyCount(part)
@@ -11238,7 +11245,7 @@ function partShapeLeaves(part, path5 = "") {
11238
11245
  const leaves = [];
11239
11246
  part.children.forEach((child, index) => {
11240
11247
  if (child instanceof Shape || child instanceof ShapeGroup) {
11241
- leaves.push(...partShapeLeaves(child, childPath(path5, part.childName(index), index)));
11248
+ leaves.push(...partShapeLeaves(child, childPath(path7, part.childName(index), index)));
11242
11249
  }
11243
11250
  });
11244
11251
  return leaves;
@@ -14061,10 +14068,10 @@ function roundVec2(point) {
14061
14068
  function roundVec3(point) {
14062
14069
  return [round2(point[0]), round2(point[1]), round2(point[2])];
14063
14070
  }
14064
- function fingerprint(path5) {
14071
+ function fingerprint(path7) {
14065
14072
  try {
14066
- if (!statSync6(path5).isFile()) return null;
14067
- const hash = createHash("sha256").update(readFileSync8(path5)).digest("hex");
14073
+ if (!statSync6(path7).isFile()) return null;
14074
+ const hash = createHash("sha256").update(readFileSync8(path7)).digest("hex");
14068
14075
  return `sha256:${hash}`;
14069
14076
  } catch {
14070
14077
  return null;
@@ -14473,8 +14480,8 @@ function printSectionSummary(result) {
14473
14480
  console.log("");
14474
14481
  console.log(`Replay: forgecad inspect replay ${result.artifacts.resultJson} --source <candidate>`);
14475
14482
  }
14476
- function readInspectionResult(path5) {
14477
- const resolved = resolve11(path5);
14483
+ function readInspectionResult(path7) {
14484
+ const resolved = resolve11(path7);
14478
14485
  if (!existsSync7(resolved)) throw new Error(`Inspection result not found: ${resolved}`);
14479
14486
  const parsed = JSON.parse(readFileSync8(resolved, "utf-8"));
14480
14487
  if (parsed.kind !== "forgecad.inspect.section.result" || parsed.replaySpec?.tool !== "section") {
@@ -14791,20 +14798,20 @@ function selectRegion(regions, seed, operation) {
14791
14798
  operationCompatible: false
14792
14799
  };
14793
14800
  }
14794
- function collectProfileUses(plan, path5 = "$") {
14801
+ function collectProfileUses(plan, path7 = "$") {
14795
14802
  switch (plan.kind) {
14796
14803
  case "extrude":
14797
- return [{ operation: "extrude", profile: plan.profile, path: `${path5}.profile` }];
14804
+ return [{ operation: "extrude", profile: plan.profile, path: `${path7}.profile` }];
14798
14805
  case "cut":
14799
- return [...collectProfileUses(plan.base, `${path5}.base`), { operation: "cut", profile: plan.profile, path: `${path5}.profile` }];
14806
+ return [...collectProfileUses(plan.base, `${path7}.base`), { operation: "cut", profile: plan.profile, path: `${path7}.profile` }];
14800
14807
  case "revolve":
14801
- return [{ operation: "revolve", profile: plan.profile, path: `${path5}.profile` }];
14808
+ return [{ operation: "revolve", profile: plan.profile, path: `${path7}.profile` }];
14802
14809
  case "queryOwner":
14803
- return collectProfileUses(plan.base, `${path5}.base`);
14810
+ return collectProfileUses(plan.base, `${path7}.base`);
14804
14811
  case "transform":
14805
- return collectProfileUses(plan.base, `${path5}.base`);
14812
+ return collectProfileUses(plan.base, `${path7}.base`);
14806
14813
  case "boolean":
14807
- return plan.shapes.flatMap((shape, index) => collectProfileUses(shape, `${path5}.shapes[${index}]`));
14814
+ return plan.shapes.flatMap((shape, index) => collectProfileUses(shape, `${path7}.shapes[${index}]`));
14808
14815
  case "shell":
14809
14816
  case "hole":
14810
14817
  case "trimByPlane":
@@ -14817,32 +14824,32 @@ function collectProfileUses(plan, path5 = "$") {
14817
14824
  case "surfaceExtend":
14818
14825
  case "surfaceThicken":
14819
14826
  case "surfaceSolid":
14820
- return collectProfileUses(plan.base, `${path5}.base`);
14827
+ return collectProfileUses(plan.base, `${path7}.base`);
14821
14828
  case "surfaceSew":
14822
- return plan.shapes.flatMap((shape, index) => collectProfileUses(shape, `${path5}.shapes[${index}]`));
14829
+ return plan.shapes.flatMap((shape, index) => collectProfileUses(shape, `${path7}.shapes[${index}]`));
14823
14830
  default:
14824
14831
  return [];
14825
14832
  }
14826
14833
  }
14827
- function summarizeProfilePlan(plan, path5) {
14834
+ function summarizeProfilePlan(plan, path7) {
14828
14835
  switch (plan.kind) {
14829
14836
  case "boolean":
14830
14837
  return {
14831
- path: path5,
14838
+ path: path7,
14832
14839
  kind: plan.kind,
14833
14840
  op: plan.op,
14834
- children: plan.profiles.map((profile, index) => summarizeProfilePlan(profile, `${path5}.profiles[${index}]`))
14841
+ children: plan.profiles.map((profile, index) => summarizeProfilePlan(profile, `${path7}.profiles[${index}]`))
14835
14842
  };
14836
14843
  case "offset":
14837
- return { path: path5, kind: plan.kind, children: [summarizeProfilePlan(plan.base, `${path5}.base`)] };
14844
+ return { path: path7, kind: plan.kind, children: [summarizeProfilePlan(plan.base, `${path7}.base`)] };
14838
14845
  case "project":
14839
14846
  return {
14840
- path: path5,
14847
+ path: path7,
14841
14848
  kind: plan.kind,
14842
- children: plan.replayProfile ? [summarizeProfilePlan(plan.replayProfile, `${path5}.replayProfile`)] : []
14849
+ children: plan.replayProfile ? [summarizeProfilePlan(plan.replayProfile, `${path7}.replayProfile`)] : []
14843
14850
  };
14844
14851
  default:
14845
- return { path: path5, kind: plan.kind, children: [] };
14852
+ return { path: path7, kind: plan.kind, children: [] };
14846
14853
  }
14847
14854
  }
14848
14855
  function readSketchBounds(sketch, diagnostics) {
@@ -17315,12 +17322,12 @@ var ESTABLISHED_LOWERCASE_PUBLIC_API_GLOBALS = /* @__PURE__ */ new Set([
17315
17322
  "require"
17316
17323
  ]);
17317
17324
  function readText(relativePath) {
17318
- const path5 = resolvePackagePath(import.meta.url, relativePath);
17319
- return readFileSync9(path5, "utf8");
17325
+ const path7 = resolvePackagePath(import.meta.url, relativePath);
17326
+ return readFileSync9(path7, "utf8");
17320
17327
  }
17321
17328
  function readSource(relativePath) {
17322
- const path5 = resolvePackagePath(import.meta.url, relativePath);
17323
- return ts.createSourceFile(path5, readText(relativePath), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
17329
+ const path7 = resolvePackagePath(import.meta.url, relativePath);
17330
+ return ts.createSourceFile(path7, readText(relativePath), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
17324
17331
  }
17325
17332
  function propertyNameText(name) {
17326
17333
  if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
@@ -17595,8 +17602,8 @@ function parseArgs4(argv) {
17595
17602
  requireScriptInputPaths(inputPaths);
17596
17603
  return { inputPaths, json, compact, noFailExit, paramOverrides, jointOverrides };
17597
17604
  }
17598
- function finding2(code, message, path5) {
17599
- return { level: "error", code, message, path: path5 };
17605
+ function finding2(code, message, path7) {
17606
+ return { level: "error", code, message, path: path7 };
17600
17607
  }
17601
17608
  function failedReport(source, modelName, errorMessage2, code = "SIM.CHECK.ERROR") {
17602
17609
  const errors = [finding2(code, errorMessage2)];
@@ -17616,8 +17623,8 @@ function resolveSimulationModel(result, jointOverrides) {
17616
17623
  return collectSimulationModel(assembly2.describe(), { state: jointOverrides });
17617
17624
  }
17618
17625
  function renderFinding(finding3) {
17619
- const path5 = finding3.path ? ` (${finding3.path})` : "";
17620
- return ` - [${finding3.code}] ${finding3.message}${path5}`;
17626
+ const path7 = finding3.path ? ` (${finding3.path})` : "";
17627
+ return ` - [${finding3.code}] ${finding3.message}${path7}`;
17621
17628
  }
17622
17629
  function printHuman3(report) {
17623
17630
  if (report.ok) {
@@ -18076,8 +18083,8 @@ function renderFeedbackText(report) {
18076
18083
  for (const finding3 of report.findings) {
18077
18084
  const glyph = finding3.level === "error" ? "\u2717" : finding3.level === "warning" ? "\u26A0" : "\xB7";
18078
18085
  const suggest = finding3.suggest ? ` \u2192 ${finding3.suggest}` : "";
18079
- const path5 = finding3.path ? ` (${finding3.path})` : "";
18080
- lines.push(` ${glyph} [${finding3.code}] ${finding3.message}${path5}${suggest}`);
18086
+ const path7 = finding3.path ? ` (${finding3.path})` : "";
18087
+ lines.push(` ${glyph} [${finding3.code}] ${finding3.message}${path7}${suggest}`);
18081
18088
  }
18082
18089
  if (report.trust.assumptions.length > 0) {
18083
18090
  lines.push(` assumptions: ${report.trust.assumptions.join("; ")}`);
@@ -18882,10 +18889,10 @@ function commandPatternMatches(pattern, commandPath) {
18882
18889
  if (pattern.length === 0 || pattern.length > commandPath.length) return false;
18883
18890
  return pattern.every((part, index) => part === commandPath[index]);
18884
18891
  }
18885
- function readForgeCadCliConfig(path5 = defaultConfigPath()) {
18886
- if (!existsSync8(path5)) return null;
18887
- const parsed = JSON.parse(readFileSync11(path5, "utf-8"));
18888
- return { config: parsed, path: path5 };
18892
+ function readForgeCadCliConfig(path7 = defaultConfigPath()) {
18893
+ if (!existsSync8(path7)) return null;
18894
+ const parsed = JSON.parse(readFileSync11(path7, "utf-8"));
18895
+ return { config: parsed, path: path7 };
18889
18896
  }
18890
18897
  function disabledCommandMatch(commandPath, loaded = readForgeCadCliConfig()) {
18891
18898
  const command = commandPath.join(" ");
@@ -19288,20 +19295,20 @@ async function buildBrepBlob(objects) {
19288
19295
  }
19289
19296
  topoShape = compound;
19290
19297
  }
19291
- const path5 = "/tmp/forgecad-export.brep";
19298
+ const path7 = "/tmp/forgecad-export.brep";
19292
19299
  const pr = new oc.Message_ProgressRange_1();
19293
- const writeSuccess = oc.BRepTools.Write_3(topoShape, path5, pr);
19300
+ const writeSuccess = oc.BRepTools.Write_3(topoShape, path7, pr);
19294
19301
  if (!writeSuccess) {
19295
19302
  throw new Error("BREP export: BRepTools.Write_3 returned failure.");
19296
19303
  }
19297
19304
  let data;
19298
19305
  try {
19299
- data = oc.FS.readFile(path5);
19306
+ data = oc.FS.readFile(path7);
19300
19307
  } catch (err) {
19301
19308
  throw new Error(`BREP export: failed to read written file from virtual FS \u2014 ${err.message || err}`);
19302
19309
  }
19303
19310
  try {
19304
- oc.FS.unlink(path5);
19311
+ oc.FS.unlink(path7);
19305
19312
  } catch {
19306
19313
  }
19307
19314
  return new Blob([data.buffer], { type: "application/octet-stream" });
@@ -19980,9 +19987,9 @@ function readValue4(argv, idx, flag) {
19980
19987
  }
19981
19988
  return next;
19982
19989
  }
19983
- function inferFormatFromPath(path5) {
19984
- if (!path5) return null;
19985
- const ext = extname4(path5).toLowerCase();
19990
+ function inferFormatFromPath(path7) {
19991
+ if (!path7) return null;
19992
+ const ext = extname4(path7).toLowerCase();
19986
19993
  if (ext === ".gif") return "gif";
19987
19994
  if (ext === ".mp4") return "mp4";
19988
19995
  return null;
@@ -21395,9 +21402,9 @@ function normalizeFeaArtifactDigest(value, label) {
21395
21402
  function normalizeFeaArtifactDigestMap(value, label) {
21396
21403
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
21397
21404
  const map = {};
21398
- for (const [path5, digest] of Object.entries(value)) {
21399
- if (!path5.trim()) throw new Error(`${label} cannot contain an empty path`);
21400
- map[path5] = normalizeFeaArtifactDigest(digest, `${label}.${path5}`);
21405
+ for (const [path7, digest] of Object.entries(value)) {
21406
+ if (!path7.trim()) throw new Error(`${label} cannot contain an empty path`);
21407
+ map[path7] = normalizeFeaArtifactDigest(digest, `${label}.${path7}`);
21401
21408
  }
21402
21409
  return map;
21403
21410
  }
@@ -24127,8 +24134,8 @@ function parseGmshMsh41SurfaceMesh(text, surfaceEntityIds) {
24127
24134
  if (triangles.length === 0) throw new Error("Gmsh MSH contains no exterior surface triangles for FEA deformed rendering.");
24128
24135
  return { nodes, triangles, surfaceElements };
24129
24136
  }
24130
- function readJson(packageDir, path5) {
24131
- return requireRecord(JSON.parse(readFileSync17(join9(packageDir, path5), "utf-8")), path5);
24137
+ function readJson(packageDir, path7) {
24138
+ return requireRecord(JSON.parse(readFileSync17(join9(packageDir, path7), "utf-8")), path7);
24132
24139
  }
24133
24140
  function deformedPosition(base, displacement, scaleFactor) {
24134
24141
  return add2(base, scale2(displacement, scaleFactor));
@@ -24235,8 +24242,8 @@ function positiveFiniteNumber(value, label) {
24235
24242
  }
24236
24243
  return value;
24237
24244
  }
24238
- function readPackageJson(packageDir, path5) {
24239
- return JSON.parse(readFileSync18(join10(packageDir, path5), "utf-8"));
24245
+ function readPackageJson(packageDir, path7) {
24246
+ return JSON.parse(readFileSync18(join10(packageDir, path7), "utf-8"));
24240
24247
  }
24241
24248
  function packageYieldStrengthMpa(packageDir) {
24242
24249
  const manifest = readPackageJson(packageDir, "fea-manifest.json");
@@ -25599,9 +25606,9 @@ function fileStemForObject(name) {
25599
25606
  function defaultOutputPath4(scriptPath, format) {
25600
25607
  return replaceCliInputExtension(scriptPath, format === "json" ? ".implicit.json" : ".implicit-webgpu");
25601
25608
  }
25602
- function writeJsonFile(path5, value) {
25603
- mkdirSync6(dirname6(path5), { recursive: true });
25604
- writeFileSync11(path5, `${JSON.stringify(value, null, 2)}
25609
+ function writeJsonFile(path7, value) {
25610
+ mkdirSync6(dirname6(path7), { recursive: true });
25611
+ writeFileSync11(path7, `${JSON.stringify(value, null, 2)}
25605
25612
  `, "utf-8");
25606
25613
  }
25607
25614
  async function runImplicitExportCli(argv = process.argv.slice(2)) {
@@ -29695,19 +29702,19 @@ function requireMetricValue(report, key) {
29695
29702
  if (value === null) throw new Error(`Solved structural FEA feedback is missing finite metric ${key}`);
29696
29703
  return value;
29697
29704
  }
29698
- function requireDigest(map, path5, label) {
29699
- const digest = map[path5];
29700
- if (!digest) throw new Error(`fea-manifest.json artifactDigests is missing ${label} (${path5}).`);
29705
+ function requireDigest(map, path7, label) {
29706
+ const digest = map[path7];
29707
+ if (!digest) throw new Error(`fea-manifest.json artifactDigests is missing ${label} (${path7}).`);
29701
29708
  return digest;
29702
29709
  }
29703
- function requireActualDigest(map, path5, label) {
29704
- const digest = map[path5];
29705
- if (!digest) throw new Error(`actual package artifact digest is missing ${label} (${path5}).`);
29710
+ function requireActualDigest(map, path7, label) {
29711
+ const digest = map[path7];
29712
+ if (!digest) throw new Error(`actual package artifact digest is missing ${label} (${path7}).`);
29706
29713
  return digest;
29707
29714
  }
29708
- function requirePackageDigestMatch(expected, actual, path5, label) {
29709
- const expectedDigest = requireDigest(expected, path5, label);
29710
- requireMatchingFeaArtifactDigest(requireActualDigest(actual, path5, label), expectedDigest, label);
29715
+ function requirePackageDigestMatch(expected, actual, path7, label) {
29716
+ const expectedDigest = requireDigest(expected, path7, label);
29717
+ requireMatchingFeaArtifactDigest(requireActualDigest(actual, path7, label), expectedDigest, label);
29711
29718
  return expectedDigest;
29712
29719
  }
29713
29720
  function resolveStructuralStressPackageDigests(manifestArtifactDigests, actualArtifactDigests, paths) {
@@ -29726,8 +29733,8 @@ function resolveStructuralStressPackageDigests(manifestArtifactDigests, actualAr
29726
29733
  } : {}
29727
29734
  };
29728
29735
  }
29729
- function requireArtifactDigestForPath(digests, path5, label) {
29730
- return requirePackageDigestMatch(digests.expected, digests.actual, path5, label);
29736
+ function requireArtifactDigestForPath(digests, path7, label) {
29737
+ return requirePackageDigestMatch(digests.expected, digests.actual, path7, label);
29731
29738
  }
29732
29739
  function requireFeedbackMetricMatches(report, key, value) {
29733
29740
  const feedbackValue = requireMetricValue(report, key);
@@ -29977,15 +29984,15 @@ function failedReport4(source, message, code, suggest) {
29977
29984
  timeMs: 0
29978
29985
  };
29979
29986
  }
29980
- function readPackageJson2(packageDir, path5) {
29981
- return JSON.parse(readFileSync22(join16(packageDir, path5), "utf-8"));
29987
+ function readPackageJson2(packageDir, path7) {
29988
+ return JSON.parse(readFileSync22(join16(packageDir, path7), "utf-8"));
29982
29989
  }
29983
29990
  function digestPackageArtifacts(packageDir, manifest) {
29984
29991
  const declared = manifest.artifactDigests;
29985
29992
  if (!declared || typeof declared !== "object" || Array.isArray(declared)) return {};
29986
29993
  const digests = {};
29987
- for (const path5 of Object.keys(declared)) {
29988
- digests[path5] = digestFeaArtifactBytes(readFileSync22(join16(packageDir, path5)));
29994
+ for (const path7 of Object.keys(declared)) {
29995
+ digests[path7] = digestFeaArtifactBytes(readFileSync22(join16(packageDir, path7)));
29989
29996
  }
29990
29997
  return digests;
29991
29998
  }
@@ -31954,6 +31961,7 @@ async function exportSketchPdfInput(scriptPath, explicitOutputPath, paramOverrid
31954
31961
  import { cpSync, existsSync as existsSync17, mkdirSync as mkdirSync9, readdirSync as readdirSync6, readFileSync as readFileSync24, rmSync as rmSync4, writeFileSync as writeFileSync16 } from "fs";
31955
31962
  import { homedir as homedir6 } from "os";
31956
31963
  import { extname as extname12, join as join17, relative as relative5, resolve as resolve40 } from "path";
31964
+ import { createInterface } from "readline";
31957
31965
  var INSTALL_TARGETS = {
31958
31966
  agents: { label: "agent-compatible", root: join17(homedir6(), ".agents", "skills") },
31959
31967
  claude: { label: "Claude Code", root: join17(homedir6(), ".claude", "skills") },
@@ -31962,27 +31970,6 @@ var INSTALL_TARGETS = {
31962
31970
  };
31963
31971
  var ALL_INSTALL_TARGETS = Object.keys(INSTALL_TARGETS);
31964
31972
  var DEFAULT_INSTALL_TARGET = "agents";
31965
- var RETIRED_COMPANION_SKILL_NAMES = [
31966
- "forgecad-3d-reconstruction",
31967
- "forgecad-assembly-contract",
31968
- "forgecad-benchmark-reconstruction",
31969
- "forgecad-blockout",
31970
- "forgecad-blockout-model",
31971
- "forgecad-component-model",
31972
- "forgecad-high-level-spec",
31973
- "forgecad-image-replicator",
31974
- "forgecad-lld",
31975
- "forgecad-make-a-model",
31976
- "forgecad-model-grader",
31977
- "forgecad-mujoco-verify",
31978
- "forgecad-prepare-prompt",
31979
- "forgecad-project",
31980
- "forgecad-reconstruction-benchmark",
31981
- "forgecad-render-inspect",
31982
- "forgecad-spec-by-walking-through-it",
31983
- "forgecad-visual-prompt",
31984
- "forgecad-visual-spec"
31985
- ];
31986
31973
  function installUsage() {
31987
31974
  return [
31988
31975
  "Usage: forgecad skill install [--target agents|claude|codex|opencode|all] [--core-only]",
@@ -32073,7 +32060,39 @@ If you are running from a source checkout, run: npm run build:skill:forgecad`
32073
32060
  console.log(`ForgeCAD skill installed to ${dest} [standard model authoring]`);
32074
32061
  return dest;
32075
32062
  }
32076
- function installCompanionLibrary(targetRoot) {
32063
+ function confirm(question) {
32064
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
32065
+ return new Promise((resolve54) => {
32066
+ rl.question(`${question} [y/N] `, (answer) => {
32067
+ rl.close();
32068
+ resolve54(/^(y|yes)$/i.test(answer.trim()));
32069
+ });
32070
+ });
32071
+ }
32072
+ function discoverCompanionSkillNames(srcLibrary) {
32073
+ return readdirSync6(srcLibrary, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync17(join17(srcLibrary, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
32074
+ }
32075
+ function findStaleForgeCadCompanionSkillNames(targetRoot, currentSkillNames) {
32076
+ if (!existsSync17(targetRoot)) return [];
32077
+ return readdirSync6(targetRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => name.startsWith("forgecad-")).filter((name) => !currentSkillNames.has(name)).filter((name) => existsSync17(join17(targetRoot, name, "SKILL.md"))).sort();
32078
+ }
32079
+ async function removeConfirmedStaleCompanionSkills(targetRoot, currentSkillNames) {
32080
+ const staleNames = findStaleForgeCadCompanionSkillNames(targetRoot, currentSkillNames);
32081
+ if (staleNames.length === 0) return;
32082
+ console.warn(`Found ForgeCAD-named skill directories that are not in this package at ${targetRoot}:`);
32083
+ for (const name of staleNames) console.warn(` - ${name}`);
32084
+ if (!process.stdin.isTTY || !process.stderr.isTTY) {
32085
+ console.warn("Skipping stale skill cleanup because this shell is not interactive. Delete them manually or rerun in a terminal.");
32086
+ return;
32087
+ }
32088
+ if (!await confirm("Remove these stale ForgeCAD companion skill directories?")) {
32089
+ console.log("Skipped stale ForgeCAD companion skill cleanup.");
32090
+ return;
32091
+ }
32092
+ for (const name of staleNames) rmSync4(join17(targetRoot, name), { recursive: true, force: true });
32093
+ console.log(`Removed ${staleNames.length} stale ForgeCAD companion skill${staleNames.length === 1 ? "" : "s"}.`);
32094
+ }
32095
+ async function installCompanionLibrary(targetRoot) {
32077
32096
  const srcLibrary = resolvePackagePath(import.meta.url, "dist-skill", "library");
32078
32097
  if (!existsSync17(srcLibrary)) {
32079
32098
  throw new Error(
@@ -32082,17 +32101,15 @@ If you are running from a source checkout, run: npm run build:skill:forgecad`
32082
32101
  );
32083
32102
  }
32084
32103
  mkdirSync9(targetRoot, { recursive: true });
32085
- for (const name of RETIRED_COMPANION_SKILL_NAMES) {
32086
- rmSync4(join17(targetRoot, name), { recursive: true, force: true });
32087
- }
32104
+ const currentSkillNames = discoverCompanionSkillNames(srcLibrary);
32105
+ await removeConfirmedStaleCompanionSkills(targetRoot, new Set(currentSkillNames));
32088
32106
  const installed = [];
32089
- for (const entry of readdirSync6(srcLibrary, { withFileTypes: true })) {
32090
- if (!entry.isDirectory()) continue;
32091
- const src = join17(srcLibrary, entry.name);
32092
- const dest = join17(targetRoot, entry.name);
32107
+ for (const name of currentSkillNames) {
32108
+ const src = join17(srcLibrary, name);
32109
+ const dest = join17(targetRoot, name);
32093
32110
  rmSync4(dest, { recursive: true, force: true });
32094
32111
  cpSync(src, dest, { recursive: true });
32095
- installed.push(entry.name);
32112
+ installed.push(name);
32096
32113
  }
32097
32114
  if (installed.length === 0) {
32098
32115
  throw new Error(`No companion skills found in ${srcLibrary}`);
@@ -32107,7 +32124,7 @@ async function runSkillInstallCli(argv = []) {
32107
32124
  const config = INSTALL_TARGETS[target];
32108
32125
  console.log(`Installing ForgeCAD skills for ${config.label} at ${config.root}`);
32109
32126
  installCoreSkill(config.root);
32110
- if (options.includeLibrary) installCompanionLibrary(config.root);
32127
+ if (options.includeLibrary) await installCompanionLibrary(config.root);
32111
32128
  }
32112
32129
  console.log(`Reload your agent (Claude Code, Codex, OpenCode, \u2026) to activate.`);
32113
32130
  }
@@ -32129,11 +32146,11 @@ If you are running from a source checkout, run: npm run build:skill:forgecad`
32129
32146
  console.log(`ForgeCAD context written to ${dest}`);
32130
32147
  console.log(`Paste the contents into any AI chat UI (Claude.ai, ChatGPT, Gemini, \u2026) to get full ForgeCAD API knowledge.`);
32131
32148
  }
32132
- function normalizeRelativePath(path5) {
32133
- return path5.replace(/\\/g, "/");
32149
+ function normalizeRelativePath(path7) {
32150
+ return path7.replace(/\\/g, "/");
32134
32151
  }
32135
- function languageForPath(path5) {
32136
- const ext = extname12(path5).toLowerCase();
32152
+ function languageForPath(path7) {
32153
+ const ext = extname12(path7).toLowerCase();
32137
32154
  if (ext === ".js" || ext === ".mjs" || ext === ".cjs") return "js";
32138
32155
  if (ext === ".ts" || ext === ".tsx") return "ts";
32139
32156
  if (ext === ".json") return "json";
@@ -32165,8 +32182,8 @@ function findSkillFiles(root) {
32165
32182
  return a.label.localeCompare(b.label);
32166
32183
  });
32167
32184
  }
32168
- function renderFlattenedFile(label, path5, isFirst) {
32169
- const raw = readFileSync24(path5, "utf-8").trimEnd().replaceAll("{{SKILL_DIR}}/", "").replaceAll("{{SKILL_DIR}}", ".");
32185
+ function renderFlattenedFile(label, path7, isFirst) {
32186
+ const raw = readFileSync24(path7, "utf-8").trimEnd().replaceAll("{{SKILL_DIR}}/", "").replaceAll("{{SKILL_DIR}}", ".");
32170
32187
  if (isFirst) return raw;
32171
32188
  if (label.endsWith(".md")) {
32172
32189
  return [`## File: \`${label}\``, "", raw].join("\n");
@@ -32239,9 +32256,9 @@ import { resolve as resolve41 } from "path";
32239
32256
 
32240
32257
  // cli/forge-studio-server.ts
32241
32258
  import chokidar from "chokidar";
32242
- import fs2 from "fs";
32259
+ import fs3 from "fs";
32243
32260
  import http from "http";
32244
- import path3 from "path";
32261
+ import path4 from "path";
32245
32262
 
32246
32263
  // server/importAnalysis.ts
32247
32264
  var FORGE_IMPORT_RE2 = /\b(?:importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.svgSketch|Import\.dxfSketch)\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
@@ -32269,19 +32286,19 @@ function extractImports2(code) {
32269
32286
  let m;
32270
32287
  const forgeRe = new RegExp(FORGE_IMPORT_RE2.source, FORGE_IMPORT_RE2.flags);
32271
32288
  while ((m = forgeRe.exec(code)) !== null) {
32272
- const path5 = m[1] ?? m[2];
32289
+ const path7 = m[1] ?? m[2];
32273
32290
  const fn = m[0].match(/\b(importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.svgSketch|Import\.dxfSketch)/)?.[1];
32274
- add3(path5, kindMap[fn] ?? "forgeMesh");
32291
+ add3(path7, kindMap[fn] ?? "forgeMesh");
32275
32292
  }
32276
32293
  const reqRe = new RegExp(REQUIRE_RE2.source, REQUIRE_RE2.flags);
32277
32294
  while ((m = reqRe.exec(code)) !== null) {
32278
- const path5 = m[1] ?? m[2];
32279
- add3(path5, "require");
32295
+ const path7 = m[1] ?? m[2];
32296
+ add3(path7, "require");
32280
32297
  }
32281
32298
  const esRe = new RegExp(ES_IMPORT_RE2.source, ES_IMPORT_RE2.flags);
32282
32299
  while ((m = esRe.exec(code)) !== null) {
32283
- const path5 = m[1] ?? m[2];
32284
- add3(path5, "esImport");
32300
+ const path7 = m[1] ?? m[2];
32301
+ add3(path7, "esImport");
32285
32302
  }
32286
32303
  return refs;
32287
32304
  }
@@ -32350,7 +32367,7 @@ import { join as join19 } from "path";
32350
32367
  import { chmodSync as chmodSync2, existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync25, unlinkSync, writeFileSync as writeFileSync17 } from "fs";
32351
32368
  import { homedir as homedir7 } from "os";
32352
32369
  import { join as join18 } from "path";
32353
- import { createInterface } from "readline";
32370
+ import { createInterface as createInterface2 } from "readline";
32354
32371
 
32355
32372
  // cli/server-url.ts
32356
32373
  import { execFile, execFileSync } from "child_process";
@@ -32396,14 +32413,14 @@ function normalizeServerUrl(raw, label = "Server URL") {
32396
32413
  function isOfficialServerUrl(server) {
32397
32414
  return normalizeServerUrl(server) === DEFAULT_SERVER;
32398
32415
  }
32399
- function requireRelativeApiPath(path5) {
32400
- if (!path5.startsWith("/") || path5.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(path5)) {
32416
+ function requireRelativeApiPath(path7) {
32417
+ if (!path7.startsWith("/") || path7.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(path7)) {
32401
32418
  throw new Error("Authenticated ForgeCAD requests must use a relative API path.");
32402
32419
  }
32403
- return path5;
32420
+ return path7;
32404
32421
  }
32405
- function serverUrlWithPath(server, path5) {
32406
- return `${normalizeServerUrl(server)}${requireRelativeApiPath(path5)}`;
32422
+ function serverUrlWithPath(server, path7) {
32423
+ return `${normalizeServerUrl(server)}${requireRelativeApiPath(path7)}`;
32407
32424
  }
32408
32425
  function normalizeBrowserUrl(raw) {
32409
32426
  const url = parseUrl(raw, "Browser URL");
@@ -32493,7 +32510,7 @@ function getEnvToken() {
32493
32510
  return { token, server: normalizeServerUrl(process.env.FORGECAD_SERVER ?? DEFAULT_SERVER, "FORGECAD_SERVER") };
32494
32511
  }
32495
32512
  function prompt(question) {
32496
- const rl = createInterface({ input: process.stdin, output: process.stderr });
32513
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
32497
32514
  return new Promise((resolve54) => {
32498
32515
  rl.question(question, (answer) => {
32499
32516
  rl.close();
@@ -32684,8 +32701,8 @@ async function refreshTokens(auth) {
32684
32701
  return null;
32685
32702
  }
32686
32703
  }
32687
- async function authenticatedFetch(path5, init2) {
32688
- const apiPath = requireRelativeApiPath(path5);
32704
+ async function authenticatedFetch(path7, init2) {
32705
+ const apiPath = requireRelativeApiPath(path7);
32689
32706
  const envToken = getEnvToken();
32690
32707
  if (envToken) {
32691
32708
  const url = serverUrlWithPath(envToken.server, apiPath);
@@ -32816,6 +32833,19 @@ function requireManifest(projectDir) {
32816
32833
  }
32817
32834
  return m;
32818
32835
  }
32836
+ function hasRemoteProject(manifest) {
32837
+ return typeof manifest.projectId === "string" && manifest.projectId.length > 0;
32838
+ }
32839
+ function requireRemoteManifest(projectDir, action = "use this command") {
32840
+ const manifest = requireManifest(projectDir);
32841
+ if (!hasRemoteProject(manifest)) {
32842
+ throw new Error(
32843
+ `This ForgeCAD project has not been created on the server yet.
32844
+ Run \`forgecad project push\` to create the hosted project, then ${action}.`
32845
+ );
32846
+ }
32847
+ return manifest;
32848
+ }
32819
32849
  function contentHash(content) {
32820
32850
  return createHash2("sha256").update(content, "utf-8").digest("hex").slice(0, 16);
32821
32851
  }
@@ -32839,14 +32869,14 @@ function scanLocalFiles(projectDir) {
32839
32869
  return files;
32840
32870
  }
32841
32871
  function remoteFilesFromInit(entries) {
32842
- return Object.entries(entries).map(([path5, content]) => ({
32843
- path: path5,
32872
+ return Object.entries(entries).map(([path7, content]) => ({
32873
+ path: path7,
32844
32874
  content,
32845
32875
  hash: contentHash(content)
32846
32876
  }));
32847
32877
  }
32848
- function projectPathLeaf(path5) {
32849
- return path5.split("/").pop() ?? path5;
32878
+ function projectPathLeaf(path7) {
32879
+ return path7.split("/").pop() ?? path7;
32850
32880
  }
32851
32881
  function findProjectMoveCandidates(fromPath, remoteFile, untracked) {
32852
32882
  const fromLeaf = projectPathLeaf(fromPath);
@@ -32875,17 +32905,17 @@ function diffFiles(local, remote) {
32875
32905
  const remoteMap = new Map(remote.map((f2) => [f2.path, f2]));
32876
32906
  const allPaths = /* @__PURE__ */ new Set([...localMap.keys(), ...remoteMap.keys()]);
32877
32907
  const diffs = [];
32878
- for (const path5 of allPaths) {
32879
- const l = localMap.get(path5);
32880
- const r = remoteMap.get(path5);
32908
+ for (const path7 of allPaths) {
32909
+ const l = localMap.get(path7);
32910
+ const r = remoteMap.get(path7);
32881
32911
  if (l && !r) {
32882
- diffs.push({ path: path5, status: "added" });
32912
+ diffs.push({ path: path7, status: "added" });
32883
32913
  } else if (!l && r) {
32884
- diffs.push({ path: path5, status: "deleted" });
32914
+ diffs.push({ path: path7, status: "deleted" });
32885
32915
  } else if (l && r && l.hash !== r.hash) {
32886
- diffs.push({ path: path5, status: "modified" });
32916
+ diffs.push({ path: path7, status: "modified" });
32887
32917
  } else {
32888
- diffs.push({ path: path5, status: "unchanged" });
32918
+ diffs.push({ path: path7, status: "unchanged" });
32889
32919
  }
32890
32920
  }
32891
32921
  return diffs.sort((a, b) => a.path.localeCompare(b.path));
@@ -33086,7 +33116,7 @@ import { existsSync as existsSync20, readdirSync as readdirSync8, statSync as st
33086
33116
  import { createServer as createServer2 } from "net";
33087
33117
  import { join as join20 } from "path";
33088
33118
  var startedComputeServer = null;
33089
- var REQUIRED_ENGINE_VERSION = "native-occt-node-api-0.3.1";
33119
+ var REQUIRED_ENGINE_VERSION = "backend-ir-node-api-0.1.0";
33090
33120
  var REQUIRED_PLAN_KINDS = [
33091
33121
  "box",
33092
33122
  "cylinder",
@@ -33119,12 +33149,14 @@ async function isHealthy(computeUrl) {
33119
33149
  const response = await fetch(`${computeUrl}/health`, { signal: AbortSignal.timeout(1e3) });
33120
33150
  if (!response.ok) return false;
33121
33151
  const body = await response.json().catch(() => null);
33122
- if (body?.status !== "ok" || body?.nativeOcctAvailable !== true || body?.engineVersion !== REQUIRED_ENGINE_VERSION) return false;
33152
+ if (body?.status !== "ok" || body?.engineVersion !== REQUIRED_ENGINE_VERSION) return false;
33153
+ if (body?.nativeOcctAvailable !== true && body?.sdfAvailable !== true) return false;
33123
33154
  const capabilitiesResponse = await fetch(`${computeUrl}/capabilities`, { signal: AbortSignal.timeout(1e3) });
33124
33155
  if (!capabilitiesResponse.ok) return false;
33125
33156
  const capabilities = await capabilitiesResponse.json().catch(() => null);
33126
33157
  const supportedKinds = capabilities?.nativeOcct?.supportedPlanKinds;
33127
- return Array.isArray(supportedKinds) && REQUIRED_PLAN_KINDS.every((kind) => supportedKinds.includes(kind));
33158
+ const supportedSdfKinds = capabilities?.sdf?.supportedPlanKinds;
33159
+ return Array.isArray(supportedKinds) && REQUIRED_PLAN_KINDS.every((kind) => supportedKinds.includes(kind)) && Array.isArray(supportedSdfKinds) && supportedSdfKinds.includes("sdf");
33128
33160
  } catch {
33129
33161
  return false;
33130
33162
  }
@@ -33135,7 +33167,7 @@ async function waitForHealthy(computeUrl, child) {
33135
33167
  if (child.exitCode != null) break;
33136
33168
  await new Promise((resolve54) => setTimeout(resolve54, 50));
33137
33169
  }
33138
- throw new Error(`Native OCCT compute backend did not become healthy at ${computeUrl}.`);
33170
+ throw new Error(`Backend compute server did not become healthy at ${computeUrl}.`);
33139
33171
  }
33140
33172
  function closeChild(child) {
33141
33173
  return new Promise((resolve54) => {
@@ -33150,13 +33182,13 @@ function closeChild(child) {
33150
33182
  function hasLocalNativeComputeSource(packageRoot) {
33151
33183
  return existsSync20(join20(packageRoot, "apps/backend/src/server.ts")) && existsSync20(join20(packageRoot, "scripts/build-native-occt.mjs"));
33152
33184
  }
33153
- function newestMtimeMs(path5) {
33154
- if (!existsSync20(path5)) return 0;
33155
- const stat = statSync11(path5);
33185
+ function newestMtimeMs(path7) {
33186
+ if (!existsSync20(path7)) return 0;
33187
+ const stat = statSync11(path7);
33156
33188
  if (!stat.isDirectory()) return stat.mtimeMs;
33157
33189
  let newest = stat.mtimeMs;
33158
- for (const entry of readdirSync8(path5, { withFileTypes: true })) {
33159
- newest = Math.max(newest, newestMtimeMs(join20(path5, entry.name)));
33190
+ for (const entry of readdirSync8(path7, { withFileTypes: true })) {
33191
+ newest = Math.max(newest, newestMtimeMs(join20(path7, entry.name)));
33160
33192
  }
33161
33193
  return newest;
33162
33194
  }
@@ -33192,7 +33224,7 @@ async function pickComputeUrl(preferred) {
33192
33224
  } catch {
33193
33225
  }
33194
33226
  }
33195
- throw new Error(`No local port available for native OCCT compute near ${preferred.href}.`);
33227
+ throw new Error(`No local port available for backend compute near ${preferred.href}.`);
33196
33228
  }
33197
33229
  function startLocalNativeComputeServer(options) {
33198
33230
  if (startedComputeServer) return startedComputeServer;
@@ -33227,7 +33259,7 @@ function startLocalNativeComputeServer(options) {
33227
33259
  const selected = new URL(computeUrl);
33228
33260
  const port = selected.port || (selected.protocol === "https:" ? "443" : "80");
33229
33261
  const host = listenHostForUrl(selected);
33230
- console.log(`[compute] Starting native OCCT backend at ${computeUrl}`);
33262
+ console.log(`[compute] Starting backend compute server at ${computeUrl}`);
33231
33263
  const child = spawn3("npx", ["tsx", "apps/backend/src/server.ts"], {
33232
33264
  cwd: options.packageRoot,
33233
33265
  env: { ...process.env, FORGE_BACKEND_PORT: port, FORGE_BACKEND_HOST: host },
@@ -33528,6 +33560,279 @@ function updateLocalAgentContextLatestBundle(projectRoot, filePath, latestBundle
33528
33560
  return next;
33529
33561
  }
33530
33562
 
33563
+ // cli/manual-param-edits-storage.ts
33564
+ import fs2 from "fs";
33565
+ import path3 from "path";
33566
+
33567
+ // src/studio/manual-params/schema.ts
33568
+ var MANUAL_PARAM_EDITS_DRAFT_KIND = "forgecad.manualParamEditsDraft.v1";
33569
+ var MANUAL_PARAM_EDITS_KIND = "forgecad.manualParamEdits.v1";
33570
+ function isRecord2(value) {
33571
+ return typeof value === "object" && value !== null && !Array.isArray(value);
33572
+ }
33573
+ function requireString4(value, label) {
33574
+ if (typeof value !== "string" || value.trim() === "") throw new Error(`Invalid manual edits ${label}`);
33575
+ return value.trim();
33576
+ }
33577
+ function optionalString3(value) {
33578
+ if (value == null) return void 0;
33579
+ if (typeof value !== "string") throw new Error("Invalid manual edits unit");
33580
+ const trimmed = value.trim();
33581
+ return trimmed === "" ? void 0 : trimmed;
33582
+ }
33583
+ function requireFiniteNumber2(value, label) {
33584
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`Invalid manual edits ${label}`);
33585
+ return value;
33586
+ }
33587
+ function requireInteger(value, label) {
33588
+ const number = requireFiniteNumber2(value, label);
33589
+ if (!Number.isInteger(number)) throw new Error(`Invalid manual edits ${label}`);
33590
+ return number;
33591
+ }
33592
+ function parseFilePath(value) {
33593
+ const filePath = requireString4(value, "filePath").replace(/\\/g, "/");
33594
+ const parts = filePath.split("/");
33595
+ if (filePath.startsWith("/") || parts.some((part) => part === "" || part === "." || part === "..") || filePath.includes("\0")) {
33596
+ throw new Error("Invalid manual edits filePath");
33597
+ }
33598
+ return filePath;
33599
+ }
33600
+ function parseContinuity(value, label) {
33601
+ if (value === "G0" || value === "G1" || value === "G2") return value;
33602
+ throw new Error(`Invalid manual edits ${label}`);
33603
+ }
33604
+ function parsePlacementRuleMode(value, label) {
33605
+ if (value === "allow" || value === "warn" || value === "prevent") return value;
33606
+ throw new Error(`Invalid manual edits ${label}`);
33607
+ }
33608
+ function parseOverrides(value) {
33609
+ if (!isRecord2(value)) throw new Error("Invalid manual edits overrides");
33610
+ const overrides = {};
33611
+ for (const [key, rawValue] of Object.entries(value)) {
33612
+ if (typeof rawValue !== "number" && typeof rawValue !== "string") throw new Error(`Invalid manual edits overrides.${key}`);
33613
+ if (typeof rawValue === "number" && !Number.isFinite(rawValue)) throw new Error(`Invalid manual edits overrides.${key}`);
33614
+ overrides[key] = rawValue;
33615
+ }
33616
+ return Object.fromEntries(Object.entries(overrides).sort(([a], [b]) => a.localeCompare(b)));
33617
+ }
33618
+ function parsePathPoint(value, label) {
33619
+ if (!isRecord2(value)) throw new Error(`Invalid manual edits ${label}`);
33620
+ return {
33621
+ x: requireFiniteNumber2(value.x, `${label}.x`),
33622
+ y: requireFiniteNumber2(value.y, `${label}.y`)
33623
+ };
33624
+ }
33625
+ function parseSplinePoint(value, label) {
33626
+ const point = parsePathPoint(value, label);
33627
+ return { ...point, g: parseContinuity(isRecord2(value) ? value.g : void 0, `${label}.g`) };
33628
+ }
33629
+ function parsePlacementFootprint(value, label) {
33630
+ if (!isRecord2(value)) throw new Error(`Invalid manual edits ${label}`);
33631
+ const type = requireString4(value.type, `${label}.type`);
33632
+ if (type === "rect") {
33633
+ return {
33634
+ type,
33635
+ width: requireFiniteNumber2(value.width, `${label}.width`),
33636
+ height: requireFiniteNumber2(value.height, `${label}.height`)
33637
+ };
33638
+ }
33639
+ if (type === "circle") {
33640
+ return { type, radius: requireFiniteNumber2(value.radius, `${label}.radius`) };
33641
+ }
33642
+ throw new Error(`Invalid manual edits ${label}.type`);
33643
+ }
33644
+ function parsePlacementFrame(value, label) {
33645
+ if (!isRecord2(value)) throw new Error(`Invalid manual edits ${label}`);
33646
+ return {
33647
+ center: parsePathPoint(value.center, `${label}.center`),
33648
+ width: requireFiniteNumber2(value.width, `${label}.width`),
33649
+ height: requireFiniteNumber2(value.height, `${label}.height`)
33650
+ };
33651
+ }
33652
+ function parsePlacementZone(value, label) {
33653
+ if (!isRecord2(value)) throw new Error(`Invalid manual edits ${label}`);
33654
+ return {
33655
+ ...parsePlacementFrame(value, label),
33656
+ id: requireString4(value.id, `${label}.id`),
33657
+ label: optionalString3(value.label)
33658
+ };
33659
+ }
33660
+ function parsePlacementRules(value, label) {
33661
+ if (!isRecord2(value)) throw new Error(`Invalid manual edits ${label}`);
33662
+ return {
33663
+ bounds: parsePlacementRuleMode(value.bounds, `${label}.bounds`),
33664
+ collisions: parsePlacementRuleMode(value.collisions, `${label}.collisions`),
33665
+ snap: requireFiniteNumber2(value.snap, `${label}.snap`)
33666
+ };
33667
+ }
33668
+ function parsePlacementItem(value, label) {
33669
+ if (!isRecord2(value)) throw new Error(`Invalid manual edits ${label}`);
33670
+ return {
33671
+ id: requireString4(value.id, `${label}.id`),
33672
+ label: optionalString3(value.label),
33673
+ footprint: parsePlacementFootprint(value.footprint, `${label}.footprint`),
33674
+ x: requireFiniteNumber2(value.x, `${label}.x`),
33675
+ y: requireFiniteNumber2(value.y, `${label}.y`),
33676
+ angle: requireFiniteNumber2(value.angle, `${label}.angle`),
33677
+ zone: optionalString3(value.zone),
33678
+ locked: value.locked === void 0 ? void 0 : Boolean(value.locked)
33679
+ };
33680
+ }
33681
+ function parseManualParam(value, label) {
33682
+ if (!isRecord2(value)) throw new Error(`Invalid manual edits ${label}`);
33683
+ const kind = requireString4(value.kind, `${label}.kind`);
33684
+ const name = requireString4(value.name, `${label}.name`);
33685
+ if (kind === "path2d") {
33686
+ const minPoints = requireInteger(value.minPoints, `${label}.minPoints`);
33687
+ const maxPoints = requireInteger(value.maxPoints, `${label}.maxPoints`);
33688
+ if (minPoints < 0 || maxPoints < minPoints) throw new Error(`Invalid manual edits ${label} point range`);
33689
+ if (!Array.isArray(value.points)) throw new Error(`Invalid manual edits ${label}.points`);
33690
+ return {
33691
+ kind,
33692
+ name,
33693
+ closed: Boolean(value.closed),
33694
+ minPoints,
33695
+ maxPoints,
33696
+ unit: optionalString3(value.unit),
33697
+ points: value.points.map((point, index) => parsePathPoint(point, `${label}.points[${index}]`))
33698
+ };
33699
+ }
33700
+ if (kind === "spline2d") {
33701
+ const minPoints = requireInteger(value.minPoints, `${label}.minPoints`);
33702
+ const maxPoints = requireInteger(value.maxPoints, `${label}.maxPoints`);
33703
+ if (minPoints < 0 || maxPoints < minPoints) throw new Error(`Invalid manual edits ${label} point range`);
33704
+ if (!Array.isArray(value.points)) throw new Error(`Invalid manual edits ${label}.points`);
33705
+ const degree = requireInteger(value.degree, `${label}.degree`);
33706
+ return {
33707
+ kind,
33708
+ name,
33709
+ closed: Boolean(value.closed),
33710
+ degree,
33711
+ minPoints,
33712
+ maxPoints,
33713
+ unit: optionalString3(value.unit),
33714
+ points: value.points.map((point, index) => parseSplinePoint(point, `${label}.points[${index}]`))
33715
+ };
33716
+ }
33717
+ if (kind === "placement2d") {
33718
+ if (!Array.isArray(value.zones)) throw new Error(`Invalid manual edits ${label}.zones`);
33719
+ if (!Array.isArray(value.items)) throw new Error(`Invalid manual edits ${label}.items`);
33720
+ return {
33721
+ kind,
33722
+ name,
33723
+ frame: parsePlacementFrame(value.frame, `${label}.frame`),
33724
+ zones: value.zones.map((zone, index) => parsePlacementZone(zone, `${label}.zones[${index}]`)),
33725
+ rules: parsePlacementRules(value.rules, `${label}.rules`),
33726
+ unit: optionalString3(value.unit),
33727
+ items: value.items.map((item, index) => parsePlacementItem(item, `${label}.items[${index}]`))
33728
+ };
33729
+ }
33730
+ throw new Error(`Invalid manual edits ${label}.kind`);
33731
+ }
33732
+ function parseManualParams(value) {
33733
+ if (!Array.isArray(value)) throw new Error("Invalid manual edits manualParams");
33734
+ return value.map((manualParam, index) => parseManualParam(manualParam, `manualParams[${index}]`));
33735
+ }
33736
+ function parseManualParamEditsDraft(value) {
33737
+ if (!isRecord2(value)) throw new Error("Invalid manual edits draft");
33738
+ if (value.kind !== MANUAL_PARAM_EDITS_DRAFT_KIND) throw new Error("Invalid manual edits draft kind");
33739
+ return {
33740
+ kind: MANUAL_PARAM_EDITS_DRAFT_KIND,
33741
+ filePath: parseFilePath(value.filePath),
33742
+ overrides: parseOverrides(value.overrides ?? {}),
33743
+ manualParams: parseManualParams(value.manualParams)
33744
+ };
33745
+ }
33746
+ function parseManualParamEditsPayload(value) {
33747
+ if (!isRecord2(value)) throw new Error("Invalid manual edits payload");
33748
+ if (value.kind !== MANUAL_PARAM_EDITS_KIND) throw new Error("Invalid manual edits kind");
33749
+ const revision = requireInteger(value.revision, "revision");
33750
+ if (revision < 1) throw new Error("Invalid manual edits revision");
33751
+ return {
33752
+ kind: MANUAL_PARAM_EDITS_KIND,
33753
+ filePath: parseFilePath(value.filePath),
33754
+ revision,
33755
+ submittedAt: requireString4(value.submittedAt, "submittedAt"),
33756
+ overrides: parseOverrides(value.overrides ?? {}),
33757
+ manualParams: parseManualParams(value.manualParams)
33758
+ };
33759
+ }
33760
+ function createSubmittedManualParamEdits(draftValue, previousRevision, submittedAt = (/* @__PURE__ */ new Date()).toISOString()) {
33761
+ const draft = parseManualParamEditsDraft(draftValue);
33762
+ return {
33763
+ kind: MANUAL_PARAM_EDITS_KIND,
33764
+ filePath: draft.filePath,
33765
+ revision: Math.max(0, previousRevision ?? 0) + 1,
33766
+ submittedAt,
33767
+ overrides: draft.overrides,
33768
+ manualParams: draft.manualParams
33769
+ };
33770
+ }
33771
+ function formatManualParamEditsText(payload, source) {
33772
+ const lines = [
33773
+ `Manual parameter edits for ${payload.filePath}`,
33774
+ `Revision: ${payload.revision}${source ? ` (${source})` : ""}`,
33775
+ `Submitted: ${payload.submittedAt}`,
33776
+ `Changed override keys: ${Object.keys(payload.overrides).length}`,
33777
+ `Manual controls: ${payload.manualParams.length}`,
33778
+ "",
33779
+ "Apply these by updating the matching Param.path2d(...), Param.spline2d(...), and Param.placement2d(...) defaults in source.",
33780
+ "Use `--format json` for exact point, G-continuity, footprint, and zone data."
33781
+ ];
33782
+ if (payload.manualParams.length > 0) {
33783
+ lines.push("");
33784
+ for (const edit of payload.manualParams) {
33785
+ if (edit.kind === "path2d") {
33786
+ lines.push(
33787
+ `- path2d ${edit.name}: ${edit.points.length} ${edit.closed ? "outline" : "centerline"} point${edit.points.length === 1 ? "" : "s"}`
33788
+ );
33789
+ } else if (edit.kind === "spline2d") {
33790
+ const gCounts = edit.points.reduce(
33791
+ (counts, point) => ({ ...counts, [point.g]: counts[point.g] + 1 }),
33792
+ { G0: 0, G1: 0, G2: 0 }
33793
+ );
33794
+ lines.push(
33795
+ `- spline2d ${edit.name}: ${edit.points.length} point${edit.points.length === 1 ? "" : "s"}, degree ${edit.degree}, G0 ${gCounts.G0}, G1 ${gCounts.G1}, G2 ${gCounts.G2}`
33796
+ );
33797
+ } else {
33798
+ const zoneCount = edit.zones.length;
33799
+ lines.push(
33800
+ `- placement2d ${edit.name}: ${edit.items.length} item${edit.items.length === 1 ? "" : "s"}, ${zoneCount} zone${zoneCount === 1 ? "" : "s"}, collisions ${edit.rules.collisions}`
33801
+ );
33802
+ }
33803
+ }
33804
+ }
33805
+ return lines.join("\n");
33806
+ }
33807
+
33808
+ // cli/manual-param-edits-storage.ts
33809
+ var MANUAL_PARAM_EDITS_ROOT = path3.join(".forgecad", "manual-param-edits", "files");
33810
+ function localManualParamEditsPath(projectRoot, filePath) {
33811
+ const normalizedFilePath = filePath.split("\\").join("/");
33812
+ const contextRoot = path3.resolve(projectRoot, MANUAL_PARAM_EDITS_ROOT);
33813
+ const resolved = path3.resolve(contextRoot, `${normalizedFilePath}.json`);
33814
+ if (!resolved.startsWith(contextRoot + path3.sep) && resolved !== contextRoot) {
33815
+ throw new Error(`Invalid manual edits file path: ${filePath}`);
33816
+ }
33817
+ return resolved;
33818
+ }
33819
+ function readLocalManualParamEdits(projectRoot, filePath) {
33820
+ const contextPath = localManualParamEditsPath(projectRoot, filePath);
33821
+ if (!fs2.existsSync(contextPath)) return null;
33822
+ return parseManualParamEditsPayload(JSON.parse(fs2.readFileSync(contextPath, "utf-8")));
33823
+ }
33824
+ function writeSubmittedLocalManualParamEdits(projectRoot, draft) {
33825
+ const draftFilePath = typeof draft === "object" && draft !== null && "filePath" in draft ? String(draft.filePath) : "";
33826
+ const existing = draftFilePath ? readLocalManualParamEdits(projectRoot, draftFilePath) : null;
33827
+ const context = createSubmittedManualParamEdits(draft, existing?.revision ?? null);
33828
+ const contextPath = localManualParamEditsPath(projectRoot, context.filePath);
33829
+ fs2.mkdirSync(path3.dirname(contextPath), { recursive: true });
33830
+ const tempPath = `${contextPath}.${process.pid}.${Date.now()}.tmp`;
33831
+ fs2.writeFileSync(tempPath, JSON.stringify(context, null, 2) + "\n", "utf-8");
33832
+ fs2.renameSync(tempPath, contextPath);
33833
+ return context;
33834
+ }
33835
+
33531
33836
  // cli/forge-studio-server.ts
33532
33837
  var PROJECT_FILE_EXTS = [
33533
33838
  ".forge.js",
@@ -33583,14 +33888,14 @@ var MIME = {
33583
33888
  ".md": "text/plain; charset=utf-8"
33584
33889
  };
33585
33890
  function slugFromDir(dir) {
33586
- const name = path3.basename(dir);
33891
+ const name = path4.basename(dir);
33587
33892
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "project";
33588
33893
  }
33589
33894
  function buildProjectRegistry(dirs) {
33590
33895
  const projects = [];
33591
33896
  const usedSlugs = /* @__PURE__ */ new Set();
33592
33897
  for (const dir of dirs) {
33593
- const absDir = path3.resolve(dir);
33898
+ const absDir = path4.resolve(dir);
33594
33899
  let slug2 = slugFromDir(absDir);
33595
33900
  if (usedSlugs.has(slug2)) {
33596
33901
  let i = 2;
@@ -33601,7 +33906,7 @@ function buildProjectRegistry(dirs) {
33601
33906
  projects.push({
33602
33907
  id: slug2,
33603
33908
  // Stable, URL-safe ID
33604
- name: path3.basename(absDir),
33909
+ name: path4.basename(absDir),
33605
33910
  slug: slug2,
33606
33911
  dir: absDir
33607
33912
  });
@@ -33640,19 +33945,19 @@ function readBinaryBody(req) {
33640
33945
  });
33641
33946
  }
33642
33947
  function scanProjectFiles(projectDir) {
33643
- const abs = path3.resolve(projectDir);
33948
+ const abs = path4.resolve(projectDir);
33644
33949
  const files = {};
33645
33950
  const folderSet = /* @__PURE__ */ new Set();
33646
33951
  function scan(dir, prefix) {
33647
- for (const item of fs2.readdirSync(dir, { withFileTypes: true })) {
33952
+ for (const item of fs3.readdirSync(dir, { withFileTypes: true })) {
33648
33953
  if (item.name.startsWith(".")) continue;
33649
33954
  const rel = prefix ? `${prefix}/${item.name}` : item.name;
33650
33955
  if (item.isDirectory()) {
33651
33956
  if (shouldSkipProjectDirectory2(item.name)) continue;
33652
33957
  folderSet.add(rel);
33653
- scan(path3.join(dir, item.name), rel);
33958
+ scan(path4.join(dir, item.name), rel);
33654
33959
  } else if (item.isFile() && !isLocalProjectManifestPath(rel) && isProjectFile(item.name)) {
33655
- files[rel] = fs2.readFileSync(path3.join(dir, item.name), "utf-8");
33960
+ files[rel] = fs3.readFileSync(path4.join(dir, item.name), "utf-8");
33656
33961
  } else if (item.isFile() && isBinaryProjectFile(item.name)) {
33657
33962
  files[rel] = "";
33658
33963
  }
@@ -33665,17 +33970,17 @@ function scanProjectFiles(projectDir) {
33665
33970
  return { files, folders: [...folderSet] };
33666
33971
  }
33667
33972
  function scanProjectFileListing(projectDir) {
33668
- const abs = path3.resolve(projectDir);
33973
+ const abs = path4.resolve(projectDir);
33669
33974
  const files = [];
33670
33975
  const folders = [];
33671
33976
  function scan(dir, prefix) {
33672
- for (const item of fs2.readdirSync(dir, { withFileTypes: true })) {
33977
+ for (const item of fs3.readdirSync(dir, { withFileTypes: true })) {
33673
33978
  if (item.name.startsWith(".")) continue;
33674
33979
  const rel = prefix ? `${prefix}/${item.name}` : item.name;
33675
33980
  if (item.isDirectory()) {
33676
33981
  if (shouldSkipProjectDirectory2(item.name)) continue;
33677
33982
  folders.push(rel);
33678
- scan(path3.join(dir, item.name), rel);
33983
+ scan(path4.join(dir, item.name), rel);
33679
33984
  } else if (item.isFile() && !isLocalProjectManifestPath(rel) && (isProjectFile(item.name) || isBinaryProjectFile(item.name))) {
33680
33985
  files.push(rel);
33681
33986
  }
@@ -33690,11 +33995,11 @@ function scanProjectFileListing(projectDir) {
33690
33995
  function resolveProjectFile(projectDir, filename, opts = {}) {
33691
33996
  if (!projectDir) throw new Error("No project directory");
33692
33997
  if (!filename) throw new Error("Invalid file path");
33693
- const abs = path3.resolve(projectDir);
33694
- const filePath = path3.isAbsolute(filename) ? path3.resolve(filename) : path3.resolve(abs, filename);
33998
+ const abs = path4.resolve(projectDir);
33999
+ const filePath = path4.isAbsolute(filename) ? path4.resolve(filename) : path4.resolve(abs, filename);
33695
34000
  if (!isProjectFile(filePath) && !(opts.allowBinary && isBinaryProjectFile(filePath))) throw new Error("Invalid file type");
33696
- const rel = path3.relative(abs, filePath);
33697
- if (rel.startsWith("..") || path3.isAbsolute(rel)) {
34001
+ const rel = path4.relative(abs, filePath);
34002
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) {
33698
34003
  throw new Error(`Path "${filename}" is outside the project root`);
33699
34004
  }
33700
34005
  if (isLocalProjectManifestPath(rel.replace(/\\/g, "/"))) throw new Error("Project manifest is not editable");
@@ -33703,31 +34008,31 @@ function resolveProjectFile(projectDir, filename, opts = {}) {
33703
34008
  function resolveProjectDirectory(projectDir, dirPath) {
33704
34009
  if (!projectDir) throw new Error("No project directory");
33705
34010
  if (!dirPath) throw new Error("Invalid directory path");
33706
- const abs = path3.resolve(projectDir);
33707
- const resolved = path3.resolve(abs, dirPath);
33708
- const rel = path3.relative(abs, resolved);
33709
- if (rel.startsWith("..") || path3.isAbsolute(rel)) {
34011
+ const abs = path4.resolve(projectDir);
34012
+ const resolved = path4.resolve(abs, dirPath);
34013
+ const rel = path4.relative(abs, resolved);
34014
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) {
33710
34015
  throw new Error(`Path "${dirPath}" is outside the project root`);
33711
34016
  }
33712
34017
  return { dirPath: resolved, dirname: rel.replace(/\\/g, "/") };
33713
34018
  }
33714
34019
  function serveStatic(distDir, req, res) {
33715
- const absDistDir = path3.resolve(distDir);
34020
+ const absDistDir = path4.resolve(distDir);
33716
34021
  const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0]);
33717
- let filePath = path3.resolve(absDistDir, urlPath === "/" ? "index.html" : "." + urlPath);
33718
- if (!filePath.startsWith(absDistDir + path3.sep) && filePath !== absDistDir) {
34022
+ let filePath = path4.resolve(absDistDir, urlPath === "/" ? "index.html" : "." + urlPath);
34023
+ if (!filePath.startsWith(absDistDir + path4.sep) && filePath !== absDistDir) {
33719
34024
  res.statusCode = 403;
33720
34025
  res.end("Forbidden");
33721
34026
  return true;
33722
34027
  }
33723
- if (!fs2.existsSync(filePath) || !fs2.statSync(filePath).isFile()) {
33724
- filePath = path3.join(absDistDir, "index.html");
34028
+ if (!fs3.existsSync(filePath) || !fs3.statSync(filePath).isFile()) {
34029
+ filePath = path4.join(absDistDir, "index.html");
33725
34030
  }
33726
- if (!fs2.existsSync(filePath)) return false;
33727
- const ext = path3.extname(filePath).toLowerCase();
34031
+ if (!fs3.existsSync(filePath)) return false;
34032
+ const ext = path4.extname(filePath).toLowerCase();
33728
34033
  const mime = MIME[ext] ?? "application/octet-stream";
33729
34034
  const isText = mime.includes("charset");
33730
- const content = fs2.readFileSync(filePath, isText ? "utf-8" : null);
34035
+ const content = fs3.readFileSync(filePath, isText ? "utf-8" : null);
33731
34036
  res.statusCode = 200;
33732
34037
  res.setHeader("Content-Type", mime);
33733
34038
  res.setHeader("Cache-Control", urlPath.startsWith("/assets/") ? "max-age=31536000,immutable" : "no-cache");
@@ -33845,18 +34150,18 @@ data: ${JSON.stringify(data)}
33845
34150
  });
33846
34151
  }
33847
34152
  for (const proj of projects) {
33848
- const abs = path3.resolve(proj.dir);
34153
+ const abs = path4.resolve(proj.dir);
33849
34154
  const watcher = chokidar.watch(abs, {
33850
34155
  ignoreInitial: true,
33851
- ignored: (candidate) => path3.relative(abs, candidate).split(/[\\/]/).some(shouldSkipProjectDirectory2)
34156
+ ignored: (candidate) => path4.relative(abs, candidate).split(/[\\/]/).some(shouldSkipProjectDirectory2)
33852
34157
  });
33853
34158
  watcherReadyPromises.push(waitForWatcherReady(watcher));
33854
34159
  watcher.on("add", (f2) => {
33855
- const rel = path3.relative(abs, f2).replace(/\\/g, "/");
34160
+ const rel = path4.relative(abs, f2).replace(/\\/g, "/");
33856
34161
  if (isLocalProjectManifestPath(rel)) return;
33857
34162
  if (isProjectFile(f2)) {
33858
34163
  try {
33859
- broadcastToProject(proj.id, "change", { filename: rel, content: fs2.readFileSync(f2, "utf-8") });
34164
+ broadcastToProject(proj.id, "change", { filename: rel, content: fs3.readFileSync(f2, "utf-8") });
33860
34165
  } catch {
33861
34166
  }
33862
34167
  } else if (isBinaryProjectFile(f2)) {
@@ -33864,11 +34169,11 @@ data: ${JSON.stringify(data)}
33864
34169
  }
33865
34170
  });
33866
34171
  watcher.on("change", (f2) => {
33867
- const rel = path3.relative(abs, f2).replace(/\\/g, "/");
34172
+ const rel = path4.relative(abs, f2).replace(/\\/g, "/");
33868
34173
  if (isLocalProjectManifestPath(rel)) return;
33869
34174
  if (isProjectFile(f2)) {
33870
34175
  try {
33871
- broadcastToProject(proj.id, "change", { filename: rel, content: fs2.readFileSync(f2, "utf-8") });
34176
+ broadcastToProject(proj.id, "change", { filename: rel, content: fs3.readFileSync(f2, "utf-8") });
33872
34177
  } catch {
33873
34178
  }
33874
34179
  } else if (isBinaryProjectFile(f2)) {
@@ -33876,7 +34181,7 @@ data: ${JSON.stringify(data)}
33876
34181
  }
33877
34182
  });
33878
34183
  watcher.on("unlink", (f2) => {
33879
- const rel = path3.relative(abs, f2).replace(/\\/g, "/");
34184
+ const rel = path4.relative(abs, f2).replace(/\\/g, "/");
33880
34185
  if (isLocalProjectManifestPath(rel)) return;
33881
34186
  if (!isProjectFile(f2) && !isBinaryProjectFile(f2)) return;
33882
34187
  broadcastToProject(proj.id, "delete", { filename: rel });
@@ -34035,11 +34340,11 @@ data: ${JSON.stringify(data)}
34035
34340
  }
34036
34341
  try {
34037
34342
  const resolved = resolveProjectFile(proj.dir, filename);
34038
- if (!fs2.existsSync(resolved.filePath)) {
34343
+ if (!fs3.existsSync(resolved.filePath)) {
34039
34344
  sendJson(res, 404, { error: `File not found: ${filename}` });
34040
34345
  return;
34041
34346
  }
34042
- const content = fs2.readFileSync(resolved.filePath, "utf-8");
34347
+ const content = fs3.readFileSync(resolved.filePath, "utf-8");
34043
34348
  sendJson(res, 200, { filename: resolved.filename, content });
34044
34349
  } catch (e) {
34045
34350
  sendJson(res, 400, { error: e.message });
@@ -34063,7 +34368,7 @@ data: ${JSON.stringify(data)}
34063
34368
  }
34064
34369
  try {
34065
34370
  const resolved = resolveProjectFile(proj.dir, entry);
34066
- if (!fs2.existsSync(resolved.filePath)) {
34371
+ if (!fs3.existsSync(resolved.filePath)) {
34067
34372
  sendJson(res, 404, { error: `File not found: ${entry}` });
34068
34373
  return;
34069
34374
  }
@@ -34123,6 +34428,54 @@ data: ${JSON.stringify(data)}
34123
34428
  return;
34124
34429
  }
34125
34430
  }
34431
+ {
34432
+ const m = matchProjectRoute(url2, method, "GET", "/manual-param-edits");
34433
+ if (m) {
34434
+ const proj = lookupProject(m.projectId);
34435
+ if (!proj) {
34436
+ sendJson(res, 404, { error: "Project not found" });
34437
+ return;
34438
+ }
34439
+ const params = new URLSearchParams(m.query);
34440
+ const filePath = params.get("file");
34441
+ if (!filePath) {
34442
+ sendJson(res, 400, { error: "Missing file parameter" });
34443
+ return;
34444
+ }
34445
+ try {
34446
+ const resolved = resolveProjectFile(proj.dir, filePath);
34447
+ const context = readLocalManualParamEdits(proj.dir, resolved.filename);
34448
+ if (!context) {
34449
+ sendJson(res, 404, { error: `No submitted manual edits for ${resolved.filename}` });
34450
+ return;
34451
+ }
34452
+ sendJson(res, 200, { context });
34453
+ } catch (e) {
34454
+ sendJson(res, 400, { error: e.message });
34455
+ }
34456
+ return;
34457
+ }
34458
+ }
34459
+ {
34460
+ const m = matchProjectRoute(url2, method, "POST", "/manual-param-edits");
34461
+ if (m) {
34462
+ const proj = lookupProject(m.projectId);
34463
+ if (!proj) {
34464
+ sendJson(res, 404, { error: "Project not found" });
34465
+ return;
34466
+ }
34467
+ readJsonBody(req).then((body) => {
34468
+ if (!body || typeof body.filePath !== "string") {
34469
+ sendJson(res, 400, { error: "Invalid manual edits draft" });
34470
+ return;
34471
+ }
34472
+ const resolved = resolveProjectFile(proj.dir, body.filePath);
34473
+ const context = writeSubmittedLocalManualParamEdits(proj.dir, { ...body, filePath: resolved.filename });
34474
+ sendJson(res, 200, { context });
34475
+ }).catch((e) => sendJson(res, 400, { error: e.message }));
34476
+ return;
34477
+ }
34478
+ }
34126
34479
  {
34127
34480
  const m = method === "GET" ? matchSSERoute(url2, "/watch") : null;
34128
34481
  if (m) {
@@ -34171,8 +34524,8 @@ data: ${JSON.stringify(data)}
34171
34524
  return;
34172
34525
  }
34173
34526
  const resolved = resolveProjectFile(proj.dir, filename);
34174
- fs2.mkdirSync(path3.dirname(resolved.filePath), { recursive: true });
34175
- fs2.writeFileSync(resolved.filePath, content, "utf-8");
34527
+ fs3.mkdirSync(path4.dirname(resolved.filePath), { recursive: true });
34528
+ fs3.writeFileSync(resolved.filePath, content, "utf-8");
34176
34529
  sendJson(res, 200, { success: true });
34177
34530
  }).catch((e) => sendJson(res, 500, { error: e.message }));
34178
34531
  return;
@@ -34198,8 +34551,8 @@ data: ${JSON.stringify(data)}
34198
34551
  sendJson(res, 400, { error: `Not a binary import file: ${filename}` });
34199
34552
  return;
34200
34553
  }
34201
- fs2.mkdirSync(path3.dirname(resolved.filePath), { recursive: true });
34202
- fs2.writeFileSync(resolved.filePath, body);
34554
+ fs3.mkdirSync(path4.dirname(resolved.filePath), { recursive: true });
34555
+ fs3.writeFileSync(resolved.filePath, body);
34203
34556
  sendJson(res, 200, { success: true });
34204
34557
  }).catch((e) => sendJson(res, 500, { error: e.message }));
34205
34558
  return;
@@ -34220,8 +34573,8 @@ data: ${JSON.stringify(data)}
34220
34573
  return;
34221
34574
  }
34222
34575
  const resolved = resolveProjectFile(proj.dir, filename, { allowBinary: true });
34223
- if (fs2.existsSync(resolved.filePath)) {
34224
- fs2.unlinkSync(resolved.filePath);
34576
+ if (fs3.existsSync(resolved.filePath)) {
34577
+ fs3.unlinkSync(resolved.filePath);
34225
34578
  }
34226
34579
  sendJson(res, 200, { success: true });
34227
34580
  }).catch((e) => sendJson(res, 500, { error: e.message }));
@@ -34242,12 +34595,12 @@ data: ${JSON.stringify(data)}
34242
34595
  sendJson(res, 400, { error: "Invalid request" });
34243
34596
  return;
34244
34597
  }
34245
- const absDir = path3.resolve(proj.dir, dirPath);
34246
- if (!absDir.startsWith(proj.dir + path3.sep) && absDir !== proj.dir) {
34598
+ const absDir = path4.resolve(proj.dir, dirPath);
34599
+ if (!absDir.startsWith(proj.dir + path4.sep) && absDir !== proj.dir) {
34247
34600
  sendJson(res, 403, { error: "Path outside project root" });
34248
34601
  return;
34249
34602
  }
34250
- fs2.mkdirSync(absDir, { recursive: true });
34603
+ fs3.mkdirSync(absDir, { recursive: true });
34251
34604
  sendJson(res, 200, { success: true });
34252
34605
  }).catch((e) => sendJson(res, 500, { error: e.message }));
34253
34606
  return;
@@ -34278,15 +34631,15 @@ data: ${JSON.stringify(data)}
34278
34631
  sendJson(res, 400, { error: "Cannot delete project root" });
34279
34632
  return;
34280
34633
  }
34281
- if (!fs2.existsSync(resolved.dirPath)) {
34634
+ if (!fs3.existsSync(resolved.dirPath)) {
34282
34635
  sendJson(res, 200, { success: true });
34283
34636
  return;
34284
34637
  }
34285
- if (!fs2.statSync(resolved.dirPath).isDirectory()) {
34638
+ if (!fs3.statSync(resolved.dirPath).isDirectory()) {
34286
34639
  sendJson(res, 400, { error: "Path is not a directory" });
34287
34640
  return;
34288
34641
  }
34289
- fs2.rmSync(resolved.dirPath, { recursive: true, force: true });
34642
+ fs3.rmSync(resolved.dirPath, { recursive: true, force: true });
34290
34643
  sendJson(res, 200, { success: true });
34291
34644
  }).catch((e) => sendJson(res, 500, { error: e.message }));
34292
34645
  return;
@@ -34308,16 +34661,16 @@ data: ${JSON.stringify(data)}
34308
34661
  }
34309
34662
  const fromResolved = resolveProjectFile(proj.dir, from, { allowBinary: true });
34310
34663
  const toResolved = resolveProjectFile(proj.dir, to, { allowBinary: true });
34311
- if (!fs2.existsSync(fromResolved.filePath)) {
34664
+ if (!fs3.existsSync(fromResolved.filePath)) {
34312
34665
  sendJson(res, 404, { error: `File not found: ${from}` });
34313
34666
  return;
34314
34667
  }
34315
- if (fs2.existsSync(toResolved.filePath)) {
34668
+ if (fs3.existsSync(toResolved.filePath)) {
34316
34669
  sendJson(res, 409, { error: `Destination already exists: ${to}` });
34317
34670
  return;
34318
34671
  }
34319
- fs2.mkdirSync(path3.dirname(toResolved.filePath), { recursive: true });
34320
- fs2.renameSync(fromResolved.filePath, toResolved.filePath);
34672
+ fs3.mkdirSync(path4.dirname(toResolved.filePath), { recursive: true });
34673
+ fs3.renameSync(fromResolved.filePath, toResolved.filePath);
34321
34674
  sendJson(res, 200, { success: true });
34322
34675
  }).catch((e) => sendJson(res, 500, { error: e.message }));
34323
34676
  return;
@@ -34337,10 +34690,10 @@ data: ${JSON.stringify(data)}
34337
34690
  sendJson(res, 400, { error: "Missing path parameter" });
34338
34691
  return;
34339
34692
  }
34340
- const absProject = path3.resolve(proj.dir);
34341
- const filePath = path3.resolve(absProject, filename);
34342
- const rel = path3.relative(absProject, filePath);
34343
- if (rel.startsWith("..") || path3.isAbsolute(rel)) {
34693
+ const absProject = path4.resolve(proj.dir);
34694
+ const filePath = path4.resolve(absProject, filename);
34695
+ const rel = path4.relative(absProject, filePath);
34696
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) {
34344
34697
  sendJson(res, 403, { error: "Path outside project root" });
34345
34698
  return;
34346
34699
  }
@@ -34348,11 +34701,11 @@ data: ${JSON.stringify(data)}
34348
34701
  sendJson(res, 400, { error: `Not a binary import file: ${filename}` });
34349
34702
  return;
34350
34703
  }
34351
- if (!fs2.existsSync(filePath)) {
34704
+ if (!fs3.existsSync(filePath)) {
34352
34705
  sendJson(res, 404, { error: `File not found: ${filename}` });
34353
34706
  return;
34354
34707
  }
34355
- const data = fs2.readFileSync(filePath);
34708
+ const data = fs3.readFileSync(filePath);
34356
34709
  res.writeHead(200, { "Content-Type": "application/octet-stream" });
34357
34710
  res.end(data);
34358
34711
  return;
@@ -34369,6 +34722,16 @@ data: ${JSON.stringify(data)}
34369
34722
  sendJson(res, 200, { linked: false });
34370
34723
  return;
34371
34724
  }
34725
+ if (!hasRemoteProject(manifest)) {
34726
+ sendJson(res, 200, {
34727
+ linked: false,
34728
+ initialized: true,
34729
+ slug: manifest.slug,
34730
+ server: manifest.server,
34731
+ message: "Run `forgecad project push` to create the hosted project."
34732
+ });
34733
+ return;
34734
+ }
34372
34735
  const auth = readStoredAuth();
34373
34736
  if (!auth) {
34374
34737
  sendJson(res, 200, { linked: true, authenticated: false, slug: manifest.slug, server: manifest.server });
@@ -34410,6 +34773,10 @@ data: ${JSON.stringify(data)}
34410
34773
  sendJson(res, 400, { error: "Not linked to a remote project" });
34411
34774
  return;
34412
34775
  }
34776
+ if (!hasRemoteProject(manifest)) {
34777
+ sendJson(res, 400, { error: "Project has not been pushed yet. Run `forgecad project push` first." });
34778
+ return;
34779
+ }
34413
34780
  const auth = readStoredAuth();
34414
34781
  if (!auth) {
34415
34782
  sendJson(res, 401, { error: "Not authenticated. Run `forgecad login` first." });
@@ -34580,7 +34947,7 @@ async function runStudioCli(argv = process.argv.slice(2)) {
34580
34947
  // cli/forge-context.ts
34581
34948
  import { existsSync as existsSync22 } from "fs";
34582
34949
  import { mkdir as mkdir4, rename as rename2, rm as rm2, writeFile as writeFile8 } from "fs/promises";
34583
- import path4 from "path";
34950
+ import path5 from "path";
34584
34951
  function usage21(command) {
34585
34952
  if (command === "render") return "Usage: forgecad context render <model.forge.js> [--out <dir>] [--force] [--size <px>]";
34586
34953
  return `Usage: forgecad context ${command} <model.forge.js> [--format text|json]`;
@@ -34649,16 +35016,16 @@ ${usage21("render")}`);
34649
35016
  return { filePath, outDir, force, size };
34650
35017
  }
34651
35018
  function resolveProjectFile2(filePath) {
34652
- const absFilePath = path4.resolve(filePath);
35019
+ const absFilePath = path5.resolve(filePath);
34653
35020
  if (!existsSync22(absFilePath)) throw new Error(`File not found: ${filePath}`);
34654
35021
  const projectRoot = findProjectRoot(absFilePath);
34655
- const fileName = path4.relative(projectRoot, absFilePath).replace(/\\/g, "/");
34656
- if (fileName.startsWith("..") || path4.isAbsolute(fileName)) throw new Error(`File is outside project root: ${filePath}`);
35022
+ const fileName = path5.relative(projectRoot, absFilePath).replace(/\\/g, "/");
35023
+ if (fileName.startsWith("..") || path5.isAbsolute(fileName)) throw new Error(`File is outside project root: ${filePath}`);
34657
35024
  return { projectRoot, fileName };
34658
35025
  }
34659
35026
  async function readRemoteAgentContext(projectRoot, fileName) {
34660
35027
  const manifest = readManifest(projectRoot);
34661
- if (!manifest) return null;
35028
+ if (!manifest || !hasRemoteProject(manifest)) return null;
34662
35029
  const res = await authenticatedFetch(`/api/projects/${manifest.projectId}/agent-context?file=${encodeURIComponent(fileName)}`);
34663
35030
  if (res.status === 404) return null;
34664
35031
  if (!res.ok) {
@@ -34707,9 +35074,9 @@ function outputContextStem(fileName) {
34707
35074
  return fileName.replace(/\.js$/i, "").split("\\").join("/");
34708
35075
  }
34709
35076
  async function allocateBundleDir(projectRoot, fileName, revision, outDir, force) {
34710
- const requested = outDir ? path4.resolve(outDir) : (() => {
35077
+ const requested = outDir ? path5.resolve(outDir) : (() => {
34711
35078
  const { dateDir, timeStem } = timestampParts();
34712
- return path4.resolve(projectRoot, "outputs", "context", outputContextStem(fileName), dateDir, `${timeStem}-rev${revision}`);
35079
+ return path5.resolve(projectRoot, "outputs", "context", outputContextStem(fileName), dateDir, `${timeStem}-rev${revision}`);
34713
35080
  })();
34714
35081
  if (force) {
34715
35082
  await rm2(requested, { recursive: true, force: true });
@@ -34727,7 +35094,7 @@ async function allocateBundleDir(projectRoot, fileName, revision, outDir, force)
34727
35094
  throw new Error(`Could not allocate a non-colliding context bundle directory near ${requested}`);
34728
35095
  }
34729
35096
  function bundleRelativePath(projectRoot, bundleDir) {
34730
- return path4.relative(projectRoot, bundleDir).split(path4.sep).join("/");
35097
+ return path5.relative(projectRoot, bundleDir).split(path5.sep).join("/");
34731
35098
  }
34732
35099
  function renderRequirePath(fileName) {
34733
35100
  return `./${fileName.split("\\").join("/")}`;
@@ -34789,13 +35156,13 @@ return rendered;
34789
35156
  `;
34790
35157
  }
34791
35158
  async function writeContextRenderWrapper(projectRoot, fileName, context) {
34792
- const wrapperPath = path4.join(projectRoot, `.forgecad-context-render-${process.pid}-${Date.now()}.forge.js`);
35159
+ const wrapperPath = path5.join(projectRoot, `.forgecad-context-render-${process.pid}-${Date.now()}.forge.js`);
34793
35160
  await writeFile8(wrapperPath, buildContextRenderWrapper(fileName, context), "utf-8");
34794
35161
  return wrapperPath;
34795
35162
  }
34796
35163
  async function renderContextViews(wrapperPath, viewsDir, size) {
34797
35164
  await mkdir4(viewsDir, { recursive: true });
34798
- const outputBase = path4.join(viewsDir, "view.png");
35165
+ const outputBase = path5.join(viewsDir, "view.png");
34799
35166
  await runRenderCli([
34800
35167
  wrapperPath,
34801
35168
  outputBase,
@@ -34812,11 +35179,11 @@ async function renderContextViews(wrapperPath, viewsDir, size) {
34812
35179
  ]);
34813
35180
  const viewNames = ["iso", "front", "right", "top"];
34814
35181
  for (const view of viewNames) {
34815
- const produced = path4.join(viewsDir, `view_${view}.png`);
34816
- const target = path4.join(viewsDir, `${view}.png`);
35182
+ const produced = path5.join(viewsDir, `view_${view}.png`);
35183
+ const target = path5.join(viewsDir, `${view}.png`);
34817
35184
  if (existsSync22(produced)) await rename2(produced, target);
34818
35185
  }
34819
- return viewNames.filter((view) => existsSync22(path4.join(viewsDir, `${view}.png`)));
35186
+ return viewNames.filter((view) => existsSync22(path5.join(viewsDir, `${view}.png`)));
34820
35187
  }
34821
35188
  function briefMarkdown(context, views) {
34822
35189
  const lines = [
@@ -34845,11 +35212,11 @@ function briefMarkdown(context, views) {
34845
35212
  async function writeContextBundle(projectRoot, bundleDir, context, views) {
34846
35213
  const latestBundle = bundleRelativePath(projectRoot, bundleDir);
34847
35214
  const updatedContext = updateLocalAgentContextLatestBundle(projectRoot, context.filePath, latestBundle);
34848
- await writeFile8(path4.join(bundleDir, "context.json"), `${JSON.stringify(updatedContext, null, 2)}
35215
+ await writeFile8(path5.join(bundleDir, "context.json"), `${JSON.stringify(updatedContext, null, 2)}
34849
35216
  `, "utf-8");
34850
- await writeFile8(path4.join(bundleDir, "brief.md"), briefMarkdown(updatedContext, views), "utf-8");
35217
+ await writeFile8(path5.join(bundleDir, "brief.md"), briefMarkdown(updatedContext, views), "utf-8");
34851
35218
  await writeFile8(
34852
- path4.join(bundleDir, "manifest.json"),
35219
+ path5.join(bundleDir, "manifest.json"),
34853
35220
  `${JSON.stringify(
34854
35221
  {
34855
35222
  kind: "forgecad.agentContextBundle.v1",
@@ -34926,7 +35293,7 @@ async function runContextRenderCli(args = process.argv.slice(2)) {
34926
35293
  throw new Error("context render currently requires a local submitted context sidecar.");
34927
35294
  }
34928
35295
  const bundleDir = await allocateBundleDir(loaded.projectRoot, loaded.filePath, loaded.context.revision, parsed.outDir, parsed.force);
34929
- const viewsDir = path4.join(bundleDir, "views");
35296
+ const viewsDir = path5.join(bundleDir, "views");
34930
35297
  wrapperPath = await writeContextRenderWrapper(loaded.projectRoot, loaded.filePath, loaded.context);
34931
35298
  const views = await renderContextViews(wrapperPath, viewsDir, parsed.size);
34932
35299
  if (views.length === 0) throw new Error("Context render did not produce any view PNGs.");
@@ -34936,7 +35303,7 @@ async function runContextRenderCli(args = process.argv.slice(2)) {
34936
35303
  console.log(` File: ${updatedContext.filePath}`);
34937
35304
  console.log(` Revision: ${updatedContext.revision}`);
34938
35305
  console.log(` Bundle: ${bundleRelativePath(loaded.projectRoot, bundleDir)}`);
34939
- console.log(` Brief: ${path4.join(bundleRelativePath(loaded.projectRoot, bundleDir), "brief.md")}`);
35306
+ console.log(` Brief: ${path5.join(bundleRelativePath(loaded.projectRoot, bundleDir), "brief.md")}`);
34940
35307
  } catch (error) {
34941
35308
  console.error(error instanceof Error ? error.message : String(error));
34942
35309
  process.exit(1);
@@ -34945,17 +35312,139 @@ async function runContextRenderCli(args = process.argv.slice(2)) {
34945
35312
  }
34946
35313
  }
34947
35314
 
35315
+ // cli/forge-manual-edits.ts
35316
+ import { existsSync as existsSync23 } from "fs";
35317
+ import path6 from "path";
35318
+ function usage22(command) {
35319
+ return `Usage: forgecad manual-edits ${command} <model.forge.js> [--format text|json]`;
35320
+ }
35321
+ function parseArgs19(command, args) {
35322
+ let filePath;
35323
+ let format = "text";
35324
+ for (let i = 0; i < args.length; i++) {
35325
+ const arg = args[i];
35326
+ if (arg === "--format") {
35327
+ const value = args[++i];
35328
+ if (value !== "text" && value !== "json") throw new Error(usage22(command));
35329
+ format = value;
35330
+ } else if (arg.startsWith("--format=")) {
35331
+ const value = arg.slice("--format=".length);
35332
+ if (value !== "text" && value !== "json") throw new Error(usage22(command));
35333
+ format = value;
35334
+ } else if (arg.startsWith("-")) {
35335
+ throw new Error(`Unknown option: ${arg}
35336
+ ${usage22(command)}`);
35337
+ } else if (!filePath) {
35338
+ filePath = arg;
35339
+ } else {
35340
+ throw new Error(`Unexpected argument: ${arg}
35341
+ ${usage22(command)}`);
35342
+ }
35343
+ }
35344
+ if (!filePath) throw new Error(usage22(command));
35345
+ return { filePath, format };
35346
+ }
35347
+ function resolveProjectFile3(filePath) {
35348
+ const absFilePath = path6.resolve(filePath);
35349
+ if (!existsSync23(absFilePath)) throw new Error(`File not found: ${filePath}`);
35350
+ const projectRoot = findProjectRoot(absFilePath);
35351
+ const fileName = path6.relative(projectRoot, absFilePath).replace(/\\/g, "/");
35352
+ if (fileName.startsWith("..") || path6.isAbsolute(fileName)) throw new Error(`File is outside project root: ${filePath}`);
35353
+ return { projectRoot, fileName };
35354
+ }
35355
+ async function readRemoteManualParamEdits(projectRoot, fileName) {
35356
+ const manifest = readManifest(projectRoot);
35357
+ if (!manifest || !hasRemoteProject(manifest)) return null;
35358
+ const res = await authenticatedFetch(`/api/projects/${manifest.projectId}/manual-param-edits?file=${encodeURIComponent(fileName)}`);
35359
+ if (res.status === 404) return null;
35360
+ if (!res.ok) {
35361
+ const body = await res.json().catch(() => ({}));
35362
+ throw new Error(body.error ?? `Failed to read remote manual edits (${res.status})`);
35363
+ }
35364
+ const data = await res.json();
35365
+ return parseManualParamEditsPayload(data.context);
35366
+ }
35367
+ async function loadManualParamEdits(filePath) {
35368
+ const { projectRoot, fileName } = resolveProjectFile3(filePath);
35369
+ const local = readLocalManualParamEdits(projectRoot, fileName);
35370
+ if (local) return { projectRoot, filePath: fileName, context: local, source: "local" };
35371
+ try {
35372
+ const remote = await readRemoteManualParamEdits(projectRoot, fileName);
35373
+ if (remote) return { projectRoot, filePath: fileName, context: remote, source: "remote" };
35374
+ return { projectRoot, filePath: fileName, context: null };
35375
+ } catch (error) {
35376
+ return {
35377
+ projectRoot,
35378
+ filePath: fileName,
35379
+ context: null,
35380
+ remoteError: error instanceof Error ? error.message : String(error)
35381
+ };
35382
+ }
35383
+ }
35384
+ function statusForLoaded2(loaded) {
35385
+ return {
35386
+ filePath: loaded.filePath,
35387
+ exists: loaded.context !== null,
35388
+ revision: loaded.context?.revision ?? null,
35389
+ submittedAt: loaded.context?.submittedAt ?? null,
35390
+ manualParamCount: loaded.context?.manualParams.length ?? 0,
35391
+ overrideCount: loaded.context ? Object.keys(loaded.context.overrides).length : 0,
35392
+ source: loaded.source
35393
+ };
35394
+ }
35395
+ async function runManualEditsGetCli(args = process.argv.slice(2)) {
35396
+ try {
35397
+ const parsed = parseArgs19("get", args);
35398
+ const loaded = await loadManualParamEdits(parsed.filePath);
35399
+ if (!loaded.context) {
35400
+ console.error(`No submitted manual edits for ${loaded.filePath}.`);
35401
+ if (loaded.remoteError) console.error(`Remote lookup failed: ${loaded.remoteError}`);
35402
+ process.exit(1);
35403
+ }
35404
+ if (parsed.format === "json") {
35405
+ console.log(JSON.stringify({ context: loaded.context, source: loaded.source }, null, 2));
35406
+ return;
35407
+ }
35408
+ console.log(formatManualParamEditsText(loaded.context, loaded.source));
35409
+ } catch (error) {
35410
+ console.error(error instanceof Error ? error.message : String(error));
35411
+ process.exit(1);
35412
+ }
35413
+ }
35414
+ async function runManualEditsStatusCli(args = process.argv.slice(2)) {
35415
+ try {
35416
+ const parsed = parseArgs19("status", args);
35417
+ const loaded = await loadManualParamEdits(parsed.filePath);
35418
+ const status = statusForLoaded2(loaded);
35419
+ if (parsed.format === "json") {
35420
+ console.log(JSON.stringify({ status, remoteError: loaded.remoteError }, null, 2));
35421
+ return;
35422
+ }
35423
+ if (!loaded.context) {
35424
+ console.log(`${loaded.filePath}: no submitted manual edits`);
35425
+ if (loaded.remoteError) console.log(`remote lookup failed: ${loaded.remoteError}`);
35426
+ return;
35427
+ }
35428
+ console.log(
35429
+ `${loaded.filePath}: rev ${loaded.context.revision}, ${loaded.context.manualParams.length} manual control${loaded.context.manualParams.length === 1 ? "" : "s"}, ${Object.keys(loaded.context.overrides).length} changed override key${Object.keys(loaded.context.overrides).length === 1 ? "" : "s"}, submitted ${loaded.context.submittedAt}${loaded.source ? ` (${loaded.source})` : ""}`
35430
+ );
35431
+ } catch (error) {
35432
+ console.error(error instanceof Error ? error.message : String(error));
35433
+ process.exit(1);
35434
+ }
35435
+ }
35436
+
34948
35437
  // cli/forge-svg.ts
34949
35438
  import { readFile as readFile5, writeFile as writeFile9 } from "fs/promises";
34950
35439
  import { basename as basename17, resolve as resolve42 } from "path";
34951
- function usage22() {
35440
+ function usage23() {
34952
35441
  console.error("Usage: forgecad export svg <script.forge.js> [input ...] [--output path] [--param Key=Value]");
34953
35442
  process.exit(1);
34954
35443
  }
34955
35444
  function defaultSvgOutput(scriptPath) {
34956
35445
  return scriptPath.replace(/\.(forge|sketch)\.js$/, ".svg").replace(/\.js$/, ".svg");
34957
35446
  }
34958
- function parseArgs19(argv) {
35447
+ function parseArgs20(argv) {
34959
35448
  const inputPaths = [];
34960
35449
  let outputPath;
34961
35450
  const paramOverrides = {};
@@ -34985,11 +35474,11 @@ function parseArgs19(argv) {
34985
35474
  async function runSvgCli(argv = process.argv.slice(2)) {
34986
35475
  let parsed;
34987
35476
  try {
34988
- parsed = parseArgs19(argv);
35477
+ parsed = parseArgs20(argv);
34989
35478
  requireExistingInputPaths(parsed.inputPaths);
34990
35479
  } catch (error) {
34991
35480
  console.error(error instanceof Error ? error.message : String(error));
34992
- usage22();
35481
+ usage23();
34993
35482
  }
34994
35483
  let failures = 0;
34995
35484
  for (const [index, scriptPath] of parsed.inputPaths.entries()) {
@@ -35446,7 +35935,7 @@ function buildUrdfRobotPackage(spec) {
35446
35935
  }
35447
35936
 
35448
35937
  // cli/forge-urdf.ts
35449
- function parseArgs20(argv) {
35938
+ function parseArgs21(argv) {
35450
35939
  const inputPaths = [];
35451
35940
  let outputPath;
35452
35941
  const paramOverrides = {};
@@ -35498,7 +35987,7 @@ function resolveSimulationModel4(result, jointOverrides) {
35498
35987
  return collectSimulationModel(def, { state: jointOverrides });
35499
35988
  }
35500
35989
  async function runUrdfCli(argv = process.argv.slice(2)) {
35501
- const { inputPaths, outputPath, paramOverrides, jointOverrides } = parseArgs20(argv);
35990
+ const { inputPaths, outputPath, paramOverrides, jointOverrides } = parseArgs21(argv);
35502
35991
  requireExistingInputPaths(inputPaths);
35503
35992
  let failures = 0;
35504
35993
  for (const [index, scriptPath] of inputPaths.entries()) {
@@ -36189,7 +36678,7 @@ ${jointResults.map((result) => result.text).join("\n\n")}
36189
36678
  }
36190
36679
 
36191
36680
  // cli/forge-usd.ts
36192
- function parseArgs21(argv) {
36681
+ function parseArgs22(argv) {
36193
36682
  const inputPaths = [];
36194
36683
  let outputPath;
36195
36684
  const paramOverrides = {};
@@ -36239,7 +36728,7 @@ function resolveSimulationModel5(result, jointOverrides) {
36239
36728
  return collectSimulationModel(def, { state: jointOverrides });
36240
36729
  }
36241
36730
  async function runUsdCli(argv = process.argv.slice(2)) {
36242
- const { inputPaths, outputPath, paramOverrides, jointOverrides } = parseArgs21(argv);
36731
+ const { inputPaths, outputPath, paramOverrides, jointOverrides } = parseArgs22(argv);
36243
36732
  requireExistingInputPaths(inputPaths);
36244
36733
  let failures = 0;
36245
36734
  for (const [index, scriptPath] of inputPaths.entries()) {
@@ -36357,7 +36846,7 @@ ${url}`);
36357
36846
 
36358
36847
  // cli/forge-publish.ts
36359
36848
  import { execSync as execSync4 } from "child_process";
36360
- import { existsSync as existsSync23 } from "fs";
36849
+ import { existsSync as existsSync24 } from "fs";
36361
36850
  import { dirname as dirname12, relative as relative6, resolve as resolve45 } from "path";
36362
36851
  function findProjectRoot3(dir) {
36363
36852
  let current = resolve45(dir);
@@ -36434,7 +36923,7 @@ async function runPublishCli(args) {
36434
36923
  process.exit(1);
36435
36924
  }
36436
36925
  const resolved = resolve45(filePath);
36437
- if (!existsSync23(resolved)) {
36926
+ if (!existsSync24(resolved)) {
36438
36927
  console.error(`File not found: ${filePath}`);
36439
36928
  process.exit(1);
36440
36929
  }
@@ -36446,6 +36935,11 @@ async function runPublishCli(args) {
36446
36935
  console.error("or `cd` into an existing project directory.");
36447
36936
  process.exit(1);
36448
36937
  }
36938
+ if (!hasRemoteProject(project.manifest)) {
36939
+ console.error("This project has not been created on the server yet.");
36940
+ console.error(" Run `forgecad project push` first, then publish the file.");
36941
+ process.exit(1);
36942
+ }
36449
36943
  const relPath = relative6(project.root, resolved).replace(/\\/g, "/");
36450
36944
  if (!skipSync) {
36451
36945
  console.log(`Project "${project.manifest.slug}" \u2014 syncing...`);
@@ -36485,16 +36979,16 @@ async function runPublishCli(args) {
36485
36979
 
36486
36980
  // cli/doctor-dependencies.ts
36487
36981
  import { execFileSync as execFileSync3, execSync as execSync5 } from "child_process";
36488
- import { existsSync as existsSync24 } from "fs";
36982
+ import { existsSync as existsSync25 } from "fs";
36489
36983
  function findExecutablePath2(staticCandidates, binCandidates) {
36490
36984
  for (const candidate of staticCandidates) {
36491
- if (existsSync24(candidate)) return candidate;
36985
+ if (existsSync25(candidate)) return candidate;
36492
36986
  }
36493
36987
  for (const bin of binCandidates) {
36494
36988
  try {
36495
36989
  const cmd = process.platform === "win32" ? `where ${bin}` : `command -v ${bin}`;
36496
36990
  const found = execSync5(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split(/\r?\n/)[0];
36497
- if (found && existsSync24(found)) return found;
36991
+ if (found && existsSync25(found)) return found;
36498
36992
  } catch {
36499
36993
  }
36500
36994
  }
@@ -36505,13 +36999,13 @@ function findExecutableWithEnv(envName, staticCandidates, binCandidates) {
36505
36999
  if (!configured) return { path: findExecutablePath2(staticCandidates, binCandidates) };
36506
37000
  const looksLikePath2 = configured.includes("/") || configured.includes("\\");
36507
37001
  if (looksLikePath2) {
36508
- return existsSync24(configured) ? { path: configured } : { path: null, note: `${envName}=${configured} was not found` };
37002
+ return existsSync25(configured) ? { path: configured } : { path: null, note: `${envName}=${configured} was not found` };
36509
37003
  }
36510
37004
  const found = findExecutablePath2([], [configured]);
36511
37005
  return found ? { path: found } : { path: null, note: `${envName}=${configured} was not found on PATH` };
36512
37006
  }
36513
37007
  function findChrome() {
36514
- if (process.env.CHROME_PATH && existsSync24(process.env.CHROME_PATH)) return process.env.CHROME_PATH;
37008
+ if (process.env.CHROME_PATH && existsSync25(process.env.CHROME_PATH)) return process.env.CHROME_PATH;
36515
37009
  const candidatesByPlatform = {
36516
37010
  darwin: [
36517
37011
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
@@ -36545,7 +37039,7 @@ function findChrome() {
36545
37039
  ]);
36546
37040
  }
36547
37041
  function findFfmpeg() {
36548
- if (process.env.FFMPEG_PATH && existsSync24(process.env.FFMPEG_PATH)) return process.env.FFMPEG_PATH;
37042
+ if (process.env.FFMPEG_PATH && existsSync25(process.env.FFMPEG_PATH)) return process.env.FFMPEG_PATH;
36549
37043
  const candidatesByPlatform = {
36550
37044
  darwin: ["/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg"],
36551
37045
  linux: ["/usr/bin/ffmpeg", "/snap/bin/ffmpeg"],
@@ -36584,8 +37078,8 @@ function getVersion(executable, args) {
36584
37078
  return void 0;
36585
37079
  }
36586
37080
  }
36587
- function okCheck(name, path5, version, usedBy) {
36588
- return { name, status: "ok", path: path5, version, usedBy };
37081
+ function okCheck(name, path7, version, usedBy) {
37082
+ return { name, status: "ok", path: path7, version, usedBy };
36589
37083
  }
36590
37084
  function warnCheck(name, note, usedBy) {
36591
37085
  return { name, status: "warn", note, usedBy };
@@ -36724,12 +37218,12 @@ async function recordCliCommandEvent(input) {
36724
37218
  }
36725
37219
 
36726
37220
  // cli/forge-project.ts
36727
- import { existsSync as existsSync26, mkdirSync as mkdirSync14, readFileSync as readFileSync30, writeFileSync as writeFileSync22 } from "fs";
36728
- import { createInterface as createInterface2 } from "readline";
37221
+ import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync30, writeFileSync as writeFileSync22 } from "fs";
37222
+ import { createInterface as createInterface3 } from "readline";
36729
37223
  import { basename as basename18, join as join22, resolve as resolve46 } from "path";
36730
37224
 
36731
37225
  // cli/license.ts
36732
- import { existsSync as existsSync25, mkdirSync as mkdirSync13, readFileSync as readFileSync29, unlinkSync as unlinkSync2, writeFileSync as writeFileSync21 } from "fs";
37226
+ import { existsSync as existsSync26, mkdirSync as mkdirSync13, readFileSync as readFileSync29, unlinkSync as unlinkSync2, writeFileSync as writeFileSync21 } from "fs";
36733
37227
  import { homedir as homedir8 } from "os";
36734
37228
  import { join as join21 } from "path";
36735
37229
  var VALID_TIERS = /* @__PURE__ */ new Set(["free", "pro", "team"]);
@@ -36780,16 +37274,16 @@ function licenseDir() {
36780
37274
  }
36781
37275
  function ensureLicenseDir() {
36782
37276
  const dir = licenseDir();
36783
- if (!existsSync25(dir)) mkdirSync13(dir, { recursive: true });
37277
+ if (!existsSync26(dir)) mkdirSync13(dir, { recursive: true });
36784
37278
  }
36785
37279
  function licenseFilePath() {
36786
37280
  return join21(licenseDir(), "license.json");
36787
37281
  }
36788
37282
  function readStoredLicense() {
36789
- const path5 = licenseFilePath();
36790
- if (!existsSync25(path5)) return null;
37283
+ const path7 = licenseFilePath();
37284
+ if (!existsSync26(path7)) return null;
36791
37285
  try {
36792
- return JSON.parse(readFileSync29(path5, "utf-8"));
37286
+ return JSON.parse(readFileSync29(path7, "utf-8"));
36793
37287
  } catch {
36794
37288
  return null;
36795
37289
  }
@@ -36882,8 +37376,8 @@ function storeLicenseFromToken(token) {
36882
37376
  };
36883
37377
  }
36884
37378
  function deactivateLicense() {
36885
- const path5 = licenseFilePath();
36886
- if (existsSync25(path5)) unlinkSync2(path5);
37379
+ const path7 = licenseFilePath();
37380
+ if (existsSync26(path7)) unlinkSync2(path7);
36887
37381
  }
36888
37382
  function tryBackgroundRefresh() {
36889
37383
  const stored = readStoredLicense();
@@ -36965,11 +37459,69 @@ function printProductionOutputNotice(commandPath) {
36965
37459
  function slugify(name) {
36966
37460
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
36967
37461
  }
37462
+ var PROJECT_VISIBILITIES = /* @__PURE__ */ new Set(["private", "shared", "public"]);
37463
+ function projectInitUsage() {
37464
+ return "Usage: forgecad project init [name] [--slug <slug>] [--visibility <private|shared|public>]";
37465
+ }
37466
+ function parseProjectInitArgs(args, cwd) {
37467
+ let name;
37468
+ let slug2;
37469
+ let visibility = "private";
37470
+ for (let i = 0; i < args.length; i++) {
37471
+ const arg = args[i];
37472
+ if (arg === "--slug") {
37473
+ const value = args[++i];
37474
+ if (!value || value.startsWith("-")) throw new Error(projectInitUsage());
37475
+ slug2 = value;
37476
+ } else if (arg.startsWith("--slug=")) {
37477
+ slug2 = arg.slice("--slug=".length);
37478
+ if (!slug2) throw new Error(projectInitUsage());
37479
+ } else if (arg === "--visibility") {
37480
+ const value = args[++i];
37481
+ if (!value || value.startsWith("-")) throw new Error(projectInitUsage());
37482
+ visibility = value;
37483
+ } else if (arg.startsWith("--visibility=")) {
37484
+ visibility = arg.slice("--visibility=".length);
37485
+ if (!visibility) throw new Error(projectInitUsage());
37486
+ } else if (arg.startsWith("-")) {
37487
+ throw new Error(`Unknown project init option: ${arg}
37488
+ ${projectInitUsage()}`);
37489
+ } else if (!name) {
37490
+ name = arg;
37491
+ } else {
37492
+ throw new Error(`Unexpected project init argument: ${arg}
37493
+ ${projectInitUsage()}`);
37494
+ }
37495
+ }
37496
+ name = (name ?? basename18(cwd)).trim();
37497
+ slug2 = (slug2 ?? slugify(name)).trim();
37498
+ if (!name) throw new Error("Project name cannot be empty.");
37499
+ if (!slug2) throw new Error("Project slug cannot be empty. Pass --slug with a URL-safe name.");
37500
+ if (!PROJECT_VISIBILITIES.has(visibility)) {
37501
+ throw new Error(`Invalid project visibility "${visibility}". Expected private, shared, or public.`);
37502
+ }
37503
+ return { name, slug: slug2, visibility };
37504
+ }
37505
+ function localProjectName(cwd, manifest) {
37506
+ return (manifest.name ?? manifest.slug ?? basename18(cwd)).trim();
37507
+ }
37508
+ function localProjectSlug(cwd, manifest) {
37509
+ const slug2 = (manifest.slug ?? slugify(localProjectName(cwd, manifest))).trim();
37510
+ if (!slug2) throw new Error("Project slug cannot be empty. Re-run `forgecad project init` with --slug.");
37511
+ return slug2;
37512
+ }
37513
+ function localProjectVisibility(manifest) {
37514
+ const visibility = manifest.visibility ?? "private";
37515
+ if (!PROJECT_VISIBILITIES.has(visibility)) {
37516
+ throw new Error(`Invalid project visibility "${visibility}" in forgecad.json. Expected private, shared, or public.`);
37517
+ }
37518
+ return visibility;
37519
+ }
36968
37520
  function padRight(s, w) {
36969
37521
  return s.length >= w ? s : s + " ".repeat(w - s.length);
36970
37522
  }
36971
- function confirm(question) {
36972
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
37523
+ function confirm2(question) {
37524
+ const rl = createInterface3({ input: process.stdin, output: process.stderr });
36973
37525
  return new Promise((resolve54) => {
36974
37526
  rl.question(`${question} [y/N] `, (answer) => {
36975
37527
  rl.close();
@@ -36982,7 +37534,7 @@ function moveCandidateReasonLabel(candidate) {
36982
37534
  return candidate.reason === "same-content" ? "same content" : "same filename";
36983
37535
  }
36984
37536
  function promptChoice(question) {
36985
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
37537
+ const rl = createInterface3({ input: process.stdin, output: process.stderr });
36986
37538
  return new Promise((resolve54) => {
36987
37539
  rl.question(question, (answer) => {
36988
37540
  rl.close();
@@ -37099,44 +37651,19 @@ async function runProjectInitCli(args) {
37099
37651
  console.error(`Already a ForgeCAD project (slug: ${existing.slug}).`);
37100
37652
  process.exit(1);
37101
37653
  }
37102
- requireCliAuth();
37103
- let name;
37104
- let slug2;
37105
- let visibility = "private";
37106
- for (let i = 0; i < args.length; i++) {
37107
- if (args[i] === "--slug" && args[i + 1]) {
37108
- slug2 = args[++i];
37109
- } else if (args[i] === "--visibility" && args[i + 1]) {
37110
- visibility = args[++i];
37111
- } else if (!args[i].startsWith("-")) {
37112
- name = args[i];
37113
- }
37114
- }
37115
- name = name ?? basename18(cwd);
37116
- slug2 = slug2 ?? slugify(name);
37117
- console.log(`Creating project "${name}" (${slug2})...`);
37118
37654
  try {
37119
- const project = await createProject(slug2, name, visibility);
37655
+ const { name, slug: slug2, visibility } = parseProjectInitArgs(args, cwd);
37120
37656
  const manifest = {
37121
- projectId: project.id,
37122
- slug: project.slug,
37123
- server: getServerUrl()
37657
+ name,
37658
+ slug: slug2,
37659
+ server: getServerUrl(),
37660
+ visibility
37124
37661
  };
37125
37662
  writeManifest(cwd, manifest);
37126
- const localFiles = scanLocalFiles(cwd);
37127
- if (localFiles.length > 0) {
37128
- console.log(`Pushing ${localFiles.length} file(s)...`);
37129
- for (const file of localFiles) {
37130
- await saveRemoteFile(project.id, file.path, file.content);
37131
- console.log(` ${file.path}`);
37132
- }
37133
- }
37134
- const fileIds = await fetchFileIds(project.id);
37135
- writeManifest(cwd, { ...manifest, files: fileIds });
37136
- console.log(`\x1B[32mProject initialized.\x1B[0m`);
37137
- console.log(` ID: ${project.id}`);
37138
- console.log(` Slug: ${project.slug}`);
37139
- console.log(` URL: ${getServerUrl()}/app/p/${project.id}`);
37663
+ console.log(`\x1B[32mProject initialized locally.\x1B[0m`);
37664
+ console.log(` Name: ${name}`);
37665
+ console.log(` Slug: ${slug2}`);
37666
+ console.log(` Next: forgecad project push`);
37140
37667
  } catch (error) {
37141
37668
  console.error(`\x1B[31m${error instanceof Error ? error.message : "Init failed"}\x1B[0m`);
37142
37669
  process.exit(1);
@@ -37158,7 +37685,7 @@ async function runProjectCloneCli(args) {
37158
37685
  throw new Error(`Project "${slug2}" not found. Run \`forgecad project list\` to see available projects.`);
37159
37686
  }
37160
37687
  const targetDir = resolve46(slug2);
37161
- if (existsSync26(targetDir)) {
37688
+ if (existsSync27(targetDir)) {
37162
37689
  throw new Error(`Directory "${slug2}" already exists.`);
37163
37690
  }
37164
37691
  mkdirSync14(targetDir, { recursive: true });
@@ -37187,7 +37714,7 @@ async function runProjectCloneCli(args) {
37187
37714
  }
37188
37715
  async function runProjectPullCli(args) {
37189
37716
  const cwd = process.cwd();
37190
- let manifest = requireManifest(cwd);
37717
+ let manifest = requireRemoteManifest(cwd, "pull remote changes");
37191
37718
  requireCliAuth();
37192
37719
  const force = args.includes("--force") || args.includes("-f");
37193
37720
  if (!manifest.files) {
@@ -37224,7 +37751,7 @@ async function runProjectPullCli(args) {
37224
37751
  console.log(`
37225
37752
  ${incoming.length} file(s) will be overwritten locally.`);
37226
37753
  if (!force) {
37227
- const ok = await confirm("Proceed?");
37754
+ const ok = await confirm2("Proceed?");
37228
37755
  if (!ok) {
37229
37756
  console.log("Aborted.");
37230
37757
  return;
@@ -37254,15 +37781,61 @@ async function runProjectPushCli(args) {
37254
37781
  requireCliAuth();
37255
37782
  const force = args.includes("--force") || args.includes("-f");
37256
37783
  const deleteMissing = args.includes("--delete-missing");
37257
- if (!manifest.files) {
37258
- console.log("Upgrading manifest to track file IDs...");
37259
- const fileIds = await fetchFileIds(manifest.projectId);
37260
- manifest = { ...manifest, files: fileIds };
37261
- writeManifest(cwd, manifest);
37262
- }
37263
- console.log(`Pushing to "${manifest.slug}"...`);
37264
37784
  try {
37265
37785
  const localFiles = scanLocalFiles(cwd);
37786
+ if (!hasRemoteProject(manifest)) {
37787
+ const name = localProjectName(cwd, manifest);
37788
+ const slug2 = localProjectSlug(cwd, manifest);
37789
+ const visibility = localProjectVisibility(manifest);
37790
+ console.log(`Project "${name}" (${slug2}) has no hosted project yet.`);
37791
+ if (localFiles.length > 0) {
37792
+ for (const file of localFiles) {
37793
+ console.log(` \x1B[32m+ add \x1B[0m ${file.path}`);
37794
+ }
37795
+ console.log(` ${localFiles.length} file(s) will be uploaded.`);
37796
+ } else {
37797
+ console.log(" No local project files found; the hosted project will start empty.");
37798
+ }
37799
+ if (!force) {
37800
+ const ok = await confirm2(`Create hosted project "${name}" and push now?`);
37801
+ if (!ok) {
37802
+ console.log("Aborted.");
37803
+ return;
37804
+ }
37805
+ }
37806
+ console.log(`Creating hosted project "${name}" (${slug2})...`);
37807
+ const project = await createProject(slug2, name, visibility);
37808
+ manifest = {
37809
+ ...manifest,
37810
+ projectId: project.id,
37811
+ name,
37812
+ slug: project.slug,
37813
+ server: getServerUrl(),
37814
+ visibility
37815
+ };
37816
+ writeManifest(cwd, manifest);
37817
+ if (localFiles.length > 0) {
37818
+ console.log(`Pushing ${localFiles.length} file(s)...`);
37819
+ for (const file of localFiles) {
37820
+ await saveRemoteFile(project.id, file.path, file.content);
37821
+ console.log(` ${file.path}`);
37822
+ }
37823
+ }
37824
+ const fileIds2 = await fetchFileIds(project.id);
37825
+ writeManifest(cwd, { ...manifest, files: fileIds2 });
37826
+ console.log(`\x1B[32mProject pushed.\x1B[0m`);
37827
+ console.log(` ID: ${project.id}`);
37828
+ console.log(` Slug: ${project.slug}`);
37829
+ console.log(` URL: ${getServerUrl()}/app/p/${project.id}`);
37830
+ return;
37831
+ }
37832
+ if (!manifest.files) {
37833
+ console.log("Upgrading manifest to track file IDs...");
37834
+ const fileIds2 = await fetchFileIds(manifest.projectId);
37835
+ manifest = { ...manifest, files: fileIds2 };
37836
+ writeManifest(cwd, manifest);
37837
+ }
37838
+ console.log(`Pushing to "${manifest.slug}"...`);
37266
37839
  let remoteFiles = await fetchRemoteFiles(manifest.projectId);
37267
37840
  let movedCount = 0;
37268
37841
  if (!deleteMissing && manifest.files && Object.keys(manifest.files).length > 0) {
@@ -37279,8 +37852,8 @@ Checking ${orphaned.length} missing tracked file(s) against ${untracked.length}
37279
37852
  }
37280
37853
  const remainingUntracked = [...untracked].sort((a, b) => a.path.localeCompare(b.path));
37281
37854
  const skippedMovePrompts = [];
37282
- const removeRemainingUntracked = (path5) => {
37283
- const index = remainingUntracked.findIndex((file) => file.path === path5);
37855
+ const removeRemainingUntracked = (path7) => {
37856
+ const index = remainingUntracked.findIndex((file) => file.path === path7);
37284
37857
  if (index >= 0) remainingUntracked.splice(index, 1);
37285
37858
  };
37286
37859
  for (const [fromPath, fileId] of orphaned) {
@@ -37304,7 +37877,7 @@ Checking ${orphaned.length} missing tracked file(s) against ${untracked.length}
37304
37877
  }
37305
37878
  if (candidates.length === 0 && remainingUntracked.length === 1) {
37306
37879
  const candidate = remainingUntracked[0];
37307
- const yes = await confirm(`Did you move ${fromPath} \u2192 ${candidate.path}?`);
37880
+ const yes = await confirm2(`Did you move ${fromPath} \u2192 ${candidate.path}?`);
37308
37881
  if (yes) {
37309
37882
  remoteFiles = await applyProjectPushMove({ cwd, manifest, remoteFiles, fromPath, toPath: candidate.path, fileId });
37310
37883
  movedCount++;
@@ -37322,7 +37895,7 @@ Checking ${orphaned.length} missing tracked file(s) against ${untracked.length}
37322
37895
  }
37323
37896
  if (candidates.length === 1) {
37324
37897
  const candidate = candidates[0];
37325
- const yes = await confirm(`Did you move ${fromPath} \u2192 ${candidate.path} (${moveCandidateReasonLabel(candidate)})?`);
37898
+ const yes = await confirm2(`Did you move ${fromPath} \u2192 ${candidate.path} (${moveCandidateReasonLabel(candidate)})?`);
37326
37899
  if (yes) {
37327
37900
  remoteFiles = await applyProjectPushMove({ cwd, manifest, remoteFiles, fromPath, toPath: candidate.path, fileId });
37328
37901
  movedCount++;
@@ -37360,7 +37933,7 @@ Checking ${orphaned.length} missing tracked file(s) against ${untracked.length}
37360
37933
  }
37361
37934
  } else if (deleteMissing && manifest.files && Object.keys(manifest.files).length > 0) {
37362
37935
  const localPathSet = new Set(localFiles.map((f2) => f2.path));
37363
- const missingTrackedCount = Object.keys(manifest.files).filter((path5) => !localPathSet.has(path5)).length;
37936
+ const missingTrackedCount = Object.keys(manifest.files).filter((path7) => !localPathSet.has(path7)).length;
37364
37937
  if (missingTrackedCount > 0) {
37365
37938
  console.log(`
37366
37939
  --delete-missing: skipping move detection for ${missingTrackedCount} missing tracked file(s).`);
@@ -37399,7 +37972,7 @@ Checking ${orphaned.length} missing tracked file(s) against ${untracked.length}
37399
37972
  }
37400
37973
  console.log(` ${outgoing.length} change(s) total.`);
37401
37974
  if (!force) {
37402
- const ok = await confirm("Push these changes?");
37975
+ const ok = await confirm2("Push these changes?");
37403
37976
  if (!ok) {
37404
37977
  console.log("Aborted.");
37405
37978
  return;
@@ -37426,9 +37999,26 @@ Checking ${orphaned.length} missing tracked file(s) against ${untracked.length}
37426
37999
  async function runProjectStatusCli(_args) {
37427
38000
  const cwd = process.cwd();
37428
38001
  const manifest = requireManifest(cwd);
37429
- requireCliAuth();
37430
38002
  try {
37431
38003
  const localFiles = scanLocalFiles(cwd);
38004
+ if (!hasRemoteProject(manifest)) {
38005
+ console.log(`Project: ${manifest.slug}`);
38006
+ console.log(`Server: ${manifest.server}`);
38007
+ console.log("Remote: not created yet");
38008
+ console.log(`Files: ${localFiles.length} local`);
38009
+ console.log("");
38010
+ if (localFiles.length === 0) {
38011
+ console.log(" Local-only project. Run `forgecad project push` to create the hosted project.");
38012
+ } else {
38013
+ for (const file of localFiles) {
38014
+ console.log(` \x1B[32m+ local \x1B[0m ${file.path}`);
38015
+ }
38016
+ console.log("");
38017
+ console.log(` ${localFiles.length} file(s) will be uploaded when you run \`forgecad project push\`.`);
38018
+ }
38019
+ return;
38020
+ }
38021
+ requireCliAuth();
37432
38022
  const remoteFiles = await fetchRemoteFiles(manifest.projectId);
37433
38023
  const diffs = diffFiles(
37434
38024
  localFiles,
@@ -37463,7 +38053,7 @@ async function runProjectListCli(_args) {
37463
38053
  try {
37464
38054
  const projects = await listProjects();
37465
38055
  if (projects.length === 0) {
37466
- console.log("No projects. Create one with `forgecad project init`.");
38056
+ console.log("No hosted projects. Run `forgecad project init` in a project folder, then `forgecad project push`.");
37467
38057
  return;
37468
38058
  }
37469
38059
  const idWidth = Math.max(2, ...projects.map((p) => p.id.length));
@@ -37512,6 +38102,11 @@ async function runProjectOpenCli(_args) {
37512
38102
  openBrowser2(url2);
37513
38103
  return;
37514
38104
  }
38105
+ if (!hasRemoteProject(manifest)) {
38106
+ console.error("This project has not been created on the server yet.");
38107
+ console.error(" Run `forgecad studio .` for local editing, or `forgecad project push` to create the hosted project first.");
38108
+ process.exit(1);
38109
+ }
37515
38110
  const server = normalizeServerUrl(manifest.server || readStoredAuth()?.server || "https://forgecad.io", "Project server URL");
37516
38111
  const url = `${server}/app?project=${manifest.projectId}`;
37517
38112
  console.log(`Opening "${manifest.slug}" in browser...`);
@@ -37592,7 +38187,7 @@ return assembly('Project Assembly')
37592
38187
  };
37593
38188
  async function runProjectInfoCli(_args) {
37594
38189
  const cwd = process.cwd();
37595
- const manifest = requireManifest(cwd);
38190
+ const manifest = requireRemoteManifest(cwd, "show project info");
37596
38191
  requireCliAuth();
37597
38192
  try {
37598
38193
  const project = await getProject(manifest.projectId);
@@ -37616,7 +38211,7 @@ async function runProjectInfoCli(_args) {
37616
38211
  }
37617
38212
  async function runProjectRenameCli(args) {
37618
38213
  const cwd = process.cwd();
37619
- const manifest = requireManifest(cwd);
38214
+ const manifest = requireRemoteManifest(cwd, "rename it");
37620
38215
  requireCliAuth();
37621
38216
  let name;
37622
38217
  let slug2;
@@ -37645,7 +38240,7 @@ async function runProjectRenameCli(args) {
37645
38240
  }
37646
38241
  async function runProjectSetVisibilityCli(args) {
37647
38242
  const cwd = process.cwd();
37648
- const manifest = requireManifest(cwd);
38243
+ const manifest = requireRemoteManifest(cwd, "change its visibility");
37649
38244
  requireCliAuth();
37650
38245
  const visibility = args.find((a) => !a.startsWith("-"));
37651
38246
  if (!visibility || !["private", "shared", "public"].includes(visibility)) {
@@ -37662,14 +38257,14 @@ async function runProjectSetVisibilityCli(args) {
37662
38257
  }
37663
38258
  async function runProjectDeleteCli(args) {
37664
38259
  const cwd = process.cwd();
37665
- const manifest = requireManifest(cwd);
38260
+ const manifest = requireRemoteManifest(cwd, "delete it");
37666
38261
  requireCliAuth();
37667
38262
  const force = args.includes("--force") || args.includes("-f");
37668
38263
  try {
37669
38264
  const project = await getProject(manifest.projectId);
37670
38265
  if (!force) {
37671
38266
  console.log(`\x1B[31mThis will permanently delete project "${project.name}" (${project.slug}) and all its files.\x1B[0m`);
37672
- const ok = await confirm("Are you sure?");
38267
+ const ok = await confirm2("Are you sure?");
37673
38268
  if (!ok) {
37674
38269
  console.log("Aborted.");
37675
38270
  return;
@@ -37684,7 +38279,7 @@ async function runProjectDeleteCli(args) {
37684
38279
  }
37685
38280
  async function runFileListCli(args) {
37686
38281
  const cwd = process.cwd();
37687
- const manifest = requireManifest(cwd);
38282
+ const manifest = requireRemoteManifest(cwd, "list remote files");
37688
38283
  requireCliAuth();
37689
38284
  const filterPath = args.find((a) => !a.startsWith("-"));
37690
38285
  try {
@@ -37710,7 +38305,7 @@ async function runFileListCli(args) {
37710
38305
  }
37711
38306
  async function runFileReadCli(args) {
37712
38307
  const cwd = process.cwd();
37713
- const manifest = requireManifest(cwd);
38308
+ const manifest = requireRemoteManifest(cwd, "read remote files");
37714
38309
  requireCliAuth();
37715
38310
  const filePath = args.find((a) => !a.startsWith("-"));
37716
38311
  if (!filePath) {
@@ -37727,7 +38322,7 @@ async function runFileReadCli(args) {
37727
38322
  }
37728
38323
  async function runFileSaveCli(args) {
37729
38324
  const cwd = process.cwd();
37730
- const manifest = requireManifest(cwd);
38325
+ const manifest = requireRemoteManifest(cwd, "save remote files");
37731
38326
  requireCliAuth();
37732
38327
  let filePath;
37733
38328
  let content;
@@ -37755,7 +38350,7 @@ async function runFileSaveCli(args) {
37755
38350
  content = Buffer.concat(chunks).toString("utf-8");
37756
38351
  } else if (!content) {
37757
38352
  const localPath = join22(cwd, filePath);
37758
- if (!existsSync26(localPath)) {
38353
+ if (!existsSync27(localPath)) {
37759
38354
  console.error(`Local file not found: ${filePath}`);
37760
38355
  console.error("Use --content or --stdin to provide content directly.");
37761
38356
  process.exit(1);
@@ -37771,7 +38366,7 @@ async function runFileSaveCli(args) {
37771
38366
  }
37772
38367
  async function runFileDeleteCli(args) {
37773
38368
  const cwd = process.cwd();
37774
- const manifest = requireManifest(cwd);
38369
+ const manifest = requireRemoteManifest(cwd, "delete remote files");
37775
38370
  requireCliAuth();
37776
38371
  const force = args.includes("--force") || args.includes("-f");
37777
38372
  const filePath = args.find((a) => !a.startsWith("-"));
@@ -37781,7 +38376,7 @@ async function runFileDeleteCli(args) {
37781
38376
  }
37782
38377
  try {
37783
38378
  if (!force) {
37784
- const ok = await confirm(`Delete remote file "${filePath}"?`);
38379
+ const ok = await confirm2(`Delete remote file "${filePath}"?`);
37785
38380
  if (!ok) {
37786
38381
  console.log("Aborted.");
37787
38382
  return;
@@ -37796,7 +38391,7 @@ async function runFileDeleteCli(args) {
37796
38391
  }
37797
38392
  async function runFileRenameCli(args) {
37798
38393
  const cwd = process.cwd();
37799
- const manifest = requireManifest(cwd);
38394
+ const manifest = requireRemoteManifest(cwd, "rename remote files");
37800
38395
  requireCliAuth();
37801
38396
  const positionals = args.filter((a) => !a.startsWith("-"));
37802
38397
  if (positionals.length < 2) {
@@ -37816,7 +38411,7 @@ async function runFileRenameCli(args) {
37816
38411
  }
37817
38412
  async function runFileMkdirCli(args) {
37818
38413
  const cwd = process.cwd();
37819
- const manifest = requireManifest(cwd);
38414
+ const manifest = requireRemoteManifest(cwd, "create remote folders");
37820
38415
  requireCliAuth();
37821
38416
  const dirPath = args.find((a) => !a.startsWith("-"));
37822
38417
  if (!dirPath) {
@@ -37833,7 +38428,7 @@ async function runFileMkdirCli(args) {
37833
38428
  }
37834
38429
  async function runFileCopyCli(args) {
37835
38430
  const cwd = process.cwd();
37836
- const manifest = requireManifest(cwd);
38431
+ const manifest = requireRemoteManifest(cwd, "copy remote files");
37837
38432
  requireCliAuth();
37838
38433
  let destFilename;
37839
38434
  const positionals = [];
@@ -37865,7 +38460,7 @@ async function runFileCopyCli(args) {
37865
38460
  }
37866
38461
  async function runProjectMembersCli(_args) {
37867
38462
  const cwd = process.cwd();
37868
- const manifest = requireManifest(cwd);
38463
+ const manifest = requireRemoteManifest(cwd, "manage members");
37869
38464
  requireCliAuth();
37870
38465
  try {
37871
38466
  const members = await listMembers(manifest.projectId);
@@ -37887,7 +38482,7 @@ async function runProjectMembersCli(_args) {
37887
38482
  }
37888
38483
  async function runProjectAddMemberCli(args) {
37889
38484
  const cwd = process.cwd();
37890
- const manifest = requireManifest(cwd);
38485
+ const manifest = requireRemoteManifest(cwd, "add members");
37891
38486
  requireCliAuth();
37892
38487
  let email;
37893
38488
  let role = "editor";
@@ -37916,7 +38511,7 @@ async function runProjectAddMemberCli(args) {
37916
38511
  }
37917
38512
  async function runProjectRemoveMemberCli(args) {
37918
38513
  const cwd = process.cwd();
37919
- const manifest = requireManifest(cwd);
38514
+ const manifest = requireRemoteManifest(cwd, "remove members");
37920
38515
  requireCliAuth();
37921
38516
  const email = args.find((a) => !a.startsWith("-"));
37922
38517
  if (!email) {
@@ -37938,7 +38533,7 @@ async function runProjectRemoveMemberCli(args) {
37938
38533
  }
37939
38534
  async function runProjectSetRoleCli(args) {
37940
38535
  const cwd = process.cwd();
37941
- const manifest = requireManifest(cwd);
38536
+ const manifest = requireRemoteManifest(cwd, "change member roles");
37942
38537
  requireCliAuth();
37943
38538
  const positionals = args.filter((a) => !a.startsWith("-"));
37944
38539
  if (positionals.length < 2) {
@@ -37997,7 +38592,7 @@ async function runSharesDeleteCli(args) {
37997
38592
  }
37998
38593
  try {
37999
38594
  if (!force) {
38000
- const ok = await confirm(`Unpublish share "${shareId}"?`);
38595
+ const ok = await confirm2(`Unpublish share "${shareId}"?`);
38001
38596
  if (!ok) {
38002
38597
  console.log("Aborted.");
38003
38598
  return;
@@ -38028,7 +38623,7 @@ async function runNewCli(args) {
38028
38623
  }
38029
38624
  name = name ?? `my-${template}`;
38030
38625
  const filename = name.endsWith(tmpl.ext) ? name : `${name}${tmpl.ext}`;
38031
- if (existsSync26(filename)) {
38626
+ if (existsSync27(filename)) {
38032
38627
  console.error(`File already exists: ${filename}`);
38033
38628
  process.exit(1);
38034
38629
  }
@@ -38122,7 +38717,7 @@ async function runTokenRevokeCli(args) {
38122
38717
  process.exit(1);
38123
38718
  }
38124
38719
  if (!force) {
38125
- const ok = await confirm(`Revoke token "${token.name}"? Any CI/CD pipelines using it will stop working.`);
38720
+ const ok = await confirm2(`Revoke token "${token.name}"? Any CI/CD pipelines using it will stop working.`);
38126
38721
  if (!ok) {
38127
38722
  console.log("Cancelled.");
38128
38723
  return;
@@ -38178,13 +38773,13 @@ function findCollisions(entries) {
38178
38773
  }
38179
38774
  return collisions;
38180
38775
  }
38181
- function usage23() {
38776
+ function usage24() {
38182
38777
  console.error("Usage: forgecad check params <script.forge.js> [--samples N]");
38183
38778
  process.exit(1);
38184
38779
  }
38185
38780
  async function runParamCheckCli(argv = process.argv.slice(2)) {
38186
38781
  const scriptPath = argv[0];
38187
- if (!scriptPath) usage23();
38782
+ if (!scriptPath) usage24();
38188
38783
  const samplesArg = argv.indexOf("--samples");
38189
38784
  const numSamples = samplesArg >= 0 ? parseInt(argv[samplesArg + 1], 10) : 8;
38190
38785
  const code = readFileSync31(resolve47(scriptPath), "utf-8");
@@ -38868,7 +39463,7 @@ function buildPrintCheckReport(objects, verifications, options) {
38868
39463
  }
38869
39464
 
38870
39465
  // cli/print-check.ts
38871
- function usage24() {
39466
+ function usage25() {
38872
39467
  console.error(
38873
39468
  "Usage: forgecad check print <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [input ...] [--json] [--output report.json] [--profile fdm-pla-0.4mm] [--param Key=Value] [--joint JointName=Value]"
38874
39469
  );
@@ -38908,7 +39503,7 @@ function takeValue(argv, index, name) {
38908
39503
  }
38909
39504
  return value;
38910
39505
  }
38911
- function parseArgs22(argv) {
39506
+ function parseArgs23(argv) {
38912
39507
  const { consumed: focusConsumed } = parseFocusFlags(argv);
38913
39508
  const consumed = new Set(focusConsumed);
38914
39509
  const profileOverrides = {};
@@ -39003,7 +39598,7 @@ function parseArgs22(argv) {
39003
39598
  }
39004
39599
  }
39005
39600
  const positional = argv.filter((arg, index) => !consumed.has(index) && !arg.startsWith("-"));
39006
- if (positional.length === 0) usage24();
39601
+ if (positional.length === 0) usage25();
39007
39602
  const inputPaths = requireInputPaths(positional, "Missing input path.");
39008
39603
  requireRenderableInputPaths(inputPaths);
39009
39604
  requireSingleInputForOutputPath(inputPaths, outputPath);
@@ -39087,7 +39682,7 @@ function scriptErrorReport(scriptPath, message, profile, elapsedMs) {
39087
39682
  };
39088
39683
  }
39089
39684
  async function runPrintCheckCli(argv = process.argv.slice(2)) {
39090
- const args = parseArgs22(argv);
39685
+ const args = parseArgs23(argv);
39091
39686
  requireExistingInputPaths(args.inputPaths);
39092
39687
  const profile = resolvePrintProfile(args.profileId, args.profileOverrides);
39093
39688
  const reports = [];
@@ -39322,7 +39917,7 @@ sys.exit(0 if ok else 1)
39322
39917
  }
39323
39918
 
39324
39919
  // cli/forge-pinocchio.ts
39325
- function parseArgs23(argv) {
39920
+ function parseArgs24(argv) {
39326
39921
  const inputPaths = [];
39327
39922
  let outputPath;
39328
39923
  const paramOverrides = {};
@@ -39369,7 +39964,7 @@ function resolveSimulationModel6(result, jointOverrides) {
39369
39964
  return collectSimulationModel(def, { state: jointOverrides });
39370
39965
  }
39371
39966
  async function runPinocchioCli(argv = process.argv.slice(2)) {
39372
- const { inputPaths, outputPath, paramOverrides, jointOverrides } = parseArgs23(argv);
39967
+ const { inputPaths, outputPath, paramOverrides, jointOverrides } = parseArgs24(argv);
39373
39968
  requireExistingInputPaths(inputPaths);
39374
39969
  let failures = 0;
39375
39970
  for (const [index, scriptPath] of inputPaths.entries()) {
@@ -39407,9 +40002,9 @@ async function exportPinocchioInput(scriptPath, explicitOutputPath, paramOverrid
39407
40002
  }
39408
40003
 
39409
40004
  // cli/sim-dynamics.ts
39410
- import { existsSync as existsSync27, readFileSync as readFileSync33, statSync as statSync12 } from "fs";
40005
+ import { existsSync as existsSync28, readFileSync as readFileSync33, statSync as statSync12 } from "fs";
39411
40006
  import { join as join23, resolve as resolve50 } from "path";
39412
- function parseArgs24(argv) {
40007
+ function parseArgs25(argv) {
39413
40008
  const inputPaths = [];
39414
40009
  let json = false;
39415
40010
  let compact = false;
@@ -39450,12 +40045,12 @@ function failedReport5(source, message, code, suggest) {
39450
40045
  }
39451
40046
  function resolveFeedbackPath2(inputPath) {
39452
40047
  const abs = resolve50(inputPath);
39453
- if (existsSync27(abs) && statSync12(abs).isDirectory()) return join23(abs, "feedback.json");
40048
+ if (existsSync28(abs) && statSync12(abs).isDirectory()) return join23(abs, "feedback.json");
39454
40049
  return abs;
39455
40050
  }
39456
40051
  function ingest(inputPath) {
39457
40052
  const feedbackPath = resolveFeedbackPath2(inputPath);
39458
- if (!existsSync27(feedbackPath)) {
40053
+ if (!existsSync28(feedbackPath)) {
39459
40054
  return failedReport5(
39460
40055
  inputPath,
39461
40056
  `No results at ${feedbackPath}.`,
@@ -39481,7 +40076,7 @@ function ingest(inputPath) {
39481
40076
  return { ...report, source: inputPath };
39482
40077
  }
39483
40078
  async function runSimDynamicsCli(argv = process.argv.slice(2)) {
39484
- const options = parseArgs24(argv);
40079
+ const options = parseArgs25(argv);
39485
40080
  const reports = options.inputPaths.map((inputPath) => ingest(inputPath));
39486
40081
  printFeedback(reports.length === 1 ? reports[0] : { runs: reports }, options.json, options.compact);
39487
40082
  if (reports.some((report) => !report.ok) && !options.noFailExit) process.exitCode = 1;
@@ -39489,7 +40084,7 @@ async function runSimDynamicsCli(argv = process.argv.slice(2)) {
39489
40084
 
39490
40085
  // cli/sim-mass.ts
39491
40086
  var DEFAULT_DENSITY_KG_M35 = 1e3;
39492
- function parseArgs25(argv) {
40087
+ function parseArgs26(argv) {
39493
40088
  const inputPaths = [];
39494
40089
  const paramOverrides = {};
39495
40090
  const jointOverrides = {};
@@ -39648,7 +40243,7 @@ function analyze2(source, options, objects) {
39648
40243
  };
39649
40244
  }
39650
40245
  async function runSimMassCli(argv = process.argv.slice(2)) {
39651
- const options = parseArgs25(argv);
40246
+ const options = parseArgs26(argv);
39652
40247
  requireExistingInputPaths(options.inputPaths);
39653
40248
  await init();
39654
40249
  const reports = [];
@@ -39776,7 +40371,7 @@ function analyzeMechanism(input) {
39776
40371
 
39777
40372
  // cli/sim-mechanism.ts
39778
40373
  var DEFAULT_DENSITY_KG_M36 = 1e3;
39779
- function parseArgs26(argv) {
40374
+ function parseArgs27(argv) {
39780
40375
  const inputPaths = [];
39781
40376
  const paramOverrides = {};
39782
40377
  const jointOverrides = {};
@@ -39871,7 +40466,7 @@ function flattenShapes(part, out) {
39871
40466
  else if (part instanceof ShapeGroup) part.children.forEach((child) => flattenShapes(child, out));
39872
40467
  }
39873
40468
  async function runSimMechanismCli(argv = process.argv.slice(2)) {
39874
- const options = parseArgs26(argv);
40469
+ const options = parseArgs27(argv);
39875
40470
  requireExistingInputPaths(options.inputPaths);
39876
40471
  await init();
39877
40472
  const reports = [];
@@ -40317,7 +40912,7 @@ function resolveToleranceResponses(verifications, specOverrides) {
40317
40912
  }
40318
40913
 
40319
40914
  // cli/sim-tolerance.ts
40320
- function parseArgs27(argv) {
40915
+ function parseArgs28(argv) {
40321
40916
  const inputPaths = [];
40322
40917
  const paramOverrides = {};
40323
40918
  const tolOverrides = /* @__PURE__ */ new Map();
@@ -40606,7 +41201,7 @@ async function analyze4(scriptPath, options) {
40606
41201
  };
40607
41202
  }
40608
41203
  async function runSimToleranceCli(argv = process.argv.slice(2)) {
40609
- const options = parseArgs27(argv);
41204
+ const options = parseArgs28(argv);
40610
41205
  requireExistingInputPaths(options.inputPaths);
40611
41206
  await init();
40612
41207
  const reports = [];
@@ -41572,9 +42167,9 @@ function parseJointFlags2(argv) {
41572
42167
  }
41573
42168
  return { overrides, consumed };
41574
42169
  }
41575
- function usage25() {
42170
+ function usage26() {
41576
42171
  console.error(
41577
- "Usage: forgecad run <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [input ...] [--details] [--history] [--features] [--connectivity] [--connectivity-tolerance <mm>] [--param Key=Value] [--joint JointName=Value] [--debug-imports] [--verbose|-v] [--backend manifold|occt|truck|sdf] [--quality live|default|high] [--solver-profile] [--solver-debug-out <dir>]"
42172
+ "Usage: forgecad run <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [input ...] [--details] [--history] [--features] [--connectivity] [--connectivity-tolerance <mm>] [--param Key=Value] [--joint JointName=Value] [--project-root <dir>] [--debug-imports] [--verbose|-v] [--backend manifold|occt|truck|sdf] [--quality live|default|high] [--solver-profile] [--solver-debug-out <dir>]"
41578
42173
  );
41579
42174
  process.exit(1);
41580
42175
  }
@@ -41773,11 +42368,12 @@ async function runScriptCli(argv = process.argv.slice(2)) {
41773
42368
  const explicitBackend = parseBackendArg2(argv);
41774
42369
  const quality = parseQualityArg2(argv);
41775
42370
  const solverDebugOut = parseRequiredArg(argv, "--solver-debug-out");
42371
+ const projectRoot = parseRequiredArg(argv, "--project-root");
41776
42372
  const positional = argv.filter(
41777
- (arg, i) => !paramConsumed.has(i) && !jointConsumed.has(i) && !focusConsumed.has(i) && !connectivityConsumed.has(i) && arg !== "--details" && arg !== "--history" && arg !== "--features" && arg !== "--solver-profile" && arg !== "--debug-imports" && arg !== "--journeys" && arg !== "--journeys-json" && arg !== "--verbose" && arg !== "-v" && arg !== "--backend" && argv[i - 1] !== "--backend" && arg !== "--quality" && argv[i - 1] !== "--quality" && arg !== "-q" && argv[i - 1] !== "-q" && arg !== "--solver-debug-out" && argv[i - 1] !== "--solver-debug-out"
42373
+ (arg, i) => !paramConsumed.has(i) && !jointConsumed.has(i) && !focusConsumed.has(i) && !connectivityConsumed.has(i) && arg !== "--details" && arg !== "--history" && arg !== "--features" && arg !== "--solver-profile" && arg !== "--debug-imports" && arg !== "--journeys" && arg !== "--journeys-json" && arg !== "--verbose" && arg !== "-v" && arg !== "--backend" && argv[i - 1] !== "--backend" && arg !== "--quality" && argv[i - 1] !== "--quality" && arg !== "-q" && argv[i - 1] !== "-q" && arg !== "--solver-debug-out" && argv[i - 1] !== "--solver-debug-out" && arg !== "--project-root" && argv[i - 1] !== "--project-root"
41778
42374
  );
41779
42375
  const scriptPaths = positional;
41780
- if (scriptPaths.length === 0) usage25();
42376
+ if (scriptPaths.length === 0) usage26();
41781
42377
  requireRenderableInputPaths(scriptPaths);
41782
42378
  requireExistingInputPaths(scriptPaths);
41783
42379
  if (scriptPaths.length > 1 && printJourneysJson) {
@@ -41799,7 +42395,7 @@ async function runScriptCli(argv = process.argv.slice(2)) {
41799
42395
  debugSvgSnapshots: true
41800
42396
  });
41801
42397
  }
41802
- const input = loadCliScriptInput(scriptPath);
42398
+ const input = loadCliScriptInput(scriptPath, { projectRoot });
41803
42399
  activeBackend = resolveCliBackend(explicitBackend, input) ?? CLI_DEFAULT_BACKEND;
41804
42400
  await initCliBackend(activeBackend);
41805
42401
  resetSolverStats();
@@ -41988,13 +42584,36 @@ async function runScriptCli(argv = process.argv.slice(2)) {
41988
42584
  const connectivity = analyzePhysicalConnectivity2(entries, connectivityOptions);
41989
42585
  for (const line of formatPhysicalConnectivity(connectivity)) console.log(line);
41990
42586
  }
41991
- console.log(
41992
- `\u2713 Params: ${result.params.map((p) => {
41993
- const label = p.choices ? `${p.choices[p.value]}` : `${p.value}`;
41994
- const changed = p.value !== p.defaultValue ? " *" : "";
41995
- return `${p.name}=${label}${changed}`;
41996
- }).join(", ")}`
41997
- );
42587
+ if (result.params.length > 0) {
42588
+ console.log(
42589
+ `\u2713 Params: ${result.params.map((p) => {
42590
+ const label = p.choices ? `${p.choices[p.value]}` : `${p.value}`;
42591
+ const changed = p.value !== p.defaultValue ? " *" : "";
42592
+ return `${p.name}=${label}${changed}`;
42593
+ }).join(", ")}`
42594
+ );
42595
+ }
42596
+ if (result.stringParams.length > 0) {
42597
+ console.log(`\u2713 String Params: ${result.stringParams.map((p) => `${p.name}=${JSON.stringify(p.value)}`).join(", ")}`);
42598
+ }
42599
+ if (result.listParams.length > 0) {
42600
+ console.log(`\u2713 List Params: ${result.listParams.map((p) => `${p.name} (${p.items.length} rows)`).join(", ")}`);
42601
+ }
42602
+ if (result.path2dParams.length > 0) {
42603
+ console.log(
42604
+ `\u2713 Path Params: ${result.path2dParams.map((p) => `${p.name} (${p.points.length} ${p.closed ? "outline" : "centerline"} points)`).join(", ")}`
42605
+ );
42606
+ }
42607
+ if (result.spline2dParams.length > 0) {
42608
+ console.log(
42609
+ `\u2713 Spline Params: ${result.spline2dParams.map((p) => `${p.name} (${p.points.length} points, degree ${p.degree})`).join(", ")}`
42610
+ );
42611
+ }
42612
+ if (result.placement2dParams.length > 0) {
42613
+ console.log(
42614
+ `\u2713 Placement Params: ${result.placement2dParams.map((p) => `${p.name} (${p.items.length} items${p.zones.length > 0 ? `, ${p.zones.length} zones` : ""})`).join(", ")}`
42615
+ );
42616
+ }
41998
42617
  console.log(`\u2713 Time: ${result.timeMs.toFixed(0)}ms`);
41999
42618
  if (!printSolverProfile && solverDebugOut) {
42000
42619
  const bundles = await writeSolverDebugArtifacts(solverDebugOut, scriptPath, result.objects);
@@ -42155,7 +42774,7 @@ async function runScriptCli(argv = process.argv.slice(2)) {
42155
42774
  // cli/forge-render-section.ts
42156
42775
  import { writeFile as writeFile11 } from "fs/promises";
42157
42776
  import { basename as basename20, extname as extname16, resolve as resolve53 } from "path";
42158
- function usage26() {
42777
+ function usage27() {
42159
42778
  console.error(
42160
42779
  "Usage: forgecad render section <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [input ...] [--output path] [--format svg|png] [--param Key=Value] [--joint JointName=Value] [--plane XY|XZ|YZ] [--offset <number>] [--size <px>] [--edges <off|thin|bold>] [--chrome-path <path>] [--background <color>]"
42161
42780
  );
@@ -42233,7 +42852,7 @@ function parseRenderSectionArgs(argv) {
42233
42852
  background = argv[++i];
42234
42853
  } else if (argv[i].startsWith("-")) {
42235
42854
  console.error(`Unknown option: ${argv[i]}`);
42236
- usage26();
42855
+ usage27();
42237
42856
  } else {
42238
42857
  inputPaths.push(argv[i]);
42239
42858
  }
@@ -42324,7 +42943,7 @@ async function renderSectionInput(options, scriptPath, resolvedChromePath) {
42324
42943
 
42325
42944
  // cli/update-check.ts
42326
42945
  import { spawn as spawn4 } from "child_process";
42327
- import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync34, writeFileSync as writeFileSync25 } from "fs";
42946
+ import { existsSync as existsSync29, mkdirSync as mkdirSync17, readFileSync as readFileSync34, writeFileSync as writeFileSync25 } from "fs";
42328
42947
  import { homedir as homedir9 } from "os";
42329
42948
  import { dirname as dirname15, join as join25 } from "path";
42330
42949
  var PACKAGE_NAME = "forgecad";
@@ -42348,7 +42967,7 @@ function readCache() {
42348
42967
  function writeCache(cache) {
42349
42968
  const p = cachePath();
42350
42969
  const dir = dirname15(p);
42351
- if (!existsSync28(dir)) mkdirSync17(dir, { recursive: true });
42970
+ if (!existsSync29(dir)) mkdirSync17(dir, { recursive: true });
42352
42971
  writeFileSync25(p, JSON.stringify(cache), "utf-8");
42353
42972
  }
42354
42973
  function shouldSkip() {
@@ -43948,9 +44567,9 @@ var commands = [
43948
44567
  group: "Modeling",
43949
44568
  path: ["run"],
43950
44569
  summary: "Execute a Forge script quickly and print the inner-loop build summary: build count, verification results, parameters, and timing.",
43951
- description: "The fast validation command: runs the script with the real geometry kernel (no browser) and reports build status, object count, `verify.*` pass/fail with expected vs actual values (non-fatal \u2014 the model still renders), parameter values, script logs, and timing. Run it frequently while editing.\n\nA bare `forgecad run` skips expensive diagnostics. Opt in with `--details` (volumes/bounds), `--history` (construction tree), `--features` (feature tallies), `--solver-profile` (constraint solver timing), or `--connectivity` (physical connected components \u2014 bbox contact is evidence, exact geometry is checked by default). `--quality live|default|high` selects the same geometry quality profile as the editor and export tools; `live` is fastest for large models.\n\nDirect `.stl`/`.obj`/`.3mf`/`.step`/`.stp` inputs are imported automatically; STEP/STP auto-selects OCCT unless you pass `--backend`. For deeper confidence gates, prefer `inspect mechanical-integrity`, `check print`, or `inspect fit interference` instead of turning `run` into a catch-all audit command.",
44570
+ description: "The fast validation command: runs the script with the real geometry kernel (no browser) and reports build status, object count, `verify.*` pass/fail with expected vs actual values (non-fatal \u2014 the model still renders), parameter values, script logs, and timing. Run it frequently while editing.\n\nA bare `forgecad run` skips expensive diagnostics. Opt in with `--details` (volumes/bounds), `--history` (construction tree), `--features` (feature tallies), `--solver-profile` (constraint solver timing), or `--connectivity` (physical connected components \u2014 bbox contact is evidence, exact geometry is checked by default). `--quality live|default|high` selects the same geometry quality profile as the editor and export tools; `live` is fastest for large models.\n\nDirect `.stl`/`.obj`/`.3mf`/`.step`/`.stp` inputs are imported automatically; STEP/STP auto-selects OCCT unless you pass `--backend`. Use `--project-root <dir>` when running a file inside a larger uninitialized folder and you intentionally want relative imports to resolve against that root. For deeper confidence gates, prefer `inspect mechanical-integrity`, `check print`, or `inspect fit interference` instead of turning `run` into a catch-all audit command.",
43952
44571
  usage: [
43953
- "forgecad run <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [input ...] [--details] [--history] [--features] [--connectivity] [--connectivity-tolerance <mm>] [--focus [names]] [--hide names] [--journeys] [--journeys-json] [--param Key=Value] [--joint JointName=Value] [--debug-imports] [--verbose] [--backend manifold|occt|truck|sdf] [--quality live|default|high] [--solver-profile] [--solver-debug-out <dir>]"
44572
+ "forgecad run <model.forge.js|asset.stl|asset.obj|asset.3mf|asset.step|asset.stp> [input ...] [--details] [--history] [--features] [--connectivity] [--connectivity-tolerance <mm>] [--focus [names]] [--hide names] [--journeys] [--journeys-json] [--param Key=Value] [--joint JointName=Value] [--project-root <dir>] [--debug-imports] [--verbose] [--backend manifold|occt|truck|sdf] [--quality live|default|high] [--solver-profile] [--solver-debug-out <dir>]"
43954
44573
  ],
43955
44574
  examples: [
43956
44575
  "forgecad run examples/api/static-assembly-connectors.forge.js",
@@ -43964,6 +44583,7 @@ var commands = [
43964
44583
  "forgecad run examples/products/cup.forge.js --backend occt",
43965
44584
  "forgecad run examples/products/cup.forge.js --backend truck --quality live",
43966
44585
  "forgecad run examples/products/cup.forge.js --debug-imports",
44586
+ "forgecad run assembly/main.forge.js --project-root .",
43967
44587
  'forgecad run examples/products/cup.forge.js -p "Wall Thickness=3" -p "Body Height=200"',
43968
44588
  "forgecad run examples/constraints/06-complex-spectrogram.forge.js --solver-debug-out tmp/spectrogram-debug"
43969
44589
  ],
@@ -43990,6 +44610,13 @@ var commands = [
43990
44610
  valueLabel: "<mm>"
43991
44611
  },
43992
44612
  { name: "--debug-imports", description: "Print the import trace" },
44613
+ {
44614
+ name: "--project-root",
44615
+ description: "Project/import root for this run; enables intentional relative imports within that directory",
44616
+ argument: "required",
44617
+ valueLabel: "<dir>",
44618
+ valueKind: "directory"
44619
+ },
43993
44620
  { name: "--journeys", description: "Print model journey summaries declared with scene({ journeys })" },
43994
44621
  { name: "--journeys-json", description: "Emit machine-readable journey metadata and diagnostics only" },
43995
44622
  {
@@ -44509,11 +45136,22 @@ var commands = [
44509
45136
  options: [
44510
45137
  ...PARAM_OPTIONS,
44511
45138
  { name: "--study", description: "Run one named FEA study", argument: "required", valueLabel: "<name>" },
44512
- { name: "--output", description: "Output result root directory", argument: "required", valueLabel: "<dir>", valueKind: "directory" },
45139
+ {
45140
+ name: "--output",
45141
+ description: "Output result root directory",
45142
+ argument: "required",
45143
+ valueLabel: "<dir>",
45144
+ valueKind: "directory"
45145
+ },
44513
45146
  { name: "--no-render", description: "Run solver and write reports without PNG render artifacts" },
44514
45147
  { name: "--camera", description: "Camera/view label for stress renders", argument: "required", valueLabel: "<view>" },
44515
45148
  { name: "--size", description: "Render image size in pixels", argument: "required", valueLabel: "<px>" },
44516
- { name: "--chrome-path", description: "Chrome/Chromium executable path for PNG rendering", argument: "required", valueLabel: "<path>" },
45149
+ {
45150
+ name: "--chrome-path",
45151
+ description: "Chrome/Chromium executable path for PNG rendering",
45152
+ argument: "required",
45153
+ valueLabel: "<path>"
45154
+ },
44517
45155
  { name: "--json", description: "Emit the FEA result index JSON" },
44518
45156
  { name: "--compact", description: "Minify JSON output" },
44519
45157
  { name: "--no-fail-exit", description: "Always exit 0 after writing the result bundle" }
@@ -44566,11 +45204,21 @@ var commands = [
44566
45204
  { value: "deformed", description: "CalculiX displacement shape" }
44567
45205
  ]
44568
45206
  },
44569
- { name: "--exaggerate", description: "Deformation exaggeration factor; requires --shape deformed", argument: "required", valueLabel: "<x>" },
45207
+ {
45208
+ name: "--exaggerate",
45209
+ description: "Deformation exaggeration factor; requires --shape deformed",
45210
+ argument: "required",
45211
+ valueLabel: "<x>"
45212
+ },
44570
45213
  { name: "--output", description: "Output PNG path", argument: "required", valueLabel: "<path>", valueKind: "path" },
44571
45214
  { name: "--camera", description: "Camera/view label for stress renders", argument: "required", valueLabel: "<view>" },
44572
45215
  { name: "--size", description: "Render image size in pixels", argument: "required", valueLabel: "<px>" },
44573
- { name: "--chrome-path", description: "Chrome/Chromium executable path for PNG rendering", argument: "required", valueLabel: "<path>" }
45216
+ {
45217
+ name: "--chrome-path",
45218
+ description: "Chrome/Chromium executable path for PNG rendering",
45219
+ argument: "required",
45220
+ valueLabel: "<path>"
45221
+ }
44574
45222
  ],
44575
45223
  positionals: [{ description: "FEA study result directory", valueKind: "directory" }]
44576
45224
  },
@@ -44585,10 +45233,20 @@ var commands = [
44585
45233
  completion: {
44586
45234
  options: [
44587
45235
  { name: "--output", description: "Output comparison directory", argument: "required", valueLabel: "<dir>", valueKind: "directory" },
44588
- { name: "--field", description: "Comparison field. Only safety is supported so scales stay locked.", argument: "required", valueLabel: "<safety>" },
45236
+ {
45237
+ name: "--field",
45238
+ description: "Comparison field. Only safety is supported so scales stay locked.",
45239
+ argument: "required",
45240
+ valueLabel: "<safety>"
45241
+ },
44589
45242
  { name: "--camera", description: "Shared render camera", argument: "required", valueLabel: "<name>" },
44590
45243
  { name: "--size", description: "Shared render image size in pixels", argument: "required", valueLabel: "<px>" },
44591
- { name: "--chrome-path", description: "Chrome/Chromium executable path for PNG rendering", argument: "required", valueLabel: "<path>" }
45244
+ {
45245
+ name: "--chrome-path",
45246
+ description: "Chrome/Chromium executable path for PNG rendering",
45247
+ argument: "required",
45248
+ valueLabel: "<path>"
45249
+ }
44592
45250
  ],
44593
45251
  positionals: [
44594
45252
  { description: "First FEA study result directory", valueKind: "directory" },
@@ -44607,7 +45265,13 @@ var commands = [
44607
45265
  options: [
44608
45266
  ...PARAM_OPTIONS,
44609
45267
  { name: "--study", description: "Run one named FEA study", argument: "required", valueLabel: "<name>" },
44610
- { name: "--output", description: "Output result root directory", argument: "required", valueLabel: "<dir>", valueKind: "directory" },
45268
+ {
45269
+ name: "--output",
45270
+ description: "Output result root directory",
45271
+ argument: "required",
45272
+ valueLabel: "<dir>",
45273
+ valueKind: "directory"
45274
+ },
44611
45275
  { name: "--json", description: "Emit the FEA result index JSON" },
44612
45276
  { name: "--compact", description: "Minify JSON output" },
44613
45277
  { name: "--no-fail-exit", description: "Always exit 0 after writing the result bundle" }
@@ -45157,10 +45821,50 @@ var commands = [
45157
45821
  },
45158
45822
  run: runContextRenderCli
45159
45823
  },
45824
+ {
45825
+ group: "Project",
45826
+ path: ["manual-edits"],
45827
+ summary: "Read submitted manual parameter edits for a ForgeCAD entry file.",
45828
+ description: "Agent-facing manual edit commands. The browser submits manual path and spline canvas edits when the user chooses Send to AI; these commands let a local agent read the exact point and G-continuity data before updating source defaults.",
45829
+ usage: ["forgecad manual-edits <command>"],
45830
+ examples: ["forgecad manual-edits status main.forge.js", "forgecad manual-edits get main.forge.js --format json"],
45831
+ run: async () => {
45832
+ console.error("`forgecad manual-edits` requires a subcommand.");
45833
+ console.error("");
45834
+ console.error("Available subcommands:");
45835
+ console.error(" forgecad manual-edits get <model.forge.js> [--format text|json]");
45836
+ console.error(" forgecad manual-edits status <model.forge.js> [--format text|json]");
45837
+ process.exit(1);
45838
+ }
45839
+ },
45840
+ {
45841
+ group: "Project",
45842
+ path: ["manual-edits", "get"],
45843
+ summary: "Print the latest submitted manual parameter edits for one entry file.",
45844
+ usage: ["forgecad manual-edits get <model.forge.js> [--format text|json]"],
45845
+ examples: ["forgecad manual-edits get main.forge.js", "forgecad manual-edits get main.forge.js --format json"],
45846
+ completion: {
45847
+ positionals: [{ description: "ForgeCAD entry file", valueKind: "file" }],
45848
+ options: [{ name: "--format", description: "Output format", argument: "required", valueLabel: "<text|json>" }]
45849
+ },
45850
+ run: runManualEditsGetCli
45851
+ },
45852
+ {
45853
+ group: "Project",
45854
+ path: ["manual-edits", "status"],
45855
+ summary: "Show whether one entry file has submitted manual parameter edits.",
45856
+ usage: ["forgecad manual-edits status <model.forge.js> [--format text|json]"],
45857
+ examples: ["forgecad manual-edits status main.forge.js", "forgecad manual-edits status main.forge.js --format json"],
45858
+ completion: {
45859
+ positionals: [{ description: "ForgeCAD entry file", valueKind: "file" }],
45860
+ options: [{ name: "--format", description: "Output format", argument: "required", valueLabel: "<text|json>" }]
45861
+ },
45862
+ run: runManualEditsStatusCli
45863
+ },
45160
45864
  {
45161
45865
  group: "Project",
45162
45866
  path: ["project", "init"],
45163
- summary: "Initialize the current directory as a ForgeCAD project and create it on the server.",
45867
+ summary: "Initialize the current directory as a local ForgeCAD project.",
45164
45868
  usage: ["forgecad project init [name] [--slug <slug>] [--visibility <private|shared|public>]"],
45165
45869
  examples: ["forgecad project init", 'forgecad project init "My Gearbox" --slug my-gearbox'],
45166
45870
  completion: {
@@ -45210,7 +45914,7 @@ var commands = [
45210
45914
  {
45211
45915
  group: "Project",
45212
45916
  path: ["project", "push"],
45213
- summary: "Upload local changes to the remote project.",
45917
+ summary: "Create the hosted project if needed, then upload local changes.",
45214
45918
  usage: ["forgecad project push [--force] [--delete-missing]"],
45215
45919
  examples: ["forgecad project push", "forgecad project push --delete-missing"],
45216
45920
  completion: {
@@ -46384,7 +47088,7 @@ var commands = [
46384
47088
  { name: "--update", description: "Regenerate compiler snapshots" }
46385
47089
  ]
46386
47090
  },
46387
- run: async (args) => (await import("./check-compiler-7YAHVXYM.js")).runCheckCompilerCli(args)
47091
+ run: async (args) => (await import("./check-compiler-UJWUEIDC.js")).runCheckCompilerCli(args)
46388
47092
  },
46389
47093
  {
46390
47094
  group: "Checks",
@@ -46407,7 +47111,7 @@ var commands = [
46407
47111
  { name: "--update", description: "Regenerate query-propagation snapshots" }
46408
47112
  ]
46409
47113
  },
46410
- run: async (args) => (await import("./check-query-propagation-ZRR6IOJW.js")).runCheckQueryPropagationCli(args)
47114
+ run: async (args) => (await import("./check-query-propagation-O2EPDJSY.js")).runCheckQueryPropagationCli(args)
46411
47115
  },
46412
47116
  {
46413
47117
  group: "Checks",
@@ -46825,8 +47529,8 @@ function isOptionToken(value) {
46825
47529
  return /^--?[A-Za-z]/.test(value);
46826
47530
  }
46827
47531
  function supportsEqualsOptionSyntax(command, optionName) {
46828
- const path5 = command.path.join(" ");
46829
- return (path5 === "signup" || path5 === "login") && optionName === "--server" || path5 === "login" && optionName === "--token" || path5 === "skill install" && optionName === "--target";
47532
+ const path7 = command.path.join(" ");
47533
+ return (path7 === "signup" || path7 === "login") && optionName === "--server" || path7 === "login" && optionName === "--token" || path7 === "skill install" && optionName === "--target";
46830
47534
  }
46831
47535
  function validateCommandArgs(command, args) {
46832
47536
  if (command.hidden) return;
@@ -46993,17 +47697,17 @@ function oclifSummaryFor(command) {
46993
47697
  function stripAnsiCodes(value) {
46994
47698
  return value.replace(/\x1b\[[0-9;]*m/g, "");
46995
47699
  }
46996
- function commandPathId(path5) {
46997
- return path5.join(" ");
47700
+ function commandPathId(path7) {
47701
+ return path7.join(" ");
46998
47702
  }
46999
- function findCommandDefinition(path5) {
47000
- const id = commandPathId(path5);
47703
+ function findCommandDefinition(path7) {
47704
+ const id = commandPathId(path7);
47001
47705
  return commands.find((command) => !command.hidden && commandPathId(command.path) === id);
47002
47706
  }
47003
- function commandOrTopicSummary(path5) {
47004
- const command = findCommandDefinition(path5);
47707
+ function commandOrTopicSummary(path7) {
47708
+ const command = findCommandDefinition(path7);
47005
47709
  if (command) return oclifSummaryFor(command);
47006
- return TOPIC_DESCRIPTIONS[commandPathId(path5)];
47710
+ return TOPIC_DESCRIPTIONS[commandPathId(path7)];
47007
47711
  }
47008
47712
  function visibleChildCommands(topicPath) {
47009
47713
  return commands.filter((command) => {
@@ -47051,7 +47755,7 @@ function topicNameForHelp(topic) {
47051
47755
  var ForgeCadHelp = class extends Help {
47052
47756
  async showRootHelp() {
47053
47757
  const groupSections = ROOT_HELP_GROUPS.map(({ heading, paths }) => {
47054
- const rows = paths.map((path5) => [`forgecad ${commandPathId(path5)}`, commandOrTopicSummary(path5)]);
47758
+ const rows = paths.map((path7) => [`forgecad ${commandPathId(path7)}`, commandOrTopicSummary(path7)]);
47055
47759
  return `${heading}
47056
47760
  ${helpRows(rows)}`;
47057
47761
  });