tan-cli 0.7.0__py3-none-any.whl

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 (330) hide show
  1. tan/__init__.py +0 -0
  2. tan/__main__.py +120 -0
  3. tan/cli.py +939 -0
  4. tan/command_table.py +265 -0
  5. tan/commands/__init__.py +8 -0
  6. tan/commands/bootstrap_cmd.py +4856 -0
  7. tan/commands/bootstrap_patches.py +164 -0
  8. tan/commands/bootstrap_toolchain_fetch.py +155 -0
  9. tan/commands/bootstrap_toolchain_pin.py +36 -0
  10. tan/commands/build/__init__.py +1 -0
  11. tan/commands/build/configure_inputs.py +277 -0
  12. tan/commands/build/execute.py +1852 -0
  13. tan/commands/build/host_python.py +59 -0
  14. tan/commands/build/link_stale.py +78 -0
  15. tan/commands/build/manifest.py +428 -0
  16. tan/commands/build/materialise.py +81 -0
  17. tan/commands/build/token_substitution.py +381 -0
  18. tan/commands/build/toolchain.py +482 -0
  19. tan/commands/build/user_defines_wipe.py +115 -0
  20. tan/commands/build/workspace_patches.py +65 -0
  21. tan/commands/build_cmd.py +2236 -0
  22. tan/commands/build_output.py +331 -0
  23. tan/commands/clean_cmd.py +1183 -0
  24. tan/commands/completion_cmd.py +786 -0
  25. tan/commands/debug_config_cmd.py +2425 -0
  26. tan/commands/diff_cmd.py +940 -0
  27. tan/commands/doctor_cmd.py +4767 -0
  28. tan/commands/examples_cmd.py +644 -0
  29. tan/commands/explain_cmd.py +1319 -0
  30. tan/commands/faultdecode_cmd.py +931 -0
  31. tan/commands/flash_atoc_guard.py +240 -0
  32. tan/commands/flash_cmd.py +5731 -0
  33. tan/commands/flash_mram_guard.py +150 -0
  34. tan/commands/flash_ram.py +578 -0
  35. tan/commands/flash_raw.py +583 -0
  36. tan/commands/generate_cmd.py +1456 -0
  37. tan/commands/image_cmd.py +753 -0
  38. tan/commands/init_cmd.py +1740 -0
  39. tan/commands/inspect_cmd.py +377 -0
  40. tan/commands/kconfig_cmd.py +734 -0
  41. tan/commands/model_cmd.py +1514 -0
  42. tan/commands/model_device_cmd.py +351 -0
  43. tan/commands/model_device_live.py +234 -0
  44. tan/commands/model_host_cmd.py +430 -0
  45. tan/commands/model_zoo_cmd.py +363 -0
  46. tan/commands/monitor_actions.py +125 -0
  47. tan/commands/monitor_cmd.py +626 -0
  48. tan/commands/monitor_session.py +505 -0
  49. tan/commands/new_som_cmd.py +1522 -0
  50. tan/commands/pinmux_cmd.py +568 -0
  51. tan/commands/presets_cmd.py +1123 -0
  52. tan/commands/probe_cmd.py +421 -0
  53. tan/commands/reset_cmd.py +354 -0
  54. tan/commands/reset_confirm.py +168 -0
  55. tan/commands/run_cmd.py +637 -0
  56. tan/commands/scaffold_cmd.py +490 -0
  57. tan/commands/sdk_cmd.py +1723 -0
  58. tan/commands/size_cmd.py +1020 -0
  59. tan/commands/support_bundle_cmd.py +1073 -0
  60. tan/commands/trace_cmd.py +341 -0
  61. tan/commands/validate_cmd.py +2009 -0
  62. tan/commands/west_forward_cmd.py +736 -0
  63. tan/commands/workspace_patch_check.py +296 -0
  64. tan/core/__init__.py +1 -0
  65. tan/core/alp_conf_pregen.py +100 -0
  66. tan/core/artifact_provenance.py +240 -0
  67. tan/core/atoc_guard.py +276 -0
  68. tan/core/atoc_guard_messages.py +232 -0
  69. tan/core/atoc_replacement.py +118 -0
  70. tan/core/atomic_write.py +178 -0
  71. tan/core/board_context.py +351 -0
  72. tan/core/board_diagnostic.py +111 -0
  73. tan/core/board_files.py +221 -0
  74. tan/core/board_validator.py +542 -0
  75. tan/core/board_validator_compat.py +196 -0
  76. tan/core/board_validator_run.py +129 -0
  77. tan/core/board_validator_schema.py +186 -0
  78. tan/core/board_validator_skew.py +46 -0
  79. tan/core/board_yaml_edit.py +295 -0
  80. tan/core/board_yaml_pos.py +118 -0
  81. tan/core/bootstrap.py +2572 -0
  82. tan/core/build_plan.py +625 -0
  83. tan/core/consent.py +104 -0
  84. tan/core/console_filter.py +245 -0
  85. tan/core/debug_launch.py +1803 -0
  86. tan/core/devicetree_lint.py +168 -0
  87. tan/core/dir_removal.py +229 -0
  88. tan/core/doctor_git.py +172 -0
  89. tan/core/doctor_libraries.py +253 -0
  90. tan/core/doctor_render.py +185 -0
  91. tan/core/doctor_scope.py +66 -0
  92. tan/core/doctor_setools.py +237 -0
  93. tan/core/doctor_stale.py +180 -0
  94. tan/core/document_guards.py +723 -0
  95. tan/core/dp_id.py +97 -0
  96. tan/core/elf_load.py +63 -0
  97. tan/core/error_catalog.py +171 -0
  98. tan/core/example_catalog.py +499 -0
  99. tan/core/example_facets.py +151 -0
  100. tan/core/faultdecode.py +521 -0
  101. tan/core/flash_plan.py +2501 -0
  102. tan/core/flow_d_report.py +381 -0
  103. tan/core/flow_d_uptime.py +164 -0
  104. tan/core/fs_confine.py +130 -0
  105. tan/core/global_flags.py +230 -0
  106. tan/core/host_python.py +210 -0
  107. tan/core/image_bundle.py +247 -0
  108. tan/core/inert.py +147 -0
  109. tan/core/jlink_binary.py +128 -0
  110. tan/core/jlink_probe.py +481 -0
  111. tan/core/jsonc_splice.py +404 -0
  112. tan/core/launch_provenance.py +257 -0
  113. tan/core/link_refusal.py +76 -0
  114. tan/core/metadata_schema.py +376 -0
  115. tan/core/model_check.py +227 -0
  116. tan/core/model_device.py +449 -0
  117. tan/core/model_doctor.py +198 -0
  118. tan/core/model_host.py +48 -0
  119. tan/core/model_list.py +112 -0
  120. tan/core/model_zoo.py +353 -0
  121. tan/core/module_template.py +340 -0
  122. tan/core/mram_link.py +87 -0
  123. tan/core/new_som_ospi.py +94 -0
  124. tan/core/os_class.py +159 -0
  125. tan/core/patch_paths.py +87 -0
  126. tan/core/pending.py +40 -0
  127. tan/core/plain_zephyr_plan.py +138 -0
  128. tan/core/plan_exec.py +373 -0
  129. tan/core/plan_tokens.py +600 -0
  130. tan/core/probe.py +155 -0
  131. tan/core/probe_plan.py +237 -0
  132. tan/core/proxy.py +168 -0
  133. tan/core/publish.py +31 -0
  134. tan/core/python_floor.py +178 -0
  135. tan/core/ram_run.py +694 -0
  136. tan/core/ram_watch.py +231 -0
  137. tan/core/raw_write.py +160 -0
  138. tan/core/reset_plan.py +56 -0
  139. tan/core/run.py +161 -0
  140. tan/core/scaffold.py +2203 -0
  141. tan/core/scaffold_selftest_identity.py +225 -0
  142. tan/core/sdk_default_registry.py +565 -0
  143. tan/core/sdk_discovery.py +996 -0
  144. tan/core/sdk_removal.py +465 -0
  145. tan/core/serial_actions.py +175 -0
  146. tan/core/serial_capture.py +185 -0
  147. tan/core/serial_url.py +45 -0
  148. tan/core/setools.py +608 -0
  149. tan/core/setools_scratch.py +243 -0
  150. tan/core/shapes.py +194 -0
  151. tan/core/size.py +610 -0
  152. tan/core/som_buildability.py +204 -0
  153. tan/core/som_schema_version.py +62 -0
  154. tan/core/subprocess_env.py +201 -0
  155. tan/core/sysbuild_image.py +94 -0
  156. tan/core/system_manifest.py +827 -0
  157. tan/core/text_layout.py +73 -0
  158. tan/core/timestamp.py +182 -0
  159. tan/core/tool_lookup.py +383 -0
  160. tan/core/toolchain_pin.py +117 -0
  161. tan/core/toolchain_provision.py +1224 -0
  162. tan/core/trusted_path.py +134 -0
  163. tan/core/uboot_breakin.py +154 -0
  164. tan/core/uri_reference.py +415 -0
  165. tan/core/user_defines.py +152 -0
  166. tan/core/venv.py +332 -0
  167. tan/core/west_patches.py +143 -0
  168. tan/core/west_workspace_refusal.py +159 -0
  169. tan/core/zephyr_env.py +124 -0
  170. tan/env.py +185 -0
  171. tan/envelope.py +647 -0
  172. tan/exit_codes.py +12 -0
  173. tan/lazy_typer.py +185 -0
  174. tan/model/__init__.py +2 -0
  175. tan/model/_gen_fixture.py +132 -0
  176. tan/model/adapters/__init__.py +116 -0
  177. tan/model/adapters/cpu.py +26 -0
  178. tan/model/adapters/deepx.py +94 -0
  179. tan/model/adapters/drpai.py +242 -0
  180. tan/model/adapters/ethos_u.py +951 -0
  181. tan/model/adapters/executorch.py +55 -0
  182. tan/model/analyze.py +694 -0
  183. tan/model/build.py +304 -0
  184. tan/model/check.py +682 -0
  185. tan/model/manifest.py +625 -0
  186. tan/model/measure.py +216 -0
  187. tan/model/npu_ops_validate.py +130 -0
  188. tan/model/package.py +125 -0
  189. tan/model/perf.py +635 -0
  190. tan/model/perf_apply.py +721 -0
  191. tan/model/prep.py +193 -0
  192. tan/model/sram_fit.py +502 -0
  193. tan/model/targets.py +374 -0
  194. tan/model/tensorio.py +287 -0
  195. tan/net.py +73 -0
  196. tan/output_format.py +213 -0
  197. tan/planner/__init__.py +219 -0
  198. tan/planner/aperture.py +202 -0
  199. tan/planner/buildplan.py +1092 -0
  200. tan/planner/camera_owner.py +192 -0
  201. tan/planner/cameras.py +132 -0
  202. tan/planner/carveout.py +755 -0
  203. tan/planner/cli.py +274 -0
  204. tan/planner/headers.py +315 -0
  205. tan/planner/kconfig.py +2435 -0
  206. tan/planner/kconfig_symbols.py +473 -0
  207. tan/planner/libraries.py +677 -0
  208. tan/planner/link_target.py +237 -0
  209. tan/planner/linux_ownership.py +216 -0
  210. tan/planner/loader.py +1525 -0
  211. tan/planner/manifest.py +116 -0
  212. tan/planner/memory.py +161 -0
  213. tan/planner/memregion.py +56 -0
  214. tan/planner/models.py +533 -0
  215. tan/planner/orchestrator.py +874 -0
  216. tan/planner/ownership.py +203 -0
  217. tan/planner/partition.py +1160 -0
  218. tan/planner/paths.py +32 -0
  219. tan/planner/plain_slice.py +139 -0
  220. tan/planner/project_emit/__init__.py +60 -0
  221. tan/planner/project_emit/bom_netlist.py +375 -0
  222. tan/planner/project_emit/dts.py +580 -0
  223. tan/planner/project_emit/hw_info.py +221 -0
  224. tan/planner/project_emit/native_sim.py +136 -0
  225. tan/planner/project_emit/west_libs.py +213 -0
  226. tan/planner/project_loader.py +264 -0
  227. tan/planner/sdk_capability.py +203 -0
  228. tan/planner/sdk_compat.py +395 -0
  229. tan/planner/secure.py +350 -0
  230. tan/planner/slugs.py +394 -0
  231. tan/planner/som_metadata.py +325 -0
  232. tan/planner/strict_loaders.py +131 -0
  233. tan/planner/template.py +1037 -0
  234. tan/planner/template_pins.py +535 -0
  235. tan/planner/template_rewrite.py +714 -0
  236. tan/planner/topology.py +208 -0
  237. tan/planner/validate.py +365 -0
  238. tan/planner/whole_device_alias.py +67 -0
  239. tan/planner/zephyr_board.py +3510 -0
  240. tan/planner_cli.py +97 -0
  241. tan/planner_emit.py +617 -0
  242. tan/planner_root.py +119 -0
  243. tan/soc_ref.py +58 -0
  244. tan/templates/__init__.py +34 -0
  245. tan/templates/vendored/MANIFEST.md +1862 -0
  246. tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
  247. tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
  248. tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
  249. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
  250. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
  251. tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
  252. tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
  253. tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
  254. tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
  255. tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
  256. tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
  257. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
  258. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
  259. tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
  260. tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
  261. tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
  262. tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
  263. tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
  264. tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
  265. tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
  266. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
  267. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
  268. tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
  269. tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
  270. tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
  271. tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
  272. tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
  273. tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
  274. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
  275. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
  276. tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
  277. tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
  278. tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
  279. tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
  280. tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
  281. tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
  282. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
  283. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
  284. tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
  285. tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
  286. tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
  287. tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
  288. tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
  289. tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
  290. tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
  291. tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
  292. tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
  293. tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
  294. tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
  295. tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
  296. tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
  297. tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
  298. tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
  299. tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
  300. tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
  301. tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
  302. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
  303. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
  304. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
  305. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
  306. tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
  307. tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
  308. tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
  309. tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
  310. tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
  311. tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
  312. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
  313. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
  314. tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
  315. tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
  316. tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
  317. tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
  318. tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
  319. tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
  320. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
  321. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
  322. tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
  323. tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
  324. tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
  325. tan/version.py +102 -0
  326. tan_cli-0.7.0.dist-info/METADATA +125 -0
  327. tan_cli-0.7.0.dist-info/RECORD +330 -0
  328. tan_cli-0.7.0.dist-info/WHEEL +5 -0
  329. tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
  330. tan_cli-0.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,786 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """`tan completion` -- emit a shell completion script for bash, zsh, or fish.
3
+
4
+ Mirrors `crates/tan-cli/src/commands/completion.rs`. The three scripts below
5
+ are embedded verbatim: byte-for-byte captures of the reference Rust oracle's
6
+ own `data.script` field (`target/debug/tan.exe completion --shell
7
+ <bash|zsh|fish> --format json`), the same "captured, not generated" contract
8
+ the oracle's own module docstring describes (there it is `include_str!` over a
9
+ committed `.bash`/`.zsh`/`.fish` file; here it is a literal, since this unit's
10
+ file allowlist is `completion_cmd.py` alone).
11
+
12
+ **One deliberate exception to that, tan-cli#403: the `--format` value lists.**
13
+ The captures hardcoded `text json` in all three scripts, so `tan validate
14
+ --format <TAB>` offered two of the four values `validate` really accepts and
15
+ actively taught an IDE integrator that `diagnostic-v1` and `sarif` do not
16
+ exist. Those lists are now placeholders spliced from `tan.output_format`'s
17
+ enums by `_fill_formats` -- the same declarations the parser and `--help`
18
+ derive from, so the surfaces cannot disagree again. Everything else in the
19
+ scripts is still the oracle's own bytes; the emitted `data.script` therefore
20
+ diverges from v0.4.1's exactly where v0.4.1 was wrong about this port's own
21
+ format/target surface.
22
+
23
+ **A second splice, tan-cli#503: `generate --target`'s value list.** The fish
24
+ capture hardcoded 9 of the 12 real `--target` values, silently missing
25
+ `os-topology` (a member of the default/`--all` set, so a bare `tan generate`
26
+ runs it), `composed-route-table` and `ipc-contract-h` (both explicit-only, but
27
+ real). Also spliced from `generate_cmd`'s own tables now, same mechanism and
28
+ same reasoning as `--format` above. bash and zsh publish no `--target` value
29
+ list at all, so fish was (and is) the only surface this touches.
30
+
31
+ **A third, hand-edited exception, tan-cli#503: `doctor` drops
32
+ `--target-kind`/`--server`.** The oracle's `doctor` declared both
33
+ (`crates/tan-cli/src/cli.rs`), so the captured scripts still offer them, but
34
+ `doctor_cmd.py` deliberately did NOT port that debug half (see its own module
35
+ docstring) -- `support-bundle` and `debug-config` genuinely still take both.
36
+ Completing a flag `tan doctor` rejects at exit 2 is worse than not offering
37
+ it, so the three `doctor` arms had the two flags (and, in zsh/fish, their
38
+ value lists) removed by hand.
39
+
40
+ **A fourth, hand-edited exception, tan-cli#503: none of the three scripts
41
+ found the subcommand reliably.** All three defects are the same shape -- a
42
+ positional or state assumption that a real invocation breaks -- and all three
43
+ were measured in a real shell (bash 5.2.21, zsh 5.9 under a pty, fish 3.7.0
44
+ via `complete -C`), before and after.
45
+
46
+ - **bash keyed every decision off `${COMP_WORDS[1]}`**, which is the
47
+ subcommand only when nothing precedes it. Before: `tan --sdk-root /x <TAB>`
48
+ offered not one of the 31 command names (the `$cword -eq 1` gate missed) and
49
+ `tan --sdk-root /x size --<TAB>` offered no
50
+ `--build-root`/`--board`/`--fail-over-budget` (the per-command `case`
51
+ missed). Both now read one `$subcmd` scanned once at the top of
52
+ `_tan_complete`. That scan also has to survive `$COMP_WORDBREAKS`: bash
53
+ re-splits argv on `=` and `:`, so `--sdk-root=/x` reaches the function as
54
+ three words and `C:/proj` as three more -- see the scan's own comment.
55
+ - **zsh's `args`-state `case $words[2]` never matched anything.** In that
56
+ state `_arguments` has REINDEXED `words`, so `words[1]` is the subcommand
57
+ and `words[2]` is the word being completed: instrumented in zsh 5.9,
58
+ `tan size --<TAB>` arrived with `words=(size --)`, `CURRENT=2`. All 21 arms
59
+ were unreachable and every subcommand fell to `*)` -- `tan size --<TAB>`
60
+ offered the 12 global flags and none of `size`'s own three, `tan doctor
61
+ --<TAB>` offered neither `--build` nor `--fix`. Now `case $subcmd`, the same
62
+ value bash uses, which that probe confirmed is in scope and correct there
63
+ (`subcmd_scan=size`).
64
+ - **fish's command list was gated on `__fish_use_subcommand`**, which returns
65
+ false at the first non-switch token -- and a global flag's VALUE is one. So
66
+ `complete -C 'tan --sdk-root /x '` returned NOTHING at all: no command
67
+ names, and no flags either, because fish offers options only when the
68
+ current token starts with `-`. Now gated on `not
69
+ __fish_seen_subcommand_from <the 31>`, which ignores flag values. (fish's
70
+ per-command flags were already correct -- `__fish_seen_subcommand_from`
71
+ scans every word -- so only the command-list half was broken.)
72
+
73
+ The `$cword -eq 1` gate, the per-command `case`es and the fish condition were
74
+ all the oracle's own captured bytes; `crates/` was deleted in tan-cli#269, so
75
+ this port owns them now and they are fixed rather than preserved.
76
+
77
+ **A fifth, hand-edited exception, tan-cli#503: `--version` is offered at the
78
+ root only.** All three captures listed it in the always-available flag set, so
79
+ it was offered on all 31 subcommands, and no subcommand prints a version for
80
+ it: measured, 29 answer Click's `No such option: --version (Possible options:
81
+ --verbose)`, `lock` forwards it to `west` (`unexpected arguments:
82
+ ['--version']`), and `quality`/`migrate` refuse earlier on their own required
83
+ flag and only reach `west` once that is supplied. It is root-only by design
84
+ (`cli.py`: "it lives on `Cli` directly in clap, not `GlobalArgs`, and is
85
+ root-only on both sides already"), and `tan.core.global_flags.GLOBAL_FLAGS` --
86
+ the one table the parser and `accept_global_flags` both read -- does not
87
+ contain it. bash splits `root_flags` out of `global_flags`; zsh splices it
88
+ into the root `_arguments -C` call and no arm; fish gates it on the same
89
+ `not __fish_seen_subcommand_from` condition as the command list. Inherited
90
+ from the oracle's capture, same as the reads above, and owned here for the
91
+ same reason.
92
+
93
+ **Why hand-captured and not Typer/Click's own shell-completion machinery.**
94
+ Typer ships one (`click.shell_completion`, gated off here via `app =
95
+ typer.Typer(add_completion=False)` in `cli.py`), but it is not a substitute:
96
+ it activates through a completely different mechanism -- sourcing eval output
97
+ from an `_TAN_COMPLETE=<shell>_source tan` environment-variable trigger
98
+ Click's own dispatcher special-cases at import time, not a static script this
99
+ command prints -- and it introspects THIS PROCESS's live Click command tree
100
+ rather than emitting the oracle's fixed command/flag tables. Even
101
+ functionally equivalent tab-completion from it would not reproduce
102
+ `data.script` byte-for-byte, which is the wire contract this command's JSON
103
+ envelope carries (an extension or script that diffs/hashes that field would
104
+ see every invocation as a regression). Hand-captured scripts are therefore
105
+ the only way to be a faithful port here, not a shortcut around one.
106
+
107
+ Every failure is an envelope, never a traceback: the ONLY failure this
108
+ command has is an unsupported `--shell` value, mirroring `resolve_shell` in
109
+ the Rust 1:1 (default `bash`; trim + lowercase; anything else is
110
+ `completion.shell-unsupported`, exit `RUNTIME_FAILURE`). There is no project
111
+ resolution, no SDK checkout, no filesystem read and no subprocess -- `project`
112
+ stays `null`/`null` in every envelope, matching the oracle's `null_project()`.
113
+ """
114
+
115
+ from __future__ import annotations
116
+
117
+ import sys
118
+
119
+ import typer
120
+
121
+ from tan.commands.generate_cmd import (
122
+ ALL_EMIT_MODES,
123
+ COMPOSED_ROUTE_TABLE,
124
+ IPC_CONTRACT_H,
125
+ ZEPHYR_BOARD,
126
+ )
127
+ from tan.envelope import Envelope, Issue, Project, emit
128
+ from tan.exit_codes import ExitCode
129
+ from tan.output_format import (
130
+ FORMAT_HELP,
131
+ WIDE_FORMAT_COMMANDS,
132
+ OutputFormat,
133
+ ValidateOutputFormat,
134
+ format_values,
135
+ resolve_format,
136
+ )
137
+
138
+ #: `data.schemaVersion` for this command's payload.
139
+ DATA_SCHEMA_VERSION = "1"
140
+
141
+ #: The frozen issue code for an unsupported `--shell` value
142
+ #: (`contract/issue-codes.json`; `emittedBy` there already names this file).
143
+ SHELL_UNSUPPORTED_CODE = "completion.shell-unsupported"
144
+ #: Verbatim from `completion.rs`'s `Issue.message` -- the JSON-mode wording.
145
+ SHELL_UNSUPPORTED_MESSAGE = "Unsupported shell. Allowed values: bash, zsh, fish."
146
+ #: Verbatim from `completion.rs`'s `text` line -- the text-mode (stderr) wording.
147
+ SHELL_UNSUPPORTED_TEXT_LINE = "completion: unsupported shell. Use --shell bash|zsh|fish."
148
+
149
+ #: Where the captured scripts had a hardcoded `--format` value list, they now
150
+ #: carry a placeholder `_fill_formats` (below) substitutes from the CLI's own
151
+ #: enums -- tan-cli#403. The scripts are otherwise still byte-for-byte oracle
152
+ #: captures; these five markers are the ONLY derived text in them.
153
+ _FORMATS_MARK = "@FORMATS@"
154
+ _WIDE_FORMATS_MARK = "@WIDE_FORMATS@"
155
+ #: The commands with the wider domain, as a bash/zsh `case` alternation
156
+ #: (`a|b`) and as fish's space-separated argument list respectively.
157
+ _WIDE_COMMANDS_ALT_MARK = "@WIDE_COMMANDS_ALT@"
158
+ _WIDE_COMMANDS_LIST_MARK = "@WIDE_COMMANDS_LIST@"
159
+ #: `generate --target`'s value list (tan-cli#503): the fish capture hardcoded
160
+ #: 9 of the 12 real values, missing `os-topology` (a bare `tan generate`
161
+ #: default target) plus the explicit-only `composed-route-table` and
162
+ #: `ipc-contract-h`. Spliced from `generate_cmd`'s own tables, same as the
163
+ #: `--format` lists above, so this cannot go stale a second time.
164
+ _TARGETS_MARK = "@TARGETS@"
165
+ #: `ALL_EMIT_MODES` is the default/`--all` set; `ZEPHYR_BOARD`,
166
+ #: `COMPOSED_ROUTE_TABLE` and `IPC_CONTRACT_H` are the explicit-only targets
167
+ #: it excludes (`generate_cmd.EXPLICIT_ONLY_TARGETS`) but `--target` still
168
+ #: accepts by name.
169
+ _TARGET_VALUES = " ".join(
170
+ (*ALL_EMIT_MODES, ZEPHYR_BOARD, COMPOSED_ROUTE_TABLE, IPC_CONTRACT_H)
171
+ )
172
+ #: The 31 subcommand names, in the captured scripts' own order (tan-cli#503).
173
+ #: fish needs them TWICE -- once as the command list it offers, and once as
174
+ #: the `not __fish_seen_subcommand_from ...` condition that decides whether a
175
+ #: subcommand has been typed yet -- so they are spliced rather than written
176
+ #: out a second time. `tests/commands/test_completion_command.py` diffs this
177
+ #: against `tan.cli._SUBCOMMAND_NAMES`; the module itself cannot import
178
+ #: `tan.cli` (import cycle -- see the docstring), the test can.
179
+ _COMMANDS_MARK = "@COMMANDS@"
180
+ _COMMAND_NAMES = (
181
+ "validate generate init scaffold examples doctor completion diff presets "
182
+ "pinmux explain inspect trace debug-config support-bundle sdk bootstrap "
183
+ "build kconfig image flash run clean size migrate lock quality "
184
+ "model monitor new-som faultdecode probe reset"
185
+ )
186
+
187
+
188
+ def _fill_formats(template: str) -> str:
189
+ """Splice the declared `--format`/`--target` value lists into one captured
190
+ script.
191
+
192
+ The oracle generated its completions from the same clap `ValueEnum` that
193
+ fed its parser and its `--help`, so all three surfaces agreed by
194
+ construction. This port emits TEXT rather than walking a live command
195
+ graph (`completion_cmd` cannot import `tan.cli` -- that is an import
196
+ cycle), so the equivalent guarantee is: never type a value list into the
197
+ script text, splice it from the enum, and let
198
+ `tests/commands/test_output_format.py` diff the emitted script against
199
+ what the parser really accepts, per command.
200
+ """
201
+ return (
202
+ template.replace(_FORMATS_MARK, " ".join(format_values(OutputFormat)))
203
+ .replace(_WIDE_FORMATS_MARK, " ".join(format_values(ValidateOutputFormat)))
204
+ .replace(_WIDE_COMMANDS_ALT_MARK, "|".join(WIDE_FORMAT_COMMANDS))
205
+ .replace(_WIDE_COMMANDS_LIST_MARK, " ".join(WIDE_FORMAT_COMMANDS))
206
+ .replace(_TARGETS_MARK, _TARGET_VALUES)
207
+ .replace(_COMMANDS_MARK, _COMMAND_NAMES)
208
+ )
209
+
210
+
211
+ #: Verbatim bash completion script, captured from the reference oracle.
212
+ _BASH_TEMPLATE = """# tan CLI bash completion
213
+ _tan_complete() {
214
+ local cur prev words cword
215
+
216
+ COMPREPLY=()
217
+ cur="${COMP_WORDS[COMP_CWORD]}"
218
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
219
+ cword=${COMP_CWORD}
220
+
221
+ local commands="validate generate init scaffold examples doctor completion diff presets pinmux explain inspect trace debug-config support-bundle sdk bootstrap build kconfig image flash run clean size migrate lock quality model monitor new-som faultdecode probe reset"
222
+ # `--version` is deliberately NOT in `global_flags` but IS in `root_flags`
223
+ # (tan-cli#503): it is root-only, and no subcommand prints a version for it
224
+ # -- see the module docstring's fifth exception for the measurement on all
225
+ # 32. Known and NOT fixed here: the root list is offered wherever no
226
+ # subcommand has been typed, yet the bare root command accepts far less than
227
+ # it offers (`tan --sdk-root /x --version`, `tan --verbose --version` and
228
+ # `tan --ci --version` all exit 2 -- `cli._reorder_global_flags` relocates a
229
+ # global flag ONTO the subcommand, and there is none here). Narrowing that
230
+ # list changes what `tan <TAB>` offers, which is beyond tan-cli#503's
231
+ # report, and it behaves identically before and after this change.
232
+ local global_flags="--project --board-yaml --sdk-root --target --all --format --verbose --quiet --no-color --non-interactive --ci --help"
233
+ local root_flags="$global_flags --version"
234
+
235
+ # ONE subcommand scan, three consumers below (tan-cli#503). A fixed word
236
+ # index is only the subcommand when nothing precedes it -- but every global
237
+ # flag is typeable BEFORE the subcommand (`tan --sdk-root /x validate
238
+ # --format <TAB>`, the exact pre-subcommand shape `output_format.py`'s
239
+ # docstring names as the one alp-sdk-vscode's `withSdkRoot` actually uses),
240
+ # so word 1 there is `--sdk-root`. That mis-selected the `--format` value
241
+ # list (fixed already), AND made `$cword -eq 1` miss, so `tan --sdk-root /x
242
+ # <TAB>` offered not one subcommand name, AND made the per-command `case`
243
+ # miss, so `tan --sdk-root /x size --<TAB>` offered no `--build-root`.
244
+ # Scan for the real subcommand instead of assuming its position: skip every
245
+ # global flag that consumes a following value, and the first bare word left
246
+ # is the subcommand (or "" if none was typed yet).
247
+ #
248
+ # bash does NOT hand this function argv: it hands it argv re-split on
249
+ # `$COMP_WORDBREAKS`, which contains `=` and `:` by default. Measured in
250
+ # bash 5.2.21 under a pty, `tan --sdk-root=/x size --<TAB>` arrives as
251
+ # `(tan --sdk-root = /x size --)` with `COMP_CWORD=5`, and
252
+ # `tan --sdk-root C:/proj size --<TAB>` as `(tan --sdk-root C : /proj size
253
+ # --)` with `COMP_CWORD=6`. Plain skip-N arithmetic lands on `=` or `:` in
254
+ # both, so the scan has to step over a `=` that follows a value flag, and
255
+ # to treat a bare word as the subcommand only when it IS one of the 31.
256
+ # `--sdk-root=/x` is a supported argv (`tan --sdk-root=/nonexistent
257
+ # validate` parses and runs), so this is not a hypothetical shape.
258
+ local value_flags="--project --board-yaml --sdk-root --target --format"
259
+ local subcmd="" at_value=0 i=1 w skip vf c
260
+ while [[ $i -lt $cword ]]; do
261
+ w="${COMP_WORDS[$i]}"
262
+ if [[ "$w" == --*=* ]]; then
263
+ # Self-contained; only reachable with a `$COMP_WORDBREAKS` that drops
264
+ # `=`, since the default splits this into three words instead.
265
+ i=$((i + 1))
266
+ continue
267
+ fi
268
+ if [[ "$w" == --* ]]; then
269
+ skip=0
270
+ for vf in $value_flags; do
271
+ [[ "$w" == "$vf" ]] && skip=1 && break
272
+ done
273
+ # `--flag` `=` `value`: step over the separator as well as the value.
274
+ [[ $skip -eq 1 && "${COMP_WORDS[$((i + 1))]}" == "=" ]] && skip=2
275
+ i=$((i + 1 + skip))
276
+ continue
277
+ fi
278
+ for c in $commands; do
279
+ [[ "$w" == "$c" ]] && subcmd="$w" && break
280
+ done
281
+ [[ -n "$subcmd" ]] && break
282
+ # A bare word that is not a command name is a wordbreak fragment of some
283
+ # value (`C`, `:`, `/proj`); step over it rather than mistake it for the
284
+ # subcommand. The positional skip above still protects a value that
285
+ # happens to BE a command name (`tan --sdk-root size validate`).
286
+ i=$((i + 1))
287
+ done
288
+ # The word being completed sits in a flag's VALUE slot (`tan --sdk-root
289
+ # <TAB>` -- a path, and `tan --sdk-root=<TAB>`, whose `prev` is the split-off
290
+ # `=`), not the subcommand slot. Offering 31 command names there would be a
291
+ # new wrong answer, so this shape keeps the pre-existing fall-through.
292
+ for vf in $value_flags; do
293
+ [[ "$prev" == "$vf" ]] && at_value=1 && break
294
+ done
295
+ [[ "$prev" == "=" ]] && at_value=1
296
+
297
+ if [[ "$prev" == "--format" ]]; then
298
+ local formats="@FORMATS@"
299
+ case "$subcmd" in
300
+ @WIDE_COMMANDS_ALT@) formats="@WIDE_FORMATS@" ;;
301
+ esac
302
+ COMPREPLY=( $(compgen -W "$formats" -- "$cur") )
303
+ return
304
+ fi
305
+
306
+ if [[ "$prev" == "--shell" ]]; then
307
+ COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
308
+ return
309
+ fi
310
+
311
+ if [[ -z "$subcmd" && $at_value -eq 0 ]]; then
312
+ COMPREPLY=( $(compgen -W "$commands $root_flags" -- "$cur") )
313
+ return
314
+ fi
315
+
316
+ case "$subcmd" in
317
+ validate)
318
+ COMPREPLY=( $(compgen -W "$global_flags --offline" -- "$cur") )
319
+ ;;
320
+ generate)
321
+ COMPREPLY=( $(compgen -W "$global_flags --force --core" -- "$cur") )
322
+ ;;
323
+ explain)
324
+ COMPREPLY=( $(compgen -W "$global_flags --template --code" -- "$cur") )
325
+ ;;
326
+ init)
327
+ COMPREPLY=( $(compgen -W "$global_flags --template --from-example --name --destination --som --cores --preview --force" -- "$cur") )
328
+ ;;
329
+ scaffold)
330
+ COMPREPLY=( $(compgen -W "$global_flags --template --name --destination --preview --force" -- "$cur") )
331
+ ;;
332
+ probe)
333
+ COMPREPLY=( $(compgen -W "identify read $global_flags --core --project --build-root --probe-serial --probe-usb-path --jlink" -- "$cur") )
334
+ ;;
335
+ reset)
336
+ COMPREPLY=( $(compgen -W "$global_flags --project --pulse-ms --confirm-console --expect --confirm-baud --confirm-timeout --confirm-window --probe-serial --probe-usb-path --jlink" -- "$cur") )
337
+ ;;
338
+ diff|presets)
339
+ COMPREPLY=( $(compgen -W "$global_flags" -- "$cur") )
340
+ ;;
341
+ examples)
342
+ COMPREPLY=( $(compgen -W "$global_flags --filter" -- "$cur") )
343
+ ;;
344
+ completion)
345
+ COMPREPLY=( $(compgen -W "$global_flags --shell" -- "$cur") )
346
+ ;;
347
+ pinmux)
348
+ COMPREPLY=( $(compgen -W "$global_flags --sku --family" -- "$cur") )
349
+ ;;
350
+ doctor)
351
+ COMPREPLY=( $(compgen -W "$global_flags --build --fix" -- "$cur") )
352
+ ;;
353
+ inspect)
354
+ COMPREPLY=( $(compgen -W "$global_flags --path --show-origin" -- "$cur") )
355
+ ;;
356
+ trace)
357
+ COMPREPLY=( $(compgen -W "$global_flags --path" -- "$cur") )
358
+ ;;
359
+ debug-config)
360
+ COMPREPLY=( $(compgen -W "$global_flags --target-kind --server --core --pre-launch-task --svd --preview" -- "$cur") )
361
+ ;;
362
+ support-bundle)
363
+ COMPREPLY=( $(compgen -W "$global_flags --destination --target-kind --server --path" -- "$cur") )
364
+ ;;
365
+ sdk)
366
+ COMPREPLY=( $(compgen -W "$global_flags list install current switch --destination --global" -- "$cur") )
367
+ ;;
368
+ bootstrap)
369
+ COMPREPLY=( $(compgen -W "$global_flags --no-pip --no-west --print-env --allow-partial --workspace" -- "$cur") )
370
+ ;;
371
+ build)
372
+ COMPREPLY=( $(compgen -W "$global_flags --plan-from --materialise --native --no-auto-bootstrap --pristine" -- "$cur") )
373
+ ;;
374
+ kconfig)
375
+ COMPREPLY=( $(compgen -W "$global_flags --core" -- "$cur") )
376
+ ;;
377
+ image)
378
+ COMPREPLY=( $(compgen -W "$global_flags --build-root" -- "$cur") )
379
+ ;;
380
+ flash)
381
+ COMPREPLY=( $(compgen -W "$global_flags --build-root --dry-run --core --helper --skip-missing-tools" -- "$cur") )
382
+ ;;
383
+ run)
384
+ COMPREPLY=( $(compgen -W "$global_flags --flash --core" -- "$cur") )
385
+ ;;
386
+ clean)
387
+ COMPREPLY=( $(compgen -W "$global_flags --build-root --dry-run" -- "$cur") )
388
+ ;;
389
+ size)
390
+ COMPREPLY=( $(compgen -W "$global_flags --build-root --board --fail-over-budget" -- "$cur") )
391
+ ;;
392
+ *)
393
+ COMPREPLY=( $(compgen -W "$global_flags" -- "$cur") )
394
+ ;;
395
+ esac
396
+ }
397
+
398
+ complete -F _tan_complete tan
399
+ """
400
+
401
+ #: Verbatim zsh completion script, captured from the reference oracle.
402
+ _ZSH_TEMPLATE = """#compdef tan
403
+
404
+ _tan() {
405
+ local -a commands
406
+ commands=(
407
+ 'validate:Validate board.yaml config'
408
+ 'generate:Generate derived artifacts'
409
+ 'init:Initialize a starter project'
410
+ 'scaffold:Scaffold module files'
411
+ 'examples:List SDK example projects'
412
+ 'doctor:Run debug and environment checks'
413
+ 'completion:Generate shell completion script'
414
+ 'diff:Show board normalization diff'
415
+ 'presets:List SDK presets'
416
+ 'probe:Read-only J-Link identity and memory read'
417
+ 'reset:One bare nRESET pulse through J-Link'
418
+ 'pinmux:Show pinmux capability table'
419
+ 'explain:Explain templates and targets'
420
+ 'inspect:Inspect effective resolved values'
421
+ 'trace:Trace generation decisions'
422
+ 'debug-config:Generate a launch.json debug configuration'
423
+ 'support-bundle:Export support bundle payload'
424
+ 'sdk:Manage local SDK installs'
425
+ 'bootstrap:Set up the SDK build environment'
426
+ 'build:Build the project natively'
427
+ 'kconfig:Show the board-scoped Kconfig symbol menu'
428
+ 'image:Assemble a flashable image bundle'
429
+ 'flash:Flash slices and helper MCUs onto the device'
430
+ 'run:Build then run the project'
431
+ 'clean:Remove the build dir and state cache'
432
+ 'size:Report per-slice firmware footprint'
433
+ 'migrate:Migrate board.yaml to the current schema'
434
+ 'lock:Pin/lock library dependencies'
435
+ 'quality:Run board.yaml quality checks'
436
+ 'model:Compile and package board.yaml models'
437
+ 'monitor:Open a serial console to the board'
438
+ 'new-som:Scaffold a new SoM metadata skeleton'
439
+ 'faultdecode:Decode an ARM Cortex-M fault dump'
440
+ )
441
+
442
+ # Every flag `GlobalArgs` marks `global = true` (cli.rs) is accepted by
443
+ # clap on EVERY subcommand — AND on the root command itself, before any
444
+ # subcommand word is even typed — so every arm below splices this in, and
445
+ # so does the root `_arguments -C` call a few lines down. Unlike bash's
446
+ # single `$global_flags` string var, zsh's per-arm `_arguments` has no
447
+ # inheritance of its own (issue #92 MAJOR 2) — a flag left out of an arm
448
+ # here is simply not completable for that subcommand (or, left out of the
449
+ # root call, not completable at `tan --<TAB>` before a subcommand: issue
450
+ # #92 round-3 FINDING 1).
451
+ #
452
+ # `--version` is the ONE flag that is not in that set (tan-cli#503): it is
453
+ # root-only, refused by all 31 subcommands, so it is spliced into the root
454
+ # `_arguments -C` call below and into no arm.
455
+ local -a global_args
456
+ global_args=(
457
+ '--project[Project root]:path:_files -/'
458
+ '--board-yaml[board.yaml path]:path:_files'
459
+ '--sdk-root[SDK root]:path:_files -/'
460
+ '--target[Generation target]'
461
+ '--all[Generate all targets]'
462
+ '--verbose[Verbose output]'
463
+ '--quiet[Quiet output]'
464
+ '--no-color[Disable color output]'
465
+ '--non-interactive[Disable prompts]'
466
+ '--ci[CI mode]'
467
+ '--help[Show help]'
468
+ )
469
+
470
+ # `--format` is appended rather than listed above because its value list is
471
+ # not the same for every command (tan-cli#403): `validate` also completes
472
+ # the two IDE-oriented documents. Chosen here, once, so every `_arguments`
473
+ # call below still splices exactly one `${global_args[@]}` and no arm can
474
+ # drift.
475
+ #
476
+ # `$words[2]` is only the subcommand when nothing precedes it -- but
477
+ # `--format` is itself a global flag typeable BEFORE the subcommand
478
+ # (`tan --sdk-root /x validate --format <TAB>`), so this read at the
479
+ # function top (before `_arguments` reindexes `words`) used to silently
480
+ # fall back to the narrow list for that shape (tan-cli#503). Scan for the
481
+ # real subcommand: skip every global flag that consumes a following value,
482
+ # and the first bare word left is the subcommand.
483
+ local subcmd="" i=2
484
+ while (( i <= ${#words[@]} )); do
485
+ case "$words[i]" in
486
+ --project|--board-yaml|--sdk-root|--target|--format) (( i += 2 )); continue ;;
487
+ --*) (( i += 1 )); continue ;;
488
+ esac
489
+ subcmd="$words[i]"
490
+ break
491
+ done
492
+ case "$subcmd" in
493
+ @WIDE_COMMANDS_ALT@) global_args+=('--format[Output format]:format:(@WIDE_FORMATS@)') ;;
494
+ *) global_args+=('--format[Output format]:format:(@FORMATS@)') ;;
495
+ esac
496
+
497
+ _arguments -C '1:command:->command' '*::arg:->args' "${global_args[@]}" '--version[Show version]'
498
+
499
+ case $state in
500
+ command)
501
+ _describe 'command' commands
502
+ ;;
503
+ args)
504
+ # `$subcmd`, not `$words[2]` (tan-cli#503). In this state `_arguments`
505
+ # has REINDEXED `words` so that `words[1]` is the subcommand and
506
+ # `words[2]` is the word being completed -- measured in zsh 5.9 under a
507
+ # pty, `tan size --<TAB>` reaches here with `words=(size --)` and
508
+ # `CURRENT=2`, so `$words[2]` was `--`. It never matched a subcommand
509
+ # name, so all 21 arms below were unreachable and every command fell to
510
+ # `*)`: `tan size --<TAB>` offered the 12 global flags and none of
511
+ # `--build-root`/`--board`/`--fail-over-budget`. `$subcmd` is computed
512
+ # from the ORIGINAL `words` at the top of this function and is still in
513
+ # scope here (same probe: `subcmd_scan=size`), so it is right both for
514
+ # this reindexing and for a leading global flag.
515
+ case $subcmd in
516
+ validate)
517
+ _arguments '--offline[Offline structural validation only]' "${global_args[@]}"
518
+ ;;
519
+ completion)
520
+ _arguments '--shell[Shell type]:shell:(bash zsh fish)' "${global_args[@]}"
521
+ ;;
522
+ generate)
523
+ _arguments '--force[Overwrite existing files]' '--core[Core id (zephyr-board target)]' "${global_args[@]}"
524
+ ;;
525
+ explain)
526
+ _arguments '--template[Template id]' '--code[Diagnostic or error code]' "${global_args[@]}"
527
+ ;;
528
+ examples)
529
+ _arguments '--filter[Substring match on id/title]' "${global_args[@]}"
530
+ ;;
531
+ init)
532
+ _arguments '--template[Template id]' '--from-example[Example source dir]' '--name[Name value]' '--destination[Output directory]:path:_files -/' '--som[SoM SKU]' '--cores[Cores list]' '--preview[Preview only]' '--force[Overwrite existing files]' "${global_args[@]}"
533
+ ;;
534
+ scaffold)
535
+ _arguments '--template[Template id]' '--name[Name value]' '--destination[Output directory]:path:_files -/' '--preview[Preview only]' '--force[Overwrite existing files]' "${global_args[@]}"
536
+ ;;
537
+ pinmux)
538
+ _arguments '--sku[SoM SKU]' '--family[Pinmux family]' "${global_args[@]}"
539
+ ;;
540
+ doctor)
541
+ _arguments '--build[Build readiness preflight]' '--fix[Auto-repair a fixable blocker]' "${global_args[@]}"
542
+ ;;
543
+ inspect)
544
+ _arguments '--path[Field path]' '--show-origin[Include source metadata]' "${global_args[@]}"
545
+ ;;
546
+ trace)
547
+ _arguments '--path[Field path]' "${global_args[@]}"
548
+ ;;
549
+ debug-config)
550
+ _arguments '--target-kind[Debug target]:target:(zephyr-mcu baremetal-mcu yocto-userspace native-host)' '--server[Debug server]:server:(jlink openocd pyocd gdbserver none)' '--core[Build slice core id]' '--pre-launch-task[VS Code task to run before launching]' '--svd[Path to a user-supplied SVD for the peripheral view]:svd:_files -g "*.svd"' '--preview[Preview only]' "${global_args[@]}"
551
+ ;;
552
+ support-bundle)
553
+ _arguments '--destination[Output directory]:path:_files -/' '--target-kind[Debug target]:target:(zephyr-mcu baremetal-mcu yocto-userspace native-host)' '--server[Debug server]:server:(jlink openocd pyocd gdbserver none)' '--path[Field path]' "${global_args[@]}"
554
+ ;;
555
+ sdk)
556
+ _arguments '1:subcommand:(list install current switch)' '--destination[Cache root]:path:_files -/' '--global[Pin the machine-global default]' "${global_args[@]}"
557
+ ;;
558
+ bootstrap)
559
+ _arguments '--no-pip[Skip pip install]' '--no-west[Skip west init/update]' '--print-env[Print environment lines only]' '--allow-partial[Report success despite a failed dependency install]' '--workspace[Build the workspace at this path]:path:_files -/' "${global_args[@]}"
560
+ ;;
561
+ build)
562
+ _arguments '--plan-from[Read build plan from file]:path:_files' '--materialise[Materialise plan files]' '--native[Build natively]' '--no-auto-bootstrap[Never bootstrap implicitly]' '--pristine[Force-wipe build dirs before dispatch]' "${global_args[@]}"
563
+ ;;
564
+ kconfig)
565
+ _arguments '--core[Core id to scope the menu to]' "${global_args[@]}"
566
+ ;;
567
+ image)
568
+ _arguments '--build-root[Override build root]:path:_files -/' "${global_args[@]}"
569
+ ;;
570
+ flash)
571
+ _arguments '--build-root[Override build root]:path:_files -/' '--dry-run[Print planned commands only]' '--core[Flash only this core]' '--helper[Flash only this helper MCU]' '--skip-missing-tools[Skip entries with no tool on PATH]' "${global_args[@]}"
572
+ ;;
573
+ run)
574
+ _arguments '--flash[Flash the board after building]' '--core[Flash only this core]' "${global_args[@]}"
575
+ ;;
576
+ clean)
577
+ _arguments '--build-root[Override build root]:path:_files -/' '--dry-run[List targets without removing]' "${global_args[@]}"
578
+ ;;
579
+ size)
580
+ _arguments '--build-root[Override build root]:path:_files -/' '--board[Override SoM SKU]' '--fail-over-budget[Exit non-zero over budget]' "${global_args[@]}"
581
+ ;;
582
+ *)
583
+ _arguments "${global_args[@]}"
584
+ ;;
585
+ esac
586
+ ;;
587
+ esac
588
+ }
589
+
590
+ compdef _tan tan
591
+ """
592
+
593
+ #: Verbatim fish completion script, captured from the reference oracle.
594
+ _FISH_TEMPLATE = """complete -c tan -f
595
+ complete -c tan -n 'not __fish_seen_subcommand_from @COMMANDS@' -a '@COMMANDS@'
596
+ complete -c tan -l project -d 'Project root'
597
+ complete -c tan -l board-yaml -d 'board.yaml path'
598
+ complete -c tan -l sdk-root -d 'SDK root path'
599
+ complete -c tan -l target -d 'Generation target' -a '@TARGETS@'
600
+ complete -c tan -l all -d 'Generate all targets'
601
+ complete -c tan -n 'not __fish_seen_subcommand_from @WIDE_COMMANDS_LIST@' -l format -d 'Output format' -a '@FORMATS@'
602
+ complete -c tan -n '__fish_seen_subcommand_from @WIDE_COMMANDS_LIST@' -l format -d 'Output format' -a '@WIDE_FORMATS@'
603
+ complete -c tan -l verbose -d 'Verbose output'
604
+ complete -c tan -l quiet -d 'Quiet output'
605
+ complete -c tan -l no-color -d 'Disable color output'
606
+ complete -c tan -l non-interactive -d 'Disable prompts'
607
+ complete -c tan -l ci -d 'CI mode'
608
+ complete -c tan -l help -d 'Show help'
609
+ complete -c tan -n 'not __fish_seen_subcommand_from @COMMANDS@' -l version -d 'Show version'
610
+ complete -c tan -n '__fish_seen_subcommand_from validate' -l offline -d 'Offline structural validation only'
611
+ complete -c tan -n '__fish_seen_subcommand_from generate' -l force -d 'Overwrite existing files'
612
+ complete -c tan -n '__fish_seen_subcommand_from generate' -l core -d 'Core id (zephyr-board target)'
613
+ complete -c tan -n '__fish_seen_subcommand_from explain' -l template -d 'Template id'
614
+ complete -c tan -n '__fish_seen_subcommand_from explain' -l code -d 'Diagnostic or error code'
615
+ complete -c tan -n '__fish_seen_subcommand_from examples' -l filter -d 'Substring match on id/title'
616
+ complete -c tan -n '__fish_seen_subcommand_from init scaffold' -l template -d 'Template id'
617
+ complete -c tan -n '__fish_seen_subcommand_from init' -l from-example -d 'Example source dir'
618
+ complete -c tan -n '__fish_seen_subcommand_from init scaffold' -l name -d 'Name value'
619
+ complete -c tan -n '__fish_seen_subcommand_from init scaffold' -l destination -d 'Destination path'
620
+ complete -c tan -n '__fish_seen_subcommand_from init' -l som -d 'SoM SKU'
621
+ complete -c tan -n '__fish_seen_subcommand_from init' -l cores -d 'Cores list'
622
+ complete -c tan -n '__fish_seen_subcommand_from init scaffold' -l preview -d 'Preview only'
623
+ complete -c tan -n '__fish_seen_subcommand_from init scaffold' -l force -d 'Overwrite existing files'
624
+ complete -c tan -n '__fish_seen_subcommand_from pinmux' -l sku -d 'SoM SKU'
625
+ complete -c tan -n '__fish_seen_subcommand_from pinmux' -l family -d 'Pinmux family'
626
+ complete -c tan -n '__fish_seen_subcommand_from doctor' -l build -d 'Build readiness preflight'
627
+ complete -c tan -n '__fish_seen_subcommand_from doctor' -l fix -d 'Auto-repair a fixable blocker'
628
+ complete -c tan -n '__fish_seen_subcommand_from inspect trace support-bundle' -l path -d 'Field path'
629
+ complete -c tan -n '__fish_seen_subcommand_from inspect' -l show-origin -d 'Include source metadata'
630
+ complete -c tan -n '__fish_seen_subcommand_from debug-config' -l target-kind -d 'Debug target kind' -a 'zephyr-mcu baremetal-mcu yocto-userspace native-host'
631
+ complete -c tan -n '__fish_seen_subcommand_from debug-config' -l server -d 'Debug server' -a 'jlink openocd pyocd gdbserver none'
632
+ complete -c tan -n '__fish_seen_subcommand_from debug-config' -l core -d 'Build slice core id'
633
+ complete -c tan -n '__fish_seen_subcommand_from debug-config' -l pre-launch-task -d 'VS Code task to run before launching'
634
+ complete -c tan -n '__fish_seen_subcommand_from debug-config' -l svd -r -d 'Path to a user-supplied SVD for the peripheral view'
635
+ complete -c tan -n '__fish_seen_subcommand_from debug-config' -l preview -d 'Preview only'
636
+ complete -c tan -n '__fish_seen_subcommand_from support-bundle' -l destination -d 'Destination path'
637
+ complete -c tan -n '__fish_seen_subcommand_from support-bundle' -l target-kind -d 'Debug target kind' -a 'zephyr-mcu baremetal-mcu yocto-userspace native-host'
638
+ complete -c tan -n '__fish_seen_subcommand_from support-bundle' -l server -d 'Debug server' -a 'jlink openocd pyocd gdbserver none'
639
+ complete -c tan -n '__fish_seen_subcommand_from completion' -l shell -d 'Shell type' -a 'bash zsh fish'
640
+ complete -c tan -n '__fish_seen_subcommand_from sdk' -a 'list install current switch'
641
+ complete -c tan -n '__fish_seen_subcommand_from sdk' -l destination -d 'Cache root'
642
+ complete -c tan -n '__fish_seen_subcommand_from sdk' -l global -d 'Pin the machine-global default'
643
+ complete -c tan -n '__fish_seen_subcommand_from bootstrap' -l no-pip -d 'Skip pip install'
644
+ complete -c tan -n '__fish_seen_subcommand_from bootstrap' -l no-west -d 'Skip west init/update'
645
+ complete -c tan -n '__fish_seen_subcommand_from bootstrap' -l print-env -d 'Print environment lines only'
646
+ complete -c tan -n '__fish_seen_subcommand_from bootstrap' -l allow-partial -d 'Report success despite a failed dependency install'
647
+ complete -c tan -n '__fish_seen_subcommand_from bootstrap' -l workspace -d 'Build the workspace at this path'
648
+ complete -c tan -n '__fish_seen_subcommand_from build' -l plan-from -d 'Read build plan from file'
649
+ complete -c tan -n '__fish_seen_subcommand_from build' -l materialise -d 'Materialise plan files'
650
+ complete -c tan -n '__fish_seen_subcommand_from build' -l native -d 'Build natively'
651
+ complete -c tan -n '__fish_seen_subcommand_from build' -l no-auto-bootstrap -d 'Never bootstrap implicitly'
652
+ complete -c tan -n '__fish_seen_subcommand_from build' -l pristine -d 'Force-wipe build dirs before dispatch'
653
+ complete -c tan -n '__fish_seen_subcommand_from kconfig' -l core -d 'Core id to scope the menu to'
654
+ complete -c tan -n '__fish_seen_subcommand_from image flash clean size' -l build-root -d 'Override build root'
655
+ complete -c tan -n '__fish_seen_subcommand_from flash' -l dry-run -d 'Print planned commands only'
656
+ complete -c tan -n '__fish_seen_subcommand_from flash' -l core -d 'Flash only this core'
657
+ complete -c tan -n '__fish_seen_subcommand_from flash' -l helper -d 'Flash only this helper MCU'
658
+ complete -c tan -n '__fish_seen_subcommand_from flash' -l skip-missing-tools -d 'Skip entries with no tool on PATH'
659
+ complete -c tan -n '__fish_seen_subcommand_from run' -l flash -d 'Flash the board after building'
660
+ complete -c tan -n '__fish_seen_subcommand_from run' -l core -d 'Flash only this core'
661
+ complete -c tan -n '__fish_seen_subcommand_from clean' -l dry-run -d 'List targets without removing'
662
+ complete -c tan -n '__fish_seen_subcommand_from size' -l board -d 'Override SoM SKU'
663
+ complete -c tan -n '__fish_seen_subcommand_from size' -l fail-over-budget -d 'Exit non-zero over budget'
664
+ """
665
+
666
+ #: The three scripts as EMITTED: the captures above with their `--format`
667
+ #: value lists spliced in from the CLI's own enums (tan-cli#403).
668
+ BASH_SCRIPT = _fill_formats(_BASH_TEMPLATE)
669
+ ZSH_SCRIPT = _fill_formats(_ZSH_TEMPLATE)
670
+ FISH_SCRIPT = _fill_formats(_FISH_TEMPLATE)
671
+
672
+
673
+ def resolve_shell(raw: str | None) -> str | None:
674
+ """Mirror Rust's `resolve_shell`: default `bash`; trim + lowercase; else
675
+ `None` (unsupported)."""
676
+ normalized = (raw if raw is not None else "bash").strip().lower()
677
+ if normalized in ("bash", "zsh", "fish"):
678
+ return normalized
679
+ return None
680
+
681
+
682
+ def script_for(shell: str) -> str:
683
+ """Select the embedded script for a resolved `shell` name. Mirrors Rust's
684
+ `script_for`, including its fallback: an unrecognised value (unreachable
685
+ from `completion()` below, since that already rejected it) falls back to
686
+ bash rather than raising."""
687
+ if shell == "zsh":
688
+ return ZSH_SCRIPT
689
+ if shell == "fish":
690
+ return FISH_SCRIPT
691
+ return BASH_SCRIPT
692
+
693
+
694
+ def _null_project() -> Project:
695
+ """`completion` is project-agnostic: no root, no board.yaml, ever."""
696
+ return Project(root=None, board_yaml=None)
697
+
698
+
699
+ def completion(
700
+ ctx: typer.Context,
701
+ shell: str = typer.Option(
702
+ None,
703
+ "--shell",
704
+ metavar="SHELL",
705
+ help="Target shell (bash, zsh, or fish). Defaults to bash.",
706
+ ),
707
+ project: str = typer.Option(
708
+ None, "--project", metavar="PATH", help="Project root (defaults to '.')."
709
+ ),
710
+ board_yaml: str = typer.Option(
711
+ None, "--board-yaml", metavar="PATH", help="Explicit board.yaml path."
712
+ ),
713
+ sdk_root: str = typer.Option(
714
+ None, "--sdk-root", metavar="PATH", help="alp-sdk checkout root."
715
+ ),
716
+ output_format: OutputFormat = typer.Option(None, "--format", help=FORMAT_HELP),
717
+ quiet: bool = typer.Option(False, "--quiet", hidden=True),
718
+ verbose: bool = typer.Option(False, "--verbose", hidden=True),
719
+ no_color: bool = typer.Option(False, "--no-color", hidden=True),
720
+ non_interactive: bool = typer.Option(False, "--non-interactive", hidden=True),
721
+ ci: bool = typer.Option(False, "--ci", hidden=True),
722
+ target: str = typer.Option(None, "--target", hidden=True),
723
+ all_targets: bool = typer.Option(False, "--all", hidden=True),
724
+ ) -> None:
725
+ """Emit a shell completion script (bash, zsh, or fish)."""
726
+ # `project`/`board_yaml`/`sdk_root` are clap `GlobalArgs` (`global = true`)
727
+ # this command never reads -- `completion` is project-agnostic on the
728
+ # oracle too (see `_null_project` above). `quiet`/`verbose`/`no_color`/
729
+ # `non_interactive`/`ci`/`target`/`all_targets` are the rest of that same
730
+ # set: declared ONLY so the argv surface matches clap (`tan completion
731
+ # --ci` must exit 0, not a Click usage error), never read. Mirrors
732
+ # `clean_cmd.clean`'s identical block.
733
+ del project, board_yaml, sdk_root
734
+ del quiet, verbose, no_color, non_interactive, ci, target, all_targets
735
+
736
+ # `--format` is accepted BEFORE the subcommand too (clap's `global =
737
+ # true`; verified against the oracle: `tan --format json completion
738
+ # --shell zsh` reaches this command and emits the envelope). The root
739
+ # callback (`cli.py`) records a leading value on `ctx.obj`; this option
740
+ # overrides it when repeated after the subcommand name -- that precedence
741
+ # is `resolve_format`'s, written once for the eleven commands that honour
742
+ # the leading position. An explicit `--format ""` still exits 2 (measured
743
+ # against the oracle: `tan completion --format ""` -> rc 2); since
744
+ # tan-cli#403 the declared enum refuses it at PARSE time, so no truthiness
745
+ # test in this body can mistake it for an absent flag.
746
+ resolved_format = resolve_format(output_format, ctx.obj, choices=OutputFormat)
747
+ json_mode = resolved_format == "json"
748
+
749
+ resolved_shell = resolve_shell(shell)
750
+ if resolved_shell is None:
751
+ if json_mode:
752
+ emit(
753
+ Envelope(
754
+ "completion",
755
+ _null_project(),
756
+ {"schemaVersion": DATA_SCHEMA_VERSION, "shell": "bash", "script": ""},
757
+ [Issue(SHELL_UNSUPPORTED_CODE, "error", SHELL_UNSUPPORTED_MESSAGE)],
758
+ ExitCode.RUNTIME_FAILURE,
759
+ )
760
+ )
761
+ else:
762
+ # stderr, like every other command's text-mode error line; stdout
763
+ # stays empty on this path (matches the oracle, measured).
764
+ print(SHELL_UNSUPPORTED_TEXT_LINE, file=sys.stderr)
765
+ raise typer.Exit(int(ExitCode.RUNTIME_FAILURE))
766
+
767
+ script = script_for(resolved_shell)
768
+ if json_mode:
769
+ emit(
770
+ Envelope(
771
+ "completion",
772
+ _null_project(),
773
+ {"schemaVersion": DATA_SCHEMA_VERSION, "shell": resolved_shell, "script": script},
774
+ [],
775
+ ExitCode.SUCCESS,
776
+ )
777
+ )
778
+ else:
779
+ # The script IS the payload (README: "tan completion --shell zsh
780
+ # emits a completion script"), and the only sane way to consume it is
781
+ # `eval "$(tan completion --shell zsh)"` / `> file` stdout capture --
782
+ # so print it straight to stdout, not through the stderr-only
783
+ # text-line convention every other command's text mode uses. Mirrors
784
+ # `completion.rs`'s own `println!` plus its comment on why.
785
+ print(script)
786
+ raise typer.Exit(int(ExitCode.SUCCESS))