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,49 @@
|
|
|
1
|
+
"""CLI command for `dft search`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from dataface.agent_api.search import SearchResult, search_dashboards
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def search_command(
|
|
13
|
+
query: str,
|
|
14
|
+
json_output: bool = False,
|
|
15
|
+
limit: int = 10,
|
|
16
|
+
project_dir: Path | None = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Search dashboards by keyword with ranked results."""
|
|
19
|
+
result = search_dashboards(
|
|
20
|
+
query=query,
|
|
21
|
+
project_dir=(project_dir or Path(".")).resolve(),
|
|
22
|
+
limit=limit,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if json_output:
|
|
26
|
+
typer.echo(result.model_dump_json(indent=2, exclude_none=True))
|
|
27
|
+
if not result.success:
|
|
28
|
+
raise typer.Exit(1)
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
_print_rich(result)
|
|
32
|
+
if not result.success:
|
|
33
|
+
raise typer.Exit(1)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _print_rich(result: SearchResult) -> None:
|
|
37
|
+
if not result.success:
|
|
38
|
+
for e in result.errors:
|
|
39
|
+
typer.echo(f"Error: {e}", err=True)
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
if not result.results:
|
|
43
|
+
typer.echo("No results found.")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
for hit in result.results:
|
|
47
|
+
typer.echo(f" [{hit.match_score:.2f}] {hit.title} {hit.source_path}")
|
|
48
|
+
if hit.summary:
|
|
49
|
+
typer.echo(f" {hit.summary}")
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Serve command implementation."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
import uvicorn
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def serve_command(
|
|
12
|
+
port: int | None = None,
|
|
13
|
+
host: str = "localhost",
|
|
14
|
+
project_dir: Path | None = None,
|
|
15
|
+
connection: str | None = None,
|
|
16
|
+
dialect: str | None = None,
|
|
17
|
+
target: str | None = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Start unified Dataface server.
|
|
20
|
+
|
|
21
|
+
This command starts a FastAPI server that serves:
|
|
22
|
+
- Profile templates at /profile/{template}/?model=X&column=Y
|
|
23
|
+
- Any face file at /{path}/?var=value (path.yml → /path/)
|
|
24
|
+
- Health check at /health
|
|
25
|
+
|
|
26
|
+
Example URLs:
|
|
27
|
+
http://localhost:9876/health
|
|
28
|
+
http://localhost:9876/profile/model/?model=fct_orders&theme=dark
|
|
29
|
+
http://localhost:9876/profile/numeric_column/?model=fct_orders&column=revenue
|
|
30
|
+
http://localhost:9876/sales/?region=West
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
port: Port number to serve on (auto-resolved if not specified)
|
|
34
|
+
host: Host address to bind to
|
|
35
|
+
project_dir: Project directory for resolving face file paths
|
|
36
|
+
connection: Database connection string
|
|
37
|
+
dialect: SQL dialect (auto-detected from dbt profile, or duckdb)
|
|
38
|
+
target: dbt target name (defaults to DBT_TARGET env var, then profile default)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
None (exits with code 0 on success, 1 on errors)
|
|
42
|
+
"""
|
|
43
|
+
from dataface.core.project_roots import (
|
|
44
|
+
discover_project_root,
|
|
45
|
+
infer_dialect_from_dbt,
|
|
46
|
+
)
|
|
47
|
+
from dataface.core.serve.bootstrap import apply_default_theme_from_env
|
|
48
|
+
from dataface.core.serve.port import resolve_port
|
|
49
|
+
from dataface.core.serve.server import create_server
|
|
50
|
+
|
|
51
|
+
# Discover project root when not explicitly provided
|
|
52
|
+
resolved_dir = project_dir if project_dir else discover_project_root()
|
|
53
|
+
|
|
54
|
+
# Apply DFT_DEFAULT_THEME before anything else — fails fast on invalid names.
|
|
55
|
+
try:
|
|
56
|
+
apply_default_theme_from_env()
|
|
57
|
+
except ValueError as e:
|
|
58
|
+
typer.echo(f"Error: {e}", err=True)
|
|
59
|
+
raise typer.Exit(1) from None
|
|
60
|
+
|
|
61
|
+
# Resolve port: --port > DFT_PORT > dataface.yml > hash(project_dir)
|
|
62
|
+
port = resolve_port(
|
|
63
|
+
explicit_port=port,
|
|
64
|
+
project_dir=resolved_dir,
|
|
65
|
+
host=host,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Resolve dbt target: CLI flag > DBT_TARGET env > profile default
|
|
69
|
+
effective_target = target or os.environ.get("DBT_TARGET") or None
|
|
70
|
+
|
|
71
|
+
# Infer dialect from dbt profile when not explicitly provided
|
|
72
|
+
effective_dialect = dialect
|
|
73
|
+
if effective_dialect is None:
|
|
74
|
+
inferred = infer_dialect_from_dbt(resolved_dir, effective_target)
|
|
75
|
+
if inferred:
|
|
76
|
+
typer.echo(f" Auto-detected dialect: {inferred} (from dbt profile)")
|
|
77
|
+
effective_dialect = inferred or "duckdb"
|
|
78
|
+
|
|
79
|
+
# Create FastAPI app
|
|
80
|
+
app = create_server(
|
|
81
|
+
project_dir=resolved_dir,
|
|
82
|
+
connection=connection,
|
|
83
|
+
dialect=effective_dialect,
|
|
84
|
+
target=effective_target,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Start server
|
|
88
|
+
typer.echo("🚀 Starting dashboard server")
|
|
89
|
+
typer.echo(f" URL: http://{host}:{port}")
|
|
90
|
+
typer.echo(f" Health: http://{host}:{port}/health")
|
|
91
|
+
typer.echo("")
|
|
92
|
+
typer.echo(" Routes:")
|
|
93
|
+
typer.echo(f" http://{host}:{port}/profile/model/?model=<table>")
|
|
94
|
+
typer.echo(
|
|
95
|
+
f" http://{host}:{port}/profile/numeric_column/?model=<table>&column=<col>"
|
|
96
|
+
)
|
|
97
|
+
typer.echo(f" http://{host}:{port}/<name>/?var=value")
|
|
98
|
+
typer.echo("")
|
|
99
|
+
typer.echo(" Press CTRL+C to stop")
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
uvicorn.run(
|
|
103
|
+
app,
|
|
104
|
+
host=host,
|
|
105
|
+
port=port,
|
|
106
|
+
log_level="info",
|
|
107
|
+
timeout_graceful_shutdown=3,
|
|
108
|
+
)
|
|
109
|
+
except KeyboardInterrupt:
|
|
110
|
+
typer.echo("\n👋 Server stopped")
|
|
111
|
+
sys.exit(0)
|
|
112
|
+
except Exception as e: # noqa: BLE001
|
|
113
|
+
typer.echo(f"Error starting server: {e}", err=True)
|
|
114
|
+
raise typer.Exit(1) from None
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Skills command — thin wrapper over dataface.agent_api.skills."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.padding import Padding
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from dataface.agent_api import skills as _api
|
|
12
|
+
from dataface.cli._console import dft_console
|
|
13
|
+
from dataface.cli._json_output import print_json_result
|
|
14
|
+
|
|
15
|
+
err_console = dft_console(stderr=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def skills_command(
|
|
19
|
+
name: str | None = None,
|
|
20
|
+
search: str | None = None,
|
|
21
|
+
limit: int = 10,
|
|
22
|
+
as_json: bool = False,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""List packaged agent skills, search them, or show one by name — selected from the input args."""
|
|
25
|
+
if name is not None and search is not None:
|
|
26
|
+
err_console.print(
|
|
27
|
+
"[red]Error:[/red] cannot combine a skill name with --search; use one or the other."
|
|
28
|
+
)
|
|
29
|
+
raise typer.Exit(1)
|
|
30
|
+
|
|
31
|
+
if search is not None:
|
|
32
|
+
result = _api.search_skills(search, limit=limit, surface="cli")
|
|
33
|
+
if as_json:
|
|
34
|
+
print_json_result(result)
|
|
35
|
+
return
|
|
36
|
+
_print_search_results(result)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
if name is None:
|
|
40
|
+
listing = _api.list_skills(surface="cli")
|
|
41
|
+
if as_json:
|
|
42
|
+
print_json_result(listing)
|
|
43
|
+
return
|
|
44
|
+
_print_skills_table(listing)
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
skill = _api.get_skill(name, surface="cli")
|
|
49
|
+
except _api.SkillNotFound as exc:
|
|
50
|
+
print(str(exc), file=sys.stderr)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
if as_json:
|
|
53
|
+
print_json_result(skill)
|
|
54
|
+
else:
|
|
55
|
+
print(skill.body)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _print_skills_table(result: _api.SkillList) -> None:
|
|
59
|
+
if not result.skills:
|
|
60
|
+
typer.echo("No skills available.")
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
console = dft_console()
|
|
64
|
+
|
|
65
|
+
workflows = [s for s in result.skills if s.kind == "workflow"]
|
|
66
|
+
patterns = [s for s in result.skills if s.kind == "pattern"]
|
|
67
|
+
|
|
68
|
+
typer.echo(
|
|
69
|
+
"Agent skills — workflows and layout patterns for building Dataface dashboards."
|
|
70
|
+
)
|
|
71
|
+
typer.echo("YAML field reference is `dft docs`, not skills.\n")
|
|
72
|
+
|
|
73
|
+
name_width = max(len(s.name) for s in result.skills)
|
|
74
|
+
|
|
75
|
+
if workflows:
|
|
76
|
+
console.print("[bold]Workflows[/bold]")
|
|
77
|
+
console.print(Padding(_skill_table(workflows, name_width), (0, 0, 0, 2)))
|
|
78
|
+
console.print()
|
|
79
|
+
|
|
80
|
+
if patterns:
|
|
81
|
+
console.print("[bold]Patterns[/bold]")
|
|
82
|
+
console.print(Padding(_skill_table(patterns, name_width), (0, 0, 0, 2)))
|
|
83
|
+
console.print()
|
|
84
|
+
|
|
85
|
+
typer.echo("Next steps")
|
|
86
|
+
typer.echo(
|
|
87
|
+
" dft skills <name> Read the full skill body and copy the example YAML"
|
|
88
|
+
)
|
|
89
|
+
typer.echo(" dft docs YAML syntax reference")
|
|
90
|
+
typer.echo("")
|
|
91
|
+
typer.echo('Find a skill: dft skills -s "<query>"')
|
|
92
|
+
typer.echo('YAML syntax: dft docs -s "<query>"')
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _skill_table(skills: list[_api.Skill], name_width: int) -> Table:
|
|
96
|
+
t = Table(show_header=False, box=None, padding=(0, 2, 0, 0))
|
|
97
|
+
t.add_column("Name", style="bold", no_wrap=True, width=name_width)
|
|
98
|
+
t.add_column("Description", overflow="fold")
|
|
99
|
+
for skill in skills:
|
|
100
|
+
suffix = r" [dim]\[scaffold][/dim]" if skill.has_examples else ""
|
|
101
|
+
t.add_row(skill.name, f"{_first_sentence(skill.description)}{suffix}")
|
|
102
|
+
return t
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _print_search_results(result: _api.SkillSearchResult) -> None:
|
|
106
|
+
if not result.hits:
|
|
107
|
+
typer.echo(f'Agent skills — search results for "{result.query}"\n')
|
|
108
|
+
typer.echo(
|
|
109
|
+
'No skills matched. Try `dft skills -s "kpi"` or `dft skills` (full list).'
|
|
110
|
+
)
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
console = dft_console()
|
|
114
|
+
typer.echo(f'Agent skills — search results for "{result.query}"\n')
|
|
115
|
+
for hit in result.hits:
|
|
116
|
+
scaffold = r" \[scaffold]" if hit.has_examples else ""
|
|
117
|
+
console.print(
|
|
118
|
+
f" [bold]{hit.name}[/bold] \\[{hit.kind}]{scaffold} "
|
|
119
|
+
f"[dim]score {hit.score:.2f}[/dim]"
|
|
120
|
+
)
|
|
121
|
+
console.print(f" {_first_sentence(hit.description)}")
|
|
122
|
+
typer.echo("")
|
|
123
|
+
typer.echo(
|
|
124
|
+
f"{len(result.hits)} matches. "
|
|
125
|
+
"Read one: `dft skills <name>` — copy the example YAML directly."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _first_sentence(text: str) -> str:
|
|
130
|
+
"""Collapse a YAML block-scalar description to a single sentence."""
|
|
131
|
+
flat = " ".join(text.split())
|
|
132
|
+
head, sep, _ = flat.partition(".")
|
|
133
|
+
return head + sep if sep else flat
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""`dft init skills` thin wrapper: parse args, call agent_api.skill_install."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from dataface.agent_api import skill_install
|
|
10
|
+
from dataface.core.project_roots import find_dataface_project, find_git_root
|
|
11
|
+
|
|
12
|
+
_VALID_TARGETS = skill_install.SKILL_INSTALL_TARGETS
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _resolve_project_dir(start: Path) -> Path:
|
|
16
|
+
"""Return the directory where skills should be installed.
|
|
17
|
+
|
|
18
|
+
Skills are agent tooling that belong at the git root, not the operational
|
|
19
|
+
project root. When a .git directory exists above the dataface.yml, install
|
|
20
|
+
at the git root so monorepos with nested dataface projects don't accumulate
|
|
21
|
+
divergent skill copies.
|
|
22
|
+
"""
|
|
23
|
+
project_root = find_dataface_project(start)
|
|
24
|
+
if project_root is None:
|
|
25
|
+
project_root = start.resolve()
|
|
26
|
+
git_root = find_git_root(project_root)
|
|
27
|
+
if git_root is not None:
|
|
28
|
+
return git_root
|
|
29
|
+
return project_root
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _resolve_targets(
|
|
33
|
+
target: str | None,
|
|
34
|
+
*,
|
|
35
|
+
all_targets: bool,
|
|
36
|
+
dir_override: Path | None,
|
|
37
|
+
project_root: Path,
|
|
38
|
+
) -> list[Path]:
|
|
39
|
+
if dir_override is not None:
|
|
40
|
+
return [dir_override.resolve()]
|
|
41
|
+
|
|
42
|
+
if target is not None:
|
|
43
|
+
key = target.lower()
|
|
44
|
+
if key not in _VALID_TARGETS:
|
|
45
|
+
typer.echo(
|
|
46
|
+
f"Unknown target: {target}. "
|
|
47
|
+
f"Supported: {', '.join(sorted(_VALID_TARGETS))}",
|
|
48
|
+
err=True,
|
|
49
|
+
)
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
return [skill_install.target_dir_for(key, project_root=project_root)]
|
|
52
|
+
|
|
53
|
+
detected = skill_install.detect_skill_targets(project_root)
|
|
54
|
+
if all_targets:
|
|
55
|
+
if not detected:
|
|
56
|
+
typer.echo(
|
|
57
|
+
"No agent skill targets detected (.cursor/, AGENTS.md, CLAUDE.md).\n"
|
|
58
|
+
"Pass an explicit target: dft init skills agents | claude",
|
|
59
|
+
err=True,
|
|
60
|
+
)
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
keys = detected
|
|
63
|
+
elif detected:
|
|
64
|
+
keys = detected
|
|
65
|
+
else:
|
|
66
|
+
typer.echo(
|
|
67
|
+
"No agent skill targets detected (.cursor/, AGENTS.md, CLAUDE.md).\n"
|
|
68
|
+
"Specify a target or pass --dir:\n"
|
|
69
|
+
" dft init skills agents\n"
|
|
70
|
+
" dft init skills claude\n"
|
|
71
|
+
" dft init skills --all\n"
|
|
72
|
+
" dft init skills --dir .agents/skills",
|
|
73
|
+
err=True,
|
|
74
|
+
)
|
|
75
|
+
raise typer.Exit(1)
|
|
76
|
+
|
|
77
|
+
return [skill_install.target_dir_for(k, project_root=project_root) for k in keys]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _format_legacy_note(legacy_dirs: list[Path], project_root: Path) -> str | None:
|
|
81
|
+
if not legacy_dirs:
|
|
82
|
+
return None
|
|
83
|
+
first = legacy_dirs[0]
|
|
84
|
+
try:
|
|
85
|
+
rel = first.relative_to(project_root)
|
|
86
|
+
except ValueError:
|
|
87
|
+
rel = first
|
|
88
|
+
return (
|
|
89
|
+
f"Legacy skill install detected at {rel} — "
|
|
90
|
+
"re-run `dft init skills agents` and remove the legacy dir manually."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def run_init_skills(
|
|
95
|
+
target: str | None,
|
|
96
|
+
all_targets: bool,
|
|
97
|
+
dir_override: Path | None,
|
|
98
|
+
force: bool,
|
|
99
|
+
check: bool,
|
|
100
|
+
project_dir: Path | None = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Shared implementation for ``dft init skills``."""
|
|
103
|
+
if target is not None and (all_targets or dir_override is not None):
|
|
104
|
+
typer.echo(
|
|
105
|
+
"Specify one of: a target name, --all, or --dir — not combined.",
|
|
106
|
+
err=True,
|
|
107
|
+
)
|
|
108
|
+
raise typer.Exit(1)
|
|
109
|
+
if all_targets and dir_override is not None:
|
|
110
|
+
typer.echo("Specify either --all or --dir, not both.", err=True)
|
|
111
|
+
raise typer.Exit(1)
|
|
112
|
+
|
|
113
|
+
project_root = _resolve_project_dir(
|
|
114
|
+
project_dir if project_dir is not None else Path.cwd()
|
|
115
|
+
)
|
|
116
|
+
target_dirs = _resolve_targets(
|
|
117
|
+
target,
|
|
118
|
+
all_targets=all_targets,
|
|
119
|
+
dir_override=dir_override,
|
|
120
|
+
project_root=project_root,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
prefix = "Would install" if check else "Installed"
|
|
124
|
+
any_legacy = False
|
|
125
|
+
for target_dir in target_dirs:
|
|
126
|
+
result = skill_install.install_skills(
|
|
127
|
+
target_dir=target_dir,
|
|
128
|
+
project_root=project_root,
|
|
129
|
+
force=force,
|
|
130
|
+
check=check,
|
|
131
|
+
)
|
|
132
|
+
try:
|
|
133
|
+
rel = target_dir.relative_to(project_root)
|
|
134
|
+
except ValueError:
|
|
135
|
+
rel = target_dir
|
|
136
|
+
if result.installed:
|
|
137
|
+
typer.echo(f" {prefix} workflow skills ({len(result.installed)}) → {rel}/")
|
|
138
|
+
for name in sorted(result.installed):
|
|
139
|
+
typer.echo(f" ✓ {name}")
|
|
140
|
+
elif result.skipped_existing:
|
|
141
|
+
typer.echo(
|
|
142
|
+
f" Workflow skills unchanged ({len(result.skipped_existing)})"
|
|
143
|
+
f" → {rel}/"
|
|
144
|
+
)
|
|
145
|
+
if result.retired_removed:
|
|
146
|
+
typer.echo(f" Removed retired: {', '.join(result.retired_removed)}")
|
|
147
|
+
if result.skipped_existing:
|
|
148
|
+
typer.echo(f" Unchanged (identical): {', '.join(result.skipped_existing)}")
|
|
149
|
+
legacy_note = _format_legacy_note(result.legacy_dirs_detected, project_root)
|
|
150
|
+
if legacy_note:
|
|
151
|
+
any_legacy = True
|
|
152
|
+
typer.echo(f" {legacy_note}")
|
|
153
|
+
|
|
154
|
+
if check:
|
|
155
|
+
typer.echo("")
|
|
156
|
+
typer.echo(" Dry run — no files written.")
|
|
157
|
+
elif any_legacy:
|
|
158
|
+
typer.echo("")
|
|
159
|
+
typer.echo(
|
|
160
|
+
" Re-run after removing legacy .cursor/skills or .codex/skills dirs."
|
|
161
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""validate command — thin wrapper over dataface.agent_api.validate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from dataface.agent_api.validate import ValidateResult
|
|
11
|
+
from dataface.cli._console import dft_console
|
|
12
|
+
from dataface.cli._error_format import print_render_warnings, print_structured_errors
|
|
13
|
+
|
|
14
|
+
_console = dft_console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def validate_command(
|
|
18
|
+
paths: list[Path] | None,
|
|
19
|
+
project_dir: Path | None = None,
|
|
20
|
+
json_output: bool = False,
|
|
21
|
+
strict: bool = False,
|
|
22
|
+
) -> None:
|
|
23
|
+
from dataface.agent_api.validate import validate_paths
|
|
24
|
+
|
|
25
|
+
results = validate_paths(paths, project_dir=project_dir)
|
|
26
|
+
_emit(results, json_output=json_output, strict=strict)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _emit(
|
|
30
|
+
results: list[ValidateResult],
|
|
31
|
+
*,
|
|
32
|
+
json_output: bool,
|
|
33
|
+
strict: bool,
|
|
34
|
+
) -> None:
|
|
35
|
+
if json_output:
|
|
36
|
+
if len(results) == 1:
|
|
37
|
+
typer.echo(results[0].model_dump_json(exclude_none=True, indent=2))
|
|
38
|
+
else:
|
|
39
|
+
typer.echo(
|
|
40
|
+
json.dumps(
|
|
41
|
+
[r.model_dump(mode="json", exclude_none=True) for r in results],
|
|
42
|
+
indent=2,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
has_errors = any(r.errors for r in results)
|
|
46
|
+
raise typer.Exit(0 if not has_errors else 1)
|
|
47
|
+
|
|
48
|
+
has_errors = False
|
|
49
|
+
has_warnings = False
|
|
50
|
+
for r in results:
|
|
51
|
+
if r.errors:
|
|
52
|
+
has_errors = True
|
|
53
|
+
print_structured_errors(r.errors)
|
|
54
|
+
if r.warnings:
|
|
55
|
+
has_warnings = True
|
|
56
|
+
print_render_warnings(r.warnings, path=r.path)
|
|
57
|
+
if not r.errors and not r.warnings:
|
|
58
|
+
_console.print(f"✓ {r.path} OK")
|
|
59
|
+
|
|
60
|
+
if has_errors:
|
|
61
|
+
raise typer.Exit(1)
|
|
62
|
+
if strict and has_warnings:
|
|
63
|
+
raise typer.Exit(1)
|