fp-cloud-cli 0.0.1b1__tar.gz → 0.0.1b2__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.
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/CHANGELOG.md +30 -0
- {fp_cloud_cli-0.0.1b1/fp_cloud_cli.egg-info → fp_cloud_cli-0.0.1b2}/PKG-INFO +1 -1
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/_click_compat.py +31 -8
- fp_cloud_cli-0.0.1b2/fp_cli/_version.py +1 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/permissions.py +1 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2/fp_cloud_cli.egg-info}/PKG-INFO +1 -1
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cloud_cli.egg-info/SOURCES.txt +2 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/pyproject.toml +9 -2
- fp_cloud_cli-0.0.1b2/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/conftest.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_alerting.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_analytics.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_audits.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_auth.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_auth_mode.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_click_compat.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_client.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_commands.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_config.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_dashboards_agent.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_dates.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_enforcement_logic.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_facets.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_failproofai_home.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_failproofai_home.cpython-312.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_fp_home_contract.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_hardening.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_help_table_coverage.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_keys_queries.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_list.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_multivalue.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_no_customer_identifiers.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_operator.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_orgs.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_output.cpython-312-pytest-9.1.1.pyc +0 -0
- fp_cloud_cli-0.0.1b2/tests/__pycache__/test_pagination.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_policy_check.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_readme_matches_reality.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_review_fixes.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_telemetry_completeness.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_usage.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_v1_origin_diagnostic.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_v1_routing.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__pycache__/test_whoami.cpython-312-pytest-9.1.1.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_click_compat.py +54 -0
- fp_cloud_cli-0.0.1b2/tests/test_pagination.py +204 -0
- fp_cloud_cli-0.0.1b1/fp_cli/_version.py +0 -1
- fp_cloud_cli-0.0.1b1/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/LICENSE +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/MANIFEST.in +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/README.md +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/__init__.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/__main__.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/_context.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/analytics.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/analytics_config.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/analytics_registry.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/app.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/auth.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/client.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/__init__.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/_write.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/agent_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/alerts_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/audits_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/auth_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/errors_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/evals_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/events_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/fleet_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/guardrails_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/incidents_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/keys_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/list_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/orgs_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/policies_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/queries_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/sessions_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/settings_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/usage_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/commands/users_cmds.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/config.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/dates.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/enforcement.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/errors.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/models.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/orgs.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/output.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/policy_check.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/py.typed +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/select.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cli/theme.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cloud_cli.egg-info/dependency_links.txt +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cloud_cli.egg-info/entry_points.txt +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cloud_cli.egg-info/requires.txt +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/fp_cloud_cli.egg-info/top_level.txt +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/setup.cfg +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/__init__.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/conftest.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_alerting.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_analytics.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_audits.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_auth.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_auth_mode.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_client.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_commands.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_config.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_dashboards_agent.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_dates.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_enforcement_logic.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_facets.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_failproofai_home.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_fp_home_contract.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_hardening.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_help_table_coverage.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_keys_queries.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_list.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_multivalue.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_no_customer_identifiers.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_operator.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_orgs.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_output.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_policy_check.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_readme_matches_reality.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_review_fixes.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_telemetry_completeness.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_usage.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_v1_origin_diagnostic.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_v1_routing.py +0 -0
- {fp_cloud_cli-0.0.1b1 → fp_cloud_cli-0.0.1b2}/tests/test_whoami.py +0 -0
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog — `fp` CLI
|
|
2
2
|
|
|
3
|
+
## 0.0.1b2 — 2026-08-25
|
|
4
|
+
|
|
5
|
+
Open for the next release. `0.0.1b1` published on 2026-08-24 and the `bump` job
|
|
6
|
+
moved the version here automatically. Add entries as changes merge — this section
|
|
7
|
+
becomes the GitHub Release body when it ships.
|
|
8
|
+
|
|
9
|
+
### Fixes
|
|
10
|
+
|
|
11
|
+
- **Typed errors keep their exit codes under typer 0.27.2.** That release moved
|
|
12
|
+
`Abort` out of its vendored `typer._click.exceptions`, and `_click_compat`
|
|
13
|
+
imported all six of its symbols under a single
|
|
14
|
+
`try: … except ImportError: from click import …`. One missing name was enough
|
|
15
|
+
to send the whole block to the fallback, binding `ClickException`,
|
|
16
|
+
`UsageError`, `BadParameter`, `Command` and `Parameter` to the pip `click`
|
|
17
|
+
distribution — which is *not* the Click Typer runs, and Typer catches only its
|
|
18
|
+
own. So every typed error escaped its handler: `fp alerts show ghost` exited
|
|
19
|
+
**1 with an empty stderr** instead of **6** with `no alert named "ghost"`, and
|
|
20
|
+
likewise for exits 2, 3, 4 and 5. Nothing warned; the CLI imported and every
|
|
21
|
+
happy path passed. The Click is now decided **once**, on whether `typer._click`
|
|
22
|
+
exists at all, and every symbol imported from that decision, so a future move
|
|
23
|
+
fails at import — a CLI that refuses to start — instead of silently flattening
|
|
24
|
+
the exit-code contract. `Abort` is resolved from `typer.Abort`, which is by
|
|
25
|
+
construction the class `typer.prompt` raises and typer's own `_main` catches on
|
|
26
|
+
every version in range. Suite verified green against typer 0.25.1, 0.27.0,
|
|
27
|
+
0.27.1 and 0.27.2 (#771)
|
|
28
|
+
|
|
29
|
+
### Dependencies
|
|
30
|
+
|
|
31
|
+
- typer 0.27.1 → 0.27.2, click 8.4.2 → 8.5.0, posthog 7.42.0 → 7.44.2 (#771)
|
|
32
|
+
|
|
3
33
|
## 0.0.1b1 — 2026-08-24
|
|
4
34
|
|
|
5
35
|
### The package is `fp-cloud-cli`; the command is still `fp`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fp-cloud-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.1b2
|
|
4
4
|
Summary: Command line client for FailproofAI Cloud — sessions, events, evaluations and audits from your terminal
|
|
5
5
|
Author-email: Failproof AI <failproofai@exosphere.host>
|
|
6
6
|
License: MIT License
|
|
@@ -11,9 +11,10 @@ compiles, and every happy path still passes:
|
|
|
11
11
|
should print ``✗ …`` and exit 5 exits **1 with an empty stderr**, and the
|
|
12
12
|
``rich_format_error`` hook in ``app.py`` (reached only *after* that catch) never
|
|
13
13
|
runs. Same for the ``UsageError``/``BadParameter`` we raise by hand.
|
|
14
|
-
* ``Abort`` — ``typer.prompt`` raises
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
* ``Abort`` — ``typer.prompt`` raises the ``Abort`` **typer** exports on closed stdin
|
|
15
|
+
(0.27.2 moved that class out of the vendored Click entirely), so a pip-Click
|
|
16
|
+
``except click.Abort`` stops matching and the clean "no TTY, pass a slug" usage error
|
|
17
|
+
becomes a bare abort.
|
|
17
18
|
* Options — Typer 0.26+ has no ``Option`` class in its vendored Click *at all*: every
|
|
18
19
|
option in a Typer-built tree is a ``typer.core.TyperOption``, subclassing
|
|
19
20
|
``Parameter`` directly. ``isinstance(param, click.Option)`` is then quietly always
|
|
@@ -27,14 +28,36 @@ again, and that our errors are still the class Typer catches.
|
|
|
27
28
|
|
|
28
29
|
from __future__ import annotations
|
|
29
30
|
|
|
31
|
+
# `Abort` is resolved from `typer` itself rather than from either Click, because it is
|
|
32
|
+
# the one symbol here that is not stably a Click class: typer 0.27.2 moved it out of the
|
|
33
|
+
# vendored `typer._click.exceptions` into a plain `typer.exceptions.Abort(RuntimeError)`.
|
|
34
|
+
# `typer.Abort` tracks that move — it is `click.Abort` before 0.26, the vendored class
|
|
35
|
+
# through 0.27.1, the RuntimeError from 0.27.2 — and it is by construction the class
|
|
36
|
+
# `typer.prompt` raises and typer's own `_main` catches, which is the only property
|
|
37
|
+
# `select.py`'s `except click.Abort` needs.
|
|
38
|
+
from typer import Abort as Abort
|
|
39
|
+
|
|
40
|
+
# Pick the Click ONCE, on whether typer vendors one at all, then import every symbol
|
|
41
|
+
# from that choice. Deciding per symbol — a single `try` around the whole vendored
|
|
42
|
+
# import block, falling back to pip `click` on any ImportError — is what shipped
|
|
43
|
+
# through 0.27.1 and it failed exactly as silently as this module exists to prevent:
|
|
44
|
+
# 0.27.2 removing `typer._click.exceptions.Abort` made that one missing name rebind ALL
|
|
45
|
+
# SIX symbols to pip Click, so every typed error escaped Typer's handler as exit 1 with
|
|
46
|
+
# an empty stderr. A name that goes missing inside the chosen Click must raise here, at
|
|
47
|
+
# import, where it is a CLI that refuses to start rather than one that silently stops
|
|
48
|
+
# reporting errors.
|
|
30
49
|
try:
|
|
31
|
-
# typer >= 0.26
|
|
32
|
-
# Click in play, so binding to it here would reintroduce the whole class of bug.
|
|
33
|
-
from typer._click import ClickException, Command, Parameter
|
|
34
|
-
from typer._click.exceptions import Abort, BadParameter, UsageError
|
|
50
|
+
import typer._click as _typer_click # typer >= 0.26 vendors its own Click
|
|
35
51
|
except ImportError: # typer < 0.26 drives the pip `click` distribution directly
|
|
52
|
+
_typer_click = None # type: ignore[assignment]
|
|
53
|
+
|
|
54
|
+
if _typer_click is not None:
|
|
55
|
+
# pip `click` may well still be installed — it is simply not the Click in play, so
|
|
56
|
+
# binding to it here would reintroduce the whole class of bug.
|
|
57
|
+
from typer._click import ClickException, Command, Parameter
|
|
58
|
+
from typer._click.exceptions import BadParameter, UsageError
|
|
59
|
+
else:
|
|
36
60
|
from click import ( # type: ignore[assignment]
|
|
37
|
-
Abort,
|
|
38
61
|
BadParameter,
|
|
39
62
|
ClickException,
|
|
40
63
|
Command,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1b2"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fp-cloud-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.1b2
|
|
4
4
|
Summary: Command line client for FailproofAI Cloud — sessions, events, evaluations and audits from your terminal
|
|
5
5
|
Author-email: Failproof AI <failproofai@exosphere.host>
|
|
6
6
|
License: MIT License
|
|
@@ -79,6 +79,7 @@ tests/test_no_customer_identifiers.py
|
|
|
79
79
|
tests/test_operator.py
|
|
80
80
|
tests/test_orgs.py
|
|
81
81
|
tests/test_output.py
|
|
82
|
+
tests/test_pagination.py
|
|
82
83
|
tests/test_policy_check.py
|
|
83
84
|
tests/test_readme_matches_reality.py
|
|
84
85
|
tests/test_review_fixes.py
|
|
@@ -114,6 +115,7 @@ tests/__pycache__/test_no_customer_identifiers.cpython-312-pytest-9.1.1.pyc
|
|
|
114
115
|
tests/__pycache__/test_operator.cpython-312-pytest-9.1.1.pyc
|
|
115
116
|
tests/__pycache__/test_orgs.cpython-312-pytest-9.1.1.pyc
|
|
116
117
|
tests/__pycache__/test_output.cpython-312-pytest-9.1.1.pyc
|
|
118
|
+
tests/__pycache__/test_pagination.cpython-312-pytest-9.1.1.pyc
|
|
117
119
|
tests/__pycache__/test_policy_check.cpython-312-pytest-9.1.1.pyc
|
|
118
120
|
tests/__pycache__/test_readme_matches_reality.cpython-312-pytest-9.1.1.pyc
|
|
119
121
|
tests/__pycache__/test_review_fixes.cpython-312-pytest-9.1.1.pyc
|
|
@@ -31,7 +31,14 @@ dependencies = [
|
|
|
31
31
|
# through that — which is what makes both sides of the split work. Import Click from
|
|
32
32
|
# there and nowhere else; `tests/test_click_compat.py` enforces it, because every way
|
|
33
33
|
# this breaks is silent (typed errors collapse to exit 1 with an empty stderr, and the
|
|
34
|
-
# telemetry flag catalog empties). Suite verified against 0.25.1
|
|
34
|
+
# telemetry flag catalog empties). Suite verified against 0.25.1, 0.27.0, 0.27.1 and
|
|
35
|
+
# 0.27.2.
|
|
36
|
+
#
|
|
37
|
+
# The split keeps moving INSIDE the vendored tree, so the shim resolves each symbol
|
|
38
|
+
# separately: 0.27.2 moved `Abort` out of `typer._click.exceptions` into
|
|
39
|
+
# `typer.exceptions`, and the one all-or-nothing `try/except ImportError` that used to
|
|
40
|
+
# wrap the vendored import sent every OTHER symbol to pip Click over that single
|
|
41
|
+
# missing name — 105 tests red, every typed error at exit 1.
|
|
35
42
|
#
|
|
36
43
|
# The floor is 0.13, not 0.12, because 0.12 cannot BUILD this app: the global
|
|
37
44
|
# `--insecure/--secure` option in `app.py` is an `Optional[bool]` with a
|
|
@@ -39,7 +46,7 @@ dependencies = [
|
|
|
39
46
|
# rejects with `TypeError: Secondary flag is not valid for non-boolean flag`.
|
|
40
47
|
# That fires while the command tree is constructed, before any parsing, so
|
|
41
48
|
# `fp --version`, `fp --help` and every subcommand die identically — an
|
|
42
|
-
# installed CLI that cannot run one command. `uv.lock` pins 0.27.
|
|
49
|
+
# installed CLI that cannot run one command. `uv.lock` pins 0.27.2, so
|
|
43
50
|
# neither CI nor the publish smoke test ever installed the broken part of the
|
|
44
51
|
# range. Measured across 0.12.0/0.12.3/0.12.5 (all fail) and 0.13.0 upward
|
|
45
52
|
# (all pass).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
import ast
|
|
12
12
|
import pathlib
|
|
13
13
|
|
|
14
|
+
import pytest
|
|
14
15
|
import typer
|
|
15
16
|
from typer.testing import CliRunner
|
|
16
17
|
|
|
@@ -95,6 +96,59 @@ def test_error_exit_codes_are_distinct_per_class():
|
|
|
95
96
|
assert issubclass(KeyModeUnsupportedError, _click_compat.ClickException)
|
|
96
97
|
|
|
97
98
|
|
|
99
|
+
def test_every_symbol_comes_from_the_click_typer_runs():
|
|
100
|
+
"""No symbol may quietly come from pip Click while Typer is running its own.
|
|
101
|
+
|
|
102
|
+
This is the alarm for a *partial* miss. `_click_compat` used to wrap the whole
|
|
103
|
+
vendored import in one `try: … except ImportError: from click import …`, so a single
|
|
104
|
+
name disappearing from `typer._click` rebound **all six** symbols to pip Click — and
|
|
105
|
+
that is not hypothetical: typer 0.27.2 moved `Abort` out of
|
|
106
|
+
`typer._click.exceptions`, and the fallback fired, and 105 tests went red at once
|
|
107
|
+
with typed errors collapsing to exit 1. Assert the provenance of each symbol
|
|
108
|
+
separately, so the next move is one failure naming one symbol.
|
|
109
|
+
"""
|
|
110
|
+
typer_click = pytest.importorskip(
|
|
111
|
+
"typer._click", reason="typer < 0.26 has no vendored Click; pip click IS the right one"
|
|
112
|
+
)
|
|
113
|
+
wrong = {
|
|
114
|
+
name: obj.__module__
|
|
115
|
+
for name in ("ClickException", "UsageError", "BadParameter", "Command", "Parameter")
|
|
116
|
+
if not (obj := getattr(_click_compat, name)).__module__.startswith(typer_click.__name__)
|
|
117
|
+
}
|
|
118
|
+
assert not wrong, f"these came from pip Click, not the Click Typer runs: {wrong}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_abort_is_the_one_typer_raises():
|
|
122
|
+
"""`Abort` is the exception NOT to pin to a Click — pin it to Typer.
|
|
123
|
+
|
|
124
|
+
It is `click.Abort` before typer 0.26, the vendored class through 0.27.1, and a
|
|
125
|
+
plain `typer.exceptions.Abort(RuntimeError)` from 0.27.2 on. `typer.Abort` is the
|
|
126
|
+
class `typer.prompt` raises and typer's own `_main` catches on every one of those,
|
|
127
|
+
which is the only thing `select.py`'s `except click.Abort` needs to be true.
|
|
128
|
+
"""
|
|
129
|
+
assert _click_compat.Abort is typer.Abort
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_hand_raised_usage_errors_reach_typers_handler():
|
|
133
|
+
"""The other half of the contract: `UsageError`/`BadParameter` we raise ourselves.
|
|
134
|
+
|
|
135
|
+
`test_typed_errors_reach_typers_handler` covers the `ClickException` subclasses in
|
|
136
|
+
`errors.py`; these two are raised directly through the shim (`raise
|
|
137
|
+
click.UsageError(...)`) from a dozen call sites, and bound to the wrong Click they
|
|
138
|
+
escape uncaught the same way — exit 1, empty stderr, instead of a clean exit 2.
|
|
139
|
+
"""
|
|
140
|
+
for exc_class in (_click_compat.UsageError, _click_compat.BadParameter):
|
|
141
|
+
app = typer.Typer()
|
|
142
|
+
|
|
143
|
+
@app.command()
|
|
144
|
+
def boom() -> None:
|
|
145
|
+
raise exc_class("bad input")
|
|
146
|
+
|
|
147
|
+
result = CliRunner().invoke(app, [])
|
|
148
|
+
assert result.exit_code == 2, (exc_class, result.output)
|
|
149
|
+
assert "bad input" in result.output
|
|
150
|
+
|
|
151
|
+
|
|
98
152
|
def test_is_option_recognises_typer_options():
|
|
99
153
|
"""Typer's vendored Click has no `Option` class, so `isinstance` cannot answer this.
|
|
100
154
|
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""When `--all` stops early, the response must say so.
|
|
2
|
+
|
|
3
|
+
The defect these pin was measured against a live instance, on the CLI this one
|
|
4
|
+
was moved from:
|
|
5
|
+
|
|
6
|
+
fp --json sessions --since 7d --limit 200 -> 200 rows, next_cursor set
|
|
7
|
+
fp --json sessions --since 7d --all -> 50 rows (25x undercount)
|
|
8
|
+
fp --json sessions --since 7d --all --limit 5000 -> 1,274 rows
|
|
9
|
+
|
|
10
|
+
`--all` inherits `--limit`'s default of 50 — which is documented and deliberate
|
|
11
|
+
here — but the envelope then hard-coded `next_cursor: null`, so a 4% sample of a
|
|
12
|
+
customer's fleet came back with exit 0, no warning, and a response that read as
|
|
13
|
+
complete. Nothing in the output could tell you otherwise.
|
|
14
|
+
|
|
15
|
+
The fix (`Walk`, carried through `client.paginate`) is present in this repo and
|
|
16
|
+
was untested when these were written. That combination is the one worth
|
|
17
|
+
guarding: a silent-completeness bug cannot be caught by reading the code, only
|
|
18
|
+
by asserting on what the envelope claims.
|
|
19
|
+
|
|
20
|
+
So every test here checks the REPORTED completeness, not just the row count.
|
|
21
|
+
|
|
22
|
+
The fake feed is a real cursor server — it honours `limit` and `cursor` — not a
|
|
23
|
+
list of canned pages. A fixture that ignored `limit` would pass no matter how
|
|
24
|
+
few rows the CLI asked for, which is to say it could not fail on the bug.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
|
|
31
|
+
import httpx
|
|
32
|
+
import pytest
|
|
33
|
+
import respx
|
|
34
|
+
|
|
35
|
+
from fp_cli import client
|
|
36
|
+
from fp_cli.app import app
|
|
37
|
+
|
|
38
|
+
from .conftest import BASE_URL as BASE
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _feed(key: str, total: int, row):
|
|
42
|
+
"""respx side_effect: a cursor-paginated feed holding ``total`` rows.
|
|
43
|
+
|
|
44
|
+
The cursor is the offset of the next row (opaque to the CLI, which only
|
|
45
|
+
round-trips it). ``limit`` is honoured exactly, so the CLI's own page sizing
|
|
46
|
+
decides how much of the feed it can actually reach.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def handler(request: httpx.Request) -> httpx.Response:
|
|
50
|
+
params = request.url.params
|
|
51
|
+
limit = int(params["limit"])
|
|
52
|
+
offset = int(params.get("cursor") or 0)
|
|
53
|
+
rows = [row(i) for i in range(offset, min(offset + limit, total))]
|
|
54
|
+
nxt = offset + len(rows)
|
|
55
|
+
return httpx.Response(
|
|
56
|
+
200, json={key: rows, "next_cursor": str(nxt) if nxt < total else None}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return handler
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _event_row(i: int) -> dict:
|
|
63
|
+
return {
|
|
64
|
+
"id": i, "session_id": "s", "agent_id": "a", "event_type": "tool_use",
|
|
65
|
+
"ts": "2026-08-24T12:00:00Z", "environment": "prod", "summary": f"row {i}",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _session_row(i: int) -> dict:
|
|
70
|
+
return {
|
|
71
|
+
"session_id": f"sess-{i}", "agent_id": "a", "environment": "prod",
|
|
72
|
+
"last_event_at": "2026-08-24T12:00:00Z", "latest_evaluation": None,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _eval_row(i: int) -> dict:
|
|
77
|
+
return {
|
|
78
|
+
"id": f"eval-{i}", "session_id": "s", "agent_id": "a", "environment": "prod",
|
|
79
|
+
"status": "done", "scores": {"helpfulness": 0.9}, "created_at": "2026-08-24T12:00:00Z",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# (command, endpoint, server key, envelope key, row builder). Every command that
|
|
84
|
+
# takes `--all`; the bug was reported on `sessions` but lived in the shared
|
|
85
|
+
# walker, so all four had it. `errors` is the one where the two keys differ: it
|
|
86
|
+
# reads the shared errored-events feed (`{"events": …}`) and re-labels it
|
|
87
|
+
# `errors` on the way out.
|
|
88
|
+
FEEDS = [
|
|
89
|
+
("events", "/api/events/summary", "events", "events", _event_row),
|
|
90
|
+
("sessions", "/api/sessions", "sessions", "sessions", _session_row),
|
|
91
|
+
("evals", "/api/evaluations", "evaluations", "evaluations", _eval_row),
|
|
92
|
+
("errors", "/api/events/summary", "events", "errors", _event_row),
|
|
93
|
+
]
|
|
94
|
+
IDS = [f[0] for f in FEEDS]
|
|
95
|
+
|
|
96
|
+
TOTAL = 274 # > the 50 default, and not a multiple of the 200 page size
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.parametrize("command,endpoint,server_key,key,row", FEEDS, ids=IDS)
|
|
100
|
+
@respx.mock
|
|
101
|
+
def test_all_stopped_by_the_default_limit_is_reported_not_silent(
|
|
102
|
+
logged_in, runner, command, endpoint, server_key, key, row
|
|
103
|
+
):
|
|
104
|
+
"""THE REGRESSION. `--all` stopping at 50 is the documented behaviour; saying
|
|
105
|
+
the feed was exhausted is the bug."""
|
|
106
|
+
respx.get(f"{BASE}{endpoint}").mock(side_effect=_feed(server_key, TOTAL, row))
|
|
107
|
+
res = runner.invoke(app, ["--base-url", BASE, "--json", command, "--all"])
|
|
108
|
+
assert res.exit_code == 0, res.output
|
|
109
|
+
out = json.loads(res.stdout)
|
|
110
|
+
assert len(out[key]) == 50, "the documented default bound still applies"
|
|
111
|
+
assert out["next_cursor"] is not None, (
|
|
112
|
+
"a walk that stopped on --limit must hand back a resumable cursor; "
|
|
113
|
+
"null here is the 4%-sample-reported-as-complete bug"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@pytest.mark.parametrize("command,endpoint,server_key,key,row", FEEDS, ids=IDS)
|
|
118
|
+
@respx.mock
|
|
119
|
+
def test_all_that_exhausts_the_feed_reports_complete(
|
|
120
|
+
logged_in, runner, command, endpoint, server_key, key, row
|
|
121
|
+
):
|
|
122
|
+
"""The other direction, and the reason `truncated` cannot just be "we hit the
|
|
123
|
+
limit": a walk that took every row must NOT claim there is more."""
|
|
124
|
+
respx.get(f"{BASE}{endpoint}").mock(side_effect=_feed(server_key, 30, row))
|
|
125
|
+
res = runner.invoke(app, ["--base-url", BASE, "--json", command, "--all", "--limit", "500"])
|
|
126
|
+
assert res.exit_code == 0, res.output
|
|
127
|
+
out = json.loads(res.stdout)
|
|
128
|
+
assert len(out[key]) == 30
|
|
129
|
+
assert out["next_cursor"] is None, "an exhausted feed must not look truncated"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@respx.mock
|
|
133
|
+
def test_an_explicit_limit_is_honoured_and_reported(logged_in, runner):
|
|
134
|
+
"""`--all --limit N` is how you actually get more than the default, so the
|
|
135
|
+
bound has to hold exactly and still report the remainder."""
|
|
136
|
+
respx.get(f"{BASE}/api/sessions").mock(side_effect=_feed("sessions", TOTAL, _session_row))
|
|
137
|
+
res = runner.invoke(
|
|
138
|
+
app, ["--base-url", BASE, "--json", "sessions", "--all", "--limit", "220"]
|
|
139
|
+
)
|
|
140
|
+
assert res.exit_code == 0, res.output
|
|
141
|
+
out = json.loads(res.stdout)
|
|
142
|
+
assert len(out["sessions"]) == 220
|
|
143
|
+
assert out["next_cursor"] is not None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@respx.mock
|
|
147
|
+
def test_all_pages_in_chunks_rather_than_one_giant_request(logged_in, runner):
|
|
148
|
+
"""The walk must actually WALK. Asking for 220 in a single request would work
|
|
149
|
+
against this fake and fail against a server that caps a page at 200."""
|
|
150
|
+
route = respx.get(f"{BASE}/api/sessions").mock(
|
|
151
|
+
side_effect=_feed("sessions", TOTAL, _session_row)
|
|
152
|
+
)
|
|
153
|
+
res = runner.invoke(
|
|
154
|
+
app, ["--base-url", BASE, "--json", "sessions", "--all", "--limit", "220"]
|
|
155
|
+
)
|
|
156
|
+
assert res.exit_code == 0, res.output
|
|
157
|
+
assert route.call_count > 1, "a 220-row walk must span more than one request"
|
|
158
|
+
for call in route.calls:
|
|
159
|
+
assert int(call.request.url.params["limit"]) <= 200
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# --- the walker itself, below the CLI ---------------------------------------
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _pages(total: int, page: int):
|
|
166
|
+
"""A `fetch_page` over ``total`` rows, honouring the limit it is given."""
|
|
167
|
+
|
|
168
|
+
def fetch(cursor=None, limit=None):
|
|
169
|
+
offset = int(cursor or 0)
|
|
170
|
+
size = min(limit or page, page)
|
|
171
|
+
items = list(range(offset, min(offset + size, total)))
|
|
172
|
+
nxt = offset + len(items)
|
|
173
|
+
return client.Page(items=items, next_cursor=str(nxt) if nxt < total else None)
|
|
174
|
+
|
|
175
|
+
return fetch
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_walk_records_the_cursor_it_stopped_on():
|
|
179
|
+
walk = client.Walk()
|
|
180
|
+
items = list(client.paginate(_pages(500, 200), limit=250, walk=walk))
|
|
181
|
+
assert len(items) == 250
|
|
182
|
+
assert walk.truncated is True
|
|
183
|
+
assert walk.next_cursor is not None, "a bounded walk must be resumable"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_walk_that_exhausts_the_feed_is_not_flagged_truncated():
|
|
187
|
+
walk = client.Walk()
|
|
188
|
+
items = list(client.paginate(_pages(120, 200), limit=500, walk=walk))
|
|
189
|
+
assert len(items) == 120
|
|
190
|
+
assert walk.truncated is False
|
|
191
|
+
assert walk.next_cursor is None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_truncated_is_not_derivable_from_the_row_count():
|
|
195
|
+
"""The subtle case, and why `Walk` exists at all: a walk whose limit lands
|
|
196
|
+
EXACTLY on the last row is complete, and one that lands exactly on a page
|
|
197
|
+
boundary with more behind it is not. Row count cannot tell those apart."""
|
|
198
|
+
exact = client.Walk()
|
|
199
|
+
list(client.paginate(_pages(200, 200), limit=200, walk=exact))
|
|
200
|
+
assert exact.truncated is False, "taking every row is not a truncation"
|
|
201
|
+
|
|
202
|
+
boundary = client.Walk()
|
|
203
|
+
list(client.paginate(_pages(400, 200), limit=200, walk=boundary))
|
|
204
|
+
assert boundary.truncated is True, "stopping on a boundary with more behind it is"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.1b1"
|
|
Binary file
|
|
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
|