imagen-cli 0.1.2__tar.gz → 0.1.4__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.
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/PKG-INFO +5 -3
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/README.md +4 -2
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/README.zh-CN.md +4 -2
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/pyproject.toml +1 -1
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/skill_targets.py +23 -4
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/skills_service.py +17 -8
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/history.py +7 -1
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/entrypoint.py +14 -4
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/human.py +163 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/options.py +1 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/renderers.py +2 -2
- imagen_cli-0.1.4/src/imagen_cli/cli/selection.py +142 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_bundled_skill_guides.py +1 -1
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_setup_selection.py +7 -1
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_skills_install.py +30 -12
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_skills_safety.py +5 -5
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_skills_update.py +25 -4
- imagen_cli-0.1.4/tests/unit/test_cli_selection.py +62 -0
- imagen_cli-0.1.4/tests/unit/test_skill_result_render.py +30 -0
- imagen_cli-0.1.4/tests/unit/test_skill_targets.py +79 -0
- imagen_cli-0.1.2/src/imagen_cli/cli/selection.py +0 -27
- imagen_cli-0.1.2/tests/unit/test_cli_selection.py +0 -10
- imagen_cli-0.1.2/tests/unit/test_skill_targets.py +0 -30
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/.gitignore +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/LICENSE +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/README.en.md +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/__init__.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/__main__.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/approval_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/artifact_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/batch_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/batch_summary.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/cache_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/cancel_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/cleanup_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/container.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/cost_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/execution_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/export_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/filters_advanced.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/filters_fast.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/filters_frequency.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/filters_numpy.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/filters_rank.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/filters_restoration.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/filters_structure.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/fork_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/import_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/job_state.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/media_ops.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/model_catalog_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/opener.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/operator_catalog.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/operator_catalog_en.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/outcome_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/planning_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/postprocess_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/postprocess_worker.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/preflight.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/profile_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/quality_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/reconcile.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/release_readiness.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/session_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/sheet_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/skill_targets.NOTICE.md +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/skill_targets.json +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/superres.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/application/update_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/bundled_skills/imagen/SKILL.md +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/bundled_skills/imagen/references/workflows.md +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/bundled_skills/imagen-assets/SKILL.md +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/bundled_skills/imagen-batch/SKILL.md +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/asset.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/batch.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/cache.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/create.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/discovery.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/job.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/models.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/plan.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/postprocess.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/profile.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/session.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/skills.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/commands/worker.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/creation_input.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/diagnostics.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/generated_docs.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/input_readers.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/preview.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/registry.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/tui/__init__.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/tui/app.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/tui/preview.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/cli/usage.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/domain/__init__.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/domain/contracts.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/domain/enums.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/domain/errors.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/domain/models.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/domain/primitive.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/execution/configured.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/execution/foreground.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/execution/queued.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/execution/recovery.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/execution/worker.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/i18n.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/automatic1111.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/base.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/bfl.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/comfyui.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/common.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/contracts.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/gemini.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/google_cloud.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/model_platforms.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/openai_compatible.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/openai_images.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/openai_responses.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/openrouter.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/providers/stability.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/artifacts.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/config.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/database.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/download.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/migrations.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/private_state.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/queries.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/repositories.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/safety.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/src/imagen_cli/storage/workspace.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/__init__.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/conftest.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_asset_commands.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_cli_output_modes.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_cost_usage.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_creation_input.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_creation_planning_cli.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_discovery_commands.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_gemini_contract.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_generated_docs.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_job_cancel.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_local_discovery.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_machine_contracts.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_openai_images_contract.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_openai_responses.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_plan_commands.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_profile_commands.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_provider_adapters.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_provider_contracts.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_registry_composition_foundation.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_requirements_traceability.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_secret_redaction.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_t037_create_handlers.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_t039_local_job_handlers.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/contract/test_t039_local_session_handlers.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fault/test_cancel_races.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fault/test_create_recovery.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fault/test_download_security.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fault/test_file_recovery.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fault/test_no_duplicate_create.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fault/test_portable_archive.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fixtures/__init__.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fixtures/contracts/creation-input.schema.json +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fixtures/contracts/event.schema.json +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fixtures/contracts/examples.json +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fixtures/contracts/result.schema.json +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/fixtures/offline.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_approval_execution.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_approved_execution.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_async_consumer.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_async_worker.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_batch_execution.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_cli_approved_journey.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_configured_gemini.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_configured_timeout.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_cross_provider_approval.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_experimental_postprocess_cli.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_i18n_cli.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_local_cli.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_m1_safety_gate.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_response_recovery.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_session_concurrency.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_session_history.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_session_resume_edit.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_skills_targets_cli.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/integration/test_worker_lifecycle.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/live/conftest.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/live/journey.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/live/test_authorized_providers.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/live/test_provider_journey.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/smoke/test_cross_platform.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/smoke/test_human_terminal.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/smoke/test_offline_contract.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/smoke/test_performance.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/smoke/test_preview.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/smoke/test_release_readiness.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/smoke/test_tui.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/smoke/test_tui_i18n.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/storage/test_migrations_v2.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/support.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_advanced_ops.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_artifact_outcome_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_batch_summary.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_cache_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_cli_sync_gaps.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_cli_usage_errors.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_cli_version_and_session_view.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_credentials.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_domain_validation.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_fast_guided.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_filters_advanced.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_filters_numpy.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_filters_restoration.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_filters_structure.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_foreground.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_frequency_detection.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_gc_staging.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_i18n.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_job_state.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_journey_dossier.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_kuwahara_identity.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_local_laplacian.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_media_ops.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_offline_fixtures.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_operator_catalog_plain_language.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_operator_catalog_upgrade.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_planning_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_budget.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_catalog_display.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_color.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_eval.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_op_parser.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_provenance.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_registry.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_replay.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_replay_pipeline.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_versions.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_postprocess_worker.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_private_permissions.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_process_identity.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_profile_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_profile_set_and_catalog.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_provider_base.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_quality_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_repository_invariants.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_session_remove_artifact.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_sheet_service.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_ssim_local.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_storage.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_superres.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_transform_preset_cli.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_tui_controls.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_tui_palette_arguments.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_tui_shared_presets.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_update_check.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_weighted_guided.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_weighted_median.py +0 -0
- {imagen_cli-0.1.2 → imagen_cli-0.1.4}/tests/unit/test_wls_detail.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: imagen-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Persistent image creation workbench for humans and agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/NekoCCN/ImagenCli
|
|
6
6
|
Project-URL: Source, https://github.com/NekoCCN/ImagenCli
|
|
@@ -449,6 +449,8 @@ Imagen ships skills that teach coding agents when to use it, how to discover com
|
|
|
449
449
|
|
|
450
450
|
```bash
|
|
451
451
|
imagen skills list
|
|
452
|
+
imagen skills install # asks which agents, in a terminal
|
|
453
|
+
imagen skills install --target universal # the shared .agents/skills directory
|
|
452
454
|
imagen skills install --target claude-code --target codex
|
|
453
455
|
imagen skills install --scope global --target claude-code
|
|
454
456
|
imagen skills uninstall --target codex
|
|
@@ -457,10 +459,10 @@ imagen skills uninstall --target codex
|
|
|
457
459
|
<img src="assets/imagen-cli-skills.png" alt="Agent skill installation options" width="760">
|
|
458
460
|
|
|
459
461
|
- Default scope is the current project; `--scope global` installs into user directories.
|
|
460
|
-
- `--target` may be repeated
|
|
462
|
+
- Without `--target`, skills go to `universal` — the shared `.agents/skills` directory that most agents read. Add more agents with `--target`, which may be repeated; `--target all` selects every target in the bundled compatibility table.
|
|
463
|
+
- In a terminal without `--target`, Imagen opens a checklist: arrows move, space selects, `a` selects all, enter confirms, `q` cancels. The shared directory and any agent directory already present in the project start ticked. Without a terminal — or with `--no-interactive` — the command installs into the default target and never prompts.
|
|
461
464
|
- Project-only agents are reported when global installation is requested.
|
|
462
465
|
- Installation writes a managed manifest (`.imagen-install.json`). Uninstall refuses unmanaged directories and symlinked paths. A locally modified managed skill needs `--force --yes`.
|
|
463
|
-
- When run in a terminal without `--target`, Imagen shows an interactive target selector.
|
|
464
466
|
|
|
465
467
|
The bundled skills are `imagen`, `imagen-assets`, and `imagen-batch`.
|
|
466
468
|
|
|
@@ -407,6 +407,8 @@ Imagen ships skills that teach coding agents when to use it, how to discover com
|
|
|
407
407
|
|
|
408
408
|
```bash
|
|
409
409
|
imagen skills list
|
|
410
|
+
imagen skills install # asks which agents, in a terminal
|
|
411
|
+
imagen skills install --target universal # the shared .agents/skills directory
|
|
410
412
|
imagen skills install --target claude-code --target codex
|
|
411
413
|
imagen skills install --scope global --target claude-code
|
|
412
414
|
imagen skills uninstall --target codex
|
|
@@ -415,10 +417,10 @@ imagen skills uninstall --target codex
|
|
|
415
417
|
<img src="assets/imagen-cli-skills.png" alt="Agent skill installation options" width="760">
|
|
416
418
|
|
|
417
419
|
- Default scope is the current project; `--scope global` installs into user directories.
|
|
418
|
-
- `--target` may be repeated
|
|
420
|
+
- Without `--target`, skills go to `universal` — the shared `.agents/skills` directory that most agents read. Add more agents with `--target`, which may be repeated; `--target all` selects every target in the bundled compatibility table.
|
|
421
|
+
- In a terminal without `--target`, Imagen opens a checklist: arrows move, space selects, `a` selects all, enter confirms, `q` cancels. The shared directory and any agent directory already present in the project start ticked. Without a terminal — or with `--no-interactive` — the command installs into the default target and never prompts.
|
|
419
422
|
- Project-only agents are reported when global installation is requested.
|
|
420
423
|
- Installation writes a managed manifest (`.imagen-install.json`). Uninstall refuses unmanaged directories and symlinked paths. A locally modified managed skill needs `--force --yes`.
|
|
421
|
-
- When run in a terminal without `--target`, Imagen shows an interactive target selector.
|
|
422
424
|
|
|
423
425
|
The bundled skills are `imagen`, `imagen-assets`, and `imagen-batch`.
|
|
424
426
|
|
|
@@ -379,6 +379,8 @@ Imagen 不会静默安装超分运行依赖或权重。源码许可不等于模
|
|
|
379
379
|
|
|
380
380
|
```bash
|
|
381
381
|
imagen skills list
|
|
382
|
+
imagen skills install # 在终端里询问装到哪些 agent
|
|
383
|
+
imagen skills install --target universal # 共享的 .agents/skills 目录
|
|
382
384
|
imagen skills install --target claude-code --target codex
|
|
383
385
|
imagen skills install --scope global --target claude-code
|
|
384
386
|
imagen skills uninstall --target codex
|
|
@@ -387,10 +389,10 @@ imagen skills uninstall --target codex
|
|
|
387
389
|
<img src="assets/imagen-cli-skills.png" alt="Agent skill 安装选项" width="760">
|
|
388
390
|
|
|
389
391
|
- 默认安装到当前项目;`--scope global` 安装到用户目录。
|
|
390
|
-
- `--target`
|
|
392
|
+
- 未指定 `--target` 时安装到 `universal`,即多数 agent 都会读取的共享目录 `.agents/skills`。需要更多 agent 时重复传 `--target`;`--target all` 选择兼容表中的全部目标。
|
|
393
|
+
- 在终端中未指定 `--target` 时打开勾选列表:方向键移动、空格选择、`a` 全选、回车确认、`q` 取消。共享目录和项目里已存在的 agent 目录默认勾选。没有终端(或加 `--no-interactive`)时直接装到默认目标,不提问。
|
|
391
394
|
- 全局安装时会报告仅支持项目作用域的目标。
|
|
392
395
|
- 安装会写入托管清单(`.imagen-install.json`)。卸载不会删除未托管目录或符号链接路径;已修改的受管 skill 需要 `--force --yes`。
|
|
393
|
-
- 在终端中未指定 `--target` 时,会显示交互式目标选择器。
|
|
394
396
|
|
|
395
397
|
随包 skills 为 `imagen`、`imagen-assets` 和 `imagen-batch`。
|
|
396
398
|
|
|
@@ -11,6 +11,22 @@ def targets() -> list[dict[str, str]]:
|
|
|
11
11
|
return list(json.loads(Path(__file__).with_suffix(".json").read_text(encoding="utf-8")))
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
DEFAULT_TARGET = "universal"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def default_root(scope: str) -> Path:
|
|
18
|
+
return root_for(DEFAULT_TARGET, scope)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def prompt_order() -> tuple[list[str], list[str]]:
|
|
22
|
+
"""Picker labels (shared directory first, then detected agents) and what starts ticked."""
|
|
23
|
+
found = set(detected()) - {DEFAULT_TARGET}
|
|
24
|
+
names = [row["name"] for row in targets() if row["name"] != DEFAULT_TARGET]
|
|
25
|
+
first = [name for name in names if name in found]
|
|
26
|
+
rest = [name for name in names if name not in found]
|
|
27
|
+
return [DEFAULT_TARGET, *first, *rest], [DEFAULT_TARGET, *first]
|
|
28
|
+
|
|
29
|
+
|
|
14
30
|
def root_for(target: str, scope: str) -> Path:
|
|
15
31
|
if scope not in {"project", "global"}:
|
|
16
32
|
raise ValueError("scope must be project or global")
|
|
@@ -27,16 +43,19 @@ def root_for(target: str, scope: str) -> Path:
|
|
|
27
43
|
if target == "claude-code" and os.environ.get("CLAUDE_CONFIG_DIR"):
|
|
28
44
|
return Path(os.environ["CLAUDE_CONFIG_DIR"]).expanduser() / "skills"
|
|
29
45
|
if value.startswith("~/.config/"):
|
|
30
|
-
config = Path(os.environ.get("XDG_CONFIG_HOME"
|
|
46
|
+
config = Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config")
|
|
31
47
|
return config / value.removeprefix("~/.config/")
|
|
32
48
|
return Path(value).expanduser()
|
|
33
49
|
|
|
34
50
|
|
|
35
51
|
def detected() -> list[str]:
|
|
52
|
+
"""Agents whose directory already exists in this project; the shared one is implicit."""
|
|
53
|
+
shared = root_for(DEFAULT_TARGET, "project")
|
|
36
54
|
found = []
|
|
37
55
|
for row in targets():
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
candidate = Path.cwd() / row["project"]
|
|
57
|
+
if candidate == shared or candidate.parent == Path.cwd():
|
|
58
|
+
continue
|
|
59
|
+
if candidate.parent.is_dir():
|
|
41
60
|
found.append(row["name"])
|
|
42
61
|
return found
|
|
@@ -57,7 +57,7 @@ def root_for(scope: str) -> Path:
|
|
|
57
57
|
def _roots(
|
|
58
58
|
scope: str, agents: list[str] | None, *, deduplicate: bool = True
|
|
59
59
|
) -> list[tuple[str, Path]]:
|
|
60
|
-
names = agents or [
|
|
60
|
+
names = agents or [skill_targets.DEFAULT_TARGET]
|
|
61
61
|
if "all" in names:
|
|
62
62
|
names = [row["name"] for row in skill_targets.targets() if row.get(scope)]
|
|
63
63
|
roots = []
|
|
@@ -160,7 +160,7 @@ def list_(
|
|
|
160
160
|
states = {
|
|
161
161
|
current: {
|
|
162
162
|
agent: _state(root, entry["name"], bundled)
|
|
163
|
-
for agent, root in _roots(current, agents, deduplicate=False)
|
|
163
|
+
for agent, root in _roots(current, agents or ["all"], deduplicate=False)
|
|
164
164
|
}
|
|
165
165
|
for current in scopes
|
|
166
166
|
}
|
|
@@ -271,7 +271,7 @@ def install_(
|
|
|
271
271
|
expanded = (
|
|
272
272
|
[row["name"] for row in skill_targets.targets() if row.get(scope)]
|
|
273
273
|
if agents and "all" in agents
|
|
274
|
-
else agents or [
|
|
274
|
+
else agents or [skill_targets.DEFAULT_TARGET]
|
|
275
275
|
)
|
|
276
276
|
return ("internal_error" if failed else "succeeded"), {
|
|
277
277
|
"scope": scope,
|
|
@@ -314,10 +314,20 @@ def update_(
|
|
|
314
314
|
if not (target / "SKILL.md").is_file():
|
|
315
315
|
continue
|
|
316
316
|
record = _managed(target, name)
|
|
317
|
+
source = Path(bundled[name]["path"])
|
|
317
318
|
if record is None:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
# A copy Imagen did not write: identical is fine, anything else is not ours.
|
|
320
|
+
if _manifest(target, name)["files"] == _manifest(source, name)["files"]:
|
|
321
|
+
current.append({"name": name, "path": str(target), "agent": agent})
|
|
322
|
+
else:
|
|
323
|
+
conflicts.append(
|
|
324
|
+
{
|
|
325
|
+
"name": name,
|
|
326
|
+
"path": str(target),
|
|
327
|
+
"agent": agent,
|
|
328
|
+
"reason": "unmanaged",
|
|
329
|
+
}
|
|
330
|
+
)
|
|
321
331
|
continue
|
|
322
332
|
recorded = record.get("files")
|
|
323
333
|
dirty = _manifest(target, name)["files"] != recorded
|
|
@@ -327,11 +337,10 @@ def update_(
|
|
|
327
337
|
"name": name,
|
|
328
338
|
"path": str(target),
|
|
329
339
|
"agent": agent,
|
|
330
|
-
"reason": "modified
|
|
340
|
+
"reason": "modified",
|
|
331
341
|
}
|
|
332
342
|
)
|
|
333
343
|
continue
|
|
334
|
-
source = Path(bundled[name]["path"])
|
|
335
344
|
if (
|
|
336
345
|
not dirty
|
|
337
346
|
and record.get("skill_version") == bundled[name]["version"]
|
|
@@ -4,6 +4,7 @@ import hashlib
|
|
|
4
4
|
import json
|
|
5
5
|
from typing import Any, cast
|
|
6
6
|
|
|
7
|
+
from imagen_cli import i18n
|
|
7
8
|
from imagen_cli.application.cleanup_service import collect, delete_session, remove_session_artifact
|
|
8
9
|
from imagen_cli.application.export_service import _rows, export_session
|
|
9
10
|
from imagen_cli.application.fork_service import fork
|
|
@@ -118,7 +119,12 @@ def gc_(
|
|
|
118
119
|
"deleted": [],
|
|
119
120
|
}
|
|
120
121
|
if yes is not True or plan is None:
|
|
121
|
-
raise ValueError(
|
|
122
|
+
raise ValueError(
|
|
123
|
+
i18n.t(
|
|
124
|
+
"--plan and --yes are required for destructive GC",
|
|
125
|
+
"删除资产需要 --plan 与 --yes",
|
|
126
|
+
)
|
|
127
|
+
)
|
|
122
128
|
if plan != digest:
|
|
123
129
|
return "conflict", {
|
|
124
130
|
"dry_run": False,
|
|
@@ -328,6 +328,17 @@ def _parse(argv: list[str]) -> tuple[str, list[str] | dict[str, Any], SharedOpti
|
|
|
328
328
|
"imagen help",
|
|
329
329
|
)
|
|
330
330
|
if target_command is not None and target_command not in COMMANDS:
|
|
331
|
+
group = sorted(
|
|
332
|
+
name for name in COMMANDS if name.startswith(f"{target_command} ")
|
|
333
|
+
)
|
|
334
|
+
if group:
|
|
335
|
+
raise UsageError(
|
|
336
|
+
i18n.t(
|
|
337
|
+
f"'{target_command}' is a command group: {', '.join(group)}.",
|
|
338
|
+
f"'{target_command}' 是一组命令:{', '.join(group)}。",
|
|
339
|
+
),
|
|
340
|
+
f"imagen help {group[0]}",
|
|
341
|
+
)
|
|
331
342
|
raise UsageError(
|
|
332
343
|
i18n.t(
|
|
333
344
|
f"Unknown help target '{target_command}'.",
|
|
@@ -622,11 +633,10 @@ def main(
|
|
|
622
633
|
and not any(value == "--target" or value.startswith("--target=") for value in argv)
|
|
623
634
|
and not any(value in {"--help", "-h"} for value in argv)
|
|
624
635
|
):
|
|
625
|
-
from imagen_cli.application.skill_targets import
|
|
636
|
+
from imagen_cli.application.skill_targets import prompt_order
|
|
626
637
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
)
|
|
638
|
+
labels, preselected = prompt_order()
|
|
639
|
+
selected = choose(labels, stdin=sys.stdin, stderr=stderr, selected=preselected)
|
|
630
640
|
if not selected:
|
|
631
641
|
raise KeyboardInterrupt
|
|
632
642
|
for name in selected:
|
|
@@ -52,6 +52,154 @@ def _unknown_line(turn: dict[str, Any]) -> str:
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
def _conflict_reason(code: object) -> str:
|
|
56
|
+
if code == "unmanaged":
|
|
57
|
+
return t(
|
|
58
|
+
"not installed by Imagen and the content differs; delete that folder and reinstall",
|
|
59
|
+
"不是 Imagen 安装的,内容也不同;删掉该目录后重新安装",
|
|
60
|
+
)
|
|
61
|
+
if code == "modified":
|
|
62
|
+
return t("edited after install", "安装后被修改过")
|
|
63
|
+
return str(code or t("not managed by Imagen", "不是 Imagen 安装的"))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _detail_result_lines(data: dict[str, Any]) -> list[str]:
|
|
67
|
+
"""Single-resource results: artifacts, providers, models, GC, workers, exports."""
|
|
68
|
+
lines: list[str] = []
|
|
69
|
+
artifact = data.get("artifact")
|
|
70
|
+
if isinstance(artifact, dict) and artifact.get("id"):
|
|
71
|
+
size = ""
|
|
72
|
+
if artifact.get("width") and artifact.get("height"):
|
|
73
|
+
size = f" · {artifact['width']}×{artifact['height']}"
|
|
74
|
+
lines.append(t(f"Artifact {artifact['id']}", f"产物 {artifact['id']}") + size)
|
|
75
|
+
if artifact.get("byte_size"):
|
|
76
|
+
lines.append(f"{artifact['byte_size']} bytes")
|
|
77
|
+
if data.get("path"):
|
|
78
|
+
lines.append(t("File: ", "文件:") + str(data["path"]))
|
|
79
|
+
if data.get("available") is False:
|
|
80
|
+
lines.append(t("Not available on disk", "磁盘上不可用"))
|
|
81
|
+
for row in _rows(data, "owners")[:_MAX_ROWS]:
|
|
82
|
+
lines.append(
|
|
83
|
+
f" {row.get('role') or ''} · {row.get('session_id') or ''}"
|
|
84
|
+
f" · {row.get('source') or ''}"
|
|
85
|
+
)
|
|
86
|
+
model = data.get("model")
|
|
87
|
+
if isinstance(model, dict) and model.get("id"):
|
|
88
|
+
evidence = model.get("evidence") or "manual"
|
|
89
|
+
lines.append(t(f"Model {model['id']} ({evidence})", f"模型 {model['id']}({evidence})"))
|
|
90
|
+
for row in _rows(data, "items")[:_MAX_ROWS]:
|
|
91
|
+
lines.append(f"{row.get('profile')} · {row.get('provider')}/{row.get('family')}")
|
|
92
|
+
if isinstance(data.get("plan"), str) and "candidates" in data:
|
|
93
|
+
plan = data["plan"]
|
|
94
|
+
lines.append(t(f"GC plan {plan}", f"GC 清单 {plan}"))
|
|
95
|
+
for row in _rows(data, "candidates")[:_MAX_ROWS]:
|
|
96
|
+
lines.append(f" {row.get('id')} · {row.get('path')}")
|
|
97
|
+
if data.get("dry_run"):
|
|
98
|
+
lines.append(
|
|
99
|
+
t(
|
|
100
|
+
f"To delete: imagen asset gc --plan {plan} --yes",
|
|
101
|
+
f"确认删除:imagen asset gc --plan {plan} --yes",
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
deleted = data.get("deleted") or []
|
|
106
|
+
lines.append(t(f"Deleted {len(deleted)} artifacts", f"已删除 {len(deleted)} 个产物"))
|
|
107
|
+
if "workers" in data:
|
|
108
|
+
workers = _rows(data, "workers")
|
|
109
|
+
if not workers:
|
|
110
|
+
lines.append(t("No worker is running", "没有正在运行的 worker"))
|
|
111
|
+
for row in workers[:_MAX_ROWS]:
|
|
112
|
+
lines.append(f"{row.get('id')} · {row.get('state')} · {row.get('pid') or ''}")
|
|
113
|
+
if "recovered" in data:
|
|
114
|
+
recovered = data.get("recovered") or []
|
|
115
|
+
lines.append(
|
|
116
|
+
t(f"Recovered {len(recovered)} leases", f"接管 {len(recovered)} 个租约")
|
|
117
|
+
if recovered
|
|
118
|
+
else t("No expired lease", "没有过期租约")
|
|
119
|
+
)
|
|
120
|
+
worker = data.get("worker")
|
|
121
|
+
if isinstance(worker, dict) and worker.get("id"):
|
|
122
|
+
lines.append(f"worker {worker.get('id')} · {worker.get('state') or ''}")
|
|
123
|
+
if isinstance(data.get("archive"), str):
|
|
124
|
+
lines.append(t(f"Exported to {data['archive']}", f"已导出到 {data['archive']}"))
|
|
125
|
+
if data.get("turns") is not None:
|
|
126
|
+
lines.append(
|
|
127
|
+
f"{data.get('turns')} turns · {data.get('artifacts')} artifacts"
|
|
128
|
+
)
|
|
129
|
+
batch = data.get("batch")
|
|
130
|
+
if isinstance(batch, dict) and batch.get("id"):
|
|
131
|
+
summary = data.get("summary") or {}
|
|
132
|
+
state = summary.get("state") or batch.get("state") or ""
|
|
133
|
+
lines.append(t(f"Batch {batch['id']} · {state}", f"批次 {batch['id']} · {state}"))
|
|
134
|
+
if summary.get("child_count") is not None:
|
|
135
|
+
lines.append(
|
|
136
|
+
t(
|
|
137
|
+
f" {summary.get('complete_success_count')}/{summary.get('child_count')}"
|
|
138
|
+
f" complete · {summary.get('saved_artifact_count')} saved",
|
|
139
|
+
f" {summary.get('complete_success_count')}/{summary.get('child_count')}"
|
|
140
|
+
f" 完成 · 已保存 {summary.get('saved_artifact_count')}",
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
for row in _rows(data, "children")[:_MAX_ROWS]:
|
|
144
|
+
child = row.get("job_id") or row.get("id") or ""
|
|
145
|
+
lines.append(f" {child} · {row.get('state') or row.get('remote_state') or ''}")
|
|
146
|
+
approval = data.get("approval")
|
|
147
|
+
if isinstance(approval, dict) and approval.get("plan_id"):
|
|
148
|
+
lines.append(f"plan {approval.get('plan_id')} · {approval.get('state') or ''}")
|
|
149
|
+
if isinstance(data.get("model"), str) and data.get("sha256"):
|
|
150
|
+
lines.append(
|
|
151
|
+
t(
|
|
152
|
+
f"Downloaded {data['model']} → {data.get('path')} ({data.get('bytes')} bytes)",
|
|
153
|
+
f"已下载 {data['model']} → {data.get('path')}({data.get('bytes')} 字节)",
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
return lines
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _skill_result_lines(data: dict[str, Any]) -> list[str]:
|
|
160
|
+
"""What `skills install`, `update` and `uninstall` did, one line each."""
|
|
161
|
+
lines: list[str] = []
|
|
162
|
+
for row in _rows(data, "installed")[:_MAX_ROWS]:
|
|
163
|
+
lines.append(
|
|
164
|
+
t(f"Installed {row.get('name')} → {row.get('path')}",
|
|
165
|
+
f"已安装 {row.get('name')} → {row.get('path')}")
|
|
166
|
+
)
|
|
167
|
+
for row in _rows(data, "removed")[:_MAX_ROWS]:
|
|
168
|
+
lines.append(
|
|
169
|
+
t(f"Removed {row.get('name')} ({row.get('path')})",
|
|
170
|
+
f"已卸载 {row.get('name')}({row.get('path')})")
|
|
171
|
+
)
|
|
172
|
+
updated = data.get("updated")
|
|
173
|
+
if isinstance(updated, list) and updated:
|
|
174
|
+
names = ", ".join(str(name) for name in updated)
|
|
175
|
+
lines.append(t(f"Updated: {names}", f"已更新:{names}"))
|
|
176
|
+
current = data.get("current")
|
|
177
|
+
if isinstance(current, list) and current:
|
|
178
|
+
names = ", ".join(str(name) for name in current)
|
|
179
|
+
lines.append(t(f"Already current: {names}", f"已是最新:{names}"))
|
|
180
|
+
for row in _rows(data, "existing")[:_MAX_ROWS]:
|
|
181
|
+
lines.append(
|
|
182
|
+
t(
|
|
183
|
+
f"Skipped {row.get('name')}: {row.get('path')} exists (add --force to replace)",
|
|
184
|
+
f"跳过 {row.get('name')}:{row.get('path')} 已存在(加 --force 覆盖)",
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
for row in _rows(data, "conflicts")[:_MAX_ROWS]:
|
|
188
|
+
reason = _conflict_reason(row.get("reason"))
|
|
189
|
+
lines.append(
|
|
190
|
+
t(
|
|
191
|
+
f"Conflict {row.get('name')}: {reason} ({row.get('path')})",
|
|
192
|
+
f"冲突 {row.get('name')}:{reason}({row.get('path')})",
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
for row in _rows(data, "failed")[:_MAX_ROWS]:
|
|
196
|
+
lines.append(
|
|
197
|
+
t(f"Failed {row.get('name')} ({row.get('path')})",
|
|
198
|
+
f"失败 {row.get('name')}({row.get('path')})")
|
|
199
|
+
)
|
|
200
|
+
return lines
|
|
201
|
+
|
|
202
|
+
|
|
55
203
|
def _schema_rows(data: dict[str, Any]) -> list[tuple[str, str, str]]:
|
|
56
204
|
schema = data.get("input_schema") or {}
|
|
57
205
|
properties = schema.get("properties") or {}
|
|
@@ -260,6 +408,8 @@ def _plain_lines(value: dict[str, Any]) -> list[str]:
|
|
|
260
408
|
)
|
|
261
409
|
note = t(" · update available", " · 可更新") if stale_any else ""
|
|
262
410
|
lines.append(f"{row.get('name')} · {row.get('version')} · {where}{note}")
|
|
411
|
+
lines.extend(_skill_result_lines(data))
|
|
412
|
+
lines.extend(_detail_result_lines(data))
|
|
263
413
|
if data.get("installed") and data.get("latest"):
|
|
264
414
|
state = (
|
|
265
415
|
t("update available", "有可用更新")
|
|
@@ -365,6 +515,10 @@ def _error_lines(value: dict[str, Any]) -> list[str]:
|
|
|
365
515
|
if command:
|
|
366
516
|
lines.append(t(f"Next: {command}", f"下一步:{command}"))
|
|
367
517
|
status = value.get("status", "unknown")
|
|
518
|
+
data = value.get("data")
|
|
519
|
+
if isinstance(data, dict):
|
|
520
|
+
# A conflict still carries what was skipped or refused.
|
|
521
|
+
lines.extend(_skill_result_lines(data))
|
|
368
522
|
return lines + [t(f"status: {status}", f"状态:{status}")]
|
|
369
523
|
|
|
370
524
|
|
|
@@ -549,6 +703,12 @@ def _rich(value: dict[str, Any], width: int) -> str:
|
|
|
549
703
|
t("yes", "是") if stale_any else t("no", "否"),
|
|
550
704
|
)
|
|
551
705
|
console.print(table)
|
|
706
|
+
skill_results = _skill_result_lines(data)
|
|
707
|
+
for line in skill_results:
|
|
708
|
+
console.print(line)
|
|
709
|
+
detail_results = _detail_result_lines(data)
|
|
710
|
+
for line in detail_results:
|
|
711
|
+
console.print(line)
|
|
552
712
|
if data.get("installed") and data.get("latest"):
|
|
553
713
|
state = (
|
|
554
714
|
t("update available", "有可用更新")
|
|
@@ -664,6 +824,9 @@ def _rich(value: dict[str, Any], width: int) -> str:
|
|
|
664
824
|
artifacts,
|
|
665
825
|
parameters,
|
|
666
826
|
details,
|
|
827
|
+
skills,
|
|
828
|
+
skill_results,
|
|
829
|
+
detail_results,
|
|
667
830
|
)
|
|
668
831
|
):
|
|
669
832
|
console.print(json.dumps(data, ensure_ascii=False))
|
|
@@ -57,6 +57,7 @@ COMMAND_CONTROLS: dict[str, set[str]] = {
|
|
|
57
57
|
"session remove-image": {"purge"},
|
|
58
58
|
"asset gc": {"dry_run", "yes"},
|
|
59
59
|
"cache purge": {"plan", "yes"},
|
|
60
|
+
"skills uninstall": {"yes"},
|
|
60
61
|
"transform": {"pipeline", "op", "preset", "session", "timeout"},
|
|
61
62
|
"ops": {"op", "preset", "verbose"},
|
|
62
63
|
"models refresh": {"profile", "send_credential"},
|
|
@@ -43,7 +43,7 @@ ERROR_REASONS = {
|
|
|
43
43
|
"validation_error": "the request parameters are invalid",
|
|
44
44
|
"unsupported_capability": "this provider or model does not support the requested capability",
|
|
45
45
|
"not_found": "no matching record exists in this workspace",
|
|
46
|
-
"conflict": "the
|
|
46
|
+
"conflict": "the command conflicted and changed nothing",
|
|
47
47
|
"wait_timeout": "the job did not finish before the timeout; it may still be running",
|
|
48
48
|
"approval_required": "this step needs an explicit approval first",
|
|
49
49
|
"provider_error": "the provider returned an error",
|
|
@@ -60,7 +60,7 @@ ERROR_REASONS_ZH = {
|
|
|
60
60
|
"validation_error": "请求参数不合法",
|
|
61
61
|
"unsupported_capability": "当前供应商或模型不支持该能力",
|
|
62
62
|
"not_found": "当前工作区中没有匹配的记录",
|
|
63
|
-
"conflict": "
|
|
63
|
+
"conflict": "命令发生冲突,未做任何改动",
|
|
64
64
|
"wait_timeout": "任务在超时前未结束,可能仍在运行",
|
|
65
65
|
"approval_required": "该步骤需要先显式批准",
|
|
66
66
|
"provider_error": "供应商返回错误",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Small terminal selector shared by explicit setup commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TextIO
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.live import Live
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
from imagen_cli.i18n import t
|
|
13
|
+
|
|
14
|
+
# Which keys do what; `apply` is pure so the state machine is testable without a TTY.
|
|
15
|
+
_UP = {"\x1b[A", "\x1bOA", "\x00H", "\xe0H", "k"}
|
|
16
|
+
_DOWN = {"\x1b[B", "\x1bOB", "\x00P", "\xe0P", "j"}
|
|
17
|
+
_CONFIRM = {"\r", "\n"}
|
|
18
|
+
_CANCEL = {"\x1b", "q"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def apply(
|
|
22
|
+
labels: list[str], picked: list[str], cursor: int, key: str
|
|
23
|
+
) -> tuple[list[str], int, bool | None]:
|
|
24
|
+
"""One keystroke of the checklist: (selection, cursor, done)."""
|
|
25
|
+
if key in _UP:
|
|
26
|
+
return picked, (cursor - 1) % len(labels), None
|
|
27
|
+
if key in _DOWN:
|
|
28
|
+
return picked, (cursor + 1) % len(labels), None
|
|
29
|
+
if key == " ":
|
|
30
|
+
name = labels[cursor]
|
|
31
|
+
if name in picked:
|
|
32
|
+
return [item for item in picked if item != name], cursor, None
|
|
33
|
+
return [*picked, name], cursor, None
|
|
34
|
+
if key == "a":
|
|
35
|
+
return ([] if len(picked) == len(labels) else list(labels)), cursor, None
|
|
36
|
+
if key in _CANCEL:
|
|
37
|
+
return [], cursor, False
|
|
38
|
+
if key in _CONFIRM:
|
|
39
|
+
return (picked or [labels[cursor]]), cursor, True
|
|
40
|
+
return picked, cursor, None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _tty(stream: TextIO) -> bool:
|
|
44
|
+
try:
|
|
45
|
+
return stream.isatty()
|
|
46
|
+
except (AttributeError, ValueError):
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _read_key(stream: TextIO) -> str:
|
|
51
|
+
if os.name == "nt":
|
|
52
|
+
import msvcrt
|
|
53
|
+
|
|
54
|
+
getwch = msvcrt.getwch # type: ignore[attr-defined] # typeshed omits it off Windows
|
|
55
|
+
char: str = getwch()
|
|
56
|
+
return char + (getwch() if char in "\x00\xe0" else "")
|
|
57
|
+
import select
|
|
58
|
+
import termios
|
|
59
|
+
import tty
|
|
60
|
+
|
|
61
|
+
descriptor = stream.fileno()
|
|
62
|
+
settings = termios.tcgetattr(descriptor)
|
|
63
|
+
try:
|
|
64
|
+
tty.setcbreak(descriptor, termios.TCSADRAIN) # TCSAFLUSH drops queued keys
|
|
65
|
+
# Read the fd, not the text wrapper: its buffer would hide the escape tail.
|
|
66
|
+
char = os.read(descriptor, 1).decode("utf-8", "replace")
|
|
67
|
+
if char == "\x1b":
|
|
68
|
+
for _ in range(2):
|
|
69
|
+
if not select.select([descriptor], [], [], 0.05)[0]:
|
|
70
|
+
break
|
|
71
|
+
char += os.read(descriptor, 1).decode("utf-8", "replace")
|
|
72
|
+
finally:
|
|
73
|
+
termios.tcsetattr(descriptor, termios.TCSADRAIN, settings)
|
|
74
|
+
return char
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _render(labels: list[str], picked: list[str], cursor: int, height: int) -> Text:
|
|
78
|
+
start = max(0, min(cursor - height // 2, len(labels) - height))
|
|
79
|
+
body = Text()
|
|
80
|
+
for index in range(start, min(start + height, len(labels))):
|
|
81
|
+
name = labels[index]
|
|
82
|
+
body.append("› " if index == cursor else " ", style="bold cyan")
|
|
83
|
+
body.append("◉ " if name in picked else "○ ", style="green" if name in picked else "dim")
|
|
84
|
+
body.append(name, style="bold" if index == cursor else "")
|
|
85
|
+
body.append("\n")
|
|
86
|
+
if start or start + height < len(labels):
|
|
87
|
+
end = min(start + height, len(labels))
|
|
88
|
+
body.append(f" {start + 1}-{end}/{len(labels)}\n", style="dim")
|
|
89
|
+
body.append(
|
|
90
|
+
t(
|
|
91
|
+
"↑/↓ move · space select · a all · enter confirm · q cancel",
|
|
92
|
+
"↑/↓ 移动 · 空格选择 · a 全选 · 回车确认 · q 取消",
|
|
93
|
+
),
|
|
94
|
+
style="dim",
|
|
95
|
+
)
|
|
96
|
+
return body
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _pick(numbers: list[str], stdin: TextIO, stderr: TextIO, selected: list[str]) -> list[str]:
|
|
100
|
+
for index, label in enumerate(numbers, 1):
|
|
101
|
+
mark = "*" if label in selected else " "
|
|
102
|
+
stderr.write(f"{index:2}. [{mark}] {label}\n")
|
|
103
|
+
stderr.write(
|
|
104
|
+
t(
|
|
105
|
+
"Enter numbers (comma-separated, blank keeps the starred ones): ",
|
|
106
|
+
"输入序号(多个用逗号分隔,空白保留带 * 的项):",
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
stderr.flush()
|
|
110
|
+
value = stdin.readline()
|
|
111
|
+
if value == "":
|
|
112
|
+
return []
|
|
113
|
+
value = value.strip()
|
|
114
|
+
if not value:
|
|
115
|
+
return list(selected)
|
|
116
|
+
try:
|
|
117
|
+
indices = list(dict.fromkeys(int(item.strip()) - 1 for item in value.split(",")))
|
|
118
|
+
if any(index < 0 or index >= len(numbers) for index in indices):
|
|
119
|
+
return []
|
|
120
|
+
return [numbers[index] for index in indices]
|
|
121
|
+
except ValueError:
|
|
122
|
+
return []
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def choose(
|
|
126
|
+
labels: list[str], *, stdin: TextIO, stderr: TextIO, selected: list[str] | None = None
|
|
127
|
+
) -> list[str]:
|
|
128
|
+
"""Tick agents in a checklist; `selected` starts ticked."""
|
|
129
|
+
picked = [label for label in labels if label in (selected or [])]
|
|
130
|
+
if not _tty(stdin) or not _tty(stderr):
|
|
131
|
+
return _pick(labels, stdin, stderr, picked)
|
|
132
|
+
console = Console(file=stderr, force_terminal=True, highlight=False)
|
|
133
|
+
height = max(5, min(len(labels), console.height - 3))
|
|
134
|
+
cursor = 0
|
|
135
|
+
with Live(
|
|
136
|
+
_render(labels, picked, cursor, height), console=console, transient=True
|
|
137
|
+
) as live:
|
|
138
|
+
while True:
|
|
139
|
+
picked, cursor, done = apply(labels, picked, cursor, _read_key(stdin))
|
|
140
|
+
if done is not None:
|
|
141
|
+
return picked if done else []
|
|
142
|
+
live.update(_render(labels, picked, cursor, height))
|
|
@@ -6,7 +6,7 @@ from imagen_cli.cli.entrypoint import _parse
|
|
|
6
6
|
def test_installed_guide_has_workflow_reference(tmp_path, monkeypatch):
|
|
7
7
|
monkeypatch.chdir(tmp_path)
|
|
8
8
|
assert skills_service.install_(["imagen"])[0] == "succeeded"
|
|
9
|
-
root = tmp_path / ".
|
|
9
|
+
root = tmp_path / ".agents/skills/imagen"
|
|
10
10
|
text = (root / "SKILL.md").read_text()
|
|
11
11
|
assert "references/workflows.md" in text
|
|
12
12
|
guide = (root / "references/workflows.md").read_text()
|
|
@@ -11,10 +11,16 @@ class Terminal(io.StringIO):
|
|
|
11
11
|
|
|
12
12
|
def test_interactive_skills_and_model_cancel(tmp_path, monkeypatch):
|
|
13
13
|
monkeypatch.chdir(tmp_path)
|
|
14
|
+
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
15
|
+
monkeypatch.setenv("USERPROFILE", str(tmp_path / "home"))
|
|
14
16
|
monkeypatch.setattr("sys.stdin", Terminal("\n"))
|
|
15
17
|
out, err = Terminal(), io.StringIO()
|
|
16
|
-
assert main(["skills", "install"], stdout=out, stderr=err)
|
|
18
|
+
assert main(["skills", "install"], stdout=out, stderr=err) == 0
|
|
19
|
+
assert (tmp_path / ".agents/skills/imagen/SKILL.md").is_file()
|
|
17
20
|
assert not (tmp_path / ".claude").exists()
|
|
21
|
+
monkeypatch.setattr("sys.stdin", Terminal(""))
|
|
22
|
+
out, err = Terminal(), io.StringIO()
|
|
23
|
+
assert main(["skills", "install", "--skill", "imagen-batch"], stdout=out, stderr=err) != 0
|
|
18
24
|
monkeypatch.setattr("sys.stdin", Terminal("\n"))
|
|
19
25
|
out, err = Terminal(), io.StringIO()
|
|
20
26
|
assert main(["superres", "fetch"], stdout=out, stderr=err) != 0
|