datajunction 0.0.76__tar.gz → 0.0.78__tar.gz

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 (169) hide show
  1. {datajunction-0.0.76 → datajunction-0.0.78}/PKG-INFO +1 -1
  2. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/__about__.py +1 -1
  3. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/cli.py +49 -8
  4. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/mcp/server.py +124 -24
  5. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/mcp/tools.py +257 -30
  6. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/skills/datajunction.md +103 -116
  7. {datajunction-0.0.76 → datajunction-0.0.78}/tests/mcp/test_server_tools.py +133 -9
  8. {datajunction-0.0.76 → datajunction-0.0.78}/tests/mcp/test_tools.py +817 -13
  9. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_cli.py +143 -5
  10. {datajunction-0.0.76 → datajunction-0.0.78}/.coveragerc +0 -0
  11. {datajunction-0.0.76 → datajunction-0.0.78}/.gitignore +0 -0
  12. {datajunction-0.0.76 → datajunction-0.0.78}/.isort.cfg +0 -0
  13. {datajunction-0.0.76 → datajunction-0.0.78}/.pre-commit-config.yaml +0 -0
  14. {datajunction-0.0.76 → datajunction-0.0.78}/LICENSE.txt +0 -0
  15. {datajunction-0.0.76 → datajunction-0.0.78}/Makefile +0 -0
  16. {datajunction-0.0.76 → datajunction-0.0.78}/README.md +0 -0
  17. {datajunction-0.0.76 → datajunction-0.0.78}/claude_desktop_config.example.json +0 -0
  18. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/__init__.py +0 -0
  19. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/_base.py +0 -0
  20. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/_internal.py +0 -0
  21. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/admin.py +0 -0
  22. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/builder.py +0 -0
  23. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/client.py +0 -0
  24. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/compile.py +0 -0
  25. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/deployment.py +0 -0
  26. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/exceptions.py +0 -0
  27. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/mcp/__init__.py +0 -0
  28. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/mcp/cli.py +0 -0
  29. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/mcp/config.py +0 -0
  30. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/mcp/formatters.py +0 -0
  31. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/models.py +0 -0
  32. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/nodes.py +0 -0
  33. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/init_system_nodes.py +0 -0
  34. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/date.dimension.yaml +0 -0
  35. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/dimension_link.dimension.yaml +0 -0
  36. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/dj.yaml +0 -0
  37. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/is_active.dimension.yaml +0 -0
  38. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/materialization.dimension.yaml +0 -0
  39. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/node_type.dimension.yaml +0 -0
  40. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/node_without_description.metric.yaml +0 -0
  41. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/nodes.dimension.yaml +0 -0
  42. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/number_of_materializations.metric.yaml +0 -0
  43. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/number_of_nodes.metric.yaml +0 -0
  44. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/seed/nodes/user.dimension.yaml +0 -0
  45. {datajunction-0.0.76 → datajunction-0.0.78}/datajunction/tags.py +0 -0
  46. {datajunction-0.0.76 → datajunction-0.0.78}/pyproject.toml +0 -0
  47. {datajunction-0.0.76 → datajunction-0.0.78}/setup.cfg +0 -0
  48. {datajunction-0.0.76 → datajunction-0.0.78}/tests/__init__.py +0 -0
  49. {datajunction-0.0.76 → datajunction-0.0.78}/tests/conftest.py +0 -0
  50. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/deploy0/dj.yaml +0 -0
  51. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/deploy0/roads/companies.yaml +0 -0
  52. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/deploy0/roads/companies_dim.yaml +0 -0
  53. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/deploy0/roads/contractor.yaml +0 -0
  54. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/deploy0/roads/contractors.yaml +0 -0
  55. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/deploy0/roads/us_state.yaml +0 -0
  56. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/deploy0/roads/us_states.yaml +0 -0
  57. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/dj.yaml +0 -0
  58. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/avg_length_of_employment.metric.yaml +0 -0
  59. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/avg_repair_price.metric.yaml +0 -0
  60. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/avg_time_to_dispatch.metric.yaml +0 -0
  61. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/contractor.dimension.yaml +0 -0
  62. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/contractors.source.yaml +0 -0
  63. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/date.source.yaml +0 -0
  64. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/date_dim.dimension.yaml +0 -0
  65. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/dispatcher.dimension.yaml +0 -0
  66. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/dispatchers.source.yaml +0 -0
  67. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/hard_hat.dimension.yaml +0 -0
  68. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/hard_hat_state.source.yaml +0 -0
  69. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/hard_hats.source.yaml +0 -0
  70. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/local_hard_hats.dimension.yaml +0 -0
  71. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/municipality.source.yaml +0 -0
  72. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/municipality_dim.dimension.yaml +0 -0
  73. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/municipality_municipality_type.source.yaml +0 -0
  74. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/municipality_type.source.yaml +0 -0
  75. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/national_level_agg.transform.yaml +0 -0
  76. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/num_repair_orders.metric.yaml +0 -0
  77. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/regional_level_agg.transform.yaml +0 -0
  78. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/regional_repair_efficiency.metric.yaml +0 -0
  79. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/repair_order.dimension.yaml +0 -0
  80. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/repair_order_details.source.yaml +0 -0
  81. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/repair_order_transform.transform.yaml +0 -0
  82. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/repair_orders.source.yaml +0 -0
  83. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/repair_orders_cube.cube.yaml +0 -0
  84. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/repair_type.source.yaml +0 -0
  85. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/total_repair_cost.metric.yaml +0 -0
  86. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/total_repair_order_discounts.metric.yaml +0 -0
  87. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/us_region.source.yaml +0 -0
  88. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/us_state.dimension.yaml +0 -0
  89. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project1/roads/us_states.source.yaml +0 -0
  90. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project10/dj.yaml +0 -0
  91. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/avg_length_of_employment.metric.yaml +0 -0
  92. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/avg_repair_price.metric.yaml +0 -0
  93. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/avg_time_to_dispatch.metric.yaml +0 -0
  94. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/contractor.dimension.yaml +0 -0
  95. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/contractors.source.yaml +0 -0
  96. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/date.source.yaml +0 -0
  97. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/date_dim.dimension.yaml +0 -0
  98. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/dispatcher.dimension.yaml +0 -0
  99. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/dispatchers.source.yaml +0 -0
  100. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/dj.yaml +0 -0
  101. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/hard_hat.dimension.yaml +0 -0
  102. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/hard_hat_state.source.yaml +0 -0
  103. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/hard_hats.source.yaml +0 -0
  104. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/local_hard_hats.dimension.yaml +0 -0
  105. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/municipality.source.yaml +0 -0
  106. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/municipality_dim.dimension.yaml +0 -0
  107. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/municipality_municipality_type.source.yaml +0 -0
  108. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/municipality_type.source.yaml +0 -0
  109. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/national_level_agg.transform.yaml +0 -0
  110. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/num_repair_orders.metric.yaml +0 -0
  111. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/regional_level_agg.transform.yaml +0 -0
  112. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/regional_repair_efficiency.metric.yaml +0 -0
  113. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/repair_order.dimension.yaml +0 -0
  114. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/repair_order_details.source.yaml +0 -0
  115. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/repair_order_transform.transform.yaml +0 -0
  116. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/repair_orders.source.yaml +0 -0
  117. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/repair_orders_cube.cube.yaml +0 -0
  118. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/repair_type.source.yaml +0 -0
  119. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/total_repair_cost.metric.yaml +0 -0
  120. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/total_repair_order_discounts.metric.yaml +0 -0
  121. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/us_region.source.yaml +0 -0
  122. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/us_state.dimension.yaml +0 -0
  123. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project11/us_states.source.yaml +0 -0
  124. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project12/dj.yaml +0 -0
  125. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project12/roads/companies.source.yaml +0 -0
  126. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project12/roads/companies_dim.dimension.yaml +0 -0
  127. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project12/roads/contractor.dimension.yaml +0 -0
  128. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project12/roads/contractors.source.yaml +0 -0
  129. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project12/roads/us_state.dimension.yaml +0 -0
  130. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project12/roads/us_states.source.yaml +0 -0
  131. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project2/dj.yaml +0 -0
  132. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project2/some_node.source.yaml +0 -0
  133. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project3/dj.yaml +0 -0
  134. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project3/some_node.yaml +0 -0
  135. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project4/dj.yaml +0 -0
  136. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project4/very/very/deeply/nested/namespace/some_node.source.yaml +0 -0
  137. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project5/dj.yaml +0 -0
  138. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project5/some_node.a.b.c.source.yaml +0 -0
  139. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project6/dj.yaml +0 -0
  140. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project6/roads/contractor.dimension.yaml +0 -0
  141. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project6/roads/contractors.source.yaml +0 -0
  142. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project7/dj.yaml +0 -0
  143. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project7/roads/contractor.dimension.yaml +0 -0
  144. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project7/roads/contractors.source.yaml +0 -0
  145. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project8/dj.yaml +0 -0
  146. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project9/dj.yaml +0 -0
  147. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project9/roads/companies.source.yaml +0 -0
  148. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project9/roads/companies_dim.dimension.yaml +0 -0
  149. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project9/roads/contractor.dimension.yaml +0 -0
  150. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project9/roads/contractors.source.yaml +0 -0
  151. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project9/roads/us_state.dimension.yaml +0 -0
  152. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples/project9/roads/us_states.source.yaml +0 -0
  153. {datajunction-0.0.76 → datajunction-0.0.78}/tests/examples.py +0 -0
  154. {datajunction-0.0.76 → datajunction-0.0.78}/tests/mcp/README.md +0 -0
  155. {datajunction-0.0.76 → datajunction-0.0.78}/tests/mcp/__init__.py +0 -0
  156. {datajunction-0.0.76 → datajunction-0.0.78}/tests/mcp/test_cli.py +0 -0
  157. {datajunction-0.0.76 → datajunction-0.0.78}/tests/mcp/test_formatters.py +0 -0
  158. {datajunction-0.0.76 → datajunction-0.0.78}/tests/mcp/test_visualize_metrics.py +0 -0
  159. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test__internal.py +0 -0
  160. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_admin.py +0 -0
  161. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_base.py +0 -0
  162. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_builder.py +0 -0
  163. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_client.py +0 -0
  164. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_compile.py +0 -0
  165. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_deploy.py +0 -0
  166. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_generated_client.py +0 -0
  167. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_integration.py +0 -0
  168. {datajunction-0.0.76 → datajunction-0.0.78}/tests/test_models.py +0 -0
  169. {datajunction-0.0.76 → datajunction-0.0.78}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datajunction
3
- Version: 0.0.76
3
+ Version: 0.0.78
4
4
  Summary: DataJunction client library for connecting to a DataJunction server
5
5
  Project-URL: repository, https://github.com/DataJunction/dj
6
6
  Author-email: DataJunction Authors <yian.shang@gmail.com>
@@ -2,4 +2,4 @@
2
2
  Version for Hatch
3
3
  """
4
4
 
5
- __version__ = "0.0.76"
5
+ __version__ = "0.0.78"
@@ -1266,6 +1266,18 @@ class DJCLI:
1266
1266
  dest="mcp",
1267
1267
  help="Skip MCP server configuration",
1268
1268
  )
1269
+ setup_claude_parser.add_argument(
1270
+ "--agents",
1271
+ action="store_true",
1272
+ default=True,
1273
+ help="Install DJ subagent to ~/.claude/agents/ (default: True)",
1274
+ )
1275
+ setup_claude_parser.add_argument(
1276
+ "--no-agents",
1277
+ action="store_false",
1278
+ dest="agents",
1279
+ help="Skip subagent installation",
1280
+ )
1269
1281
 
1270
1282
  return parser
1271
1283
 
@@ -1356,6 +1368,7 @@ class DJCLI:
1356
1368
  output_dir=Path(args.output),
1357
1369
  skills=args.skills,
1358
1370
  mcp=args.mcp,
1371
+ agents=args.agents,
1359
1372
  )
1360
1373
  else:
1361
1374
  parser.print_help() # pragma: no cover
@@ -1377,6 +1390,7 @@ class DJCLI:
1377
1390
  output_dir: Path,
1378
1391
  skills: bool = True,
1379
1392
  mcp: bool = True,
1393
+ agents: bool = True,
1380
1394
  ):
1381
1395
  """Configure Claude Code integration with DJ."""
1382
1396
  import json
@@ -1464,23 +1478,50 @@ class DJCLI:
1464
1478
  "[red]✗ Bundled skill not found. Please ensure datajunction is properly installed.[/red]",
1465
1479
  )
1466
1480
 
1481
+ # Install subagent if requested
1482
+ if agents:
1483
+ agents_dir = Path.home() / ".claude" / "agents"
1484
+ agents_dir.mkdir(parents=True, exist_ok=True)
1485
+ agent_file = agents_dir / "dj.md"
1486
+
1487
+ console.print("[bold]🤖 Installing DJ subagent[/bold]\n")
1488
+
1489
+ subagent_content = """\
1490
+ ---
1491
+ name: dj
1492
+ description: >
1493
+ DataJunction semantic layer expert. Use proactively for any DataJunction
1494
+ or DJ work — querying metrics, exploring nodes and dimensions, building
1495
+ SQL, understanding lineage, and semantic layer design.
1496
+ skills:
1497
+ - datajunction
1498
+ model: inherit
1499
+ ---
1500
+ """
1501
+ with open(agent_file, "w") as f:
1502
+ f.write(subagent_content)
1503
+
1504
+ console.print(f"[green]✓ Installed subagent to {agent_file}[/green]\n")
1505
+
1467
1506
  # Setup MCP if requested
1468
1507
  if mcp:
1469
1508
  self._setup_mcp_server(console)
1470
1509
 
1471
1510
  # Final success message
1472
- if skills and mcp:
1511
+ anything_installed = skills or mcp or agents
1512
+ if anything_installed: # pragma: no branch
1473
1513
  console.print(
1474
1514
  "\n[bold green]✓ Claude Code integration complete[/bold green]",
1475
1515
  )
1516
+ parts = []
1517
+ if skills:
1518
+ parts.append("skill")
1519
+ if agents:
1520
+ parts.append("subagent")
1521
+ if mcp:
1522
+ parts.append("MCP server")
1476
1523
  console.print(
1477
- "[dim]Skills and MCP server are now configured. Restart Claude Code to load changes.[/dim]",
1478
- )
1479
- elif skills:
1480
- console.print("\n[dim]Skills are now available in Claude Code.[/dim]")
1481
- elif mcp: # pragma: no branch
1482
- console.print(
1483
- "\n[dim]MCP server configured. Restart Claude Code to load changes.[/dim]",
1524
+ f"[dim]{', '.join(parts).capitalize()} installed. Restart Claude Code to load changes.[/dim]",
1484
1525
  )
1485
1526
 
1486
1527
  except Exception as e: # pragma: no cover
@@ -42,38 +42,63 @@ async def list_tools() -> list[types.Tool]:
42
42
  types.Tool(
43
43
  name="search_nodes",
44
44
  description=(
45
- "Search for DataJunction nodes (metrics, dimensions, cubes, sources, transforms) "
46
- "by name fragment or other properties. Returns a list of matching nodes with "
47
- "their basic information including status, tags, and owners. "
48
- "TIP: Use the 'namespace' parameter to narrow searches - namespaces are the primary "
49
- "organizational structure in DJ (e.g., 'demo.metrics', 'common.dimensions')."
45
+ "Search for DataJunction nodes (metrics, dimensions, cubes, sources, transforms). "
46
+ "All filters are optional and combinable: name fragment, node type, namespace, tags, "
47
+ "status (valid/invalid), mode (published/draft), owner, and materialization. "
48
+ "TIP: Use 'namespace' to narrow searches to a domain. "
49
+ "Use 'statuses: [invalid]' to find broken nodes. "
50
+ "Use 'mode: draft' to see in-progress work on a branch. "
51
+ "Use 'has_materialization: true' to find cubes with materializations."
50
52
  ),
51
53
  inputSchema={
52
54
  "type": "object",
53
55
  "properties": {
54
56
  "query": {
55
57
  "type": "string",
56
- "description": "Search term - fragment of node name to search for (e.g., 'revenue', 'user')",
58
+ "description": "Optional: Fragment of node name to search for (e.g., 'revenue', 'user'). Can be omitted when filtering by tag.",
57
59
  },
58
60
  "node_type": {
59
61
  "type": "string",
60
62
  "enum": ["metric", "dimension", "cube", "source", "transform"],
61
- "description": "Optional: Filter results to specific node type",
63
+ "description": "Optional: Filter results to a specific node type",
62
64
  },
63
65
  "namespace": {
64
66
  "type": "string",
65
67
  "description": (
66
- "Optional: Filter results to specific namespace (e.g., 'demo.metrics', 'common.dimensions'). "
67
- "HIGHLY RECOMMENDED - namespaces are the primary way to organize nodes in DJ. "
68
- "Use this to narrow search results to a specific domain or area."
68
+ "Optional: Filter results to a specific namespace (e.g., 'demo.metrics', 'common.dimensions'). "
69
+ "HIGHLY RECOMMENDED - namespaces are the primary way to organize nodes in DJ."
69
70
  ),
70
71
  },
72
+ "tags": {
73
+ "type": "array",
74
+ "items": {"type": "string"},
75
+ "description": "Optional: Filter to nodes tagged with ALL of these tag names (e.g., ['revenue', 'core'])",
76
+ },
77
+ "statuses": {
78
+ "type": "array",
79
+ "items": {"type": "string", "enum": ["valid", "invalid"]},
80
+ "description": "Optional: Filter by node status (e.g., ['valid'] for healthy nodes, ['invalid'] to find broken ones)",
81
+ },
82
+ "mode": {
83
+ "type": "string",
84
+ "enum": ["published", "draft"],
85
+ "description": "Optional: Filter by mode — 'published' for production nodes, 'draft' for in-progress work on a branch",
86
+ },
87
+ "owned_by": {
88
+ "type": "string",
89
+ "description": "Optional: Filter to nodes owned by this username or email",
90
+ },
91
+ "has_materialization": {
92
+ "type": "boolean",
93
+ "default": False,
94
+ "description": "Optional: If true, return only nodes that have materializations configured",
95
+ },
71
96
  "limit": {
72
97
  "type": "integer",
73
98
  "default": 100,
74
99
  "minimum": 1,
75
100
  "maximum": 1000,
76
- "description": "Maximum number of results to return (default: 100, max: 1000)",
101
+ "description": "Maximum number of results to return (default: 100)",
77
102
  },
78
103
  "prefer_main_branch": {
79
104
  "type": "boolean",
@@ -81,11 +106,9 @@ async def list_tools() -> list[types.Tool]:
81
106
  "description": (
82
107
  "When true and namespace is provided, automatically searches the .main branch "
83
108
  "(e.g., 'finance' becomes 'finance.main'). Set to false to search all branches."
84
- "Default: true."
85
109
  ),
86
110
  },
87
111
  },
88
- "required": ["query"],
89
112
  },
90
113
  ),
91
114
  types.Tool(
@@ -107,22 +130,27 @@ async def list_tools() -> list[types.Tool]:
107
130
  },
108
131
  ),
109
132
  types.Tool(
110
- name="get_common_dimensions",
133
+ name="get_common",
111
134
  description=(
112
- "Find dimensions that are available across multiple metrics. "
113
- "Use this to determine which dimensions you can use when querying multiple metrics together. "
114
- "Returns the list of common dimensions that work across all specified metrics."
135
+ "Bidirectional semantic compatibility lookup. "
136
+ "Pass 'metrics' to find which dimensions are shared across all those metrics (i.e. what can I slice these metrics by?). "
137
+ "Pass 'dimensions' to find which metrics can be queried by all those dimensions (i.e. what can I analyze by this dimension?). "
138
+ "Provide exactly one of metrics or dimensions."
115
139
  ),
116
140
  inputSchema={
117
141
  "type": "object",
118
142
  "properties": {
119
- "metric_names": {
143
+ "metrics": {
144
+ "type": "array",
145
+ "items": {"type": "string"},
146
+ "description": "List of metric node names — returns the dimensions common across all of them",
147
+ },
148
+ "dimensions": {
120
149
  "type": "array",
121
150
  "items": {"type": "string"},
122
- "description": "List of metric node names to analyze (e.g., ['finance.revenue', 'growth.users'])",
151
+ "description": "List of dimension attribute names returns metrics compatible with all of them",
123
152
  },
124
153
  },
125
- "required": ["metric_names"],
126
154
  },
127
155
  ),
128
156
  types.Tool(
@@ -264,6 +292,58 @@ async def list_tools() -> list[types.Tool]:
264
292
  "required": ["node_name"],
265
293
  },
266
294
  ),
295
+ types.Tool(
296
+ name="get_query_plan",
297
+ description=(
298
+ "Get the query execution plan for a set of metrics, showing how DJ decomposes them "
299
+ "into grain groups and atomic aggregation components. "
300
+ "A grain group is a set of metrics that share a common dimensional grain and can be "
301
+ "computed together in a single SQL query. "
302
+ "Each component is an atomic aggregation (e.g., SUM(amount), COUNT(*)) that feeds "
303
+ "into the final metric formula. "
304
+ "Use this to understand multi-metric query structure, debug unexpected results, "
305
+ "validate semantic model design, or explain how a metric is computed."
306
+ ),
307
+ inputSchema={
308
+ "type": "object",
309
+ "properties": {
310
+ "metrics": {
311
+ "type": "array",
312
+ "items": {"type": "string"},
313
+ "description": "List of metric node names to analyze (e.g., ['finance.daily_revenue', 'growth.new_users'])",
314
+ },
315
+ "dimensions": {
316
+ "type": "array",
317
+ "items": {"type": "string"},
318
+ "description": "Optional: List of dimensions to group by — affects grain group assignment",
319
+ },
320
+ "filters": {
321
+ "type": "array",
322
+ "items": {"type": "string"},
323
+ "description": "Optional: SQL filter conditions",
324
+ },
325
+ "dialect": {
326
+ "type": "string",
327
+ "description": "Optional: Target SQL dialect (e.g., 'spark', 'trino', 'postgres')",
328
+ },
329
+ "use_materialized": {
330
+ "type": "boolean",
331
+ "default": True,
332
+ "description": "Optional: Whether to use materialized tables when available (default: true)",
333
+ },
334
+ "include_temporal_filters": {
335
+ "type": "boolean",
336
+ "default": False,
337
+ "description": "Optional: Include temporal partition filters if the metrics resolve to a cube with partitions",
338
+ },
339
+ "lookback_window": {
340
+ "type": "string",
341
+ "description": "Optional: Lookback window for temporal filters (e.g., '3 DAY', '1 WEEK'). Only used when include_temporal_filters is true.",
342
+ },
343
+ },
344
+ "required": ["metrics"],
345
+ },
346
+ ),
267
347
  types.Tool(
268
348
  name="visualize_metrics",
269
349
  description=(
@@ -350,9 +430,14 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
350
430
 
351
431
  elif name == "search_nodes":
352
432
  result = await tools.search_nodes(
353
- query=arguments["query"],
433
+ query=arguments.get("query", ""),
354
434
  node_type=arguments.get("node_type"),
355
435
  namespace=arguments.get("namespace"),
436
+ tags=arguments.get("tags"),
437
+ statuses=arguments.get("statuses"),
438
+ mode=arguments.get("mode"),
439
+ owned_by=arguments.get("owned_by"),
440
+ has_materialization=arguments.get("has_materialization", False),
356
441
  limit=arguments.get("limit", 100),
357
442
  prefer_main_branch=arguments.get("prefer_main_branch", True),
358
443
  )
@@ -362,9 +447,10 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
362
447
  name=arguments["name"],
363
448
  )
364
449
 
365
- elif name == "get_common_dimensions":
366
- result = await tools.get_common_dimensions(
367
- metric_names=arguments["metric_names"],
450
+ elif name == "get_common":
451
+ result = await tools.get_common(
452
+ metrics=arguments.get("metrics"),
453
+ dimensions=arguments.get("dimensions"),
368
454
  )
369
455
 
370
456
  elif name == "build_metric_sql":
@@ -387,6 +473,20 @@ async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
387
473
  limit=arguments.get("limit"),
388
474
  )
389
475
 
476
+ elif name == "get_query_plan":
477
+ result = await tools.get_query_plan(
478
+ metrics=arguments["metrics"],
479
+ dimensions=arguments.get("dimensions"),
480
+ filters=arguments.get("filters"),
481
+ dialect=arguments.get("dialect"),
482
+ use_materialized=arguments.get("use_materialized", True),
483
+ include_temporal_filters=arguments.get(
484
+ "include_temporal_filters",
485
+ False,
486
+ ),
487
+ lookback_window=arguments.get("lookback_window"),
488
+ )
489
+
390
490
  elif name == "get_node_lineage":
391
491
  result = await tools.get_node_lineage(
392
492
  node_name=arguments["node_name"],