cmpnd 0.62.0__tar.gz → 0.63.0__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.
- {cmpnd-0.62.0 → cmpnd-0.63.0}/PKG-INFO +1 -1
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/org.py +71 -4
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/top.py +2 -2
- {cmpnd-0.62.0 → cmpnd-0.63.0}/docs/cli.md +8 -3
- {cmpnd-0.62.0 → cmpnd-0.63.0}/pyproject.toml +1 -1
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_org.py +160 -4
- {cmpnd-0.62.0 → cmpnd-0.63.0}/uv.lock +1 -1
- {cmpnd-0.62.0 → cmpnd-0.63.0}/.gitignore +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/AGENTS.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/CLAUDE.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/README.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_assessments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_authored_import.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_deployments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_exporter_pool.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_gepa_patch.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_http.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_program_patch.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_rlm_patch.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_runtime_tools.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_timespec.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/_valsets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/authored.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/api_client.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/admin.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/assessments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/auth.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/deployments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/evals.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/login.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/optimizations.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/signatures.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/tool_servers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/commands/valsets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/deprecation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/pagination.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/shell.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/trace_render.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/tree_help.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/cli/verbs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/client.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/configuration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/context.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/dataset_sync.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/decorators.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/deployment.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/deployment_metadata.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/deployment_runs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/eval_handler.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/execution.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/exporter.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/exporter_http.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/imaging.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/ir/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/ir/decode.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/ir/encode.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/ir/hash.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/ir/program.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/ir/reflection.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/ir/types.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/ir/xxh64.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/lm_configuration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/mcp_servers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/models.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/named_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/optimization.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/optimizers/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/optimizers/gepa.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/optimizers/gepa_callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/optimizers/resume.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/optimizers/tracker.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/packaging.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/references.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/retry.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/cmpnd/traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/docs/2026-04-02-code-review.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/docs/client.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/docs/custom-proposer.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/docs/reading-traces.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/docs/sdk-instrumentation.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/examples/generalizing_proposer.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/examples/local_ollama.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/LINEAGE.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/README.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/bodies.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/program_names.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/protocols.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/py.typed +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/request_log.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/retry.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/ingest/pyproject.toml +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/audit-sdist.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/capture_review_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/check-pip-install.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/check_release_artifacts.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/fixtures/review_run.json.gz +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/gepa_resume_bug.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/install-test-env.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/release-stress.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/require_version_bump.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/seed_review_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/selfhost_compat.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/scripts/test-in-linux.sh +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/stubs/dspy/__init__.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/stubs/dspy/primitives/__init__.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/stubs/dspy/primitives/prediction.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/stubs/dspy/utils/__init__.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/stubs/dspy/utils/callback.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/stubs/dspy/utils/dummies.pyi +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/_credential_route.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/_deployment_signature_helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/_exporter_helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/_fake_clock.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/_identity_helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/_resolve_refs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/_traced_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/committee_task.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/fake_lm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/helpers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/target_capabilities.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_assessments_cli.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_capture_prompts.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_composite_module.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_deploy_execute.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_deploy_pandas.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_deploy_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_edge_cases.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_example_hash.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_gepa_optimization.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_interpreter_spans.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_lm_accuracy.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_module_tracing.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_optimization_lineage.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_failures.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_loop.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_review_committee.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_parallel_export.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_project_attribution.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_react_tool_spans.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_save_load.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_trace_structure.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_traces_read.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_user_attribution.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/e2e/test_valsets_cli.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/01_predict_qa.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/08_opaque_module.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/10_react.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/11_refine.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/12_retrieve.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/13_knn.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/14_parallel.json +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/identity_vectors/README.md +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/ingest_conformance.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/authored_optimize/judge.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/hosted_optimize_probe.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/optimization_run_probe.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/test_logs_renders_traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/test_optimization_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/test_optimizing_progress.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/integration/test_unhappy_path.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/__main__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/drain.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/ingest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/latency_transport.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/rlm_shape.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/rlm_size_report.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/sweep.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/test_rlm_shape.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/load/test_rlm_size_report.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/seed_parity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_auth.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_deployments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_evals.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_health.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_modules.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_optimizations.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_signatures.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_trace_streaming.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/parity/test_traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/span_reference_fixture.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/_provider.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/_readiness.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/_reliability.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/_result.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/mint_bedrock_token.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_bedrock_converse.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_deploy_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_multi_client_stress.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_optimize_client_committee.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_optimize_loop.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_optimize_stress.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_provider_roles.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_readiness.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_real_provider_twins.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_reliability.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_reliability_classify.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_rlm_wasm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/stress/test_trace_search.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_adapter_reentrancy.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_assessments_api.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_authored.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_authored_binding_credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_authored_bundle_collisions.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_authored_metric_contract.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_authored_named_models.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_authored_optimize.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_candidate_lineage.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_admin.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_api_client.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_assessments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_auth.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_deployment_runs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_deployments.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_evals.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_login.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_main.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_optimizations.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_pagination.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_parity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_programs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_provider_credentials.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_schema_sample.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_shell.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_signatures.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_tool_servers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_top.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_traces.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_traces_program_and_module.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_traces_search_wire.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_tree_help.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cli/test_valsets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_client.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_client_floor.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_client_program_names.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_clustering/__init__.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_clustering/conftest.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_clustering/test_edge_cases.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_clustering/test_hash_consistency.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_clustering/test_type_conversion.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_cmp551_grouping_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_configuration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_context.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_context_propagation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_credential_kwargs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_credential_selection.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_credential_values.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_dataset_polling.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_dataset_sync.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_datasets.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_decision_client_spans.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_decorators.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_deploy.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_deploy_mcp_tool_references.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_deploy_program_id.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_deployment_metadata.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_deployment_runs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_deployments_read.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_eval.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_eval_example_attribution.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_eval_start.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_eval_trace_grouping.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_exception_paths.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_execute.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_execute_run_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_exporter.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_exporter_program_names.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_exporter_queue.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_exporter_real.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_fake_lm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_gepa_callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_gepa_integration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_gepa_output_normalization.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_gepa_resume.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_imaging.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_import_weight.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_ingest_conformance.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_ingest_second_producer.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_integration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_interpreter_callback.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_ir_encoder_properties.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_ir_golden_vectors.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_ir_roundtrip.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_lm15_native_calls.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_lm_configuration.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_lm_usage_shapes.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_mcp_tools.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_models.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_multi_client_stress_readiness.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimization_progress.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimization_start.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimize.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimize_budget.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimize_lift.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimize_lms.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimize_proposer.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimize_reflection_lm.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_optimize_terminal_failure.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_packaging.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_packaging_callable_source.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_packaging_mcp_tools.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_packaging_program_source.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_poll_until.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_program_id_deprecation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_program_identity.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_program_lineage_persist.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_program_names_in_deploy.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_programs_create.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_project_payload.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_real_provider_twins_readiness.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_reflector_properties.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_reflector_smoke.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_release_stress_script.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_replaced_names.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_repro_reactv2_bugs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_require_version_bump.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_retry.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_reversible_failures.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_rlm_patch.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_run_attribution_paths.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_runtime_tools.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_schemas.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_seal_lm_credential.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_seed_review_run.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_signature_inputs.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_span_references.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_span_sent_once.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_system_one.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_trace_context_concurrency.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_trace_fact_wire_contract.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_trace_facts.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_trace_render.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_traces_read.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_tracked_gepa.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_tracker_enrich.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_truncation.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_upload_auth_headers.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_valsets_api.py +0 -0
- {cmpnd-0.62.0 → cmpnd-0.63.0}/tests/test_xxh64.py +0 -0
|
@@ -29,17 +29,21 @@ adds one) and `switch` picks which stored key later commands use, the way the
|
|
|
29
29
|
web app switches a session's active org. `list` reads
|
|
30
30
|
/api/v1/auth/organizations and marks the orgs you hold a key for and the
|
|
31
31
|
active one. `switch` needs no network call when the org's key is already
|
|
32
|
-
stored.
|
|
33
|
-
|
|
32
|
+
stored. When none is stored for an org you are an approved member of, it
|
|
33
|
+
offers to mint one with the current key's scopes (`--mint` skips the
|
|
34
|
+
question), stores it and switches to it. `cmpnd org leave` removes your own
|
|
35
|
+
membership and drops that org's stored key.
|
|
34
36
|
|
|
35
37
|
Always registered (no env gate); the server is the authority.
|
|
36
38
|
"""
|
|
37
39
|
|
|
38
40
|
from __future__ import annotations
|
|
39
41
|
|
|
42
|
+
import socket
|
|
40
43
|
import sys
|
|
41
44
|
import urllib.parse
|
|
42
45
|
from argparse import ArgumentParser, _SubParsersAction
|
|
46
|
+
from datetime import datetime, timezone
|
|
43
47
|
from typing import Any
|
|
44
48
|
|
|
45
49
|
from .. import api_client, credentials
|
|
@@ -91,6 +95,11 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
|
|
|
91
95
|
|
|
92
96
|
switch = sub.add_parser("switch", help="Use the stored key for another of your organizations")
|
|
93
97
|
switch.add_argument("org", help="Organization slug or id (from `org list`)")
|
|
98
|
+
switch.add_argument(
|
|
99
|
+
"--mint",
|
|
100
|
+
action="store_true",
|
|
101
|
+
help="When no key is stored for the org, mint one with the current key's scopes without asking",
|
|
102
|
+
)
|
|
94
103
|
switch.set_defaults(func=handle_switch, _skip_resolve_config=True)
|
|
95
104
|
|
|
96
105
|
leave = sub.add_parser("leave", help="Leave one of your organizations and drop its stored key")
|
|
@@ -214,12 +223,70 @@ def handle_switch(_unused_client: api_client.ApiClient | None, args: Any) -> Non
|
|
|
214
223
|
api_key, _ = api_client.resolve_config(args)
|
|
215
224
|
except api_client.CliError:
|
|
216
225
|
raise _no_key_stored(f"'{target}'", endpoint, target) from None
|
|
217
|
-
|
|
226
|
+
client = api_client.ApiClient(api_key, endpoint)
|
|
227
|
+
for row in _memberships(client):
|
|
218
228
|
if target in (row.get("id"), row.get("slug")):
|
|
219
|
-
|
|
229
|
+
_mint_and_switch(client, row, target, ask=not getattr(args, "mint", False))
|
|
230
|
+
return
|
|
220
231
|
raise api_client.CliError(f"You are not a member of an organization '{target}' on {endpoint}.")
|
|
221
232
|
|
|
222
233
|
|
|
234
|
+
def _mint_and_switch(client: api_client.ApiClient, row: dict[str, Any], target: str, *, ask: bool) -> None:
|
|
235
|
+
"""Mint a key for a member org that has none stored, store it and switch.
|
|
236
|
+
|
|
237
|
+
The request names no scopes, so serve grants the calling key's own
|
|
238
|
+
non-admin scopes and the new key is never broader than the one in use.
|
|
239
|
+
serve refuses with 400 or 403 when the calling key cannot mint, and the
|
|
240
|
+
browser login is then the way to get the key.
|
|
241
|
+
"""
|
|
242
|
+
endpoint = client._endpoint
|
|
243
|
+
label = str(row.get("name") or row.get("slug") or target)
|
|
244
|
+
if row.get("approved") is False:
|
|
245
|
+
raise api_client.CliError(
|
|
246
|
+
f"Your membership in {label} is not approved yet, so no key can be minted for it. "
|
|
247
|
+
"An owner of the org approves it."
|
|
248
|
+
)
|
|
249
|
+
if ask:
|
|
250
|
+
if not sys.stdin.isatty():
|
|
251
|
+
raise api_client.CliError(
|
|
252
|
+
f"No key is stored for {label} on {endpoint}. Run {top.hint_org_switch(target, mint=True)} "
|
|
253
|
+
f"to mint one with the current key's scopes, or {top.hint_login(target)}."
|
|
254
|
+
)
|
|
255
|
+
try:
|
|
256
|
+
answer = input(f"No key is stored for {label}. Mint one with the current key's scopes? [Y/n] ")
|
|
257
|
+
except EOFError:
|
|
258
|
+
# Ctrl-D at the prompt declines.
|
|
259
|
+
print(file=sys.stderr)
|
|
260
|
+
answer = "n"
|
|
261
|
+
if answer.strip().lower() not in ("", "y", "yes"):
|
|
262
|
+
raise _no_key_stored(label, endpoint, target)
|
|
263
|
+
|
|
264
|
+
hostname = socket.gethostname() or "unknown"
|
|
265
|
+
name = f"CLI: {hostname} {datetime.now(timezone.utc).strftime('%Y-%m-%d')}"
|
|
266
|
+
try:
|
|
267
|
+
created = client.post("/api/v1/org/keys", json={"name": name, "org_id": row.get("id") or target})
|
|
268
|
+
except api_client.CliError as exc:
|
|
269
|
+
if exc.status_code in (400, 403):
|
|
270
|
+
raise api_client.CliError(
|
|
271
|
+
f"The current key could not mint a key for {label}: {exc}. Run {top.hint_login(target)} to store one."
|
|
272
|
+
) from exc
|
|
273
|
+
raise
|
|
274
|
+
token = created.get("token") if isinstance(created, dict) else None
|
|
275
|
+
org_id = created.get("org_id") if isinstance(created, dict) else None
|
|
276
|
+
if not isinstance(token, str) or not token or not isinstance(org_id, str) or not org_id:
|
|
277
|
+
raise api_client.CliError(f"The response did not include a key for {label}. Nothing was stored.")
|
|
278
|
+
name_out = created.get("org_name")
|
|
279
|
+
slug_out = created.get("org_slug")
|
|
280
|
+
credentials.save(
|
|
281
|
+
endpoint,
|
|
282
|
+
token,
|
|
283
|
+
org_id=org_id,
|
|
284
|
+
org_name=name_out if isinstance(name_out, str) else None,
|
|
285
|
+
org_slug=slug_out if isinstance(slug_out, str) else None,
|
|
286
|
+
)
|
|
287
|
+
print(f"Minted a key for {name_out or slug_out or org_id} ({org_id}) and switched to it on {endpoint}")
|
|
288
|
+
|
|
289
|
+
|
|
223
290
|
def _say_active_moved(endpoint: str, moved_to: str | None) -> None:
|
|
224
291
|
if moved_to:
|
|
225
292
|
k = {k.org_id: k for k in credentials.stored_keys(endpoint)}.get(moved_to)
|
|
@@ -180,8 +180,8 @@ def hint_login(org: str | None = None) -> str:
|
|
|
180
180
|
return f"`{cmd}` in a terminal, then restart the shell" if _SHELL_MODE else f"`{cmd}`"
|
|
181
181
|
|
|
182
182
|
|
|
183
|
-
def hint_org_switch(org: str) -> str:
|
|
184
|
-
cmd = f"cmpnd org switch {org}"
|
|
183
|
+
def hint_org_switch(org: str, *, mint: bool = False) -> str:
|
|
184
|
+
cmd = f"cmpnd org switch {org}" + (" --mint" if mint else "")
|
|
185
185
|
# Same reason as hint_login: the shell picks its key once, at startup.
|
|
186
186
|
# Both are public, unlike the other hint helpers, because only org.py
|
|
187
187
|
# calls them.
|
|
@@ -438,9 +438,14 @@ mode 0600. An active pointer names the org whose key commands use.
|
|
|
438
438
|
is stored, and `active` marks the org of the key in use. When `--api-key` or
|
|
439
439
|
`CMPND_API_KEY` is set, `active` marks that key's org.
|
|
440
440
|
- `cmpnd org switch <slug-or-id>` makes another org's stored key active, with
|
|
441
|
-
no network call. When no key is stored for
|
|
442
|
-
|
|
443
|
-
|
|
441
|
+
no network call. When no key is stored for an org where you are an approved
|
|
442
|
+
member, it asks whether to mint one with the current key's scopes, stores
|
|
443
|
+
it and switches to it. `--mint` mints without asking, and without a
|
|
444
|
+
terminal to ask in it mints only with `--mint`. The request names no
|
|
445
|
+
scopes, so serve grants the current key's non-admin scopes. When serve
|
|
446
|
+
refuses the mint, the error names the `cmpnd login --org` command to run.
|
|
447
|
+
`switch` refuses while `--api-key` or `CMPND_API_KEY` is set, because
|
|
448
|
+
either one overrides the stored keys.
|
|
444
449
|
- `cmpnd org leave <slug-or-id>` removes your membership in that org and drops
|
|
445
450
|
its stored key. If that key was active, the CLI switches to another stored
|
|
446
451
|
key, preferring your new default org's, or clears the pointer, and says
|
|
@@ -5,7 +5,7 @@ name = "cmpnd"
|
|
|
5
5
|
# it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
|
|
6
6
|
# hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
|
|
7
7
|
# this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.63.0"
|
|
9
9
|
description = "DSPy observability and deployment SDK for cmpnd"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
from argparse import Namespace
|
|
7
7
|
from unittest import mock
|
|
8
8
|
|
|
9
|
+
import httpx
|
|
9
10
|
import pytest
|
|
10
11
|
|
|
11
12
|
from cmpnd.cli import credentials
|
|
@@ -125,8 +126,8 @@ def _client_for(api_key: str):
|
|
|
125
126
|
return client
|
|
126
127
|
|
|
127
128
|
|
|
128
|
-
def _switch_args(target: str) -> Namespace:
|
|
129
|
-
return Namespace(org=target, endpoint=EP, api_key=None)
|
|
129
|
+
def _switch_args(target: str, *, mint: bool = False) -> Namespace:
|
|
130
|
+
return Namespace(org=target, endpoint=EP, api_key=None, mint=mint)
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
def _write_legacy(api_key: str) -> None:
|
|
@@ -396,14 +397,20 @@ class TestOrgSwitch:
|
|
|
396
397
|
org.handle_switch(None, _switch_args("acme"))
|
|
397
398
|
assert credentials.load(EP) == "cmpnd_acme"
|
|
398
399
|
|
|
399
|
-
def
|
|
400
|
+
def test_member_without_a_stored_key_is_told_how_to_get_one(self, config_dir, monkeypatch):
|
|
401
|
+
# With no terminal to ask in and no --mint, nothing is minted: the
|
|
402
|
+
# error names both ways to get the key.
|
|
403
|
+
monkeypatch.setattr(org.sys.stdin, "isatty", lambda: False)
|
|
400
404
|
credentials.save(EP, "cmpnd_own", org_id="o_own", org_slug="own")
|
|
401
405
|
client = _client_for("cmpnd_own")
|
|
402
406
|
client.get.return_value = MEMBERSHIPS
|
|
403
407
|
with mock.patch.object(org.api_client, "ApiClient", return_value=client):
|
|
404
|
-
with pytest.raises(CliError
|
|
408
|
+
with pytest.raises(CliError) as err:
|
|
405
409
|
org.handle_switch(None, _switch_args("beta"))
|
|
410
|
+
assert "cmpnd login --org beta" in str(err.value)
|
|
411
|
+
assert "cmpnd org switch beta --mint" in str(err.value)
|
|
406
412
|
client.get.assert_called_once_with("/api/v1/auth/organizations")
|
|
413
|
+
client.post.assert_not_called()
|
|
407
414
|
assert credentials.load(EP) == "cmpnd_own"
|
|
408
415
|
|
|
409
416
|
def test_non_member_is_refused(self, config_dir):
|
|
@@ -433,3 +440,152 @@ class TestOrgSwitch:
|
|
|
433
440
|
legacy.get.assert_called_once_with("/api/v1/auth/me")
|
|
434
441
|
assert credentials.load(EP) == "cmpnd_legacy"
|
|
435
442
|
assert credentials.active_org(EP) == "o_own"
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
MINTED_BETA = {
|
|
446
|
+
"id": "k_beta",
|
|
447
|
+
"name": "CLI: box 2026-10-29",
|
|
448
|
+
"token": "cmpnd_beta",
|
|
449
|
+
"scopes": ["traces:read"],
|
|
450
|
+
"org_id": "o_beta",
|
|
451
|
+
"org_name": "Beta",
|
|
452
|
+
"org_slug": "beta",
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class TestOrgSwitchMintsAMissingKey:
|
|
457
|
+
"""`org switch` to a member org with no stored key offers to mint one."""
|
|
458
|
+
|
|
459
|
+
@pytest.fixture
|
|
460
|
+
def own_key(self, config_dir):
|
|
461
|
+
credentials.save(EP, "cmpnd_own", org_id="o_own", org_slug="own")
|
|
462
|
+
client = _client_for("cmpnd_own")
|
|
463
|
+
client.get.return_value = MEMBERSHIPS
|
|
464
|
+
with mock.patch.object(org.api_client, "ApiClient", autospec=True) as ctor:
|
|
465
|
+
ctor.return_value = client
|
|
466
|
+
yield client, ctor
|
|
467
|
+
|
|
468
|
+
def test_mint_flag_mints_and_switches(self, own_key, capsys):
|
|
469
|
+
client, ctor = own_key
|
|
470
|
+
client.post.return_value = MINTED_BETA
|
|
471
|
+
|
|
472
|
+
org.handle_switch(None, _switch_args("beta", mint=True))
|
|
473
|
+
|
|
474
|
+
ctor.assert_called_once_with("cmpnd_own", EP)
|
|
475
|
+
client.post.assert_called_once()
|
|
476
|
+
assert credentials.load(EP) == "cmpnd_beta"
|
|
477
|
+
assert credentials.active_org(EP) == "o_beta"
|
|
478
|
+
# The key it switched from stays stored.
|
|
479
|
+
assert {k.org_id for k in credentials.stored_keys(EP)} == {"o_own", "o_beta"}
|
|
480
|
+
assert "Beta" in capsys.readouterr().out
|
|
481
|
+
|
|
482
|
+
def test_terminal_prompt_yes_mints(self, own_key, monkeypatch):
|
|
483
|
+
client, _ = own_key
|
|
484
|
+
client.post.return_value = MINTED_BETA
|
|
485
|
+
monkeypatch.setattr(org.sys.stdin, "isatty", lambda: True)
|
|
486
|
+
monkeypatch.setattr("builtins.input", lambda _prompt="": "")
|
|
487
|
+
|
|
488
|
+
org.handle_switch(None, _switch_args("beta"))
|
|
489
|
+
|
|
490
|
+
assert credentials.load(EP) == "cmpnd_beta"
|
|
491
|
+
|
|
492
|
+
def test_ctrl_d_at_the_prompt_declines(self, own_key, monkeypatch):
|
|
493
|
+
client, _ = own_key
|
|
494
|
+
monkeypatch.setattr(org.sys.stdin, "isatty", lambda: True)
|
|
495
|
+
|
|
496
|
+
def eof(_prompt=""):
|
|
497
|
+
raise EOFError
|
|
498
|
+
|
|
499
|
+
monkeypatch.setattr("builtins.input", eof)
|
|
500
|
+
|
|
501
|
+
with pytest.raises(CliError, match="cmpnd login --org beta"):
|
|
502
|
+
org.handle_switch(None, _switch_args("beta"))
|
|
503
|
+
|
|
504
|
+
client.post.assert_not_called()
|
|
505
|
+
assert credentials.load(EP) == "cmpnd_own"
|
|
506
|
+
|
|
507
|
+
def test_terminal_prompt_no_mints_nothing(self, own_key, monkeypatch):
|
|
508
|
+
client, _ = own_key
|
|
509
|
+
monkeypatch.setattr(org.sys.stdin, "isatty", lambda: True)
|
|
510
|
+
monkeypatch.setattr("builtins.input", lambda _prompt="": "n")
|
|
511
|
+
|
|
512
|
+
with pytest.raises(CliError, match="cmpnd login --org beta"):
|
|
513
|
+
org.handle_switch(None, _switch_args("beta"))
|
|
514
|
+
|
|
515
|
+
client.post.assert_not_called()
|
|
516
|
+
assert credentials.load(EP) == "cmpnd_own"
|
|
517
|
+
|
|
518
|
+
@pytest.mark.parametrize("status", [400, 403])
|
|
519
|
+
def test_a_refused_mint_falls_back_to_the_login_hint(self, own_key, status):
|
|
520
|
+
# A key without the scopes to mint, or a serve that refuses the
|
|
521
|
+
# request, leaves the browser login as the way to get the key.
|
|
522
|
+
client, _ = own_key
|
|
523
|
+
client.post.side_effect = CliError("the calling key holds no non-admin scopes to grant", status_code=status)
|
|
524
|
+
|
|
525
|
+
with pytest.raises(CliError) as err:
|
|
526
|
+
org.handle_switch(None, _switch_args("beta", mint=True))
|
|
527
|
+
|
|
528
|
+
assert "no non-admin scopes" in str(err.value)
|
|
529
|
+
assert "cmpnd login --org beta" in str(err.value)
|
|
530
|
+
assert credentials.load(EP) == "cmpnd_own"
|
|
531
|
+
assert {k.org_id for k in credentials.stored_keys(EP)} == {"o_own"}
|
|
532
|
+
|
|
533
|
+
def test_a_transport_failure_is_not_turned_into_a_login_hint(self, own_key):
|
|
534
|
+
client, _ = own_key
|
|
535
|
+
client.post.side_effect = CliError("could not connect")
|
|
536
|
+
|
|
537
|
+
with pytest.raises(CliError) as err:
|
|
538
|
+
org.handle_switch(None, _switch_args("beta", mint=True))
|
|
539
|
+
|
|
540
|
+
assert str(err.value) == "could not connect"
|
|
541
|
+
assert credentials.load(EP) == "cmpnd_own"
|
|
542
|
+
|
|
543
|
+
def test_pending_membership_is_not_offered_a_key(self, own_key):
|
|
544
|
+
client, _ = own_key
|
|
545
|
+
pending = {
|
|
546
|
+
"organizations": [
|
|
547
|
+
*MEMBERSHIPS["organizations"][:2],
|
|
548
|
+
{**MEMBERSHIPS["organizations"][2], "approved": False},
|
|
549
|
+
],
|
|
550
|
+
"total": 3,
|
|
551
|
+
}
|
|
552
|
+
client.get.return_value = pending
|
|
553
|
+
|
|
554
|
+
with pytest.raises(CliError, match="not approved"):
|
|
555
|
+
org.handle_switch(None, _switch_args("beta", mint=True))
|
|
556
|
+
|
|
557
|
+
client.post.assert_not_called()
|
|
558
|
+
assert credentials.load(EP) == "cmpnd_own"
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
class TestOrgSwitchMintOverTheWire:
|
|
562
|
+
"""The mint request as a real ``httpx.Client`` sends it."""
|
|
563
|
+
|
|
564
|
+
def test_mint_request_names_no_scopes_and_authenticates_with_the_current_key(self, config_dir):
|
|
565
|
+
credentials.save(EP, "cmpnd_own", org_id="o_own", org_slug="own")
|
|
566
|
+
sent: list[httpx.Request] = []
|
|
567
|
+
|
|
568
|
+
def handle(request: httpx.Request) -> httpx.Response:
|
|
569
|
+
sent.append(request)
|
|
570
|
+
if request.method == "GET" and request.url.path == "/api/v1/auth/organizations":
|
|
571
|
+
return httpx.Response(200, json=MEMBERSHIPS)
|
|
572
|
+
if request.method == "POST" and request.url.path == "/api/v1/org/keys":
|
|
573
|
+
return httpx.Response(201, json=MINTED_BETA)
|
|
574
|
+
return httpx.Response(404, json={"error": "not found"})
|
|
575
|
+
|
|
576
|
+
real = httpx.Client
|
|
577
|
+
with mock.patch("httpx.Client", lambda **kwargs: real(**kwargs, transport=httpx.MockTransport(handle))):
|
|
578
|
+
org.handle_switch(None, _switch_args("beta", mint=True))
|
|
579
|
+
|
|
580
|
+
posts = [r for r in sent if r.method == "POST"]
|
|
581
|
+
assert len(posts) == 1
|
|
582
|
+
assert posts[0].url == httpx.URL(f"{EP}/api/v1/org/keys")
|
|
583
|
+
assert posts[0].headers["X-API-Key"] == "cmpnd_own"
|
|
584
|
+
body = json.loads(posts[0].content)
|
|
585
|
+
# No scopes in the body: serve grants the calling key's own non-admin
|
|
586
|
+
# scopes, so the minted key is never broader than the one in use.
|
|
587
|
+
assert set(body) == {"name", "org_id"}
|
|
588
|
+
assert body["org_id"] == "o_beta"
|
|
589
|
+
assert body["name"].startswith("CLI: ")
|
|
590
|
+
assert credentials.load(EP) == "cmpnd_beta"
|
|
591
|
+
assert credentials.active_org(EP) == "o_beta"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|