databay-cli 0.1.0__py3-none-any.whl
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.
- databay/__init__.py +5 -0
- databay/__main__.py +7 -0
- databay/agent_skill.py +184 -0
- databay/api.py +626 -0
- databay/cli/__init__.py +0 -0
- databay/cli/auth.py +377 -0
- databay/cli/build.py +89 -0
- databay/cli/commands/__init__.py +0 -0
- databay/cli/commands/alerting_webhook.py +19 -0
- databay/cli/commands/alerting_webhook_tag.py +14 -0
- databay/cli/commands/alerting_webhook_type.py +14 -0
- databay/cli/commands/alerting_webhook_undelivered.py +15 -0
- databay/cli/commands/announcement.py +17 -0
- databay/cli/commands/api_key.py +18 -0
- databay/cli/commands/api_key_project.py +16 -0
- databay/cli/commands/backfill_reason_code.py +14 -0
- databay/cli/commands/backfill_run.py +17 -0
- databay/cli/commands/canvas.py +16 -0
- databay/cli/commands/canvas_graph.py +15 -0
- databay/cli/commands/canvas_table_check.py +15 -0
- databay/cli/commands/dashboards.py +21 -0
- databay/cli/commands/dashboards_card.py +20 -0
- databay/cli/commands/dashboards_dataset.py +14 -0
- databay/cli/commands/dashboards_folder.py +16 -0
- databay/cli/commands/dashboards_home.py +17 -0
- databay/cli/commands/dashboards_placement.py +16 -0
- databay/cli/commands/db.py +19 -0
- databay/cli/commands/db_column.py +14 -0
- databay/cli/commands/docs_article.py +16 -0
- databay/cli/commands/dq_result.py +14 -0
- databay/cli/commands/dq_rule.py +16 -0
- databay/cli/commands/feature.py +14 -0
- databay/cli/commands/google_ads_source.py +14 -0
- databay/cli/commands/google_analytics_source.py +14 -0
- databay/cli/commands/instagram_account.py +14 -0
- databay/cli/commands/instagram_source.py +14 -0
- databay/cli/commands/linkedin_account.py +14 -0
- databay/cli/commands/linkedin_source.py +14 -0
- databay/cli/commands/me.py +14 -0
- databay/cli/commands/meta_ads_source.py +14 -0
- databay/cli/commands/metric_models.py +26 -0
- databay/cli/commands/metrics.py +14 -0
- databay/cli/commands/models.py +16 -0
- databay/cli/commands/nlq.py +15 -0
- databay/cli/commands/notebook_credential.py +16 -0
- databay/cli/commands/org.py +14 -0
- databay/cli/commands/org_member.py +17 -0
- databay/cli/commands/org_offboarding.py +14 -0
- databay/cli/commands/pipe.py +26 -0
- databay/cli/commands/pipe_backfill_run.py +14 -0
- databay/cli/commands/pipe_config_option.py +14 -0
- databay/cli/commands/pipe_destination.py +16 -0
- databay/cli/commands/pipe_leftover.py +16 -0
- databay/cli/commands/pipe_sink_type.py +14 -0
- databay/cli/commands/pipe_source_type.py +14 -0
- databay/cli/commands/pipe_status_bucket.py +14 -0
- databay/cli/commands/pipe_stuck_delete.py +14 -0
- databay/cli/commands/plan.py +14 -0
- databay/cli/commands/project.py +21 -0
- databay/cli/commands/project_budget.py +15 -0
- databay/cli/commands/project_member.py +18 -0
- databay/cli/commands/project_privilege.py +16 -0
- databay/cli/commands/project_share.py +16 -0
- databay/cli/commands/provision.py +14 -0
- databay/cli/commands/provisioning.py +14 -0
- databay/cli/commands/registry.py +14 -0
- databay/cli/commands/resource_home.py +14 -0
- databay/cli/commands/schema_change.py +18 -0
- databay/cli/commands/schema_history.py +17 -0
- databay/cli/commands/schema_version.py +16 -0
- databay/cli/commands/shopify_store.py +14 -0
- databay/cli/commands/sink_data_type.py +14 -0
- databay/cli/commands/source.py +24 -0
- databay/cli/commands/source_catalog.py +15 -0
- databay/cli/commands/source_check_run.py +17 -0
- databay/cli/commands/source_hidden_type.py +14 -0
- databay/cli/commands/source_name.py +14 -0
- databay/cli/commands/source_pipe.py +14 -0
- databay/cli/commands/source_publication.py +16 -0
- databay/cli/commands/source_readiness_override.py +15 -0
- databay/cli/commands/source_setup_choice.py +16 -0
- databay/cli/commands/source_stream.py +15 -0
- databay/cli/commands/source_table.py +14 -0
- databay/cli/commands/ssbi_job.py +18 -0
- databay/cli/commands/transform_type.py +14 -0
- databay/cli/commands/usage.py +14 -0
- databay/cli/commands/warehouse.py +21 -0
- databay/cli/commands/warehouse_access_assignment.py +16 -0
- databay/cli/commands/warehouse_access_role.py +22 -0
- databay/cli/commands/warehouse_access_settings.py +17 -0
- databay/cli/commands/warehouse_login.py +20 -0
- databay/cli/commands/warehouse_principal.py +17 -0
- databay/cli/commands/warehouse_service_account.py +17 -0
- databay/cli/commands/warehouse_service_account_credential.py +18 -0
- databay/cli/common.py +93 -0
- databay/cli/console.py +121 -0
- databay/cli/flags.py +183 -0
- databay/cli/handwritten.py +22 -0
- databay/cli/main.py +198 -0
- databay/cli/metrics.py +171 -0
- databay/cli/org.py +193 -0
- databay/cli/output.py +134 -0
- databay/cli/pipe_wait.py +357 -0
- databay/cli/profile.py +230 -0
- databay/cli/runner.py +432 -0
- databay/cli/skill.py +64 -0
- databay/cli/sql.py +84 -0
- databay/compat.py +161 -0
- databay/credentials.py +217 -0
- databay/error_catalog.py +226 -0
- databay/error_codes.py +3265 -0
- databay/errors.py +222 -0
- databay/feature_header.py +40 -0
- databay/invoke.py +225 -0
- databay/locks.py +41 -0
- databay/login_flow.py +245 -0
- databay/model_base.py +40 -0
- databay/models/__init__.py +0 -0
- databay/models/alerting_webhook.py +46 -0
- databay/models/alerting_webhook_tag.py +11 -0
- databay/models/alerting_webhook_type.py +11 -0
- databay/models/alerting_webhook_undelivered.py +23 -0
- databay/models/announcement.py +37 -0
- databay/models/api_key.py +81 -0
- databay/models/api_key_project.py +29 -0
- databay/models/backfill_reason_code.py +11 -0
- databay/models/backfill_run.py +41 -0
- databay/models/canvas.py +68 -0
- databay/models/canvas_graph.py +86 -0
- databay/models/canvas_table_check.py +181 -0
- databay/models/dashboards.py +79 -0
- databay/models/dashboards_card.py +37 -0
- databay/models/dashboards_dataset.py +27 -0
- databay/models/dashboards_folder.py +18 -0
- databay/models/dashboards_home.py +28 -0
- databay/models/db.py +34 -0
- databay/models/db_column.py +11 -0
- databay/models/docs_article.py +59 -0
- databay/models/dq_result.py +27 -0
- databay/models/dq_rule.py +43 -0
- databay/models/feature.py +15 -0
- databay/models/google_ads_source.py +11 -0
- databay/models/google_analytics_source.py +11 -0
- databay/models/instagram_source.py +11 -0
- databay/models/linkedin_source.py +11 -0
- databay/models/me.py +71 -0
- databay/models/meta_ads_source.py +11 -0
- databay/models/metric_models.py +274 -0
- databay/models/metrics.py +158 -0
- databay/models/models.py +73 -0
- databay/models/nlq.py +92 -0
- databay/models/notebook_credential.py +51 -0
- databay/models/org.py +44 -0
- databay/models/org_member.py +123 -0
- databay/models/org_offboarding.py +36 -0
- databay/models/pipe.py +340 -0
- databay/models/pipe_config_option.py +25 -0
- databay/models/pipe_destination.py +128 -0
- databay/models/pipe_leftover.py +51 -0
- databay/models/pipe_sink_type.py +11 -0
- databay/models/pipe_source_type.py +11 -0
- databay/models/pipe_status_bucket.py +13 -0
- databay/models/pipe_stuck_delete.py +25 -0
- databay/models/plan.py +37 -0
- databay/models/project.py +154 -0
- databay/models/project_budget.py +22 -0
- databay/models/project_member.py +67 -0
- databay/models/project_privilege.py +21 -0
- databay/models/project_share.py +76 -0
- databay/models/provision.py +12 -0
- databay/models/provisioning.py +20 -0
- databay/models/registry.py +18 -0
- databay/models/resource_home.py +35 -0
- databay/models/schema_change.py +96 -0
- databay/models/schema_history.py +32 -0
- databay/models/schema_version.py +23 -0
- databay/models/shared.py +875 -0
- databay/models/shopify_store.py +19 -0
- databay/models/sink_data_type.py +11 -0
- databay/models/source.py +263 -0
- databay/models/source_catalog.py +82 -0
- databay/models/source_check_run.py +63 -0
- databay/models/source_hidden_type.py +13 -0
- databay/models/source_name.py +18 -0
- databay/models/source_pipe.py +16 -0
- databay/models/source_publication.py +30 -0
- databay/models/source_readiness_override.py +13 -0
- databay/models/source_setup_choice.py +33 -0
- databay/models/source_stream.py +65 -0
- databay/models/source_table.py +27 -0
- databay/models/sql.py +31 -0
- databay/models/ssbi_job.py +97 -0
- databay/models/transform_type.py +40 -0
- databay/models/usage.py +112 -0
- databay/models/warehouse.py +38 -0
- databay/models/warehouse_access_assignment.py +70 -0
- databay/models/warehouse_access_role.py +148 -0
- databay/models/warehouse_access_settings.py +35 -0
- databay/models/warehouse_login.py +20 -0
- databay/models/warehouse_principal.py +49 -0
- databay/models/warehouse_service_account.py +35 -0
- databay/models/warehouse_service_account_credential.py +11 -0
- databay/oidc.py +205 -0
- databay/operation.py +191 -0
- databay/operations/__init__.py +0 -0
- databay/operations/alerting_webhook.py +317 -0
- databay/operations/alerting_webhook_tag.py +35 -0
- databay/operations/alerting_webhook_type.py +35 -0
- databay/operations/alerting_webhook_undelivered.py +74 -0
- databay/operations/announcement.py +135 -0
- databay/operations/api_key.py +198 -0
- databay/operations/api_key_project.py +130 -0
- databay/operations/backfill_reason_code.py +35 -0
- databay/operations/backfill_run.py +245 -0
- databay/operations/canvas.py +136 -0
- databay/operations/canvas_graph.py +102 -0
- databay/operations/canvas_table_check.py +101 -0
- databay/operations/dashboards.py +441 -0
- databay/operations/dashboards_card.py +406 -0
- databay/operations/dashboards_dataset.py +50 -0
- databay/operations/dashboards_folder.py +146 -0
- databay/operations/dashboards_home.py +176 -0
- databay/operations/dashboards_placement.py +152 -0
- databay/operations/db.py +337 -0
- databay/operations/db_column.py +57 -0
- databay/operations/docs_article.py +101 -0
- databay/operations/dq_result.py +43 -0
- databay/operations/dq_rule.py +155 -0
- databay/operations/feature.py +35 -0
- databay/operations/google_ads_source.py +40 -0
- databay/operations/google_analytics_source.py +40 -0
- databay/operations/instagram_account.py +47 -0
- databay/operations/instagram_source.py +40 -0
- databay/operations/linkedin_account.py +47 -0
- databay/operations/linkedin_source.py +40 -0
- databay/operations/me.py +57 -0
- databay/operations/meta_ads_source.py +40 -0
- databay/operations/metric_models.py +612 -0
- databay/operations/metrics.py +218 -0
- databay/operations/models.py +123 -0
- databay/operations/nlq.py +100 -0
- databay/operations/notebook_credential.py +95 -0
- databay/operations/org.py +79 -0
- databay/operations/org_member.py +192 -0
- databay/operations/org_offboarding.py +43 -0
- databay/operations/pipe.py +882 -0
- databay/operations/pipe_backfill_run.py +72 -0
- databay/operations/pipe_config_option.py +50 -0
- databay/operations/pipe_destination.py +178 -0
- databay/operations/pipe_leftover.py +110 -0
- databay/operations/pipe_sink_type.py +35 -0
- databay/operations/pipe_source_type.py +35 -0
- databay/operations/pipe_status_bucket.py +35 -0
- databay/operations/pipe_stuck_delete.py +52 -0
- databay/operations/plan.py +35 -0
- databay/operations/project.py +346 -0
- databay/operations/project_budget.py +135 -0
- databay/operations/project_member.py +226 -0
- databay/operations/project_privilege.py +143 -0
- databay/operations/project_share.py +160 -0
- databay/operations/provision.py +35 -0
- databay/operations/provisioning.py +35 -0
- databay/operations/registry.py +35 -0
- databay/operations/resource_home.py +80 -0
- databay/operations/schema_change.py +222 -0
- databay/operations/schema_history.py +136 -0
- databay/operations/schema_version.py +115 -0
- databay/operations/shopify_store.py +40 -0
- databay/operations/sink_data_type.py +43 -0
- databay/operations/source.py +1207 -0
- databay/operations/source_catalog.py +135 -0
- databay/operations/source_check_run.py +242 -0
- databay/operations/source_hidden_type.py +59 -0
- databay/operations/source_name.py +69 -0
- databay/operations/source_pipe.py +57 -0
- databay/operations/source_publication.py +139 -0
- databay/operations/source_readiness_override.py +96 -0
- databay/operations/source_setup_choice.py +161 -0
- databay/operations/source_stream.py +99 -0
- databay/operations/source_table.py +59 -0
- databay/operations/sql.py +54 -0
- databay/operations/ssbi_job.py +242 -0
- databay/operations/transform_type.py +35 -0
- databay/operations/usage.py +35 -0
- databay/operations/warehouse.py +343 -0
- databay/operations/warehouse_access_assignment.py +179 -0
- databay/operations/warehouse_access_role.py +473 -0
- databay/operations/warehouse_access_settings.py +172 -0
- databay/operations/warehouse_login.py +308 -0
- databay/operations/warehouse_principal.py +174 -0
- databay/operations/warehouse_service_account.py +206 -0
- databay/operations/warehouse_service_account_credential.py +285 -0
- databay/paths.py +32 -0
- databay/platform_check.py +20 -0
- databay/profiles.py +181 -0
- databay/redact.py +22 -0
- databay/reference.py +218 -0
- databay/resource.py +167 -0
- databay/resources/__init__.py +0 -0
- databay/resources/alerting_webhook.py +84 -0
- databay/resources/alerting_webhook_tag.py +21 -0
- databay/resources/alerting_webhook_type.py +21 -0
- databay/resources/alerting_webhook_undelivered.py +35 -0
- databay/resources/announcement.py +45 -0
- databay/resources/api_key.py +56 -0
- databay/resources/api_key_project.py +47 -0
- databay/resources/backfill_reason_code.py +21 -0
- databay/resources/backfill_run.py +78 -0
- databay/resources/canvas.py +43 -0
- databay/resources/canvas_graph.py +38 -0
- databay/resources/canvas_table_check.py +38 -0
- databay/resources/dashboards.py +105 -0
- databay/resources/dashboards_card.py +97 -0
- databay/resources/dashboards_dataset.py +27 -0
- databay/resources/dashboards_folder.py +47 -0
- databay/resources/dashboards_home.py +59 -0
- databay/resources/dashboards_placement.py +47 -0
- databay/resources/db.py +87 -0
- databay/resources/db_column.py +28 -0
- databay/resources/docs_article.py +37 -0
- databay/resources/dq_result.py +25 -0
- databay/resources/dq_rule.py +48 -0
- databay/resources/feature.py +21 -0
- databay/resources/google_ads_source.py +25 -0
- databay/resources/google_analytics_source.py +25 -0
- databay/resources/instagram_account.py +27 -0
- databay/resources/instagram_source.py +25 -0
- databay/resources/linkedin_account.py +27 -0
- databay/resources/linkedin_source.py +25 -0
- databay/resources/me.py +27 -0
- databay/resources/meta_ads_source.py +25 -0
- databay/resources/metric_models.py +154 -0
- databay/resources/metrics.py +57 -0
- databay/resources/models.py +42 -0
- databay/resources/nlq.py +34 -0
- databay/resources/notebook_credential.py +41 -0
- databay/resources/org.py +29 -0
- databay/resources/org_member.py +53 -0
- databay/resources/org_offboarding.py +25 -0
- databay/resources/pipe.py +208 -0
- databay/resources/pipe_backfill_run.py +28 -0
- databay/resources/pipe_config_option.py +26 -0
- databay/resources/pipe_destination.py +49 -0
- databay/resources/pipe_leftover.py +42 -0
- databay/resources/pipe_sink_type.py +21 -0
- databay/resources/pipe_source_type.py +21 -0
- databay/resources/pipe_status_bucket.py +21 -0
- databay/resources/pipe_stuck_delete.py +27 -0
- databay/resources/plan.py +21 -0
- databay/resources/project.py +92 -0
- databay/resources/project_budget.py +38 -0
- databay/resources/project_member.py +68 -0
- databay/resources/project_privilege.py +48 -0
- databay/resources/project_share.py +47 -0
- databay/resources/provision.py +21 -0
- databay/resources/provisioning.py +21 -0
- databay/resources/registry.py +21 -0
- databay/resources/resource_home.py +29 -0
- databay/resources/schema_change.py +62 -0
- databay/resources/schema_history.py +52 -0
- databay/resources/schema_version.py +43 -0
- databay/resources/shopify_store.py +25 -0
- databay/resources/sink_data_type.py +25 -0
- databay/resources/source.py +132 -0
- databay/resources/source_catalog.py +34 -0
- databay/resources/source_check_run.py +67 -0
- databay/resources/source_hidden_type.py +25 -0
- databay/resources/source_name.py +27 -0
- databay/resources/source_pipe.py +28 -0
- databay/resources/source_publication.py +47 -0
- databay/resources/source_readiness_override.py +37 -0
- databay/resources/source_setup_choice.py +50 -0
- databay/resources/source_stream.py +38 -0
- databay/resources/source_table.py +27 -0
- databay/resources/sql.py +27 -0
- databay/resources/ssbi_job.py +69 -0
- databay/resources/transform_type.py +21 -0
- databay/resources/usage.py +21 -0
- databay/resources/warehouse.py +136 -0
- databay/resources/warehouse_access_assignment.py +51 -0
- databay/resources/warehouse_access_role.py +118 -0
- databay/resources/warehouse_access_settings.py +56 -0
- databay/resources/warehouse_login.py +81 -0
- databay/resources/warehouse_principal.py +51 -0
- databay/resources/warehouse_service_account.py +61 -0
- databay/resources/warehouse_service_account_credential.py +78 -0
- databay/sdk.py +77 -0
- databay/settings.py +90 -0
- databay/skill/SKILL.md +357 -0
- databay/skill/references/commands.md +7025 -0
- databay/skill/references/errors.md +727 -0
- databay/spec.py +362 -0
- databay/stored_token.py +134 -0
- databay/tokens.py +108 -0
- databay/transport.py +357 -0
- databay/update_check.py +151 -0
- databay/version.py +6 -0
- databay/waits.py +330 -0
- databay_cli-0.1.0.dist-info/METADATA +42 -0
- databay_cli-0.1.0.dist-info/RECORD +403 -0
- databay_cli-0.1.0.dist-info/WHEEL +4 -0
- databay_cli-0.1.0.dist-info/entry_points.txt +2 -0
- databay_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
databay/__init__.py
ADDED
databay/__main__.py
ADDED
databay/agent_skill.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The agent skill that ships in ``databay/skill/``: where each agent loads it, and installing it there.
|
|
3
|
+
|
|
4
|
+
The installed ``SKILL.md`` carries the CLI version in its frontmatter (``metadata``), so
|
|
5
|
+
``databay status`` can tell a copy from another CLI version. Standard library only.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import shutil
|
|
12
|
+
import uuid
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Literal
|
|
16
|
+
|
|
17
|
+
from databay.errors import DatabayError, DatabayUsageError
|
|
18
|
+
|
|
19
|
+
Scope = Literal["user", "project"]
|
|
20
|
+
SCOPES: tuple[Scope, ...] = ("user", "project")
|
|
21
|
+
|
|
22
|
+
#: Each supported agent's skills folder, under the home (user scope) or the working directory
|
|
23
|
+
#: (project scope). Codex reads ``~/.agents/skills`` and ``.agents/skills`` from the working
|
|
24
|
+
#: directory up to the project root (openai/codex ``codex-rs/ext/skills/src/host_roots.rs``).
|
|
25
|
+
TARGETS: dict[str, Path] = {"claude": Path(".claude") / "skills", "codex": Path(".agents") / "skills"}
|
|
26
|
+
|
|
27
|
+
SKILL_NAME = "databay"
|
|
28
|
+
VERSION_KEY = "databay-cli-version"
|
|
29
|
+
SOURCE = Path(__file__).resolve().parent / "skill"
|
|
30
|
+
_VERSION_LINE = re.compile(rf"^\s+{re.escape(VERSION_KEY)}:\s*\"?([^\"\s]+)\"?\s*$", re.MULTILINE)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class Installed:
|
|
35
|
+
"""
|
|
36
|
+
A skill folder found at ``path``.
|
|
37
|
+
|
|
38
|
+
``version`` is the CLI version its ``SKILL.md`` records; when it is ``None``, ``problem`` says
|
|
39
|
+
why (no ``SKILL.md``, unreadable, no frontmatter, or no version in it).
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
target: str
|
|
43
|
+
scope: Scope
|
|
44
|
+
path: Path
|
|
45
|
+
version: str | None
|
|
46
|
+
problem: str | None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def skill_dir(target: str, scope: Scope, *, home: Path, cwd: Path) -> Path:
|
|
50
|
+
"""The folder ``target`` loads the skill from; an unknown target is ``unsupported_target``."""
|
|
51
|
+
if target not in TARGETS:
|
|
52
|
+
raise DatabayUsageError(
|
|
53
|
+
f"no skill format for target {target!r}; supported targets: {', '.join(TARGETS)}",
|
|
54
|
+
code="unsupported_target",
|
|
55
|
+
details={"target": target},
|
|
56
|
+
)
|
|
57
|
+
base = home if scope == "user" else cwd
|
|
58
|
+
return base / TARGETS[target] / SKILL_NAME
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _present(path: Path) -> bool:
|
|
62
|
+
"""Something is at ``path``: a folder, a file or a symlink, broken or not."""
|
|
63
|
+
return path.exists() or path.is_symlink()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _frontmatter_end(text: str) -> int | None:
|
|
67
|
+
"""The index of the closing ``---`` line of ``text``'s frontmatter, or ``None`` when it has none."""
|
|
68
|
+
if not text.startswith("---\n"):
|
|
69
|
+
return None
|
|
70
|
+
end = text.find("\n---\n", 3)
|
|
71
|
+
return end if end >= 0 else None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def rendered_skill(version: str) -> str:
|
|
75
|
+
"""The packaged ``SKILL.md`` with ``version`` added to its frontmatter as ``metadata.databay-cli-version``."""
|
|
76
|
+
path = SOURCE / "SKILL.md"
|
|
77
|
+
text = path.read_text(encoding="utf-8")
|
|
78
|
+
end = _frontmatter_end(text)
|
|
79
|
+
if end is None:
|
|
80
|
+
raise DatabayError(f"{path} has no --- frontmatter")
|
|
81
|
+
if re.search(r"^metadata:", text[:end], re.MULTILINE):
|
|
82
|
+
raise DatabayError(f"{path} declares metadata itself; the installer writes that block")
|
|
83
|
+
return f'{text[:end]}\nmetadata:\n {VERSION_KEY}: "{version}"{text[end:]}'
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def read_installed(target: str, scope: Scope, path: Path) -> Installed:
|
|
87
|
+
"""The copy at ``path``: the version its ``SKILL.md`` records, or the reason none could be read."""
|
|
88
|
+
skill = path / "SKILL.md"
|
|
89
|
+
try:
|
|
90
|
+
# CRLF is accepted: an editor on another platform may have rewritten the file.
|
|
91
|
+
text = skill.read_text(encoding="utf-8").replace("\r\n", "\n")
|
|
92
|
+
except FileNotFoundError:
|
|
93
|
+
return Installed(target, scope, path, None, f"{skill} does not exist")
|
|
94
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
95
|
+
return Installed(target, scope, path, None, f"{skill} cannot be read ({type(exc).__name__})")
|
|
96
|
+
end = _frontmatter_end(text)
|
|
97
|
+
if end is None:
|
|
98
|
+
return Installed(target, scope, path, None, f"{skill} has no --- frontmatter")
|
|
99
|
+
found = _VERSION_LINE.search(text[:end])
|
|
100
|
+
if found is None:
|
|
101
|
+
return Installed(target, scope, path, None, f"{skill} records no metadata.{VERSION_KEY}")
|
|
102
|
+
return Installed(target, scope, path, found.group(1), None)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def project_dirs(*, home: Path, cwd: Path) -> list[Path]:
|
|
106
|
+
"""
|
|
107
|
+
``cwd`` and its parents, where a project-scope copy may sit, nearest first.
|
|
108
|
+
|
|
109
|
+
The walk stops at the home: a copy there is the user-scope one.
|
|
110
|
+
"""
|
|
111
|
+
found = []
|
|
112
|
+
for directory in [cwd, *cwd.parents]:
|
|
113
|
+
if directory == home or directory in home.parents:
|
|
114
|
+
break
|
|
115
|
+
found.append(directory)
|
|
116
|
+
return found
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def find_installed(*, home: Path, cwd: Path) -> list[Installed]:
|
|
120
|
+
"""Every installed copy: per target, the user one, then project ones from ``cwd`` up."""
|
|
121
|
+
bases: list[tuple[Scope, Path]] = [("user", home), *[("project", d) for d in project_dirs(home=home, cwd=cwd)]]
|
|
122
|
+
found = []
|
|
123
|
+
for target, folder in TARGETS.items():
|
|
124
|
+
for scope, base in bases:
|
|
125
|
+
path = base / folder / SKILL_NAME
|
|
126
|
+
if _present(path):
|
|
127
|
+
found.append(read_installed(target, scope, path))
|
|
128
|
+
return found
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def install(
|
|
132
|
+
target: str, scope: Scope, *, force: bool, version: str, home: Path, cwd: Path
|
|
133
|
+
) -> tuple[Path, Installed | None]:
|
|
134
|
+
"""
|
|
135
|
+
Write the skill (``SKILL.md`` and ``references/``) where ``target`` loads it.
|
|
136
|
+
|
|
137
|
+
Returns the folder it wrote and the copy it replaced, if any. A copy from another version
|
|
138
|
+
(or of no readable version) is replaced only with ``force``; otherwise it is ``skill_exists``.
|
|
139
|
+
The new copy is staged beside the old one and swapped in; a failed swap puts the old one back.
|
|
140
|
+
"""
|
|
141
|
+
destination = skill_dir(target, scope, home=home, cwd=cwd)
|
|
142
|
+
previous = read_installed(target, scope, destination) if _present(destination) else None
|
|
143
|
+
if previous is not None and previous.version != version and not force:
|
|
144
|
+
installed = previous.version or f"an unknown version ({previous.problem})"
|
|
145
|
+
raise DatabayUsageError(
|
|
146
|
+
f"{destination} holds the databay skill of CLI {installed}; this CLI is {version}",
|
|
147
|
+
code="skill_exists",
|
|
148
|
+
next_command=f"databay skill install --target {target} --scope {scope} --force",
|
|
149
|
+
details={"path": str(destination), "installed_version": previous.version, "cli_version": version},
|
|
150
|
+
)
|
|
151
|
+
staging = destination.parent / f".{SKILL_NAME}-new-{uuid.uuid4().hex}"
|
|
152
|
+
try:
|
|
153
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
shutil.copytree(SOURCE / "references", staging / "references")
|
|
155
|
+
(staging / "SKILL.md").write_text(rendered_skill(version), encoding="utf-8")
|
|
156
|
+
_swap(staging, destination, has_previous=previous is not None)
|
|
157
|
+
except OSError as exc:
|
|
158
|
+
raise DatabayError(
|
|
159
|
+
f"could not write the skill to {destination}: {type(exc).__name__}: {exc.strerror or exc}",
|
|
160
|
+
code="skill_write_failed",
|
|
161
|
+
details={"path": str(destination)},
|
|
162
|
+
) from None
|
|
163
|
+
finally:
|
|
164
|
+
if staging.exists():
|
|
165
|
+
shutil.rmtree(staging)
|
|
166
|
+
return destination, previous
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _swap(staging: Path, destination: Path, *, has_previous: bool) -> None:
|
|
170
|
+
"""Move ``staging`` to ``destination``; the copy already there is moved aside first and removed after."""
|
|
171
|
+
if not has_previous:
|
|
172
|
+
staging.rename(destination)
|
|
173
|
+
return
|
|
174
|
+
aside = destination.parent / f".{SKILL_NAME}-old-{uuid.uuid4().hex}"
|
|
175
|
+
destination.rename(aside)
|
|
176
|
+
try:
|
|
177
|
+
staging.rename(destination)
|
|
178
|
+
except OSError:
|
|
179
|
+
aside.rename(destination)
|
|
180
|
+
raise
|
|
181
|
+
if aside.is_symlink() or not aside.is_dir():
|
|
182
|
+
aside.unlink()
|
|
183
|
+
return
|
|
184
|
+
shutil.rmtree(aside)
|