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.
Files changed (455) hide show
  1. d3_format/__init__.py +14 -0
  2. d3_format/errors.py +19 -0
  3. d3_format/format.py +551 -0
  4. d3_format/spec.py +159 -0
  5. dataface/DATAFACE_SYNTAX.md +1135 -0
  6. dataface/__init__.py +93 -0
  7. dataface/_docs_site.py +20 -0
  8. dataface/_install_hint.py +26 -0
  9. dataface/agent_api/__init__.py +79 -0
  10. dataface/agent_api/_init_templates/__init__.py +0 -0
  11. dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
  12. dataface/agent_api/_init_templates/dataface.yml +15 -0
  13. dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
  14. dataface/agent_api/_init_templates/index.md +24 -0
  15. dataface/agent_api/_paths.py +118 -0
  16. dataface/agent_api/_project_agents_md.py +43 -0
  17. dataface/agent_api/_session_store.py +486 -0
  18. dataface/agent_api/_state.py +28 -0
  19. dataface/agent_api/chat.py +221 -0
  20. dataface/agent_api/dashboards.py +257 -0
  21. dataface/agent_api/describe.py +366 -0
  22. dataface/agent_api/describe_query.py +120 -0
  23. dataface/agent_api/docs/__init__.py +25 -0
  24. dataface/agent_api/docs/_loader.py +292 -0
  25. dataface/agent_api/docs/yaml-reference.md +2757 -0
  26. dataface/agent_api/file_refs.py +118 -0
  27. dataface/agent_api/init.py +126 -0
  28. dataface/agent_api/inspect.py +128 -0
  29. dataface/agent_api/mcp_install.py +170 -0
  30. dataface/agent_api/query.py +274 -0
  31. dataface/agent_api/schema.py +658 -0
  32. dataface/agent_api/schema_search.py +284 -0
  33. dataface/agent_api/search.py +270 -0
  34. dataface/agent_api/skill_install.py +141 -0
  35. dataface/agent_api/skill_render.py +90 -0
  36. dataface/agent_api/skills.py +293 -0
  37. dataface/agent_api/surface_aliases.yaml +128 -0
  38. dataface/agent_api/validate.py +175 -0
  39. dataface/agent_api/validate_query.py +84 -0
  40. dataface/ai/__init__.py +39 -0
  41. dataface/ai/agent.py +139 -0
  42. dataface/ai/context.py +45 -0
  43. dataface/ai/events.py +62 -0
  44. dataface/ai/external_mcp.py +610 -0
  45. dataface/ai/generate_sql.py +96 -0
  46. dataface/ai/llm.py +403 -0
  47. dataface/ai/mcp/__init__.py +51 -0
  48. dataface/ai/mcp/server.py +289 -0
  49. dataface/ai/memories.py +85 -0
  50. dataface/ai/prompts.py +177 -0
  51. dataface/ai/schema_context.py +138 -0
  52. dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
  53. dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
  54. dataface/ai/skills/dashboard-build/SKILL.md +212 -0
  55. dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
  56. dataface/ai/skills/dashboard-design/SKILL.md +182 -0
  57. dataface/ai/skills/dashboard-review/SKILL.md +113 -0
  58. dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
  59. dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
  60. dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
  61. dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
  62. dataface/ai/skills/drill-down-link/SKILL.md +112 -0
  63. dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
  64. dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
  65. dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
  66. dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
  67. dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
  68. dataface/ai/skills/kpi-row/SKILL.md +101 -0
  69. dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
  70. dataface/ai/skills/report-design/SKILL.md +184 -0
  71. dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
  72. dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
  73. dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
  74. dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
  75. dataface/ai/skills/time-series-trend/SKILL.md +93 -0
  76. dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
  77. dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
  78. dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
  79. dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
  80. dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
  81. dataface/ai/tool_schemas.py +132 -0
  82. dataface/ai/tools/__init__.py +312 -0
  83. dataface/ai/yaml_utils.py +57 -0
  84. dataface/cli/__init__.py +3 -0
  85. dataface/cli/_console.py +48 -0
  86. dataface/cli/_error_format.py +83 -0
  87. dataface/cli/_extras.py +190 -0
  88. dataface/cli/_json_output.py +8 -0
  89. dataface/cli/_parsing.py +17 -0
  90. dataface/cli/_version_info.py +56 -0
  91. dataface/cli/commands/__init__.py +3 -0
  92. dataface/cli/commands/_agent_input.py +205 -0
  93. dataface/cli/commands/_agent_server.py +115 -0
  94. dataface/cli/commands/chat.py +645 -0
  95. dataface/cli/commands/describe.py +107 -0
  96. dataface/cli/commands/docs.py +131 -0
  97. dataface/cli/commands/extension.py +179 -0
  98. dataface/cli/commands/init.py +240 -0
  99. dataface/cli/commands/inspect.py +94 -0
  100. dataface/cli/commands/mcp_init.py +167 -0
  101. dataface/cli/commands/query.py +386 -0
  102. dataface/cli/commands/render.py +291 -0
  103. dataface/cli/commands/schema.py +411 -0
  104. dataface/cli/commands/search.py +49 -0
  105. dataface/cli/commands/serve.py +114 -0
  106. dataface/cli/commands/skills.py +133 -0
  107. dataface/cli/commands/skills_init.py +161 -0
  108. dataface/cli/commands/validate.py +63 -0
  109. dataface/cli/main.py +1501 -0
  110. dataface/core/__init__.py +75 -0
  111. dataface/core/compile/__init__.py +244 -0
  112. dataface/core/compile/_jinja_helpers.py +78 -0
  113. dataface/core/compile/channel.py +222 -0
  114. dataface/core/compile/chart_focus.py +101 -0
  115. dataface/core/compile/chart_resolved.py +169 -0
  116. dataface/core/compile/chart_type_detection.py +489 -0
  117. dataface/core/compile/chart_update.py +261 -0
  118. dataface/core/compile/colors.py +64 -0
  119. dataface/core/compile/compiler.py +904 -0
  120. dataface/core/compile/config.py +823 -0
  121. dataface/core/compile/custom_chart_types.py +208 -0
  122. dataface/core/compile/data_table_attachment.py +1287 -0
  123. dataface/core/compile/detect.py +110 -0
  124. dataface/core/compile/errors.py +302 -0
  125. dataface/core/compile/filter_injection.py +319 -0
  126. dataface/core/compile/introspection.py +527 -0
  127. dataface/core/compile/jinja.py +511 -0
  128. dataface/core/compile/labels_env.py +52 -0
  129. dataface/core/compile/markdown.py +154 -0
  130. dataface/core/compile/meta.py +388 -0
  131. dataface/core/compile/models/__init__.py +0 -0
  132. dataface/core/compile/models/chart/__init__.py +0 -0
  133. dataface/core/compile/models/chart/authored.py +2137 -0
  134. dataface/core/compile/models/chart/compiled.py +398 -0
  135. dataface/core/compile/models/config.py +347 -0
  136. dataface/core/compile/models/face/__init__.py +0 -0
  137. dataface/core/compile/models/face/authored.py +659 -0
  138. dataface/core/compile/models/face/compiled.py +522 -0
  139. dataface/core/compile/models/factories.py +201 -0
  140. dataface/core/compile/models/markers.py +40 -0
  141. dataface/core/compile/models/palette.py +36 -0
  142. dataface/core/compile/models/primitives.py +415 -0
  143. dataface/core/compile/models/query/__init__.py +0 -0
  144. dataface/core/compile/models/query/authored.py +246 -0
  145. dataface/core/compile/models/query/compiled.py +710 -0
  146. dataface/core/compile/models/refs.py +137 -0
  147. dataface/core/compile/models/source.py +611 -0
  148. dataface/core/compile/models/style/__init__.py +0 -0
  149. dataface/core/compile/models/style/authored.py +481 -0
  150. dataface/core/compile/models/style/compiled.py +3399 -0
  151. dataface/core/compile/models/style/merged.py +1682 -0
  152. dataface/core/compile/models/theme.py +362 -0
  153. dataface/core/compile/models/variable/__init__.py +0 -0
  154. dataface/core/compile/models/variable/authored.py +254 -0
  155. dataface/core/compile/models/vega_lite/__init__.py +0 -0
  156. dataface/core/compile/models/vega_lite/config.py +510 -0
  157. dataface/core/compile/models/vega_lite/contracts.py +171 -0
  158. dataface/core/compile/normalize_charts.py +494 -0
  159. dataface/core/compile/normalize_layout.py +1000 -0
  160. dataface/core/compile/normalize_queries.py +297 -0
  161. dataface/core/compile/normalize_variables.py +489 -0
  162. dataface/core/compile/normalizer.py +543 -0
  163. dataface/core/compile/palette.py +1100 -0
  164. dataface/core/compile/parameterized.py +658 -0
  165. dataface/core/compile/parser.py +228 -0
  166. dataface/core/compile/schema.py +20 -0
  167. dataface/core/compile/schema_renderers/__init__.py +0 -0
  168. dataface/core/compile/schema_renderers/json_schema.py +163 -0
  169. dataface/core/compile/schema_renderers/prompt.py +152 -0
  170. dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
  171. dataface/core/compile/sizing.py +2126 -0
  172. dataface/core/compile/sources.py +518 -0
  173. dataface/core/compile/sql_authoring_lint.py +56 -0
  174. dataface/core/compile/style_cascade.py +471 -0
  175. dataface/core/compile/typography.py +299 -0
  176. dataface/core/compile/validator.py +301 -0
  177. dataface/core/compile/variables.py +53 -0
  178. dataface/core/compile/vega_config.py +98 -0
  179. dataface/core/compile/vega_lite/__init__.py +6 -0
  180. dataface/core/compile/vega_lite/validation.py +95 -0
  181. dataface/core/compile/yaml_error_formatter.py +838 -0
  182. dataface/core/connections.py +38 -0
  183. dataface/core/dashboard.py +358 -0
  184. dataface/core/defaults/default_config.yml +101 -0
  185. dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
  186. dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
  187. dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
  188. dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
  189. dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
  190. dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
  191. dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
  192. dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
  193. dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
  194. dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
  195. dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
  196. dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
  197. dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
  198. dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
  199. dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
  200. dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
  201. dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
  202. dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
  203. dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
  204. dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
  205. dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
  206. dataface/core/defaults/palettes/sequential/amber.yml +22 -0
  207. dataface/core/defaults/palettes/sequential/blue.yml +22 -0
  208. dataface/core/defaults/palettes/sequential/brown.yml +22 -0
  209. dataface/core/defaults/palettes/sequential/gray.yml +22 -0
  210. dataface/core/defaults/palettes/sequential/green.yml +22 -0
  211. dataface/core/defaults/palettes/sequential/purple.yml +22 -0
  212. dataface/core/defaults/palettes/sequential/rust.yml +22 -0
  213. dataface/core/defaults/palettes/sequential/teal.yml +22 -0
  214. dataface/core/defaults/palettes/tone/negative.yml +32 -0
  215. dataface/core/defaults/palettes/tone/positive.yml +22 -0
  216. dataface/core/defaults/palettes/tone/warning.yml +22 -0
  217. dataface/core/defaults/themes/_base.yaml +786 -0
  218. dataface/core/defaults/themes/bi.yaml +16 -0
  219. dataface/core/defaults/themes/carbong100.yaml +41 -0
  220. dataface/core/defaults/themes/cream.yaml +122 -0
  221. dataface/core/defaults/themes/dark.yaml +40 -0
  222. dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
  223. dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
  224. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
  225. dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
  226. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
  227. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
  228. dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
  229. dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
  230. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
  231. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
  232. dataface/core/defaults/themes/editorial.yaml +147 -0
  233. dataface/core/defaults/themes/light.yaml +30 -0
  234. dataface/core/defaults/themes/looker.yaml +17 -0
  235. dataface/core/defaults/themes/stark.yaml +134 -0
  236. dataface/core/errors/__init__.py +67 -0
  237. dataface/core/errors/codes_compile.py +56 -0
  238. dataface/core/errors/codes_execute.py +177 -0
  239. dataface/core/errors/codes_render.py +106 -0
  240. dataface/core/errors/codes_unknown.py +15 -0
  241. dataface/core/errors/hints.py +74 -0
  242. dataface/core/errors/registry.py +42 -0
  243. dataface/core/errors/structured.py +92 -0
  244. dataface/core/execute/__init__.py +91 -0
  245. dataface/core/execute/adapters/__init__.py +49 -0
  246. dataface/core/execute/adapters/adapter_registry.py +400 -0
  247. dataface/core/execute/adapters/base.py +245 -0
  248. dataface/core/execute/adapters/csv_adapter.py +239 -0
  249. dataface/core/execute/adapters/dbt_adapter.py +283 -0
  250. dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
  251. dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
  252. dataface/core/execute/adapters/dbt_utils.py +150 -0
  253. dataface/core/execute/adapters/http_adapter.py +224 -0
  254. dataface/core/execute/adapters/metricflow_adapter.py +94 -0
  255. dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
  256. dataface/core/execute/adapters/sql_adapter.py +710 -0
  257. dataface/core/execute/adapters/values_adapter.py +58 -0
  258. dataface/core/execute/batch.py +744 -0
  259. dataface/core/execute/cache_backend.py +135 -0
  260. dataface/core/execute/cache_keys.py +66 -0
  261. dataface/core/execute/dbt_jinja.py +21 -0
  262. dataface/core/execute/dialects/__init__.py +121 -0
  263. dataface/core/execute/dialects/athena.py +75 -0
  264. dataface/core/execute/dialects/base.py +302 -0
  265. dataface/core/execute/dialects/bigquery.py +38 -0
  266. dataface/core/execute/dialects/databricks.py +68 -0
  267. dataface/core/execute/dialects/duckdb.py +35 -0
  268. dataface/core/execute/dialects/mysql.py +68 -0
  269. dataface/core/execute/dialects/postgres.py +39 -0
  270. dataface/core/execute/dialects/redshift.py +12 -0
  271. dataface/core/execute/dialects/snowflake.py +51 -0
  272. dataface/core/execute/dialects/sqlserver.py +92 -0
  273. dataface/core/execute/duckdb_cache.py +712 -0
  274. dataface/core/execute/duckdb_config.py +26 -0
  275. dataface/core/execute/errors.py +213 -0
  276. dataface/core/execute/executor.py +1249 -0
  277. dataface/core/execute/parallel.py +162 -0
  278. dataface/core/execute/setup_sql.py +58 -0
  279. dataface/core/execute/source_registry.py +72 -0
  280. dataface/core/execute/source_resolver.py +255 -0
  281. dataface/core/execute/sql_guard.py +387 -0
  282. dataface/core/execute/sql_literals.py +199 -0
  283. dataface/core/fonts.py +52 -0
  284. dataface/core/inspect/__init__.py +32 -0
  285. dataface/core/inspect/cache_factory.py +98 -0
  286. dataface/core/inspect/db_types.py +162 -0
  287. dataface/core/inspect/dbt_schema.py +96 -0
  288. dataface/core/inspect/defaults.yml +37 -0
  289. dataface/core/inspect/fanout_risk.py +109 -0
  290. dataface/core/inspect/manifest_utils.py +77 -0
  291. dataface/core/inspect/partials/categorical.yml +40 -0
  292. dataface/core/inspect/partials/date.yml +40 -0
  293. dataface/core/inspect/partials/numeric.yml +55 -0
  294. dataface/core/inspect/partition_types.py +38 -0
  295. dataface/core/inspect/query_validator.py +975 -0
  296. dataface/core/inspect/renderer.py +354 -0
  297. dataface/core/inspect/resolver.py +808 -0
  298. dataface/core/inspect/search.py +461 -0
  299. dataface/core/inspect/sources/__init__.py +32 -0
  300. dataface/core/inspect/sources/dbt.py +738 -0
  301. dataface/core/inspect/sources/duckdb_utils.py +66 -0
  302. dataface/core/inspect/templates/__init__.py +1 -0
  303. dataface/core/inspect/templates/categorical_column.yml +196 -0
  304. dataface/core/inspect/templates/charts.yml +109 -0
  305. dataface/core/inspect/templates/date_column.yml +248 -0
  306. dataface/core/inspect/templates/model.yml +138 -0
  307. dataface/core/inspect/templates/numeric_column.yml +261 -0
  308. dataface/core/inspect/templates/quality.yml +80 -0
  309. dataface/core/inspect/templates/string_column.yml +263 -0
  310. dataface/core/project_roots.py +165 -0
  311. dataface/core/render/__init__.py +87 -0
  312. dataface/core/render/board_links.py +176 -0
  313. dataface/core/render/chart/__init__.py +27 -0
  314. dataface/core/render/chart/arc_attached_table.py +251 -0
  315. dataface/core/render/chart/artifacts.py +16 -0
  316. dataface/core/render/chart/callout.py +225 -0
  317. dataface/core/render/chart/decisions.py +358 -0
  318. dataface/core/render/chart/geo.py +700 -0
  319. dataface/core/render/chart/kpi.py +916 -0
  320. dataface/core/render/chart/labels.py +76 -0
  321. dataface/core/render/chart/pipeline.py +818 -0
  322. dataface/core/render/chart/presentation.py +36 -0
  323. dataface/core/render/chart/profile.py +3438 -0
  324. dataface/core/render/chart/render_single.py +347 -0
  325. dataface/core/render/chart/renderers.py +193 -0
  326. dataface/core/render/chart/rendering.py +565 -0
  327. dataface/core/render/chart/serialization.py +90 -0
  328. dataface/core/render/chart/spark.py +496 -0
  329. dataface/core/render/chart/spark_bar.py +370 -0
  330. dataface/core/render/chart/spec_builders.py +154 -0
  331. dataface/core/render/chart/standard_renderer.py +2645 -0
  332. dataface/core/render/chart/table.py +2957 -0
  333. dataface/core/render/chart/table_support.py +1452 -0
  334. dataface/core/render/chart/tick_values.py +66 -0
  335. dataface/core/render/chart/time_unit_detect.py +809 -0
  336. dataface/core/render/chart/title_overflow.py +157 -0
  337. dataface/core/render/chart/type_inference.py +122 -0
  338. dataface/core/render/chart/validation.py +99 -0
  339. dataface/core/render/chart/vega_lite.py +125 -0
  340. dataface/core/render/chart/vega_lite_types.py +268 -0
  341. dataface/core/render/chart/vl_field_maps.py +346 -0
  342. dataface/core/render/chart_interactivity.py +24 -0
  343. dataface/core/render/control_registry.py +287 -0
  344. dataface/core/render/converters/__init__.py +24 -0
  345. dataface/core/render/converters/chart.py +276 -0
  346. dataface/core/render/converters/html.py +98 -0
  347. dataface/core/render/converters/pdf.py +40 -0
  348. dataface/core/render/converters/png.py +41 -0
  349. dataface/core/render/errors.py +144 -0
  350. dataface/core/render/face_api.py +160 -0
  351. dataface/core/render/faces.py +1194 -0
  352. dataface/core/render/font_measurement.py +48 -0
  353. dataface/core/render/font_support.py +197 -0
  354. dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
  355. dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
  356. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
  357. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
  358. dataface/core/render/fonts/InterVariable.ttf +0 -0
  359. dataface/core/render/fonts/InterVariable.woff2 +0 -0
  360. dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
  361. dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
  362. dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
  363. dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
  364. dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
  365. dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
  366. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
  367. dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
  368. dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
  369. dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
  370. dataface/core/render/fonts/_emoji_font_face.css +43 -0
  371. dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
  372. dataface/core/render/format_utils.py +329 -0
  373. dataface/core/render/geo_defaults.yml +28 -0
  374. dataface/core/render/json_format.py +146 -0
  375. dataface/core/render/layout_sizing.py +865 -0
  376. dataface/core/render/layouts.py +541 -0
  377. dataface/core/render/markdown_defaults.yml +16 -0
  378. dataface/core/render/missing_vars_prompt.py +79 -0
  379. dataface/core/render/placeholder.py +389 -0
  380. dataface/core/render/render_result.py +14 -0
  381. dataface/core/render/renderer.py +467 -0
  382. dataface/core/render/script_embedding.py +16 -0
  383. dataface/core/render/svg_utils.py +212 -0
  384. dataface/core/render/template_loader.py +69 -0
  385. dataface/core/render/templates/controls/_styles.css +606 -0
  386. dataface/core/render/templates/controls/checkbox.html +16 -0
  387. dataface/core/render/templates/controls/date.html +16 -0
  388. dataface/core/render/templates/controls/number.html +19 -0
  389. dataface/core/render/templates/controls/readonly.html +9 -0
  390. dataface/core/render/templates/controls/select.html +21 -0
  391. dataface/core/render/templates/controls/slider.html +22 -0
  392. dataface/core/render/templates/controls/text.html +16 -0
  393. dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
  394. dataface/core/render/templates/scripts/variables.js +976 -0
  395. dataface/core/render/templates/svg/grid_pattern.svg +3 -0
  396. dataface/core/render/templates/svg/styles.css +51 -0
  397. dataface/core/render/terminal.py +311 -0
  398. dataface/core/render/terminal_charts.py +563 -0
  399. dataface/core/render/terminal_defaults.yml +2 -0
  400. dataface/core/render/terminal_layouts.py +299 -0
  401. dataface/core/render/terminal_text.py +31 -0
  402. dataface/core/render/text/__init__.py +1 -0
  403. dataface/core/render/text/case.py +113 -0
  404. dataface/core/render/text_format.py +129 -0
  405. dataface/core/render/utils.py +106 -0
  406. dataface/core/render/variable_controls.py +946 -0
  407. dataface/core/render/variable_input_refinement.py +140 -0
  408. dataface/core/render/warnings/__init__.py +15 -0
  409. dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
  410. dataface/core/render/warnings/base.py +44 -0
  411. dataface/core/render/warnings/fanout_risk.py +15 -0
  412. dataface/core/render/warnings/from_query_diagnostic.py +56 -0
  413. dataface/core/render/warnings/missing_join_predicate.py +13 -0
  414. dataface/core/render/warnings/query_parse_error.py +14 -0
  415. dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
  416. dataface/core/render/warnings/reaggregation.py +14 -0
  417. dataface/core/render/warnings/registry.py +45 -0
  418. dataface/core/render/warnings/suppression.py +46 -0
  419. dataface/core/render/warnings/temporal_single_point.py +63 -0
  420. dataface/core/render/warnings/unreferenced_chart.py +15 -0
  421. dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
  422. dataface/core/render/yaml_format.py +167 -0
  423. dataface/core/resolve_face.py +195 -0
  424. dataface/core/schema/__init__.py +0 -0
  425. dataface/core/schema/guidance.py +151 -0
  426. dataface/core/scoped_paths.py +59 -0
  427. dataface/core/serve/__init__.py +14 -0
  428. dataface/core/serve/bootstrap.py +39 -0
  429. dataface/core/serve/embedded.py +57 -0
  430. dataface/core/serve/port.py +129 -0
  431. dataface/core/serve/server.py +938 -0
  432. dataface/core/serve/templates/__init__.py +0 -0
  433. dataface/core/serve/templates/directory.yml +6 -0
  434. dataface/core/serve/templates/error.html.j2 +217 -0
  435. dataface/core/utils.py +121 -0
  436. dataface/core/validate.py +64 -0
  437. dataface/integrations/__init__.py +0 -0
  438. dataface/integrations/highlighting.py +351 -0
  439. dataface/integrations/markdown.py +537 -0
  440. dataface/py.typed +0 -0
  441. dataface-0.1.2.dist-info/METADATA +375 -0
  442. dataface-0.1.2.dist-info/RECORD +455 -0
  443. dataface-0.1.2.dist-info/WHEEL +4 -0
  444. dataface-0.1.2.dist-info/entry_points.txt +2 -0
  445. dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
  446. mdsvg/__init__.py +168 -0
  447. mdsvg/fonts.py +656 -0
  448. mdsvg/images.py +299 -0
  449. mdsvg/parser.py +629 -0
  450. mdsvg/playground.py +284 -0
  451. mdsvg/py.typed +2 -0
  452. mdsvg/renderer.py +1623 -0
  453. mdsvg/style.py +355 -0
  454. mdsvg/types.py +200 -0
  455. 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.")