dotman-git 1.3.0__tar.gz → 1.4.0__tar.gz

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 (187) hide show
  1. {dotman_git-1.3.0 → dotman_git-1.4.0}/CHANGELOG.md +44 -0
  2. {dotman_git-1.3.0 → dotman_git-1.4.0}/PKG-INFO +9 -10
  3. {dotman_git-1.3.0 → dotman_git-1.4.0}/README.md +7 -7
  4. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/__init__.py +1 -1
  5. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/__init__.py +6 -2
  6. dotman_git-1.4.0/dot_man/cli/bootstrap_cmd.py +240 -0
  7. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/common.py +22 -0
  8. dotman_git-1.4.0/dot_man/cli/completion_engine.py +445 -0
  9. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/completions_cmd.py +36 -21
  10. dotman_git-1.4.0/dot_man/cli/config_cmd.py +243 -0
  11. dotman_git-1.3.0/dot_man/cli/config_cmd.py → dotman_git-1.4.0/dot_man/cli/config_tutorial.py +114 -378
  12. dotman_git-1.4.0/dot_man/cli/hooks_cmd.py +66 -0
  13. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/main.py +7 -2
  14. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/navigate_cmd.py +16 -286
  15. dotman_git-1.4.0/dot_man/cli/navigate_preview.py +189 -0
  16. dotman_git-1.4.0/dot_man/cli/save_cmd.py +190 -0
  17. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/status_cmd.py +43 -6
  18. dotman_git-1.4.0/dot_man/cli/vault_cmd.py +93 -0
  19. dotman_git-1.4.0/dot_man/completions/dot-man.fish +13 -0
  20. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/core.py +8 -4
  21. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/dotman_config.py +2 -0
  22. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/files.py +4 -4
  23. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/section.py +13 -5
  24. dotman_git-1.4.0/dot_man/shared_sync.py +301 -0
  25. {dotman_git-1.3.0 → dotman_git-1.4.0}/dotman_git.egg-info/PKG-INFO +9 -10
  26. {dotman_git-1.3.0 → dotman_git-1.4.0}/dotman_git.egg-info/SOURCES.txt +14 -1
  27. {dotman_git-1.3.0 → dotman_git-1.4.0}/pyproject.toml +10 -5
  28. dotman_git-1.4.0/tests/test_audit_cmd_extended.py +217 -0
  29. dotman_git-1.4.0/tests/test_bootstrap_cmd.py +269 -0
  30. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_completion.py +9 -11
  31. dotman_git-1.4.0/tests/test_completion_engine.py +333 -0
  32. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_completions_cmd.py +33 -3
  33. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_config_cmd.py +12 -9
  34. dotman_git-1.4.0/tests/test_export_cmd.py +184 -0
  35. dotman_git-1.4.0/tests/test_revert_cmd_extended.py +106 -0
  36. dotman_git-1.4.0/tests/test_shared_sync.py +330 -0
  37. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_shell_completions.py +9 -11
  38. dotman_git-1.4.0/tests/test_tag_cmd.py +205 -0
  39. dotman_git-1.4.0/tests/test_vault.py +281 -0
  40. dotman_git-1.4.0/tests/test_vault_cmd.py +165 -0
  41. dotman_git-1.4.0/tests/test_verify_cmd.py +276 -0
  42. dotman_git-1.3.0/dot_man/completions/dot-man.fish +0 -58
  43. dotman_git-1.3.0/dot_man/completions/install.sh +0 -26
  44. dotman_git-1.3.0/tests/test_export_cmd.py +0 -94
  45. dotman_git-1.3.0/tests/test_tag_cmd.py +0 -105
  46. dotman_git-1.3.0/tests/test_vault.py +0 -148
  47. dotman_git-1.3.0/tests/test_verify_cmd.py +0 -93
  48. {dotman_git-1.3.0 → dotman_git-1.4.0}/CONTRIBUTING.md +0 -0
  49. {dotman_git-1.3.0 → dotman_git-1.4.0}/LICENSE +0 -0
  50. {dotman_git-1.3.0 → dotman_git-1.4.0}/MANIFEST.in +0 -0
  51. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/backups.py +0 -0
  52. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/branch_ops.py +0 -0
  53. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/add_cmd.py +0 -0
  54. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/audit_cmd.py +0 -0
  55. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/backup_cmd.py +0 -0
  56. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/branch_cmd.py +0 -0
  57. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/clean_cmd.py +0 -0
  58. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/completions.py +0 -0
  59. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/deploy_cmd.py +0 -0
  60. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/discover_cmd.py +0 -0
  61. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/doctor_cmd.py +0 -0
  62. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/edit_cmd.py +0 -0
  63. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/encrypt_cmd.py +0 -0
  64. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/export_cmd.py +0 -0
  65. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/import_cmd.py +0 -0
  66. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/init_cmd.py +0 -0
  67. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/interface.py +0 -0
  68. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/log_cmd.py +0 -0
  69. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/onboarding.py +0 -0
  70. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/profile_cmd.py +0 -0
  71. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/remote_cmd.py +0 -0
  72. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/restore_cmd.py +0 -0
  73. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/revert_cmd.py +0 -0
  74. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/rollback_cmd.py +0 -0
  75. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/show_cmd.py +0 -0
  76. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/switch_cmd.py +0 -0
  77. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/tag_cmd.py +0 -0
  78. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/template_cmd.py +0 -0
  79. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/tui_cmd.py +0 -0
  80. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/verify_cmd.py +0 -0
  81. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/cli/watch_cmd.py +0 -0
  82. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/completions/_dot-man.zsh +0 -0
  83. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/completions/dot-man.bash +0 -0
  84. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/config.py +0 -0
  85. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/config_detector.py +0 -0
  86. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/constants.py +0 -0
  87. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/encryption.py +0 -0
  88. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/exceptions.py +0 -0
  89. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/global_config.py +0 -0
  90. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/hooks.py +0 -0
  91. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/interactive.py +0 -0
  92. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/lock.py +0 -0
  93. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/merge.py +0 -0
  94. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/operations.py +0 -0
  95. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/py.typed +0 -0
  96. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/save_deploy_ops.py +0 -0
  97. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/secrets.py +0 -0
  98. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/status_ops.py +0 -0
  99. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/tui_log.py +0 -0
  100. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/ui.py +0 -0
  101. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/utils.py +0 -0
  102. {dotman_git-1.3.0 → dotman_git-1.4.0}/dot_man/vault.py +0 -0
  103. {dotman_git-1.3.0 → dotman_git-1.4.0}/dotman_git.egg-info/dependency_links.txt +0 -0
  104. {dotman_git-1.3.0 → dotman_git-1.4.0}/dotman_git.egg-info/entry_points.txt +0 -0
  105. {dotman_git-1.3.0 → dotman_git-1.4.0}/dotman_git.egg-info/requires.txt +0 -0
  106. {dotman_git-1.3.0 → dotman_git-1.4.0}/dotman_git.egg-info/top_level.txt +0 -0
  107. {dotman_git-1.3.0 → dotman_git-1.4.0}/setup.cfg +0 -0
  108. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_add_cmd.py +0 -0
  109. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_audit_cmd.py +0 -0
  110. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_backup_cmd.py +0 -0
  111. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_backups.py +0 -0
  112. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_branch_cmd.py +0 -0
  113. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_branch_ops.py +0 -0
  114. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_clean.py +0 -0
  115. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_cli_commands.py +0 -0
  116. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_cli_extra.py +0 -0
  117. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_cli_help.py +0 -0
  118. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_cli_revert.py +0 -0
  119. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_cli_v1.py +0 -0
  120. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_common_v1.py +0 -0
  121. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_comprehensive.py +0 -0
  122. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_config.py +0 -0
  123. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_config_cmd_v1.py +0 -0
  124. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_config_ops_v1.py +0 -0
  125. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_config_preservation.py +0 -0
  126. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_config_section.py +0 -0
  127. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_core.py +0 -0
  128. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_core_extended.py +0 -0
  129. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_core_v1.py +0 -0
  130. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_custom_secrets.py +0 -0
  131. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_deploy_cmd.py +0 -0
  132. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_discover_cmd.py +0 -0
  133. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_doctor_cmd.py +0 -0
  134. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_dotman_config.py +0 -0
  135. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_edit_cmd.py +0 -0
  136. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_encrypt_cmd.py +0 -0
  137. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_encryption.py +0 -0
  138. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_exceptions.py +0 -0
  139. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_files_atomic.py +0 -0
  140. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_files_comprehensive.py +0 -0
  141. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_files_extended.py +0 -0
  142. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_final_push.py +0 -0
  143. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_fixtures.py +0 -0
  144. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_git_manager.py +0 -0
  145. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_git_manager_extended.py +0 -0
  146. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_global_config.py +0 -0
  147. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_hooks.py +0 -0
  148. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_hooks_new.py +0 -0
  149. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_import_cmd.py +0 -0
  150. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_init_cmd.py +0 -0
  151. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_integration.py +0 -0
  152. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_interactive.py +0 -0
  153. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_lock.py +0 -0
  154. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_log_checkout_tag.py +0 -0
  155. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_log_cmd.py +0 -0
  156. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_log_cmd_v1.py +0 -0
  157. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_merge.py +0 -0
  158. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_more_integration.py +0 -0
  159. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_navigate_cmd.py +0 -0
  160. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_navigate_helpers.py +0 -0
  161. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_navigate_switch_v1.py +0 -0
  162. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_onboarding.py +0 -0
  163. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_operations.py +0 -0
  164. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_ops_v1.py +0 -0
  165. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_performance_logic.py +0 -0
  166. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_profile_cmd.py +0 -0
  167. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_profile_cmd_v1.py +0 -0
  168. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_profile_extra.py +0 -0
  169. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_remote_cmd.py +0 -0
  170. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_restore_cmd.py +0 -0
  171. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_rollback_cmd.py +0 -0
  172. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_save_cmd.py +0 -0
  173. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_save_deploy_ops.py +0 -0
  174. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_secrets.py +0 -0
  175. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_section.py +0 -0
  176. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_show_cmd.py +0 -0
  177. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_status_cmd.py +0 -0
  178. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_status_ops.py +0 -0
  179. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_switch_cmd.py +0 -0
  180. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_switch_enhancements.py +0 -0
  181. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_symlink_deploy.py +0 -0
  182. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_template_cmd.py +0 -0
  183. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_template_cmd_v1.py +0 -0
  184. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_template_extra.py +0 -0
  185. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_ui.py +0 -0
  186. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_utils.py +0 -0
  187. {dotman_git-1.3.0 → dotman_git-1.4.0}/tests/test_watch_cmd.py +0 -0
@@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - **Shell completions for `add` command** — file/directory path completion using `@completes("add", param="path")` decorator
13
+ - **Shell completions for `bootstrap` command** — package manager name completion (`brew`, `apt`, `dnf`, `pacman`, etc.) via `@completes("bootstrap", param="pm")` decorator
14
+ - **19 new documentation pages** — dedicated pages for `init`, `add`, `deploy`, `tag`, `log`, `diff`, `revert`, `import`, `export`, `discover`, `encrypt`, `hooks`, `template`, `profile`, `watch`, `rollback`, `backup`, `verify`, `completions` commands
15
+ - **Docs navigation restructured** — commands grouped by category (Core, Configuration, Inspection, Tags, Encryption, Import/Export, Hooks, Shell)
16
+ - **Roadmap updated** — consistent with v1.4.0 state, removed stale duplicate sections
17
+
18
+ ### Fixed
19
+
20
+ - **`save_cmd.py`** — fixed `_sync_shared_sections` → `sync_shared_sections` name mismatch that caused `NameError` on `--commit` flag
21
+ - **`shared_sync.py`** — `_sync_branch` now records overridden sections in `report.skipped` even when other shared sections are still wanted (partial overrides)
22
+ - **`test_shared_sync.py`** — fixed test expectations for `test_edits_reach_non_overriding_branches` (work overrides fish, so only server gets propagated), `test_unparsable_target_config_is_skipped_safely` (fixed `run_git()` call to use `subprocess.run` with `input=`), `test_source_branch_never_modified` (fixed log assertion to use `-1` flag)
23
+ - **Tests** — all 1700 tests passing (up from 1691), 1 skipped
24
+
25
+ ### Changed
26
+
27
+ - **Repository cleanup** — single source of truth for completion scripts (`dot_man/completions/`); removed the duplicated `scripts/completions/` copies and the unused packaged `install.sh`. `install.sh` now installs completions via the built-in `dot-man completions` command. Removed the stale `docs/development/roadmap.md` duplicate and surfaced `docs/roadmap.md` in the docs site nav.
28
+
29
+ ### Removed
30
+
31
+ - **Tracked artifacts** — dropped `coverage.json` (256 KB CI artifact) and the author's personal `config.toml` from version control; both now gitignored along with benchmark output.
32
+ - **Local clutter** — `.gitignore` fixed (broken merged pattern) and extended: coverage reports, benchmarks, stale build/dist/site outputs no longer pollute the working tree.
33
+
34
+ ## [1.4.0] - 2026-08-25
35
+
36
+ ### Added
37
+
38
+ - **Decorator-driven completion engine** — new `dot_man/cli/completion_engine.py` gathers commands, subcommands, options, accepted values (static or dynamic) and defaults from the Click CLI definition itself. Extended via the `@completes(path, param=...)` decorator registry; a `(path, param)` registration wins over the generic one.
39
+ - **`dot-man --complete <shell>` protocol** — handled before onboarding/git/side effects in `main()`. Fish output uses `value<TAB>description` lines so descriptions render natively.
40
+ - **Defaults & choices surfaced** — option completions now show `[choices: bash|zsh|fish|all]` and `[default: all]` inline (e.g. `dot-man completions --s<TAB>`).
41
+ - **Alias labeling** — shell shortcuts like `nav`, `cpl`, `aud` are listed as `(alias)` entries instead of indistinguishable duplicates.
42
+ - **`--option=<TAB>` form** — completing after `=` emits full tokens (`--shell=fish`).
43
+ - **Static choice provider** — `config set KEY <TAB>` suggests `true`/`false`.
44
+
45
+ ### Fixed
46
+
47
+ - **Fish completions showed invalid/stale choices** — the hand-written `dot-man.fish` listed missing commands (`backup`, `tag`, `template`, `profile`, `doctor`, ...), treated navigate options as subcommands, and kept deprecated `switch` prominent. It is now a tiny delegating script calling `dot-man --complete fish` on every TAB (wifi-tracker style), so completions can never go stale.
48
+ - **Unformatted fish output** — the old script's naive `string split ","` broke on help text containing commas and dropped descriptions. Tab-separated engine output renders descriptions natively in fish.
49
+
50
+ ### Tests
51
+
52
+ - 37 new tests in `tests/test_completion_engine.py` (registry priority, root/subcommand listing, static choices, defaults display, alias labeling, eq-form values, dynamic git-backed callbacks, request parsing, formatting, entry safety). Suite: 1682 passed, coverage 84% (up from 81%).
53
+
10
54
  ## [1.3.0] - 2026-06-27
11
55
 
12
56
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotman-git
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Dotfile manager with git-powered branching
5
5
  Author-email: Bishoy Ehab <beshoyehabhhmm@gmail.com>
6
6
  License: MIT
@@ -18,7 +18,6 @@ Classifier: License :: OSI Approved :: MIT License
18
18
  Classifier: Operating System :: POSIX :: Linux
19
19
  Classifier: Operating System :: MacOS
20
20
  Classifier: Programming Language :: Python :: 3
21
- Classifier: Programming Language :: Python :: 3.9
22
21
  Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
24
23
  Classifier: Programming Language :: Python :: 3.12
@@ -26,7 +25,7 @@ Classifier: Programming Language :: Python :: 3.13
26
25
  Classifier: Topic :: System :: Systems Administration
27
26
  Classifier: Topic :: Utilities
28
27
  Classifier: Typing :: Typed
29
- Requires-Python: >=3.9
28
+ Requires-Python: >=3.10
30
29
  Description-Content-Type: text/markdown
31
30
  License-File: LICENSE
32
31
  Requires-Dist: click>=8.0
@@ -64,19 +63,20 @@ Dynamic: license-file
64
63
  [![CI](https://github.com/BeshoyEhab/dot-man/actions/workflows/ci.yml/badge.svg)](https://github.com/BeshoyEhab/dot-man/actions/workflows/ci.yml)
65
64
  [![PyPI version](https://img.shields.io/pypi/v/dotman-git?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/dotman-git/)
66
65
  [![PyPI downloads](https://img.shields.io/pypi/dm/dotman-git?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/dotman-git/)
67
- [![Python](https://img.shields.io/pypi/pyversions/dotman-git?logo=python&logoColor=white)](https://pypi.org/project/dotman-git/)
66
+ [![Python](https://img.shields.io/pypi/pyversions/dotman-git?logo=python&logoColor=white&v=2)](https://pypi.org/project/dotman-git/)
68
67
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
69
68
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
70
69
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
71
70
  [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
72
- [![Coverage](https://img.shields.io/badge/coverage-83%25-green)](https://github.com/BeshoyEhab/dot-man)
71
+ [![Coverage](https://img.shields.io/badge/coverage-84%25-green)](https://github.com/BeshoyEhab/dot-man)
72
+ [![Docs](https://img.shields.io/badge/docs-online-blue)](https://beshoyehab.github.io/dot-man/)
73
73
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/BeshoyEhab/dot-man/pulls)
74
74
 
75
75
  ---
76
76
 
77
77
  *Switch your entire development environment in one command.*
78
78
 
79
- [**Install**](#installation) · [**Quick Start**](#quick-start) · [**Commands**](#commands) · [**Config**](#configuration) · [**Docs**](docs/)
79
+ [**Install**](https://github.com/BeshoyEhab/dot-man#installation) · [**Quick Start**](https://github.com/BeshoyEhab/dot-man#quick-start) · [**Commands**](https://github.com/BeshoyEhab/dot-man#commands) · [**Config**](https://github.com/BeshoyEhab/dot-man#configuration) · [**Docs**](https://beshoyehab.github.io/dot-man/)
80
80
 
81
81
  </div>
82
82
 
@@ -125,7 +125,7 @@ Your Machine dot-man Repo Any Machine
125
125
  <td>
126
126
 
127
127
  **Developer**
128
- - 🐚 Shell completions (bash/zsh/fish)
128
+ - 🐚 Shell completions (bash/zsh/fish) — decorator-driven engine, always in sync with the CLI
129
129
  - 🎨 Syntax-highlighted rich diffs
130
130
  - 🩺 `doctor` and `verify` diagnostics
131
131
  - 📦 PyPI — `pip install dotman-git`
@@ -181,7 +181,6 @@ dot-man add ~/.config/nvim
181
181
 
182
182
  # 3. Create a "work" profile (branch)
183
183
  dot-man navigate work
184
- # btw it was "switch" instead of "navigate" untill Bishoy decided to change it to "navigate" for some silly reasons and push that changes so as community help us to make it "switch" again.
185
184
 
186
185
  # 4. Edit your work-specific configs, then switch back — changes are saved automatically
187
186
  dot-man navigate main
@@ -480,10 +479,10 @@ System variables (`{{HOSTNAME}}`, `{{USER}}`, `{{SHELL}}`, etc.) are auto-popula
480
479
 
481
480
  | Metric | Value |
482
481
  |--------|-------|
483
- | Version | `1.2.3` |
482
+ | Version | `1.4.0` |
484
483
  | Python | `3.10 · 3.11 · 3.12 · 3.13` |
485
484
  | Platforms | Linux, macOS |
486
- | Test Coverage | 81% (1526 tests) |
485
+ | Test Coverage | 84% (1682 tests) |
487
486
  | Commands | 30+ |
488
487
  | PyPI | [`dotman-git`](https://pypi.org/project/dotman-git/) |
489
488
 
@@ -7,19 +7,20 @@
7
7
  [![CI](https://github.com/BeshoyEhab/dot-man/actions/workflows/ci.yml/badge.svg)](https://github.com/BeshoyEhab/dot-man/actions/workflows/ci.yml)
8
8
  [![PyPI version](https://img.shields.io/pypi/v/dotman-git?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/dotman-git/)
9
9
  [![PyPI downloads](https://img.shields.io/pypi/dm/dotman-git?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/dotman-git/)
10
- [![Python](https://img.shields.io/pypi/pyversions/dotman-git?logo=python&logoColor=white)](https://pypi.org/project/dotman-git/)
10
+ [![Python](https://img.shields.io/pypi/pyversions/dotman-git?logo=python&logoColor=white&v=2)](https://pypi.org/project/dotman-git/)
11
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
12
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
13
13
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
14
14
  [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
15
- [![Coverage](https://img.shields.io/badge/coverage-83%25-green)](https://github.com/BeshoyEhab/dot-man)
15
+ [![Coverage](https://img.shields.io/badge/coverage-84%25-green)](https://github.com/BeshoyEhab/dot-man)
16
+ [![Docs](https://img.shields.io/badge/docs-online-blue)](https://beshoyehab.github.io/dot-man/)
16
17
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/BeshoyEhab/dot-man/pulls)
17
18
 
18
19
  ---
19
20
 
20
21
  *Switch your entire development environment in one command.*
21
22
 
22
- [**Install**](#installation) · [**Quick Start**](#quick-start) · [**Commands**](#commands) · [**Config**](#configuration) · [**Docs**](docs/)
23
+ [**Install**](https://github.com/BeshoyEhab/dot-man#installation) · [**Quick Start**](https://github.com/BeshoyEhab/dot-man#quick-start) · [**Commands**](https://github.com/BeshoyEhab/dot-man#commands) · [**Config**](https://github.com/BeshoyEhab/dot-man#configuration) · [**Docs**](https://beshoyehab.github.io/dot-man/)
23
24
 
24
25
  </div>
25
26
 
@@ -68,7 +69,7 @@ Your Machine dot-man Repo Any Machine
68
69
  <td>
69
70
 
70
71
  **Developer**
71
- - 🐚 Shell completions (bash/zsh/fish)
72
+ - 🐚 Shell completions (bash/zsh/fish) — decorator-driven engine, always in sync with the CLI
72
73
  - 🎨 Syntax-highlighted rich diffs
73
74
  - 🩺 `doctor` and `verify` diagnostics
74
75
  - 📦 PyPI — `pip install dotman-git`
@@ -124,7 +125,6 @@ dot-man add ~/.config/nvim
124
125
 
125
126
  # 3. Create a "work" profile (branch)
126
127
  dot-man navigate work
127
- # btw it was "switch" instead of "navigate" untill Bishoy decided to change it to "navigate" for some silly reasons and push that changes so as community help us to make it "switch" again.
128
128
 
129
129
  # 4. Edit your work-specific configs, then switch back — changes are saved automatically
130
130
  dot-man navigate main
@@ -423,10 +423,10 @@ System variables (`{{HOSTNAME}}`, `{{USER}}`, `{{SHELL}}`, etc.) are auto-popula
423
423
 
424
424
  | Metric | Value |
425
425
  |--------|-------|
426
- | Version | `1.2.3` |
426
+ | Version | `1.4.0` |
427
427
  | Python | `3.10 · 3.11 · 3.12 · 3.13` |
428
428
  | Platforms | Linux, macOS |
429
- | Test Coverage | 81% (1526 tests) |
429
+ | Test Coverage | 84% (1682 tests) |
430
430
  | Commands | 30+ |
431
431
  | PyPI | [`dotman-git`](https://pypi.org/project/dotman-git/) |
432
432
 
@@ -1,4 +1,4 @@
1
1
  """dot-man: Dotfile manager with git-powered branching."""
2
2
 
3
- __version__ = "1.3.0"
3
+ __version__ = "1.4.0"
4
4
  __author__ = "Bishoy Ehab"
@@ -4,7 +4,7 @@ This package provides the CLI entry point for dot-man.
4
4
  It exposes the modular CLI implementation.
5
5
  """
6
6
 
7
- from . import discover_cmd, encrypt_cmd, export_cmd, import_cmd
7
+ from . import bootstrap_cmd, discover_cmd, encrypt_cmd, export_cmd, import_cmd
8
8
  from .add_cmd import add
9
9
  from .audit_cmd import audit
10
10
  from .backup_cmd import backup
@@ -35,19 +35,21 @@ from .config_cmd import config
35
35
  from .deploy_cmd import deploy
36
36
  from .doctor_cmd import doctor
37
37
  from .edit_cmd import edit
38
+ from .hooks_cmd import hooks
38
39
 
39
40
  # Import commands for easier access and backward compatibility
40
41
  from .init_cmd import init
41
42
  from .interface import cli
42
43
  from .log_cmd import checkout, diff, log
43
44
  from .main import main
44
- from .navigate_cmd import hooks, navigate
45
+ from .navigate_cmd import navigate
45
46
  from .onboarding import is_first_run, mark_onboarded, run_onboarding
46
47
  from .profile_cmd import profile
47
48
  from .remote_cmd import remote, sync
48
49
  from .restore_cmd import restore
49
50
  from .revert_cmd import revert
50
51
  from .rollback_cmd import rollback
52
+ from .save_cmd import save
51
53
  from .show_cmd import show
52
54
  from .status_cmd import status
53
55
  from .switch_cmd import switch
@@ -96,6 +98,7 @@ __all__ = [
96
98
  "config",
97
99
  "revert",
98
100
  "restore",
101
+ "save",
99
102
  "clean",
100
103
  "doctor",
101
104
  "verify",
@@ -112,6 +115,7 @@ __all__ = [
112
115
  "export_cmd",
113
116
  "encrypt_cmd",
114
117
  "discover_cmd",
118
+ "bootstrap_cmd",
115
119
  "completions",
116
120
  "watch",
117
121
  "rollback",
@@ -0,0 +1,240 @@
1
+ """Bootstrap command for dot-man — install packages via system package managers."""
2
+
3
+ import logging
4
+ import shutil
5
+ import subprocess
6
+
7
+ import click
8
+
9
+ from .. import ui
10
+ from .common import require_init, success, warn
11
+ from .interface import cli as main
12
+
13
+ # Package manager detection: command → (install_cmd, update_cmd)
14
+ PACKAGE_MANAGERS = {
15
+ "brew": {
16
+ "install": "brew install {package}",
17
+ "update": "brew update",
18
+ "detect": "brew",
19
+ },
20
+ "apt": {
21
+ "install": "sudo apt-get install -y {package}",
22
+ "update": "sudo apt-get update",
23
+ "detect": "apt-get",
24
+ },
25
+ "dnf": {
26
+ "install": "sudo dnf install -y {package}",
27
+ "update": "sudo dnf check-update",
28
+ "detect": "dnf",
29
+ },
30
+ "pacman": {
31
+ "install": "sudo pacman -S --noconfirm {package}",
32
+ "update": "sudo pacman -Sy",
33
+ "detect": "pacman",
34
+ },
35
+ "zypper": {
36
+ "install": "sudo zypper install -y {package}",
37
+ "update": "sudo zypper refresh",
38
+ "detect": "zypper",
39
+ },
40
+ "nix-env": {
41
+ "install": "nix-env -iA nixpkgs.{package}",
42
+ "update": "nix-channel --update",
43
+ "detect": "nix-env",
44
+ },
45
+ "xbps-install": {
46
+ "install": "sudo xbps-install -y {package}",
47
+ "update": "sudo xbps-install -Su",
48
+ "detect": "xbps-install",
49
+ },
50
+ "pkg": {
51
+ "install": "pkg install -y {package}",
52
+ "update": "pkg update",
53
+ "detect": "pkg",
54
+ },
55
+ }
56
+
57
+
58
+ def _detect_package_manager() -> str | None:
59
+ """Detect the available package manager on this system."""
60
+ import platform
61
+
62
+ os_name = platform.system().lower()
63
+
64
+ # Preferred order by OS
65
+ if os_name == "darwin":
66
+ preferred = ["brew"]
67
+ elif os_name == "linux":
68
+ preferred = ["apt", "dnf", "pacman", "zypper", "xbps-install", "nix-env"]
69
+ elif os_name == "freebsd":
70
+ preferred = ["pkg"]
71
+ else:
72
+ preferred = list(PACKAGE_MANAGERS.keys())
73
+
74
+ for pm in preferred:
75
+ if shutil.which(PACKAGE_MANAGERS[pm]["detect"]):
76
+ return pm
77
+
78
+ return None
79
+
80
+
81
+ def _load_bootstrap_packages() -> dict[str, list[str]]:
82
+ """Load packages from dot-man config or bootstrap.toml."""
83
+ try:
84
+ from ..config import DotManConfig
85
+
86
+ config = DotManConfig()
87
+ config.load()
88
+
89
+ # Check for [bootstrap] section in config
90
+ bootstrap = config._data.get("bootstrap", {})
91
+ if bootstrap:
92
+ packages = {}
93
+ # Support both flat list and grouped format
94
+ if isinstance(bootstrap, dict):
95
+ for key, value in bootstrap.items():
96
+ if key == "packages" and isinstance(value, list):
97
+ packages["default"] = value
98
+ elif isinstance(value, list):
99
+ packages[key] = value
100
+ return packages
101
+
102
+ except Exception as e:
103
+ logging.debug(f"Failed to load bootstrap config: {e}")
104
+
105
+ return {}
106
+
107
+
108
+ def _run_with_pm(pm: str, command_template: str, package: str) -> bool:
109
+ """Run a package manager command. Returns True on success."""
110
+ cmd = command_template.format(package=package)
111
+ ui.console.print(f" [cyan]$ {cmd}[/cyan]")
112
+ try:
113
+ result = subprocess.run(
114
+ cmd, shell=True, capture_output=True, text=True, timeout=300
115
+ )
116
+ if result.returncode == 0:
117
+ return True
118
+ else:
119
+ warn(f"Failed to install {package} (exit code {result.returncode})")
120
+ if result.stderr:
121
+ for line in result.stderr.splitlines()[:3]:
122
+ ui.console.print(f" [dim]{line}[/dim]")
123
+ return False
124
+ except subprocess.TimeoutExpired:
125
+ warn(f"Timeout installing {package}")
126
+ return False
127
+ except Exception as e:
128
+ warn(f"Error installing {package}: {e}")
129
+ return False
130
+
131
+
132
+ @main.command("bootstrap")
133
+ @click.option(
134
+ "--list",
135
+ "list_only",
136
+ is_flag=True,
137
+ help="List packages that would be installed without installing",
138
+ )
139
+ @click.option("--pm", help="Package manager to use (auto-detected if not specified)")
140
+ @click.option("--update", is_flag=True, help="Update package manager first")
141
+ @click.argument("packages", nargs=-1)
142
+ @require_init
143
+ def bootstrap(list_only: bool, pm: str | None, update: bool, packages: tuple[str, ...]):
144
+ """Install system packages via package managers.
145
+
146
+ Detects your system package manager (brew, apt, dnf, pacman, etc.)
147
+ and installs the specified packages. Packages can also be defined
148
+ in your dot-man config under [bootstrap].
149
+
150
+ Examples:
151
+ # Install specific packages
152
+ dot-man bootstrap neovim tmux git
153
+
154
+ # List what would be installed from config
155
+ dot-man bootstrap --list
156
+
157
+ # Use a specific package manager
158
+ dot-man bootstrap --pm brew neovim
159
+
160
+ # Update package manager first
161
+ dot-man bootstrap --update neovim
162
+ """
163
+ # Detect package manager
164
+ if not pm:
165
+ pm = _detect_package_manager()
166
+ if not pm:
167
+ ui.console.print("[red]No supported package manager found.[/red]")
168
+ ui.console.print(
169
+ "Supported: brew, apt, dnf, pacman, zypper, nix-env, xbps-install, pkg"
170
+ )
171
+ ui.console.print("Use --pm to specify one manually.")
172
+ raise SystemExit(1)
173
+
174
+ if pm not in PACKAGE_MANAGERS:
175
+ ui.console.print(f"[red]Unknown package manager: {pm}[/red]")
176
+ raise SystemExit(1)
177
+
178
+ pm_info = PACKAGE_MANAGERS[pm]
179
+ ui.console.print(f"[bold]Using package manager:[/bold] [cyan]{pm}[/cyan]")
180
+
181
+ # Collect packages from args and config
182
+ all_packages: dict[str, list[str]] = {}
183
+
184
+ if packages:
185
+ all_packages["command-line"] = list(packages)
186
+
187
+ config_packages = _load_bootstrap_packages()
188
+ all_packages.update(config_packages)
189
+
190
+ if not all_packages:
191
+ ui.console.print("[yellow]No packages specified.[/yellow]")
192
+ ui.console.print("Usage: dot-man bootstrap <package1> <package2> ...")
193
+ ui.console.print("Or add [bootstrap] section to your dot-man.toml:")
194
+ ui.console.print(' [bootstrap]\n packages = ["neovim", "tmux", "git"]')
195
+ return
196
+
197
+ # List mode
198
+ if list_only:
199
+ ui.console.print()
200
+ ui.console.print("[bold]Packages to install:[/bold]")
201
+ for group, pkgs in all_packages.items():
202
+ ui.console.print(f" [cyan]{group}:[/cyan]")
203
+ for pkg in pkgs:
204
+ ui.console.print(f" • {pkg}")
205
+ ui.console.print()
206
+ total = sum(len(pkgs) for pkgs in all_packages.values())
207
+ ui.console.print(f" [dim]{total} package(s) total[/dim]")
208
+ return
209
+
210
+ # Update package manager if requested
211
+ if update:
212
+ ui.console.print()
213
+ ui.console.print("[bold]Updating package manager...[/bold]")
214
+ _run_with_pm(pm, pm_info["update"], "")
215
+
216
+ # Install packages
217
+ installed = 0
218
+ failed = 0
219
+
220
+ for group, pkgs in all_packages.items():
221
+ ui.console.print()
222
+ ui.console.print(f"[bold]Installing {group}:[/bold]")
223
+ for pkg in pkgs:
224
+ ui.console.print(f"\n[bold]→ {pkg}[/bold]")
225
+ result = _run_with_pm(pm, pm_info["install"], pkg)
226
+ if result:
227
+ installed += 1
228
+ else:
229
+ failed += 1
230
+
231
+ # Summary
232
+ ui.console.print()
233
+ ui.console.print("[bold]Bootstrap complete:[/bold]")
234
+ ui.console.print(f" [green]✓ {installed} installed[/green]")
235
+ if failed:
236
+ ui.console.print(f" [red]✗ {failed} failed[/red]")
237
+ ui.console.print()
238
+
239
+ if installed > 0:
240
+ success(f"Installed {installed} package(s) via {pm}")
@@ -111,6 +111,28 @@ class DotManGroup(click.Group):
111
111
  ctx.exit(2)
112
112
 
113
113
 
114
+ def sync_shared_sections(ops) -> None:
115
+ """Propagate ``shared = true`` sections to non-overriding branches.
116
+
117
+ Called after a commit was created on the current branch. Failures
118
+ are reported but never abort the surrounding command.
119
+ """
120
+ try:
121
+ from ..shared_sync import SharedSectionSync
122
+
123
+ report = SharedSectionSync(ops).sync()
124
+ if not report.shared_sections:
125
+ return
126
+ for branch, sections in report.propagated.items():
127
+ ui.console.print(
128
+ f" [dim]↔ shared → '{branch}': {', '.join(sections)}[/dim]"
129
+ )
130
+ for branch, reason in report.errors.items():
131
+ warn(f"Shared-section sync failed for '{branch}': {reason}")
132
+ except Exception as e:
133
+ logging.debug("Shared-section sync skipped: %s", e)
134
+
135
+
114
136
  def require_init(func):
115
137
  """Decorator to require initialization before running command."""
116
138