tinybird 0.0.1.dev242__tar.gz → 0.0.1.dev245__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.

Potentially problematic release.


This version of tinybird might be problematic. Click here for more details.

Files changed (126) hide show
  1. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/datafile/common.py +28 -3
  3. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/__cli__.py +2 -2
  4. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/agent.py +38 -11
  5. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/prompts.py +36 -0
  6. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/tools/create_datafile.py +17 -15
  7. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/tools/plan.py +0 -1
  8. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/utils.py +2 -0
  9. tinybird-0.0.1.dev245/tinybird/tb/modules/build.py +205 -0
  10. tinybird-0.0.1.dev242/tinybird/tb/modules/build.py → tinybird-0.0.1.dev245/tinybird/tb/modules/build_common.py +166 -354
  11. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/cli.py +1 -1
  12. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/secret.py +2 -50
  13. tinybird-0.0.1.dev245/tinybird/tb/modules/secret_common.py +56 -0
  14. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/test.py +1 -1
  15. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird.egg-info/PKG-INFO +1 -1
  16. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird.egg-info/SOURCES.txt +2 -0
  17. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/setup.cfg +0 -0
  18. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/__cli__.py +0 -0
  19. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/ch_utils/constants.py +0 -0
  20. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/ch_utils/engine.py +0 -0
  21. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/check_pypi.py +0 -0
  22. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/client.py +0 -0
  23. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/config.py +0 -0
  24. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/connectors.py +0 -0
  25. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/context.py +0 -0
  26. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/datafile/exceptions.py +0 -0
  27. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/datafile/parse_connection.py +0 -0
  28. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/datafile/parse_datasource.py +0 -0
  29. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/datafile/parse_pipe.py +0 -0
  30. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/datatypes.py +0 -0
  31. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/feedback_manager.py +0 -0
  32. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/git_settings.py +0 -0
  33. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/prompts.py +0 -0
  34. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/sql.py +0 -0
  35. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/sql_template.py +0 -0
  36. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/sql_template_fmt.py +0 -0
  37. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/sql_toolset.py +0 -0
  38. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/syncasync.py +0 -0
  39. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/check_pypi.py +0 -0
  40. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/cli.py +0 -0
  41. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/client.py +0 -0
  42. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/config.py +0 -0
  43. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/__init__.py +0 -0
  44. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/animations.py +0 -0
  45. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/banner.py +0 -0
  46. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/memory.py +0 -0
  47. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/models.py +0 -0
  48. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/tools/__init__.py +0 -0
  49. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/tools/explore.py +0 -0
  50. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/agent/tools/preview_datafile.py +0 -0
  51. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/cicd.py +0 -0
  52. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/common.py +0 -0
  53. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/config.py +0 -0
  54. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/connection.py +0 -0
  55. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/copy.py +0 -0
  56. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/create.py +0 -0
  57. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/build.py +0 -0
  58. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/build_common.py +0 -0
  59. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  60. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  61. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/diff.py +0 -0
  62. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/fixture.py +0 -0
  63. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/format_common.py +0 -0
  64. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  65. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  66. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  67. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/playground.py +0 -0
  68. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datafile/pull.py +0 -0
  69. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/datasource.py +0 -0
  70. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/deployment.py +0 -0
  71. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/deprecations.py +0 -0
  72. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/dev_server.py +0 -0
  73. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/endpoint.py +0 -0
  74. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/exceptions.py +0 -0
  75. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/feedback_manager.py +0 -0
  76. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/info.py +0 -0
  77. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/infra.py +0 -0
  78. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/job.py +0 -0
  79. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/llm.py +0 -0
  80. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/llm_utils.py +0 -0
  81. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/local.py +0 -0
  82. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/local_common.py +0 -0
  83. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/login.py +0 -0
  84. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/logout.py +0 -0
  85. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/materialization.py +0 -0
  86. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/mock.py +0 -0
  87. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/open.py +0 -0
  88. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/pipe.py +0 -0
  89. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/project.py +0 -0
  90. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/regions.py +0 -0
  91. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/shell.py +0 -0
  92. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/sink.py +0 -0
  93. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/table.py +0 -0
  94. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/telemetry.py +0 -0
  95. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  96. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  97. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/token.py +0 -0
  98. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/watch.py +0 -0
  99. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/workspace.py +0 -0
  100. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb/modules/workspace_members.py +0 -0
  101. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli.py +0 -0
  102. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/auth.py +0 -0
  103. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/branch.py +0 -0
  104. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/cicd.py +0 -0
  105. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/cli.py +0 -0
  106. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/common.py +0 -0
  107. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/config.py +0 -0
  108. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/connection.py +0 -0
  109. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/datasource.py +0 -0
  110. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/exceptions.py +0 -0
  111. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/fmt.py +0 -0
  112. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/job.py +0 -0
  113. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/pipe.py +0 -0
  114. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/regions.py +0 -0
  115. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/tag.py +0 -0
  116. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/telemetry.py +0 -0
  117. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/test.py +0 -0
  118. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  119. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  120. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/workspace.py +0 -0
  121. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  122. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird/tornado_template.py +0 -0
  123. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird.egg-info/dependency_links.txt +0 -0
  124. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird.egg-info/entry_points.txt +0 -0
  125. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird.egg-info/requires.txt +0 -0
  126. {tinybird-0.0.1.dev242 → tinybird-0.0.1.dev245}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev242
3
+ Version: 0.0.1.dev245
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -1698,11 +1698,36 @@ def parse(
1698
1698
  def version(*args: str, **kwargs: Any) -> None:
1699
1699
  pass # whatever, it's deprecated
1700
1700
 
1701
- @not_supported_yet()
1702
1701
  def shared_with(*args: str, **kwargs: Any) -> None:
1702
+ # Count total workspaces collected
1703
+ total_workspaces = 0
1704
+
1703
1705
  for entries in args:
1704
- # In case they specify multiple workspaces
1705
- doc.shared_with += [workspace.strip() for workspace in entries.splitlines()]
1706
+ # In case they specify multiple workspaces, handle both line-separated and comma-separated values
1707
+ lines = entries.splitlines()
1708
+ for line in lines:
1709
+ # Split by comma and strip whitespace from each workspace name
1710
+ workspaces = [workspace.strip().rstrip(",") for workspace in line.split(",") if workspace.strip()]
1711
+ doc.shared_with += workspaces
1712
+ total_workspaces += len(workspaces)
1713
+
1714
+ # Add warning once if any workspaces were found
1715
+ if total_workspaces > 0:
1716
+ warnings.append(
1717
+ DatafileParseWarning(
1718
+ message=(
1719
+ f"{kwargs['cmd'].upper()} is not fully implemented in Forward and will be ignored while this warning is present."
1720
+ )
1721
+ )
1722
+ )
1723
+
1724
+ # Validate that at least one workspace was provided
1725
+ if total_workspaces == 0:
1726
+ raise DatafileSyntaxError(
1727
+ "SHARED_WITH requires at least one workspace name",
1728
+ lineno=kwargs["lineno"],
1729
+ pos=1,
1730
+ )
1706
1731
 
1707
1732
  def __init_engine(v: str):
1708
1733
  if not parser_state.current_node:
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
4
4
  __url__ = 'https://www.tinybird.co/docs/forward/commands'
5
5
  __author__ = 'Tinybird'
6
6
  __author_email__ = 'support@tinybird.co'
7
- __version__ = '0.0.1.dev242'
8
- __revision__ = '62ad714'
7
+ __version__ = '0.0.1.dev245'
8
+ __revision__ = 'f0c439a'
@@ -1,5 +1,8 @@
1
1
  import sys
2
+ import uuid
2
3
  from datetime import datetime
4
+ from functools import partial
5
+ from typing import Any
3
6
 
4
7
  import click
5
8
  from prompt_toolkit import prompt
@@ -26,20 +29,29 @@ from tinybird.tb.modules.agent.animations import ThinkingAnimation
26
29
  from tinybird.tb.modules.agent.banner import display_banner
27
30
  from tinybird.tb.modules.agent.memory import clear_history, load_history
28
31
  from tinybird.tb.modules.agent.models import create_model
29
- from tinybird.tb.modules.agent.prompts import datafile_instructions, plan_instructions, sql_instructions
32
+ from tinybird.tb.modules.agent.prompts import (
33
+ datafile_instructions,
34
+ plan_instructions,
35
+ resources_prompt,
36
+ sql_instructions,
37
+ )
30
38
  from tinybird.tb.modules.agent.tools.create_datafile import create_datafile
31
39
  from tinybird.tb.modules.agent.tools.explore import explore_data
32
40
  from tinybird.tb.modules.agent.tools.plan import plan
33
41
  from tinybird.tb.modules.agent.tools.preview_datafile import preview_datafile
34
42
  from tinybird.tb.modules.agent.utils import TinybirdAgentContext
43
+ from tinybird.tb.modules.build_common import process as build_process
44
+ from tinybird.tb.modules.exceptions import CLIBuildException
35
45
  from tinybird.tb.modules.feedback_manager import FeedbackManager
46
+ from tinybird.tb.modules.local_common import get_tinybird_local_client
47
+ from tinybird.tb.modules.project import Project
36
48
 
37
49
 
38
50
  class TinybirdAgent:
39
- def __init__(self, token: str, host: str, folder: str):
51
+ def __init__(self, token: str, host: str, project: Project):
40
52
  self.token = token
41
53
  self.host = host
42
- self.folder = folder
54
+ self.project = project
43
55
  self.messages: list[ModelMessage] = []
44
56
  self.agent = Agent(
45
57
  model=create_model(token, host),
@@ -83,9 +95,10 @@ You have access to the following tools:
83
95
  4. Without asking, use the `create_datafile` tool to create the datafile, because it will ask for confirmation before creating the file.
84
96
  5. Check the result of the `create_datafile` tool to see if the datafile was created successfully.
85
97
  6. If the datafile was created successfully, report the result to the user.
86
- 7. If the datafile was not created successfully, finish the process and just wait for a new user prompt.
98
+ 7. If the datafile was not created, finish the process and just wait for a new user prompt.
99
+ 8. If the datafile was created successfully, but the built failed, try to fix the error and repeat the process.
87
100
 
88
- IMPORTANT: If the user cancels some of the steps or there is an error, DO NOT continue with the plan. Stop the process and wait for the user before using any other tool.
101
+ IMPORTANT: If the user cancels some of the steps or there is an error in file creation, DO NOT continue with the plan. Stop the process and wait for the user before using any other tool.
89
102
 
90
103
  # When planning the creation or update of resources:
91
104
  {plan_instructions}
@@ -134,9 +147,10 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
134
147
  """Keep only the last 5 messages to manage token usage."""
135
148
  return self.messages[-5:] if len(self.messages) > 5 else self.messages
136
149
 
137
- def run(self, user_prompt: str) -> None:
150
+ def run(self, user_prompt: str, project: Project) -> None:
151
+ user_prompt = f"{user_prompt}\n\n# Existing resources in the project:\n{resources_prompt(project)}"
138
152
  client = TinyB(token=self.token, host=self.host)
139
-
153
+ folder = self.project.folder
140
154
  thinking_animation = ThinkingAnimation(message="Chirping", delay=0.15)
141
155
  thinking_animation.start()
142
156
 
@@ -145,7 +159,9 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
145
159
  deps=TinybirdAgentContext(
146
160
  # context does not support the whole client, so we need to pass only the functions we need
147
161
  explore_data=client.explore_data,
148
- folder=self.folder,
162
+ build_project=partial(build_project, folder=folder),
163
+ get_project_files=project.get_project_files,
164
+ folder=folder,
149
165
  thinking_animation=thinking_animation,
150
166
  ),
151
167
  message_history=self.messages,
@@ -158,11 +174,13 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
158
174
  click.echo("\n")
159
175
 
160
176
 
161
- def run_agent(token: str, host: str, folder: str):
177
+ def run_agent(config: dict[str, Any], project: Project):
162
178
  display_banner()
163
179
 
164
180
  try:
165
- agent = TinybirdAgent(token, host, folder)
181
+ token = config["token"]
182
+ host = config["host"]
183
+ agent = TinybirdAgent(token, host, project)
166
184
  click.echo()
167
185
  click.echo(FeedbackManager.success(message="Welcome to Tinybird Code"))
168
186
  click.echo(FeedbackManager.info(message="Describe what you want to create and I'll help you build it"))
@@ -207,7 +225,7 @@ def run_agent(token: str, host: str, folder: str):
207
225
  elif user_input.strip() == "":
208
226
  continue
209
227
  else:
210
- agent.run(user_input)
228
+ agent.run(user_input, project)
211
229
 
212
230
  except KeyboardInterrupt:
213
231
  click.echo(FeedbackManager.info(message="Goodbye!"))
@@ -219,3 +237,12 @@ def run_agent(token: str, host: str, folder: str):
219
237
  except Exception as e:
220
238
  click.echo(FeedbackManager.error(message=f"Error: {e}"))
221
239
  sys.exit(1)
240
+
241
+
242
+ def build_project(folder: str) -> None:
243
+ workspace_name = f"tmp_workspace_{uuid.uuid4()}"
244
+ project = Project(folder, workspace_name=workspace_name)
245
+ local_client = get_tinybird_local_client({"path": folder, "name": workspace_name}, test=True, silent=True)
246
+ build_error = build_process(project=project, tb_client=local_client, watch=False, silent=True, exit_on_error=False)
247
+ if build_error:
248
+ raise CLIBuildException(build_error)
@@ -1,3 +1,7 @@
1
+ from pathlib import Path
2
+
3
+ from tinybird.tb.modules.project import Project
4
+
1
5
  plan_instructions = """
2
6
  When asked to create a plan, you MUST respond with this EXACT format and NOTHING ELSE:
3
7
 
@@ -81,3 +85,35 @@ datafile_instructions = """
81
85
  - Datasource files will be created under the `/datasources` folder.
82
86
  </datafile_instructions>
83
87
  """
88
+
89
+
90
+ def resources_prompt(project: Project) -> str:
91
+ files = project.get_project_files()
92
+
93
+ if not files:
94
+ return "No resources found"
95
+
96
+ paths = [Path(file_path) for file_path in files]
97
+
98
+ def get_resource_type(path: Path) -> str:
99
+ if path.suffix.lower() == ".pipe":
100
+ return Project.get_pipe_type(str(path))
101
+ elif path.suffix.lower() == ".datasource":
102
+ return "datasource"
103
+ elif path.suffix.lower() == ".connection":
104
+ return "connection"
105
+ return "unknown"
106
+
107
+ return "\n".join(
108
+ [
109
+ f"""
110
+ <resource>
111
+ <path>{file_path.relative_to(project.folder)}</path>
112
+ <type>{get_resource_type(file_path)}</type>
113
+ <name>{file_path.stem}</name>
114
+ <content>{file_path.read_text()}</content>
115
+ </resource>
116
+ """
117
+ for file_path in paths
118
+ ]
119
+ )
@@ -4,13 +4,16 @@ import click
4
4
  from pydantic_ai import RunContext
5
5
 
6
6
  from tinybird.tb.modules.agent.utils import Datafile, TinybirdAgentContext, show_options
7
+ from tinybird.tb.modules.exceptions import CLIBuildException
8
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
7
9
 
8
10
 
9
- def get_resource_confirmation(resource: Datafile) -> bool:
11
+ def get_resource_confirmation(resource: Datafile, exists: bool) -> bool:
10
12
  """Get user confirmation for creating a resource"""
11
13
  while True:
14
+ action = "create" if not exists else "update"
12
15
  result = show_options(
13
- options=[f"Yes, create {resource.type} '{resource.name}'", "No, and tell Tinybird Code what to do"],
16
+ options=[f"Yes, {action} {resource.type} '{resource.name}'", "No, and tell Tinybird Code what to do"],
14
17
  title=f"What would you like to do with {resource.type} '{resource.name}'?",
15
18
  )
16
19
 
@@ -36,29 +39,28 @@ def create_datafile(ctx: RunContext[TinybirdAgentContext], resource: Datafile) -
36
39
  """
37
40
  try:
38
41
  ctx.deps.thinking_animation.stop()
39
- click.echo()
40
42
  click.echo(resource.content)
41
- confirmation = get_resource_confirmation(resource)
43
+ resource.pathname = resource.pathname.removeprefix("/")
44
+ path = Path(ctx.deps.folder) / resource.pathname
45
+ exists = str(path) in ctx.deps.get_project_files()
46
+ confirmation = get_resource_confirmation(resource, exists)
42
47
  ctx.deps.thinking_animation.start()
43
48
 
44
49
  if not confirmation:
45
50
  return f"Resource {resource.pathname} was not created. User cancelled creation."
46
51
 
47
- resource.pathname = resource.pathname.removeprefix("/")
48
-
49
- path = Path(ctx.deps.folder) / resource.pathname
50
-
51
52
  folder_path = path.parent
52
-
53
- if not folder_path.exists():
54
- folder_path.mkdir()
55
-
56
- if not path.exists():
57
- path.touch()
53
+ folder_path.mkdir(parents=True, exist_ok=True)
54
+ path.touch(exist_ok=True)
58
55
 
59
56
  path.write_text(resource.content)
60
-
57
+ ctx.deps.build_project()
61
58
  return f"Created {resource.pathname}"
62
59
 
60
+ except CLIBuildException as e:
61
+ ctx.deps.thinking_animation.stop()
62
+ click.echo(FeedbackManager.error(message=e))
63
+ ctx.deps.thinking_animation.start()
64
+ return f"Error building project: {e}"
63
65
  except Exception as e:
64
66
  return f"Error creating {resource.pathname}: {e}"
@@ -34,7 +34,6 @@ def plan(ctx: RunContext[TinybirdAgentContext], plan: str) -> str:
34
34
  """
35
35
  try:
36
36
  ctx.deps.thinking_animation.stop()
37
- click.echo()
38
37
  click.echo(plan)
39
38
  confirmation = get_plan_confirmation()
40
39
  ctx.deps.thinking_animation.start()
@@ -20,7 +20,9 @@ from pydantic import BaseModel, Field
20
20
  class TinybirdAgentContext(BaseModel):
21
21
  explore_data: Callable[[str], str]
22
22
  folder: str
23
+ build_project: Callable[[], None]
23
24
  thinking_animation: Any
25
+ get_project_files: Callable[[], List[str]]
24
26
 
25
27
 
26
28
  default_style = Style.from_dict(
@@ -0,0 +1,205 @@
1
+ import threading
2
+ import time
3
+ from copy import deepcopy
4
+ from functools import partial
5
+ from pathlib import Path
6
+ from typing import Callable, List
7
+ from urllib.parse import urlencode
8
+
9
+ import click
10
+
11
+ import tinybird.context as context
12
+ from tinybird.datafile.exceptions import ParseException
13
+ from tinybird.datafile.parse_datasource import parse_datasource
14
+ from tinybird.datafile.parse_pipe import parse_pipe
15
+ from tinybird.tb.client import TinyB
16
+ from tinybird.tb.modules.build_common import process
17
+ from tinybird.tb.modules.cli import cli
18
+ from tinybird.tb.modules.config import CLIConfig
19
+ from tinybird.tb.modules.datafile.playground import folder_playground
20
+ from tinybird.tb.modules.dev_server import BuildStatus, start_server
21
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
22
+ from tinybird.tb.modules.project import Project
23
+ from tinybird.tb.modules.secret_common import load_secrets
24
+ from tinybird.tb.modules.shell import Shell, print_table_formatted
25
+ from tinybird.tb.modules.watch import watch_files, watch_project
26
+
27
+
28
+ @cli.command()
29
+ @click.option("--watch", is_flag=True, default=False, help="Watch for changes and rebuild automatically")
30
+ @click.pass_context
31
+ def build(ctx: click.Context, watch: bool) -> None:
32
+ """
33
+ Validate and build the project server side.
34
+ """
35
+ project: Project = ctx.ensure_object(dict)["project"]
36
+ tb_client: TinyB = ctx.ensure_object(dict)["client"]
37
+
38
+ if project.has_deeper_level():
39
+ click.echo(
40
+ FeedbackManager.warning(
41
+ message="Your project contains directories nested deeper than the default scan depth (max_depth=3). "
42
+ "Files in these deeper directories will not be processed. "
43
+ "To include all nested directories, run `tb --max-depth <depth> <cmd>` with a higher depth value."
44
+ )
45
+ )
46
+
47
+ load_secrets(project, tb_client)
48
+ click.echo(FeedbackManager.highlight_building_project())
49
+ process(project=project, tb_client=tb_client, watch=False)
50
+ if watch:
51
+ run_watch(
52
+ project=project,
53
+ tb_client=tb_client,
54
+ process=partial(process, project=project, tb_client=tb_client, watch=True),
55
+ )
56
+
57
+
58
+ @cli.command("dev", help="Build the project server side and watch for changes.")
59
+ @click.option("--data-origin", type=str, default="", help="Data origin: local or cloud")
60
+ @click.option("--ui", is_flag=True, default=False, help="Connect your local project to Tinybird UI")
61
+ @click.pass_context
62
+ def dev(ctx: click.Context, data_origin: str, ui: bool) -> None:
63
+ if data_origin == "cloud":
64
+ return dev_cloud(ctx)
65
+ project: Project = ctx.ensure_object(dict)["project"]
66
+ tb_client: TinyB = ctx.ensure_object(dict)["client"]
67
+ build_status = BuildStatus()
68
+ if ui:
69
+ server_thread = threading.Thread(
70
+ target=start_server, args=(project, tb_client, process, build_status), daemon=True
71
+ )
72
+ server_thread.start()
73
+ # Wait for the server to start
74
+ time.sleep(0.5)
75
+
76
+ load_secrets(project, tb_client)
77
+ click.echo(FeedbackManager.highlight_building_project())
78
+ process(project=project, tb_client=tb_client, watch=True, build_status=build_status)
79
+ run_watch(
80
+ project=project,
81
+ tb_client=tb_client,
82
+ process=partial(process, project=project, tb_client=tb_client, build_status=build_status),
83
+ )
84
+
85
+
86
+ def run_watch(project: Project, tb_client: TinyB, process: Callable) -> None:
87
+ shell = Shell(project=project, tb_client=tb_client, playground=False)
88
+ click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
89
+ watcher_thread = threading.Thread(
90
+ target=watch_project,
91
+ args=(shell, process, project),
92
+ daemon=True,
93
+ )
94
+ watcher_thread.start()
95
+ shell.run()
96
+
97
+
98
+ def is_vendor(f: Path) -> bool:
99
+ return f.parts[0] == "vendor"
100
+
101
+
102
+ def get_vendor_workspace(f: Path) -> str:
103
+ return f.parts[1]
104
+
105
+
106
+ def is_endpoint(f: Path) -> bool:
107
+ return f.suffix == ".pipe" and not is_vendor(f) and f.parts[0] == "endpoints"
108
+
109
+
110
+ def is_pipe(f: Path) -> bool:
111
+ return f.suffix == ".pipe" and not is_vendor(f)
112
+
113
+
114
+ def check_filenames(filenames: List[str]):
115
+ parser_matrix = {".pipe": parse_pipe, ".datasource": parse_datasource}
116
+ incl_suffix = ".incl"
117
+
118
+ for filename in filenames:
119
+ file_suffix = Path(filename).suffix
120
+ if file_suffix == incl_suffix:
121
+ continue
122
+
123
+ parser = parser_matrix.get(file_suffix)
124
+ if not parser:
125
+ raise ParseException(FeedbackManager.error_unsupported_datafile(extension=file_suffix))
126
+
127
+ parser(filename)
128
+
129
+
130
+ def dev_cloud(
131
+ ctx: click.Context,
132
+ ) -> None:
133
+ project: Project = ctx.ensure_object(dict)["project"]
134
+ config = CLIConfig.get_project_config()
135
+ tb_client: TinyB = config.get_client()
136
+ context.disable_template_security_validation.set(True)
137
+
138
+ def process(filenames: List[str], watch: bool = False):
139
+ datafiles = [f for f in filenames if f.endswith(".datasource") or f.endswith(".pipe")]
140
+ if len(datafiles) > 0:
141
+ check_filenames(filenames=datafiles)
142
+ folder_playground(
143
+ project, config, tb_client, filenames=datafiles, is_internal=False, current_ws=None, local_ws=None
144
+ )
145
+ if len(filenames) > 0 and watch:
146
+ filename = filenames[0]
147
+ build_and_print_resource(config, tb_client, filename)
148
+
149
+ datafiles = project.get_project_files()
150
+ filenames = datafiles
151
+
152
+ def build_once(filenames: List[str]):
153
+ ok = False
154
+ try:
155
+ click.echo(FeedbackManager.highlight(message="» Building project...\n"))
156
+ time_start = time.time()
157
+ process(filenames=filenames, watch=False)
158
+ time_end = time.time()
159
+ elapsed_time = time_end - time_start
160
+
161
+ click.echo(FeedbackManager.success(message=f"\n✓ Build completed in {elapsed_time:.1f}s"))
162
+ ok = True
163
+ except Exception as e:
164
+ error_path = Path(".tb_error.txt")
165
+ if error_path.exists():
166
+ content = error_path.read_text()
167
+ content += f"\n\n{str(e)}"
168
+ error_path.write_text(content)
169
+ else:
170
+ error_path.write_text(str(e))
171
+ click.echo(FeedbackManager.error_exception(error=e))
172
+ ok = False
173
+ return ok
174
+
175
+ build_ok = build_once(filenames)
176
+
177
+ shell = Shell(project=project, tb_client=tb_client, playground=True)
178
+ click.echo(FeedbackManager.gray(message="\nWatching for changes..."))
179
+ watcher_thread = threading.Thread(
180
+ target=watch_files, args=(filenames, process, shell, project, build_ok), daemon=True
181
+ )
182
+ watcher_thread.start()
183
+ shell.run()
184
+
185
+
186
+ def build_and_print_resource(config: CLIConfig, tb_client: TinyB, filename: str):
187
+ resource_path = Path(filename)
188
+ name = resource_path.stem
189
+ playground_name = name if filename.endswith(".pipe") else None
190
+ user_client = deepcopy(tb_client)
191
+ user_client.token = config.get_user_token() or ""
192
+ cli_params = {}
193
+ cli_params["workspace_id"] = config.get("id", None)
194
+ data = user_client._req(f"/v0/playgrounds?{urlencode(cli_params)}")
195
+ playgrounds = data["playgrounds"]
196
+ playground = next((p for p in playgrounds if p["name"] == (f"{playground_name}" + "__tb__playground")), None)
197
+ if not playground:
198
+ return
199
+ playground_id = playground["id"]
200
+ last_node = playground["nodes"][-1]
201
+ if not last_node:
202
+ return
203
+ node_sql = last_node["sql"]
204
+ res = tb_client.query(f"{node_sql} FORMAT JSON", playground=playground_id)
205
+ print_table_formatted(res, name)