tinybird 0.0.1.dev247__tar.gz → 0.0.1.dev249__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 (136) hide show
  1. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/ch_utils/engine.py +3 -2
  3. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/prompts.py +2 -0
  4. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/__cli__.py +2 -2
  5. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/agent.py +53 -15
  6. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/models.py +2 -1
  7. tinybird-0.0.1.dev249/tinybird/tb/modules/agent/tools/create_datafile.py +71 -0
  8. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/read_fixture_data.py +2 -2
  9. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/utils.py +292 -3
  10. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/deployment.py +22 -1
  11. tinybird-0.0.1.dev249/tinybird/tb/modules/login.py +39 -0
  12. tinybird-0.0.1.dev247/tinybird/tb/modules/login.py → tinybird-0.0.1.dev249/tinybird/tb/modules/login_common.py +8 -32
  13. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird.egg-info/PKG-INFO +1 -1
  14. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird.egg-info/SOURCES.txt +1 -0
  15. tinybird-0.0.1.dev247/tinybird/tb/modules/agent/tools/create_datafile.py +0 -145
  16. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/setup.cfg +0 -0
  17. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/__cli__.py +0 -0
  18. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/ch_utils/constants.py +0 -0
  19. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/check_pypi.py +0 -0
  20. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/client.py +0 -0
  21. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/config.py +0 -0
  22. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/connectors.py +0 -0
  23. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/context.py +0 -0
  24. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/datafile/common.py +0 -0
  25. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/datafile/exceptions.py +0 -0
  26. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/datafile/parse_connection.py +0 -0
  27. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/datafile/parse_datasource.py +0 -0
  28. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/datafile/parse_pipe.py +0 -0
  29. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/datatypes.py +0 -0
  30. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/feedback_manager.py +0 -0
  31. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/git_settings.py +0 -0
  32. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/sql.py +0 -0
  33. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/sql_template.py +0 -0
  34. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/sql_template_fmt.py +0 -0
  35. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/sql_toolset.py +0 -0
  36. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/syncasync.py +0 -0
  37. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/check_pypi.py +0 -0
  38. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/cli.py +0 -0
  39. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/client.py +0 -0
  40. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/config.py +0 -0
  41. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/__init__.py +0 -0
  42. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/animations.py +0 -0
  43. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/banner.py +0 -0
  44. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/memory.py +0 -0
  45. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/prompts.py +0 -0
  46. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/__init__.py +0 -0
  47. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/append.py +0 -0
  48. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/build.py +0 -0
  49. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/deploy.py +0 -0
  50. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/deploy_check.py +0 -0
  51. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/explore.py +0 -0
  52. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/mock.py +0 -0
  53. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/plan.py +0 -0
  54. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/agent/tools/preview_datafile.py +0 -0
  55. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/build.py +0 -0
  56. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/build_common.py +0 -0
  57. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/cicd.py +0 -0
  58. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/cli.py +0 -0
  59. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/common.py +0 -0
  60. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/config.py +0 -0
  61. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/connection.py +0 -0
  62. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/copy.py +0 -0
  63. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/create.py +0 -0
  64. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/build.py +0 -0
  65. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/build_common.py +0 -0
  66. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  67. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  68. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/diff.py +0 -0
  69. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/fixture.py +0 -0
  70. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/format_common.py +0 -0
  71. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  72. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  73. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  74. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/playground.py +0 -0
  75. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datafile/pull.py +0 -0
  76. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/datasource.py +0 -0
  77. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/deployment_common.py +0 -0
  78. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/deprecations.py +0 -0
  79. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/dev_server.py +0 -0
  80. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/endpoint.py +0 -0
  81. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/exceptions.py +0 -0
  82. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/feedback_manager.py +0 -0
  83. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/info.py +0 -0
  84. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/infra.py +0 -0
  85. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/job.py +0 -0
  86. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/llm.py +0 -0
  87. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/llm_utils.py +0 -0
  88. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/local.py +0 -0
  89. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/local_common.py +0 -0
  90. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/logout.py +0 -0
  91. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/materialization.py +0 -0
  92. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/mock.py +0 -0
  93. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/mock_common.py +0 -0
  94. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/open.py +0 -0
  95. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/pipe.py +0 -0
  96. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/project.py +0 -0
  97. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/regions.py +0 -0
  98. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/secret.py +0 -0
  99. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/secret_common.py +0 -0
  100. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/shell.py +0 -0
  101. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/sink.py +0 -0
  102. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/table.py +0 -0
  103. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/telemetry.py +0 -0
  104. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/test.py +0 -0
  105. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  106. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  107. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/token.py +0 -0
  108. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/watch.py +0 -0
  109. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/workspace.py +0 -0
  110. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb/modules/workspace_members.py +0 -0
  111. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli.py +0 -0
  112. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/auth.py +0 -0
  113. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/branch.py +0 -0
  114. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/cicd.py +0 -0
  115. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/cli.py +0 -0
  116. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/common.py +0 -0
  117. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/config.py +0 -0
  118. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/connection.py +0 -0
  119. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/datasource.py +0 -0
  120. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/exceptions.py +0 -0
  121. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/fmt.py +0 -0
  122. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/job.py +0 -0
  123. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/pipe.py +0 -0
  124. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/regions.py +0 -0
  125. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/tag.py +0 -0
  126. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/telemetry.py +0 -0
  127. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/test.py +0 -0
  128. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  129. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  130. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/workspace.py +0 -0
  131. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  132. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird/tornado_template.py +0 -0
  133. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird.egg-info/dependency_links.txt +0 -0
  134. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird.egg-info/entry_points.txt +0 -0
  135. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/tinybird.egg-info/requires.txt +0 -0
  136. {tinybird-0.0.1.dev247 → tinybird-0.0.1.dev249}/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.dev247
3
+ Version: 0.0.1.dev249
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/forward/commands
6
6
  Author: Tinybird
@@ -134,8 +134,9 @@ class TableDetails:
134
134
  _version = self.details.get("version", None)
135
135
  return _version
136
136
 
137
- def is_replicated(self):
138
- return "Replicated" in self.details.get("engine", None)
137
+ def is_replicated(self) -> bool:
138
+ engine: Optional[str] = self.details.get("engine", None)
139
+ return engine is not None and "Replicated" in engine
139
140
 
140
141
  def is_mergetree_family(self) -> bool:
141
142
  return self.engine is not None and "mergetree" in self.engine.lower()
@@ -776,6 +776,8 @@ datasource_instructions = """
776
776
  - Use always json paths to define the schema. Example: `user_id` String `json:$.user_id`,
777
777
  - Array columns are supported with a special syntax. Example: `items` Array(String) `json:$.items[:]`
778
778
  - If the datasource is using an S3 or GCS connection, they need to set IMPORT_CONNECTION_NAME, IMPORT_BUCKET_URI and IMPORT_SCHEDULE (GCS @on-demand only, S3 supports @auto too)
779
+ - Unless the user asks for them, do not include ENGINE_PARTITION_KEY and ENGINE_PRIMARY_KEY.
780
+ - DateTime64 type without precision is not supported. Use DateTime64(3) instead.
779
781
  </datasource_file_instructions>
780
782
  """
781
783
 
@@ -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.dev247'
8
- __revision__ = '379a827'
7
+ __version__ = '0.0.1.dev249'
8
+ __revision__ = 'ef4e98c'
@@ -1,3 +1,4 @@
1
+ import shlex
1
2
  import subprocess
2
3
  import sys
3
4
  from datetime import datetime
@@ -54,19 +55,29 @@ from tinybird.tb.modules.deployment_common import create_deployment
54
55
  from tinybird.tb.modules.exceptions import CLIBuildException, CLIMockException
55
56
  from tinybird.tb.modules.feedback_manager import FeedbackManager
56
57
  from tinybird.tb.modules.local_common import get_tinybird_local_client
58
+ from tinybird.tb.modules.login_common import login
57
59
  from tinybird.tb.modules.mock_common import append_mock_data, create_mock_data
58
60
  from tinybird.tb.modules.project import Project
59
61
 
60
62
 
61
63
  class TinybirdAgent:
62
- def __init__(self, token: str, host: str, project: Project, dangerously_skip_permissions: bool):
64
+ def __init__(
65
+ self,
66
+ token: str,
67
+ user_token: str,
68
+ host: str,
69
+ workspace_id: str,
70
+ project: Project,
71
+ dangerously_skip_permissions: bool,
72
+ ):
63
73
  self.token = token
74
+ self.user_token = user_token
64
75
  self.host = host
65
76
  self.dangerously_skip_permissions = dangerously_skip_permissions
66
77
  self.project = project
67
78
  self.messages: list[ModelMessage] = []
68
79
  self.agent = Agent(
69
- model=create_model(token, host),
80
+ model=create_model(user_token, host, workspace_id),
70
81
  deps_type=TinybirdAgentContext,
71
82
  system_prompt=f"""
72
83
  You are a Tinybird Code, an agentic CLI that can help users to work with Tinybird.
@@ -75,7 +86,7 @@ You are an interactive CLI tool that helps users with data engineering tasks. Us
75
86
 
76
87
  # Tone and style
77
88
  You should be concise, direct, and to the point.
78
- Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting.
89
+ Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting. Do not use emojis.
79
90
  Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
80
91
  If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
81
92
  IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do.
@@ -116,6 +127,7 @@ You have access to the following tools:
116
127
  8. If the datafile was created successfully, but the built failed, try to fix the error and repeat the process.
117
128
 
118
129
  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.
130
+ IMPORTANT: Every time you finish a plan and start a new resource creation or update process, create a new plan before starting with the changes.
119
131
 
120
132
  # When planning the creation or update of resources:
121
133
  {plan_instructions}
@@ -217,18 +229,41 @@ Today is {datetime.now().strftime("%Y-%m-%d")}
217
229
 
218
230
 
219
231
  def run_agent(config: dict[str, Any], project: Project, dangerously_skip_permissions: bool):
220
- display_banner()
221
-
232
+ token = config.get("token", None)
233
+ host = config.get("host", None)
234
+ user_token = config.get("user_token", None)
235
+ workspace_id = config.get("id", None)
222
236
  try:
223
- token = config["token"]
224
- host = config["host"]
225
- agent = TinybirdAgent(token, host, project, dangerously_skip_permissions)
237
+ if not token or not host or not workspace_id or not user_token:
238
+ yes = click.confirm(
239
+ FeedbackManager.warning(
240
+ message="Tinybird Code requires authentication. Do you want to authenticate now? [Y/n]"
241
+ ),
242
+ prompt_suffix="",
243
+ show_default=False,
244
+ default=True,
245
+ )
246
+ if yes:
247
+ click.echo()
248
+ login(host, auth_host="https://cloud.tinybird.co", workspace=None, interactive=False, method="browser")
249
+ click.echo()
250
+ cli_config = CLIConfig.get_project_config()
251
+ token = cli_config.get_token()
252
+ user_token = cli_config.get_user_token()
253
+ host = cli_config.get_host()
254
+
255
+ if not token or not host or not user_token:
256
+ click.echo(
257
+ FeedbackManager.error(message="Tinybird Code requires authentication. Run 'tb login' first.")
258
+ )
259
+ return
260
+
261
+ display_banner()
262
+ agent = TinybirdAgent(token, user_token, host, workspace_id, project, dangerously_skip_permissions)
226
263
  click.echo()
227
- if config.get("token"):
228
- click.echo(FeedbackManager.info(message="Describe what you want to create and I'll help you build it"))
229
- click.echo(FeedbackManager.info(message="Run /help for more commands"))
230
- else:
231
- click.echo(FeedbackManager.info(message="Run /login to authenticate"))
264
+ click.echo(FeedbackManager.info(message="Describe what you want to create and I'll help you build it"))
265
+ click.echo(FeedbackManager.info(message="Run /help for more commands"))
266
+
232
267
  click.echo()
233
268
 
234
269
  except Exception as e:
@@ -240,7 +275,7 @@ def run_agent(config: dict[str, Any], project: Project, dangerously_skip_permiss
240
275
  while True:
241
276
  try:
242
277
  user_input = prompt(
243
- [("class:prompt", "tb » ")],
278
+ [("class:prompt", f"tb ({project.workspace_name}) » ")],
244
279
  history=load_history(),
245
280
  cursor=CursorShape.BLOCK,
246
281
  style=Style.from_dict(
@@ -250,7 +285,10 @@ def run_agent(config: dict[str, Any], project: Project, dangerously_skip_permiss
250
285
  }
251
286
  ),
252
287
  )
253
-
288
+ if user_input.startswith("tb "):
289
+ cmd_parts = shlex.split(user_input)
290
+ subprocess.run(cmd_parts, check=True)
291
+ continue
254
292
  if user_input.lower() in ["/exit", "/quit"]:
255
293
  click.echo(FeedbackManager.info(message="Goodbye!"))
256
294
  break
@@ -7,11 +7,12 @@ from pydantic_ai.providers.anthropic import AnthropicProvider
7
7
  def create_model(
8
8
  token: str,
9
9
  base_url: str,
10
+ workspace_id: str,
10
11
  model: AnthropicModelName = "claude-4-sonnet-20250514",
11
12
  ):
12
13
  client = AsyncAnthropic(
13
14
  base_url=base_url,
14
- http_client=AsyncClient(params={"token": token}),
15
+ http_client=AsyncClient(params={"token": token, "workspace_id": workspace_id}),
15
16
  auth_token=token,
16
17
  )
17
18
  return AnthropicModel(
@@ -0,0 +1,71 @@
1
+ from pathlib import Path
2
+
3
+ import click
4
+ from pydantic_ai import RunContext
5
+
6
+ from tinybird.tb.modules.agent.utils import Datafile, TinybirdAgentContext, create_terminal_box, show_options
7
+ from tinybird.tb.modules.exceptions import CLIBuildException
8
+ from tinybird.tb.modules.feedback_manager import FeedbackManager
9
+
10
+
11
+ def get_resource_confirmation(resource: Datafile, exists: bool) -> bool:
12
+ """Get user confirmation for creating a resource"""
13
+ while True:
14
+ action = "create" if not exists else "update"
15
+ result = show_options(
16
+ options=[f"Yes, {action} {resource.type} '{resource.name}'", "No, and tell Tinybird Code what to do"],
17
+ title=f"What would you like to do with {resource.type} '{resource.name}'?",
18
+ )
19
+
20
+ if result is None: # Cancelled
21
+ return False
22
+
23
+ if result.startswith("Yes"):
24
+ return True
25
+ elif result.startswith("No"):
26
+ return False
27
+
28
+ return False
29
+
30
+
31
+ def create_datafile(ctx: RunContext[TinybirdAgentContext], resource: Datafile) -> str:
32
+ """Given a resource representation, create a file in the project folder
33
+
34
+ Args:
35
+ resource (Datafile): The resource to create. Required.
36
+
37
+ Returns:
38
+ str: If the resource was created or not.
39
+ """
40
+ try:
41
+ ctx.deps.thinking_animation.stop()
42
+ resource.pathname = resource.pathname.removeprefix("/")
43
+ path = Path(ctx.deps.folder) / resource.pathname
44
+ content = resource.content
45
+ exists = str(path) in ctx.deps.get_project_files()
46
+ if exists:
47
+ content = create_terminal_box(path.read_text(), resource.content, title=resource.pathname)
48
+ else:
49
+ content = create_terminal_box(resource.content, title=resource.pathname)
50
+ click.echo(content)
51
+ confirmation = ctx.deps.dangerously_skip_permissions or get_resource_confirmation(resource, exists)
52
+
53
+ if not confirmation:
54
+ ctx.deps.thinking_animation.start()
55
+ return f"Resource {resource.pathname} was not created. User cancelled creation."
56
+
57
+ folder_path = path.parent
58
+ folder_path.mkdir(parents=True, exist_ok=True)
59
+ path.touch(exist_ok=True)
60
+ path.write_text(resource.content)
61
+ ctx.deps.build_project(test=True, silent=True)
62
+ ctx.deps.thinking_animation.start()
63
+ return f"Created {resource.pathname}"
64
+
65
+ except CLIBuildException as e:
66
+ ctx.deps.thinking_animation.stop()
67
+ click.echo(FeedbackManager.error(message=e))
68
+ ctx.deps.thinking_animation.start()
69
+ return f"Error building project: {e}"
70
+ except Exception as e:
71
+ return f"Error creating {resource.pathname}: {e}"
@@ -23,6 +23,6 @@ def read_fixture_data(ctx: RunContext[TinybirdAgentContext], fixture_pathname: s
23
23
  response = ctx.deps.analyze_fixture(fixture_path=str(fixture_path))
24
24
  # limit content to first 10 rows
25
25
  data = response["preview"]["data"][:10]
26
- schema = response["analysis"]["schema"]
26
+ columns = response["analysis"]["columns"]
27
27
 
28
- return f"#Result of analysis of {fixture_pathname}:\n##Data sample:\n{json.dumps(data)}\n##Schema:\n{schema}"
28
+ return f"#Result of analysis of {fixture_pathname}:\n##Columns:\n{json.dumps(columns)}\n##Data sample:\n{json.dumps(data)}"
@@ -1,6 +1,7 @@
1
+ import difflib
1
2
  import os
2
3
  from contextlib import contextmanager
3
- from typing import Any, Callable, List, Optional
4
+ from typing import Any, Callable, List, Optional, Tuple
4
5
 
5
6
  import click
6
7
  from prompt_toolkit.application import Application, get_app
@@ -13,9 +14,17 @@ from prompt_toolkit.layout.dimension import LayoutDimension as D
13
14
  from prompt_toolkit.mouse_events import MouseEventType
14
15
  from prompt_toolkit.patch_stdout import patch_stdout as pt_patch_stdout
15
16
  from prompt_toolkit.shortcuts import PromptSession
16
- from prompt_toolkit.styles import Style
17
+ from prompt_toolkit.styles import Style as PromptStyle
17
18
  from pydantic import BaseModel, Field
18
19
 
20
+ try:
21
+ from colorama import Back, Fore, Style, init
22
+
23
+ init(autoreset=True)
24
+ COLORAMA_AVAILABLE = True
25
+ except ImportError:
26
+ COLORAMA_AVAILABLE = False
27
+
19
28
 
20
29
  class TinybirdAgentContext(BaseModel):
21
30
  folder: str
@@ -32,7 +41,7 @@ class TinybirdAgentContext(BaseModel):
32
41
  dangerously_skip_permissions: bool
33
42
 
34
43
 
35
- default_style = Style.from_dict(
44
+ default_style = PromptStyle.from_dict(
36
45
  {
37
46
  "separator": "#6C6C6C",
38
47
  "questionmark": "#FF9D00 bold",
@@ -388,3 +397,283 @@ class Datafile(BaseModel):
388
397
  description: str
389
398
  pathname: str
390
399
  dependencies: List[str] = Field(default_factory=list)
400
+
401
+
402
+ def create_terminal_box(content: str, new_content: Optional[str] = None, title: Optional[str] = None) -> str:
403
+ """
404
+ Create a formatted box with automatic line numbers that fills the terminal width.
405
+ Optionally shows a diff between content and new_content.
406
+
407
+ Args:
408
+ content: The original text content to display in the box (without line numbers)
409
+ new_content: Optional new content to show as a diff against the original
410
+ title: Optional title to display as header, if not provided will use first line of content
411
+
412
+ Returns:
413
+ A string containing the formatted box with line numbers added
414
+ """
415
+ # Get terminal width, default to 80 if can't determine
416
+ try:
417
+ terminal_width = os.get_terminal_size().columns
418
+ except:
419
+ terminal_width = 80
420
+
421
+ # Box characters
422
+ top_left = "╭"
423
+ top_right = "╮"
424
+ bottom_left = "╰"
425
+ bottom_right = "╯"
426
+ horizontal = "─"
427
+ vertical = "│"
428
+
429
+ # Calculate available width for content (terminal_width - 2 borders - 2 spaces padding)
430
+ available_width = terminal_width - 4
431
+
432
+ # Split content into lines
433
+ lines = content.strip().split("\n")
434
+ new_lines = new_content.strip().split("\n") if new_content else []
435
+
436
+ # Check if we have a title parameter or should use first line as header
437
+ header = title
438
+ content_lines = lines
439
+ new_content_lines = new_lines
440
+
441
+ if header is None and lines:
442
+ # Use first line as header if no title provided
443
+ header = lines[0]
444
+ content_lines = lines[1:] if len(lines) > 1 else []
445
+ if new_lines:
446
+ # Skip header in new content too
447
+ new_content_lines = new_lines[1:] if len(new_lines) > 1 else []
448
+ elif header is not None:
449
+ # Title provided, use all content lines as-is
450
+ content_lines = lines
451
+ new_content_lines = new_lines
452
+
453
+ # Process content lines
454
+ processed_lines = []
455
+
456
+ if new_content is None:
457
+ # No diff, just add line numbers
458
+ line_number = 1
459
+ for line in content_lines:
460
+ processed_lines.extend(_process_line(line, line_number, available_width, None))
461
+ line_number += 1
462
+ else:
463
+ # Create diff and process it properly
464
+ diff = list(
465
+ difflib.unified_diff(
466
+ content_lines,
467
+ new_content_lines,
468
+ lineterm="",
469
+ n=3, # Add some context lines
470
+ )
471
+ )
472
+
473
+ # Process the unified diff output
474
+ old_line_num = 1
475
+ new_line_num = 1
476
+ old_index = 0
477
+ new_index = 0
478
+
479
+ # Parse the diff output
480
+ i = 0
481
+ while i < len(diff):
482
+ line = diff[i]
483
+ if line.startswith("@@"):
484
+ # Parse hunk header to get line numbers
485
+ import re
486
+
487
+ match = re.match(r"@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@", line)
488
+ if match:
489
+ old_line_num = int(match.group(1))
490
+ new_line_num = int(match.group(2))
491
+ old_index = old_line_num - 1
492
+ new_index = new_line_num - 1
493
+ elif line.startswith("---") or line.startswith("+++"):
494
+ # Skip file headers
495
+ pass
496
+ elif line.startswith(" "):
497
+ # Context line (unchanged)
498
+ content = line[1:] # Remove the leading space
499
+ if old_index < len(content_lines) and content_lines[old_index] == content:
500
+ processed_lines.extend(_process_line(content, old_line_num, available_width, None))
501
+ old_line_num += 1
502
+ new_line_num += 1
503
+ old_index += 1
504
+ new_index += 1
505
+ elif line.startswith("-"):
506
+ # Removed line
507
+ content = line[1:] # Remove the leading minus
508
+ processed_lines.extend(_process_line(content, old_line_num, available_width, "-"))
509
+ old_line_num += 1
510
+ old_index += 1
511
+ elif line.startswith("+"):
512
+ # Added line
513
+ content = line[1:] # Remove the leading plus
514
+ processed_lines.extend(_process_line(content, new_line_num, available_width, "+"))
515
+ new_line_num += 1
516
+ new_index += 1
517
+ i += 1
518
+
519
+ # Add any remaining unchanged lines that weren't in the diff
520
+ while old_index < len(content_lines) and new_index < len(new_content_lines):
521
+ if content_lines[old_index] == new_content_lines[new_index]:
522
+ processed_lines.extend(_process_line(content_lines[old_index], old_line_num, available_width, None))
523
+ old_line_num += 1
524
+ new_line_num += 1
525
+ old_index += 1
526
+ new_index += 1
527
+ else:
528
+ break
529
+
530
+ # Build the box
531
+ result = []
532
+
533
+ # Top border
534
+ result.append(top_left + horizontal * (terminal_width - 2) + top_right)
535
+
536
+ # Add header if exists
537
+ if header:
538
+ # Center the header
539
+ header_padding = (available_width - len(header)) // 2
540
+ header_line = (
541
+ vertical
542
+ + " "
543
+ + " " * header_padding
544
+ + header
545
+ + " " * (available_width - len(header) - header_padding)
546
+ + " "
547
+ + vertical
548
+ )
549
+ result.append(header_line)
550
+ # Empty line after header
551
+ result.append(vertical + " " * (terminal_width - 2) + vertical)
552
+
553
+ # Content lines
554
+ for line_num, content, diff_marker in processed_lines:
555
+ if line_num is not None:
556
+ # Line with number
557
+ if COLORAMA_AVAILABLE:
558
+ line_num_str = f"{Fore.LIGHTBLACK_EX}{line_num:>4}{Style.RESET_ALL}"
559
+ else:
560
+ line_num_str = f"{line_num:>4}"
561
+
562
+ if diff_marker:
563
+ if COLORAMA_AVAILABLE:
564
+ if diff_marker == "-":
565
+ # Fill the entire content area with red background
566
+ content_with_bg = f"{Back.RED}{diff_marker} {content}{Style.RESET_ALL}"
567
+ # Calculate padding needed for the content area
568
+ content_area_width = available_width - 9 # 9 is reduced prefix length
569
+ content_padding = content_area_width - len(f"{diff_marker} {content}")
570
+ if content_padding > 0:
571
+ content_with_bg = (
572
+ f"{Back.RED}{diff_marker} {content}{' ' * content_padding}{Style.RESET_ALL}"
573
+ )
574
+ line = f"{vertical} {line_num_str} {content_with_bg}"
575
+ elif diff_marker == "+":
576
+ # Fill the entire content area with green background
577
+ content_with_bg = f"{Back.GREEN}{diff_marker} {content}{Style.RESET_ALL}"
578
+ # Calculate padding needed for the content area
579
+ content_area_width = available_width - 9 # 9 is reduced prefix length
580
+ content_padding = content_area_width - len(f"{diff_marker} {content}")
581
+ if content_padding > 0:
582
+ content_with_bg = (
583
+ f"{Back.GREEN}{diff_marker} {content}{' ' * content_padding}{Style.RESET_ALL}"
584
+ )
585
+ line = f"{vertical} {line_num_str} {content_with_bg}"
586
+ else:
587
+ line = f"{vertical} {line_num:>4} {diff_marker} {content}"
588
+ else:
589
+ line = f"{vertical} {line_num_str} {content}"
590
+ else:
591
+ # Continuation line without number - fill background starting from where symbol would be
592
+ if diff_marker and COLORAMA_AVAILABLE:
593
+ if diff_marker == "-":
594
+ # Calculate how much space we need to fill with background
595
+ content_area_width = available_width - 9 # 9 is reduced prefix length
596
+ content_padding = content_area_width - len(
597
+ content
598
+ ) # Don't subtract spaces, they're in the background
599
+ if content_padding > 0:
600
+ line = f"{vertical} {Back.RED} {content}{' ' * content_padding}{Style.RESET_ALL}"
601
+ else:
602
+ line = f"{vertical} {Back.RED} {content}{Style.RESET_ALL}"
603
+ elif diff_marker == "+":
604
+ # Calculate how much space we need to fill with background
605
+ content_area_width = available_width - 9 # 9 is reduced prefix length
606
+ content_padding = content_area_width - len(
607
+ content
608
+ ) # Don't subtract spaces, they're in the background
609
+ if content_padding > 0:
610
+ line = f"{vertical} {Back.GREEN} {content}{' ' * content_padding}{Style.RESET_ALL}"
611
+ else:
612
+ line = f"{vertical} {Back.GREEN} {content}{Style.RESET_ALL}"
613
+ else:
614
+ line = f"{vertical} {content}"
615
+
616
+ # Pad to terminal width
617
+ # Need to account for ANSI escape sequences not taking visual space
618
+ if COLORAMA_AVAILABLE:
619
+ # Calculate visible length (excluding ANSI codes)
620
+ visible_line = line
621
+ # Remove all ANSI escape sequences for length calculation
622
+ import re
623
+
624
+ ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
625
+ visible_line = ansi_escape.sub("", visible_line)
626
+ padding_needed = terminal_width - len(visible_line) - 1
627
+ else:
628
+ padding_needed = terminal_width - len(line) - 1
629
+
630
+ line += " " * padding_needed + vertical
631
+ result.append(line)
632
+
633
+ # Empty line before bottom (only if we have content)
634
+ if processed_lines:
635
+ result.append(vertical + " " * (terminal_width - 2) + vertical)
636
+
637
+ # Bottom border
638
+ result.append(bottom_left + horizontal * (terminal_width - 2) + bottom_right)
639
+
640
+ return "\n".join(result)
641
+
642
+
643
+ def _process_line(
644
+ line: str, line_number: int, available_width: int, diff_marker: Optional[str]
645
+ ) -> List[Tuple[Optional[int], str, Optional[str]]]:
646
+ """
647
+ Process a single line, handling wrapping if necessary.
648
+
649
+ Returns a list of tuples (line_number, content, diff_marker)
650
+ """
651
+ # Calculate space needed for line number and spacing
652
+ # " 9999 " for normal lines or " 9999 + " for diff lines
653
+ prefix_length = 9 # Reduced from 13 to 9
654
+
655
+ # Available width for actual content
656
+ content_width = available_width - prefix_length
657
+
658
+ processed: List[Tuple[Optional[int], str, Optional[str]]] = []
659
+
660
+ if len(line) <= content_width:
661
+ # Line fits, add it as is
662
+ processed.append((line_number, line, diff_marker))
663
+ else:
664
+ # Line needs wrapping
665
+ # First line with line number
666
+ first_part = line[:content_width]
667
+ processed.append((line_number, first_part, diff_marker))
668
+
669
+ # Remaining wrapped lines without line numbers
670
+ remaining = line[content_width:]
671
+ while remaining:
672
+ if len(remaining) <= content_width:
673
+ processed.append((None, remaining, diff_marker))
674
+ break
675
+ else:
676
+ processed.append((None, remaining[:content_width], diff_marker))
677
+ remaining = remaining[content_width:]
678
+
679
+ return processed
@@ -211,6 +211,7 @@ def deployment_ls(ctx: click.Context, include_deleted: bool) -> None:
211
211
  List all the deployments you have in the project.
212
212
  """
213
213
  client = ctx.ensure_object(dict)["client"]
214
+ output = ctx.ensure_object(dict)["output"]
214
215
 
215
216
  TINYBIRD_API_KEY = client.token
216
217
  HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
@@ -243,7 +244,27 @@ def deployment_ls(ctx: click.Context, include_deleted: bool) -> None:
243
244
  )
244
245
 
245
246
  table.reverse()
246
- echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
247
+
248
+ # Handle different output formats
249
+ if output == "json":
250
+ # Create JSON structure
251
+ deployments_json = []
252
+ for row in table:
253
+ deployments_json.append({"id": row[0], "status": row[1], "created_at": row[2]})
254
+ from tinybird.tb.modules.common import echo_json
255
+
256
+ echo_json({"deployments": deployments_json})
257
+ elif output == "csv":
258
+ # Create CSV output
259
+ csv_output = f"{columns[0]},{columns[1]},{columns[2]}\n"
260
+ for row in table:
261
+ csv_output += f"{row[0]},{row[1]},{row[2]}\n"
262
+ from tinybird.tb.modules.common import force_echo
263
+
264
+ force_echo(csv_output)
265
+ else:
266
+ # Default human-readable output
267
+ echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
247
268
 
248
269
 
249
270
  @deployment_group.command(name="promote")
@@ -0,0 +1,39 @@
1
+ from typing import Optional
2
+
3
+ import click
4
+
5
+ from tinybird.tb.modules.cli import cli
6
+ from tinybird.tb.modules.login_common import login
7
+
8
+
9
+ @cli.command("login", help="Authenticate using the browser.")
10
+ @click.option(
11
+ "--host",
12
+ type=str,
13
+ default=None,
14
+ help="Set custom host if it's different than https://api.europe-west2.gcp.tinybird.co. See https://www.tinybird.co/docs/api-reference/overview#regions-and-endpoints for the available list of regions.",
15
+ )
16
+ @click.option(
17
+ "--auth-host",
18
+ default="https://cloud.tinybird.co",
19
+ help="Set the host to authenticate to. If unset, the default host will be used.",
20
+ )
21
+ @click.option(
22
+ "--workspace",
23
+ help="Set the workspace to authenticate to. If unset, the default workspace will be used.",
24
+ )
25
+ @click.option(
26
+ "-i",
27
+ "--interactive",
28
+ is_flag=True,
29
+ default=False,
30
+ help="Show available regions and select where to authenticate to",
31
+ )
32
+ @click.option(
33
+ "--method",
34
+ type=click.Choice(["browser", "code"]),
35
+ default="browser",
36
+ help="Set the authentication method to use. Default: browser.",
37
+ )
38
+ def login_cmd(host: Optional[str], auth_host: str, workspace: str, interactive: bool, method: str):
39
+ login(host, auth_host, workspace, interactive, method)