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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""describe command — thin wrapper over dataface.agent_api.describe."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from dataface.agent_api.describe import DescribeFaceResult
|
|
13
|
+
from dataface.cli._console import dft_console
|
|
14
|
+
from dataface.cli._error_format import print_structured_errors
|
|
15
|
+
|
|
16
|
+
console = dft_console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def describe_command(
|
|
20
|
+
paths: list[Path], *, json_output: bool = False, project_dir: Path | None = None
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Describe the structure of one or more face files."""
|
|
23
|
+
from dataface.agent_api.describe import describe_paths
|
|
24
|
+
|
|
25
|
+
results = describe_paths(paths, project_dir=project_dir)
|
|
26
|
+
_emit(results, json_output=json_output)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _emit(results: list[DescribeFaceResult], *, json_output: bool) -> None:
|
|
30
|
+
if json_output:
|
|
31
|
+
if len(results) == 1:
|
|
32
|
+
typer.echo(results[0].model_dump_json(exclude_none=True, indent=2))
|
|
33
|
+
else:
|
|
34
|
+
typer.echo(
|
|
35
|
+
json.dumps(
|
|
36
|
+
[r.model_dump(mode="json", exclude_none=True) for r in results],
|
|
37
|
+
indent=2,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
has_errors = any(r.errors for r in results)
|
|
41
|
+
raise typer.Exit(0 if not has_errors else 1)
|
|
42
|
+
|
|
43
|
+
has_errors = False
|
|
44
|
+
for r in results:
|
|
45
|
+
if len(results) > 1:
|
|
46
|
+
console.rule(str(r.path))
|
|
47
|
+
if r.errors:
|
|
48
|
+
has_errors = True
|
|
49
|
+
print_structured_errors(r.errors)
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
if r.title:
|
|
53
|
+
console.print(f"\n[bold]{r.title}[/bold]")
|
|
54
|
+
if r.description:
|
|
55
|
+
console.print(f"[dim]{r.description}[/dim]")
|
|
56
|
+
console.print()
|
|
57
|
+
|
|
58
|
+
if r.queries:
|
|
59
|
+
console.print("[bold]Queries[/bold]")
|
|
60
|
+
t: Any = Table(
|
|
61
|
+
show_header=True, header_style="bold", box=None, padding=(0, 2, 0, 0)
|
|
62
|
+
)
|
|
63
|
+
t.add_column("Name")
|
|
64
|
+
t.add_column("Type")
|
|
65
|
+
t.add_column("Summary")
|
|
66
|
+
for q in r.queries:
|
|
67
|
+
t.add_row(q.name, q.type, q.summary)
|
|
68
|
+
console.print(t)
|
|
69
|
+
console.print()
|
|
70
|
+
|
|
71
|
+
if r.charts:
|
|
72
|
+
console.print("[bold]Charts[/bold]")
|
|
73
|
+
t = Table(
|
|
74
|
+
show_header=True, header_style="bold", box=None, padding=(0, 2, 0, 0)
|
|
75
|
+
)
|
|
76
|
+
t.add_column("Name")
|
|
77
|
+
t.add_column("Type")
|
|
78
|
+
t.add_column("Query")
|
|
79
|
+
t.add_column("Encoding")
|
|
80
|
+
for c in r.charts:
|
|
81
|
+
enc = ", ".join(f"{k}={v}" for k, v in c.encoding.items())
|
|
82
|
+
t.add_row(c.name, c.type, c.query, enc)
|
|
83
|
+
console.print(t)
|
|
84
|
+
console.print()
|
|
85
|
+
|
|
86
|
+
if r.variables:
|
|
87
|
+
console.print("[bold]Variables[/bold]")
|
|
88
|
+
t = Table(
|
|
89
|
+
show_header=True, header_style="bold", box=None, padding=(0, 2, 0, 0)
|
|
90
|
+
)
|
|
91
|
+
t.add_column("Name")
|
|
92
|
+
t.add_column("Type")
|
|
93
|
+
t.add_column("Default")
|
|
94
|
+
for v in r.variables:
|
|
95
|
+
t.add_row(
|
|
96
|
+
v.name, v.type, str(v.default) if v.default is not None else ""
|
|
97
|
+
)
|
|
98
|
+
console.print(t)
|
|
99
|
+
console.print()
|
|
100
|
+
|
|
101
|
+
if r.layout:
|
|
102
|
+
console.print("[bold]Layout[/bold]")
|
|
103
|
+
console.print(f" {r.layout.primitive}: {', '.join(r.layout.items)}")
|
|
104
|
+
console.print()
|
|
105
|
+
|
|
106
|
+
if has_errors:
|
|
107
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""CLI command for `dft docs`."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich.markdown import Markdown
|
|
5
|
+
from rich.padding import Padding
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from dataface._docs_site import docs_site_url
|
|
9
|
+
from dataface.agent_api.docs import docs as _docs
|
|
10
|
+
from dataface.cli._console import dft_console, is_plain_output
|
|
11
|
+
from dataface.cli._json_output import print_json_result
|
|
12
|
+
|
|
13
|
+
console = dft_console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def docs_command(
|
|
17
|
+
topic: str | None,
|
|
18
|
+
search: str | None,
|
|
19
|
+
json_output: bool,
|
|
20
|
+
limit: int,
|
|
21
|
+
) -> None:
|
|
22
|
+
result = _docs(topic=topic, search=search, limit=limit)
|
|
23
|
+
|
|
24
|
+
if json_output:
|
|
25
|
+
print_json_result(result)
|
|
26
|
+
if not result.success:
|
|
27
|
+
raise typer.Exit(1)
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
if not result.success:
|
|
31
|
+
for err in result.errors:
|
|
32
|
+
typer.echo(err, err=True)
|
|
33
|
+
for hint in result.hints:
|
|
34
|
+
typer.echo(hint, err=True)
|
|
35
|
+
raise typer.Exit(1)
|
|
36
|
+
|
|
37
|
+
if result.mode == "index":
|
|
38
|
+
_emit_topic_index(result.topics)
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if result.mode == "search":
|
|
42
|
+
if not result.search:
|
|
43
|
+
typer.echo("No results found.")
|
|
44
|
+
return
|
|
45
|
+
for hit in result.search:
|
|
46
|
+
typer.echo(f"{hit.topic} ({hit.score:.2f}) {hit.snippet}")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if result.topic is not None:
|
|
50
|
+
_emit_markdown(result.topic.content)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _emit_topic_index(topics: list) -> None:
|
|
54
|
+
if not topics:
|
|
55
|
+
typer.echo("No topics found.")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
web_docs = f"{docs_site_url()}/cli/docs/"
|
|
59
|
+
if not is_plain_output():
|
|
60
|
+
_emit_topic_index_rich(topics, web_docs)
|
|
61
|
+
else:
|
|
62
|
+
_emit_topic_index_plain(topics, web_docs)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _emit_topic_index_rich(topics: list, web_docs: str) -> None:
|
|
66
|
+
typer.echo(
|
|
67
|
+
"Dataface (`dft`) is a dbt-native dashboard layer. You author dashboards as "
|
|
68
|
+
'YAML "faces" — queries, charts, variables, and layout — and `dft` compiles '
|
|
69
|
+
"and renders them."
|
|
70
|
+
)
|
|
71
|
+
typer.echo("")
|
|
72
|
+
typer.echo(
|
|
73
|
+
"These docs are the offline YAML language reference bundled with `dft`. "
|
|
74
|
+
"Every field here is enforced by the compiler — unknown keys are errors."
|
|
75
|
+
)
|
|
76
|
+
typer.echo("")
|
|
77
|
+
console.print(f"Web docs: [link={web_docs}]{web_docs}[/link]")
|
|
78
|
+
console.print("[dim]Override base URL with DFT_DOCS_URL[/dim]")
|
|
79
|
+
console.print("")
|
|
80
|
+
console.print("[bold]Topics[/bold]")
|
|
81
|
+
console.print(Padding(_topic_table(topics), (0, 0, 0, 2)))
|
|
82
|
+
console.print("")
|
|
83
|
+
typer.echo("Next steps")
|
|
84
|
+
typer.echo(" dft docs <topic> Read one section with full formatting")
|
|
85
|
+
typer.echo(" dft docs cheatsheet One-screen essentials")
|
|
86
|
+
typer.echo(" dft docs all Full reference, unsliced")
|
|
87
|
+
typer.echo(' dft docs -s "<query>" Search across topics')
|
|
88
|
+
typer.echo(" dft skills Agent workflows and layout patterns")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _emit_topic_index_plain(topics: list, web_docs: str) -> None:
|
|
92
|
+
typer.echo(
|
|
93
|
+
"Dataface (`dft`) is a dbt-native dashboard layer. You author dashboards as "
|
|
94
|
+
'YAML "faces" — queries, charts, variables, and layout — and `dft` compiles '
|
|
95
|
+
"and renders them.\n"
|
|
96
|
+
)
|
|
97
|
+
typer.echo(
|
|
98
|
+
"These docs are the offline YAML language reference bundled with `dft`. "
|
|
99
|
+
"Every field here is enforced by the compiler — unknown keys are errors.\n"
|
|
100
|
+
)
|
|
101
|
+
typer.echo(f"Web docs: {web_docs}")
|
|
102
|
+
typer.echo("Override base URL with DFT_DOCS_URL\n")
|
|
103
|
+
typer.echo("Topics")
|
|
104
|
+
width = max(len(entry.id) for entry in topics)
|
|
105
|
+
for entry in topics:
|
|
106
|
+
suffix = f" {entry.description}" if entry.description else ""
|
|
107
|
+
typer.echo(f" {entry.id.ljust(width)}{suffix}")
|
|
108
|
+
typer.echo("")
|
|
109
|
+
typer.echo("Next steps")
|
|
110
|
+
typer.echo(" dft docs <topic> Read one section")
|
|
111
|
+
typer.echo(" dft docs cheatsheet One-screen essentials")
|
|
112
|
+
typer.echo(" dft docs all Full reference, unsliced")
|
|
113
|
+
typer.echo(' dft docs -s "<query>" Search across topics')
|
|
114
|
+
typer.echo(" dft skills Agent workflows and layout patterns")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _topic_table(topics: list) -> Table:
|
|
118
|
+
name_width = max(len(entry.id) for entry in topics)
|
|
119
|
+
table = Table(show_header=False, box=None, padding=(0, 2, 0, 0))
|
|
120
|
+
table.add_column("Topic", style="bold", no_wrap=True, width=name_width)
|
|
121
|
+
table.add_column("Description", overflow="fold")
|
|
122
|
+
for entry in topics:
|
|
123
|
+
table.add_row(entry.id, entry.description)
|
|
124
|
+
return table
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _emit_markdown(content: str) -> None:
|
|
128
|
+
if not is_plain_output():
|
|
129
|
+
console.print(Markdown(content))
|
|
130
|
+
else:
|
|
131
|
+
typer.echo(content, nl=False)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Install the Dataface VS Code / Cursor extension.
|
|
2
|
+
|
|
3
|
+
The extension is currently distributed as a `.vsix` via GitHub Releases on
|
|
4
|
+
the (private) ``fivetran/dataface`` repo, tagged ``vscode-ext-v*``.
|
|
5
|
+
Marketplace publish is gated on M3 launch criteria. ``dft init code`` (and
|
|
6
|
+
``dft init cursor``):
|
|
7
|
+
|
|
8
|
+
1. Uses ``gh`` (the user's authenticated GitHub CLI — required for private
|
|
9
|
+
repo asset downloads) to find the latest ``vscode-ext-v*`` release and
|
|
10
|
+
download its ``.vsix``.
|
|
11
|
+
2. Runs ``code --install-extension <path>`` (or
|
|
12
|
+
``cursor --install-extension <path>``).
|
|
13
|
+
|
|
14
|
+
Once the extension is on the Marketplace, the implementation will switch to
|
|
15
|
+
``code --install-extension dataface.dataface`` (a one-line change) and
|
|
16
|
+
the ``gh`` requirement goes away.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import tempfile
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
REPO = "fivetran/dataface"
|
|
30
|
+
RELEASES_HTML = f"https://github.com/{REPO}/releases"
|
|
31
|
+
TAG_PREFIX = "vscode-ext-v"
|
|
32
|
+
|
|
33
|
+
# Editor name → CLI binary on PATH.
|
|
34
|
+
EDITORS: dict[str, str] = {
|
|
35
|
+
"code": "code",
|
|
36
|
+
"vscode": "code", # alias
|
|
37
|
+
"cursor": "cursor",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class ReleaseAsset:
|
|
43
|
+
tag: str
|
|
44
|
+
version: str
|
|
45
|
+
vsix_name: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _gh_available() -> bool:
|
|
49
|
+
return shutil.which("gh") is not None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _latest_vsix_release() -> ReleaseAsset | None:
|
|
53
|
+
"""Return the most recent vscode-ext-v* release with a .vsix asset.
|
|
54
|
+
|
|
55
|
+
Uses `gh api` for authenticated access (private repo). Returns None if
|
|
56
|
+
`gh` is missing, the user isn't authenticated, or no matching release
|
|
57
|
+
exists.
|
|
58
|
+
"""
|
|
59
|
+
if not _gh_available():
|
|
60
|
+
return None
|
|
61
|
+
try:
|
|
62
|
+
proc = subprocess.run(
|
|
63
|
+
[
|
|
64
|
+
"gh",
|
|
65
|
+
"api",
|
|
66
|
+
f"repos/{REPO}/releases?per_page=20",
|
|
67
|
+
],
|
|
68
|
+
capture_output=True,
|
|
69
|
+
text=True,
|
|
70
|
+
timeout=15,
|
|
71
|
+
)
|
|
72
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
73
|
+
return None
|
|
74
|
+
if proc.returncode != 0:
|
|
75
|
+
return None
|
|
76
|
+
try:
|
|
77
|
+
releases = json.loads(proc.stdout)
|
|
78
|
+
except ValueError:
|
|
79
|
+
return None
|
|
80
|
+
if not isinstance(releases, list):
|
|
81
|
+
return None
|
|
82
|
+
for release in releases:
|
|
83
|
+
tag = release.get("tag_name", "")
|
|
84
|
+
if not tag.startswith(TAG_PREFIX):
|
|
85
|
+
continue
|
|
86
|
+
for asset in release.get("assets") or []:
|
|
87
|
+
name = asset.get("name", "")
|
|
88
|
+
if name.endswith(".vsix"):
|
|
89
|
+
return ReleaseAsset(
|
|
90
|
+
tag=tag,
|
|
91
|
+
version=tag.removeprefix(TAG_PREFIX),
|
|
92
|
+
vsix_name=name,
|
|
93
|
+
)
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def install_extension(editor: str, emit: Callable[[str], None] = print) -> int:
|
|
98
|
+
"""Auto-install the latest VSIX into the named editor.
|
|
99
|
+
|
|
100
|
+
Returns shell-style exit code: 0 on success, non-zero on failure.
|
|
101
|
+
``editor`` is one of ``EDITORS`` keys (``code``, ``vscode``, ``cursor``).
|
|
102
|
+
"""
|
|
103
|
+
cli = EDITORS.get(editor.lower())
|
|
104
|
+
if cli is None:
|
|
105
|
+
emit(f"Unknown editor '{editor}'. Supported: {', '.join(sorted(EDITORS))}.")
|
|
106
|
+
return 2
|
|
107
|
+
|
|
108
|
+
binary = shutil.which(cli)
|
|
109
|
+
if binary is None:
|
|
110
|
+
emit(
|
|
111
|
+
f"'{cli}' not found on PATH — install the editor first, or open\n"
|
|
112
|
+
f"{cli} once and enable its shell command from the command palette."
|
|
113
|
+
)
|
|
114
|
+
return 1
|
|
115
|
+
|
|
116
|
+
if not _gh_available():
|
|
117
|
+
emit(
|
|
118
|
+
"`gh` (GitHub CLI) is required to download the .vsix from the\n"
|
|
119
|
+
f"private {REPO} repo. Install it (https://cli.github.com) and\n"
|
|
120
|
+
"run `gh auth login`, then retry."
|
|
121
|
+
)
|
|
122
|
+
return 1
|
|
123
|
+
|
|
124
|
+
release = _latest_vsix_release()
|
|
125
|
+
if release is None:
|
|
126
|
+
emit(
|
|
127
|
+
f"Could not find a vscode-ext-v* release with a .vsix asset on {REPO}.\n"
|
|
128
|
+
f"Browse {RELEASES_HTML} or build from source via\n"
|
|
129
|
+
"`apps/ide/vscode-extension/justfile`'s `vsix` recipe."
|
|
130
|
+
)
|
|
131
|
+
return 1
|
|
132
|
+
|
|
133
|
+
with tempfile.TemporaryDirectory(prefix="dft-ext-") as tmpdir:
|
|
134
|
+
vsix_path = Path(tmpdir) / release.vsix_name
|
|
135
|
+
emit(f"Downloading {release.vsix_name} from {release.tag}…")
|
|
136
|
+
try:
|
|
137
|
+
dl = subprocess.run(
|
|
138
|
+
[
|
|
139
|
+
"gh",
|
|
140
|
+
"release",
|
|
141
|
+
"download",
|
|
142
|
+
release.tag,
|
|
143
|
+
"--repo",
|
|
144
|
+
REPO,
|
|
145
|
+
"--pattern",
|
|
146
|
+
release.vsix_name,
|
|
147
|
+
"--output",
|
|
148
|
+
str(vsix_path),
|
|
149
|
+
],
|
|
150
|
+
capture_output=True,
|
|
151
|
+
text=True,
|
|
152
|
+
timeout=60,
|
|
153
|
+
)
|
|
154
|
+
except subprocess.TimeoutExpired:
|
|
155
|
+
emit(" gh release download timed out after 60s.")
|
|
156
|
+
return 1
|
|
157
|
+
if dl.returncode != 0:
|
|
158
|
+
for line in (dl.stderr or "").rstrip().splitlines():
|
|
159
|
+
emit(f" {line}")
|
|
160
|
+
return dl.returncode
|
|
161
|
+
emit(f"Installing into {cli}…")
|
|
162
|
+
try:
|
|
163
|
+
result = subprocess.run(
|
|
164
|
+
[binary, "--install-extension", str(vsix_path)],
|
|
165
|
+
capture_output=True,
|
|
166
|
+
text=True,
|
|
167
|
+
timeout=60,
|
|
168
|
+
)
|
|
169
|
+
except subprocess.TimeoutExpired:
|
|
170
|
+
emit(f" {cli} --install-extension timed out after 60s.")
|
|
171
|
+
return 1
|
|
172
|
+
for line in (result.stdout or "").rstrip().splitlines():
|
|
173
|
+
emit(f" {line}")
|
|
174
|
+
if result.returncode != 0:
|
|
175
|
+
for line in (result.stderr or "").rstrip().splitlines():
|
|
176
|
+
emit(f" {line}")
|
|
177
|
+
return result.returncode
|
|
178
|
+
emit(f"✓ Installed Dataface {release.version} in {cli}.")
|
|
179
|
+
return 0
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Init command — wizard that bootstraps a Dataface project."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from dataface.agent_api._project_agents_md import has_dataface_markers
|
|
12
|
+
from dataface.agent_api.init import init_project
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _resolve(
|
|
16
|
+
flag: bool | None,
|
|
17
|
+
yes: bool,
|
|
18
|
+
prompt: str,
|
|
19
|
+
default: bool,
|
|
20
|
+
) -> bool:
|
|
21
|
+
"""Resolve a wizard question: explicit flag > yes/non-TTY default > prompt."""
|
|
22
|
+
if flag is not None:
|
|
23
|
+
return flag
|
|
24
|
+
if yes or not sys.stdin.isatty():
|
|
25
|
+
return default
|
|
26
|
+
return typer.confirm(prompt, default=default)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run_wizard(
|
|
30
|
+
*,
|
|
31
|
+
project_dir: Path | None,
|
|
32
|
+
force: bool,
|
|
33
|
+
yes: bool,
|
|
34
|
+
agents_md: bool | None,
|
|
35
|
+
claude_md: bool | None,
|
|
36
|
+
skills: bool | None,
|
|
37
|
+
mcp: bool | None,
|
|
38
|
+
chat_extra: bool | None,
|
|
39
|
+
with_playground: bool | None,
|
|
40
|
+
vscode: bool | None,
|
|
41
|
+
cursor: bool | None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Run the dft init wizard: prompt, scaffold, dispatch opt-ins."""
|
|
44
|
+
root = (project_dir or Path(".")).resolve()
|
|
45
|
+
|
|
46
|
+
# Detection-aware AGENTS.md prompt
|
|
47
|
+
agents_md_path = root / "AGENTS.md"
|
|
48
|
+
if not agents_md_path.exists():
|
|
49
|
+
agents_md_prompt = (
|
|
50
|
+
"Create AGENTS.md at the project root with Dataface instructions?"
|
|
51
|
+
)
|
|
52
|
+
elif has_dataface_markers(agents_md_path.read_text()):
|
|
53
|
+
agents_md_prompt = "Refresh the Dataface section in AGENTS.md?"
|
|
54
|
+
else:
|
|
55
|
+
agents_md_prompt = "Append Dataface instructions to your existing AGENTS.md?"
|
|
56
|
+
|
|
57
|
+
do_agents_md = _resolve(agents_md, yes=yes, prompt=agents_md_prompt, default=True)
|
|
58
|
+
|
|
59
|
+
# CLAUDE.md — skip prompt entirely if file already exists (never overwrite)
|
|
60
|
+
claude_md_path = root / "CLAUDE.md"
|
|
61
|
+
if claude_md_path.exists():
|
|
62
|
+
do_claude_md = False
|
|
63
|
+
else:
|
|
64
|
+
do_claude_md = _resolve(
|
|
65
|
+
claude_md,
|
|
66
|
+
yes=yes,
|
|
67
|
+
prompt="Create CLAUDE.md (one-line @AGENTS.md pointer for Claude Code)?",
|
|
68
|
+
default=True,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
from dataface.agent_api import skill_install
|
|
72
|
+
|
|
73
|
+
will_have_skill_markers = (
|
|
74
|
+
do_agents_md or do_claude_md or bool(skill_install.detect_skill_targets(root))
|
|
75
|
+
)
|
|
76
|
+
if skills is not None:
|
|
77
|
+
do_skills = skills
|
|
78
|
+
elif will_have_skill_markers:
|
|
79
|
+
do_skills = _resolve(
|
|
80
|
+
None,
|
|
81
|
+
yes=yes,
|
|
82
|
+
prompt="Install Dataface workflow skills for AI assistants?",
|
|
83
|
+
default=True,
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
do_skills = False
|
|
87
|
+
|
|
88
|
+
# MCP
|
|
89
|
+
do_mcp = _resolve(
|
|
90
|
+
mcp,
|
|
91
|
+
yes=yes,
|
|
92
|
+
prompt="Set up the Dataface MCP server for AI assistants?",
|
|
93
|
+
default=True,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Chat extra — skip prompt if already installed
|
|
97
|
+
from dataface.cli._extras import _missing_packages
|
|
98
|
+
|
|
99
|
+
if not _missing_packages("chat"):
|
|
100
|
+
do_chat_extra = False
|
|
101
|
+
else:
|
|
102
|
+
do_chat_extra = _resolve(
|
|
103
|
+
chat_extra,
|
|
104
|
+
yes=yes,
|
|
105
|
+
prompt="Install the dataface[chat] extra now (so 'dft chat' works without prompting later)?",
|
|
106
|
+
default=True,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Playground extra — skip prompt if already installed
|
|
110
|
+
if not _missing_packages("playground"):
|
|
111
|
+
do_with_playground = False
|
|
112
|
+
else:
|
|
113
|
+
do_with_playground = _resolve(
|
|
114
|
+
with_playground,
|
|
115
|
+
yes=yes,
|
|
116
|
+
prompt="Install the dataface[playground] extra now (so 'dft playground' works without prompting later)?",
|
|
117
|
+
default=True,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# IDE detection — only prompt for IDEs found on PATH
|
|
121
|
+
code_found = shutil.which("code") is not None
|
|
122
|
+
cursor_found = shutil.which("cursor") is not None
|
|
123
|
+
|
|
124
|
+
if vscode is not None:
|
|
125
|
+
do_vscode = vscode
|
|
126
|
+
elif code_found:
|
|
127
|
+
do_vscode = _resolve(
|
|
128
|
+
None,
|
|
129
|
+
yes=yes,
|
|
130
|
+
prompt="Install the Dataface extension into VS Code?",
|
|
131
|
+
default=True,
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
do_vscode = False
|
|
135
|
+
|
|
136
|
+
if cursor is not None:
|
|
137
|
+
do_cursor = cursor
|
|
138
|
+
elif cursor_found:
|
|
139
|
+
do_cursor = _resolve(
|
|
140
|
+
None,
|
|
141
|
+
yes=yes,
|
|
142
|
+
prompt="Install the Dataface extension into Cursor?",
|
|
143
|
+
default=True,
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
do_cursor = False
|
|
147
|
+
|
|
148
|
+
if not code_found and not cursor_found and vscode is None and cursor is None:
|
|
149
|
+
typer.echo(
|
|
150
|
+
" VS Code / Cursor not found on PATH; skipping extension install. "
|
|
151
|
+
"Run 'dft init code' or 'dft init cursor' later if you install one."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Core scaffold
|
|
155
|
+
result = init_project(
|
|
156
|
+
project_dir=root,
|
|
157
|
+
force=force,
|
|
158
|
+
write_agents_md=do_agents_md,
|
|
159
|
+
write_claude_md=do_claude_md,
|
|
160
|
+
eject_inspect=False,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Output scaffold results
|
|
164
|
+
if result.dbt_detected:
|
|
165
|
+
typer.echo(f" dbt project detected: {result.project_dir / 'dbt_project.yml'}")
|
|
166
|
+
for f in result.created_files:
|
|
167
|
+
typer.echo(f" created {f}")
|
|
168
|
+
for f in result.refreshed_files:
|
|
169
|
+
typer.echo(f" refreshed {f}")
|
|
170
|
+
for f in result.appended_files:
|
|
171
|
+
typer.echo(f" appended {f}")
|
|
172
|
+
for f in result.skipped_files:
|
|
173
|
+
typer.echo(f" skipped {f} (already exists)")
|
|
174
|
+
for hint in result.hints:
|
|
175
|
+
typer.echo(f"\n Tip: {hint}")
|
|
176
|
+
|
|
177
|
+
# Opt-in dispatches
|
|
178
|
+
install_warnings: list[str] = []
|
|
179
|
+
|
|
180
|
+
if do_skills:
|
|
181
|
+
from dataface.cli.commands import skills_init
|
|
182
|
+
|
|
183
|
+
skills_init.run_init_skills(
|
|
184
|
+
target=None,
|
|
185
|
+
all_targets=False,
|
|
186
|
+
dir_override=None,
|
|
187
|
+
force=force,
|
|
188
|
+
check=False,
|
|
189
|
+
project_dir=root,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if do_mcp:
|
|
193
|
+
from dataface.cli.commands import mcp_init
|
|
194
|
+
|
|
195
|
+
mcp_init.run_init(
|
|
196
|
+
client=None,
|
|
197
|
+
all_clients=False,
|
|
198
|
+
force=False,
|
|
199
|
+
project_dir=root,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if do_chat_extra:
|
|
203
|
+
from dataface.cli._extras import install_extras
|
|
204
|
+
|
|
205
|
+
install_extras("chat", interactive=False)
|
|
206
|
+
|
|
207
|
+
if do_with_playground:
|
|
208
|
+
from dataface.cli._extras import install_extras
|
|
209
|
+
|
|
210
|
+
install_extras("playground", interactive=False)
|
|
211
|
+
|
|
212
|
+
if do_vscode:
|
|
213
|
+
from dataface.cli.commands import extension as extension_cmd
|
|
214
|
+
|
|
215
|
+
rc = extension_cmd.install_extension("code", emit=typer.echo)
|
|
216
|
+
if rc != 0:
|
|
217
|
+
install_warnings.append(
|
|
218
|
+
"VS Code extension install failed (see output above)."
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if do_cursor:
|
|
222
|
+
from dataface.cli.commands import extension as extension_cmd
|
|
223
|
+
|
|
224
|
+
rc = extension_cmd.install_extension("cursor", emit=typer.echo)
|
|
225
|
+
if rc != 0:
|
|
226
|
+
install_warnings.append(
|
|
227
|
+
"Cursor extension install failed (see output above)."
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Final success block
|
|
231
|
+
typer.echo("")
|
|
232
|
+
for warning in install_warnings:
|
|
233
|
+
typer.echo(f" Warning: {warning}")
|
|
234
|
+
if install_warnings:
|
|
235
|
+
typer.echo("")
|
|
236
|
+
typer.echo(
|
|
237
|
+
" You're all set. Your project has a faces/ directory with a starter dashboard."
|
|
238
|
+
)
|
|
239
|
+
typer.echo("")
|
|
240
|
+
typer.echo(" Run `dft serve` to preview it in your browser.")
|