kanibako-cli 1.6.0.dev31__tar.gz → 1.6.0.dev36__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 (201) hide show
  1. {kanibako_cli-1.6.0.dev31/src/kanibako_cli.egg-info → kanibako_cli-1.6.0.dev36}/PKG-INFO +2 -1
  2. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/pyproject.toml +2 -2
  3. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/__init__.py +1 -1
  4. kanibako_cli-1.6.0.dev36/src/kanibako/agent_defaults.py +183 -0
  5. kanibako_cli-1.6.0.dev36/src/kanibako/agent_representation.py +152 -0
  6. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/cli.py +47 -2
  7. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/agent_cmd.py +18 -2
  8. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/box/_parser.py +2 -2
  9. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/flags.py +11 -0
  10. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/helper_cmd.py +2 -0
  11. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/image.py +121 -16
  12. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/install.py +11 -9
  13. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/setup_cmd.py +10 -0
  14. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/start.py +722 -672
  15. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/workset_cmd.py +107 -35
  16. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/config.py +10 -207
  17. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/config_interface.py +198 -2
  18. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/container.py +151 -40
  19. kanibako_cli-1.6.0.dev36/src/kanibako/core_defaults.py +302 -0
  20. kanibako_cli-1.6.0.dev36/src/kanibako/data/core-defaults.yaml +195 -0
  21. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/freshness.py +1 -1
  22. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/helper_client.py +2 -1
  23. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/helper_listener.py +52 -24
  24. kanibako_cli-1.6.0.dev36/src/kanibako/image_sharing.py +287 -0
  25. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/paths.py +7 -3
  26. kanibako_cli-1.6.0.dev36/src/kanibako/settings_assemble.py +318 -0
  27. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/settings_categories.py +74 -45
  28. kanibako_cli-1.6.0.dev36/src/kanibako/settings_configset.py +417 -0
  29. kanibako_cli-1.6.0.dev36/src/kanibako/settings_expand.py +401 -0
  30. kanibako_cli-1.6.0.dev36/src/kanibako/settings_launch.py +734 -0
  31. kanibako_cli-1.6.0.dev36/src/kanibako/settings_merge.py +302 -0
  32. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/settings_resolve.py +128 -94
  33. kanibako_cli-1.6.0.dev36/src/kanibako/settings_store.py +321 -0
  34. kanibako_cli-1.6.0.dev36/src/kanibako/settings_views.py +466 -0
  35. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/targets/base.py +16 -8
  36. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/templates.py +67 -50
  37. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36/src/kanibako_cli.egg-info}/PKG-INFO +2 -1
  38. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako_cli.egg-info/SOURCES.txt +21 -5
  39. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako_cli.egg-info/requires.txt +1 -0
  40. kanibako_cli-1.6.0.dev36/tests/test_agent_representation.py +339 -0
  41. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_agent_resolution.py +39 -18
  42. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_cli.py +111 -0
  43. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_config.py +19 -111
  44. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_config_interface.py +131 -0
  45. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_container.py +212 -3
  46. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_container_extended.py +73 -23
  47. kanibako_cli-1.6.0.dev36/tests/test_defaults_golden.py +389 -0
  48. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_error_messages.py +3 -3
  49. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_freshness.py +5 -1
  50. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_helper_client.py +2 -2
  51. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_helper_cmd.py +1 -0
  52. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_helper_listener.py +144 -23
  53. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_image_sharing.py +302 -27
  54. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_resource_scoping.py +39 -11
  55. kanibako_cli-1.6.0.dev36/tests/test_settings_assemble.py +511 -0
  56. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_settings_categories.py +335 -45
  57. kanibako_cli-1.6.0.dev36/tests/test_settings_configset.py +440 -0
  58. kanibako_cli-1.6.0.dev36/tests/test_settings_expand.py +465 -0
  59. kanibako_cli-1.6.0.dev36/tests/test_settings_launch.py +354 -0
  60. kanibako_cli-1.6.0.dev36/tests/test_settings_launch_equivalence.py +478 -0
  61. kanibako_cli-1.6.0.dev36/tests/test_settings_merge.py +470 -0
  62. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_settings_resolve.py +56 -1
  63. kanibako_cli-1.6.0.dev36/tests/test_settings_store.py +463 -0
  64. kanibako_cli-1.6.0.dev36/tests/test_settings_views.py +375 -0
  65. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_setup_cmd.py +24 -0
  66. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_snapshots.py +106 -0
  67. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_templates.py +95 -81
  68. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_workset.py +5 -4
  69. kanibako_cli-1.6.0.dev31/src/kanibako/image_sharing.py +0 -133
  70. kanibako_cli-1.6.0.dev31/src/kanibako/settings_seeds.py +0 -81
  71. kanibako_cli-1.6.0.dev31/src/kanibako/settings_shares.py +0 -77
  72. kanibako_cli-1.6.0.dev31/tests/test_settings_loader.py +0 -229
  73. kanibako_cli-1.6.0.dev31/tests/test_settings_seeds.py +0 -352
  74. kanibako_cli-1.6.0.dev31/tests/test_settings_shares.py +0 -388
  75. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/LICENSE.md +0 -0
  76. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/README.md +0 -0
  77. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/setup.cfg +0 -0
  78. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/__main__.py +0 -0
  79. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/_atomic.py +0 -0
  80. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/agent_config.py +0 -0
  81. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/auth_browser.py +0 -0
  82. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/auth_parser.py +0 -0
  83. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/baseline.py +0 -0
  84. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/box_identity.py +0 -0
  85. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/box_seed.py +0 -0
  86. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/browser_sidecar.py +0 -0
  87. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/browser_state.py +0 -0
  88. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/bun_sea.py +0 -0
  89. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/channels.py +0 -0
  90. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/__init__.py +0 -0
  91. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/archive.py +0 -0
  92. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/baseline_cmd.py +0 -0
  93. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/box/__init__.py +0 -0
  94. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/box/_duplicate.py +0 -0
  95. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/box/_lifecycle.py +0 -0
  96. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/clean.py +0 -0
  97. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/diagnose.py +0 -0
  98. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/fork_cmd.py +0 -0
  99. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/restore.py +0 -0
  100. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/stop.py +0 -0
  101. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/system_cmd.py +0 -0
  102. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/upgrade.py +0 -0
  103. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/commands/vault_cmd.py +0 -0
  104. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/config_io.py +0 -0
  105. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/containerfiles.py +0 -0
  106. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/containers/Containerfile.template-android +0 -0
  107. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/containers/Containerfile.template-dotnet +0 -0
  108. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/containers/Containerfile.template-js +0 -0
  109. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/containers/Containerfile.template-jvm +0 -0
  110. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/containers/Containerfile.template-systems +0 -0
  111. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/containers/__init__.py +0 -0
  112. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/containers/tmux.conf +0 -0
  113. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/data/__init__.py +0 -0
  114. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/data/image-baseline.yaml +0 -0
  115. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/data/templates/base/INSTRUCTIONS.md +0 -0
  116. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/deprecation.py +0 -0
  117. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/errors.py +0 -0
  118. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/git.py +0 -0
  119. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/helpers.py +0 -0
  120. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/hygiene.py +0 -0
  121. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/import_reconcile.py +0 -0
  122. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/install_method.py +0 -0
  123. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/log.py +0 -0
  124. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/names.py +0 -0
  125. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/plugins/__init__.py +0 -0
  126. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/py.typed +0 -0
  127. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/registry.py +0 -0
  128. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/registry_store.py +0 -0
  129. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/rig_bundle.py +0 -0
  130. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/rig_meta.py +0 -0
  131. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/rig_registry.py +0 -0
  132. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/rig_resolve.py +0 -0
  133. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/rig_source.py +0 -0
  134. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/scripts/__init__.py +0 -0
  135. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/scripts/helper-init.sh +0 -0
  136. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/scripts/kanibako-entry +0 -0
  137. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/shellenv.py +0 -0
  138. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/shells.py +0 -0
  139. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/snapshots.py +0 -0
  140. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/targets/__init__.py +0 -0
  141. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/targets/assembly.py +0 -0
  142. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/targets/credsync.py +0 -0
  143. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/targets/no_agent.py +0 -0
  144. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/templates_image.py +0 -0
  145. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/tweakcc.py +0 -0
  146. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/tweakcc_cache.py +0 -0
  147. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/utils.py +0 -0
  148. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako/workset.py +0 -0
  149. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako_cli.egg-info/dependency_links.txt +0 -0
  150. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako_cli.egg-info/entry_points.txt +0 -0
  151. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/src/kanibako_cli.egg-info/top_level.txt +0 -0
  152. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_agent_config.py +0 -0
  153. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_archive_restore_integration.py +0 -0
  154. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_atomic.py +0 -0
  155. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_auth_browser.py +0 -0
  156. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_auth_parser.py +0 -0
  157. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_baseline.py +0 -0
  158. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_box_identity.py +0 -0
  159. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_box_seed.py +0 -0
  160. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_browser_sidecar.py +0 -0
  161. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_browser_state.py +0 -0
  162. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_bun_sea.py +0 -0
  163. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_channels.py +0 -0
  164. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_cli_exitcodes.py +0 -0
  165. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_comms.py +0 -0
  166. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_container_integration.py +0 -0
  167. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_containerfiles.py +0 -0
  168. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_credentials.py +0 -0
  169. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_deprecations.py +0 -0
  170. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_env_cmd.py +0 -0
  171. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_fork.py +0 -0
  172. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_git.py +0 -0
  173. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_helpers.py +0 -0
  174. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_hygiene.py +0 -0
  175. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_import.py +0 -0
  176. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_init_cmd.py +0 -0
  177. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_install_integration.py +0 -0
  178. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_lifecycle_integration.py +0 -0
  179. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_log.py +0 -0
  180. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_names.py +0 -0
  181. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_paths.py +0 -0
  182. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_paths_extended.py +0 -0
  183. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_registry.py +0 -0
  184. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_registry_store.py +0 -0
  185. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_resolve_box_target.py +0 -0
  186. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_rig_bundle.py +0 -0
  187. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_rig_meta.py +0 -0
  188. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_rig_registry.py +0 -0
  189. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_rig_resolve.py +0 -0
  190. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_rig_source.py +0 -0
  191. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_safety_invariants.py +0 -0
  192. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_shellenv.py +0 -0
  193. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_shells.py +0 -0
  194. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_standalone_paths.py +0 -0
  195. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_start_integration.py +0 -0
  196. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_status.py +0 -0
  197. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_system_paths.py +0 -0
  198. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_templates_image.py +0 -0
  199. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_tweakcc.py +0 -0
  200. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_utils.py +0 -0
  201. {kanibako_cli-1.6.0.dev31 → kanibako_cli-1.6.0.dev36}/tests/test_workset_paths.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kanibako-cli
3
- Version: 1.6.0.dev31
3
+ Version: 1.6.0.dev36
4
4
  Summary: Run AI coding agents in rootless containers with per-project isolation
5
5
  Requires-Python: >=3.11
6
6
  License-File: LICENSE.md
@@ -13,4 +13,5 @@ Requires-Dist: pytest-cov; extra == "dev"
13
13
  Requires-Dist: mypy; extra == "dev"
14
14
  Requires-Dist: ruff; extra == "dev"
15
15
  Requires-Dist: bump2version; extra == "dev"
16
+ Requires-Dist: pexpect>=4.8; extra == "dev"
16
17
  Dynamic: license-file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kanibako-cli"
7
- version = "1.6.0.dev31"
7
+ version = "1.6.0.dev36"
8
8
  description = "Run AI coding agents in rootless containers with per-project isolation"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = ["argcomplete>=3.0", "PyYAML>=6.0", "packaging>=20.0"]
@@ -16,7 +16,7 @@ kanibako = "kanibako.cli:main"
16
16
  no_agent = "kanibako.targets.no_agent:NoAgentTarget"
17
17
 
18
18
  [project.optional-dependencies]
19
- dev = ["pytest>=7.0", "pytest-cov", "mypy", "ruff", "bump2version"]
19
+ dev = ["pytest>=7.0", "pytest-cov", "mypy", "ruff", "bump2version", "pexpect>=4.8"]
20
20
 
21
21
  [tool.setuptools.packages.find]
22
22
  where = ["src"]
@@ -1,6 +1,6 @@
1
1
  """kanibako: Run AI coding agents in rootless containers with per-project isolation."""
2
2
 
3
- __version__ = "1.6.0.dev31"
3
+ __version__ = "1.6.0.dev36"
4
4
 
5
5
  # The oldest build version whose ``setup`` run is still considered current. The
6
6
  # setup-completion gate (W1) re-nudges the user to re-run ``kanibako setup`` when
@@ -0,0 +1,183 @@
1
+ """Per-agent plugin defaults — thin reader of each plugin's shipped defaults file.
2
+
3
+ Each agent plugin package (``kanibako.plugins.claude`` / ``…goose`` / ``…codex``)
4
+ owns ONE declarative defaults file (``<agent>-defaults.yaml``) holding that
5
+ agent's complete DEFAULT-SET — its :class:`~kanibako.targets.base.PluginDescriptor`
6
+ data (bindings, launch grammar, settings, container env, cred files, …) plus its
7
+ AGENT-scope ``default_shares`` — in the specced structured form. This mirrors how
8
+ the system/core defaults ship (:mod:`kanibako.core_defaults` /
9
+ ``kanibako/data/core-defaults.yaml``) and how containerfiles/templates ship via
10
+ :mod:`importlib.resources`.
11
+
12
+ This module is the THIN reader those plugins call from their ``descriptor`` /
13
+ ``default_shares`` properties. It builds the descriptor from the file so the
14
+ in-code descriptor is no longer hand-written per plugin.
15
+
16
+ Split (documented in each YAML header too):
17
+
18
+ * DECLARATIVE — everything in the file: the binding ORIGIN selectors (which
19
+ detected install field supplies the host source — a declarative string, NOT a
20
+ probed value), box-side destinations, the mode/operations grammar, settings
21
+ routing, container env, cred-file lifecycle, host_prep/init_dirs, and the
22
+ agent-share box_dests. Box-side destinations under the guest home are written
23
+ as ``$GUEST_HOME`` EXPRESSIONS (e.g. ``$GUEST_HOME/.local/bin/claude``) that
24
+ this loader expands from the single :data:`~kanibako.settings_resolve.GUEST_HOME`
25
+ constant — no ``/home/agent`` literal in the file.
26
+ * CODE-RESOLVED — the CRITICAL/runtime-probed HOST binary paths stay in the
27
+ plugin's ``detect()`` (the contract-path constants + npm/ELF resolution). The
28
+ file only names, declaratively, WHICH detected field (``binary`` / ``launcher`` /
29
+ ``install_dir``) each binding's host source comes from (its ``origin``); the
30
+ actual probed path is filled in later by ``descriptor_mounts`` from the
31
+ :class:`~kanibako.targets.base.AgentInstall`.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import importlib.resources
37
+ from pathlib import Path
38
+ from typing import Any
39
+
40
+ import yaml
41
+
42
+ from kanibako.settings_resolve import GUEST_HOME
43
+ from kanibako.targets.base import (
44
+ BindDefault,
45
+ BindKind,
46
+ Binding,
47
+ BindScope,
48
+ Cadence,
49
+ Channel,
50
+ CredFileSpec,
51
+ HostSrcOrigin,
52
+ Operation,
53
+ PluginDescriptor,
54
+ SafeBypass,
55
+ SettingArg,
56
+ )
57
+
58
+
59
+ def _expand(value: str) -> str:
60
+ """Expand a ``$GUEST_HOME`` prefix in a box-side path expression.
61
+
62
+ The defaults files write every in-box destination as a ``$GUEST_HOME``
63
+ expression so the guest-home literal lives in exactly one place (the
64
+ :data:`~kanibako.settings_resolve.GUEST_HOME` constant, single SoT). Only the
65
+ leading ``$GUEST_HOME`` token is substituted; the rest is left verbatim.
66
+ """
67
+ if value.startswith("$GUEST_HOME"):
68
+ return GUEST_HOME + value[len("$GUEST_HOME"):]
69
+ return value
70
+
71
+
72
+ def _load_doc(package: str, filename: str) -> dict[str, Any]:
73
+ """Read and parse a plugin's bundled defaults file."""
74
+ ref = importlib.resources.files(package).joinpath(filename)
75
+ raw = yaml.safe_load(Path(str(ref)).read_text()) or {}
76
+ if not isinstance(raw, dict):
77
+ return {}
78
+ return raw
79
+
80
+
81
+ def _build_binding(entry: dict[str, Any]) -> Binding:
82
+ """Build one :class:`Binding` from a declarative file entry.
83
+
84
+ ``origin`` names the detected install field that supplies the host source
85
+ (``binary`` / ``launcher`` / ``install_dir`` / ``literal``); ``box_dest`` is a
86
+ ``$GUEST_HOME`` expression expanded here. ``kind`` / ``scope`` map to their
87
+ enums; ``ro`` defaults to True.
88
+ """
89
+ literal = entry.get("literal_src")
90
+ return Binding(
91
+ key=entry["key"],
92
+ origin=HostSrcOrigin(entry["origin"]),
93
+ box_dest=_expand(entry["box_dest"]),
94
+ kind=BindKind(entry["kind"]),
95
+ scope=BindScope(entry["scope"]),
96
+ ro=bool(entry.get("ro", True)),
97
+ literal_src=Path(literal) if literal is not None else None,
98
+ )
99
+
100
+
101
+ def _build_safe_bypass(raw: dict[str, Any] | None) -> SafeBypass | None:
102
+ """Build the :class:`SafeBypass` toggle from its declarative block (or None)."""
103
+ if not raw:
104
+ return None
105
+ return SafeBypass(
106
+ channel=Channel(raw["channel"]),
107
+ flag=tuple(raw.get("flag", ())),
108
+ env_var=raw.get("env_var", ""),
109
+ env_value=raw.get("env_value", ""),
110
+ secure_env_value=raw.get("secure_env_value", ""),
111
+ secure_flag=tuple(raw.get("secure_flag", ())),
112
+ setting_key=raw.get("setting_key", ""),
113
+ )
114
+
115
+
116
+ def _build_setting_arg(entry: dict[str, Any]) -> SettingArg:
117
+ return SettingArg(
118
+ setting_key=entry["setting_key"],
119
+ channel=Channel(entry["channel"]),
120
+ flag=tuple(entry.get("flag", ())),
121
+ env_var=entry.get("env_var", ""),
122
+ )
123
+
124
+
125
+ def _build_cred_file(entry: dict[str, Any]) -> CredFileSpec:
126
+ return CredFileSpec(
127
+ home_rel=entry["home_rel"],
128
+ host_rel=entry["host_rel"],
129
+ cadence=Cadence(entry.get("cadence", "sync")),
130
+ mtime_gate=bool(entry.get("mtime_gate", True)),
131
+ filtered=bool(entry.get("filtered", False)),
132
+ is_dir=bool(entry.get("is_dir", False)),
133
+ )
134
+
135
+
136
+ def load_descriptor(package: str, filename: str) -> PluginDescriptor:
137
+ """Build a plugin's :class:`PluginDescriptor` from its shipped defaults file.
138
+
139
+ *package* is the plugin's import package (e.g. ``"kanibako.plugins.claude"``)
140
+ and *filename* its declarative defaults file (e.g. ``"claude-defaults.yaml"``).
141
+ The returned descriptor is byte-for-byte equivalent to the former hand-written
142
+ one — box_dest ``$GUEST_HOME`` expressions are expanded and every enum field
143
+ is mapped from its string name.
144
+ """
145
+ doc = _load_doc(package, filename)
146
+ desc = doc.get("descriptor", {})
147
+
148
+ return PluginDescriptor(
149
+ command=tuple(desc["command"]),
150
+ bindings=tuple(_build_binding(b) for b in desc.get("bindings", [])),
151
+ mode={k: tuple(v) for k, v in desc.get("mode", {}).items()},
152
+ operations={
153
+ k: Operation(tuple(v["fragment"]))
154
+ for k, v in desc.get("operations", {}).items()
155
+ },
156
+ safe_bypass=_build_safe_bypass(desc.get("safe_bypass")),
157
+ settings=tuple(_build_setting_arg(s) for s in desc.get("settings", [])),
158
+ container_env=dict(desc.get("container_env", {})),
159
+ cred_files=tuple(_build_cred_file(c) for c in desc.get("cred_files", [])),
160
+ host_prep=bool(desc.get("host_prep", False)),
161
+ init_dirs=tuple(desc.get("init_dirs", ())),
162
+ )
163
+
164
+
165
+ def load_shares(package: str, filename: str) -> dict[str, BindDefault]:
166
+ """Build a plugin's AGENT-scope ``default_shares`` map from its defaults file.
167
+
168
+ Each entry maps a scoped category key (``agent.shared.<name>``) to a STRUCTURED
169
+ bind pair ``(host_src, box_dest)`` (spec §2a — a tuple, NOT a colon-joined
170
+ string). The relative ``host_src`` joins under the per-agent store root in the
171
+ category resolver; ``box_dest`` is a ``$GUEST_HOME`` expression expanded here.
172
+ Returns ``{}`` when the file declares no shares.
173
+ """
174
+ shares: dict[str, BindDefault] = {}
175
+ for entry in _load_doc(package, filename).get("shares", []):
176
+ options = entry.get("options")
177
+ if options is not None:
178
+ shares[entry["key"]] = (
179
+ entry["host_src"], _expand(entry["box_dest"]), options,
180
+ )
181
+ else:
182
+ shares[entry["key"]] = (entry["host_src"], _expand(entry["box_dest"]))
183
+ return shares
@@ -0,0 +1,152 @@
1
+ """Agent descriptor → KeyStore representation (block 7a — PURE, ALONGSIDE).
2
+
3
+ This module represents a plugin's per-agent DESCRIPTOR delivery binds as
4
+ agent-level category-default entries in the resolved-keyspace
5
+ :class:`~kanibako.settings_store.KeyStore`, so agent binary/launcher/share
6
+ delivery flows through the ONE category keyspace (the single-route invariant),
7
+ NOT a parallel descriptor mount route.
8
+
9
+ It is item-0's hard half: each :class:`~kanibako.targets.base.Binding` in a
10
+ :class:`~kanibako.targets.base.PluginDescriptor` becomes an
11
+ ``agent.<name>.bindings.{ro,rw}.<key> = Bind(host_src, box_dest, opts)`` leaf under
12
+ the descriptor's OWN agent name ``<name>`` (``install.name``; S27) — the §2d
13
+ ``agent.<agent>.bindings.*`` key form, NOT a bare ``agent`` token (§0 L21) —
14
+ mirroring :func:`~kanibako.targets.assembly.resolve_binding_source`'s
15
+ origin→host_src resolution.
16
+
17
+ PURE + ALONGSIDE (the block-7a boundaries)
18
+ ------------------------------------------
19
+ * **Imported, single-source resolution.** host_src is resolved by IMPORTING
20
+ :func:`~kanibako.targets.assembly.resolve_binding_source` — the origin→path
21
+ logic is NOT re-implemented here.
22
+ * **No override application (S26).** ``resolve_binding_source`` is called with
23
+ ``override=""`` so this emits the origin DEFAULT only. A user repoint now comes
24
+ from a HIGHER cascade level (``box`` / ``workset`` / ``agent.<active>``), merged
25
+ by block 2b — NOT baked into the agent default here.
26
+ * **No existence check (S26).** This function NEVER touches the filesystem (no
27
+ ``Path.exists()``); the ``AGENT_CRITICAL`` must-exist safe-fail is a CONSUMER
28
+ concern (the block-7b mount/reconcile step), not representation. Feed it a
29
+ fixture install whose paths do not exist and it still represents them.
30
+ * **No expansion (§6a / spec §0).** ``@``-refs / ``$XDG`` / ``~`` are left RAW —
31
+ expansion is block 3. ``box_dest`` is carried VERBATIM: the descriptor loader
32
+ (:func:`kanibako.agent_defaults._build_binding`) has ALREADY expanded the
33
+ ``$GUEST_HOME`` box constant at load, so the :class:`Binding` this function
34
+ receives is post-expansion; re-expanding would be wrong. A LITERAL-origin raw
35
+ ``@``/``$XDG``/``~`` ``box_dest`` therefore stays raw for free (§6a).
36
+ * **Build ALONGSIDE.** Nothing here is wired into the launch path; block 7b swaps
37
+ ``descriptor_mounts`` onto this representation. ``assembly.py`` /
38
+ ``agent_defaults.py`` / ``start.py`` are UNTOUCHED.
39
+
40
+ Representation rules (the heart of the block — brief §3)
41
+ --------------------------------------------------------
42
+ For each :class:`Binding` in ``descriptor.bindings`` (order preserved):
43
+
44
+ * **host_src** = ``resolve_binding_source(binding, install, override="")`` (the
45
+ origin-resolved ``LAUNCHER`` / ``INSTALL_DIR`` / ``BINARY`` / ``LITERAL`` path),
46
+ stored as ``str(host_src)`` (``Bind.host`` is ``str``, S1).
47
+ * **box_dest** = ``binding.box_dest`` VERBATIM (see above).
48
+ * **opts** = ``"ro"`` if ``binding.ro`` else ``None``. ``None`` (NOT ``""``) means
49
+ "no per-entry mount-options override" — ``Bind.opts`` defaults to ``None`` (S1)
50
+ and reconcile falls back to the category default for an rw bind. This is the
51
+ ``Bind`` convention, distinct from ``descriptor_mounts``'s ``""`` (an argv-mount
52
+ detail, not the stored shape).
53
+ * **key path** = the descriptor's OWN agent name ``<name>`` (``install.name``;
54
+ S27): ``agent.<name>.bindings.ro.<key>`` or ``agent.<name>.bindings.rw.<key>``
55
+ per ``binding.ro``. The agent NAME is IN the key path (the §2d
56
+ ``agent.<agent>.*`` form, NOT a bare ``agent`` token, §0 L21), so this partial
57
+ merges BY NAME with 2a's discriminated ``agent.<active-name>.*`` level and any
58
+ higher-scope ``agent.<name>.*`` override (S8 / block 2b).
59
+
60
+ None-origin rule (S27, recorded here)
61
+ -------------------------------------
62
+ When ``resolve_binding_source`` returns ``None`` (an unresolvable origin — e.g.
63
+ a ``LITERAL`` binding with no ``literal_src``, or a detection field the install
64
+ left unset), the entry is **OMITTED**. This mirrors the AGENT best-effort skip in
65
+ :func:`~kanibako.targets.assembly.descriptor_mounts` and keeps the tier-2 typed
66
+ accessor honest: ``bindings`` exposes ``Mapping[str, Bind]`` (NOT ``Bind | None``)
67
+ ONLY because build omits absent/None binds (design §5/§6e) — emitting a
68
+ ``None``-host bind would be a lie a consumer crashes on.
69
+
70
+ Authority: spec ``settings-keyspace-1.6.0-target.md`` §2d L356–378
71
+ (``agent.<agent>.bindings.{ro,rw}.<key>`` — the ONLY agent key form; §0 L21
72
+ forbids a bare ``agent.<key>``) / §2a (binding REPRESENTATION);
73
+ ``~/vault/rw/keystore-design.md`` §2 (binds are ``Bind``) / §6a (raw refs). SEAMS
74
+ S1/S2/S3/S7/S8/S9 + S26/S27.
75
+ """
76
+
77
+ from __future__ import annotations
78
+
79
+ from typing import TYPE_CHECKING
80
+
81
+ from kanibako.settings_store import Bind, KeyStore
82
+ from kanibako.targets.assembly import resolve_binding_source
83
+
84
+ if TYPE_CHECKING:
85
+ from kanibako.targets.base import AgentInstall, PluginDescriptor
86
+
87
+
88
+ def agent_default_partial(
89
+ descriptor: PluginDescriptor,
90
+ install: AgentInstall,
91
+ ) -> KeyStore:
92
+ """Represent a descriptor's delivery bindings as an agent-level KeyStore partial.
93
+
94
+ Returns a :class:`~kanibako.settings_store.KeyStore` partial rooted at
95
+ ``agent.<name>`` where ``<name>`` is the descriptor's OWN agent (``install.name``;
96
+ S27 / §2d), holding each resolvable :class:`~kanibako.targets.base.Binding` as a
97
+ ``agent.<name>.bindings.{ro,rw}.<key> = Bind(host_src, box_dest, opts)`` leaf —
98
+ mirroring :func:`~kanibako.targets.assembly.resolve_binding_source` with NO
99
+ override and NO existence check (S26). See the module docstring for the full
100
+ rules and the None-origin OMIT contract.
101
+
102
+ The partial nests ``agent.<name>.bindings`` with ``ro`` / ``rw`` sub-tables; a
103
+ sub-table is present only if at least one binding lands in it, ``bindings`` is
104
+ present only if at least one binding resolved, and the result is ALWAYS a
105
+ ``KeyStore`` rooted at ``agent.<name>`` (empty ``agent.<name>`` node when no
106
+ binding resolves) so it merges by NAME with the 2a agent levels (S8).
107
+ Descriptor order is preserved.
108
+
109
+ PURE: no filesystem access, no mutation of *descriptor* / *install*.
110
+ """
111
+ # Build the ro/rw sub-tables locally, then attach only the non-empty ones so
112
+ # the partial shape stays minimal (an empty `ro`/`rw`/`bindings` node would be
113
+ # an absent-vs-present-empty distinction the merge need not carry).
114
+ ro_binds = KeyStore()
115
+ rw_binds = KeyStore()
116
+
117
+ for binding in descriptor.bindings:
118
+ src = resolve_binding_source(binding, install, override="")
119
+ if src is None:
120
+ # None-origin → OMIT (S27); matches the AGENT best-effort skip and
121
+ # keeps tier-2 `Mapping[str, Bind]` honest (design §5/§6e).
122
+ continue
123
+ opts = "ro" if binding.ro else None
124
+ bind = Bind(str(src), binding.box_dest, opts)
125
+ # `binding.key` is the stable override key; place it under ro/rw by `ro`.
126
+ if binding.ro:
127
+ ro_binds[binding.key] = bind
128
+ else:
129
+ rw_binds[binding.key] = bind
130
+
131
+ bindings = KeyStore()
132
+ if dict.__len__(ro_binds):
133
+ bindings["ro"] = ro_binds
134
+ if dict.__len__(rw_binds):
135
+ bindings["rw"] = rw_binds
136
+
137
+ # Root under the descriptor's OWN agent name (§2d ``agent.<agent>.*``): the
138
+ # claude descriptor's binds land at ``agent.claude.bindings.*``. The agent NAME
139
+ # is part of the KEY PATH (NOT a bare ``agent`` token, §0 L21) — so this partial
140
+ # merges BY NAME with 2a's discriminated ``agent.<active>.*`` level and any
141
+ # higher-scope ``agent.<name>.*`` override (block 2b).
142
+ name = install.name
143
+ agent_sub = KeyStore()
144
+ if dict.__len__(bindings):
145
+ agent_sub["bindings"] = bindings
146
+
147
+ agent = KeyStore()
148
+ agent[name] = agent_sub
149
+
150
+ partial = KeyStore()
151
+ partial["agent"] = agent
152
+ return partial
@@ -184,6 +184,44 @@ _SUBCOMMANDS = {
184
184
  }
185
185
 
186
186
 
187
+ def _normalize_command(effective: list[str]) -> list[str]:
188
+ """Reorder argv so a leading global-style flag doesn't swallow a subcommand.
189
+
190
+ The dispatcher's fallback rule ``effective[0] not in _SUBCOMMANDS -> prepend
191
+ "start"`` treats a LEADING flag (e.g. ``--agent goose shell``) as a bare
192
+ ``start`` with that flag — turning ``--agent goose shell`` into ``start
193
+ shell`` with ``project="shell"``, which both launches the wrong thing and
194
+ fires the Gate-1 setup nudge.
195
+
196
+ To honour the blanket-flag design (``kanibako --agent X <subcommand>`` ==
197
+ ``kanibako <subcommand> --agent X``), when ``effective[0]`` is an option
198
+ (starts with ``-``) and a KNOWN subcommand appears later, move the FIRST such
199
+ subcommand token to the front, preserving the relative order of everything
200
+ else. ``--agent goose shell`` -> ``["shell", "--agent", "goose"]``.
201
+
202
+ Heuristic (kept simple, matches the documented design where flags normally
203
+ follow the subcommand): scan for the first token that is a ``_SUBCOMMANDS``
204
+ member. A flag VALUE that happens to equal a subcommand name (e.g.
205
+ ``--box shell start`` where a box is literally named "shell") would be
206
+ matched as the subcommand; this is an accepted edge case.
207
+
208
+ The genuinely-no-subcommand cases are left untouched here so the caller's
209
+ existing ``prepend "start"`` rule still handles them: ``kanibako myproject``
210
+ (bare positional) and ``kanibako -A`` / ``kanibako -N`` (leading flags, no
211
+ subcommand) both fall through unchanged.
212
+ """
213
+ if not effective or not effective[0].startswith("-"):
214
+ return effective
215
+ sub_idx = next(
216
+ (i for i, tok in enumerate(effective) if tok in _SUBCOMMANDS),
217
+ None,
218
+ )
219
+ if sub_idx is None:
220
+ return effective
221
+ sub = effective[sub_idx]
222
+ return [sub] + effective[:sub_idx] + effective[sub_idx + 1:]
223
+
224
+
187
225
  def _ensure_initialized() -> None:
188
226
  """Ensure kanibako is initialized (create config + data dirs on first run)."""
189
227
  from kanibako.config import (
@@ -256,8 +294,9 @@ def _ensure_initialized() -> None:
256
294
  # CURATED templates instead. The content ships as static package data and
257
295
  # is COPIED here into the runtime template dirs (@system.base_template +
258
296
  # @system.agents/<agent>/template), create-if-absent so user edits survive
259
- # an upgrade. The layered seed-once apply (templates.apply_template_layers)
260
- # then copies them into each new box home at creation.
297
+ # an upgrade. The layered seed-once stage+seed
298
+ # (templates.stage_and_seed_templates) then copies them into each new box
299
+ # home at creation.
261
300
  from kanibako.paths import load_std_paths
262
301
  from kanibako.templates import install_packaged_templates
263
302
 
@@ -339,6 +378,12 @@ def main(argv: list[str] | None = None) -> None:
339
378
  print(f"kanibako {__version__}")
340
379
  sys.exit(0)
341
380
  else:
381
+ # Reorder a leading global-style flag ahead of a later subcommand so
382
+ # `kanibako --agent goose shell` dispatches as `shell` (not `start` with
383
+ # project="shell"). Must run BEFORE the prepend-"start" fallback and the
384
+ # `--` split below.
385
+ effective = _normalize_command(effective)
386
+
342
387
  # If the first arg isn't a known subcommand, default to "start".
343
388
  if not effective or effective[0] not in _SUBCOMMANDS:
344
389
  effective = ["start"] + effective
@@ -433,9 +433,25 @@ def run_reauth(args: argparse.Namespace) -> int:
433
433
  return 1
434
434
 
435
435
  if target.check_auth():
436
- # Sync refreshed credentials to the project shell directory
436
+ # Sync refreshed credentials to the project shell directory. Mirror the
437
+ # start.py gate exactly: descriptor-bearing targets route their cred
438
+ # refresh through the credsync engine (descriptor.cred_files); only legacy
439
+ # (desc is None) targets fall back to the per-plugin refresh hook. An
440
+ # ungated target.refresh_credentials here would push a descriptor agent
441
+ # (e.g. goose) down its legacy path / bespoke copy.
437
442
  if proj.group_auth:
438
- target.refresh_credentials(proj.shell_path)
443
+ from pathlib import Path
444
+
445
+ from kanibako.targets import credsync
446
+
447
+ desc = target.descriptor
448
+ if desc is not None:
449
+ credsync.refresh_cred_files(
450
+ desc, target, host_home=Path.home(),
451
+ project_home=proj.shell_path, group_auth=proj.group_auth,
452
+ )
453
+ else:
454
+ target.refresh_credentials(proj.shell_path)
439
455
  print(f"{target.display_name}: authenticated.", file=sys.stderr)
440
456
  return 0
441
457
 
@@ -1226,7 +1226,7 @@ def run_config(args: argparse.Namespace) -> int:
1226
1226
  from kanibako.targets import resolve_target
1227
1227
  from kanibako.commands.start import (
1228
1228
  _build_config_env,
1229
- _build_effective_state,
1229
+ _effective_behavior_for_display,
1230
1230
  )
1231
1231
  merged = load_merged_config(
1232
1232
  config_file, project_toml if project_toml.exists() else None,
@@ -1257,7 +1257,7 @@ def run_config(args: argparse.Namespace) -> int:
1257
1257
  else:
1258
1258
  agent_cfg = None
1259
1259
  if target is not None and agent_cfg is not None:
1260
- agent_state = _build_effective_state(
1260
+ agent_state = _effective_behavior_for_display(
1261
1261
  target, agent_cfg, project_toml,
1262
1262
  global_config_path=std.settings,
1263
1263
  workset_config_path=workset_path,
@@ -249,6 +249,17 @@ def check_flag_relevance(args: argparse.Namespace) -> None:
249
249
  # the excluded commands entirely. Only a real string value counts as "set"
250
250
  # (a non-str sentinel from a MagicMock test stub is ignored).
251
251
  if isinstance(agent, str) and key not in _AGENT_FLAG_EXCLUDE:
252
+ # `shell` (and `box shell`) is a shell — it never launches an agent, so
253
+ # `--agent` is meaningless rather than wrong. Product decision: IGNORE
254
+ # it with a clear note (don't hard-error), then proceed to open a plain
255
+ # shell. run_shell never reads args.agent, so the value is dropped here.
256
+ if key in ("shell", "box shell"):
257
+ print(
258
+ "Note: --agent is ignored for 'shell' "
259
+ "(a shell never launches an agent).",
260
+ file=sys.stderr,
261
+ )
262
+ return
252
263
  if key not in AGENT_FLAG_COMMANDS:
253
264
  raise FlagRelevanceError(
254
265
  f"--agent is not valid for '{key}'. It applies to agent "
@@ -370,6 +370,7 @@ def run_stop(args: argparse.Namespace) -> int:
370
370
  send_request(_socket_path(), {
371
371
  "action": "stop",
372
372
  "container_name": container_name,
373
+ "helper_num": helper_num,
373
374
  })
374
375
  except Exception:
375
376
  pass # Best-effort stop
@@ -399,6 +400,7 @@ def run_cleanup(args: argparse.Namespace) -> int:
399
400
  send_request(_socket_path(), {
400
401
  "action": "stop",
401
402
  "container_name": container_name,
403
+ "helper_num": helper_num,
402
404
  })
403
405
  except Exception:
404
406
  pass