dataface 0.1.2__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.
- d3_format/__init__.py +14 -0
- d3_format/errors.py +19 -0
- d3_format/format.py +551 -0
- d3_format/spec.py +159 -0
- dataface/DATAFACE_SYNTAX.md +1135 -0
- dataface/__init__.py +93 -0
- dataface/_docs_site.py +20 -0
- dataface/_install_hint.py +26 -0
- dataface/agent_api/__init__.py +79 -0
- dataface/agent_api/_init_templates/__init__.py +0 -0
- dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
- dataface/agent_api/_init_templates/dataface.yml +15 -0
- dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
- dataface/agent_api/_init_templates/index.md +24 -0
- dataface/agent_api/_paths.py +118 -0
- dataface/agent_api/_project_agents_md.py +43 -0
- dataface/agent_api/_session_store.py +486 -0
- dataface/agent_api/_state.py +28 -0
- dataface/agent_api/chat.py +221 -0
- dataface/agent_api/dashboards.py +257 -0
- dataface/agent_api/describe.py +366 -0
- dataface/agent_api/describe_query.py +120 -0
- dataface/agent_api/docs/__init__.py +25 -0
- dataface/agent_api/docs/_loader.py +292 -0
- dataface/agent_api/docs/yaml-reference.md +2757 -0
- dataface/agent_api/file_refs.py +118 -0
- dataface/agent_api/init.py +126 -0
- dataface/agent_api/inspect.py +128 -0
- dataface/agent_api/mcp_install.py +170 -0
- dataface/agent_api/query.py +274 -0
- dataface/agent_api/schema.py +658 -0
- dataface/agent_api/schema_search.py +284 -0
- dataface/agent_api/search.py +270 -0
- dataface/agent_api/skill_install.py +141 -0
- dataface/agent_api/skill_render.py +90 -0
- dataface/agent_api/skills.py +293 -0
- dataface/agent_api/surface_aliases.yaml +128 -0
- dataface/agent_api/validate.py +175 -0
- dataface/agent_api/validate_query.py +84 -0
- dataface/ai/__init__.py +39 -0
- dataface/ai/agent.py +139 -0
- dataface/ai/context.py +45 -0
- dataface/ai/events.py +62 -0
- dataface/ai/external_mcp.py +610 -0
- dataface/ai/generate_sql.py +96 -0
- dataface/ai/llm.py +403 -0
- dataface/ai/mcp/__init__.py +51 -0
- dataface/ai/mcp/server.py +289 -0
- dataface/ai/memories.py +85 -0
- dataface/ai/prompts.py +177 -0
- dataface/ai/schema_context.py +138 -0
- dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
- dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
- dataface/ai/skills/dashboard-build/SKILL.md +212 -0
- dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
- dataface/ai/skills/dashboard-design/SKILL.md +182 -0
- dataface/ai/skills/dashboard-review/SKILL.md +113 -0
- dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
- dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
- dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
- dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
- dataface/ai/skills/drill-down-link/SKILL.md +112 -0
- dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
- dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
- dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
- dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
- dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
- dataface/ai/skills/kpi-row/SKILL.md +101 -0
- dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
- dataface/ai/skills/report-design/SKILL.md +184 -0
- dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
- dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
- dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
- dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
- dataface/ai/skills/time-series-trend/SKILL.md +93 -0
- dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
- dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
- dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
- dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
- dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
- dataface/ai/tool_schemas.py +132 -0
- dataface/ai/tools/__init__.py +312 -0
- dataface/ai/yaml_utils.py +57 -0
- dataface/cli/__init__.py +3 -0
- dataface/cli/_console.py +48 -0
- dataface/cli/_error_format.py +83 -0
- dataface/cli/_extras.py +190 -0
- dataface/cli/_json_output.py +8 -0
- dataface/cli/_parsing.py +17 -0
- dataface/cli/_version_info.py +56 -0
- dataface/cli/commands/__init__.py +3 -0
- dataface/cli/commands/_agent_input.py +205 -0
- dataface/cli/commands/_agent_server.py +115 -0
- dataface/cli/commands/chat.py +645 -0
- dataface/cli/commands/describe.py +107 -0
- dataface/cli/commands/docs.py +131 -0
- dataface/cli/commands/extension.py +179 -0
- dataface/cli/commands/init.py +240 -0
- dataface/cli/commands/inspect.py +94 -0
- dataface/cli/commands/mcp_init.py +167 -0
- dataface/cli/commands/query.py +386 -0
- dataface/cli/commands/render.py +291 -0
- dataface/cli/commands/schema.py +411 -0
- dataface/cli/commands/search.py +49 -0
- dataface/cli/commands/serve.py +114 -0
- dataface/cli/commands/skills.py +133 -0
- dataface/cli/commands/skills_init.py +161 -0
- dataface/cli/commands/validate.py +63 -0
- dataface/cli/main.py +1501 -0
- dataface/core/__init__.py +75 -0
- dataface/core/compile/__init__.py +244 -0
- dataface/core/compile/_jinja_helpers.py +78 -0
- dataface/core/compile/channel.py +222 -0
- dataface/core/compile/chart_focus.py +101 -0
- dataface/core/compile/chart_resolved.py +169 -0
- dataface/core/compile/chart_type_detection.py +489 -0
- dataface/core/compile/chart_update.py +261 -0
- dataface/core/compile/colors.py +64 -0
- dataface/core/compile/compiler.py +904 -0
- dataface/core/compile/config.py +823 -0
- dataface/core/compile/custom_chart_types.py +208 -0
- dataface/core/compile/data_table_attachment.py +1287 -0
- dataface/core/compile/detect.py +110 -0
- dataface/core/compile/errors.py +302 -0
- dataface/core/compile/filter_injection.py +319 -0
- dataface/core/compile/introspection.py +527 -0
- dataface/core/compile/jinja.py +511 -0
- dataface/core/compile/labels_env.py +52 -0
- dataface/core/compile/markdown.py +154 -0
- dataface/core/compile/meta.py +388 -0
- dataface/core/compile/models/__init__.py +0 -0
- dataface/core/compile/models/chart/__init__.py +0 -0
- dataface/core/compile/models/chart/authored.py +2137 -0
- dataface/core/compile/models/chart/compiled.py +398 -0
- dataface/core/compile/models/config.py +347 -0
- dataface/core/compile/models/face/__init__.py +0 -0
- dataface/core/compile/models/face/authored.py +659 -0
- dataface/core/compile/models/face/compiled.py +522 -0
- dataface/core/compile/models/factories.py +201 -0
- dataface/core/compile/models/markers.py +40 -0
- dataface/core/compile/models/palette.py +36 -0
- dataface/core/compile/models/primitives.py +415 -0
- dataface/core/compile/models/query/__init__.py +0 -0
- dataface/core/compile/models/query/authored.py +246 -0
- dataface/core/compile/models/query/compiled.py +710 -0
- dataface/core/compile/models/refs.py +137 -0
- dataface/core/compile/models/source.py +611 -0
- dataface/core/compile/models/style/__init__.py +0 -0
- dataface/core/compile/models/style/authored.py +481 -0
- dataface/core/compile/models/style/compiled.py +3399 -0
- dataface/core/compile/models/style/merged.py +1682 -0
- dataface/core/compile/models/theme.py +362 -0
- dataface/core/compile/models/variable/__init__.py +0 -0
- dataface/core/compile/models/variable/authored.py +254 -0
- dataface/core/compile/models/vega_lite/__init__.py +0 -0
- dataface/core/compile/models/vega_lite/config.py +510 -0
- dataface/core/compile/models/vega_lite/contracts.py +171 -0
- dataface/core/compile/normalize_charts.py +494 -0
- dataface/core/compile/normalize_layout.py +1000 -0
- dataface/core/compile/normalize_queries.py +297 -0
- dataface/core/compile/normalize_variables.py +489 -0
- dataface/core/compile/normalizer.py +543 -0
- dataface/core/compile/palette.py +1100 -0
- dataface/core/compile/parameterized.py +658 -0
- dataface/core/compile/parser.py +228 -0
- dataface/core/compile/schema.py +20 -0
- dataface/core/compile/schema_renderers/__init__.py +0 -0
- dataface/core/compile/schema_renderers/json_schema.py +163 -0
- dataface/core/compile/schema_renderers/prompt.py +152 -0
- dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
- dataface/core/compile/sizing.py +2126 -0
- dataface/core/compile/sources.py +518 -0
- dataface/core/compile/sql_authoring_lint.py +56 -0
- dataface/core/compile/style_cascade.py +471 -0
- dataface/core/compile/typography.py +299 -0
- dataface/core/compile/validator.py +301 -0
- dataface/core/compile/variables.py +53 -0
- dataface/core/compile/vega_config.py +98 -0
- dataface/core/compile/vega_lite/__init__.py +6 -0
- dataface/core/compile/vega_lite/validation.py +95 -0
- dataface/core/compile/yaml_error_formatter.py +838 -0
- dataface/core/connections.py +38 -0
- dataface/core/dashboard.py +358 -0
- dataface/core/defaults/default_config.yml +101 -0
- dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
- dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
- dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
- dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
- dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
- dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
- dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
- dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
- dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
- dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
- dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
- dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
- dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
- dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
- dataface/core/defaults/palettes/sequential/amber.yml +22 -0
- dataface/core/defaults/palettes/sequential/blue.yml +22 -0
- dataface/core/defaults/palettes/sequential/brown.yml +22 -0
- dataface/core/defaults/palettes/sequential/gray.yml +22 -0
- dataface/core/defaults/palettes/sequential/green.yml +22 -0
- dataface/core/defaults/palettes/sequential/purple.yml +22 -0
- dataface/core/defaults/palettes/sequential/rust.yml +22 -0
- dataface/core/defaults/palettes/sequential/teal.yml +22 -0
- dataface/core/defaults/palettes/tone/negative.yml +32 -0
- dataface/core/defaults/palettes/tone/positive.yml +22 -0
- dataface/core/defaults/palettes/tone/warning.yml +22 -0
- dataface/core/defaults/themes/_base.yaml +786 -0
- dataface/core/defaults/themes/bi.yaml +16 -0
- dataface/core/defaults/themes/carbong100.yaml +41 -0
- dataface/core/defaults/themes/cream.yaml +122 -0
- dataface/core/defaults/themes/dark.yaml +40 -0
- dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
- dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
- dataface/core/defaults/themes/editorial.yaml +147 -0
- dataface/core/defaults/themes/light.yaml +30 -0
- dataface/core/defaults/themes/looker.yaml +17 -0
- dataface/core/defaults/themes/stark.yaml +134 -0
- dataface/core/errors/__init__.py +67 -0
- dataface/core/errors/codes_compile.py +56 -0
- dataface/core/errors/codes_execute.py +177 -0
- dataface/core/errors/codes_render.py +106 -0
- dataface/core/errors/codes_unknown.py +15 -0
- dataface/core/errors/hints.py +74 -0
- dataface/core/errors/registry.py +42 -0
- dataface/core/errors/structured.py +92 -0
- dataface/core/execute/__init__.py +91 -0
- dataface/core/execute/adapters/__init__.py +49 -0
- dataface/core/execute/adapters/adapter_registry.py +400 -0
- dataface/core/execute/adapters/base.py +245 -0
- dataface/core/execute/adapters/csv_adapter.py +239 -0
- dataface/core/execute/adapters/dbt_adapter.py +283 -0
- dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
- dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
- dataface/core/execute/adapters/dbt_utils.py +150 -0
- dataface/core/execute/adapters/http_adapter.py +224 -0
- dataface/core/execute/adapters/metricflow_adapter.py +94 -0
- dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
- dataface/core/execute/adapters/sql_adapter.py +710 -0
- dataface/core/execute/adapters/values_adapter.py +58 -0
- dataface/core/execute/batch.py +744 -0
- dataface/core/execute/cache_backend.py +135 -0
- dataface/core/execute/cache_keys.py +66 -0
- dataface/core/execute/dbt_jinja.py +21 -0
- dataface/core/execute/dialects/__init__.py +121 -0
- dataface/core/execute/dialects/athena.py +75 -0
- dataface/core/execute/dialects/base.py +302 -0
- dataface/core/execute/dialects/bigquery.py +38 -0
- dataface/core/execute/dialects/databricks.py +68 -0
- dataface/core/execute/dialects/duckdb.py +35 -0
- dataface/core/execute/dialects/mysql.py +68 -0
- dataface/core/execute/dialects/postgres.py +39 -0
- dataface/core/execute/dialects/redshift.py +12 -0
- dataface/core/execute/dialects/snowflake.py +51 -0
- dataface/core/execute/dialects/sqlserver.py +92 -0
- dataface/core/execute/duckdb_cache.py +712 -0
- dataface/core/execute/duckdb_config.py +26 -0
- dataface/core/execute/errors.py +213 -0
- dataface/core/execute/executor.py +1249 -0
- dataface/core/execute/parallel.py +162 -0
- dataface/core/execute/setup_sql.py +58 -0
- dataface/core/execute/source_registry.py +72 -0
- dataface/core/execute/source_resolver.py +255 -0
- dataface/core/execute/sql_guard.py +387 -0
- dataface/core/execute/sql_literals.py +199 -0
- dataface/core/fonts.py +52 -0
- dataface/core/inspect/__init__.py +32 -0
- dataface/core/inspect/cache_factory.py +98 -0
- dataface/core/inspect/db_types.py +162 -0
- dataface/core/inspect/dbt_schema.py +96 -0
- dataface/core/inspect/defaults.yml +37 -0
- dataface/core/inspect/fanout_risk.py +109 -0
- dataface/core/inspect/manifest_utils.py +77 -0
- dataface/core/inspect/partials/categorical.yml +40 -0
- dataface/core/inspect/partials/date.yml +40 -0
- dataface/core/inspect/partials/numeric.yml +55 -0
- dataface/core/inspect/partition_types.py +38 -0
- dataface/core/inspect/query_validator.py +975 -0
- dataface/core/inspect/renderer.py +354 -0
- dataface/core/inspect/resolver.py +808 -0
- dataface/core/inspect/search.py +461 -0
- dataface/core/inspect/sources/__init__.py +32 -0
- dataface/core/inspect/sources/dbt.py +738 -0
- dataface/core/inspect/sources/duckdb_utils.py +66 -0
- dataface/core/inspect/templates/__init__.py +1 -0
- dataface/core/inspect/templates/categorical_column.yml +196 -0
- dataface/core/inspect/templates/charts.yml +109 -0
- dataface/core/inspect/templates/date_column.yml +248 -0
- dataface/core/inspect/templates/model.yml +138 -0
- dataface/core/inspect/templates/numeric_column.yml +261 -0
- dataface/core/inspect/templates/quality.yml +80 -0
- dataface/core/inspect/templates/string_column.yml +263 -0
- dataface/core/project_roots.py +165 -0
- dataface/core/render/__init__.py +87 -0
- dataface/core/render/board_links.py +176 -0
- dataface/core/render/chart/__init__.py +27 -0
- dataface/core/render/chart/arc_attached_table.py +251 -0
- dataface/core/render/chart/artifacts.py +16 -0
- dataface/core/render/chart/callout.py +225 -0
- dataface/core/render/chart/decisions.py +358 -0
- dataface/core/render/chart/geo.py +700 -0
- dataface/core/render/chart/kpi.py +916 -0
- dataface/core/render/chart/labels.py +76 -0
- dataface/core/render/chart/pipeline.py +818 -0
- dataface/core/render/chart/presentation.py +36 -0
- dataface/core/render/chart/profile.py +3438 -0
- dataface/core/render/chart/render_single.py +347 -0
- dataface/core/render/chart/renderers.py +193 -0
- dataface/core/render/chart/rendering.py +565 -0
- dataface/core/render/chart/serialization.py +90 -0
- dataface/core/render/chart/spark.py +496 -0
- dataface/core/render/chart/spark_bar.py +370 -0
- dataface/core/render/chart/spec_builders.py +154 -0
- dataface/core/render/chart/standard_renderer.py +2645 -0
- dataface/core/render/chart/table.py +2957 -0
- dataface/core/render/chart/table_support.py +1452 -0
- dataface/core/render/chart/tick_values.py +66 -0
- dataface/core/render/chart/time_unit_detect.py +809 -0
- dataface/core/render/chart/title_overflow.py +157 -0
- dataface/core/render/chart/type_inference.py +122 -0
- dataface/core/render/chart/validation.py +99 -0
- dataface/core/render/chart/vega_lite.py +125 -0
- dataface/core/render/chart/vega_lite_types.py +268 -0
- dataface/core/render/chart/vl_field_maps.py +346 -0
- dataface/core/render/chart_interactivity.py +24 -0
- dataface/core/render/control_registry.py +287 -0
- dataface/core/render/converters/__init__.py +24 -0
- dataface/core/render/converters/chart.py +276 -0
- dataface/core/render/converters/html.py +98 -0
- dataface/core/render/converters/pdf.py +40 -0
- dataface/core/render/converters/png.py +41 -0
- dataface/core/render/errors.py +144 -0
- dataface/core/render/face_api.py +160 -0
- dataface/core/render/faces.py +1194 -0
- dataface/core/render/font_measurement.py +48 -0
- dataface/core/render/font_support.py +197 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/InterVariable.ttf +0 -0
- dataface/core/render/fonts/InterVariable.woff2 +0 -0
- dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
- dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
- dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
- dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
- dataface/core/render/fonts/_emoji_font_face.css +43 -0
- dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
- dataface/core/render/format_utils.py +329 -0
- dataface/core/render/geo_defaults.yml +28 -0
- dataface/core/render/json_format.py +146 -0
- dataface/core/render/layout_sizing.py +865 -0
- dataface/core/render/layouts.py +541 -0
- dataface/core/render/markdown_defaults.yml +16 -0
- dataface/core/render/missing_vars_prompt.py +79 -0
- dataface/core/render/placeholder.py +389 -0
- dataface/core/render/render_result.py +14 -0
- dataface/core/render/renderer.py +467 -0
- dataface/core/render/script_embedding.py +16 -0
- dataface/core/render/svg_utils.py +212 -0
- dataface/core/render/template_loader.py +69 -0
- dataface/core/render/templates/controls/_styles.css +606 -0
- dataface/core/render/templates/controls/checkbox.html +16 -0
- dataface/core/render/templates/controls/date.html +16 -0
- dataface/core/render/templates/controls/number.html +19 -0
- dataface/core/render/templates/controls/readonly.html +9 -0
- dataface/core/render/templates/controls/select.html +21 -0
- dataface/core/render/templates/controls/slider.html +22 -0
- dataface/core/render/templates/controls/text.html +16 -0
- dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
- dataface/core/render/templates/scripts/variables.js +976 -0
- dataface/core/render/templates/svg/grid_pattern.svg +3 -0
- dataface/core/render/templates/svg/styles.css +51 -0
- dataface/core/render/terminal.py +311 -0
- dataface/core/render/terminal_charts.py +563 -0
- dataface/core/render/terminal_defaults.yml +2 -0
- dataface/core/render/terminal_layouts.py +299 -0
- dataface/core/render/terminal_text.py +31 -0
- dataface/core/render/text/__init__.py +1 -0
- dataface/core/render/text/case.py +113 -0
- dataface/core/render/text_format.py +129 -0
- dataface/core/render/utils.py +106 -0
- dataface/core/render/variable_controls.py +946 -0
- dataface/core/render/variable_input_refinement.py +140 -0
- dataface/core/render/warnings/__init__.py +15 -0
- dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
- dataface/core/render/warnings/base.py +44 -0
- dataface/core/render/warnings/fanout_risk.py +15 -0
- dataface/core/render/warnings/from_query_diagnostic.py +56 -0
- dataface/core/render/warnings/missing_join_predicate.py +13 -0
- dataface/core/render/warnings/query_parse_error.py +14 -0
- dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
- dataface/core/render/warnings/reaggregation.py +14 -0
- dataface/core/render/warnings/registry.py +45 -0
- dataface/core/render/warnings/suppression.py +46 -0
- dataface/core/render/warnings/temporal_single_point.py +63 -0
- dataface/core/render/warnings/unreferenced_chart.py +15 -0
- dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
- dataface/core/render/yaml_format.py +167 -0
- dataface/core/resolve_face.py +195 -0
- dataface/core/schema/__init__.py +0 -0
- dataface/core/schema/guidance.py +151 -0
- dataface/core/scoped_paths.py +59 -0
- dataface/core/serve/__init__.py +14 -0
- dataface/core/serve/bootstrap.py +39 -0
- dataface/core/serve/embedded.py +57 -0
- dataface/core/serve/port.py +129 -0
- dataface/core/serve/server.py +938 -0
- dataface/core/serve/templates/__init__.py +0 -0
- dataface/core/serve/templates/directory.yml +6 -0
- dataface/core/serve/templates/error.html.j2 +217 -0
- dataface/core/utils.py +121 -0
- dataface/core/validate.py +64 -0
- dataface/integrations/__init__.py +0 -0
- dataface/integrations/highlighting.py +351 -0
- dataface/integrations/markdown.py +537 -0
- dataface/py.typed +0 -0
- dataface-0.1.2.dist-info/METADATA +375 -0
- dataface-0.1.2.dist-info/RECORD +455 -0
- dataface-0.1.2.dist-info/WHEEL +4 -0
- dataface-0.1.2.dist-info/entry_points.txt +2 -0
- dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
- mdsvg/__init__.py +168 -0
- mdsvg/fonts.py +656 -0
- mdsvg/images.py +299 -0
- mdsvg/parser.py +629 -0
- mdsvg/playground.py +284 -0
- mdsvg/py.typed +2 -0
- mdsvg/renderer.py +1623 -0
- mdsvg/style.py +355 -0
- mdsvg/types.py +200 -0
- mdsvg/utils.py +86 -0
dataface/cli/main.py
ADDED
|
@@ -0,0 +1,1501 @@
|
|
|
1
|
+
"""Main CLI entry point."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated, Any, Literal
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
from rich import box
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from typer.core import TyperGroup
|
|
15
|
+
|
|
16
|
+
from dataface._install_hint import install_hint
|
|
17
|
+
from dataface.cli._console import is_plain_output
|
|
18
|
+
from dataface.cli._parsing import parse_kv_pairs
|
|
19
|
+
from dataface.cli.commands import (
|
|
20
|
+
chat as chat_cmd,
|
|
21
|
+
describe as describe_cmd,
|
|
22
|
+
docs as docs_cmd,
|
|
23
|
+
extension as extension_cmd,
|
|
24
|
+
init as init_cmd,
|
|
25
|
+
inspect as inspect_cmd,
|
|
26
|
+
query as query_cmd,
|
|
27
|
+
render as render_cmd,
|
|
28
|
+
schema as schema_cmd,
|
|
29
|
+
search as search_cmd,
|
|
30
|
+
serve as serve_cmd,
|
|
31
|
+
skills as skills_cmd,
|
|
32
|
+
validate as validate_cmd,
|
|
33
|
+
)
|
|
34
|
+
from dataface.cli.commands.mcp_init import run_init as _mcp_run_init
|
|
35
|
+
from dataface.cli.commands.skills_init import run_init_skills as _skills_run_init
|
|
36
|
+
from dataface.core.dashboard import RenderFormat
|
|
37
|
+
from dataface.core.project_roots import find_dataface_project
|
|
38
|
+
|
|
39
|
+
# Evaluated once at process startup — mid-session env changes won't take effect.
|
|
40
|
+
_RICH_MARKUP_MODE: Literal["rich"] | None = None if is_plain_output() else "rich"
|
|
41
|
+
# Probe for private package once at startup; avoids repeated find_spec calls.
|
|
42
|
+
_SUPER_SCHEMA_AVAILABLE: bool = (
|
|
43
|
+
importlib.util.find_spec("dataface_super_schema") is not None
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# MCP subcommand app
|
|
47
|
+
mcp_app = typer.Typer(
|
|
48
|
+
name="mcp",
|
|
49
|
+
help="MCP (Model Context Protocol) server commands for AI assistant integration.",
|
|
50
|
+
no_args_is_help=True,
|
|
51
|
+
pretty_exceptions_show_locals=False,
|
|
52
|
+
rich_markup_mode=_RICH_MARKUP_MODE,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Init subcommand app — `dft init` (project scaffold), `dft init mcp [client]`
|
|
56
|
+
# (AI integration), `dft init code|cursor|vscode` (editor extension install).
|
|
57
|
+
init_app = typer.Typer(
|
|
58
|
+
name="init",
|
|
59
|
+
help="Bootstrap a Dataface project, plus its AI / editor integrations.",
|
|
60
|
+
invoke_without_command=True,
|
|
61
|
+
pretty_exceptions_show_locals=False,
|
|
62
|
+
rich_markup_mode=_RICH_MARKUP_MODE,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Inspect subcommand app
|
|
66
|
+
inspect_app = typer.Typer(
|
|
67
|
+
name="inspect",
|
|
68
|
+
help="Inspect database tables and manage inspect templates.",
|
|
69
|
+
invoke_without_command=True,
|
|
70
|
+
pretty_exceptions_show_locals=False,
|
|
71
|
+
rich_markup_mode=_RICH_MARKUP_MODE,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Shared --project-dir option. Every user-visible command that accepts a
|
|
75
|
+
# project root uses this alias so help text, validation, and DFT_PROJECT_DIR
|
|
76
|
+
# env-var wiring stay in lock-step.
|
|
77
|
+
ProjectDirOption = Annotated[
|
|
78
|
+
Path | None,
|
|
79
|
+
typer.Option(
|
|
80
|
+
"--project-dir",
|
|
81
|
+
exists=True,
|
|
82
|
+
file_okay=False,
|
|
83
|
+
dir_okay=True,
|
|
84
|
+
resolve_path=True,
|
|
85
|
+
envvar="DFT_PROJECT_DIR",
|
|
86
|
+
help="Project root for resolving face paths and finding project config",
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Configure logging for CLI (only if no handlers configured yet)
|
|
92
|
+
if not logging.getLogger().handlers:
|
|
93
|
+
logging.basicConfig(
|
|
94
|
+
level=logging.INFO,
|
|
95
|
+
format="%(name)s - %(levelname)s - %(message)s",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _has_faces_marker(start: Path) -> bool:
|
|
100
|
+
"""True iff *start* sits inside a Dataface project that has a ``faces/`` directory.
|
|
101
|
+
|
|
102
|
+
Anchors on :func:`find_dataface_project` so the walk-up stops at the
|
|
103
|
+
project root (any of ``dataface.yml``/``dataface.yaml``/``dbt_project.yml``)
|
|
104
|
+
rather than blindly hunting upward for stray ``faces/`` directories
|
|
105
|
+
elsewhere on the filesystem. A bare-dbt repo with no scaffolded
|
|
106
|
+
``faces/`` returns False — that's the case the banner exists for.
|
|
107
|
+
"""
|
|
108
|
+
if (start / "faces").is_dir():
|
|
109
|
+
return True
|
|
110
|
+
project = find_dataface_project(start)
|
|
111
|
+
return project is not None and (project / "faces").is_dir()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _render_init_banner() -> None:
|
|
115
|
+
"""Print a nudge to run ``dft init``.
|
|
116
|
+
|
|
117
|
+
Only called from :class:`_RootHelpGroup.format_help` when cwd is
|
|
118
|
+
not inside a scaffolded project. In plain mode (agent context or
|
|
119
|
+
piped stdout) the banner renders as a single plain line so context
|
|
120
|
+
captures do not pick up Panel chrome.
|
|
121
|
+
"""
|
|
122
|
+
if is_plain_output():
|
|
123
|
+
Console(force_terminal=False, no_color=True).print(
|
|
124
|
+
"Welcome to Dataface — run `dft init` to start a new project."
|
|
125
|
+
)
|
|
126
|
+
return
|
|
127
|
+
Console().print(
|
|
128
|
+
Panel(
|
|
129
|
+
"Run [bold cyan]dft init[/bold cyan] to start a new project!",
|
|
130
|
+
title="Welcome to Dataface",
|
|
131
|
+
title_align="left",
|
|
132
|
+
border_style="yellow",
|
|
133
|
+
box=box.ROUNDED,
|
|
134
|
+
padding=(0, 1),
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class _RootHelpGroup(TyperGroup):
|
|
140
|
+
"""Root-only group: alphabetize the Options panel + commands and gate the init banner.
|
|
141
|
+
|
|
142
|
+
Click otherwise displays params in declaration order with the
|
|
143
|
+
auto-added `--help` last; that puts `--version` ahead of `--help`
|
|
144
|
+
on the root help. TyperGroup also overrides Click's sorted
|
|
145
|
+
list_commands with registration order, which buckets sub-typer
|
|
146
|
+
groups after leaf commands within the same rich_help_panel (e.g.
|
|
147
|
+
`init` after docs/playground/skills under Reference). Sorting here
|
|
148
|
+
interleaves them so each panel reads A-Z. Also prints the "run
|
|
149
|
+
`dft init`" banner above the help body when cwd isn't a scaffolded
|
|
150
|
+
Dataface project. Intentionally attached to the root Typer only —
|
|
151
|
+
sub-typer and leaf-command orders are left as authored, and
|
|
152
|
+
sub-typer help never shows the banner.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def get_params(self, ctx: Any) -> list[Any]:
|
|
156
|
+
def key(p: Any) -> str:
|
|
157
|
+
for opt in getattr(p, "opts", ()) or ():
|
|
158
|
+
if opt.startswith("--"):
|
|
159
|
+
return opt.lstrip("-").lower()
|
|
160
|
+
return (p.name or "").lower()
|
|
161
|
+
|
|
162
|
+
return sorted(super().get_params(ctx), key=key)
|
|
163
|
+
|
|
164
|
+
def list_commands(self, ctx: Any) -> list[str]:
|
|
165
|
+
# typer.rich_utils.rich_format_help iterates this list once and
|
|
166
|
+
# buckets by rich_help_panel; the per-panel order is the iteration
|
|
167
|
+
# order, and the panel-section order is determined by which panel
|
|
168
|
+
# gets its first command first. Sort by (panel-rank, name) so the
|
|
169
|
+
# documented panel order (Dashboards -> Data & SQL -> AI -> Reference)
|
|
170
|
+
# survives while sub-typer groups interleave alphabetically with
|
|
171
|
+
# leaf commands inside each panel.
|
|
172
|
+
panel_by_name: dict[str, str] = {}
|
|
173
|
+
for n in super().list_commands(ctx):
|
|
174
|
+
cmd = self.get_command(ctx, n)
|
|
175
|
+
panel_by_name[n] = getattr(cmd, "rich_help_panel", None) or ""
|
|
176
|
+
panel_rank: dict[str, int] = {}
|
|
177
|
+
for panel in panel_by_name.values():
|
|
178
|
+
panel_rank.setdefault(panel, len(panel_rank))
|
|
179
|
+
return sorted(panel_by_name, key=lambda n: (panel_rank[panel_by_name[n]], n))
|
|
180
|
+
|
|
181
|
+
def format_help(self, ctx: Any, formatter: Any) -> None:
|
|
182
|
+
if not _has_faces_marker(Path.cwd()):
|
|
183
|
+
_render_init_banner()
|
|
184
|
+
super().format_help(ctx, formatter)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# `help_option_names` on the root Click context inherits down to every
|
|
188
|
+
# sub-Typer's group context and every leaf command, so a single setting
|
|
189
|
+
# here gives `-h` to the whole CLI tree.
|
|
190
|
+
app = typer.Typer(
|
|
191
|
+
name="dft",
|
|
192
|
+
help="Declarative, dbt-native dashboards in YAML.",
|
|
193
|
+
no_args_is_help=True,
|
|
194
|
+
pretty_exceptions_show_locals=False,
|
|
195
|
+
add_completion=False,
|
|
196
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
197
|
+
cls=_RootHelpGroup,
|
|
198
|
+
rich_markup_mode=_RICH_MARKUP_MODE,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# =============================================================================
|
|
203
|
+
# init_app — `dft init …` subcommands (registered as a sub-typer at the bottom)
|
|
204
|
+
# =============================================================================
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@init_app.callback(invoke_without_command=True)
|
|
208
|
+
def init_default(
|
|
209
|
+
ctx: typer.Context,
|
|
210
|
+
project_dir: ProjectDirOption = None,
|
|
211
|
+
force: Annotated[
|
|
212
|
+
bool,
|
|
213
|
+
typer.Option("--force", "-f", help="Overwrite existing scaffold files"),
|
|
214
|
+
] = False,
|
|
215
|
+
yes: Annotated[
|
|
216
|
+
bool,
|
|
217
|
+
typer.Option("--yes", "-y", help="Accept all defaults without prompting"),
|
|
218
|
+
] = False,
|
|
219
|
+
agents_md: Annotated[
|
|
220
|
+
bool | None,
|
|
221
|
+
typer.Option("--agents-md/--no-agents-md", help="Write/append AGENTS.md"),
|
|
222
|
+
] = None,
|
|
223
|
+
claude_md: Annotated[
|
|
224
|
+
bool | None,
|
|
225
|
+
typer.Option("--claude-md/--no-claude-md", help="Create CLAUDE.md pointer"),
|
|
226
|
+
] = None,
|
|
227
|
+
skills: Annotated[
|
|
228
|
+
bool | None,
|
|
229
|
+
typer.Option(
|
|
230
|
+
"--skills/--no-skills",
|
|
231
|
+
help="Install workflow skills to agent skill directories",
|
|
232
|
+
),
|
|
233
|
+
] = None,
|
|
234
|
+
mcp: Annotated[
|
|
235
|
+
bool | None,
|
|
236
|
+
typer.Option("--mcp/--no-mcp", help="Set up MCP server for AI assistants"),
|
|
237
|
+
] = None,
|
|
238
|
+
chat_extra: Annotated[
|
|
239
|
+
bool | None,
|
|
240
|
+
typer.Option(
|
|
241
|
+
"--chat-extra/--no-chat-extra",
|
|
242
|
+
help="Install dataface[chat] extra (enables dft chat)",
|
|
243
|
+
),
|
|
244
|
+
] = None,
|
|
245
|
+
with_playground: Annotated[
|
|
246
|
+
bool | None,
|
|
247
|
+
typer.Option(
|
|
248
|
+
"--with-playground/--no-with-playground",
|
|
249
|
+
help="Install dataface[playground] extra (enables dft playground)",
|
|
250
|
+
),
|
|
251
|
+
] = None,
|
|
252
|
+
vscode: Annotated[
|
|
253
|
+
bool | None,
|
|
254
|
+
typer.Option(
|
|
255
|
+
"--vscode/--no-vscode", help="Install Dataface extension into VS Code"
|
|
256
|
+
),
|
|
257
|
+
] = None,
|
|
258
|
+
cursor: Annotated[
|
|
259
|
+
bool | None,
|
|
260
|
+
typer.Option(
|
|
261
|
+
"--cursor/--no-cursor", help="Install Dataface extension into Cursor"
|
|
262
|
+
),
|
|
263
|
+
] = None,
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Bootstrap a Dataface project in an existing repo.
|
|
266
|
+
|
|
267
|
+
Detects dbt projects, creates faces/ and faces/partials/, ejects inspect
|
|
268
|
+
templates, and writes starter dashboards. Optionally wires up MCP for AI
|
|
269
|
+
assistants and installs the dataface[chat] extra and IDE extensions.
|
|
270
|
+
|
|
271
|
+
Safe to re-run — existing files are never overwritten unless --force is used.
|
|
272
|
+
AGENTS.md: appends a short Dataface blurb when the file exists without markers;
|
|
273
|
+
refreshes only the ``<!-- dft-dataface:* -->`` section when markers are present.
|
|
274
|
+
|
|
275
|
+
Subcommands:
|
|
276
|
+
dft init skills [target] # Install workflow skills for AI assistants
|
|
277
|
+
dft init mcp [client] # Wire up MCP server
|
|
278
|
+
dft init code # Install Dataface extension into VS Code
|
|
279
|
+
dft init cursor # …or Cursor
|
|
280
|
+
|
|
281
|
+
\b
|
|
282
|
+
Examples:
|
|
283
|
+
dft init # Init with interactive wizard
|
|
284
|
+
dft init --yes # Accept all defaults non-interactively
|
|
285
|
+
dft init --project-dir ./myrepo # Init in a specific directory
|
|
286
|
+
dft init --force # Re-scaffold, overwriting files
|
|
287
|
+
dft init --no-mcp --no-vscode # Skip MCP and IDE extension
|
|
288
|
+
dft init --with-playground # Also install dataface[playground]
|
|
289
|
+
"""
|
|
290
|
+
if ctx.invoked_subcommand is not None:
|
|
291
|
+
return
|
|
292
|
+
init_cmd.run_wizard(
|
|
293
|
+
project_dir=project_dir,
|
|
294
|
+
force=force,
|
|
295
|
+
yes=yes,
|
|
296
|
+
agents_md=agents_md,
|
|
297
|
+
claude_md=claude_md,
|
|
298
|
+
skills=skills,
|
|
299
|
+
mcp=mcp,
|
|
300
|
+
chat_extra=chat_extra,
|
|
301
|
+
with_playground=with_playground,
|
|
302
|
+
vscode=vscode,
|
|
303
|
+
cursor=cursor,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@init_app.command("code")
|
|
308
|
+
def init_code() -> None:
|
|
309
|
+
"""Install the Dataface extension into VS Code.
|
|
310
|
+
|
|
311
|
+
Installs the latest release of the Dataface extension into VS Code.
|
|
312
|
+
Idempotent — re-runs upgrade to the newest version.
|
|
313
|
+
"""
|
|
314
|
+
raise typer.Exit(extension_cmd.install_extension("code", emit=typer.echo))
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@init_app.command("cursor")
|
|
318
|
+
def init_cursor() -> None:
|
|
319
|
+
"""Install the Dataface extension into Cursor.
|
|
320
|
+
|
|
321
|
+
Installs the latest release of the Dataface extension into Cursor.
|
|
322
|
+
Idempotent — re-runs upgrade to the newest version.
|
|
323
|
+
"""
|
|
324
|
+
raise typer.Exit(extension_cmd.install_extension("cursor", emit=typer.echo))
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@init_app.command("vscode")
|
|
328
|
+
def init_vscode() -> None:
|
|
329
|
+
"""Alias for `dft init code`."""
|
|
330
|
+
raise typer.Exit(extension_cmd.install_extension("code", emit=typer.echo))
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# `dft init skills [target]` — file-based workflow skill install.
|
|
334
|
+
@init_app.command("skills")
|
|
335
|
+
def init_skills(
|
|
336
|
+
target: Annotated[
|
|
337
|
+
str | None,
|
|
338
|
+
typer.Argument(
|
|
339
|
+
help="Install target: agents (Cursor/Codex/Copilot), codex, or claude"
|
|
340
|
+
),
|
|
341
|
+
] = None,
|
|
342
|
+
all_targets: Annotated[
|
|
343
|
+
bool,
|
|
344
|
+
typer.Option("--all", help="Install to every detected skill directory"),
|
|
345
|
+
] = False,
|
|
346
|
+
dir_override: Annotated[
|
|
347
|
+
Path | None,
|
|
348
|
+
typer.Option(
|
|
349
|
+
"--dir",
|
|
350
|
+
help="Explicit skills directory (e.g. .agents/skills)",
|
|
351
|
+
file_okay=False,
|
|
352
|
+
dir_okay=True,
|
|
353
|
+
resolve_path=True,
|
|
354
|
+
),
|
|
355
|
+
] = None,
|
|
356
|
+
force: Annotated[
|
|
357
|
+
bool,
|
|
358
|
+
typer.Option("--force", "-f", help="Overwrite existing skill files"),
|
|
359
|
+
] = False,
|
|
360
|
+
check: Annotated[
|
|
361
|
+
bool,
|
|
362
|
+
typer.Option(
|
|
363
|
+
"--check",
|
|
364
|
+
help="Dry run: show what would be installed without writing files",
|
|
365
|
+
),
|
|
366
|
+
] = False,
|
|
367
|
+
project_dir: ProjectDirOption = None,
|
|
368
|
+
) -> None:
|
|
369
|
+
"""Install Dataface workflow skills for file-based agent auto-discovery.
|
|
370
|
+
|
|
371
|
+
Writes CLI-rendered skill files to ``.agents/skills/`` (Cursor, Codex,
|
|
372
|
+
Copilot) and/or ``.claude/skills/`` (Claude Code). Does not configure MCP
|
|
373
|
+
or modify AGENTS.md / CLAUDE.md.
|
|
374
|
+
|
|
375
|
+
\b
|
|
376
|
+
Examples:
|
|
377
|
+
dft init skills # Detect targets and install
|
|
378
|
+
dft init skills agents # .agents/skills/ only
|
|
379
|
+
dft init skills claude # .claude/skills/ only
|
|
380
|
+
dft init skills --all # Every detected target dir
|
|
381
|
+
dft init skills --dir PATH # Explicit destination
|
|
382
|
+
dft init skills --check # Dry run
|
|
383
|
+
dft init skills -f # Overwrite existing files
|
|
384
|
+
"""
|
|
385
|
+
_skills_run_init(
|
|
386
|
+
target=target,
|
|
387
|
+
all_targets=all_targets,
|
|
388
|
+
dir_override=dir_override,
|
|
389
|
+
force=force,
|
|
390
|
+
check=check,
|
|
391
|
+
project_dir=project_dir,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# `dft init mcp [client]` — MCP config only (skills: `dft init skills`).
|
|
396
|
+
@init_app.command("mcp")
|
|
397
|
+
def init_mcp(
|
|
398
|
+
client: Annotated[
|
|
399
|
+
str | None,
|
|
400
|
+
typer.Argument(
|
|
401
|
+
help=(
|
|
402
|
+
"Client to configure: cursor, vscode, claude, claude-code, "
|
|
403
|
+
"codex, copilot, or print. Omit to auto-detect."
|
|
404
|
+
)
|
|
405
|
+
),
|
|
406
|
+
] = None,
|
|
407
|
+
all_clients: Annotated[
|
|
408
|
+
bool,
|
|
409
|
+
typer.Option("--all", help="Write MCP config files for every supported client"),
|
|
410
|
+
] = False,
|
|
411
|
+
force: Annotated[
|
|
412
|
+
bool,
|
|
413
|
+
typer.Option("--force", "-f", help="Overwrite existing MCP client config"),
|
|
414
|
+
] = False,
|
|
415
|
+
project_dir: ProjectDirOption = None,
|
|
416
|
+
) -> None:
|
|
417
|
+
"""Add Dataface to your AI client's MCP configuration.
|
|
418
|
+
|
|
419
|
+
Writes MCP server entries only. Install workflow skills separately with
|
|
420
|
+
``dft init skills``.
|
|
421
|
+
|
|
422
|
+
The project root is resolved by walking up from the current directory
|
|
423
|
+
looking for dataface.yml, dataface.yaml, or dbt_project.yml. Pass
|
|
424
|
+
--project-dir to override. When the project root differs from your AI
|
|
425
|
+
client's workspace, the recorded server command points at the project
|
|
426
|
+
so it starts in the right place.
|
|
427
|
+
|
|
428
|
+
\b
|
|
429
|
+
Examples:
|
|
430
|
+
dft init mcp # Auto-detect clients + project
|
|
431
|
+
dft init mcp cursor # Configure Cursor only
|
|
432
|
+
dft init mcp claude-code # Configure Claude Code
|
|
433
|
+
dft init mcp --all # Write every supported config file
|
|
434
|
+
dft init mcp --project-dir ./analytics # Target a specific project dir
|
|
435
|
+
dft init mcp print # Print config JSON (for manual setup)
|
|
436
|
+
"""
|
|
437
|
+
_mcp_run_init(
|
|
438
|
+
client=client,
|
|
439
|
+
all_clients=all_clients,
|
|
440
|
+
force=force,
|
|
441
|
+
project_dir=project_dir,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# =============================================================================
|
|
446
|
+
# inspect_app — `dft inspect …` subcommands (registered hidden at the bottom)
|
|
447
|
+
# =============================================================================
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@inspect_app.callback(invoke_without_command=True)
|
|
451
|
+
def inspect_default(
|
|
452
|
+
ctx: typer.Context,
|
|
453
|
+
connection: Annotated[
|
|
454
|
+
str,
|
|
455
|
+
typer.Option(help="Database connection string", hidden=True),
|
|
456
|
+
] = ":memory:",
|
|
457
|
+
dialect: Annotated[
|
|
458
|
+
str,
|
|
459
|
+
typer.Option(
|
|
460
|
+
help="SQL dialect (duckdb, postgres, bigquery, etc.)", hidden=True
|
|
461
|
+
),
|
|
462
|
+
] = "duckdb",
|
|
463
|
+
schema: Annotated[
|
|
464
|
+
str | None,
|
|
465
|
+
typer.Option(help="Schema filter", hidden=True),
|
|
466
|
+
] = None,
|
|
467
|
+
output: Annotated[
|
|
468
|
+
str,
|
|
469
|
+
typer.Option(help="Path to save inspection JSON", hidden=True),
|
|
470
|
+
] = "target/super_schema.json",
|
|
471
|
+
approximate: Annotated[
|
|
472
|
+
str,
|
|
473
|
+
typer.Option(help="Approximate profiling mode: auto, on, off", hidden=True),
|
|
474
|
+
] = "auto",
|
|
475
|
+
include: Annotated[
|
|
476
|
+
str | None,
|
|
477
|
+
typer.Option(help="Glob pattern to include tables", hidden=True),
|
|
478
|
+
] = None,
|
|
479
|
+
exclude: Annotated[
|
|
480
|
+
str | None,
|
|
481
|
+
typer.Option(help="Glob pattern to exclude tables", hidden=True),
|
|
482
|
+
] = None,
|
|
483
|
+
) -> None:
|
|
484
|
+
"""Manage inspect templates and profile database tables (with dataface-super-schema).
|
|
485
|
+
|
|
486
|
+
Without subcommand: profiles all tables (requires dataface-super-schema).
|
|
487
|
+
|
|
488
|
+
\b
|
|
489
|
+
Examples:
|
|
490
|
+
dft inspect eject model # Copy template to faces/inspect/
|
|
491
|
+
dft inspect templates # List built-in templates
|
|
492
|
+
dft inspect table orders # Profile a table (needs private pkg)
|
|
493
|
+
"""
|
|
494
|
+
if ctx.invoked_subcommand is not None:
|
|
495
|
+
return
|
|
496
|
+
if not _SUPER_SCHEMA_AVAILABLE:
|
|
497
|
+
typer.echo(
|
|
498
|
+
"Batch profiling requires the dataface-super-schema package.\n"
|
|
499
|
+
"Install it from the monorepo or your private registry.\n\n"
|
|
500
|
+
"Run 'dft inspect --help' for available template-management commands.",
|
|
501
|
+
err=True,
|
|
502
|
+
)
|
|
503
|
+
raise typer.Exit(1)
|
|
504
|
+
from dataface_super_schema.cli.commands.inspect import ( # noqa: PLC0415
|
|
505
|
+
inspect_all_command,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
inspect_all_command(
|
|
509
|
+
connection=connection,
|
|
510
|
+
dialect=dialect,
|
|
511
|
+
schema=schema,
|
|
512
|
+
output=output,
|
|
513
|
+
approximate=approximate,
|
|
514
|
+
include=include,
|
|
515
|
+
exclude=exclude,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@inspect_app.command("eject")
|
|
520
|
+
def inspect_eject(
|
|
521
|
+
templates: Annotated[
|
|
522
|
+
list[str] | None,
|
|
523
|
+
typer.Argument(help="Template names to eject (e.g., model quality)"),
|
|
524
|
+
] = None,
|
|
525
|
+
all_templates: Annotated[
|
|
526
|
+
bool,
|
|
527
|
+
typer.Option("--all", help="Eject all available templates"),
|
|
528
|
+
] = False,
|
|
529
|
+
force: Annotated[
|
|
530
|
+
bool,
|
|
531
|
+
typer.Option("--force", "-f", help="Overwrite existing files"),
|
|
532
|
+
] = False,
|
|
533
|
+
output_dir: Annotated[
|
|
534
|
+
Path | None,
|
|
535
|
+
typer.Option(
|
|
536
|
+
"--output", "-o", help="Output directory (default: faces/inspect/)"
|
|
537
|
+
),
|
|
538
|
+
] = None,
|
|
539
|
+
) -> None:
|
|
540
|
+
"""Copy inspect templates to faces/inspect/ for customization.
|
|
541
|
+
|
|
542
|
+
Ejected templates can be modified to customize the inspect dashboards.
|
|
543
|
+
The server will use your customized version instead of the built-in template.
|
|
544
|
+
|
|
545
|
+
\b
|
|
546
|
+
Examples:
|
|
547
|
+
dft inspect eject model # Eject just the model template
|
|
548
|
+
dft inspect eject model quality # Eject specific templates
|
|
549
|
+
dft inspect eject --all # Eject all templates
|
|
550
|
+
dft inspect eject model --force # Overwrite existing
|
|
551
|
+
dft inspect eject --all -o custom/ # Custom output directory
|
|
552
|
+
"""
|
|
553
|
+
inspect_cmd.eject_command(
|
|
554
|
+
templates=templates or [],
|
|
555
|
+
all_templates=all_templates,
|
|
556
|
+
force=force,
|
|
557
|
+
output_dir=output_dir,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
@inspect_app.command("templates")
|
|
562
|
+
def inspect_templates() -> None:
|
|
563
|
+
"""List available inspect templates.
|
|
564
|
+
|
|
565
|
+
Shows all built-in inspect templates that can be ejected and customized.
|
|
566
|
+
|
|
567
|
+
\b
|
|
568
|
+
Examples:
|
|
569
|
+
dft inspect templates
|
|
570
|
+
"""
|
|
571
|
+
inspect_cmd.templates_command()
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
@inspect_app.command("validate-templates")
|
|
575
|
+
def inspect_validate_templates(
|
|
576
|
+
output_dir: Annotated[
|
|
577
|
+
Path | None,
|
|
578
|
+
typer.Option(
|
|
579
|
+
"--output",
|
|
580
|
+
"-o",
|
|
581
|
+
help="Inspect template directory (default: faces/inspect/)",
|
|
582
|
+
),
|
|
583
|
+
] = None,
|
|
584
|
+
) -> None:
|
|
585
|
+
"""Validate ejected inspect templates against current built-in template versions.
|
|
586
|
+
|
|
587
|
+
Helps teams detect when upstream template changes require rebasing custom templates.
|
|
588
|
+
"""
|
|
589
|
+
inspect_cmd.validate_ejected_templates_command(output_dir=output_dir)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
# =============================================================================
|
|
593
|
+
# mcp_app — `dft mcp …` subcommands (registered as a sub-typer at the bottom)
|
|
594
|
+
# =============================================================================
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
@mcp_app.command("serve")
|
|
598
|
+
def mcp_serve(
|
|
599
|
+
project_dir: ProjectDirOption = None,
|
|
600
|
+
) -> None:
|
|
601
|
+
"""Start the MCP server for AI assistant integration.
|
|
602
|
+
|
|
603
|
+
This command starts an MCP (Model Context Protocol) server that enables
|
|
604
|
+
AI assistants like Claude, Cursor, and ChatGPT to interact with Dataface
|
|
605
|
+
dashboards.
|
|
606
|
+
|
|
607
|
+
The server speaks the MCP protocol over standard input/output — the
|
|
608
|
+
transport AI clients expect.
|
|
609
|
+
|
|
610
|
+
Requires the ``mcp`` extra (run ``dft mcp serve`` with it missing and
|
|
611
|
+
the printed install command is the canonical one for your environment).
|
|
612
|
+
|
|
613
|
+
\b
|
|
614
|
+
Examples:
|
|
615
|
+
dft mcp serve
|
|
616
|
+
dft mcp serve --project-dir ./my-project
|
|
617
|
+
|
|
618
|
+
\b
|
|
619
|
+
Configuration for Claude Desktop (~/.config/claude/config.json):
|
|
620
|
+
{
|
|
621
|
+
"mcpServers": {
|
|
622
|
+
"dataface": {
|
|
623
|
+
"command": "dft",
|
|
624
|
+
"args": ["mcp", "serve"]
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
"""
|
|
629
|
+
try:
|
|
630
|
+
from dataface.ai.mcp import run_server
|
|
631
|
+
except ImportError as e:
|
|
632
|
+
typer.echo("MCP server requires additional dependencies.", err=True)
|
|
633
|
+
typer.echo(f" Install with: {install_hint('mcp')}", err=True)
|
|
634
|
+
missing_module = str(e).split("'")[1] if "'" in str(e) else "unknown"
|
|
635
|
+
typer.echo(f" Missing: {missing_module}", err=True)
|
|
636
|
+
raise typer.Exit(1) from None
|
|
637
|
+
|
|
638
|
+
import asyncio
|
|
639
|
+
|
|
640
|
+
# Change to specified directory if provided
|
|
641
|
+
if project_dir:
|
|
642
|
+
os.chdir(project_dir)
|
|
643
|
+
typer.echo(f"Working directory: {project_dir}", err=True)
|
|
644
|
+
|
|
645
|
+
# Run the MCP server (stdio mode)
|
|
646
|
+
# Note: We don't print anything to stdout as MCP uses it for communication
|
|
647
|
+
try:
|
|
648
|
+
asyncio.run(run_server())
|
|
649
|
+
except KeyboardInterrupt:
|
|
650
|
+
# Clean exit on Ctrl+C
|
|
651
|
+
raise typer.Exit(0) from None
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
# =============================================================================
|
|
655
|
+
# Root commands — registered in alphabetical order within each panel so that
|
|
656
|
+
# commands appear A–Z inside Dashboards, Data & SQL, AI, and Reference.
|
|
657
|
+
# Panel order (Dashboards -> Data & SQL -> AI -> Reference) is determined by
|
|
658
|
+
# which panel's first command is registered first — so panel blocks must stay
|
|
659
|
+
# in that order. Sub-typer add_typer() calls are inlined at their correct
|
|
660
|
+
# alphabetical positions within each panel's block.
|
|
661
|
+
# =============================================================================
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
# --- Dashboards -------------------------------------------------------------
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
@app.command("describe", rich_help_panel="Dashboards")
|
|
668
|
+
def describe(
|
|
669
|
+
paths: Annotated[
|
|
670
|
+
list[Path],
|
|
671
|
+
typer.Argument(
|
|
672
|
+
metavar="[PATH]...",
|
|
673
|
+
help="Path(s) to face YAML files or directories to describe.",
|
|
674
|
+
),
|
|
675
|
+
],
|
|
676
|
+
json_output: Annotated[
|
|
677
|
+
bool,
|
|
678
|
+
typer.Option("--json", help="Output as JSON"),
|
|
679
|
+
] = False,
|
|
680
|
+
project_dir: ProjectDirOption = None,
|
|
681
|
+
) -> None:
|
|
682
|
+
"""Describe a dashboard's queries, charts, variables, and layout.
|
|
683
|
+
|
|
684
|
+
Compiles the face YAML and returns a structured summary without executing
|
|
685
|
+
any queries. Use this for orientation when picking up an unfamiliar dashboard.
|
|
686
|
+
Accepts multiple paths; each may be a file or a directory (walked recursively).
|
|
687
|
+
|
|
688
|
+
\b
|
|
689
|
+
Examples:
|
|
690
|
+
dft describe faces/sales.yml
|
|
691
|
+
dft describe faces/sales.yml --json
|
|
692
|
+
dft describe faces/sales.yml --project-dir ./myrepo
|
|
693
|
+
dft describe faces/
|
|
694
|
+
dft describe faces/*.yml --json | jq '.[] | select(.charts | length > 5)'
|
|
695
|
+
"""
|
|
696
|
+
describe_cmd.describe_command(
|
|
697
|
+
paths,
|
|
698
|
+
json_output=json_output,
|
|
699
|
+
project_dir=project_dir,
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
@app.command("render", rich_help_panel="Dashboards")
|
|
704
|
+
def render(
|
|
705
|
+
face: Annotated[
|
|
706
|
+
Path,
|
|
707
|
+
typer.Argument(
|
|
708
|
+
# No exists=/file_okay=: "-" is a valid stdin sentinel and must not fail the check.
|
|
709
|
+
help='Path to face YAML file, or "-" to read YAML from stdin',
|
|
710
|
+
),
|
|
711
|
+
],
|
|
712
|
+
output: Annotated[
|
|
713
|
+
Path | None,
|
|
714
|
+
typer.Option(help="Output file path (default: face name with extension)"),
|
|
715
|
+
] = None,
|
|
716
|
+
format: Annotated[
|
|
717
|
+
RenderFormat,
|
|
718
|
+
typer.Option(
|
|
719
|
+
help="Output format: svg, html, png, pdf, terminal, json, text, or yaml"
|
|
720
|
+
),
|
|
721
|
+
] = "svg",
|
|
722
|
+
project_dir: ProjectDirOption = None,
|
|
723
|
+
var: Annotated[
|
|
724
|
+
list[str] | None,
|
|
725
|
+
typer.Option(help="Variable value as key=value (repeatable)"),
|
|
726
|
+
] = None,
|
|
727
|
+
no_cache: Annotated[
|
|
728
|
+
bool,
|
|
729
|
+
typer.Option(
|
|
730
|
+
"--no-cache", help="Bypass all query caches and re-run from scratch"
|
|
731
|
+
),
|
|
732
|
+
] = False,
|
|
733
|
+
json_errors: Annotated[
|
|
734
|
+
bool,
|
|
735
|
+
typer.Option(
|
|
736
|
+
"--json-errors",
|
|
737
|
+
help=(
|
|
738
|
+
"Emit errors as JSON to stdout instead of formatted panels. "
|
|
739
|
+
"Only affects the error path; the success-path output is "
|
|
740
|
+
"still controlled by --format."
|
|
741
|
+
),
|
|
742
|
+
),
|
|
743
|
+
] = False,
|
|
744
|
+
no_warnings: Annotated[
|
|
745
|
+
bool,
|
|
746
|
+
typer.Option(
|
|
747
|
+
"--no-warnings",
|
|
748
|
+
help=(
|
|
749
|
+
"Suppress warning output to stderr. Warnings are still included "
|
|
750
|
+
"in --format json output so agents and consumers always see them."
|
|
751
|
+
),
|
|
752
|
+
),
|
|
753
|
+
] = False,
|
|
754
|
+
ignore_warning: Annotated[
|
|
755
|
+
list[str] | None,
|
|
756
|
+
typer.Option(
|
|
757
|
+
"--ignore-warning",
|
|
758
|
+
help=(
|
|
759
|
+
"Suppress a specific warning code (repeatable). Suppressed warnings "
|
|
760
|
+
"move to suppressed_warnings in --format json output. "
|
|
761
|
+
"Unknown codes print a notice but do not exit non-zero."
|
|
762
|
+
),
|
|
763
|
+
),
|
|
764
|
+
] = None,
|
|
765
|
+
) -> None:
|
|
766
|
+
"""Render a dashboard to SVG, HTML, PNG, PDF, or terminal.
|
|
767
|
+
|
|
768
|
+
Use "-" as the face argument to read YAML from stdin (useful for agents
|
|
769
|
+
and shell pipelines).
|
|
770
|
+
|
|
771
|
+
\b
|
|
772
|
+
Examples:
|
|
773
|
+
dft render faces/sales.yml
|
|
774
|
+
dft render faces/sales.yml --format html
|
|
775
|
+
dft render faces/sales.yml --var region=West --var category=Electronics
|
|
776
|
+
dft render faces/sales.yml --format terminal
|
|
777
|
+
dft render faces/sales.yml --no-cache
|
|
778
|
+
dft render faces/sales.yml --json-errors
|
|
779
|
+
dft render faces/sales.yml --no-warnings
|
|
780
|
+
dft render faces/sales.yml --ignore-warning BAR_COLOR_1_TO_1_WITH_X
|
|
781
|
+
echo 'charts: {c: {query: {type: csv, file: data.csv}}}' | dft render - --format terminal
|
|
782
|
+
"""
|
|
783
|
+
variables = parse_kv_pairs(var or [], "--var")
|
|
784
|
+
|
|
785
|
+
use_cache = not no_cache
|
|
786
|
+
ignore_codes = set(ignore_warning) if ignore_warning else None
|
|
787
|
+
|
|
788
|
+
if str(face) == "-":
|
|
789
|
+
yaml_content = sys.stdin.read()
|
|
790
|
+
if not yaml_content.strip():
|
|
791
|
+
print("Error: No YAML input received from stdin", file=sys.stderr)
|
|
792
|
+
raise typer.Exit(1)
|
|
793
|
+
render_cmd.render_command_from_yaml(
|
|
794
|
+
yaml_content=yaml_content,
|
|
795
|
+
output=str(output) if output else None,
|
|
796
|
+
format=format,
|
|
797
|
+
project_dir=project_dir,
|
|
798
|
+
variables=variables or None,
|
|
799
|
+
use_cache=use_cache,
|
|
800
|
+
json_errors=json_errors,
|
|
801
|
+
no_warnings=no_warnings,
|
|
802
|
+
ignore_codes=ignore_codes,
|
|
803
|
+
)
|
|
804
|
+
else:
|
|
805
|
+
render_cmd.render_command(
|
|
806
|
+
face_path=face,
|
|
807
|
+
output=str(output) if output else None,
|
|
808
|
+
format=format,
|
|
809
|
+
project_dir=project_dir,
|
|
810
|
+
variables=variables or None,
|
|
811
|
+
use_cache=use_cache,
|
|
812
|
+
json_errors=json_errors,
|
|
813
|
+
no_warnings=no_warnings,
|
|
814
|
+
ignore_codes=ignore_codes,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
@app.command("search", rich_help_panel="Dashboards")
|
|
819
|
+
def search(
|
|
820
|
+
query: Annotated[
|
|
821
|
+
str,
|
|
822
|
+
typer.Argument(help="Keywords to match against dashboard metadata"),
|
|
823
|
+
],
|
|
824
|
+
json_output: Annotated[
|
|
825
|
+
bool,
|
|
826
|
+
typer.Option("--json", help="Output as JSON"),
|
|
827
|
+
] = False,
|
|
828
|
+
limit: Annotated[
|
|
829
|
+
int,
|
|
830
|
+
typer.Option("--limit", help="Maximum results to return (default 10, max 50)"),
|
|
831
|
+
] = 10,
|
|
832
|
+
project_dir: ProjectDirOption = None,
|
|
833
|
+
) -> None:
|
|
834
|
+
"""Search dashboards by keyword with ranked results.
|
|
835
|
+
|
|
836
|
+
Returns dashboards ranked by relevance to the query. Each hit includes
|
|
837
|
+
the title, summary, match score, source path, and matched query/chart names.
|
|
838
|
+
|
|
839
|
+
\b
|
|
840
|
+
Examples:
|
|
841
|
+
dft search revenue
|
|
842
|
+
dft search "monthly trends" --limit 5
|
|
843
|
+
dft search orders --json
|
|
844
|
+
"""
|
|
845
|
+
search_cmd.search_command(
|
|
846
|
+
query=query,
|
|
847
|
+
json_output=json_output,
|
|
848
|
+
limit=limit,
|
|
849
|
+
project_dir=project_dir,
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
@app.command("serve", rich_help_panel="Dashboards")
|
|
854
|
+
def serve(
|
|
855
|
+
port: Annotated[
|
|
856
|
+
int | None,
|
|
857
|
+
typer.Option(help="Port number (auto-resolved if not set)"),
|
|
858
|
+
] = None,
|
|
859
|
+
host: Annotated[
|
|
860
|
+
str,
|
|
861
|
+
typer.Option(help="Host address"),
|
|
862
|
+
] = "localhost",
|
|
863
|
+
project_dir: ProjectDirOption = None,
|
|
864
|
+
connection: Annotated[
|
|
865
|
+
str | None,
|
|
866
|
+
typer.Option(help="Database connection string"),
|
|
867
|
+
] = None,
|
|
868
|
+
dialect: Annotated[
|
|
869
|
+
str | None,
|
|
870
|
+
typer.Option(help="SQL dialect (auto-detected from dbt, or duckdb)"),
|
|
871
|
+
] = None,
|
|
872
|
+
target: Annotated[
|
|
873
|
+
str | None,
|
|
874
|
+
typer.Option(
|
|
875
|
+
help="dbt target name (default: DBT_TARGET env, then profile default)"
|
|
876
|
+
),
|
|
877
|
+
] = None,
|
|
878
|
+
) -> None:
|
|
879
|
+
"""Start the dashboard server.
|
|
880
|
+
|
|
881
|
+
Renders your dashboards in the browser. Face file paths map
|
|
882
|
+
to URLs (faces/sales.yml → /sales/). Query params become variables.
|
|
883
|
+
|
|
884
|
+
Auto-discovers the project root by walking up from the current directory
|
|
885
|
+
to find dataface.yml or dbt_project.yml. When a dbt project is found,
|
|
886
|
+
the SQL dialect is inferred from the profile target.
|
|
887
|
+
|
|
888
|
+
Port is auto-resolved: --port flag > DFT_PORT env var > dataface.yml
|
|
889
|
+
port field > deterministic hash of project directory. If the chosen port
|
|
890
|
+
is occupied, the next available port is used automatically.
|
|
891
|
+
|
|
892
|
+
\b
|
|
893
|
+
Examples:
|
|
894
|
+
dft serve
|
|
895
|
+
dft serve --port 3000
|
|
896
|
+
dft serve --connection ./data.db --dialect duckdb
|
|
897
|
+
dft serve --target prod
|
|
898
|
+
"""
|
|
899
|
+
serve_cmd.serve_command(
|
|
900
|
+
port=port,
|
|
901
|
+
host=host,
|
|
902
|
+
project_dir=project_dir,
|
|
903
|
+
connection=connection,
|
|
904
|
+
dialect=dialect,
|
|
905
|
+
target=target,
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
@app.command("validate", rich_help_panel="Dashboards")
|
|
910
|
+
def validate(
|
|
911
|
+
paths: Annotated[
|
|
912
|
+
list[Path] | None,
|
|
913
|
+
typer.Argument(
|
|
914
|
+
metavar="[PATH]...",
|
|
915
|
+
help="Face YAML files or directories (default: faces/)",
|
|
916
|
+
),
|
|
917
|
+
] = None,
|
|
918
|
+
project_dir: ProjectDirOption = None,
|
|
919
|
+
json_output: Annotated[
|
|
920
|
+
bool,
|
|
921
|
+
typer.Option("--json", help="Output as JSON"),
|
|
922
|
+
] = False,
|
|
923
|
+
strict: Annotated[
|
|
924
|
+
bool,
|
|
925
|
+
typer.Option(help="Errors always fail; with --strict, warnings also fail."),
|
|
926
|
+
] = False,
|
|
927
|
+
) -> None:
|
|
928
|
+
"""Fast YAML schema + cross-reference validation. No DB, no execute.
|
|
929
|
+
|
|
930
|
+
\b
|
|
931
|
+
Examples:
|
|
932
|
+
dft validate # Validate all faces in faces/
|
|
933
|
+
dft validate faces/ # Validate all faces in a directory
|
|
934
|
+
dft validate faces/sales.yml # Validate one file
|
|
935
|
+
dft validate faces/*.yml # Shell-expanded glob
|
|
936
|
+
dft validate faces/*.yml --json | jq '.[] | select(.success == false)'
|
|
937
|
+
dft validate faces/sales.yml faces/orders.yml --strict
|
|
938
|
+
"""
|
|
939
|
+
validate_cmd.validate_command(
|
|
940
|
+
paths=paths,
|
|
941
|
+
project_dir=project_dir,
|
|
942
|
+
json_output=json_output,
|
|
943
|
+
strict=strict,
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
# --- Data & SQL -------------------------------------------------------------
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
@app.command(
|
|
951
|
+
"query",
|
|
952
|
+
rich_help_panel="Data & SQL",
|
|
953
|
+
help="""\b
|
|
954
|
+
SQL and named face queries — common forms:
|
|
955
|
+
dft query NAME --in FACE.yml
|
|
956
|
+
dft query 'SQL' --source SRC
|
|
957
|
+
dft query 'SQL' --validate
|
|
958
|
+
dft query 'SQL' --describe --source SRC
|
|
959
|
+
|
|
960
|
+
TARGET is a query name when --in is set; otherwise it's a SQL string.
|
|
961
|
+
--file reads SQL from a file. SQL execute requires --source.
|
|
962
|
+
--validate runs offline.""",
|
|
963
|
+
)
|
|
964
|
+
def query(
|
|
965
|
+
target: Annotated[
|
|
966
|
+
str | None,
|
|
967
|
+
typer.Argument(
|
|
968
|
+
metavar="TARGET",
|
|
969
|
+
help="Query name (with --in) or SQL string",
|
|
970
|
+
),
|
|
971
|
+
] = None,
|
|
972
|
+
face: Annotated[
|
|
973
|
+
Path | None,
|
|
974
|
+
typer.Option(
|
|
975
|
+
"--in",
|
|
976
|
+
"-i",
|
|
977
|
+
metavar="PATH",
|
|
978
|
+
help="Face YAML path; switches TARGET to named-query mode",
|
|
979
|
+
),
|
|
980
|
+
] = None,
|
|
981
|
+
source: Annotated[
|
|
982
|
+
str | None,
|
|
983
|
+
typer.Option(
|
|
984
|
+
"--source",
|
|
985
|
+
help="Data source (required for raw SQL execute; overrides face source for --describe)",
|
|
986
|
+
),
|
|
987
|
+
] = None,
|
|
988
|
+
validate: Annotated[
|
|
989
|
+
bool,
|
|
990
|
+
typer.Option(
|
|
991
|
+
"--validate",
|
|
992
|
+
help="Static SQL lint only (works for named queries and raw SQL)",
|
|
993
|
+
),
|
|
994
|
+
] = False,
|
|
995
|
+
describe: Annotated[
|
|
996
|
+
bool,
|
|
997
|
+
typer.Option(
|
|
998
|
+
"--describe",
|
|
999
|
+
help="Return column names and types (runs --validate gate first)",
|
|
1000
|
+
),
|
|
1001
|
+
] = False,
|
|
1002
|
+
file: Annotated[
|
|
1003
|
+
Path | None,
|
|
1004
|
+
typer.Option("--file", "-f", metavar="PATH", help="Read SQL from file"),
|
|
1005
|
+
] = None,
|
|
1006
|
+
dialect: Annotated[
|
|
1007
|
+
str | None,
|
|
1008
|
+
typer.Option("--dialect", help="SQL dialect for lint (duckdb, bigquery, …)"),
|
|
1009
|
+
] = None,
|
|
1010
|
+
var: Annotated[
|
|
1011
|
+
list[str] | None,
|
|
1012
|
+
typer.Option("--var", help="Variable override as key=value (repeatable)"),
|
|
1013
|
+
] = None,
|
|
1014
|
+
limit: Annotated[
|
|
1015
|
+
int,
|
|
1016
|
+
typer.Option("--limit", help="Max rows, execute path (default 20, max 1000)"),
|
|
1017
|
+
] = 20,
|
|
1018
|
+
show_suppressed: Annotated[
|
|
1019
|
+
bool,
|
|
1020
|
+
typer.Option("--show-suppressed", help="Include suppressed lint diagnostics"),
|
|
1021
|
+
] = False,
|
|
1022
|
+
json_output: Annotated[
|
|
1023
|
+
bool,
|
|
1024
|
+
typer.Option("--json", help="JSON output"),
|
|
1025
|
+
] = False,
|
|
1026
|
+
project_dir: ProjectDirOption = None,
|
|
1027
|
+
) -> None:
|
|
1028
|
+
variables = parse_kv_pairs(var or [], "--var")
|
|
1029
|
+
|
|
1030
|
+
query_cmd.query_command(
|
|
1031
|
+
target=target,
|
|
1032
|
+
face=face,
|
|
1033
|
+
source=source,
|
|
1034
|
+
validate=validate,
|
|
1035
|
+
describe=describe,
|
|
1036
|
+
file=file,
|
|
1037
|
+
dialect=dialect,
|
|
1038
|
+
vars=variables or None,
|
|
1039
|
+
limit=limit,
|
|
1040
|
+
show_suppressed=show_suppressed,
|
|
1041
|
+
json_output=json_output,
|
|
1042
|
+
project_dir=project_dir,
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
_SCHEMA_SEARCH_PANEL = "Search"
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
@app.command("schema", rich_help_panel="Data & SQL")
|
|
1050
|
+
def schema(
|
|
1051
|
+
source: Annotated[
|
|
1052
|
+
str | None,
|
|
1053
|
+
typer.Argument(
|
|
1054
|
+
metavar="SOURCE",
|
|
1055
|
+
help="Data source name (omit to list all sources).",
|
|
1056
|
+
),
|
|
1057
|
+
] = None,
|
|
1058
|
+
schema_name: Annotated[
|
|
1059
|
+
str | None,
|
|
1060
|
+
typer.Argument(
|
|
1061
|
+
metavar="SCHEMA",
|
|
1062
|
+
help="Schema/namespace name (requires SOURCE).",
|
|
1063
|
+
),
|
|
1064
|
+
] = None,
|
|
1065
|
+
table: Annotated[
|
|
1066
|
+
str | None,
|
|
1067
|
+
typer.Argument(
|
|
1068
|
+
metavar="TABLE",
|
|
1069
|
+
help="Table name for deep profiling (requires SOURCE SCHEMA).",
|
|
1070
|
+
),
|
|
1071
|
+
] = None,
|
|
1072
|
+
column: Annotated[
|
|
1073
|
+
str | None,
|
|
1074
|
+
typer.Argument(
|
|
1075
|
+
metavar="COLUMN",
|
|
1076
|
+
help="Column name for distribution detail (requires SOURCE SCHEMA TABLE).",
|
|
1077
|
+
),
|
|
1078
|
+
] = None,
|
|
1079
|
+
search: Annotated[
|
|
1080
|
+
str | None,
|
|
1081
|
+
typer.Option(
|
|
1082
|
+
"-s",
|
|
1083
|
+
"--search",
|
|
1084
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1085
|
+
help=(
|
|
1086
|
+
"Keyword/regexp — filters the table list when given with "
|
|
1087
|
+
"SOURCE + SCHEMA, otherwise searches the schema corpus "
|
|
1088
|
+
"(see Modes above for examples)."
|
|
1089
|
+
),
|
|
1090
|
+
),
|
|
1091
|
+
] = None,
|
|
1092
|
+
scope: Annotated[
|
|
1093
|
+
str | None,
|
|
1094
|
+
typer.Option(
|
|
1095
|
+
"--scope",
|
|
1096
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1097
|
+
help=(
|
|
1098
|
+
"Comma-list of fields to search "
|
|
1099
|
+
"(name, description, tag, tests, meta, owner). Default: all."
|
|
1100
|
+
),
|
|
1101
|
+
),
|
|
1102
|
+
] = None,
|
|
1103
|
+
regex: Annotated[
|
|
1104
|
+
bool,
|
|
1105
|
+
typer.Option(
|
|
1106
|
+
"--regex",
|
|
1107
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1108
|
+
help="Treat -s keyword as a regular expression.",
|
|
1109
|
+
),
|
|
1110
|
+
] = False,
|
|
1111
|
+
role: Annotated[
|
|
1112
|
+
str | None,
|
|
1113
|
+
typer.Option(
|
|
1114
|
+
"--role",
|
|
1115
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1116
|
+
help="Filter columns by profiled role (e.g. identifier, measure, time).",
|
|
1117
|
+
),
|
|
1118
|
+
] = None,
|
|
1119
|
+
tag: Annotated[
|
|
1120
|
+
str | None,
|
|
1121
|
+
typer.Option(
|
|
1122
|
+
"--tag",
|
|
1123
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1124
|
+
help="Filter to rows tagged with this value.",
|
|
1125
|
+
),
|
|
1126
|
+
] = None,
|
|
1127
|
+
has_test: Annotated[
|
|
1128
|
+
str | None,
|
|
1129
|
+
typer.Option(
|
|
1130
|
+
"--has-test",
|
|
1131
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1132
|
+
help="Filter columns whose dbt tests include this name (e.g. not_null).",
|
|
1133
|
+
),
|
|
1134
|
+
] = None,
|
|
1135
|
+
missing: Annotated[
|
|
1136
|
+
str | None,
|
|
1137
|
+
typer.Option(
|
|
1138
|
+
"--missing",
|
|
1139
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1140
|
+
help="Include rows that are missing this field (e.g. description).",
|
|
1141
|
+
),
|
|
1142
|
+
] = None,
|
|
1143
|
+
column_name: Annotated[
|
|
1144
|
+
str | None,
|
|
1145
|
+
typer.Option(
|
|
1146
|
+
"--column-name",
|
|
1147
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1148
|
+
help="Filter to columns matching this glob (e.g. `*_id`).",
|
|
1149
|
+
),
|
|
1150
|
+
] = None,
|
|
1151
|
+
table_name: Annotated[
|
|
1152
|
+
str | None,
|
|
1153
|
+
typer.Option(
|
|
1154
|
+
"--table-name",
|
|
1155
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1156
|
+
help="Filter to tables matching this glob (e.g. `stg_*`).",
|
|
1157
|
+
),
|
|
1158
|
+
] = None,
|
|
1159
|
+
fk_to: Annotated[
|
|
1160
|
+
str | None,
|
|
1161
|
+
typer.Option(
|
|
1162
|
+
"--fk-to",
|
|
1163
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1164
|
+
help="Filter to columns with relationships pointing at this table.",
|
|
1165
|
+
),
|
|
1166
|
+
] = None,
|
|
1167
|
+
meta: Annotated[
|
|
1168
|
+
list[str] | None,
|
|
1169
|
+
typer.Option(
|
|
1170
|
+
"--meta",
|
|
1171
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1172
|
+
help="Filter by exact-match meta key=value (repeatable).",
|
|
1173
|
+
),
|
|
1174
|
+
] = None,
|
|
1175
|
+
fields: Annotated[
|
|
1176
|
+
str | None,
|
|
1177
|
+
typer.Option(
|
|
1178
|
+
"--fields",
|
|
1179
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1180
|
+
help=(
|
|
1181
|
+
"Show only these fields on each hit "
|
|
1182
|
+
"(comma-list, e.g. `location,matched_field`)."
|
|
1183
|
+
),
|
|
1184
|
+
),
|
|
1185
|
+
] = None,
|
|
1186
|
+
limit: Annotated[
|
|
1187
|
+
int | None,
|
|
1188
|
+
typer.Option(
|
|
1189
|
+
"--limit",
|
|
1190
|
+
rich_help_panel=_SCHEMA_SEARCH_PANEL,
|
|
1191
|
+
help="Truncate to N hits (total/truncated reported).",
|
|
1192
|
+
),
|
|
1193
|
+
] = None,
|
|
1194
|
+
json_output: Annotated[
|
|
1195
|
+
bool,
|
|
1196
|
+
typer.Option(
|
|
1197
|
+
"--json",
|
|
1198
|
+
help=(
|
|
1199
|
+
"Output as JSON. "
|
|
1200
|
+
"Envelope: sources.<SRC>.schemas.<SCH>.tables.<TBL>.columns.<COL>."
|
|
1201
|
+
),
|
|
1202
|
+
),
|
|
1203
|
+
] = False,
|
|
1204
|
+
project_dir: ProjectDirOption = None,
|
|
1205
|
+
lineage_depth: Annotated[
|
|
1206
|
+
int,
|
|
1207
|
+
typer.Option(
|
|
1208
|
+
"--lineage-depth",
|
|
1209
|
+
min=1,
|
|
1210
|
+
max=10,
|
|
1211
|
+
help="Hops of upstream/downstream lineage to surface (1–10, default 1)",
|
|
1212
|
+
),
|
|
1213
|
+
] = 1,
|
|
1214
|
+
) -> None:
|
|
1215
|
+
"""Browse the data hierarchy or search the schema corpus.
|
|
1216
|
+
|
|
1217
|
+
Modes
|
|
1218
|
+
-----
|
|
1219
|
+
|
|
1220
|
+
Drill-down (default) — deepest non-None arg determines the return tier:
|
|
1221
|
+
|
|
1222
|
+
\b
|
|
1223
|
+
dft schema # list configured data sources
|
|
1224
|
+
dft schema mydb # list schemas in that source
|
|
1225
|
+
dft schema mydb main # list tables
|
|
1226
|
+
dft schema mydb main orders # profile table
|
|
1227
|
+
dft schema mydb main orders id # profile column
|
|
1228
|
+
|
|
1229
|
+
Search mode (-s / --search) — full-text + filter across the schema corpus:
|
|
1230
|
+
|
|
1231
|
+
\b
|
|
1232
|
+
dft schema -s mrr
|
|
1233
|
+
dft schema -s "" --role primary_key # filter-only (empty keyword)
|
|
1234
|
+
dft schema -s customer --column-name "*_id" --json
|
|
1235
|
+
|
|
1236
|
+
Level-3 regexp filter (-s with SOURCE + SCHEMA) — filter the table list
|
|
1237
|
+
by regexp without leaving drill-down mode:
|
|
1238
|
+
|
|
1239
|
+
\b
|
|
1240
|
+
dft schema wh analytics -s 'revenue|arr'
|
|
1241
|
+
dft schema wh analytics -s 'revenue|arr' --json
|
|
1242
|
+
dft schema -s ".*_at$" --regex --json --fields location,matched_field
|
|
1243
|
+
dft schema -s "" --missing description --table-name "stg_*"
|
|
1244
|
+
|
|
1245
|
+
Use --json for stable JSON output suitable for agent consumption.
|
|
1246
|
+
|
|
1247
|
+
Piping (drill-down) — escape hatch for ad-hoc cross-cutting queries:
|
|
1248
|
+
|
|
1249
|
+
\b
|
|
1250
|
+
# Tables with more than 1000 rows:
|
|
1251
|
+
dft schema wh analytics '*' --json | jq '.sources.wh.schemas.analytics.tables | to_entries[] | select(.value.row_count > 1000) | .key'
|
|
1252
|
+
|
|
1253
|
+
\b
|
|
1254
|
+
# Every column whose name ends in _at:
|
|
1255
|
+
dft schema wh analytics '*' '*' --json | jq '[.sources.wh.schemas.analytics.tables | to_entries[] | {table: .key, cols: [.value.columns | to_entries[] | select(.key | test("_at$")) | .key]}] | map(select(.cols | length > 0))'
|
|
1256
|
+
|
|
1257
|
+
Always quote glob args to prevent shell expansion: TABLE '*', TABLE 'stg_*'.
|
|
1258
|
+
"""
|
|
1259
|
+
schema_cmd.schema_command(
|
|
1260
|
+
source=source,
|
|
1261
|
+
schema=schema_name,
|
|
1262
|
+
table=table,
|
|
1263
|
+
column=column,
|
|
1264
|
+
json_output=json_output,
|
|
1265
|
+
project_dir=project_dir,
|
|
1266
|
+
lineage_depth=lineage_depth,
|
|
1267
|
+
search=search,
|
|
1268
|
+
scope=scope,
|
|
1269
|
+
regex=regex,
|
|
1270
|
+
role=role,
|
|
1271
|
+
tag=tag,
|
|
1272
|
+
has_test=has_test,
|
|
1273
|
+
missing=missing,
|
|
1274
|
+
column_name=column_name,
|
|
1275
|
+
table_name=table_name,
|
|
1276
|
+
fk_to=fk_to,
|
|
1277
|
+
meta=meta,
|
|
1278
|
+
fields=fields,
|
|
1279
|
+
limit=limit,
|
|
1280
|
+
)
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
# --- AI ---------------------------------------------------------------------
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
@app.command("chat", rich_help_panel="AI")
|
|
1287
|
+
def chat(
|
|
1288
|
+
prompt: Annotated[
|
|
1289
|
+
str | None,
|
|
1290
|
+
typer.Argument(help="Optional one-shot prompt"),
|
|
1291
|
+
] = None,
|
|
1292
|
+
model: Annotated[
|
|
1293
|
+
str | None,
|
|
1294
|
+
typer.Option("--model", help="Model name, optionally provider-prefixed"),
|
|
1295
|
+
] = None,
|
|
1296
|
+
continue_session: Annotated[
|
|
1297
|
+
bool,
|
|
1298
|
+
typer.Option(
|
|
1299
|
+
"-c",
|
|
1300
|
+
"--continue",
|
|
1301
|
+
help="Resume the most recent session for the current directory",
|
|
1302
|
+
),
|
|
1303
|
+
] = False,
|
|
1304
|
+
resume: Annotated[
|
|
1305
|
+
str | None,
|
|
1306
|
+
typer.Option(
|
|
1307
|
+
"-r",
|
|
1308
|
+
"--resume",
|
|
1309
|
+
help="Resume a specific session by id (see --pick for the interactive list)",
|
|
1310
|
+
),
|
|
1311
|
+
] = None,
|
|
1312
|
+
pick: Annotated[
|
|
1313
|
+
bool,
|
|
1314
|
+
typer.Option(
|
|
1315
|
+
"-p",
|
|
1316
|
+
"--pick",
|
|
1317
|
+
help="Open an interactive picker to choose a session to resume",
|
|
1318
|
+
),
|
|
1319
|
+
] = False,
|
|
1320
|
+
) -> None:
|
|
1321
|
+
"""Chat with a terminal AI agent.
|
|
1322
|
+
|
|
1323
|
+
Sessions are auto-saved to ~/.dft/sessions/ and indexed by working directory.
|
|
1324
|
+
|
|
1325
|
+
\b
|
|
1326
|
+
Examples:
|
|
1327
|
+
dft chat # Start a fresh session
|
|
1328
|
+
dft chat "What tables do I have?" # One-shot prompt
|
|
1329
|
+
dft chat -c # Resume last session for this directory
|
|
1330
|
+
dft chat -r <session-id> # Resume a specific session by id
|
|
1331
|
+
dft chat -p # Pick from a list of recent sessions
|
|
1332
|
+
"""
|
|
1333
|
+
chat_cmd.chat_command(
|
|
1334
|
+
prompt=prompt,
|
|
1335
|
+
model=model,
|
|
1336
|
+
continue_session=continue_session,
|
|
1337
|
+
resume=resume,
|
|
1338
|
+
pick=pick,
|
|
1339
|
+
)
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
app.add_typer(mcp_app, name="mcp", rich_help_panel="AI")
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
# --- Reference --------------------------------------------------------------
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
@app.command("docs", rich_help_panel="Reference")
|
|
1349
|
+
def docs(
|
|
1350
|
+
topic: Annotated[
|
|
1351
|
+
str | None,
|
|
1352
|
+
typer.Argument(
|
|
1353
|
+
help="Topic name (run `dft docs` for the index). Use 'all' for the whole reference or 'reference' for the YAML field reference."
|
|
1354
|
+
),
|
|
1355
|
+
] = None,
|
|
1356
|
+
search: Annotated[
|
|
1357
|
+
str | None,
|
|
1358
|
+
typer.Option("--search", "-s", help="Full-text query across all topics"),
|
|
1359
|
+
] = None,
|
|
1360
|
+
json_output: Annotated[
|
|
1361
|
+
bool,
|
|
1362
|
+
typer.Option("--json", help="Output as JSON"),
|
|
1363
|
+
] = False,
|
|
1364
|
+
limit: Annotated[
|
|
1365
|
+
int,
|
|
1366
|
+
typer.Option(
|
|
1367
|
+
"--limit",
|
|
1368
|
+
min=1,
|
|
1369
|
+
max=50,
|
|
1370
|
+
help="Max search hits to return (default 5, max 50)",
|
|
1371
|
+
),
|
|
1372
|
+
] = 5,
|
|
1373
|
+
) -> None:
|
|
1374
|
+
"""Browse the Dataface YAML reference offline (topics, search).
|
|
1375
|
+
|
|
1376
|
+
\b
|
|
1377
|
+
Modes:
|
|
1378
|
+
dft docs # Topic index (one row per H2 section)
|
|
1379
|
+
dft docs cheatsheet # One-page essentials
|
|
1380
|
+
dft docs <topic> # Full docs for one section
|
|
1381
|
+
dft docs all # Whole reference, unsliced
|
|
1382
|
+
dft docs reference # Generated YAML field reference
|
|
1383
|
+
dft docs --search "grid" # Substring search across all topics
|
|
1384
|
+
|
|
1385
|
+
Use --json for stable, machine-readable output.
|
|
1386
|
+
"""
|
|
1387
|
+
docs_cmd.docs_command(
|
|
1388
|
+
topic=topic,
|
|
1389
|
+
search=search,
|
|
1390
|
+
json_output=json_output,
|
|
1391
|
+
limit=limit,
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
app.add_typer(init_app, name="init", rich_help_panel="Reference")
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
# Split "extra not installed" (use stub) from "extra installed but broken"
|
|
1399
|
+
# (let the ImportError propagate) — a blanket except would swallow real
|
|
1400
|
+
# transitive-import failures into the install-hint stub.
|
|
1401
|
+
if importlib.util.find_spec("dataface_playground") is None:
|
|
1402
|
+
|
|
1403
|
+
@app.command("playground", rich_help_panel="Reference")
|
|
1404
|
+
def playground_not_installed() -> None:
|
|
1405
|
+
"""Start interactive playground with YAML editor and live preview.
|
|
1406
|
+
|
|
1407
|
+
Requires the ``playground`` extra.
|
|
1408
|
+
"""
|
|
1409
|
+
from dataface.cli._extras import require_extras
|
|
1410
|
+
|
|
1411
|
+
require_extras("playground")
|
|
1412
|
+
|
|
1413
|
+
else:
|
|
1414
|
+
from dataface_playground.cli import main as _playground_main
|
|
1415
|
+
|
|
1416
|
+
app.command("playground", rich_help_panel="Reference")(_playground_main)
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
@app.command("skills", rich_help_panel="Reference")
|
|
1420
|
+
def skills(
|
|
1421
|
+
skill_name: Annotated[
|
|
1422
|
+
str | None,
|
|
1423
|
+
typer.Argument(help="Skill name to show (default: list all)"),
|
|
1424
|
+
] = None,
|
|
1425
|
+
search: Annotated[
|
|
1426
|
+
str | None,
|
|
1427
|
+
typer.Option("--search", "-s", help="Search names, descriptions, and bodies"),
|
|
1428
|
+
] = None,
|
|
1429
|
+
limit: Annotated[
|
|
1430
|
+
int,
|
|
1431
|
+
typer.Option("--limit", min=1, max=25, help="Max search hits (default 10)"),
|
|
1432
|
+
] = 10,
|
|
1433
|
+
as_json: Annotated[
|
|
1434
|
+
bool,
|
|
1435
|
+
typer.Option("--json", help="Output as JSON"),
|
|
1436
|
+
] = False,
|
|
1437
|
+
) -> None:
|
|
1438
|
+
"""List packaged agent skills, search them, or show one by name.
|
|
1439
|
+
|
|
1440
|
+
\b
|
|
1441
|
+
Modes:
|
|
1442
|
+
dft skills # List all skills (grouped: workflows + patterns)
|
|
1443
|
+
dft skills <name> # Show the named skill body
|
|
1444
|
+
dft skills -s "<query>" # Search names, descriptions, and bodies
|
|
1445
|
+
dft skills --json # Stable JSON for any of the above
|
|
1446
|
+
|
|
1447
|
+
Skills are agent workflows and layout patterns. For YAML field reference,
|
|
1448
|
+
use `dft docs` instead.
|
|
1449
|
+
"""
|
|
1450
|
+
skills_cmd.skills_command(
|
|
1451
|
+
name=skill_name,
|
|
1452
|
+
search=search,
|
|
1453
|
+
limit=limit,
|
|
1454
|
+
as_json=as_json,
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
# Hidden — not shown in root help
|
|
1459
|
+
app.add_typer(inspect_app, name="inspect", hidden=True)
|
|
1460
|
+
|
|
1461
|
+
# Load CLI plugins registered via [project.entry-points."dataface.cli_plugins"].
|
|
1462
|
+
# Each plugin's ``register()`` callable mounts its subcommands onto Typer apps
|
|
1463
|
+
# (e.g. ``inspect_app``) before the app executes. Runs at import time so that
|
|
1464
|
+
# ``dft --help`` shows profiler commands when the private package is installed.
|
|
1465
|
+
# importlib.metadata is stdlib since Python 3.8; dataface requires >=3.10, so
|
|
1466
|
+
# the outer try is unnecessary. Plugin load errors surface to the user rather
|
|
1467
|
+
# than being silently swallowed.
|
|
1468
|
+
from importlib.metadata import entry_points as _entry_points
|
|
1469
|
+
|
|
1470
|
+
for _ep in _entry_points(group="dataface.cli_plugins"):
|
|
1471
|
+
_plugin = _ep.load()
|
|
1472
|
+
_plugin()
|
|
1473
|
+
|
|
1474
|
+
|
|
1475
|
+
def version_callback(value: bool) -> None:
|
|
1476
|
+
"""Print version and exit."""
|
|
1477
|
+
if value:
|
|
1478
|
+
from dataface.cli._version_info import collect
|
|
1479
|
+
|
|
1480
|
+
typer.echo(collect().render())
|
|
1481
|
+
raise typer.Exit()
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
@app.callback()
|
|
1485
|
+
def main(
|
|
1486
|
+
version: Annotated[
|
|
1487
|
+
bool | None,
|
|
1488
|
+
typer.Option(
|
|
1489
|
+
"--version",
|
|
1490
|
+
help="Print version and exit.",
|
|
1491
|
+
callback=version_callback,
|
|
1492
|
+
is_eager=True,
|
|
1493
|
+
),
|
|
1494
|
+
] = None,
|
|
1495
|
+
) -> None:
|
|
1496
|
+
"""dft - Declarative, dbt-native dashboards in YAML."""
|
|
1497
|
+
pass
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
if __name__ == "__main__":
|
|
1501
|
+
app()
|