@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.11

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 (224) hide show
  1. package/README.md +10 -10
  2. package/dist/api/board-types.d.ts +1 -1
  3. package/dist/api/config.d.ts +38 -2
  4. package/dist/api/index.d.ts +1 -1
  5. package/dist/api/schema/types.d.ts +15 -0
  6. package/dist/api/shared/async-runtime-static.d.ts +12 -1
  7. package/dist/api/shared/async-runtime-static.js +69 -24
  8. package/dist/api/shared/async-symbol-detector.d.ts +11 -0
  9. package/dist/api/shared/async-symbol-detector.js +140 -0
  10. package/dist/api/shared/async-types.d.ts +24 -0
  11. package/dist/api/shared/coop-scheduler.d.ts +60 -0
  12. package/dist/api/shared/coop-scheduler.js +149 -0
  13. package/dist/api/shared/display-adapter.d.ts +4 -3
  14. package/dist/api/shared/display-adapter.js +15 -118
  15. package/dist/api/shared/display-adapters/sdl.js +38 -31
  16. package/dist/api/shared/display-profile.d.ts +47 -25
  17. package/dist/api/shared/display-profile.js +31 -164
  18. package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
  19. package/dist/api/shared/framework-manifest-registry.js +27 -0
  20. package/dist/api/shared/framework-manifest.d.ts +595 -0
  21. package/dist/api/shared/framework-manifest.js +193 -0
  22. package/dist/api/shared/glcdfont.d.ts +12 -0
  23. package/dist/api/shared/glcdfont.js +124 -0
  24. package/dist/api/shared/graphics-strategy.d.ts +37 -0
  25. package/dist/api/shared/hal-op-ir.d.ts +764 -1
  26. package/dist/api/shared/hal-op-ir.js +129 -1
  27. package/dist/api/shared/index.d.ts +22 -2
  28. package/dist/api/shared/index.js +26 -1
  29. package/dist/api/shared/ir-core.d.ts +4 -0
  30. package/dist/api/shared/ir-declarations.d.ts +6 -0
  31. package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
  32. package/dist/api/shared/native-display-op-resolver.js +64 -0
  33. package/dist/api/shared/platform-strategy.d.ts +119 -2
  34. package/dist/api/shared/polyfill-helper-registry.js +18 -1
  35. package/dist/api/shared/promise-runtime.d.ts +1 -1
  36. package/dist/api/shared/promise-runtime.js +95 -13
  37. package/dist/api/shared/spdx-licenses.d.ts +139 -0
  38. package/dist/api/shared/spdx-licenses.js +385 -0
  39. package/dist/api/shared/toolchain-types.d.ts +17 -0
  40. package/dist/api/shared/types.d.ts +8 -0
  41. package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
  42. package/dist/api/shared/validate-framework-manifest.js +494 -0
  43. package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
  44. package/dist/api/shared/worker-runtime-polyfill.js +34 -0
  45. package/dist/api/shared/worker-runtime.d.ts +69 -0
  46. package/dist/api/shared/worker-runtime.js +163 -0
  47. package/dist/cli-utils.d.ts +1 -0
  48. package/dist/cli-utils.js +3 -1
  49. package/dist/cli.js +249 -32
  50. package/dist/config-loader.d.ts +7 -2
  51. package/dist/config-loader.js +23 -3
  52. package/dist/config-schema.d.ts +112 -70
  53. package/dist/config-schema.js +14 -0
  54. package/dist/contract/board-generator.d.ts +17 -0
  55. package/dist/contract/board-generator.js +57 -0
  56. package/dist/contract/contract-parser.d.ts +217 -0
  57. package/dist/contract/contract-parser.js +224 -0
  58. package/dist/contract/index.d.ts +22 -0
  59. package/dist/contract/index.js +68 -0
  60. package/dist/create/board-codegen.js +4 -4
  61. package/dist/create/board-generators.js +4 -5
  62. package/dist/create/board-spec.d.ts +72 -75
  63. package/dist/create/board-spec.js +0 -1
  64. package/dist/create/framework-catalog.d.ts +64 -0
  65. package/dist/create/framework-catalog.js +136 -0
  66. package/dist/create/index.d.ts +5 -1
  67. package/dist/create/index.js +3 -1
  68. package/dist/create/init-scaffold.d.ts +12 -3
  69. package/dist/create/init-scaffold.js +49 -14
  70. package/dist/create/init-templates.d.ts +4 -0
  71. package/dist/create/init-templates.js +271 -17
  72. package/dist/create/init-wizard.js +41 -27
  73. package/dist/create/install-deps.d.ts +32 -0
  74. package/dist/create/install-deps.js +46 -0
  75. package/dist/debug/preprocessor.js +178 -39
  76. package/dist/debug/types.d.ts +23 -0
  77. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  78. package/dist/diagnostics/mermaid-builder.js +34 -24
  79. package/dist/emit/compliance/arxml-writer.d.ts +11 -0
  80. package/dist/emit/compliance/arxml-writer.js +34 -0
  81. package/dist/emit/compliance/compliance-context.d.ts +57 -0
  82. package/dist/emit/compliance/compliance-context.js +113 -0
  83. package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
  84. package/dist/emit/compliance/deviation-ledger.js +47 -0
  85. package/dist/emit/compliance/deviation-writer.d.ts +30 -0
  86. package/dist/emit/compliance/deviation-writer.js +37 -0
  87. package/dist/emit/compliance/index.d.ts +7 -0
  88. package/dist/emit/compliance/index.js +6 -0
  89. package/dist/emit/compliance/rule-engine.d.ts +13 -0
  90. package/dist/emit/compliance/rule-engine.js +101 -0
  91. package/dist/emit/compliance/rules.d.ts +16 -0
  92. package/dist/emit/compliance/rules.js +191 -0
  93. package/dist/emit/compliance/types.d.ts +59 -0
  94. package/dist/emit/compliance/types.js +8 -0
  95. package/dist/emit/cpp-emitter.js +4 -3
  96. package/dist/emit/emitters/class-emitter.js +6 -1
  97. package/dist/emit/emitters/emitter-context.d.ts +28 -1
  98. package/dist/emit/emitters/function-emitter-impl.js +153 -53
  99. package/dist/emit/emitters/line-appender.js +19 -0
  100. package/dist/emit/emitters/line-marker.d.ts +38 -0
  101. package/dist/emit/emitters/line-marker.js +39 -0
  102. package/dist/emit/emitters/output-finalizer.d.ts +6 -0
  103. package/dist/emit/emitters/output-finalizer.js +101 -12
  104. package/dist/emit/emitters/setup.d.ts +17 -0
  105. package/dist/emit/emitters/setup.js +372 -30
  106. package/dist/emit/emitters/top-level-prep.js +8 -0
  107. package/dist/emit/emitters/type-decl-emitter.js +28 -3
  108. package/dist/emit/emitters/ui-emitter.js +33 -9
  109. package/dist/emit/expression-renderer.d.ts +1 -1
  110. package/dist/emit/expression-renderer.js +91 -6
  111. package/dist/emit/route-hal-op.js +18 -5
  112. package/dist/emit/snprintf-helpers.js +15 -4
  113. package/dist/emit/statement-renderer.d.ts +10 -0
  114. package/dist/emit/statement-renderer.js +34 -5
  115. package/dist/emit/utils/async-state-machine.js +221 -125
  116. package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
  117. package/dist/emit/utils/hal-op-cpp-type.js +40 -0
  118. package/dist/framework-package.js +2 -0
  119. package/dist/framework-registry.d.ts +17 -0
  120. package/dist/install/framework-catalog.d.ts +53 -0
  121. package/dist/install/framework-catalog.js +107 -0
  122. package/dist/install/handle-install.d.ts +35 -0
  123. package/dist/install/handle-install.js +177 -0
  124. package/dist/install/index.d.ts +4 -0
  125. package/dist/install/index.js +3 -0
  126. package/dist/ir/adc-range-validation.js +40 -26
  127. package/dist/ir/build-ir-state.d.ts +1 -0
  128. package/dist/ir/build-ir-state.js +14 -0
  129. package/dist/ir/build-ir.js +9 -5
  130. package/dist/ir/call-graph.js +16 -0
  131. package/dist/ir/expression-to-ir.js +207 -6
  132. package/dist/ir/feature-registry.js +7 -25
  133. package/dist/ir/function-builder.js +22 -0
  134. package/dist/ir/hal/hal-emitter.d.ts +5 -2
  135. package/dist/ir/hal/hal-emitter.js +50 -18
  136. package/dist/ir/hal/hal-parser.d.ts +6 -0
  137. package/dist/ir/hal/hal-parser.js +81 -0
  138. package/dist/ir/hal/hal-plugins.js +733 -1
  139. package/dist/ir/identifier-collector.js +35 -0
  140. package/dist/ir/interrupt-analysis.d.ts +5 -1
  141. package/dist/ir/interrupt-analysis.js +16 -19
  142. package/dist/ir/memory-budget-validation.js +1 -0
  143. package/dist/ir/network-validation.d.ts +4 -0
  144. package/dist/ir/network-validation.js +184 -0
  145. package/dist/ir/ownership-analysis.js +39 -1
  146. package/dist/ir/peripheral-ownership.js +5 -0
  147. package/dist/ir/peripheral-validation.d.ts +1 -1
  148. package/dist/ir/peripheral-validation.js +6 -3
  149. package/dist/ir/pin-alias-conflict.d.ts +1 -1
  150. package/dist/ir/pin-alias-conflict.js +2 -1
  151. package/dist/ir/pin-capability-validation.js +71 -32
  152. package/dist/ir/pin-mode-validation.d.ts +2 -2
  153. package/dist/ir/pin-mode-validation.js +54 -18
  154. package/dist/ir/pin-safety.d.ts +1 -1
  155. package/dist/ir/pin-safety.js +2 -1
  156. package/dist/ir/program-analysis.d.ts +81 -0
  157. package/dist/ir/program-analysis.js +429 -0
  158. package/dist/ir/pulldown-validation.d.ts +1 -1
  159. package/dist/ir/pulldown-validation.js +2 -1
  160. package/dist/ir/pwm-timer-sharing.d.ts +1 -1
  161. package/dist/ir/pwm-timer-sharing.js +2 -1
  162. package/dist/ir/render-expr.js +11 -0
  163. package/dist/ir/resource-analysis.js +2 -0
  164. package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
  165. package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
  166. package/dist/ir/timing-validation.d.ts +6 -1
  167. package/dist/ir/timing-validation.js +61 -13
  168. package/dist/ir/transformers/call-statement.js +105 -0
  169. package/dist/ir/transformers/expressions.js +62 -0
  170. package/dist/ir/transformers/hal-call-resolver.js +21 -0
  171. package/dist/ir/transformers/hal-emit-helpers.js +1 -1
  172. package/dist/ir/transformers/namespace-methods.js +17 -12
  173. package/dist/ir/transformers/ui-reactive.js +2 -2
  174. package/dist/ir/transformers/variables.js +137 -19
  175. package/dist/ir/try-catch-validation.js +2 -0
  176. package/dist/ir/type-resolution.js +20 -2
  177. package/dist/ir/ui-element-auto-wire.js +7 -5
  178. package/dist/ir/unit-suspicion-validation.js +9 -7
  179. package/dist/ir/utils/map-statements.d.ts +4 -0
  180. package/dist/ir/utils/map-statements.js +79 -0
  181. package/dist/ir/validation-orchestrator.js +15 -8
  182. package/dist/ir/worker-analysis.d.ts +10 -0
  183. package/dist/ir/worker-analysis.js +261 -0
  184. package/dist/libdef/c-to-decl.d.ts +27 -0
  185. package/dist/libdef/c-to-decl.js +397 -0
  186. package/dist/libdef/component-decls.d.ts +2 -0
  187. package/dist/libdef/component-decls.js +6 -0
  188. package/dist/libdef/component-discovery.d.ts +43 -0
  189. package/dist/libdef/component-discovery.js +83 -0
  190. package/dist/libdef/cpp-to-decl.d.ts +9 -0
  191. package/dist/libdef/cpp-to-decl.js +72 -0
  192. package/dist/libdef/registry.js +5 -2
  193. package/dist/lint-cache.d.ts +59 -0
  194. package/dist/lint-cache.js +257 -0
  195. package/dist/orchestrator/graph-builder.js +14 -9
  196. package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
  197. package/dist/platform/coop-scheduler-runtime.js +41 -0
  198. package/dist/platform/generic-strategy.d.ts +15 -3
  199. package/dist/platform/generic-strategy.js +49 -4
  200. package/dist/safety/safety-bridge.d.ts +11 -0
  201. package/dist/safety/safety-bridge.js +48 -0
  202. package/dist/safety/sidecar-bridge.d.ts +5 -0
  203. package/dist/safety/sidecar-bridge.js +22 -0
  204. package/dist/safety-hook.d.ts +79 -0
  205. package/dist/safety-hook.js +35 -0
  206. package/dist/testing.d.ts +8 -2
  207. package/dist/testing.js +9 -2
  208. package/dist/transpile.d.ts +3 -0
  209. package/dist/transpile.js +191 -49
  210. package/dist/types.d.ts +22 -3
  211. package/dist/ui-hook.d.ts +17 -3
  212. package/dist/utils/cli.js +123 -2
  213. package/dist/utils/fs.d.ts +13 -0
  214. package/dist/utils/fs.js +50 -0
  215. package/dist/utils/ui.js +3 -1
  216. package/package.json +23 -3
  217. package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
  218. package/dist/api/shared/display-adapters/eink-mono.js +0 -53
  219. package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
  220. package/dist/api/shared/display-adapters/ssd1309.js +0 -136
  221. package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
  222. package/dist/api/shared/display-adapters/st7796.js +0 -132
  223. package/dist/ir/heap-array-validation.d.ts +0 -24
  224. package/dist/ir/heap-array-validation.js +0 -29
package/dist/cli.js CHANGED
@@ -2,18 +2,21 @@
2
2
  import path from "node:path";
3
3
  import fs from "node:fs";
4
4
  import { parseCommandLine, printHelp } from "./utils/cli.js";
5
- import { scaffoldProject, printInitNextSteps, KNOWN_TARGETS } from "./create/index.js";
5
+ import { scaffoldProject, printInitNextSteps, KNOWN_TARGETS, frameworksForTarget, frameworkCatalogEntry, FRAMEWORK_CATALOG, frameworkTargetProfile } from "./create/index.js";
6
6
  import { runInitWizard } from "./create/index.js";
7
+ import { installProjectDependencies } from "./create/install-deps.js";
7
8
  import { generateLibraryDefinitions, transpileFile } from "./transpile.js";
8
- import { generateDecl, generateDeclsForDirectory } from "./libdef/cpp-to-decl.js";
9
+ import { generateDecl, generateDeclsForDirectory, generateComponentDeclsForProject } from "./libdef/cpp-to-decl.js";
9
10
  import { mapCppLocationToTs, readSourceMap, resolveMapPath } from "./mapping/source-map.js";
10
11
  import { compileSource, uploadFirmware, monitorDevice } from "./platform/toolchain.js";
11
12
  import { resolveStrategy } from "./platform/registry.js";
12
13
  import { loadFrameworkPackage } from "./framework-package.js";
13
14
  import { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
14
15
  import { loadCuttlefishConfig, generateVirtualTypeDeclaration } from "./config-loader.js";
16
+ import { generateContractBoard } from "./contract/index.js";
15
17
  import { requireUIHook, hasUIHook } from "./ui-hook.js";
16
18
  import { loadUIEngine } from "./ui/ui-bridge.js";
19
+ import { loadSafetyEngine } from "./safety/safety-bridge.js";
17
20
  import { runWatch, discoverWatchDirs } from "./watch.js";
18
21
  import { runExpectTests, assertTypeScriptInput, printDiagnostics, printMappedCompileErrors } from "./cli-utils.js";
19
22
  import { runPreviewServer } from "./preview/server.js";
@@ -31,6 +34,27 @@ function displayConfigForTranspile(config) {
31
34
  }
32
35
  return display;
33
36
  }
37
+ /**
38
+ * Apply the PSRAM config to the Arduino build: append the `PSRAM={opi|quad}`
39
+ * menu option to the FQBN (so the Arduino core's psramFound()/ps_malloc work
40
+ * at runtime — they're gated on the board menu option, not just the define)
41
+ * and add `-DBOARD_HAS_PSRAM` to the defines (so the framework's PSRAM canvas
42
+ * allocator is compiled in). No-op when psram is unset or the target isn't an
43
+ * Arduino FQBN (the PSRAM= option is Arduino-core-specific).
44
+ */
45
+ function applyPsramToArduinoBuild(buildTarget, defines, psram) {
46
+ if (!psram || !buildTarget || !buildTarget.includes(':')) {
47
+ return { buildTarget, defines: defines ?? {} };
48
+ }
49
+ // Arduino FQBN config options follow the board:option as `key=value` pairs.
50
+ // Append PSRAM= if not already present (don't clobber an explicit override).
51
+ const psramOpt = `PSRAM=${psram}`;
52
+ const adjustedTarget = buildTarget.includes('PSRAM=') ? buildTarget : `${buildTarget}:${psramOpt}`;
53
+ const adjustedDefines = { ...(defines ?? {}) };
54
+ if (!('BOARD_HAS_PSRAM' in adjustedDefines))
55
+ adjustedDefines.BOARD_HAS_PSRAM = '';
56
+ return { buildTarget: adjustedTarget, defines: adjustedDefines };
57
+ }
34
58
  async function handleCreate(options) {
35
59
  const targetId = options.target ?? options.board;
36
60
  const hasTarget = !!targetId;
@@ -40,15 +64,35 @@ async function handleCreate(options) {
40
64
  const available = KNOWN_TARGETS.map(t => ` - ${t.id} (${t.displayName})`).join("\n");
41
65
  throw new Error(`Unknown target '${targetId}'. Available targets:\n${available}`);
42
66
  }
43
- const framework = options.framework ?? target.framework;
44
- const frameworkPackage = target.isNative
45
- ? target.frameworkPackage
46
- : framework === 'avr'
47
- ? '@typecad/framework-avr'
48
- : framework === 'arduino'
49
- ? '@typecad/framework-arduino'
50
- : `@typecad/framework-${framework}`;
67
+ // Resolve the framework. --framework wins; otherwise narrow via the catalog
68
+ // for the chosen board and auto-pick when exactly one is compatible.
69
+ let frameworkId;
70
+ let frameworkPackage;
71
+ if (options.framework) {
72
+ const requested = frameworkCatalogEntry(options.framework);
73
+ if (!requested) {
74
+ const available = FRAMEWORK_CATALOG.filter(f => f.installable).map(f => f.id).join(", ");
75
+ throw new Error(`Unknown framework '${options.framework}'. Available: ${available}`);
76
+ }
77
+ frameworkId = requested.id;
78
+ frameworkPackage = requested.packageName;
79
+ }
80
+ else {
81
+ const compatible = frameworksForTarget(target).filter(f => f.installable);
82
+ if (compatible.length === 1) {
83
+ frameworkId = compatible[0].id;
84
+ frameworkPackage = compatible[0].packageName;
85
+ }
86
+ else {
87
+ const list = compatible.map(f => f.id).join(", ");
88
+ throw new Error(`Target '${target.id}' is compatible with multiple frameworks (${list}). ` +
89
+ `Pass --framework <id> to choose one.`);
90
+ }
91
+ }
51
92
  const projectName = options.projectName || 'my-project';
93
+ // Framework-specific build target + toolchain (Zephyr board id + 'west' vs
94
+ // the Arduino FQBN + 'arduino-cli').
95
+ const profile = frameworkTargetProfile(target, frameworkId);
52
96
  const result = scaffoldProject({
53
97
  projectName,
54
98
  targetId: target.id,
@@ -57,18 +101,17 @@ async function handleCreate(options) {
57
101
  architecture: target.architecture,
58
102
  boardPackage: target.boardPackage,
59
103
  frameworkPackage,
60
- framework,
61
- buildTarget: target.buildTarget,
104
+ framework: frameworkId,
105
+ buildTarget: profile.buildTarget ?? target.buildTarget,
106
+ ...(profile.toolchainType ? { toolchainType: profile.toolchainType } : {}),
62
107
  mcu: target.mcu,
63
108
  baudRate: target.isNative ? undefined : (options.baud ?? 9600),
64
109
  includeSketch: !options.noSketch,
110
+ ...(target.frameworkData
111
+ ? { frameworkData: target.frameworkData }
112
+ : {}),
65
113
  }, options.outDir);
66
- console.log(`\n${chalk.green("✓")} Created project files:`);
67
- for (const file of result.createdFiles) {
68
- const relative = path.relative(process.cwd(), file);
69
- console.log(` ${chalk.dim(relative || file)}`);
70
- }
71
- printInitNextSteps(result.options, result.outDir);
114
+ finalizeCreate(result, options);
72
115
  }
73
116
  else {
74
117
  const wizardResult = await runInitWizard({
@@ -83,13 +126,37 @@ async function handleCreate(options) {
83
126
  return;
84
127
  }
85
128
  const result = scaffoldProject(wizardResult, options.outDir);
86
- console.log(`\n${chalk.green("✓")} Created project files:`);
87
- for (const file of result.createdFiles) {
88
- const relative = path.relative(process.cwd(), file);
89
- console.log(` ${chalk.dim(relative || file)}`);
129
+ finalizeCreate(result, options);
130
+ }
131
+ }
132
+ /**
133
+ * Shared tail of `cuttlefish create`: list the created files, install the new
134
+ * project's dependencies (so it's ready to build with no extra step — skipped
135
+ * via --no-install), and print the next-steps. A failed install is non-fatal:
136
+ * the scaffold itself is valid, so we warn and point at the manual command
137
+ * rather than undoing anything.
138
+ */
139
+ function finalizeCreate(result, options) {
140
+ console.log(`\n${chalk.green("✓")} Created project files:`);
141
+ for (const file of result.createdFiles) {
142
+ const relative = path.relative(process.cwd(), file);
143
+ console.log(` ${chalk.dim(relative || file)}`);
144
+ }
145
+ let installed = false;
146
+ if (!options.noInstall) {
147
+ try {
148
+ const { pm } = installProjectDependencies({ projectDir: result.outDir });
149
+ installed = true;
150
+ console.log(`\n${chalk.green("✓")} Installed dependencies via ${pm}.`);
151
+ }
152
+ catch (err) {
153
+ const message = err instanceof Error ? err.message : String(err);
154
+ const relativeDir = path.relative(process.cwd(), result.outDir) || '.';
155
+ console.log(`\n${chalk.yellow("!")} Could not install dependencies automatically: ${message}`);
156
+ console.log(chalk.dim(` Run '${chalk.white("npm install")}' manually in ${relativeDir} when ready.`));
90
157
  }
91
- printInitNextSteps(result.options, result.outDir);
92
158
  }
159
+ printInitNextSteps(result.options, result.outDir, { installed });
93
160
  }
94
161
  async function handleBoardAdd(options) {
95
162
  const { scaffoldBoardPackages, parseBoardSpec, generateFrameworkChecklist } = await import("./create/index.js");
@@ -154,6 +221,38 @@ async function main() {
154
221
  }
155
222
  return;
156
223
  }
224
+ if (options.command === "doctor" || options.command === "licenses") {
225
+ // These commands run standalone (often before a build), so the framework
226
+ // isn't loaded yet. Load it from the config's framework field so the
227
+ // framework-supplied doctor/licenses handlers are available.
228
+ if (!hasLoadedFramework()) {
229
+ const config = loadCuttlefishConfig(process.cwd());
230
+ if (config?.framework) {
231
+ try {
232
+ loadFrameworkPackage(config.framework, process.cwd());
233
+ }
234
+ catch {
235
+ // Framework package not resolvable — fall through to the
236
+ // no-support message below rather than crashing the command.
237
+ }
238
+ }
239
+ }
240
+ const fw = hasLoadedFramework() ? getLoadedFramework() : undefined;
241
+ if (options.command === "doctor") {
242
+ if (!fw?.doctor) {
243
+ ui.printInfo("This framework provides no doctor support.");
244
+ return;
245
+ }
246
+ fw.doctor();
247
+ return;
248
+ }
249
+ if (!fw?.licenses) {
250
+ ui.printInfo("This framework provides no licenses support.");
251
+ return;
252
+ }
253
+ fw.licenses(options.strict ?? false, options.all ?? false);
254
+ return;
255
+ }
157
256
  // ── Handle build command — entry point comes from config ──────────
158
257
  if (options.command === "build") {
159
258
  const buildConfig = loadCuttlefishConfig(process.cwd());
@@ -184,6 +283,37 @@ async function main() {
184
283
  // it intentionally has no inputFile (it scans scanDir instead), so the
185
284
  // generic "Missing input file path" check would otherwise block it.
186
285
  if (options.command === "gen-decls") {
286
+ // Check for --components flag (ESP-IDF components: managed + local).
287
+ // Runs before scanDir/single-file branches; components mode has no
288
+ // inputFile by design (it scans managed_components/ + components/).
289
+ const componentsDir = options.componentsDir;
290
+ if (componentsDir) {
291
+ ui.printHeader();
292
+ ui.printStep(`Generating component declarations for ${componentsDir}...`);
293
+ const config = loadCuttlefishConfig(componentsDir);
294
+ const frameworkConfig = (config?.frameworkConfig ?? {});
295
+ const componentsNode = frameworkConfig?.components ?? {};
296
+ const managedSpecs = Object.keys(componentsNode.managed ?? {});
297
+ // idf.py stores managed deps as <namespace>__<name> (slashes → __).
298
+ const managedNames = managedSpecs.map((spec) => spec.replace("/", "__"));
299
+ const localPaths = (componentsNode.local ?? []).map((p) => path.isAbsolute(p) ? p : path.resolve(componentsDir, p));
300
+ const builtinNames = componentsNode.builtin ?? [];
301
+ const created = generateComponentDeclsForProject(componentsDir, {
302
+ managed: managedNames,
303
+ local: localPaths,
304
+ builtin: [],
305
+ idfRoot: undefined,
306
+ });
307
+ if (created.length === 0) {
308
+ ui.printInfo("No component declaration files created.");
309
+ }
310
+ else {
311
+ ui.printSuccess(`Created ${created.length} declaration file(s):`);
312
+ for (const f of created)
313
+ ui.printFileCreated(f);
314
+ }
315
+ return;
316
+ }
187
317
  // Check for --all flag (scan directory)
188
318
  const scanDir = options.scanDir;
189
319
  if (scanDir) {
@@ -255,8 +385,56 @@ async function main() {
255
385
  // ── Load cuttlefish.config.ts (config wins over CLI flags) ──────────
256
386
  const inputDir = path.dirname(path.resolve(options.inputFile));
257
387
  const config = loadCuttlefishConfig(inputDir);
388
+ // ── Contract-based board narrowing ──────────────────────────────────
389
+ // If the config names a TypeCAD contract (*.contract.json from typecad.net),
390
+ // generate the narrowed `.cuttlefish/board.ts` BEFORE env.d.ts is written
391
+ // (it emits `export * from './board.js'` for this case) and before the
392
+ // board package is resolved downstream. The generated board re-exports
393
+ // only the pins/peripherals the PCB actually wires.
394
+ if (config?.contract) {
395
+ await generateContractBoard(config);
396
+ }
397
+ // ── Pre-transpile: generate component .d.ts stubs ───────────────────
398
+ // The transpile type-check needs the .d.ts imports in main.ts to resolve.
399
+ // For ESP-IDF builtin/managed/local components, those .d.ts files are
400
+ // generated by gen-decls — so we must run it BEFORE type-checking, not
401
+ // only as part of --compile. The cache is written under the transpile
402
+ // output dir (the same path --compile uses), so user imports resolve
403
+ // identically in both flows.
404
+ if (config?.frameworkConfig) {
405
+ const fc = config.frameworkConfig;
406
+ const componentsNode = fc?.components ?? {};
407
+ const managedSpecs = Object.keys(componentsNode.managed ?? {});
408
+ const localPaths = (componentsNode.local ?? []).map((p) => path.isAbsolute(p) ? p : path.resolve(inputDir, p));
409
+ const builtinNames = componentsNode.builtin ?? [];
410
+ if (managedSpecs.length > 0 || localPaths.length > 0 || builtinNames.length > 0) {
411
+ // Resolve the transpile output dir — the cache lives under it so the
412
+ // framework's compile path and this pre-transpile pass agree on location.
413
+ // output.outDir in config is relative to the project root (inputDir);
414
+ // if absent, the transpile output goes alongside the entry file.
415
+ const outBase = config.outputOutDir
416
+ ? path.resolve(inputDir, config.outputOutDir)
417
+ : inputDir;
418
+ try {
419
+ generateComponentDeclsForProject(outBase, {
420
+ managed: managedSpecs.map((s) => s.replace('/', '__')),
421
+ local: localPaths,
422
+ builtin: [],
423
+ idfRoot: undefined,
424
+ });
425
+ }
426
+ catch {
427
+ // Non-fatal: if gen-decls fails (e.g. component not yet fetched),
428
+ // the type-checker will surface the missing-import errors with
429
+ // clearer context than crashing here.
430
+ }
431
+ }
432
+ }
258
433
  // Load the UI engine (if @typecad/ui is installed) before any UI work.
259
434
  await loadUIEngine();
435
+ // Load the safety engine (if @typecad/safety is installed) before any
436
+ // safety work.
437
+ await loadSafetyEngine();
260
438
  let effectivePlatformContext = options.platformContext;
261
439
  let effectiveTarget = options.target;
262
440
  let effectiveOutDir = options.outDir;
@@ -277,9 +455,16 @@ async function main() {
277
455
  }
278
456
  let configBuildTarget = config.buildTarget;
279
457
  if (configBuildTarget) {
458
+ // Reconstruct frameworkData from buildTarget so the toolchain sees the
459
+ // full frameworkData, not just buildTarget.
280
460
  effectivePlatformContext = {
281
461
  architecture: configBuildTarget.split(":")?.[1]?.toLowerCase(),
282
- frameworkData: { buildTarget: configBuildTarget },
462
+ frameworkData: {
463
+ buildTarget: configBuildTarget,
464
+ // Thread the PSRAM type through to the framework so it can emit the
465
+ // PSRAM-enabling Kconfig (Zephyr) / define + FQBN option (Arduino).
466
+ ...(config.psram ? { psram: config.psram } : {}),
467
+ },
283
468
  };
284
469
  }
285
470
  if (config.outputOutDir && !options.outDir) {
@@ -358,15 +543,21 @@ async function main() {
358
543
  diagnostics: options.diagnostics,
359
544
  display: displayConfigForTranspile(config),
360
545
  projectRoot: options.projectRoot,
546
+ autosar: options.autosar,
547
+ autosarArxml: options.autosarArxml,
361
548
  });
362
549
  printDiagnostics(result.diagnostics);
363
550
  ui.printTasks(result.asyncTaskNames ?? [], result.usesTimers ?? false);
551
+ if (result.diagnosticsReportPath) {
552
+ ui.printInfo(`Diagnostics report: ${result.diagnosticsReportPath}`);
553
+ }
364
554
  if (result.diagnostics.length === 0) {
365
555
  ui.printSuccess();
366
556
  initialBuildOk = true;
367
557
  // Initial compile + upload if flags are set
368
558
  if (options.compile) {
369
- const buildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
559
+ const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
560
+ const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
370
561
  const watchOpts = {
371
562
  outputDir: path.dirname(result.sourcePath),
372
563
  sourcePath: result.sourcePath,
@@ -375,8 +566,12 @@ async function main() {
375
566
  baud: options.baud ?? config?.console?.baudRate,
376
567
  optimize: config?.outputOptimize,
377
568
  extraFlags: config?.outputExtraFlags,
378
- defines: config?.outputDefines,
569
+ defines: psramDefines,
570
+ psram: config?.psram,
379
571
  frameworkConfig: config?.frameworkConfig,
572
+ zephyrConfig: config?.zephyrConfig,
573
+ display: displayConfigForTranspile(config),
574
+ debug: options.debug,
380
575
  };
381
576
  ui.printCompiling(buildTarget ?? "native");
382
577
  const compileResult = compileSource(watchOpts);
@@ -448,16 +643,22 @@ async function main() {
448
643
  diagnostics: options.diagnostics,
449
644
  display: displayConfigForTranspile(config),
450
645
  projectRoot: options.projectRoot,
646
+ autosar: options.autosar,
647
+ autosarArxml: options.autosarArxml,
451
648
  });
452
649
  printDiagnostics(rebuildResult.diagnostics);
453
650
  ui.printTasks(rebuildResult.asyncTaskNames ?? [], rebuildResult.usesTimers ?? false);
651
+ if (rebuildResult.diagnosticsReportPath) {
652
+ ui.printInfo(`Diagnostics report: ${rebuildResult.diagnosticsReportPath}`);
653
+ }
454
654
  if (rebuildResult.diagnostics.length > 0) {
455
655
  // Errors shown via printDiagnostics — skip compile/upload
456
656
  }
457
657
  else {
458
658
  ui.printSuccess();
459
659
  if (options.compile) {
460
- const buildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
660
+ const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
661
+ const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
461
662
  const rebuildOpts = {
462
663
  outputDir: path.dirname(rebuildResult.sourcePath),
463
664
  sourcePath: rebuildResult.sourcePath,
@@ -466,8 +667,12 @@ async function main() {
466
667
  baud: options.baud ?? config?.console?.baudRate,
467
668
  optimize: config?.outputOptimize,
468
669
  extraFlags: config?.outputExtraFlags,
469
- defines: config?.outputDefines,
670
+ defines: psramDefines,
671
+ psram: config?.psram,
470
672
  frameworkConfig: config?.frameworkConfig,
673
+ zephyrConfig: config?.zephyrConfig,
674
+ display: displayConfigForTranspile(config),
675
+ debug: options.debug,
471
676
  };
472
677
  ui.printCompiling(buildTarget ?? "native");
473
678
  const compileResult = compileSource(rebuildOpts);
@@ -532,9 +737,14 @@ async function main() {
532
737
  diagnostics: options.diagnostics,
533
738
  display: displayConfigForTranspile(config) ?? options.display,
534
739
  projectRoot: options.projectRoot,
740
+ autosar: options.autosar,
741
+ autosarArxml: options.autosarArxml,
535
742
  });
536
743
  printDiagnostics(result.diagnostics);
537
744
  ui.printTasks(result.asyncTaskNames ?? [], result.usesTimers ?? false);
745
+ if (result.diagnosticsReportPath) {
746
+ ui.printInfo(`Diagnostics report: ${result.diagnosticsReportPath}`);
747
+ }
538
748
  }
539
749
  if (hasFatalDiagnostics(result)) {
540
750
  ui.printError("Transpilation failed. Fix the transpiler diagnostics above before compiling.");
@@ -555,7 +765,8 @@ async function main() {
555
765
  return;
556
766
  }
557
767
  // --compile (delegates to the active framework's toolchain)
558
- const buildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
768
+ const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
769
+ const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
559
770
  const toolchainOpts = {
560
771
  outputDir: path.dirname(result.sourcePath),
561
772
  sourcePath: result.sourcePath,
@@ -564,8 +775,12 @@ async function main() {
564
775
  baud: options.baud ?? config?.console?.baudRate,
565
776
  optimize: config?.outputOptimize,
566
777
  extraFlags: config?.outputExtraFlags,
567
- defines: config?.outputDefines,
778
+ defines: psramDefines,
779
+ psram: config?.psram,
568
780
  frameworkConfig: config?.frameworkConfig,
781
+ zephyrConfig: config?.zephyrConfig,
782
+ display: displayConfigForTranspile(config),
783
+ debug: options.debug,
569
784
  };
570
785
  ui.printCompiling(buildTarget ?? "native");
571
786
  let compileResult;
@@ -616,8 +831,10 @@ async function main() {
616
831
  return;
617
832
  }
618
833
  if (options.monitor) {
619
- // --monitor (blocks until Ctrl+C)
620
- ui.printMonitoring(port, options.baud);
834
+ // --monitor (blocks until Ctrl+C). Show the resolved baud (config ?? --baud)
835
+ // in the banner — toolchainOpts.baud is built the same way, so what the
836
+ // user sees is what the framework monitor opens the port at.
837
+ ui.printMonitoring(port, toolchainOpts.baud ?? 115200);
621
838
  monitorDevice(toolchainOpts);
622
839
  return;
623
840
  }
@@ -13,7 +13,7 @@ export interface ResolvedCuttlefishConfig {
13
13
  contract?: string;
14
14
  /** Build target identifier (e.g. FQBN for Arduino CLI). */
15
15
  buildTarget?: string;
16
- /** Output framework (e.g. 'arduino', 'platformio'). */
16
+ /** Output framework (e.g. 'arduino'). */
17
17
  outputFramework?: string;
18
18
  /** Optimization level. */
19
19
  outputOptimize?: string;
@@ -21,9 +21,12 @@ export interface ResolvedCuttlefishConfig {
21
21
  outputOutDir?: string;
22
22
  /**
23
23
  * Framework package for code generation strategy.
24
- * Can be '@typecad/framework-arduino', '@typecad/framework-avr', or a custom path.
24
+ * Can be '@typecad/framework-arduino', '@typecad/framework-zephyr',
25
+ * '@typecad/framework-native', or a custom path.
25
26
  */
26
27
  framework?: string;
28
+ /** ESP32 PSRAM type ('opi' | 'quad') when the target board has PSRAM. */
29
+ psram?: 'opi' | 'quad';
27
30
  /** Entry point TypeScript file (relative to config file directory). */
28
31
  entry?: string;
29
32
  /** Path to the config file that was loaded. */
@@ -39,6 +42,8 @@ export interface ResolvedCuttlefishConfig {
39
42
  outputDefines?: Record<string, string>;
40
43
  /** Framework-specific config (e.g. `native` section). */
41
44
  frameworkConfig?: Record<string, unknown>;
45
+ /** Zephyr-specific config (e.g. `zephyr` section). */
46
+ zephyrConfig?: Record<string, unknown>;
42
47
  /** Display profile config. */
43
48
  display?: import("./api/shared/display-profile.js").DisplayConfig;
44
49
  }
@@ -297,6 +297,9 @@ export function parseConfigFile(configPath) {
297
297
  const framework = flat.get("framework");
298
298
  if (typeof framework === "string")
299
299
  resolved.framework = framework;
300
+ const psram = flat.get("psram");
301
+ if (psram === "opi" || psram === "quad")
302
+ resolved.psram = psram;
300
303
  const outputFramework = flat.get("output.framework");
301
304
  if (typeof outputFramework === "string")
302
305
  resolved.outputFramework = outputFramework;
@@ -323,8 +326,20 @@ export function parseConfigFile(configPath) {
323
326
  if (outputDefines)
324
327
  resolved.outputDefines = outputDefines;
325
328
  const nativeSection = extractFrameworkSection(configObject, "native");
326
- if (nativeSection)
329
+ if (nativeSection) {
327
330
  resolved.frameworkConfig = nativeSection;
331
+ }
332
+ // Parse zephyr-specific config section.
333
+ const zephyrKconfig = extractStringRecord(configObject, ["zephyr", "kconfig"]);
334
+ const zephyrCmakeArgs = extractStringArray(configObject, ["zephyr", "cmakeArgs"]);
335
+ const zephyrRunner = flat.get("zephyr.runner");
336
+ if (zephyrKconfig || zephyrCmakeArgs || typeof zephyrRunner === "string") {
337
+ resolved.zephyrConfig = {
338
+ ...(zephyrKconfig ? { kconfig: zephyrKconfig } : {}),
339
+ ...(zephyrCmakeArgs ? { cmakeArgs: zephyrCmakeArgs } : {}),
340
+ ...(typeof zephyrRunner === "string" ? { runner: zephyrRunner } : {}),
341
+ };
342
+ }
328
343
  // Parse display profile config (nested object with profile name, wiring, touch)
329
344
  const displaySection = extractFrameworkSection(configObject, "display");
330
345
  if (displaySection)
@@ -344,6 +359,8 @@ export function parseConfigFile(configPath) {
344
359
  structuredForValidation.entry = resolved.entry;
345
360
  if (resolved.framework)
346
361
  structuredForValidation.framework = resolved.framework;
362
+ if (resolved.psram)
363
+ structuredForValidation.psram = resolved.psram;
347
364
  if (resolved.outputFramework || resolved.outputOptimize || resolved.outputOutDir || resolved.outputExtraFlags || resolved.outputDefines) {
348
365
  structuredForValidation.output = {
349
366
  ...(resolved.outputFramework ? { framework: resolved.outputFramework } : {}),
@@ -355,6 +372,8 @@ export function parseConfigFile(configPath) {
355
372
  }
356
373
  if (resolved.console)
357
374
  structuredForValidation.console = resolved.console;
375
+ if (resolved.zephyrConfig)
376
+ structuredForValidation.zephyr = resolved.zephyrConfig;
358
377
  if (resolved.buildTarget) {
359
378
  structuredForValidation.frameworkData = { buildTarget: resolved.buildTarget };
360
379
  }
@@ -478,8 +497,9 @@ export function generateVirtualTypeDeclaration(config, platformDeclarations) {
478
497
  // `declare module` in cuttlefish-env.d.ts is treated as module augmentation
479
498
  // (because that file has `export {}`), which can't define a new module.
480
499
  //
481
- // For contract-based configs, board.ts is generated by the board-codegen
482
- // system, so don't overwrite it here.
500
+ // For contract-based configs, the narrowed board.ts is generated by the
501
+ // contract reader (`contract/board-generator.ts`, invoked from cli.ts before
502
+ // this function runs), so don't overwrite it with the generic re-export here.
483
503
  if (!config.contract && (config.board || config.mcu)) {
484
504
  const reExportSource = config.board ?? config.mcu;
485
505
  const boardTsPath = path.join(cuttlefishDir, "board.ts");