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.
- {kanibako_cli-1.6.0.dev30/src/kanibako_cli.egg-info → kanibako_cli-1.6.0rc3}/PKG-INFO +2 -1
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/pyproject.toml +2 -2
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/__init__.py +1 -1
- kanibako_cli-1.6.0rc3/src/kanibako/agent_defaults.py +183 -0
- kanibako_cli-1.6.0rc3/src/kanibako/box_seed.py +84 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/cli.py +47 -2
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/agent_cmd.py +18 -2
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/flags.py +11 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/helper_cmd.py +2 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/image.py +121 -16
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/setup_cmd.py +10 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/start.py +506 -152
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/workset_cmd.py +19 -2
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/config.py +47 -5
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/container.py +149 -39
- kanibako_cli-1.6.0rc3/src/kanibako/core_defaults.py +302 -0
- kanibako_cli-1.6.0rc3/src/kanibako/data/core-defaults.yaml +195 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/freshness.py +1 -1
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/helper_client.py +2 -1
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/helper_listener.py +52 -24
- kanibako_cli-1.6.0rc3/src/kanibako/image_sharing.py +287 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/paths.py +7 -3
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/registry_store.py +60 -1
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/settings_categories.py +52 -40
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/settings_resolve.py +73 -6
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/base.py +16 -8
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/templates.py +72 -85
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/workset.py +24 -1
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3/src/kanibako_cli.egg-info}/PKG-INFO +2 -1
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/SOURCES.txt +6 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/requires.txt +1 -0
- kanibako_cli-1.6.0rc3/tests/test_box_seed.py +137 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_cli.py +111 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_config.py +124 -17
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_container.py +212 -3
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_container_extended.py +71 -21
- kanibako_cli-1.6.0rc3/tests/test_defaults_golden.py +389 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_error_messages.py +3 -3
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_freshness.py +5 -1
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_helper_client.py +2 -2
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_helper_cmd.py +1 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_helper_listener.py +144 -23
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_image_sharing.py +302 -27
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_registry_store.py +76 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_resource_scoping.py +23 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_categories.py +335 -45
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_resolve.py +56 -1
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_seeds.py +31 -27
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_shares.py +31 -29
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_setup_cmd.py +24 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_snapshots.py +106 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_templates.py +95 -154
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_workset.py +71 -0
- kanibako_cli-1.6.0.dev30/src/kanibako/image_sharing.py +0 -133
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/LICENSE.md +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/README.md +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/setup.cfg +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/__main__.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/_atomic.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/agent_config.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/auth_browser.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/auth_parser.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/baseline.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/box_identity.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/browser_sidecar.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/browser_state.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/bun_sea.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/channels.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/__init__.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/archive.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/baseline_cmd.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/box/__init__.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/box/_duplicate.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/box/_lifecycle.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/box/_parser.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/clean.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/diagnose.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/fork_cmd.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/install.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/restore.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/stop.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/system_cmd.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/upgrade.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/commands/vault_cmd.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/config_interface.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/config_io.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containerfiles.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-android +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-dotnet +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-js +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-jvm +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/Containerfile.template-systems +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/__init__.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/containers/tmux.conf +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/data/__init__.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/data/image-baseline.yaml +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/data/templates/base/INSTRUCTIONS.md +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/deprecation.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/errors.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/git.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/helpers.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/hygiene.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/import_reconcile.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/install_method.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/log.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/names.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/plugins/__init__.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/py.typed +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/registry.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_bundle.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_meta.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_registry.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_resolve.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/rig_source.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/scripts/__init__.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/scripts/helper-init.sh +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/scripts/kanibako-entry +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/settings_seeds.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/settings_shares.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/shellenv.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/shells.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/snapshots.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/__init__.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/assembly.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/credsync.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/targets/no_agent.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/templates_image.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/tweakcc.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/tweakcc_cache.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako/utils.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/dependency_links.txt +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/entry_points.txt +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/src/kanibako_cli.egg-info/top_level.txt +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_agent_config.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_agent_resolution.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_archive_restore_integration.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_atomic.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_auth_browser.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_auth_parser.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_baseline.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_box_identity.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_browser_sidecar.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_browser_state.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_bun_sea.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_channels.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_cli_exitcodes.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_comms.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_config_interface.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_container_integration.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_containerfiles.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_credentials.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_deprecations.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_env_cmd.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_fork.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_git.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_helpers.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_hygiene.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_import.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_init_cmd.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_install_integration.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_lifecycle_integration.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_log.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_names.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_paths.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_paths_extended.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_registry.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_resolve_box_target.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_bundle.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_meta.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_registry.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_resolve.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_rig_source.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_safety_invariants.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_settings_loader.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_shellenv.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_shells.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_standalone_paths.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_start_integration.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_status.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_system_paths.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_templates_image.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_tweakcc.py +0 -0
- {kanibako_cli-1.6.0.dev30 → kanibako_cli-1.6.0rc3}/tests/test_utils.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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
|
|
260
|
-
# then copies them into each new box
|
|
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
|
-
|
|
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
|
-
|
|
782
|
-
|
|
805
|
+
return _build_template(
|
|
806
|
+
runtime, res, args.name, containers_dir, std,
|
|
783
807
|
)
|
|
784
|
-
|
|
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"
|
|
787
|
-
|
|
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
|
|
790
|
-
print(
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
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")
|