tinybird 0.0.1.dev241__tar.gz → 0.0.1.dev244__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.dev241 → tinybird-0.0.1.dev244}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/__cli__.py +2 -2
  3. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/agent.py +46 -12
  4. tinybird-0.0.1.dev244/tinybird/tb/modules/agent/animations.py +86 -0
  5. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/prompts.py +36 -0
  6. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/tools/create_datafile.py +19 -15
  7. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/tools/plan.py +5 -3
  8. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/utils.py +3 -0
  9. tinybird-0.0.1.dev244/tinybird/tb/modules/build.py +205 -0
  10. tinybird-0.0.1.dev241/tinybird/tb/modules/build.py → tinybird-0.0.1.dev244/tinybird/tb/modules/build_common.py +166 -354
  11. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/cli.py +1 -1
  12. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/secret.py +2 -50
  13. tinybird-0.0.1.dev244/tinybird/tb/modules/secret_common.py +56 -0
  14. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/test.py +1 -1
  15. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird.egg-info/PKG-INFO +1 -1
  16. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird.egg-info/SOURCES.txt +3 -0
  17. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/setup.cfg +0 -0
  18. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/__cli__.py +0 -0
  19. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/ch_utils/constants.py +0 -0
  20. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/ch_utils/engine.py +0 -0
  21. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/check_pypi.py +0 -0
  22. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/client.py +0 -0
  23. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/config.py +0 -0
  24. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/connectors.py +0 -0
  25. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/context.py +0 -0
  26. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/datafile/common.py +0 -0
  27. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/datafile/exceptions.py +0 -0
  28. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/datafile/parse_connection.py +0 -0
  29. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/datafile/parse_datasource.py +0 -0
  30. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/datafile/parse_pipe.py +0 -0
  31. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/datatypes.py +0 -0
  32. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/feedback_manager.py +0 -0
  33. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/git_settings.py +0 -0
  34. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/prompts.py +0 -0
  35. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/sql.py +0 -0
  36. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/sql_template.py +0 -0
  37. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/sql_template_fmt.py +0 -0
  38. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/sql_toolset.py +0 -0
  39. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/syncasync.py +0 -0
  40. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/check_pypi.py +0 -0
  41. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/cli.py +0 -0
  42. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/client.py +0 -0
  43. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/config.py +0 -0
  44. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/__init__.py +0 -0
  45. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/banner.py +0 -0
  46. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/memory.py +0 -0
  47. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/models.py +0 -0
  48. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/tools/__init__.py +0 -0
  49. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/tools/explore.py +0 -0
  50. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/agent/tools/preview_datafile.py +0 -0
  51. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/cicd.py +0 -0
  52. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/common.py +0 -0
  53. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/config.py +0 -0
  54. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/connection.py +0 -0
  55. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/copy.py +0 -0
  56. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/create.py +0 -0
  57. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/build.py +0 -0
  58. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/build_common.py +0 -0
  59. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  60. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  61. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/diff.py +0 -0
  62. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/fixture.py +0 -0
  63. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/format_common.py +0 -0
  64. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  65. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  66. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  67. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/playground.py +0 -0
  68. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datafile/pull.py +0 -0
  69. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/datasource.py +0 -0
  70. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/deployment.py +0 -0
  71. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/deprecations.py +0 -0
  72. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/dev_server.py +0 -0
  73. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/endpoint.py +0 -0
  74. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/exceptions.py +0 -0
  75. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/feedback_manager.py +0 -0
  76. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/info.py +0 -0
  77. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/infra.py +0 -0
  78. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/job.py +0 -0
  79. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/llm.py +0 -0
  80. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/llm_utils.py +0 -0
  81. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/local.py +0 -0
  82. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/local_common.py +0 -0
  83. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/login.py +0 -0
  84. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/logout.py +0 -0
  85. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/materialization.py +0 -0
  86. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/mock.py +0 -0
  87. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/open.py +0 -0
  88. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/pipe.py +0 -0
  89. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/project.py +0 -0
  90. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/regions.py +0 -0
  91. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/shell.py +0 -0
  92. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/sink.py +0 -0
  93. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/table.py +0 -0
  94. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/telemetry.py +0 -0
  95. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  96. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  97. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/token.py +0 -0
  98. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/watch.py +0 -0
  99. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/workspace.py +0 -0
  100. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb/modules/workspace_members.py +0 -0
  101. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli.py +0 -0
  102. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/auth.py +0 -0
  103. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/branch.py +0 -0
  104. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/cicd.py +0 -0
  105. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/cli.py +0 -0
  106. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/common.py +0 -0
  107. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/config.py +0 -0
  108. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/connection.py +0 -0
  109. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/datasource.py +0 -0
  110. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/exceptions.py +0 -0
  111. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/fmt.py +0 -0
  112. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/job.py +0 -0
  113. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/pipe.py +0 -0
  114. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/regions.py +0 -0
  115. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/tag.py +0 -0
  116. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/telemetry.py +0 -0
  117. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/test.py +0 -0
  118. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  119. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  120. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/workspace.py +0 -0
  121. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  122. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird/tornado_template.py +0 -0
  123. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird.egg-info/dependency_links.txt +0 -0
  124. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird.egg-info/entry_points.txt +0 -0
  125. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/tinybird.egg-info/requires.txt +0 -0
  126. {tinybird-0.0.1.dev241 → tinybird-0.0.1.dev244}/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.dev241
3
+ Version: 0.0.1.dev244
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -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.dev241'
8
- __revision__ = 'b44ab3b'
7
+ __version__ = '0.0.1.dev244'
8
+ __revision__ = '434c7ff'
@@ -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
@@ -22,23 +25,33 @@ from tinybird.prompts import (
22
25
  sink_pipe_instructions,
23
26
  )
24
27
  from tinybird.tb.client import TinyB
28
+ from tinybird.tb.modules.agent.animations import ThinkingAnimation
25
29
  from tinybird.tb.modules.agent.banner import display_banner
26
30
  from tinybird.tb.modules.agent.memory import clear_history, load_history
27
31
  from tinybird.tb.modules.agent.models import create_model
28
- 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
+ )
29
38
  from tinybird.tb.modules.agent.tools.create_datafile import create_datafile
30
39
  from tinybird.tb.modules.agent.tools.explore import explore_data
31
40
  from tinybird.tb.modules.agent.tools.plan import plan
32
41
  from tinybird.tb.modules.agent.tools.preview_datafile import preview_datafile
33
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
34
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
35
48
 
36
49
 
37
50
  class TinybirdAgent:
38
- def __init__(self, token: str, host: str, folder: str):
51
+ def __init__(self, token: str, host: str, project: Project):
39
52
  self.token = token
40
53
  self.host = host
41
- self.folder = folder
54
+ self.project = project
42
55
  self.messages: list[ModelMessage] = []
43
56
  self.agent = Agent(
44
57
  model=create_model(token, host),
@@ -82,9 +95,10 @@ You have access to the following tools:
82
95
  4. Without asking, use the `create_datafile` tool to create the datafile, because it will ask for confirmation before creating the file.
83
96
  5. Check the result of the `create_datafile` tool to see if the datafile was created successfully.
84
97
  6. If the datafile was created successfully, report the result to the user.
85
- 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.
86
100
 
87
- 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.
88
102
 
89
103
  # When planning the creation or update of resources:
90
104
  {plan_instructions}
@@ -125,7 +139,7 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
125
139
  Tool(explore_data, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
126
140
  Tool(preview_datafile, docstring_format="google", require_parameter_descriptions=True, takes_ctx=False),
127
141
  Tool(create_datafile, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
128
- Tool(plan, docstring_format="google", require_parameter_descriptions=True, takes_ctx=False),
142
+ Tool(plan, docstring_format="google", require_parameter_descriptions=True, takes_ctx=True),
129
143
  ],
130
144
  )
131
145
 
@@ -133,29 +147,40 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
133
147
  """Keep only the last 5 messages to manage token usage."""
134
148
  return self.messages[-5:] if len(self.messages) > 5 else self.messages
135
149
 
136
- 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)}"
137
152
  client = TinyB(token=self.token, host=self.host)
153
+ folder = self.project.folder
154
+ thinking_animation = ThinkingAnimation(message="Chirping", delay=0.15)
155
+ thinking_animation.start()
156
+
138
157
  result = self.agent.run_sync(
139
158
  user_prompt,
140
159
  deps=TinybirdAgentContext(
141
160
  # context does not support the whole client, so we need to pass only the functions we need
142
161
  explore_data=client.explore_data,
143
- folder=self.folder,
162
+ build_project=partial(build_project, folder=folder),
163
+ get_project_files=project.get_project_files,
164
+ folder=folder,
165
+ thinking_animation=thinking_animation,
144
166
  ),
145
167
  message_history=self.messages,
146
168
  )
147
169
  new_messages = result.new_messages()
148
170
  self.messages.extend(new_messages)
149
- click.echo("\n")
171
+ thinking_animation.stop()
172
+
150
173
  click.echo(result.output)
151
174
  click.echo("\n")
152
175
 
153
176
 
154
- def run_agent(token: str, host: str, folder: str):
177
+ def run_agent(config: dict[str, Any], project: Project):
155
178
  display_banner()
156
179
 
157
180
  try:
158
- agent = TinybirdAgent(token, host, folder)
181
+ token = config["token"]
182
+ host = config["host"]
183
+ agent = TinybirdAgent(token, host, project)
159
184
  click.echo()
160
185
  click.echo(FeedbackManager.success(message="Welcome to Tinybird Code"))
161
186
  click.echo(FeedbackManager.info(message="Describe what you want to create and I'll help you build it"))
@@ -200,7 +225,7 @@ def run_agent(token: str, host: str, folder: str):
200
225
  elif user_input.strip() == "":
201
226
  continue
202
227
  else:
203
- agent.run(user_input)
228
+ agent.run(user_input, project)
204
229
 
205
230
  except KeyboardInterrupt:
206
231
  click.echo(FeedbackManager.info(message="Goodbye!"))
@@ -212,3 +237,12 @@ def run_agent(token: str, host: str, folder: str):
212
237
  except Exception as e:
213
238
  click.echo(FeedbackManager.error(message=f"Error: {e}"))
214
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)
@@ -0,0 +1,86 @@
1
+ import random
2
+ import sys
3
+ import threading
4
+ from time import sleep
5
+ from typing import Optional
6
+
7
+ import click
8
+
9
+
10
+ class ThinkingAnimation:
11
+ """Thinking animation that shows changing sparkles as a prefix."""
12
+
13
+ def __init__(
14
+ self,
15
+ message: str = "Thinking",
16
+ delay: float = 0.15,
17
+ colors: bool = True,
18
+ dots: bool = True,
19
+ ):
20
+ self.message = message
21
+ self.delay = delay
22
+ self.running = False
23
+ self.thread: Optional[threading.Thread] = None
24
+ self.colors = colors and sys.stdout.isatty()
25
+ self.dots = dots
26
+
27
+ # Default sparkle characters
28
+ self.sparkle_chars = ["✧", "✦", "⋆", "✳", "✺", "✹", "*", "·"]
29
+
30
+ # ANSI color codes
31
+ self.colors_list = [
32
+ "\033[33m", # Yellow
33
+ "\033[36m", # Cyan
34
+ "\033[35m", # Magenta
35
+ "\033[93m", # Bright Yellow
36
+ "\033[96m", # Bright Cyan
37
+ ]
38
+ self.reset_color = "\033[0m"
39
+
40
+ def start(self):
41
+ """Start the animation in a separate thread."""
42
+ self.running = True
43
+ self.thread = threading.Thread(target=self._run_animation)
44
+ self.thread.daemon = True
45
+
46
+ click.echo("\n")
47
+ self.thread.start()
48
+
49
+ def stop(self):
50
+ """Stop the animation."""
51
+ self.running = False
52
+ if self.thread:
53
+ self.thread.join()
54
+ # Clear the line and reset cursor position
55
+ sys.stdout.write("\r" + " " * (len(self.message) + 10) + "\r")
56
+ sys.stdout.flush()
57
+
58
+ def _run_animation(self):
59
+ """Run the animation until stopped."""
60
+ frame_count = 0
61
+ dots_count = 0
62
+
63
+ while self.running:
64
+ # Choose a random sparkle for this frame
65
+ sparkle = random.choice(self.sparkle_chars)
66
+
67
+ if self.colors:
68
+ color = random.choice(self.colors_list)
69
+ colored_sparkle = f"{color}{sparkle}{self.reset_color}"
70
+ else:
71
+ colored_sparkle = sparkle
72
+
73
+ # Handle dots animation if enabled
74
+ if self.dots:
75
+ dots_count = (frame_count // 4) % 4 # Change dots every 4 frames
76
+ dots = "." * dots_count
77
+ display_message = f"{self.message}{dots}"
78
+ else:
79
+ display_message = self.message
80
+
81
+ # Print the message with the prefix sparkle
82
+ sys.stdout.write(f"\r{colored_sparkle} {display_message}")
83
+ sys.stdout.flush()
84
+
85
+ sleep(self.delay)
86
+ frame_count += 1
@@ -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
 
@@ -35,28 +38,29 @@ def create_datafile(ctx: RunContext[TinybirdAgentContext], resource: Datafile) -
35
38
  str: If the resource was created or not.
36
39
  """
37
40
  try:
38
- click.echo()
41
+ ctx.deps.thinking_animation.stop()
39
42
  click.echo(resource.content)
40
- 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)
47
+ ctx.deps.thinking_animation.start()
41
48
 
42
49
  if not confirmation:
43
50
  return f"Resource {resource.pathname} was not created. User cancelled creation."
44
51
 
45
- resource.pathname = resource.pathname.removeprefix("/")
46
-
47
- path = Path(ctx.deps.folder) / resource.pathname
48
-
49
52
  folder_path = path.parent
50
-
51
- if not folder_path.exists():
52
- folder_path.mkdir()
53
-
54
- if not path.exists():
55
- path.touch()
53
+ folder_path.mkdir(parents=True, exist_ok=True)
54
+ path.touch(exist_ok=True)
56
55
 
57
56
  path.write_text(resource.content)
58
-
57
+ ctx.deps.build_project()
59
58
  return f"Created {resource.pathname}"
60
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}"
61
65
  except Exception as e:
62
66
  return f"Error creating {resource.pathname}: {e}"
@@ -1,6 +1,7 @@
1
1
  import click
2
+ from pydantic_ai import RunContext
2
3
 
3
- from tinybird.tb.modules.agent.utils import show_options
4
+ from tinybird.tb.modules.agent.utils import TinybirdAgentContext, show_options
4
5
 
5
6
 
6
7
  def get_plan_confirmation() -> bool:
@@ -22,7 +23,7 @@ def get_plan_confirmation() -> bool:
22
23
  return False
23
24
 
24
25
 
25
- def plan(plan: str) -> str:
26
+ def plan(ctx: RunContext[TinybirdAgentContext], plan: str) -> str:
26
27
  """Given a plan, ask the user for confirmation to implement it
27
28
 
28
29
  Args:
@@ -32,9 +33,10 @@ def plan(plan: str) -> str:
32
33
  str: If the plan was implemented or not.
33
34
  """
34
35
  try:
35
- click.echo()
36
+ ctx.deps.thinking_animation.stop()
36
37
  click.echo(plan)
37
38
  confirmation = get_plan_confirmation()
39
+ ctx.deps.thinking_animation.start()
38
40
 
39
41
  if not confirmation:
40
42
  return "Plan was not implemented. User cancelled implementation."
@@ -20,6 +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]
24
+ thinking_animation: Any
25
+ get_project_files: Callable[[], List[str]]
23
26
 
24
27
 
25
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)