@typecad/cuttlefish 1.0.0-alpha.14 → 1.0.0-alpha.15

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 (315) hide show
  1. package/README.md +82 -79
  2. package/assets/editor-extensions/typecad-debug/README.md +25 -26
  3. package/dist/api/config.d.ts +22 -26
  4. package/dist/api/schema/types.d.ts +20 -3
  5. package/dist/api/shared/async-runtime-static.d.ts +1 -1
  6. package/dist/api/shared/async-runtime-static.js +5 -5
  7. package/dist/api/shared/async-types.d.ts +3 -2
  8. package/dist/api/shared/board-catalog.d.ts +2 -0
  9. package/dist/api/shared/board-catalog.js +6 -0
  10. package/dist/api/shared/board-resolver.d.ts +4 -7
  11. package/dist/api/shared/board-resolver.js +2 -1
  12. package/dist/api/shared/coop-scheduler.d.ts +1 -1
  13. package/dist/api/shared/coop-scheduler.js +1 -1
  14. package/dist/api/shared/cpp-type-ir.d.ts +1 -1
  15. package/dist/api/shared/cpp-type-ir.js +1 -1
  16. package/dist/api/shared/display-profile.d.ts +1 -1
  17. package/dist/api/shared/display-profile.js +3 -5
  18. package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
  19. package/dist/api/shared/framework-manifest-registry.js +0 -1
  20. package/dist/api/shared/framework-manifest.d.ts +7 -6
  21. package/dist/api/shared/framework-manifest.js +6 -11
  22. package/dist/api/shared/hal-op-ir.d.ts +294 -685
  23. package/dist/api/shared/hal-op-ir.js +31 -65
  24. package/dist/api/shared/index.d.ts +0 -4
  25. package/dist/api/shared/index.js +1 -7
  26. package/dist/api/shared/ir-core.d.ts +6 -0
  27. package/dist/api/shared/ir-declarations.d.ts +4 -0
  28. package/dist/api/shared/ir.d.ts +1 -3
  29. package/dist/api/shared/platform-strategy.d.ts +14 -37
  30. package/dist/api/shared/polyfill-helper-registry.js +0 -5
  31. package/dist/api/shared/polyfill-types.js +0 -27
  32. package/dist/api/shared/promise-runtime.js +3 -3
  33. package/dist/api/shared/spdx-licenses.d.ts +12 -10
  34. package/dist/api/shared/spdx-licenses.js +1 -1
  35. package/dist/api/shared/toolchain-types.d.ts +1 -1
  36. package/dist/api/shared/toolchain-types.js +3 -3
  37. package/dist/api/shared/types.d.ts +0 -3
  38. package/dist/api/shared/validate-framework-manifest.js +13 -33
  39. package/dist/board-catalog/dts-reader.d.ts +220 -0
  40. package/dist/board-catalog/dts-reader.js +1052 -0
  41. package/dist/board-catalog/index.d.ts +11 -0
  42. package/dist/board-catalog/index.js +12 -0
  43. package/dist/board-catalog/pinconfig.d.ts +48 -0
  44. package/dist/board-catalog/pinconfig.js +263 -0
  45. package/dist/board-catalog/sdk.d.ts +82 -0
  46. package/dist/board-catalog/sdk.js +180 -0
  47. package/dist/board-catalog/store.d.ts +196 -0
  48. package/dist/board-catalog/store.js +363 -0
  49. package/dist/board-catalog/types.d.ts +233 -0
  50. package/dist/board-catalog/types.js +7 -0
  51. package/dist/board-catalog/walker.d.ts +112 -0
  52. package/dist/board-catalog/walker.js +1101 -0
  53. package/dist/cli-utils.d.ts +2 -2
  54. package/dist/cli-utils.js +5 -5
  55. package/dist/cli.js +214 -242
  56. package/dist/config-loader.d.ts +14 -19
  57. package/dist/config-loader.js +240 -78
  58. package/dist/config-schema.d.ts +3 -5
  59. package/dist/config-schema.js +8 -8
  60. package/dist/contract/board-generator.d.ts +4 -4
  61. package/dist/contract/board-generator.js +16 -5
  62. package/dist/contract/contract-parser.d.ts +27 -0
  63. package/dist/contract/contract-parser.js +76 -0
  64. package/dist/contract/index.d.ts +1 -1
  65. package/dist/contract/index.js +70 -21
  66. package/dist/create/active-board-catalog.d.ts +9 -0
  67. package/dist/create/active-board-catalog.js +94 -0
  68. package/dist/create/board-catalog.generated.d.ts +96 -0
  69. package/dist/create/board-catalog.generated.js +128737 -0
  70. package/dist/create/board-search.d.ts +23 -0
  71. package/dist/create/board-search.js +88 -0
  72. package/dist/create/editor-integration.d.ts +4 -2
  73. package/dist/create/editor-integration.js +41 -2
  74. package/dist/create/framework-catalog.d.ts +41 -9
  75. package/dist/create/framework-catalog.js +48 -57
  76. package/dist/create/index.d.ts +5 -11
  77. package/dist/create/index.js +3 -8
  78. package/dist/create/mcu-target.d.ts +39 -0
  79. package/dist/create/mcu-target.js +80 -0
  80. package/dist/create/pack-targets.d.ts +19 -0
  81. package/dist/create/pack-targets.js +56 -0
  82. package/dist/create/scaffold.d.ts +4 -7
  83. package/dist/create/scaffold.js +19 -109
  84. package/dist/create/templates.d.ts +27 -5
  85. package/dist/create/templates.js +120 -116
  86. package/dist/create/wizard.d.ts +12 -1
  87. package/dist/create/wizard.js +163 -38
  88. package/dist/debug/preprocessor.d.ts +5 -0
  89. package/dist/debug/preprocessor.js +31 -7
  90. package/dist/diagnostics/diagnostics-report.d.ts +1 -1
  91. package/dist/diagnostics/diagnostics-report.js +3 -11
  92. package/dist/diagnostics/json-schema.d.ts +1 -1
  93. package/dist/diagnostics/md-writer.js +2 -2
  94. package/dist/diagnostics/mermaid-builder.d.ts +1 -1
  95. package/dist/diagnostics/mermaid-builder.js +1 -1
  96. package/dist/emit/cpp-emitter.js +4 -1
  97. package/dist/emit/emitters/class-emitter.js +32 -4
  98. package/dist/emit/emitters/emitter-context.d.ts +11 -0
  99. package/dist/emit/emitters/entrypoint-synthesizer.js +1 -1
  100. package/dist/emit/emitters/function-emitter-impl.js +33 -17
  101. package/dist/emit/emitters/output-finalizer.d.ts +7 -0
  102. package/dist/emit/emitters/output-finalizer.js +31 -23
  103. package/dist/emit/emitters/setup.js +125 -63
  104. package/dist/emit/emitters/top-level-prep.js +1 -1
  105. package/dist/emit/emitters/type-decl-emitter.js +13 -8
  106. package/dist/emit/emitters/ui-emitter.js +1 -1
  107. package/dist/emit/expression-renderer.d.ts +3 -3
  108. package/dist/emit/expression-renderer.js +17 -7
  109. package/dist/emit/route-hal-op.js +17 -48
  110. package/dist/emit/snprintf-helpers.d.ts +1 -3
  111. package/dist/emit/snprintf-helpers.js +1 -39
  112. package/dist/emit/statement-renderer.d.ts +0 -4
  113. package/dist/emit/statement-renderer.js +54 -45
  114. package/dist/emit/utils/async-state-machine.d.ts +16 -9
  115. package/dist/emit/utils/async-state-machine.js +56 -54
  116. package/dist/emit/utils/comment-helpers.d.ts +0 -8
  117. package/dist/emit/utils/comment-helpers.js +1 -1
  118. package/dist/emit/utils/cpp-helpers.d.ts +2 -2
  119. package/dist/emit/utils/cpp-helpers.js +2 -2
  120. package/dist/emit/utils/hal-op-cpp-type.js +4 -6
  121. package/dist/emit/utils/index.d.ts +3 -3
  122. package/dist/emit/utils/index.js +3 -3
  123. package/dist/emit/utils/type-inference.d.ts +1 -29
  124. package/dist/emit/utils/type-inference.js +0 -75
  125. package/dist/framework-package.d.ts +3 -0
  126. package/dist/framework-package.js +23 -6
  127. package/dist/framework-registry.d.ts +5 -5
  128. package/dist/frameworks/native/graphics/terminal-preview.d.ts +5 -0
  129. package/dist/frameworks/native/graphics/terminal-preview.js +33 -0
  130. package/dist/frameworks/native/index.d.ts +3 -0
  131. package/dist/frameworks/native/index.js +15 -0
  132. package/dist/frameworks/native/native-compile.d.ts +17 -0
  133. package/dist/frameworks/native/native-compile.js +170 -0
  134. package/dist/frameworks/native/native-config.d.ts +41 -0
  135. package/dist/frameworks/native/native-config.js +7 -0
  136. package/dist/frameworks/native/strategy.d.ts +97 -0
  137. package/dist/frameworks/native/strategy.js +664 -0
  138. package/dist/ir/board-resolver.d.ts +18 -23
  139. package/dist/ir/board-resolver.js +43 -401
  140. package/dist/ir/build-ir-state.d.ts +3 -0
  141. package/dist/ir/build-ir-state.js +42 -0
  142. package/dist/ir/build-ir.d.ts +1 -1
  143. package/dist/ir/build-ir.js +40 -42
  144. package/dist/ir/declaration-builders.js +1 -0
  145. package/dist/ir/expression-to-ir.js +17 -10
  146. package/dist/ir/hal/hal-emitter.d.ts +0 -2
  147. package/dist/ir/hal/hal-emitter.js +92 -41
  148. package/dist/ir/hal/hal-parser.d.ts +5 -5
  149. package/dist/ir/hal/hal-parser.js +128 -36
  150. package/dist/ir/hal/hal-plugins.d.ts +0 -1
  151. package/dist/ir/hal/hal-plugins.js +425 -716
  152. package/dist/ir/heap-analysis.js +2 -41
  153. package/dist/ir/identifier-collector.js +32 -20
  154. package/dist/ir/network-validation.js +7 -11
  155. package/dist/ir/ownership-analysis.js +75 -13
  156. package/dist/ir/peripheral-usage.d.ts +8 -2
  157. package/dist/ir/peripheral-usage.js +78 -149
  158. package/dist/ir/peripheral-validation.js +23 -3
  159. package/dist/ir/pin-capability-validation.d.ts +12 -0
  160. package/dist/ir/pin-capability-validation.js +61 -97
  161. package/dist/ir/pin-mode-validation.js +5 -5
  162. package/dist/ir/program-analysis.d.ts +8 -15
  163. package/dist/ir/program-analysis.js +140 -323
  164. package/dist/ir/pulldown-validation.js +11 -4
  165. package/dist/ir/resource-analysis.js +15 -3
  166. package/dist/ir/statement-to-ir.js +0 -6
  167. package/dist/ir/timing-validation.js +1 -1
  168. package/dist/ir/transformers/array-methods.js +11 -5
  169. package/dist/ir/transformers/call-statement.js +20 -87
  170. package/dist/ir/transformers/callback-context-registry.d.ts +1 -1
  171. package/dist/ir/transformers/callback-context-registry.js +8 -16
  172. package/dist/ir/transformers/control-flow.js +0 -13
  173. package/dist/ir/transformers/expressions.js +10 -9
  174. package/dist/ir/transformers/hal-call-resolver.d.ts +2 -2
  175. package/dist/ir/transformers/hal-call-resolver.js +24 -10
  176. package/dist/ir/transformers/hal-emit-helpers.d.ts +2 -2
  177. package/dist/ir/transformers/hal-emit-helpers.js +2 -2
  178. package/dist/ir/transformers/namespace-methods.js +1 -1
  179. package/dist/ir/transformers/ui-callback-lowering.d.ts +0 -2
  180. package/dist/ir/transformers/ui-callback-lowering.js +0 -24
  181. package/dist/ir/transformers/variables.js +338 -42
  182. package/dist/ir/try-catch-validation.js +4 -3
  183. package/dist/ir/type-resolution.js +15 -4
  184. package/dist/ir/validation-orchestrator.js +0 -9
  185. package/dist/libdef/cpp-to-decl.d.ts +0 -9
  186. package/dist/libdef/cpp-to-decl.js +0 -72
  187. package/dist/libdef/registry.d.ts +0 -1
  188. package/dist/libdef/registry.js +1 -24
  189. package/dist/library/init.d.ts +1 -1
  190. package/dist/library/init.js +137 -137
  191. package/dist/library-packages.d.ts +1 -1
  192. package/dist/library-packages.js +1 -1
  193. package/dist/mapping/peripheral-names.js +9 -4
  194. package/dist/mapping/source-map.d.ts +3 -4
  195. package/dist/mapping/source-map.js +12 -18
  196. package/dist/orchestrator/graph-builder.d.ts +3 -3
  197. package/dist/orchestrator/graph-builder.js +13 -12
  198. package/dist/orchestrator/type-checker.d.ts +3 -3
  199. package/dist/orchestrator/type-checker.js +3 -3
  200. package/dist/platform/async-runtime.js +1 -1
  201. package/dist/platform/coop-scheduler-runtime.js +2 -2
  202. package/dist/platform/generic-debug-codegen.d.ts +36 -0
  203. package/dist/platform/generic-debug-codegen.js +185 -0
  204. package/dist/platform/generic-strategy.d.ts +3 -4
  205. package/dist/platform/generic-strategy.js +5 -80
  206. package/dist/platform/index.js +1 -1
  207. package/dist/platform/registry.d.ts +2 -2
  208. package/dist/platform/registry.js +2 -2
  209. package/dist/safety/asil-decorators.d.ts +32 -0
  210. package/dist/safety/asil-decorators.js +32 -0
  211. package/dist/safety/authoring.d.ts +85 -0
  212. package/dist/safety/authoring.js +61 -0
  213. package/dist/safety/engine.d.ts +3 -0
  214. package/dist/safety/engine.js +83 -0
  215. package/dist/safety/hal/ops.d.ts +48 -0
  216. package/dist/safety/hal/ops.js +46 -0
  217. package/dist/safety/iso26262/analyze.d.ts +13 -0
  218. package/dist/safety/iso26262/analyze.js +29 -0
  219. package/dist/safety/iso26262/asil.d.ts +10 -0
  220. package/dist/safety/iso26262/asil.js +22 -0
  221. package/dist/safety/iso26262/collect.d.ts +14 -0
  222. package/dist/safety/iso26262/collect.js +137 -0
  223. package/dist/safety/iso26262/goto-checker.d.ts +10 -0
  224. package/dist/safety/iso26262/goto-checker.js +72 -0
  225. package/dist/safety/iso26262/heap-checker.d.ts +7 -0
  226. package/dist/safety/iso26262/heap-checker.js +57 -0
  227. package/dist/safety/iso26262/init-checker.d.ts +13 -0
  228. package/dist/safety/iso26262/init-checker.js +127 -0
  229. package/dist/safety/iso26262/loop-checker.d.ts +7 -0
  230. package/dist/safety/iso26262/loop-checker.js +86 -0
  231. package/dist/safety/iso26262/recursion-checker.d.ts +9 -0
  232. package/dist/safety/iso26262/recursion-checker.js +134 -0
  233. package/dist/safety/iso26262/sidecar-writer.d.ts +25 -0
  234. package/dist/safety/iso26262/sidecar-writer.js +13 -0
  235. package/dist/safety/passes/pinMode-intercept.d.ts +3 -0
  236. package/dist/safety/passes/pinMode-intercept.js +60 -0
  237. package/dist/safety/runtime/mode-table.d.ts +5 -0
  238. package/dist/safety/runtime/mode-table.js +55 -0
  239. package/dist/safety/runtime/polyfills.d.ts +6 -0
  240. package/dist/safety/runtime/polyfills.js +12 -0
  241. package/dist/safety/runtime/result-struct.d.ts +11 -0
  242. package/dist/safety/runtime/result-struct.js +98 -0
  243. package/dist/safety/runtime/safe-int.d.ts +33 -0
  244. package/dist/safety/runtime/safe-int.js +170 -0
  245. package/dist/safety/runtime/safe-traits.d.ts +4 -0
  246. package/dist/safety/runtime/safe-traits.js +95 -0
  247. package/dist/safety/runtime/safe-variable.d.ts +39 -0
  248. package/dist/safety/runtime/safe-variable.js +226 -0
  249. package/dist/safety/runtime/vote.d.ts +8 -0
  250. package/dist/safety/runtime/vote.js +92 -0
  251. package/dist/safety/runtime/write-verify.d.ts +8 -0
  252. package/dist/safety/runtime/write-verify.js +71 -0
  253. package/dist/safety/safe-int-types.d.ts +45 -0
  254. package/dist/safety/safe-int-types.js +25 -0
  255. package/dist/safety/safe-variable-types.d.ts +29 -0
  256. package/dist/safety/safe-variable-types.js +19 -0
  257. package/dist/safety/safety-bridge.d.ts +5 -6
  258. package/dist/safety/safety-bridge.js +15 -28
  259. package/dist/safety/sidecar-bridge.d.ts +1 -3
  260. package/dist/safety/sidecar-bridge.js +3 -20
  261. package/dist/safety/specifiers.d.ts +6 -0
  262. package/dist/safety/specifiers.js +14 -0
  263. package/dist/safety-hook.d.ts +8 -7
  264. package/dist/safety-hook.js +12 -13
  265. package/dist/testing.d.ts +4 -5
  266. package/dist/testing.js +5 -5
  267. package/dist/transpile/resolution.d.ts +1 -1
  268. package/dist/transpile/resolution.js +18 -9
  269. package/dist/transpile/test-pins.d.ts +18 -0
  270. package/dist/transpile/test-pins.js +142 -0
  271. package/dist/transpile.d.ts +1 -2
  272. package/dist/transpile.js +36 -30
  273. package/dist/types.d.ts +26 -26
  274. package/dist/ui-hook.d.ts +1 -1
  275. package/dist/utils/cli.d.ts +2 -2
  276. package/dist/utils/cli.js +92 -154
  277. package/dist/utils/fs.d.ts +3 -4
  278. package/dist/utils/fs.js +4 -5
  279. package/dist/utils/strings.d.ts +0 -1
  280. package/dist/utils/strings.js +0 -8
  281. package/dist/utils/ui.d.ts +5 -2
  282. package/dist/utils/ui.js +13 -3
  283. package/package.json +158 -157
  284. package/dist/add-preset.d.ts +0 -4
  285. package/dist/add-preset.js +0 -74
  286. package/dist/api/shared/async-symbol-detector.d.ts +0 -11
  287. package/dist/api/shared/async-symbol-detector.js +0 -140
  288. package/dist/api/shared/native-display-op-resolver.d.ts +0 -10
  289. package/dist/api/shared/native-display-op-resolver.js +0 -64
  290. package/dist/create/board-checklist.d.ts +0 -2
  291. package/dist/create/board-checklist.js +0 -52
  292. package/dist/create/board-codegen.d.ts +0 -9
  293. package/dist/create/board-codegen.js +0 -238
  294. package/dist/create/board-generators.d.ts +0 -12
  295. package/dist/create/board-generators.js +0 -651
  296. package/dist/create/board-spec.d.ts +0 -181
  297. package/dist/create/board-spec.js +0 -214
  298. package/dist/install/framework-catalog.d.ts +0 -53
  299. package/dist/install/framework-catalog.js +0 -107
  300. package/dist/install/handle-install.d.ts +0 -35
  301. package/dist/install/handle-install.js +0 -177
  302. package/dist/install/index.d.ts +0 -4
  303. package/dist/install/index.js +0 -3
  304. package/dist/ir/pin-state-tracking.d.ts +0 -58
  305. package/dist/ir/pin-state-tracking.js +0 -182
  306. package/dist/ir/pwm-timer-sharing.d.ts +0 -4
  307. package/dist/ir/pwm-timer-sharing.js +0 -94
  308. package/dist/ir/timer0-pwm-timing-conflict.d.ts +0 -4
  309. package/dist/ir/timer0-pwm-timing-conflict.js +0 -72
  310. package/dist/libdef/component-decls.d.ts +0 -2
  311. package/dist/libdef/component-decls.js +0 -6
  312. package/dist/libdef/component-discovery.d.ts +0 -43
  313. package/dist/libdef/component-discovery.js +0 -83
  314. package/dist/theme-tokens.d.ts +0 -22
  315. package/dist/theme-tokens.js +0 -172
package/dist/cli.js CHANGED
@@ -3,18 +3,19 @@ import path from "node:path";
3
3
  import fs from "node:fs";
4
4
  import { parseCommandLine, printHelp } from "./utils/cli.js";
5
5
  import { runLibraryCommand } from "./library/cli.js";
6
- import { scaffoldProject, printCreateNextSteps, KNOWN_TARGETS, frameworksForTarget, frameworkCatalogEntry, frameworkCompatibleWithTarget, FRAMEWORK_CATALOG, frameworkTargetProfile } from "./create/index.js";
6
+ import { scaffoldProject, printCreateNextSteps, KNOWN_TARGETS, frameworksForTarget, frameworkCatalogEntry, FRAMEWORK_CATALOG, frameworkTargetProfile, probeMethodsForBoard } from "./create/index.js";
7
+ import { findPackBoard, packBoardAsTarget } from "./create/pack-targets.js";
8
+ import { activeBoardCatalog, assertZephyrSdkForCreate, formatZephyrSdkFound, ensureFreshBoardCatalog, resetBoardCatalogOverlayCache, sdkFingerprint, PINNED_ZEPHYR_MANIFEST_REV } from "./board-catalog/index.js";
7
9
  import { generateFrameworkDebugArtifacts } from "./create/debug-artifacts.js";
8
10
  import { runCreateWizard } from "./create/index.js";
9
11
  import { installProjectDependencies } from "./create/install-deps.js";
10
- import { generateLibraryDefinitions, transpileFile } from "./transpile.js";
11
- import { generateDecl, generateDeclsForDirectory, generateComponentDeclsForProject } from "./libdef/cpp-to-decl.js";
12
- import { mapCppLocationToTs, readSourceMap, resolveMapPath } from "./mapping/source-map.js";
12
+ import { transpileFile } from "./transpile.js";
13
+ import { generateDecl, generateDeclsForDirectory } from "./libdef/cpp-to-decl.js";
13
14
  import { compileSource, uploadFirmware, monitorDevice } from "./platform/toolchain.js";
14
15
  import { resolveStrategy } from "./platform/registry.js";
15
16
  import { loadFrameworkPackage } from "./framework-package.js";
16
17
  import { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
17
- import { loadCuttlefishConfig, generateVirtualTypeDeclaration } from "./config-loader.js";
18
+ import { loadCuttlefishConfig, generateVirtualTypeDeclaration, regenBoardModule } from "./config-loader.js";
18
19
  import { generateContractBoard } from "./contract/index.js";
19
20
  import { requireUIHook, hasUIHook } from "./ui-hook.js";
20
21
  import { loadUIEngine } from "./ui/ui-bridge.js";
@@ -36,36 +37,64 @@ function displayConfigForTranspile(config) {
36
37
  }
37
38
  return display;
38
39
  }
39
- /**
40
- * Apply the PSRAM config to the Arduino build: append the `PSRAM={opi|quad}`
41
- * menu option to the FQBN (so the Arduino core's psramFound()/ps_malloc work
42
- * at runtime — they're gated on the board menu option, not just the define)
43
- * and add `-DBOARD_HAS_PSRAM` to the defines (so the framework's PSRAM canvas
44
- * allocator is compiled in). No-op when psram is unset or the target isn't an
45
- * Arduino FQBN (the PSRAM= option is Arduino-core-specific).
46
- */
47
- function applyPsramToArduinoBuild(buildTarget, defines, psram) {
48
- if (!psram || !buildTarget || !buildTarget.includes(':')) {
49
- return { buildTarget, defines: defines ?? {} };
50
- }
51
- // Arduino FQBN config options follow the board:option as `key=value` pairs.
52
- // Append PSRAM= if not already present (don't clobber an explicit override).
53
- const psramOpt = `PSRAM=${psram}`;
54
- const adjustedTarget = buildTarget.includes('PSRAM=') ? buildTarget : `${buildTarget}:${psramOpt}`;
55
- const adjustedDefines = { ...(defines ?? {}) };
56
- if (!('BOARD_HAS_PSRAM' in adjustedDefines))
57
- adjustedDefines.BOARD_HAS_PSRAM = '';
58
- return { buildTarget: adjustedTarget, defines: adjustedDefines };
59
- }
60
40
  async function handleCreate(options) {
61
41
  const targetId = options.target ?? options.board;
62
42
  const hasTarget = !!targetId;
63
- if (hasTarget) {
64
- const target = KNOWN_TARGETS.find(t => t.id === targetId);
43
+ // SDK gate: the installed Zephyr SDK is the source of truth — boards, pin
44
+ // data, everything derives from it. No SDK (or a wrong-version one) means
45
+ // the project cannot work; fail with the fix instead of scaffolding junk.
46
+ // Native-desktop projects are the one exception (no Zephyr involved).
47
+ const wantsNative = options.framework === "native" || (!options.framework && targetId === "native");
48
+ if (!wantsNative) {
49
+ // The gate doubles as the lookup — its ok-return carries everything the
50
+ // printout needs (no second discovery pass).
51
+ const sdk = assertZephyrSdkForCreate();
52
+ if (sdk) {
53
+ console.log(chalk.gray(' Zephyr SDK:'));
54
+ for (const line of formatZephyrSdkFound(sdk))
55
+ console.log(chalk.gray(line));
56
+ }
57
+ // Build/refresh the board registry from the installed SDK now — the
58
+ // wizard's board list and --board resolution below read it.
59
+ ensureFreshBoardCatalog();
60
+ resetBoardCatalogOverlayCache();
61
+ }
62
+ // Preseeded targets run non-interactively ONLY when stdin is not a TTY
63
+ // (scripts/CI pipe nothing and expect zero prompts). A human typing
64
+ // `cuttlefish create x --board <target>` still gets the wizard — it
65
+ // prints the preseeded answers and asks only what was not specified
66
+ // (probe method, serial port, baud, starter).
67
+ const interactive = process.stdin.isTTY === true;
68
+ if (hasTarget && !interactive) {
69
+ // Target resolution: --target/--board names the native target or any
70
+ // board from the catalog (a qualified target like
71
+ // 'nucleo_f411re/stm32f411xe', or a bare board id).
72
+ let target = KNOWN_TARGETS.find(t => t.id === targetId)
73
+ ?? (() => {
74
+ const pack = findPackBoard(targetId);
75
+ return pack ? packBoardAsTarget(pack) : undefined;
76
+ })();
65
77
  if (!target) {
66
- const available = KNOWN_TARGETS.map(t => ` - ${t.id} (${t.displayName})`).join("\n");
67
- throw new Error(`Unknown target '${targetId}'. Available targets:\n${available}`);
78
+ // No catalog on this machine (or a typo). A QUALIFIED target passes
79
+ // through raw: the first build syncs the catalog from the Zephyr tree
80
+ // and validates it there, where the real error is actionable.
81
+ if (targetId.includes("/")) {
82
+ target = packBoardAsTarget({
83
+ identifier: targetId,
84
+ name: targetId,
85
+ soc: targetId.split("/")[1] ?? "",
86
+ });
87
+ }
88
+ else {
89
+ const known = KNOWN_TARGETS.map(t => ` - ${t.id} (${t.displayName})`);
90
+ throw new Error(`Unknown target '${targetId}'. Built-in targets:\n${known.join("\n")}\n` +
91
+ `Or pass any board from the catalog (e.g. --board nucleo_f411re/stm32f411xe) —\n` +
92
+ `the catalog comes from your Zephyr tree; run \`cuttlefish board sync\` (or the\n` +
93
+ `installer) to generate it.`);
94
+ }
68
95
  }
96
+ const compatibleFrameworks = frameworksForTarget(target)
97
+ .filter(f => f.installable);
69
98
  // Resolve the framework. --framework wins (validated against the board's
70
99
  // compatible set); otherwise narrow via the catalog for the chosen board
71
100
  // and auto-pick when exactly one is compatible.
@@ -77,8 +106,8 @@ async function handleCreate(options) {
77
106
  const available = FRAMEWORK_CATALOG.filter(f => f.installable).map(f => f.id).join(", ");
78
107
  throw new Error(`Unknown framework '${options.framework}'. Available: ${available}`);
79
108
  }
80
- if (!frameworkCompatibleWithTarget(target, requested.id)) {
81
- const list = frameworksForTarget(target).filter(f => f.installable).map(f => f.id).join(", ");
109
+ if (!compatibleFrameworks.some(f => f.id === requested.id)) {
110
+ const list = compatibleFrameworks.map(f => f.id).join(", ");
82
111
  throw new Error(`Framework '${requested.id}' is not compatible with target '${target.id}' (${target.displayName}). ` +
83
112
  `Compatible frameworks: ${list}`);
84
113
  }
@@ -86,35 +115,53 @@ async function handleCreate(options) {
86
115
  frameworkPackage = requested.packageName;
87
116
  }
88
117
  else {
89
- const compatible = frameworksForTarget(target).filter(f => f.installable);
90
- if (compatible.length === 1) {
91
- frameworkId = compatible[0].id;
92
- frameworkPackage = compatible[0].packageName;
118
+ if (compatibleFrameworks.length === 1) {
119
+ frameworkId = compatibleFrameworks[0].id;
120
+ frameworkPackage = compatibleFrameworks[0].packageName;
93
121
  }
94
122
  else {
95
- const list = compatible.map(f => f.id).join(", ");
123
+ const list = compatibleFrameworks.map(f => f.id).join(", ");
96
124
  throw new Error(`Target '${target.id}' is compatible with multiple frameworks (${list}). ` +
97
125
  `Pass --framework <id> to choose one.`);
98
126
  }
99
127
  }
128
+ // Probe methods (Zephyr boards whose packages ship a table). An explicit
129
+ // --probe id is validated against the catalog so a typo fails at create
130
+ // time, not at the first upload.
131
+ const probeMethods = frameworkId === 'zephyr' ? probeMethodsForBoard(target.id) : [];
132
+ if (options.probe && probeMethods.length > 0 && !probeMethods.some(m => m.id === options.probe)) {
133
+ const list = probeMethods.map(m => `${m.id} (${m.description})`).join('; ');
134
+ throw new Error(`Unknown probe method '${options.probe}' for ${target.displayName}. Supported: ${list}.`);
135
+ }
100
136
  const projectName = options.projectName || 'my-project';
101
- // Framework-specific build target + toolchain (Zephyr board id + 'west' vs
102
- // the Arduino FQBN + 'arduino-cli').
137
+ // Framework-specific build target + toolchain (Zephyr board id + 'west').
103
138
  const profile = frameworkTargetProfile(target, frameworkId);
139
+ const buildTarget = profile.buildTarget ?? target.buildTarget;
104
140
  const result = scaffoldProject({
141
+ probeMethod: options.probe,
142
+ probeMethods,
105
143
  projectName,
106
144
  targetId: target.id,
107
145
  targetDisplayName: target.displayName,
108
146
  isNative: target.isNative,
109
147
  architecture: target.architecture,
110
- boardPackage: target.boardPackage,
148
+ board: target.board,
149
+ // Pack fact: does the board's devicetree declare an LED? Drives the
150
+ // starter between LED-blink and console-heartbeat (384 of 1,248 pack
151
+ // boards ship no gpio-leds node).
152
+ ...(target.board ? { hasLed: Boolean(activeBoardCatalog()[target.board]?.led) } : {}),
111
153
  frameworkPackage,
112
154
  framework: frameworkId,
113
- buildTarget: profile.buildTarget ?? target.buildTarget,
155
+ buildTarget,
114
156
  ...(profile.toolchainType ? { toolchainType: profile.toolchainType } : {}),
115
- mcu: target.mcu,
116
- baudRate: target.isNative ? undefined : (options.baud ?? 9600),
117
- includeSketch: !options.noSketch,
157
+ // soc rides the config only for bare-silicon/contract projects — a
158
+ // board target's soc derives from the identifier at boardgen time.
159
+ ...(target.board ? {} : { soc: target.soc }),
160
+ // Zephyr consoles default 115200; the 9600 default is the Arduino-class
161
+ // convention.
162
+ baudRate: target.isNative ? undefined : (options.baud ?? (frameworkId === 'zephyr' ? 115200 : 9600)),
163
+ includeStarter: !options.noStarter,
164
+ ...(options.port ? { port: options.port } : {}),
118
165
  ...(target.frameworkData
119
166
  ? { frameworkData: target.frameworkData }
120
167
  : {}),
@@ -124,10 +171,12 @@ async function handleCreate(options) {
124
171
  else {
125
172
  const wizardResult = await runCreateWizard({
126
173
  projectName: options.projectName,
127
- board: options.board,
174
+ board: options.board ?? options.target,
128
175
  framework: options.framework,
129
176
  baud: options.baud,
130
- noSketch: options.noSketch,
177
+ noStarter: options.noStarter,
178
+ probe: options.probe,
179
+ port: options.port,
131
180
  });
132
181
  if (!wizardResult) {
133
182
  console.log(chalk.yellow("Project setup cancelled."));
@@ -179,22 +228,6 @@ function finalizeCreate(result, options) {
179
228
  }
180
229
  printCreateNextSteps(result.options, result.outDir, { installed, debugProfile: debugArtifacts.length > 0 });
181
230
  }
182
- async function handleBoardAdd(options) {
183
- const { scaffoldBoardPackages, parseBoardSpec, generateFrameworkChecklist } = await import("./create/index.js");
184
- if (!fs.existsSync(options.specPath)) {
185
- throw new Error(`Spec file not found: ${options.specPath}`);
186
- }
187
- console.log(`Reading spec: ${options.specPath}`);
188
- const specText = fs.readFileSync(options.specPath, "utf8");
189
- const spec = parseBoardSpec(specText);
190
- console.log(`Generating board packages for ${spec.architecture} (${spec.boardName})...`);
191
- const result = scaffoldBoardPackages(spec, { force: options.force });
192
- console.log(`\nCreated ${result.createdFiles.length} files:`);
193
- for (const f of result.createdFiles) {
194
- console.log(` ${path.relative(process.cwd(), f)}`);
195
- }
196
- console.log(generateFrameworkChecklist(spec));
197
- }
198
231
  async function main() {
199
232
  try {
200
233
  const options = parseCommandLine(process.argv);
@@ -206,10 +239,6 @@ async function main() {
206
239
  await handleCreate(options);
207
240
  return;
208
241
  }
209
- if (options.command === "board-add") {
210
- await handleBoardAdd(options);
211
- return;
212
- }
213
242
  if (options.command === "library") {
214
243
  await runLibraryCommand(options);
215
244
  return;
@@ -221,31 +250,6 @@ async function main() {
221
250
  });
222
251
  return;
223
252
  }
224
- if (options.command === "map-error") {
225
- if (!options.mapFile || !options.cppLine || !options.cppColumn) {
226
- throw new Error("map-error requires map file and C++ line/column options.");
227
- }
228
- const mapPath = resolveMapPath(options.mapFile);
229
- const sourceMap = readSourceMap(mapPath);
230
- const mapped = mapCppLocationToTs(sourceMap, options.cppLine, options.cppColumn, options.message, options.cppFile);
231
- if (!mapped.mappedTsSpan) {
232
- console.log("No TypeScript mapping found for the provided C++ location.");
233
- }
234
- else {
235
- console.log([
236
- `Mapped TS location: ${mapped.mappedTsSpan.filePath}`,
237
- `(${mapped.mappedTsSpan.startLine},${mapped.mappedTsSpan.startColumn})`,
238
- `node=${mapped.nodeKind ?? "unknown"}`,
239
- mapped.symbolName ? `symbol=${mapped.symbolName}` : undefined,
240
- ]
241
- .filter(Boolean)
242
- .join(" "));
243
- }
244
- if (mapped.message) {
245
- console.log(`Compiler message: ${mapped.message}`);
246
- }
247
- return;
248
- }
249
253
  if (options.command === "doctor" || options.command === "licenses") {
250
254
  // These commands run standalone (often before a build), so the framework
251
255
  // isn't loaded yet. Load it from the config's framework field so the
@@ -278,6 +282,74 @@ async function main() {
278
282
  fw.licenses(options.strict ?? false, options.all ?? false);
279
283
  return;
280
284
  }
285
+ // ── Handle board catalog sync / board module regen ────────────────────
286
+ if (options.command === "board") {
287
+ const config = loadCuttlefishConfig(process.cwd());
288
+ if (!config) {
289
+ throw new Error("No cuttlefish.config.ts found in current directory.\n" +
290
+ "Run 'cuttlefish create' to create one.");
291
+ }
292
+ // The framework owns the board catalog + module generation; load it
293
+ // the same way the doctor command does.
294
+ if (!hasLoadedFramework() && config.framework) {
295
+ try {
296
+ loadFrameworkPackage(config.framework, process.cwd());
297
+ }
298
+ catch {
299
+ // Framework package not resolvable — the hook error below names
300
+ // the problem better than a loader crash would.
301
+ }
302
+ }
303
+ const strategy = hasLoadedFramework()
304
+ ? getLoadedFramework().strategy
305
+ : undefined;
306
+ if (options.subcommand === "sync") {
307
+ if (typeof strategy?.syncBoardCatalog !== "function") {
308
+ throw new Error(`Framework '${config.framework ?? "(none)"}' provides no board catalog sync — ` +
309
+ `'cuttlefish board sync' applies to Zephyr-framework projects.`);
310
+ }
311
+ const report = strategy.syncBoardCatalog(options.zephyrBase);
312
+ const version = report.provenance.version || "unknown version";
313
+ const head = report.provenance.gitHead ? ` @ ${report.provenance.gitHead.slice(0, 12)}` : "";
314
+ const fp = sdkFingerprint(report.zephyrBase);
315
+ ui.printSuccess(`Board catalog synced from Zephyr ${version}${head}`);
316
+ console.log(chalk.gray(` tree: ${report.zephyrBase}`));
317
+ if (fp)
318
+ console.log(chalk.gray(` fingerprint: ${fp} (pin ${PINNED_ZEPHYR_MANIFEST_REV})`));
319
+ console.log(chalk.gray(` catalog: ${report.overlayPath}`));
320
+ console.log(` ${report.stats.variants} board variants (${report.stats.withFacts} with pin facts` +
321
+ `${report.stats.failures > 0 ? `, ${report.stats.failures} reader failures` : ""}` +
322
+ `${report.stats.droppedYamls > 0 ? `, ${report.stats.droppedYamls} yamls with no resolvable .dts` : ""})`);
323
+ const { added, changed, removed } = report;
324
+ const list = (ids, verb) => ids.length === 0
325
+ ? chalk.gray(` 0 ${verb}`)
326
+ : ` ${ids.length} ${verb}: ${ids.slice(0, 8).join(", ")}${ids.length > 8 ? ", …" : ""}`;
327
+ console.log(chalk.gray(" vs the previous catalog (what changed in your tree):"));
328
+ console.log(list(added, "added"));
329
+ console.log(list(changed, "changed"));
330
+ console.log(list(removed, "removed"));
331
+ // Refresh the project's own board module so the sync lands in this
332
+ // project immediately (not just on the next first-build).
333
+ if (config.board && !config.contract) {
334
+ const dir = regenBoardModule(config);
335
+ ui.printSuccess(`Regenerated the board module for '${config.board}' in ${path.join(dir, "board.ts")}`);
336
+ }
337
+ return;
338
+ }
339
+ // regen: refresh a stale catalog overlay first, so a regen after
340
+ // `west update` picks up the tree's boards automatically. Best-effort
341
+ // — a missing tree or a failed walk must not block regenerating from
342
+ // whatever catalog is already present.
343
+ try {
344
+ strategy?.ensureFreshBoardCatalog?.();
345
+ }
346
+ catch {
347
+ // Non-fatal by design (see above).
348
+ }
349
+ const dir = regenBoardModule(config);
350
+ ui.printSuccess(`Regenerated the board module for '${config.board}' in ${path.join(dir, "board.ts")}`);
351
+ return;
352
+ }
281
353
  // ── Handle build command — entry point comes from config ──────────
282
354
  if (options.command === "build") {
283
355
  const buildConfig = loadCuttlefishConfig(process.cwd());
@@ -287,7 +359,7 @@ async function main() {
287
359
  }
288
360
  if (!buildConfig.entry) {
289
361
  throw new Error("cuttlefish.config.ts has no 'entry' field.\n" +
290
- "Add: entry: './src/sketch.ts'");
362
+ "Add: entry: './src/main.ts'");
291
363
  }
292
364
  const entryFile = path.resolve(path.dirname(buildConfig.configPath), buildConfig.entry);
293
365
  if (!fs.existsSync(entryFile)) {
@@ -308,37 +380,6 @@ async function main() {
308
380
  // it intentionally has no inputFile (it scans scanDir instead), so the
309
381
  // generic "Missing input file path" check would otherwise block it.
310
382
  if (options.command === "gen-decls") {
311
- // Check for --components flag (ESP-IDF components: managed + local).
312
- // Runs before scanDir/single-file branches; components mode has no
313
- // inputFile by design (it scans managed_components/ + components/).
314
- const componentsDir = options.componentsDir;
315
- if (componentsDir) {
316
- ui.printHeader();
317
- ui.printStep(`Generating component declarations for ${componentsDir}...`);
318
- const config = loadCuttlefishConfig(componentsDir);
319
- const frameworkConfig = (config?.frameworkConfig ?? {});
320
- const componentsNode = frameworkConfig?.components ?? {};
321
- const managedSpecs = Object.keys(componentsNode.managed ?? {});
322
- // idf.py stores managed deps as <namespace>__<name> (slashes → __).
323
- const managedNames = managedSpecs.map((spec) => spec.replace("/", "__"));
324
- const localPaths = (componentsNode.local ?? []).map((p) => path.isAbsolute(p) ? p : path.resolve(componentsDir, p));
325
- const builtinNames = componentsNode.builtin ?? [];
326
- const created = generateComponentDeclsForProject(componentsDir, {
327
- managed: managedNames,
328
- local: localPaths,
329
- builtin: [],
330
- idfRoot: undefined,
331
- });
332
- if (created.length === 0) {
333
- ui.printInfo("No component declaration files created.");
334
- }
335
- else {
336
- ui.printSuccess(`Created ${created.length} declaration file(s):`);
337
- for (const f of created)
338
- ui.printFileCreated(f);
339
- }
340
- return;
341
- }
342
383
  // Check for --all flag (scan directory)
343
384
  const scanDir = options.scanDir;
344
385
  if (scanDir) {
@@ -379,9 +420,9 @@ async function main() {
379
420
  if (options.expect) {
380
421
  const config = loadCuttlefishConfig(process.cwd());
381
422
  const exitCode = runExpectTests({
382
- port: options.port ?? config?.console?.port,
423
+ port: options.port,
383
424
  buildTarget: config?.buildTarget,
384
- baud: config?.console?.baudRate ?? options.baud,
425
+ baud: options.baud,
385
426
  expectFile: options.expectFile,
386
427
  });
387
428
  process.exitCode = exitCode;
@@ -390,23 +431,6 @@ async function main() {
390
431
  throw new Error("Missing input file path.");
391
432
  }
392
433
  assertTypeScriptInput(options.inputFile);
393
- if (options.command === "gen-libdefs") {
394
- const outDir = path.dirname(options.inputFile);
395
- const created = generateLibraryDefinitions({
396
- inputFile: options.inputFile,
397
- outDir,
398
- });
399
- if (created.length === 0) {
400
- console.log("No new library definition files created.");
401
- }
402
- else {
403
- console.log("Created library definition files:");
404
- for (const filePath of created) {
405
- console.log(`- ${filePath}`);
406
- }
407
- }
408
- return;
409
- }
410
434
  // ── Load cuttlefish.config.ts (config wins over CLI flags) ──────────
411
435
  const inputDir = path.dirname(path.resolve(options.inputFile));
412
436
  const config = loadCuttlefishConfig(inputDir);
@@ -419,42 +443,6 @@ async function main() {
419
443
  if (config?.contract) {
420
444
  await generateContractBoard(config);
421
445
  }
422
- // ── Pre-transpile: generate component .d.ts stubs ───────────────────
423
- // The transpile type-check needs the .d.ts imports in main.ts to resolve.
424
- // For ESP-IDF builtin/managed/local components, those .d.ts files are
425
- // generated by gen-decls — so we must run it BEFORE type-checking, not
426
- // only as part of --compile. The cache is written under the transpile
427
- // output dir (the same path --compile uses), so user imports resolve
428
- // identically in both flows.
429
- if (config?.frameworkConfig) {
430
- const fc = config.frameworkConfig;
431
- const componentsNode = fc?.components ?? {};
432
- const managedSpecs = Object.keys(componentsNode.managed ?? {});
433
- const localPaths = (componentsNode.local ?? []).map((p) => path.isAbsolute(p) ? p : path.resolve(inputDir, p));
434
- const builtinNames = componentsNode.builtin ?? [];
435
- if (managedSpecs.length > 0 || localPaths.length > 0 || builtinNames.length > 0) {
436
- // Resolve the transpile output dir — the cache lives under it so the
437
- // framework's compile path and this pre-transpile pass agree on location.
438
- // output.outDir in config is relative to the project root (inputDir);
439
- // if absent, the transpile output goes alongside the entry file.
440
- const outBase = config.outputOutDir
441
- ? path.resolve(inputDir, config.outputOutDir)
442
- : inputDir;
443
- try {
444
- generateComponentDeclsForProject(outBase, {
445
- managed: managedSpecs.map((s) => s.replace('/', '__')),
446
- local: localPaths,
447
- builtin: [],
448
- idfRoot: undefined,
449
- });
450
- }
451
- catch {
452
- // Non-fatal: if gen-decls fails (e.g. component not yet fetched),
453
- // the type-checker will surface the missing-import errors with
454
- // clearer context than crashing here.
455
- }
456
- }
457
- }
458
446
  // Load the UI engine (if @typecad/ui is installed) before any UI work.
459
447
  await loadUIEngine();
460
448
  // Load the safety engine (if @typecad/safety is installed) before any
@@ -463,9 +451,8 @@ async function main() {
463
451
  let effectivePlatformContext = options.platformContext;
464
452
  let effectiveTarget = options.target;
465
453
  let effectiveOutDir = options.outDir;
466
- let effectiveBoardPackage = options.boardPackage;
454
+ let effectiveBoardTarget = options.boardTarget;
467
455
  let effectiveFrameworkPackage = options.frameworkPackage;
468
- let effectiveMcuPackage;
469
456
  let effectivePort = options.port;
470
457
  // CUTTLEFISH_PORT env var sits between the CLI flag and the config file
471
458
  // (flag > env > config), so cross-platform uploads don't need a
@@ -474,15 +461,8 @@ async function main() {
474
461
  effectivePort = process.env.CUTTLEFISH_PORT;
475
462
  }
476
463
  if (config) {
477
- if (config.mcu) {
478
- effectiveMcuPackage = config.mcu;
479
- }
480
- // Config port is the default; CLI --port flag overrides it.
481
- if (!effectivePort && config.console?.port) {
482
- effectivePort = config.console.port;
483
- }
484
- if (config.board || config.mcu) {
485
- effectiveBoardPackage = config.board || config.mcu;
464
+ if (config.board) {
465
+ effectiveBoardTarget = config.board;
486
466
  }
487
467
  let configBuildTarget = config.buildTarget;
488
468
  if (configBuildTarget) {
@@ -493,7 +473,7 @@ async function main() {
493
473
  frameworkData: {
494
474
  buildTarget: configBuildTarget,
495
475
  // Thread the PSRAM type through to the framework so it can emit the
496
- // PSRAM-enabling Kconfig (Zephyr) / define + FQBN option (Arduino).
476
+ // PSRAM-enabling Kconfig + BOARD_HAS_PSRAM compile definition.
497
477
  ...(config.psram ? { psram: config.psram } : {}),
498
478
  },
499
479
  };
@@ -504,24 +484,6 @@ async function main() {
504
484
  if (config.framework) {
505
485
  effectiveFrameworkPackage = config.framework;
506
486
  }
507
- if (config.target) {
508
- if (effectiveFrameworkPackage) {
509
- try {
510
- loadFrameworkPackage(effectiveFrameworkPackage, inputDir);
511
- if (hasLoadedFramework()) {
512
- effectiveTarget = getLoadedFramework().strategy.id;
513
- }
514
- }
515
- catch {
516
- // Framework not loadable — keep the CLI-provided target
517
- }
518
- }
519
- }
520
- // Pass console baud rate to platform context
521
- if (config.console?.baudRate) {
522
- effectivePlatformContext = effectivePlatformContext || {};
523
- effectivePlatformContext.console = { baudRate: config.console.baudRate };
524
- }
525
487
  // Keep cuttlefish-env.d.ts in sync so the TS language server can resolve
526
488
  // '@typecad/board' imports in editor without a linter error.
527
489
  // Platform-specific declarations come from the strategy if available.
@@ -547,8 +509,7 @@ async function main() {
547
509
  ui.printHeader();
548
510
  ui.printBuildInfo({
549
511
  framework: effectiveFrameworkPackage,
550
- mcu: effectiveMcuPackage,
551
- board: effectiveBoardPackage,
512
+ board: effectiveBoardTarget,
552
513
  buildTarget: effectivePlatformContext?.frameworkData?.buildTarget,
553
514
  });
554
515
  // ── Watch mode ────────────────────────────────────────────────────────
@@ -566,7 +527,7 @@ async function main() {
566
527
  emitMaps: options.emitMaps,
567
528
  platformContext: effectivePlatformContext,
568
529
  treeShaking: options.treeShaking,
569
- boardPackage: effectiveBoardPackage,
530
+ boardTarget: effectiveBoardTarget,
570
531
  frameworkPackage: effectiveFrameworkPackage,
571
532
  debug: options.debug,
572
533
  force: options.force,
@@ -588,28 +549,29 @@ async function main() {
588
549
  // Initial compile + upload if flags are set
589
550
  if (options.compile) {
590
551
  const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
591
- const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
592
552
  const watchOpts = {
593
553
  outputDir: path.dirname(result.sourcePath),
594
554
  sourcePath: result.sourcePath,
595
- buildTarget,
555
+ buildTarget: baseBuildTarget,
596
556
  port: effectivePort,
597
- baud: options.baud ?? config?.console?.baudRate,
557
+ baud: options.baud,
598
558
  extraFlags: config?.outputExtraFlags,
599
- defines: psramDefines,
559
+ defines: config?.outputDefines ?? {},
600
560
  psram: config?.psram,
601
561
  frameworkConfig: config?.frameworkConfig,
602
562
  zephyrConfig: config?.zephyrConfig,
603
563
  display: displayConfigForTranspile(config),
604
564
  debug: options.debug,
605
565
  };
606
- ui.printCompiling(buildTarget ?? "native");
566
+ ui.printCompiling(baseBuildTarget ?? "native");
607
567
  const compileResult = compileSource(watchOpts);
608
568
  printMappedCompileErrors(compileResult, result.sourceMapPath, result.sourcePath, path.dirname(result.sourcePath));
609
569
  if (compileResult.success) {
610
570
  if (compileResult.memoryUsage)
611
571
  ui.printMemoryUsage(compileResult.memoryUsage);
612
- if (options.upload && effectivePort) {
572
+ if (options.upload) {
573
+ // No port gate here — probe/USB flashing needs none, and the
574
+ // framework reports a friendly failure when the runner does.
613
575
  ui.printUploading(effectivePort);
614
576
  const uploadResult = uploadFirmware(watchOpts);
615
577
  if (uploadResult.output)
@@ -644,7 +606,7 @@ async function main() {
644
606
  process.stdout.write("\x1Bc");
645
607
  const timestamp = new Date().toLocaleTimeString();
646
608
  const relativePath = path.relative(process.cwd(), changedFile);
647
- console.log(chalk.cyan(`⤳ Cuttlefish`) + chalk.gray(` v0.1.0`));
609
+ console.log(chalk.cyan(`⤳ Cuttlefish`) + chalk.gray(` v${ui.VERSION}`));
648
610
  console.log();
649
611
  ui.printInfo(`[${timestamp}] Change detected: ${relativePath}`);
650
612
  console.log();
@@ -665,7 +627,7 @@ async function main() {
665
627
  emitMaps: options.emitMaps,
666
628
  platformContext: effectivePlatformContext,
667
629
  treeShaking: options.treeShaking,
668
- boardPackage: effectiveBoardPackage,
630
+ boardTarget: effectiveBoardTarget,
669
631
  frameworkPackage: effectiveFrameworkPackage,
670
632
  debug: options.debug,
671
633
  force: true, // Always force in watch mode to bypass stale cache
@@ -688,28 +650,30 @@ async function main() {
688
650
  ui.printSuccess();
689
651
  if (options.compile) {
690
652
  const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
691
- const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
692
653
  const rebuildOpts = {
693
654
  outputDir: path.dirname(rebuildResult.sourcePath),
694
655
  sourcePath: rebuildResult.sourcePath,
695
- buildTarget,
656
+ buildTarget: baseBuildTarget,
696
657
  port: effectivePort,
697
- baud: options.baud ?? config?.console?.baudRate,
658
+ baud: options.baud,
698
659
  extraFlags: config?.outputExtraFlags,
699
- defines: psramDefines,
660
+ defines: config?.outputDefines ?? {},
700
661
  psram: config?.psram,
701
662
  frameworkConfig: config?.frameworkConfig,
702
663
  zephyrConfig: config?.zephyrConfig,
703
664
  display: displayConfigForTranspile(config),
704
665
  debug: options.debug,
705
666
  };
706
- ui.printCompiling(buildTarget ?? "native");
667
+ ui.printCompiling(baseBuildTarget ?? "native");
707
668
  const compileResult = compileSource(rebuildOpts);
708
669
  printMappedCompileErrors(compileResult, rebuildResult.sourceMapPath, rebuildResult.sourcePath, path.dirname(rebuildResult.sourcePath));
709
670
  if (compileResult.success) {
710
671
  if (compileResult.memoryUsage)
711
672
  ui.printMemoryUsage(compileResult.memoryUsage);
712
- if (options.upload && effectivePort) {
673
+ if (options.upload) {
674
+ // No port gate here — probe/USB flashing needs none, and
675
+ // the framework reports a friendly failure when the
676
+ // runner does.
713
677
  ui.printUploading(effectivePort);
714
678
  const uploadResult = uploadFirmware(rebuildOpts);
715
679
  if (uploadResult.output)
@@ -758,7 +722,7 @@ async function main() {
758
722
  emitMaps: options.emitMaps,
759
723
  platformContext: effectivePlatformContext,
760
724
  treeShaking: options.treeShaking,
761
- boardPackage: effectiveBoardPackage,
725
+ boardTarget: effectiveBoardTarget,
762
726
  frameworkPackage: effectiveFrameworkPackage,
763
727
  debug: options.debug,
764
728
  force: options.force,
@@ -786,7 +750,7 @@ async function main() {
786
750
  const exitCode = runExpectTests({
787
751
  port: effectivePort,
788
752
  buildTarget: effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget,
789
- baud: config?.console?.baudRate ?? options.baud,
753
+ baud: options.baud,
790
754
  expectFile: options.expectFile,
791
755
  });
792
756
  process.exitCode = exitCode;
@@ -795,22 +759,24 @@ async function main() {
795
759
  }
796
760
  // --compile (delegates to the active framework's toolchain)
797
761
  const baseBuildTarget = effectivePlatformContext?.frameworkData?.buildTarget ?? options.platformContext?.frameworkData?.buildTarget;
798
- const { buildTarget, defines: psramDefines } = applyPsramToArduinoBuild(baseBuildTarget, config?.outputDefines, config?.psram);
799
762
  const toolchainOpts = {
800
763
  outputDir: path.dirname(result.sourcePath),
801
764
  sourcePath: result.sourcePath,
802
- buildTarget,
765
+ buildTarget: baseBuildTarget,
803
766
  port: effectivePort,
804
- baud: options.baud ?? config?.console?.baudRate,
767
+ baud: options.baud,
805
768
  extraFlags: config?.outputExtraFlags,
806
- defines: psramDefines,
769
+ defines: config?.outputDefines ?? {},
807
770
  psram: config?.psram,
808
771
  frameworkConfig: config?.frameworkConfig,
809
- zephyrConfig: config?.zephyrConfig,
772
+ // --probe <method> overrides zephyr.probe from the config for this run.
773
+ zephyrConfig: options.probe
774
+ ? { ...(config?.zephyrConfig ?? {}), probe: options.probe }
775
+ : config?.zephyrConfig,
810
776
  display: displayConfigForTranspile(config),
811
777
  debug: options.debug,
812
778
  };
813
- ui.printCompiling(buildTarget ?? "native");
779
+ ui.printCompiling(baseBuildTarget ?? "native");
814
780
  let compileResult;
815
781
  try {
816
782
  compileResult = compileSource(toolchainOpts);
@@ -840,10 +806,11 @@ async function main() {
840
806
  ui.printSuccess();
841
807
  return;
842
808
  }
843
- // --upload (requires a port from --port flag or config.console.port)
844
- if (!effectivePort) {
845
- throw new Error("--upload requires a port. Set --port <port> on the command line or console.port in cuttlefish.config.ts.");
846
- }
809
+ // --upload. Whether a port is required is the active framework's call —
810
+ // probe-based flashing (openocd/jlink) and USB bootloaders (dfu-util,
811
+ // uf2) need no serial port, so a missing --port only surfaces as the
812
+ // framework's own upload failure for serial-port runners (esptool,
813
+ // bossac).
847
814
  const port = effectivePort;
848
815
  ui.printUploading(port);
849
816
  const uploadResult = uploadFirmware(toolchainOpts);
@@ -859,9 +826,14 @@ async function main() {
859
826
  return;
860
827
  }
861
828
  if (options.monitor) {
862
- // --monitor (blocks until Ctrl+C). Show the resolved baud (config ?? --baud)
863
- // in the bannertoolchainOpts.baud is built the same way, so what the
864
- // user sees is what the framework monitor opens the port at.
829
+ // --monitor (blocks until Ctrl+C). Unlike flashing, monitoring always
830
+ // needs a serial port there is no probe/USB flavor of it.
831
+ if (!port) {
832
+ throw new Error("--monitor requires a port. Set --port <port> on the command line (or the CUTTLEFISH_PORT env var).");
833
+ }
834
+ // Show the resolved --baud in the banner — toolchainOpts.baud is built
835
+ // the same way, so what the user sees is what the framework monitor
836
+ // opens the port at.
865
837
  ui.printMonitoring(port, toolchainOpts.baud ?? 115200);
866
838
  monitorDevice(toolchainOpts);
867
839
  return;
@@ -871,8 +843,8 @@ async function main() {
871
843
  ui.printSuccess();
872
844
  const exitCode = runExpectTests({
873
845
  port: effectivePort,
874
- buildTarget,
875
- baud: config?.console?.baudRate ?? options.baud,
846
+ buildTarget: baseBuildTarget,
847
+ baud: options.baud,
876
848
  expectFile: options.expectFile,
877
849
  });
878
850
  process.exitCode = exitCode;