kanibako-cli 1.5.0.dev14__tar.gz → 1.5.1__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 (166) hide show
  1. {kanibako_cli-1.5.0.dev14/src/kanibako_cli.egg-info → kanibako_cli-1.5.1}/PKG-INFO +1 -1
  2. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/README.md +14 -7
  3. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/pyproject.toml +3 -2
  4. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/__init__.py +1 -1
  5. kanibako_cli-1.5.1/src/kanibako/_atomic.py +62 -0
  6. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/auth_browser.py +7 -0
  7. kanibako_cli-1.5.1/src/kanibako/baseline.py +124 -0
  8. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/cli.py +3 -1
  9. kanibako_cli-1.5.1/src/kanibako/commands/baseline_cmd.py +235 -0
  10. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/box/__init__.py +8 -5
  11. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/box/_duplicate.py +125 -27
  12. kanibako_cli-1.5.1/src/kanibako/commands/box/_lifecycle.py +1397 -0
  13. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/box/_parser.py +100 -165
  14. kanibako_cli-1.5.1/src/kanibako/commands/diagnose.py +497 -0
  15. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/image.py +59 -79
  16. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/install.py +7 -8
  17. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/start.py +359 -87
  18. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/workset_cmd.py +332 -3
  19. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/config.py +104 -34
  20. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/config_interface.py +2 -0
  21. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/config_io.py +10 -6
  22. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/container.py +86 -90
  23. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/containerfiles.py +1 -1
  24. kanibako_cli-1.5.1/src/kanibako/containers/tmux.conf +12 -0
  25. kanibako_cli-1.5.1/src/kanibako/data/image-baseline.yaml +29 -0
  26. kanibako_cli-1.5.1/src/kanibako/freshness.py +91 -0
  27. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/helper_listener.py +11 -1
  28. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/paths.py +78 -2
  29. kanibako_cli-1.5.1/src/kanibako/registry.py +174 -0
  30. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/rig_meta.py +3 -2
  31. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/rig_registry.py +3 -1
  32. kanibako_cli-1.5.1/src/kanibako/scripts/__init__.py +0 -0
  33. kanibako_cli-1.5.1/src/kanibako/shells.py +189 -0
  34. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/targets/base.py +78 -0
  35. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/templates_image.py +3 -2
  36. kanibako_cli-1.5.1/src/kanibako/workset.py +672 -0
  37. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1/src/kanibako_cli.egg-info}/PKG-INFO +1 -1
  38. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako_cli.egg-info/SOURCES.txt +11 -2
  39. kanibako_cli-1.5.1/tests/test_atomic.py +108 -0
  40. kanibako_cli-1.5.1/tests/test_baseline.py +135 -0
  41. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_cli.py +60 -25
  42. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_config.py +148 -0
  43. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_config_interface.py +65 -0
  44. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_container.py +174 -22
  45. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_container_extended.py +13 -32
  46. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_containerfiles.py +27 -16
  47. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_error_messages.py +22 -20
  48. kanibako_cli-1.5.1/tests/test_freshness.py +168 -0
  49. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_helper_listener.py +72 -0
  50. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_paths.py +104 -0
  51. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_registry.py +108 -0
  52. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_safety_invariants.py +15 -123
  53. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_settings_seeds.py +58 -0
  54. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_settings_shares.py +64 -0
  55. kanibako_cli-1.5.1/tests/test_shells.py +267 -0
  56. kanibako_cli-1.5.1/tests/test_workset.py +686 -0
  57. kanibako_cli-1.5.0.dev14/src/kanibako/commands/box/_migrate.py +0 -574
  58. kanibako_cli-1.5.0.dev14/src/kanibako/commands/diagnose.py +0 -239
  59. kanibako_cli-1.5.0.dev14/src/kanibako/containers/Containerfile.kanibako +0 -99
  60. kanibako_cli-1.5.0.dev14/src/kanibako/freshness.py +0 -67
  61. kanibako_cli-1.5.0.dev14/src/kanibako/registry.py +0 -71
  62. kanibako_cli-1.5.0.dev14/src/kanibako/workset.py +0 -347
  63. kanibako_cli-1.5.0.dev14/tests/test_freshness.py +0 -101
  64. kanibako_cli-1.5.0.dev14/tests/test_workset.py +0 -369
  65. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/LICENSE.md +0 -0
  66. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/setup.cfg +0 -0
  67. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/__main__.py +0 -0
  68. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/auth_parser.py +0 -0
  69. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/browser_sidecar.py +0 -0
  70. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/browser_state.py +0 -0
  71. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/bun_sea.py +0 -0
  72. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/__init__.py +0 -0
  73. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/archive.py +0 -0
  74. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/clean.py +0 -0
  75. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/crab_cmd.py +0 -0
  76. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/fork_cmd.py +0 -0
  77. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/helper_cmd.py +0 -0
  78. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/refresh_credentials.py +0 -0
  79. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/restore.py +0 -0
  80. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/setup_cmd.py +0 -0
  81. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/stop.py +0 -0
  82. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/system_cmd.py +0 -0
  83. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/upgrade.py +0 -0
  84. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/commands/vault_cmd.py +0 -0
  85. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/containers/Containerfile.template-android +0 -0
  86. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/containers/Containerfile.template-dotnet +0 -0
  87. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/containers/Containerfile.template-js +0 -0
  88. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/containers/Containerfile.template-jvm +0 -0
  89. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/containers/Containerfile.template-systems +0 -0
  90. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/containers/__init__.py +0 -0
  91. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/crabs.py +0 -0
  92. {kanibako_cli-1.5.0.dev14/src/kanibako/scripts → kanibako_cli-1.5.1/src/kanibako/data}/__init__.py +0 -0
  93. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/errors.py +0 -0
  94. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/git.py +0 -0
  95. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/helper_client.py +0 -0
  96. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/helpers.py +0 -0
  97. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/hygiene.py +0 -0
  98. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/image_sharing.py +0 -0
  99. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/instructions.py +0 -0
  100. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/log.py +0 -0
  101. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/names.py +0 -0
  102. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/plugins/__init__.py +0 -0
  103. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/rig_bundle.py +0 -0
  104. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/rig_resolve.py +0 -0
  105. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/rig_source.py +0 -0
  106. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/scripts/helper-init.sh +0 -0
  107. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/scripts/kanibako-entry +0 -0
  108. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/settings_resolve.py +0 -0
  109. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/settings_seeds.py +0 -0
  110. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/settings_shares.py +0 -0
  111. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/shellenv.py +0 -0
  112. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/snapshots.py +0 -0
  113. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/targets/__init__.py +0 -0
  114. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/targets/no_agent.py +0 -0
  115. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/templates.py +0 -0
  116. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/tweakcc.py +0 -0
  117. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/tweakcc_cache.py +0 -0
  118. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako/utils.py +0 -0
  119. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako_cli.egg-info/dependency_links.txt +0 -0
  120. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako_cli.egg-info/entry_points.txt +0 -0
  121. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako_cli.egg-info/requires.txt +0 -0
  122. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/src/kanibako_cli.egg-info/top_level.txt +0 -0
  123. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_archive_restore_integration.py +0 -0
  124. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_auth_browser.py +0 -0
  125. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_auth_parser.py +0 -0
  126. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_browser_sidecar.py +0 -0
  127. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_browser_state.py +0 -0
  128. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_bun_sea.py +0 -0
  129. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_cli_exitcodes.py +0 -0
  130. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_comms.py +0 -0
  131. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_container_integration.py +0 -0
  132. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_crabs.py +0 -0
  133. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_credentials.py +0 -0
  134. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_env_cmd.py +0 -0
  135. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_fork.py +0 -0
  136. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_git.py +0 -0
  137. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_helper_client.py +0 -0
  138. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_helper_cmd.py +0 -0
  139. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_helpers.py +0 -0
  140. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_hygiene.py +0 -0
  141. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_image_sharing.py +0 -0
  142. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_init_cmd.py +0 -0
  143. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_install_integration.py +0 -0
  144. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_instructions.py +0 -0
  145. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_lifecycle_integration.py +0 -0
  146. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_log.py +0 -0
  147. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_names.py +0 -0
  148. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_paths_extended.py +0 -0
  149. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_resource_scoping.py +0 -0
  150. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_rig_bundle.py +0 -0
  151. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_rig_meta.py +0 -0
  152. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_rig_registry.py +0 -0
  153. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_rig_resolve.py +0 -0
  154. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_rig_source.py +0 -0
  155. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_settings_resolve.py +0 -0
  156. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_shellenv.py +0 -0
  157. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_snapshots.py +0 -0
  158. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_standalone_paths.py +0 -0
  159. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_start_integration.py +0 -0
  160. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_status.py +0 -0
  161. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_system_paths.py +0 -0
  162. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_templates.py +0 -0
  163. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_templates_image.py +0 -0
  164. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_tweakcc.py +0 -0
  165. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/tests/test_utils.py +0 -0
  166. {kanibako_cli-1.5.0.dev14 → kanibako_cli-1.5.1}/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.5.0.dev14
3
+ Version: 1.5.1
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
@@ -222,11 +222,13 @@ shortcuts for common operations:
222
222
  | `box config` | View or modify project configuration |
223
223
  | `box diagnose [project]` | Check project box health |
224
224
 
225
- **Relocation:**
225
+ **Relocation & conversion:**
226
226
 
227
227
  | Subcommand | Description |
228
228
  |------------|-------------|
229
- | `box move [project] <dest>` | Relocate project workspace |
229
+ | `box remap <old> [<new>]` | Update kanibako's recorded path after you moved the folder yourself (records only, no file move; `<new>` defaults to `./`) |
230
+ | `box move <old> <new>` / `box mv` | Physically relocate the workspace (both paths required; a target flag also changes ownership) |
231
+ | `box convert [<old>] (--default \| --standalone \| --workset <ws>)` | Change ownership/mode (in-place by default; `--move [path]` relocates, bare `--move` moves into the target workset; `--name` renames) |
230
232
  | `box duplicate <source> [dest]` | Copy project (`--name`, `--bare`, `--force`) |
231
233
  | `box archive [project]` | Pack session data to .txz (`--all`, `--allow-uncommitted`, `--allow-unpushed`, `--force`) |
232
234
  | `box extract <archive> [dest]` | Unpack from archive (`--name`, `--force`) |
@@ -248,7 +250,7 @@ shortcuts for common operations:
248
250
  | `rig list` / `rig ls` | List available rigs (`-q`) |
249
251
  | `rig info` / `rig inspect` | Rig details (source, size, recoverability) |
250
252
  | `rig rm` / `rig delete` | Remove rig (`--force`) |
251
- | `rig rebuild [rig]` | Rebuild from registry or stored Containerfile (`--all`) |
253
+ | `rig rebuild [rig]` | Rebuild a template, or pull a prefab/base from the registry (`--all`) |
252
254
  | `rig diagnose` | Check rig (image) status |
253
255
 
254
256
  ### `workset` Subcommands
@@ -437,13 +439,17 @@ Container rigs are built on [Droste](https://github.com/doctorjei/droste), a
437
439
  layered OCI image builder.
438
440
  [Kento](https://github.com/doctorjei/kento) converts them to LXC/VM hosts.
439
441
 
440
- Rigs are pulled automatically from GHCR on first use. If the pull fails,
441
- Kanibako falls back to a local build from the bundled Containerfiles.
442
+ Base rigs are pulled automatically from GHCR on first use; they are
443
+ **pull-only** (Kanibako does not build them locally). If a pull fails, Kanibako
444
+ reports an actionable error. To use a custom base, build it yourself from the
445
+ [kanibako-images](https://github.com/doctorjei/kanibako-images) repo and point
446
+ Kanibako at the resulting local image via `--image` / `box_image`. Toolchain
447
+ *templates* still build locally (they layer on a pulled base).
442
448
 
443
449
  ```bash
444
450
  kanibako rig list # show local rigs
445
- kanibako rig rebuild # rebuild current project's rig
446
- kanibako rig rebuild --all # rebuild all known rigs
451
+ kanibako rig rebuild # build the current template / pull the current base
452
+ kanibako rig rebuild --all # update all known rigs
447
453
  ```
448
454
 
449
455
  ### Custom Rigs
@@ -802,6 +808,7 @@ All kanibako config files are YAML.
802
808
  | `autonomous` | `true` | Enable autonomy override |
803
809
  | `persistence` | `persistent` | Session type (persistent/ephemeral) |
804
810
  | `box.image` | `kanibako-oci:latest` | Container rig |
811
+ | `box.shell` | `$KANIBAKO_SHELL` | Shell launched for a no-agent box (`kanibako start` with no agent, `kanibako shell`); resolved `box.shell` → `$KANIBAKO_SHELL` → the image's recorded login shell → `sh` |
805
812
  | `box.crab` | (auto-detect) | Agent target plugin |
806
813
  | `box.share_images` | | Share host images into the box |
807
814
  | `group_auth` | `true` | Shared credentials across the group (`true`) vs. per-project (`false`) |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kanibako-cli"
7
- version = "1.5.0.dev14"
7
+ version = "1.5.1"
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"]
@@ -22,8 +22,9 @@ dev = ["pytest>=7.0", "pytest-cov", "mypy", "ruff", "bump2version"]
22
22
  where = ["src"]
23
23
 
24
24
  [tool.setuptools.package-data]
25
- "kanibako.containers" = ["Containerfile.*"]
25
+ "kanibako.containers" = ["Containerfile.*", "tmux.conf"]
26
26
  "kanibako.scripts" = ["*.sh", "kanibako-entry"]
27
+ "kanibako.data" = ["*.yaml"]
27
28
 
28
29
  [tool.ruff]
29
30
  target-version = "py311"
@@ -1,3 +1,3 @@
1
1
  """kanibako: Run AI coding agents in rootless containers with per-project isolation."""
2
2
 
3
- __version__ = "1.5.0.dev14"
3
+ __version__ = "1.5.1"
@@ -0,0 +1,62 @@
1
+ """Atomic file writes for persistent state.
2
+
3
+ A crash mid-write with a plain ``path.write_text(...)`` leaves a torn/truncated
4
+ file that the next run fails to parse. Writing to a temp file in the *same
5
+ directory* and then ``os.replace``-ing it over the target is atomic on POSIX
6
+ (``rename(2)`` within one filesystem), so a reader sees either the old file or
7
+ the new one — never a half-written one.
8
+
9
+ These helpers are dependency-free (only the stdlib) so any module can route its
10
+ registry/state writes through them without risking an import cycle.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import tempfile
17
+ from pathlib import Path
18
+
19
+ import yaml
20
+
21
+
22
+ def atomic_write_text(path: Path, data: str) -> None:
23
+ """Write *data* to *path* atomically.
24
+
25
+ The bytes land in a temp file in ``path.parent`` (so ``os.replace`` stays on
26
+ one filesystem), are flushed and ``fsync``-ed, then renamed over *path*. On
27
+ any failure the temp file is removed and the original *path* is left intact.
28
+ Parent directories are created if needed.
29
+ """
30
+ path = Path(path)
31
+ path.parent.mkdir(parents=True, exist_ok=True)
32
+
33
+ encoded = data.encode("utf-8")
34
+ # Create the temp file in the SAME directory as the target so os.replace is
35
+ # a same-filesystem rename (atomic). delete=False so we control the rename.
36
+ fd, tmp_name = tempfile.mkstemp(
37
+ prefix=f".{path.name}.", suffix=".tmp", dir=str(path.parent),
38
+ )
39
+ tmp_path = Path(tmp_name)
40
+ try:
41
+ with os.fdopen(fd, "wb") as fh:
42
+ fh.write(encoded)
43
+ fh.flush()
44
+ os.fsync(fh.fileno())
45
+ os.replace(tmp_path, path)
46
+ except BaseException:
47
+ # Leave the original file untouched; clean up the temp on any failure.
48
+ try:
49
+ tmp_path.unlink()
50
+ except OSError:
51
+ pass
52
+ raise
53
+
54
+
55
+ def atomic_write_yaml(path: Path, data: object, **dump_kwargs: object) -> None:
56
+ """Serialize *data* to YAML and write it to *path* atomically.
57
+
58
+ A thin convenience over :func:`atomic_write_text` for the common case of a
59
+ YAML document. Extra keyword arguments are forwarded to ``yaml.safe_dump``.
60
+ """
61
+ text = yaml.safe_dump(data, **dump_kwargs)
62
+ atomic_write_text(path, text)
@@ -185,6 +185,7 @@ def auto_refresh_auth(
185
185
  *,
186
186
  headless: bool = True,
187
187
  login_timeout: float = 60,
188
+ env: dict[str, str] | None = None,
188
189
  ) -> AuthResult:
189
190
  """Orchestrate fully automated OAuth: start login, parse URL, automate browser.
190
191
 
@@ -194,6 +195,11 @@ def auto_refresh_auth(
194
195
  4. If the browser clicks "Authorize", the redirect completes the login
195
196
  5. Wait for ``claude auth login`` to finish
196
197
 
198
+ *env*, when supplied, fully replaces the environment of the spawned
199
+ ``claude auth login`` process. Callers pass it to inject
200
+ ``DISABLE_AUTOUPDATER=1`` so this host exec does not wake Claude's async
201
+ background auto-updater mid-launch.
202
+
197
203
  Returns :class:`AuthResult` indicating success or failure.
198
204
  """
199
205
  import subprocess
@@ -215,6 +221,7 @@ def auto_refresh_auth(
215
221
  stderr=subprocess.STDOUT,
216
222
  stdin=subprocess.PIPE,
217
223
  text=True,
224
+ env=env,
218
225
  )
219
226
  except (FileNotFoundError, OSError) as exc:
220
227
  return AuthResult(success=False, error=f"Failed to start auth: {exc}")
@@ -0,0 +1,124 @@
1
+ """Image-baseline manifest: the universal in-box runtime tool contract.
2
+
3
+ The baseline is a mapping of ``apt-package-name -> [executables]``. The shipped
4
+ default lives in :mod:`kanibako.data` (``image-baseline.yaml``); site and user
5
+ overlays are merged on top **additively** (the scoped-shares spirit in
6
+ :mod:`kanibako.settings_shares`): later layers add new packages or override an
7
+ existing package's executable list. Precedence, least- to most-specific:
8
+
9
+ built-in default < /etc/kanibako/image-baseline.yaml < ~/.config/kanibako/image-baseline.yaml
10
+
11
+ The package NAME is what ``apt-get install`` consumes (auto-install assumes
12
+ apt/debian); the EXECUTABLE values are what ``command -v`` probes (verify is
13
+ package-manager-agnostic). They differ on purpose (e.g. ``ripgrep`` -> ``rg``).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import importlib.resources
19
+ import sys
20
+ from collections.abc import Callable
21
+ from pathlib import Path
22
+
23
+ import yaml
24
+
25
+ from kanibako.paths import xdg
26
+
27
+ # Filename used both for the shipped default (in kanibako.data) and the overlays.
28
+ BASELINE_FILENAME = "image-baseline.yaml"
29
+
30
+
31
+ def _read_doc(path: Path) -> dict[str, list[str]]:
32
+ """Parse a baseline YAML file into ``{package: [executables]}``.
33
+
34
+ Tolerates a missing/empty file (returns ``{}``). Normalizes each value to
35
+ a list of strings; a bare string value becomes a single-element list.
36
+ """
37
+ if not path.is_file():
38
+ return {}
39
+ raw = yaml.safe_load(path.read_text()) or {}
40
+ if not isinstance(raw, dict):
41
+ return {}
42
+ result: dict[str, list[str]] = {}
43
+ for pkg, exes in raw.items():
44
+ if exes is None:
45
+ result[str(pkg)] = []
46
+ elif isinstance(exes, str):
47
+ result[str(pkg)] = [exes]
48
+ else:
49
+ result[str(pkg)] = [str(e) for e in exes]
50
+ return result
51
+
52
+
53
+ def _shipped_default() -> dict[str, list[str]]:
54
+ """Read the bundled default baseline shipped as package data."""
55
+ ref = importlib.resources.files("kanibako.data").joinpath(BASELINE_FILENAME)
56
+ return _read_doc(Path(str(ref)))
57
+
58
+
59
+ def _overlay_paths() -> list[Path]:
60
+ """Overlay locations, in additive merge order (machine then user)."""
61
+ config_home = xdg("XDG_CONFIG_HOME", ".config")
62
+ return [
63
+ Path("/etc/kanibako") / BASELINE_FILENAME,
64
+ config_home / "kanibako" / BASELINE_FILENAME,
65
+ ]
66
+
67
+
68
+ def load_baseline() -> dict[str, list[str]]:
69
+ """Return the merged baseline ``{package: [executables]}``.
70
+
71
+ Starts from the shipped default, then additively merges the machine
72
+ (``/etc``) and user (``~/.config``) overlays if present: a later layer adds
73
+ a new package or replaces an existing package's executable list. Missing
74
+ overlay files are treated as empty levels.
75
+ """
76
+ merged = _shipped_default()
77
+ for path in _overlay_paths():
78
+ merged.update(_read_doc(path))
79
+ return merged
80
+
81
+
82
+ def packages() -> list[str]:
83
+ """Return the baseline package names in sorted, stable order."""
84
+ return sorted(load_baseline())
85
+
86
+
87
+ def executables() -> list[tuple[str, str]]:
88
+ """Return ``(package, executable)`` pairs, sorted by package then executable."""
89
+ pairs: list[tuple[str, str]] = []
90
+ baseline = load_baseline()
91
+ for pkg in sorted(baseline):
92
+ for exe in baseline[pkg]:
93
+ pairs.append((pkg, exe))
94
+ return pairs
95
+
96
+
97
+ def verify(probe: Callable[[str], bool]) -> list[tuple[str, str]]:
98
+ """Return the ``(package, executable)`` pairs whose executable is missing.
99
+
100
+ *probe* answers "is this executable present?" (typically a ``command -v``
101
+ check inside an image). An empty result means the baseline is satisfied.
102
+ """
103
+ return [(pkg, exe) for pkg, exe in executables() if not probe(exe)]
104
+
105
+
106
+ def install_command(pkgs: list[str]) -> list[str]:
107
+ """Build the apt-get install argv for *pkgs* (debian).
108
+
109
+ Returns the full argv (``apt-get install -y --no-install-recommends ...``).
110
+ The caller decides where it runs (host vs. in-box) and on non-debian
111
+ systems should skip it (see :func:`warn_non_debian`).
112
+ """
113
+ return [
114
+ "apt-get", "install", "-y", "--no-install-recommends", *pkgs,
115
+ ]
116
+
117
+
118
+ def warn_non_debian() -> None:
119
+ """Emit a stderr warning that auto-install only supports apt/debian."""
120
+ print(
121
+ "kanibako baseline: automatic install assumes apt/debian; "
122
+ "install the baseline packages with your distro's package manager.",
123
+ file=sys.stderr,
124
+ )
@@ -74,6 +74,7 @@ def build_parser() -> argparse.ArgumentParser:
74
74
  from kanibako.commands.workset_cmd import add_parser as add_workset_parser
75
75
  from kanibako.commands.crab_cmd import add_parser as add_crab_parser
76
76
  from kanibako.commands.system_cmd import add_parser as add_system_parser
77
+ from kanibako.commands.baseline_cmd import add_parser as add_baseline_parser
77
78
 
78
79
  # Setup wizard (before management commands, works pre-init).
79
80
  from kanibako.commands.setup_cmd import run_setup
@@ -165,6 +166,7 @@ def build_parser() -> argparse.ArgumentParser:
165
166
  add_workset_parser(subparsers)
166
167
  add_crab_parser(subparsers)
167
168
  add_system_parser(subparsers)
169
+ add_baseline_parser(subparsers)
168
170
 
169
171
  return parser
170
172
 
@@ -179,7 +181,7 @@ _SUBCOMMANDS = {
179
181
  # Top-level aliases (delegate to box subcommands).
180
182
  "start", "stop", "shell", "ps", "list", "create", "rm",
181
183
  # Management commands.
182
- "box", "rig", "workset", "crab", "system",
184
+ "box", "rig", "workset", "crab", "system", "baseline",
183
185
  # Command aliases (#62).
184
186
  "agent", "image", "container",
185
187
  # Setup wizard.
@@ -0,0 +1,235 @@
1
+ """kanibako baseline: inspect, verify, and install the image baseline contract.
2
+
3
+ The baseline is the universal set of in-box runtime tools kanibako relies on
4
+ (see :mod:`kanibako.baseline`). ``list`` feeds a Containerfile (``apt-get
5
+ install $(kanibako baseline list)``), ``verify`` probes an image for the
6
+ required executables, and ``install`` runs the apt-get install of the package
7
+ names.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import subprocess
14
+ import sys
15
+
16
+ from kanibako import baseline as baseline_mod
17
+
18
+
19
+ def add_parser(subparsers: argparse._SubParsersAction) -> None:
20
+ p = subparsers.add_parser(
21
+ "baseline",
22
+ help="Inspect/verify the universal in-box tool baseline",
23
+ description=(
24
+ "Inspect, verify, or install the image baseline — the universal set "
25
+ "of in-box runtime tools kanibako relies on across all box variants."
26
+ ),
27
+ )
28
+ sub = p.add_subparsers(dest="baseline_command", metavar="COMMAND")
29
+
30
+ # baseline list [--executables]
31
+ list_p = sub.add_parser(
32
+ "list",
33
+ help="Print baseline package names (or executables with --executables)",
34
+ )
35
+ list_p.add_argument(
36
+ "--executables", action="store_true",
37
+ help="Print 'package: exe1 exe2' per line instead of just package names",
38
+ )
39
+ list_p.set_defaults(func=run_list)
40
+
41
+ # baseline verify [IMAGE] [--all] [--only PKG ...] [--skip PKG ...]
42
+ verify_p = sub.add_parser(
43
+ "verify",
44
+ help="Probe an image for the baseline executables",
45
+ )
46
+ verify_p.add_argument(
47
+ "image", nargs="?", default=None,
48
+ help="Image to probe (default: the configured box_image)",
49
+ )
50
+ verify_p.add_argument(
51
+ "--all", action="store_true", dest="all_images",
52
+ help="Probe every local kanibako-* image",
53
+ )
54
+ verify_p.add_argument(
55
+ "--only", nargs="+", default=None, metavar="PKG",
56
+ help="Only check these baseline packages",
57
+ )
58
+ verify_p.add_argument(
59
+ "--skip", nargs="+", default=None, metavar="PKG",
60
+ help="Skip these baseline packages",
61
+ )
62
+ verify_p.set_defaults(func=run_verify)
63
+
64
+ # baseline install [--only PKG ...] [--skip PKG ...]
65
+ install_p = sub.add_parser(
66
+ "install",
67
+ help="apt-get install the baseline packages (debian; current environment)",
68
+ )
69
+ install_p.add_argument(
70
+ "--only", nargs="+", default=None, metavar="PKG",
71
+ help="Only install these baseline packages",
72
+ )
73
+ install_p.add_argument(
74
+ "--skip", nargs="+", default=None, metavar="PKG",
75
+ help="Skip these baseline packages",
76
+ )
77
+ install_p.add_argument(
78
+ "--dry-run", action="store_true",
79
+ help="Print the install command instead of running it",
80
+ )
81
+ install_p.set_defaults(func=run_install)
82
+
83
+ # Default to list when 'baseline' is run bare.
84
+ p.set_defaults(func=run_list)
85
+
86
+
87
+ def _filter_packages(
88
+ pkgs: list[str], only: list[str] | None, skip: list[str] | None,
89
+ ) -> list[str]:
90
+ """Apply --only / --skip filters to a sorted package list."""
91
+ result = pkgs
92
+ if only:
93
+ only_set = set(only)
94
+ result = [p for p in result if p in only_set]
95
+ if skip:
96
+ skip_set = set(skip)
97
+ result = [p for p in result if p not in skip_set]
98
+ return result
99
+
100
+
101
+ def run_list(args: argparse.Namespace) -> int:
102
+ """Print the baseline.
103
+
104
+ Default: package names, space-separated on one line — a Containerfile
105
+ consumes this verbatim via ``$(kanibako baseline list)``, so stdout stays
106
+ clean (no logging). With ``--executables``: ``package: exe1 exe2`` lines.
107
+ """
108
+ if getattr(args, "executables", False):
109
+ baseline = baseline_mod.load_baseline()
110
+ for pkg in sorted(baseline):
111
+ exes = " ".join(baseline[pkg])
112
+ print(f"{pkg}: {exes}")
113
+ return 0
114
+
115
+ pkgs = baseline_mod.packages()
116
+ print(" ".join(pkgs))
117
+ return 0
118
+
119
+
120
+ def _make_probe(runtime, image: str):
121
+ """Return a ``probe(exe) -> bool`` that runs an ephemeral container.
122
+
123
+ Each probe runs ``<runtime> run --rm <image> sh -lc 'command -v <exe>'``
124
+ and reports success on a zero exit code.
125
+ """
126
+ def probe(exe: str) -> bool:
127
+ result = subprocess.run(
128
+ [
129
+ runtime.cmd, "run", "--rm", image,
130
+ "sh", "-lc", f"command -v {exe}",
131
+ ],
132
+ capture_output=True,
133
+ text=True,
134
+ )
135
+ return result.returncode == 0
136
+
137
+ return probe
138
+
139
+
140
+ def _verify_image(runtime, image: str, pkgs: list[str]) -> list[tuple[str, str]]:
141
+ """Probe *image* for the executables of *pkgs*. Return missing pairs."""
142
+ baseline = baseline_mod.load_baseline()
143
+ wanted = {pkg: baseline[pkg] for pkg in pkgs if pkg in baseline}
144
+ probe = _make_probe(runtime, image)
145
+ missing: list[tuple[str, str]] = []
146
+ for pkg in sorted(wanted):
147
+ for exe in wanted[pkg]:
148
+ if not probe(exe):
149
+ missing.append((pkg, exe))
150
+ return missing
151
+
152
+
153
+ def run_verify(args: argparse.Namespace) -> int:
154
+ """Probe one or all images for the baseline executables.
155
+
156
+ Exit code: 1 if any executable is missing in any probed image, else 0.
157
+ """
158
+ from kanibako.config import config_file_path, load_merged_config
159
+ from kanibako.container import ContainerRuntime
160
+ from kanibako.errors import ContainerError
161
+ from kanibako.paths import xdg
162
+
163
+ try:
164
+ runtime = ContainerRuntime()
165
+ except ContainerError as exc:
166
+ print(f"Error: {exc}", file=sys.stderr)
167
+ return 1
168
+
169
+ pkgs = _filter_packages(
170
+ baseline_mod.packages(),
171
+ getattr(args, "only", None),
172
+ getattr(args, "skip", None),
173
+ )
174
+
175
+ # Determine the image(s) to probe.
176
+ if getattr(args, "all_images", False):
177
+ images = [repo for repo, _size in runtime.list_local_images()]
178
+ if not images:
179
+ print("No local kanibako images found.", file=sys.stderr)
180
+ return 1
181
+ else:
182
+ image = getattr(args, "image", None)
183
+ if not image:
184
+ config_home = xdg("XDG_CONFIG_HOME", ".config")
185
+ cf = config_file_path(config_home)
186
+ merged = load_merged_config(cf, None)
187
+ image = merged.box_image
188
+ images = [image]
189
+
190
+ any_missing = False
191
+ for image in images:
192
+ missing = _verify_image(runtime, image, pkgs)
193
+ print(f"Baseline verify: {image}")
194
+ if not missing:
195
+ print(" [ok] all baseline executables present")
196
+ else:
197
+ any_missing = True
198
+ for pkg, exe in missing:
199
+ print(f" [!!] {pkg}: missing '{exe}'")
200
+ print()
201
+
202
+ return 1 if any_missing else 0
203
+
204
+
205
+ def run_install(args: argparse.Namespace) -> int:
206
+ """apt-get install the baseline packages in the CURRENT environment.
207
+
208
+ This targets the environment the command runs in (e.g. inside a box, or a
209
+ host that uses apt) — it does NOT shell into a separate box. Use it from a
210
+ Containerfile/box where ``apt-get`` is available and you have privileges.
211
+ Non-debian systems are warned and skipped.
212
+ """
213
+ import shutil
214
+
215
+ pkgs = _filter_packages(
216
+ baseline_mod.packages(),
217
+ getattr(args, "only", None),
218
+ getattr(args, "skip", None),
219
+ )
220
+ if not pkgs:
221
+ print("Nothing to install.", file=sys.stderr)
222
+ return 0
223
+
224
+ cmd = baseline_mod.install_command(pkgs)
225
+
226
+ if getattr(args, "dry_run", False):
227
+ print(" ".join(cmd))
228
+ return 0
229
+
230
+ if shutil.which("apt-get") is None:
231
+ baseline_mod.warn_non_debian()
232
+ return 1
233
+
234
+ result = subprocess.run(cmd)
235
+ return result.returncode
@@ -1,7 +1,11 @@
1
- """kanibako box: project lifecycle management (create, list, config, migrate, duplicate, move, archive, extract, purge, vault)."""
1
+ """kanibako box: project lifecycle management (create, list, config, remap, move, convert, duplicate, archive, extract, purge, vault)."""
2
2
 
3
3
  from kanibako.commands.box._duplicate import run_duplicate
4
- from kanibako.commands.box._migrate import run_migrate
4
+ from kanibako.commands.box._lifecycle import (
5
+ run_convert,
6
+ run_move,
7
+ run_remap,
8
+ )
5
9
  from kanibako.commands.box._parser import (
6
10
  _check_container_running,
7
11
  _format_credential_age,
@@ -10,13 +14,12 @@ from kanibako.commands.box._parser import (
10
14
  run_create,
11
15
  run_info,
12
16
  run_list,
13
- run_move,
14
17
  run_ps,
15
18
  run_rm,
16
19
  )
17
20
 
18
21
  __all__ = [
19
22
  "_check_container_running", "_format_credential_age",
20
- "add_parser", "run_config", "run_create", "run_duplicate",
21
- "run_info", "run_list", "run_migrate", "run_move", "run_ps", "run_rm",
23
+ "add_parser", "run_config", "run_convert", "run_create", "run_duplicate",
24
+ "run_info", "run_list", "run_move", "run_ps", "run_remap", "run_rm",
22
25
  ]