kanibako-cli 1.6.0.dev30__tar.gz → 1.6.0rc3__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 (184) hide show
  1. {kanibako_cli-1.6.0.dev30/src/kanibako_cli.egg-info → kanibako_cli-1.6.0rc3}/PKG-INFO +2 -1
  2. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/pyproject.toml +2 -2
  3. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/__init__.py +1 -1
  4. kanibako_cli-1.6.0rc3/src/kanibako/agent_defaults.py +183 -0
  5. kanibako_cli-1.6.0rc3/src/kanibako/box_seed.py +84 -0
  6. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/cli.py +47 -2
  7. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/agent_cmd.py +18 -2
  8. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/flags.py +11 -0
  9. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/helper_cmd.py +2 -0
  10. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/image.py +121 -16
  11. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/setup_cmd.py +10 -0
  12. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/start.py +506 -152
  13. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/workset_cmd.py +19 -2
  14. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/config.py +47 -5
  15. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/container.py +149 -39
  16. kanibako_cli-1.6.0rc3/src/kanibako/core_defaults.py +302 -0
  17. kanibako_cli-1.6.0rc3/src/kanibako/data/core-defaults.yaml +195 -0
  18. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/freshness.py +1 -1
  19. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/helper_client.py +2 -1
  20. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/helper_listener.py +52 -24
  21. kanibako_cli-1.6.0rc3/src/kanibako/image_sharing.py +287 -0
  22. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/paths.py +7 -3
  23. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/registry_store.py +60 -1
  24. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/settings_categories.py +52 -40
  25. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/settings_resolve.py +73 -6
  26. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/base.py +16 -8
  27. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/templates.py +72 -85
  28. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/workset.py +24 -1
  29. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3/src/kanibako_cli.egg-info}/PKG-INFO +2 -1
  30. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/SOURCES.txt +6 -0
  31. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/requires.txt +1 -0
  32. kanibako_cli-1.6.0rc3/tests/test_box_seed.py +137 -0
  33. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_cli.py +111 -0
  34. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_config.py +124 -17
  35. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_container.py +212 -3
  36. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_container_extended.py +71 -21
  37. kanibako_cli-1.6.0rc3/tests/test_defaults_golden.py +389 -0
  38. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_error_messages.py +3 -3
  39. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_freshness.py +5 -1
  40. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_helper_client.py +2 -2
  41. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_helper_cmd.py +1 -0
  42. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_helper_listener.py +144 -23
  43. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_image_sharing.py +302 -27
  44. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_registry_store.py +76 -0
  45. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_resource_scoping.py +23 -0
  46. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_categories.py +335 -45
  47. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_resolve.py +56 -1
  48. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_seeds.py +31 -27
  49. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_shares.py +31 -29
  50. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_setup_cmd.py +24 -0
  51. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_snapshots.py +106 -0
  52. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_templates.py +95 -154
  53. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_workset.py +71 -0
  54. kanibako_cli-1.6.0.dev30/src/kanibako/image_sharing.py +0 -133
  55. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/LICENSE.md +0 -0
  56. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/README.md +0 -0
  57. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/setup.cfg +0 -0
  58. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/__main__.py +0 -0
  59. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/_atomic.py +0 -0
  60. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/agent_config.py +0 -0
  61. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/auth_browser.py +0 -0
  62. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/auth_parser.py +0 -0
  63. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/baseline.py +0 -0
  64. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/box_identity.py +0 -0
  65. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/browser_sidecar.py +0 -0
  66. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/browser_state.py +0 -0
  67. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/bun_sea.py +0 -0
  68. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/channels.py +0 -0
  69. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/__init__.py +0 -0
  70. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/archive.py +0 -0
  71. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/baseline_cmd.py +0 -0
  72. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/box/__init__.py +0 -0
  73. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/box/_duplicate.py +0 -0
  74. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/box/_lifecycle.py +0 -0
  75. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/box/_parser.py +0 -0
  76. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/clean.py +0 -0
  77. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/diagnose.py +0 -0
  78. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/fork_cmd.py +0 -0
  79. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/install.py +0 -0
  80. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/restore.py +0 -0
  81. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/stop.py +0 -0
  82. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/system_cmd.py +0 -0
  83. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/upgrade.py +0 -0
  84. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/vault_cmd.py +0 -0
  85. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/config_interface.py +0 -0
  86. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/config_io.py +0 -0
  87. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containerfiles.py +0 -0
  88. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-android +0 -0
  89. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-dotnet +0 -0
  90. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-js +0 -0
  91. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-jvm +0 -0
  92. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-systems +0 -0
  93. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/__init__.py +0 -0
  94. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/tmux.conf +0 -0
  95. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/data/__init__.py +0 -0
  96. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/data/image-baseline.yaml +0 -0
  97. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/data/templates/base/INSTRUCTIONS.md +0 -0
  98. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/deprecation.py +0 -0
  99. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/errors.py +0 -0
  100. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/git.py +0 -0
  101. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/helpers.py +0 -0
  102. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/hygiene.py +0 -0
  103. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/import_reconcile.py +0 -0
  104. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/install_method.py +0 -0
  105. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/log.py +0 -0
  106. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/names.py +0 -0
  107. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/plugins/__init__.py +0 -0
  108. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/py.typed +0 -0
  109. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/registry.py +0 -0
  110. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_bundle.py +0 -0
  111. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_meta.py +0 -0
  112. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_registry.py +0 -0
  113. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_resolve.py +0 -0
  114. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_source.py +0 -0
  115. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/scripts/__init__.py +0 -0
  116. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/scripts/helper-init.sh +0 -0
  117. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/scripts/kanibako-entry +0 -0
  118. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/settings_seeds.py +0 -0
  119. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/settings_shares.py +0 -0
  120. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/shellenv.py +0 -0
  121. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/shells.py +0 -0
  122. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/snapshots.py +0 -0
  123. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/__init__.py +0 -0
  124. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/assembly.py +0 -0
  125. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/credsync.py +0 -0
  126. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/no_agent.py +0 -0
  127. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/templates_image.py +0 -0
  128. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/tweakcc.py +0 -0
  129. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/tweakcc_cache.py +0 -0
  130. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/utils.py +0 -0
  131. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/dependency_links.txt +0 -0
  132. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/entry_points.txt +0 -0
  133. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/top_level.txt +0 -0
  134. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_agent_config.py +0 -0
  135. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_agent_resolution.py +0 -0
  136. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_archive_restore_integration.py +0 -0
  137. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_atomic.py +0 -0
  138. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_auth_browser.py +0 -0
  139. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_auth_parser.py +0 -0
  140. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_baseline.py +0 -0
  141. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_box_identity.py +0 -0
  142. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_browser_sidecar.py +0 -0
  143. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_browser_state.py +0 -0
  144. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_bun_sea.py +0 -0
  145. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_channels.py +0 -0
  146. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_cli_exitcodes.py +0 -0
  147. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_comms.py +0 -0
  148. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_config_interface.py +0 -0
  149. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_container_integration.py +0 -0
  150. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_containerfiles.py +0 -0
  151. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_credentials.py +0 -0
  152. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_deprecations.py +0 -0
  153. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_env_cmd.py +0 -0
  154. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_fork.py +0 -0
  155. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_git.py +0 -0
  156. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_helpers.py +0 -0
  157. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_hygiene.py +0 -0
  158. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_import.py +0 -0
  159. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_init_cmd.py +0 -0
  160. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_install_integration.py +0 -0
  161. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_lifecycle_integration.py +0 -0
  162. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_log.py +0 -0
  163. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_names.py +0 -0
  164. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_paths.py +0 -0
  165. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_paths_extended.py +0 -0
  166. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_registry.py +0 -0
  167. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_resolve_box_target.py +0 -0
  168. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_bundle.py +0 -0
  169. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_meta.py +0 -0
  170. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_registry.py +0 -0
  171. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_resolve.py +0 -0
  172. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_source.py +0 -0
  173. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_safety_invariants.py +0 -0
  174. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_loader.py +0 -0
  175. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_shellenv.py +0 -0
  176. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_shells.py +0 -0
  177. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_standalone_paths.py +0 -0
  178. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_start_integration.py +0 -0
  179. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_status.py +0 -0
  180. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_system_paths.py +0 -0
  181. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_templates_image.py +0 -0
  182. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_tweakcc.py +0 -0
  183. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_utils.py +0 -0
  184. {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/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.dev30
3
+ Version: 1.6.0rc3
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.dev30"
7
+ version = "1.6.0rc3"
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.dev30"
3
+ __version__ = "1.6.0rc3"
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,84 @@
1
+ """Uniform per-box seed-once query/record API.
2
+
3
+ This is the single read/write surface the launch path (Step 3) will call to
4
+ ask "has this box already been seeded?" and to record "this box is now
5
+ seeded". It hides where each box's seeded flag lives by dispatching on
6
+ ``proj.mode`` (a :class:`kanibako.paths.BoxMode`) to whichever registry owns
7
+ the box:
8
+
9
+ * ``BoxMode.primary`` → the ``seeded`` section's ``projects`` domain in
10
+ ``registry.yaml`` (keyed by ``proj.name``).
11
+ * ``BoxMode.standalone`` → the ``seeded`` section's ``standalone`` domain in
12
+ ``registry.yaml`` (keyed by ``proj.name``).
13
+ * ``BoxMode.named`` → ``WorksetProject.seeded`` inside the owning
14
+ workset's ``settings.yaml`` (located via ``proj.group.root``).
15
+
16
+ It is the explicit, registry-resident successor to the brittle ``.seeded``
17
+ sentinel file (now REMOVED): Step 3 wired launch-time detection over to here,
18
+ ORing this registry flag with an existing-inbox backstop for legacy boxes.
19
+ Layering: this module
20
+ depends on ``paths`` (types only), ``registry_store``, and ``workset``; none of
21
+ those import ``box_seed``, so there is no import cycle.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from kanibako import registry_store
27
+ from kanibako.paths import BoxMode, ProjectPaths, StandardPaths
28
+
29
+
30
+ def box_is_seeded(proj: ProjectPaths, std: StandardPaths) -> bool:
31
+ """Return whether *proj*'s box has completed its one-time seed.
32
+
33
+ Dispatches on ``proj.mode`` to the owning registry. Returns False for a
34
+ box that has never been seeded (or, defensively, a ``named`` box whose
35
+ group/workset-project cannot be resolved).
36
+ """
37
+ if proj.mode is BoxMode.primary:
38
+ return registry_store.is_box_seeded(std.data_path, "projects", proj.name)
39
+ if proj.mode is BoxMode.standalone:
40
+ return registry_store.is_box_seeded(std.data_path, "standalone", proj.name)
41
+ # BoxMode.named: the flag lives on the WorksetProject.
42
+ if proj.group is None:
43
+ return False
44
+ from kanibako.errors import WorksetError
45
+ from kanibako.workset import load_workset
46
+
47
+ try:
48
+ ws = load_workset(proj.group.root)
49
+ except WorksetError:
50
+ # Defensive: a named box whose workset cannot be loaded (e.g. its
51
+ # settings.yaml is absent or not yet materialised) reads as unseeded.
52
+ return False
53
+ for p in ws.projects:
54
+ if p.name == proj.name:
55
+ return p.seeded
56
+ return False
57
+
58
+
59
+ def mark_box_seeded(proj: ProjectPaths, std: StandardPaths) -> None:
60
+ """Record that *proj*'s box has completed its one-time seed.
61
+
62
+ Dispatches on ``proj.mode`` to the owning registry. Idempotent; a no-op
63
+ (defensive) for a ``named`` box whose group/workset-project cannot be
64
+ resolved.
65
+ """
66
+ if proj.mode is BoxMode.primary:
67
+ registry_store.mark_box_seeded_entry(std.data_path, "projects", proj.name)
68
+ return
69
+ if proj.mode is BoxMode.standalone:
70
+ registry_store.mark_box_seeded_entry(std.data_path, "standalone", proj.name)
71
+ return
72
+ # BoxMode.named: persist on the WorksetProject via the workset's public API.
73
+ if proj.group is None:
74
+ return
75
+ from kanibako.errors import WorksetError
76
+ from kanibako.workset import load_workset, set_project_seeded
77
+
78
+ try:
79
+ ws = load_workset(proj.group.root)
80
+ set_project_seeded(ws, proj.name)
81
+ except WorksetError:
82
+ # Defensive: a named box whose workset cannot be loaded/updated is a
83
+ # no-op (mirrors the missing-group guard above).
84
+ return
@@ -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
 
@@ -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
@@ -33,7 +33,7 @@ from kanibako.rig_registry import (
33
33
  remove as registry_remove,
34
34
  upsert,
35
35
  )
36
- from kanibako.rig_resolve import resolve_rig
36
+ from kanibako.rig_resolve import RigResolution, resolve_rig
37
37
  from kanibako.rig_source import derive_name, detect_source_kind, fetch_to_temp
38
38
  from kanibako.shells import capture_image_shell
39
39
  from kanibako.templates_image import (
@@ -76,6 +76,30 @@ def add_parser(subparsers: argparse._SubParsersAction) -> None:
76
76
  prep_p.add_argument("--all", action="store_true", dest="all_images", help="Prep all local kanibako rigs")
77
77
  prep_p.set_defaults(func=run_prep)
78
78
 
79
+ # kanibako rig update
80
+ update_p = image_sub.add_parser(
81
+ "update",
82
+ help="Bring a rig up to date: pull a newer image (or rebuild on it)",
83
+ description=(
84
+ "Refresh a rig to the latest image. For a pulled/prefab rig, pull "
85
+ "the newer upstream image; for a template/built rig, rebuild it on "
86
+ "top of the refreshed base. Defaults to the current box's rig "
87
+ "(box.image) when no name is given. This is the everyday "
88
+ "'get the latest' path; use 'rig prep --force' to rebuild from "
89
+ "scratch."
90
+ ),
91
+ )
92
+ update_p.add_argument(
93
+ "name", nargs="?", default=None,
94
+ help="Rig name to update (omit to update the configured box.image rig; "
95
+ "use --all for every local kanibako rig)",
96
+ )
97
+ update_p.add_argument(
98
+ "--all", action="store_true", dest="all_images",
99
+ help="Update all local kanibako rigs (pull each)",
100
+ )
101
+ update_p.set_defaults(func=run_update)
102
+
79
103
  # kanibako rig add
80
104
  add_p = image_sub.add_parser(
81
105
  "add",
@@ -778,25 +802,106 @@ def run_prep(args: argparse.Namespace) -> int:
778
802
  return 0
779
803
 
780
804
  if res.kind == "template":
781
- cf = res.containerfile or get_containerfile(
782
- f"template-{args.name}", containers_dir,
805
+ return _build_template(
806
+ runtime, res, args.name, containers_dir, std,
783
807
  )
784
- if cf is None:
808
+
809
+ # prefab: pull (same as rebuild).
810
+ return _update_one(runtime, res.image, std)
811
+
812
+
813
+ def _build_template(
814
+ runtime: ContainerRuntime,
815
+ res: RigResolution,
816
+ name: str,
817
+ containers_dir: Path,
818
+ std,
819
+ ) -> int:
820
+ """Build a template rig from its Containerfile and capture its image shell.
821
+
822
+ Shared by ``rig prep`` (build/--force-rebuild paths) and ``rig update``
823
+ (rebuild-on-refreshed-base path). The build always reads ``FROM`` afresh, so
824
+ a rebuild naturally picks up a newer base image.
825
+ """
826
+ cf = res.containerfile or get_containerfile(
827
+ f"template-{name}", containers_dir,
828
+ )
829
+ if cf is None:
830
+ print(
831
+ f"error: Containerfile not found for template '{name}'.",
832
+ file=sys.stderr,
833
+ )
834
+ return 1
835
+ print(f"Building rig '{name}' from {cf.name}...")
836
+ rc = runtime.rebuild(res.image, cf, cf.parent, build_args=None)
837
+ if rc == 0:
838
+ print(f"Rig '{name}' prepped as {res.image}")
839
+ capture_image_shell(runtime, res.image, std)
840
+ else:
841
+ print(f"Build failed with exit code {rc}", file=sys.stderr)
842
+ return rc
843
+
844
+
845
+ def run_update(args: argparse.Namespace) -> int:
846
+ """Bring a rig up to date with the latest image.
847
+
848
+ Unlike ``prep`` (which short-circuits an already-prepped rig) and
849
+ ``prep --force`` (which rebuilds from scratch), ``update`` is the everyday
850
+ "get the latest" path:
851
+
852
+ * **prefab / pulled base** -> pull the upstream image again (idempotent;
853
+ podman reports whether the layers changed);
854
+ * **template / built rig** -> rebuild it, which reads ``FROM`` afresh and so
855
+ lands on the refreshed base;
856
+ * **extended (interactive)** -> has no reproducible recipe; report that it
857
+ can't be auto-updated.
858
+
859
+ With no *name*, the target defaults to the configured ``box.image`` rig (the
860
+ current box's rig). ``--all`` pulls every local kanibako rig.
861
+ """
862
+ config_file = config_file_path(xdg("XDG_CONFIG_HOME", ".config"))
863
+ config = load_config(config_file)
864
+ std = load_std_paths(config)
865
+ containers_dir = std.data_path / "containers"
866
+
867
+ try:
868
+ runtime = ContainerRuntime()
869
+ except ContainerError as e:
870
+ print(f"Error: {e}", file=sys.stderr)
871
+ return 1
872
+
873
+ if args.all_images:
874
+ return _update_all(runtime, std)
875
+
876
+ merged = load_merged_config(config_file, None)
877
+
878
+ # Default target: the configured box.image rig (the current box's rig).
879
+ name = args.name if args.name is not None else merged.box_image
880
+
881
+ registry = load_registry(registry_path(std))
882
+ res = resolve_rig(name, runtime, std, merged, registry=registry)
883
+
884
+ if res.kind == "extended":
885
+ if res.prep_action == "none":
785
886
  print(
786
- f"error: Containerfile not found for template '{args.name}'.",
787
- file=sys.stderr,
887
+ f"Rig '{name}' is an extended (interactive) rig with no recipe "
888
+ f"to rebuild; nothing to update. Re-create it with 'rig extend', "
889
+ f"or move a fresh build with 'rig export'/'rig import'.",
788
890
  )
789
- return 1
790
- print(f"Building rig '{args.name}' from {cf.name}...")
791
- rc = runtime.rebuild(res.image, cf, cf.parent, build_args=None)
792
- if rc == 0:
793
- print(f"Rig '{args.name}' prepped as {res.image}")
794
- capture_image_shell(runtime, res.image, std)
795
- else:
796
- print(f"Build failed with exit code {rc}", file=sys.stderr)
797
- return rc
891
+ return 0
892
+ print(
893
+ f"error: extended rig '{name}' image is missing and has no recipe "
894
+ f"to rebuild (use 'rig export'/'rig import' or 'rig extend').",
895
+ file=sys.stderr,
896
+ )
897
+ return 1
798
898
 
799
- # prefab: pull (same as rebuild).
899
+ if res.kind == "template":
900
+ # Rebuild on the refreshed base (FROM is re-read on every build).
901
+ return _build_template(runtime, res, name, containers_dir, std)
902
+
903
+ # prefab / pulled base: always pull, even if already local -- the whole
904
+ # point of update is to fetch a possibly-newer upstream image.
800
905
  return _update_one(runtime, res.image, std)
801
906
 
802
907
 
@@ -184,6 +184,16 @@ def run_setup(args: argparse.Namespace) -> int:
184
184
  for name, cls in targets.items():
185
185
  try:
186
186
  instance = cls()
187
+ if not getattr(instance, "has_binary", True):
188
+ # The binary-less "Shell" no-agent target needs no host binary;
189
+ # it ships in the image and is always available -- never flag it
190
+ # as "not found" (mirrors `system diagnose`).
191
+ print(
192
+ f" [ok] {instance.display_name} "
193
+ "(image default; no host binary needed)"
194
+ )
195
+ found_any = True
196
+ continue
187
197
  install = instance.detect()
188
198
  if install is not None:
189
199
  print(f" [ok] {instance.display_name} detected")