daytona 0.126.0.pre.alpha.5 → 0.134.0.alpha.1

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.
data/lib/daytona/git.rb CHANGED
@@ -37,7 +37,7 @@ module Daytona
37
37
  # "README.md"
38
38
  # ])
39
39
  def add(path, files)
40
- toolbox_api.git_add_files(DaytonaToolboxApiClient::GitAddRequest.new(path:, files:))
40
+ toolbox_api.add_files(DaytonaToolboxApiClient::GitAddRequest.new(path:, files:))
41
41
  rescue StandardError => e
42
42
  raise Sdk::Error, "Failed to add files: #{e.message}"
43
43
  end
@@ -53,7 +53,7 @@ module Daytona
53
53
  # response = sandbox.git.branches("workspace/repo")
54
54
  # puts "Branches: #{response.branches}"
55
55
  def branches(path)
56
- toolbox_api.git_list_branches(path)
56
+ toolbox_api.list_branches(path)
57
57
  rescue StandardError => e
58
58
  raise Sdk::Error, "Failed to list branches: #{e.message}"
59
59
  end
@@ -97,7 +97,7 @@ module Daytona
97
97
  # commit_id: "abc123"
98
98
  # )
99
99
  def clone(url:, path:, branch: nil, commit_id: nil, username: nil, password: nil) # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
100
- toolbox_api.git_clone_repository(
100
+ toolbox_api.clone_repository(
101
101
  DaytonaToolboxApiClient::GitCloneRequest.new(
102
102
  url: url,
103
103
  branch: branch,
@@ -135,7 +135,7 @@ module Daytona
135
135
  # )
136
136
  # puts "Commit SHA: #{commit_response.sha}"
137
137
  def commit(path:, message:, author:, email:, allow_empty: false)
138
- response = toolbox_api.git_commit_changes(
138
+ response = toolbox_api.commit_changes(
139
139
  DaytonaToolboxApiClient::GitCommitRequest.new(path:, message:, author:, email:, allow_empty:)
140
140
  )
141
141
  GitCommitResponse.new(sha: response.hash)
@@ -165,7 +165,7 @@ module Daytona
165
165
  # password: "github_token"
166
166
  # )
167
167
  def push(path:, username: nil, password: nil)
168
- toolbox_api.git_push_changes(
168
+ toolbox_api.push_changes(
169
169
  DaytonaToolboxApiClient::GitRepoRequest.new(path:, username:, password:)
170
170
  )
171
171
  rescue StandardError => e
@@ -194,7 +194,7 @@ module Daytona
194
194
  # )
195
195
  #
196
196
  def pull(path:, username: nil, password: nil)
197
- toolbox_api.git_pull_changes(
197
+ toolbox_api.pull_changes(
198
198
  DaytonaToolboxApiClient::GitRepoRequest.new(path:, username:, password:)
199
199
  )
200
200
  rescue StandardError => e
@@ -214,7 +214,7 @@ module Daytona
214
214
  # puts "Commits ahead: #{status.ahead}"
215
215
  # puts "Commits behind: #{status.behind}"
216
216
  def status(path)
217
- toolbox_api.git_get_status(path)
217
+ toolbox_api.get_status(path)
218
218
  rescue StandardError => e
219
219
  raise Sdk::Error, "Failed to get status: #{e.message}"
220
220
  end
@@ -231,7 +231,7 @@ module Daytona
231
231
  # # Checkout a branch
232
232
  # sandbox.git.checkout_branch("workspace/repo", "feature-branch")
233
233
  def checkout_branch(path, branch)
234
- toolbox_api.git_checkout_branch(
234
+ toolbox_api.checkout_branch(
235
235
  DaytonaToolboxApiClient::GitCheckoutRequest.new(path:, branch:)
236
236
  )
237
237
  rescue StandardError => e
@@ -251,7 +251,7 @@ module Daytona
251
251
  # sandbox.git.create_branch("workspace/repo", "new-feature")
252
252
  #
253
253
  def create_branch(path, name)
254
- toolbox_api.git_create_branch(
254
+ toolbox_api.create_branch(
255
255
  DaytonaToolboxApiClient::GitBranchRequest.new(path:, name:)
256
256
  )
257
257
  rescue StandardError => e
@@ -270,7 +270,7 @@ module Daytona
270
270
  # # Delete a branch
271
271
  # sandbox.git.delete_branch("workspace/repo", "old-feature")
272
272
  def delete_branch(path, name)
273
- toolbox_api.git_delete_branch(
273
+ toolbox_api.delete_branch(
274
274
  DaytonaToolboxApiClient::GitDeleteBranchRequest.new(path:, name:)
275
275
  )
276
276
  rescue StandardError => e
@@ -44,7 +44,7 @@ module Daytona
44
44
  # @param position [Daytona::LspServer::Position]
45
45
  # @return [DaytonaApiClient::CompletionList]
46
46
  def completions(path:, position:)
47
- toolbox_api.lsp_completions(
47
+ toolbox_api.completions(
48
48
  DaytonaToolboxApiClient::LspCompletionParams.new(
49
49
  language_id:,
50
50
  path_to_project:,
@@ -61,7 +61,7 @@ module Daytona
61
61
  # @param path [String]
62
62
  # @return [void]
63
63
  def did_close(path)
64
- toolbox_api.lsp_did_close(
64
+ toolbox_api.did_close(
65
65
  DaytonaToolboxApiClient::LspDocumentRequest.new(language_id:, path_to_project:, uri: uri(path))
66
66
  )
67
67
  end
@@ -74,7 +74,7 @@ module Daytona
74
74
  # @param path [String]
75
75
  # @return [void]
76
76
  def did_open(path)
77
- toolbox_api.lsp_did_open(
77
+ toolbox_api.did_open(
78
78
  DaytonaToolboxApiClient::LspDocumentRequest.new(language_id:, path_to_project:, uri: uri(path))
79
79
  )
80
80
  end
@@ -83,14 +83,14 @@ module Daytona
83
83
  #
84
84
  # @param path [String]
85
85
  # @return [Array<DaytonaToolboxApiClient::LspSymbol]
86
- def document_symbols(path) = toolbox_api.lsp_document_symbols(language_id, path_to_project, uri(path))
86
+ def document_symbols(path) = toolbox_api.document_symbols(language_id, path_to_project, uri(path))
87
87
 
88
88
  # Searches for symbols matching the query string across all files
89
89
  # in the Sandbox.
90
90
  #
91
91
  # @param query [String]
92
92
  # @return [Array<DaytonaToolboxApiClient::LspSymbol]
93
- def sandbox_symbols(query) = toolbox_api.lsp_workspace_symbols(language_id, path_to_project, query)
93
+ def sandbox_symbols(query) = toolbox_api.workspace_symbols(query, language_id, path_to_project)
94
94
 
95
95
  # Starts the language server.
96
96
  # This method must be called before using any other LSP functionality.
@@ -98,7 +98,7 @@ module Daytona
98
98
  #
99
99
  # @return [void]
100
100
  def start
101
- toolbox_api.lsp_start(
101
+ toolbox_api.start(
102
102
  DaytonaToolboxApiClient::LspServerRequest.new(language_id:, path_to_project:)
103
103
  )
104
104
  end
@@ -109,7 +109,7 @@ module Daytona
109
109
  #
110
110
  # @return [void]
111
111
  def stop
112
- toolbox_api.lsp_stop(
112
+ toolbox_api.stop(
113
113
  DaytonaToolboxApiClient::LspServerRequest.new(language_id:, path_to_project:)
114
114
  )
115
115
  end
@@ -110,8 +110,8 @@ module Daytona
110
110
  .glob(File.join(abs_path_str, '**', '*'))
111
111
  .select { |path| File.directory?(path) && Dir.empty?(path) }
112
112
  .each do |empty_dir|
113
- rel_dir = Pathname.new(empty_dir).relative_path_from(Pathname.new(abs_path_str)).to_s
114
- md5_hasher.update(rel_dir)
113
+ rel_dir = Pathname.new(empty_dir).relative_path_from(Pathname.new(abs_path_str)).to_s
114
+ md5_hasher.update(rel_dir)
115
115
  end
116
116
  end
117
117
 
@@ -222,26 +222,40 @@ module Daytona
222
222
  url.path = "/process/session/#{session_id}/command/#{command_id}/logs"
223
223
  url.query = 'follow=true'
224
224
 
225
- WebSocket::Client::Simple.connect(
225
+ completion_queue = Queue.new
226
+
227
+ ws = WebSocket::Client::Simple.connect(
226
228
  url.to_s,
227
229
  headers: toolbox_api.api_client.default_headers.dup.merge(
228
230
  'X-Daytona-Preview-Token' => preview_link.token,
229
231
  'Content-Type' => 'text/plain',
230
232
  'Accept' => 'text/plain'
231
233
  )
232
- ) do |ws|
233
- ws.on(:message) do |message|
234
- if message.type == :close
235
- ws.close
236
- next
237
- else
238
- stdout, stderr = Util.demux(message.data.to_s)
239
-
240
- on_stdout.call(stdout) unless stdout.empty?
241
- on_stderr.call(stderr) unless stderr.empty?
242
- end
234
+ )
235
+
236
+ ws.on(:message) do |message|
237
+ if message.type == :close
238
+ ws.close
239
+ completion_queue.push(:close)
240
+ else
241
+ stdout, stderr = Util.demux(message.data.to_s)
242
+
243
+ on_stdout.call(stdout) unless stdout.empty?
244
+ on_stderr.call(stderr) unless stderr.empty?
243
245
  end
244
246
  end
247
+
248
+ ws.on(:close) do
249
+ completion_queue.push(:close)
250
+ end
251
+
252
+ ws.on(:error) do |e|
253
+ completion_queue.push(:error)
254
+ raise Sdk::Error, "WebSocket error: #{e.message}"
255
+ end
256
+
257
+ # Wait for completion
258
+ completion_queue.pop
245
259
  end
246
260
 
247
261
  #
@@ -109,6 +109,9 @@ module Daytona
109
109
  # @return [Daytona::ComputerUse]
110
110
  attr_reader :computer_use
111
111
 
112
+ # @return [Daytona::CodeInterpreter]
113
+ attr_reader :code_interpreter
114
+
112
115
  # @params code_toolbox [Daytona::SandboxPythonCodeToolbox, Daytona::SandboxTsCodeToolbox]
113
116
  # @params config [Daytona::Config]
114
117
  # @params sandbox_api [DaytonaApiClient::SandboxApi]
@@ -127,6 +130,9 @@ module Daytona
127
130
  create_authenticated_client = lambda do
128
131
  client = DaytonaToolboxApiClient::ApiClient.new(toolbox_api_config)
129
132
  client.default_headers['Authorization'] = "Bearer #{config.api_key || config.jwt_token}"
133
+ client.default_headers['X-Daytona-Source'] = 'ruby-sdk'
134
+ client.default_headers['X-Daytona-SDK-Version'] = Sdk::VERSION
135
+ client.default_headers['X-Daytona-Organization-ID'] = config.organization_id if config.jwt_token
130
136
  client
131
137
  end
132
138
 
@@ -135,6 +141,8 @@ module Daytona
135
141
  git_api = DaytonaToolboxApiClient::GitApi.new(create_authenticated_client.call)
136
142
  lsp_api = DaytonaToolboxApiClient::LspApi.new(create_authenticated_client.call)
137
143
  computer_use_api = DaytonaToolboxApiClient::ComputerUseApi.new(create_authenticated_client.call)
144
+ interpreter_api = DaytonaToolboxApiClient::InterpreterApi.new(create_authenticated_client.call)
145
+ info_api = DaytonaToolboxApiClient::InfoApi.new(create_authenticated_client.call)
138
146
 
139
147
  @process = Process.new(
140
148
  sandbox_id: id,
@@ -145,7 +153,13 @@ module Daytona
145
153
  @fs = FileSystem.new(sandbox_id: id, toolbox_api: fs_api)
146
154
  @git = Git.new(sandbox_id: id, toolbox_api: git_api)
147
155
  @computer_use = ComputerUse.new(sandbox_id: id, toolbox_api: computer_use_api)
156
+ @code_interpreter = CodeInterpreter.new(
157
+ sandbox_id: id,
158
+ toolbox_api: interpreter_api,
159
+ get_preview_link: proc { |port| preview_url(port) }
160
+ )
148
161
  @lsp_api = lsp_api
162
+ @info_api = info_api
149
163
  end
150
164
 
151
165
  # Archives the sandbox, making it inactive and preserving its state. When sandboxes are
@@ -211,6 +225,33 @@ module Daytona
211
225
  refresh
212
226
  end
213
227
 
228
+ # Gets the user's home directory path for the logged in user inside the Sandbox.
229
+ #
230
+ # @return [String] The absolute path to the Sandbox user's home directory for the logged in user
231
+ #
232
+ # @example
233
+ # user_home_dir = sandbox.get_user_home_dir
234
+ # puts "Sandbox user home: #{user_home_dir}"
235
+ def get_user_home_dir
236
+ @info_api.get_user_home_dir.dir
237
+ rescue StandardError => e
238
+ raise Sdk::Error, "Failed to get user home directory: #{e.message}"
239
+ end
240
+
241
+ # Gets the working directory path inside the Sandbox.
242
+ #
243
+ # @return [String] The absolute path to the Sandbox working directory. Uses the WORKDIR specified
244
+ # in the Dockerfile if present, or falling back to the user's home directory if not.
245
+ #
246
+ # @example
247
+ # work_dir = sandbox.get_work_dir
248
+ # puts "Sandbox working directory: #{work_dir}"
249
+ def get_work_dir
250
+ @info_api.get_work_dir.dir
251
+ rescue StandardError => e
252
+ raise Sdk::Error, "Failed to get working directory path: #{e.message}"
253
+ end
254
+
214
255
  # Sets labels for the Sandbox.
215
256
  #
216
257
  # @param labels [Hash<String, String>]
@@ -227,11 +268,54 @@ module Daytona
227
268
  # @return [DaytonaApiClient::PortPreviewUrl]
228
269
  def preview_url(port) = sandbox_api.get_port_preview_url(id, port)
229
270
 
271
+ # Creates a signed preview URL for the sandbox at the specified port.
272
+ #
273
+ # @param port [Integer] The port to open the preview link on
274
+ # @param expires_in_seconds [Integer, nil] The number of seconds the signed preview URL
275
+ # will be valid for. Defaults to 60 seconds.
276
+ # @return [DaytonaApiClient::SignedPortPreviewUrl] The signed preview URL response object
277
+ #
278
+ # @example
279
+ # signed_url = sandbox.create_signed_preview_url(3000, 120)
280
+ # puts "Signed URL: #{signed_url.url}"
281
+ # puts "Token: #{signed_url.token}"
282
+ def create_signed_preview_url(port, expires_in_seconds = nil)
283
+ sandbox_api.get_signed_port_preview_url(id, port, { expires_in_seconds: })
284
+ end
285
+
286
+ # Expires a signed preview URL for the sandbox at the specified port.
287
+ #
288
+ # @param port [Integer] The port to expire the signed preview URL on
289
+ # @param token [String] The token to expire
290
+ # @return [void]
291
+ #
292
+ # @example
293
+ # sandbox.expire_signed_preview_url(3000, "token-value")
294
+ def expire_signed_preview_url(port, token)
295
+ sandbox_api.expire_signed_port_preview_url(id, port, token)
296
+ end
297
+
230
298
  # Refresh the Sandbox data from the API.
231
299
  #
232
300
  # @return [void]
233
301
  def refresh = process_response(sandbox_api.get_sandbox(id))
234
302
 
303
+ # Refreshes the sandbox activity to reset the timer for automated lifecycle management actions.
304
+ #
305
+ # This method updates the sandbox's last activity timestamp without changing its state.
306
+ # It is useful for keeping long-running sessions alive while there is still user activity.
307
+ #
308
+ # @return [void]
309
+ #
310
+ # @example
311
+ # sandbox.refresh_activity
312
+ def refresh_activity
313
+ sandbox_api.update_last_activity(id)
314
+ nil
315
+ rescue StandardError => e
316
+ raise Sdk::Error, "Failed to refresh sandbox activity: #{e.message}"
317
+ end
318
+
235
319
  # Revokes an SSH access token for the sandbox.
236
320
  #
237
321
  # @param token [String]
@@ -250,6 +334,25 @@ module Daytona
250
334
  ) { wait_for_states(operation: OPERATION_START, target_states: [DaytonaApiClient::SandboxState::STARTED]) }
251
335
  end
252
336
 
337
+ # Recovers the Sandbox from a recoverable error and waits for it to be ready.
338
+ #
339
+ # @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
340
+ # @return [void]
341
+ #
342
+ # @example
343
+ # sandbox = daytona.get('my-sandbox-id')
344
+ # sandbox.recover(timeout: 40) # Wait up to 40 seconds
345
+ # puts 'Sandbox recovered successfully'
346
+ def recover(timeout = DEFAULT_TIMEOUT)
347
+ with_timeout(
348
+ timeout:,
349
+ message: "Sandbox #{id} failed to recover within the #{timeout} seconds timeout period",
350
+ setup: proc { process_response(sandbox_api.recover_sandbox(id)) }
351
+ ) { wait_for_states(operation: OPERATION_START, target_states: [DaytonaApiClient::SandboxState::STARTED]) }
352
+ rescue StandardError => e
353
+ raise Sdk::Error, "Failed to recover sandbox: #{e.message}"
354
+ end
355
+
253
356
  # Stops the Sandbox and waits for it to be stopped.
254
357
  #
255
358
  # @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
@@ -297,6 +400,17 @@ module Daytona
297
400
  wait_for_states(operation: OPERATION_START, target_states: [DaytonaApiClient::SandboxState::STARTED])
298
401
  end
299
402
 
403
+ # Waits for the Sandbox to reach the 'stopped' state. Polls the Sandbox status until it
404
+ # reaches the 'stopped' state or encounters an error.
405
+ # Treats destroyed as stopped to cover ephemeral sandboxes that are automatically deleted after stopping.
406
+ #
407
+ # @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
408
+ # @return [void]
409
+ def wait_for_sandbox_stop(_timeout = DEFAULT_TIMEOUT)
410
+ wait_for_states(operation: OPERATION_STOP, target_states: [DaytonaApiClient::SandboxState::STOPPED,
411
+ DaytonaApiClient::SandboxState::DESTROYED])
412
+ end
413
+
300
414
  private
301
415
 
302
416
  # Build toolbox API configuration with dynamic base URL from preview link
@@ -304,7 +418,7 @@ module Daytona
304
418
  def build_toolbox_api_config
305
419
  DaytonaToolboxApiClient::Configuration.new.configure do |cfg|
306
420
  # Get the proxy toolbox URL and append sandbox ID
307
- proxy_toolbox_url = @get_proxy_toolbox_url.call
421
+ proxy_toolbox_url = @get_proxy_toolbox_url.call(id, target)
308
422
  proxy_toolbox_url += '/' unless proxy_toolbox_url.end_with?('/')
309
423
  full_url = "#{proxy_toolbox_url}#{id}"
310
424
  uri = URI(full_url)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Daytona
4
4
  module Sdk
5
- VERSION = '0.126.0.pre.alpha.5'
5
+ VERSION = '0.134.0.alpha.1'
6
6
  end
7
7
  end
data/lib/daytona/sdk.rb CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'logger'
4
4
 
5
- require 'dotenv'
6
- Dotenv.load('.env.local', '.env')
7
5
  require 'daytona_api_client'
8
6
  require 'daytona_toolbox_api_client'
9
7
  require 'toml'
@@ -12,6 +10,7 @@ require 'websocket-client-simple'
12
10
  require_relative 'sdk/version'
13
11
  require_relative 'config'
14
12
  require_relative 'common/charts'
13
+ require_relative 'common/code_interpreter'
15
14
  require_relative 'common/code_language'
16
15
  require_relative 'common/daytona'
17
16
  require_relative 'common/file_system'
@@ -22,9 +21,11 @@ require_relative 'common/pty'
22
21
  require_relative 'common/resources'
23
22
  require_relative 'common/response'
24
23
  require_relative 'common/snapshot'
24
+ require_relative 'code_interpreter'
25
25
  require_relative 'computer_use'
26
26
  require_relative 'code_toolbox/sandbox_python_code_toolbox'
27
27
  require_relative 'code_toolbox/sandbox_ts_code_toolbox'
28
+ require_relative 'code_toolbox/sandbox_js_code_toolbox'
28
29
  require_relative 'daytona'
29
30
  require_relative 'file_system'
30
31
  require_relative 'git'
@@ -40,6 +41,7 @@ require_relative 'process'
40
41
  module Daytona
41
42
  module Sdk
42
43
  class Error < StandardError; end
44
+ class TimeoutError < Error; end
43
45
 
44
46
  def self.logger = @logger ||= Logger.new($stdout, level: Logger::INFO)
45
47
  end
@@ -8,9 +8,11 @@ module Daytona
8
8
 
9
9
  # @param snapshots_api [DaytonaApiClient::SnapshotsApi] The snapshots API client
10
10
  # @param object_storage_api [DaytonaApiClient::ObjectStorageApi] The object storage API client
11
- def initialize(snapshots_api:, object_storage_api:)
11
+ # @param default_region_id [String, nil] Default region ID for snapshot creation
12
+ def initialize(snapshots_api:, object_storage_api:, default_region_id: nil)
12
13
  @snapshots_api = snapshots_api
13
14
  @object_storage_api = object_storage_api
15
+ @default_region_id = default_region_id
14
16
  end
15
17
 
16
18
  # List all Snapshots.
@@ -97,9 +99,12 @@ module Daytona
97
99
  create_snapshot_req.disk = params.resources.disk
98
100
  end
99
101
 
102
+ create_snapshot_req.region_id = params.region_id || @default_region_id
103
+
100
104
  snapshot = snapshots_api.create_snapshot(create_snapshot_req)
101
105
 
102
- snapshot = stream_logs(snapshot, on_logs:) if on_logs
106
+ # Always wait for snapshot to be ready, regardless of on_logs
107
+ snapshot = wait_for_snapshot(snapshot, on_logs:)
103
108
 
104
109
  if [DaytonaApiClient::SnapshotState::ERROR, DaytonaApiClient::SnapshotState::BUILD_FAILED].include?(snapshot.state)
105
110
  raise Sdk::Error, "Failed to create snapshot #{snapshot.name}, reason: #{snapshot.error_reason}"
@@ -148,10 +153,16 @@ module Daytona
148
153
  # @return [DaytonaApiClient::ObjectStorageApi, nil] The object storage API client
149
154
  attr_reader :object_storage_api
150
155
 
156
+ # @return [String, nil] Default region ID for snapshot creation
157
+ attr_reader :default_region_id
158
+
159
+ # Wait for snapshot to reach a terminal state (ACTIVE, ERROR, or BUILD_FAILED)
160
+ # Optionally streams logs if on_logs callback is provided
161
+ #
151
162
  # @param snapshot [DaytonaApiClient::SnapshotDto]
152
- # @param on_logs [Proc]
163
+ # @param on_logs [Proc, nil]
153
164
  # @return [DaytonaApiClient::SnapshotDto]
154
- def stream_logs(snapshot, on_logs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
165
+ def wait_for_snapshot(snapshot, on_logs:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
155
166
  terminal_states = [
156
167
  DaytonaApiClient::SnapshotState::ACTIVE,
157
168
  DaytonaApiClient::SnapshotState::ERROR,
@@ -160,13 +171,25 @@ module Daytona
160
171
 
161
172
  thread = nil
162
173
  previous_state = snapshot.state
174
+
175
+ # Log initial state if callback provided
176
+ on_logs&.call("Creating snapshot #{snapshot.name} (#{snapshot.state})")
177
+
163
178
  until terminal_states.include?(snapshot.state)
164
179
  Sdk.logger.debug("Waiting for snapshot to be created: #{snapshot.state}")
165
- if thread.nil? && snapshot.state != DaytonaApiClient::SnapshotState::BUILD_PENDING
180
+
181
+ # Start log streaming thread if callback provided and snapshot is building
182
+ if on_logs && thread.nil? && snapshot.state != DaytonaApiClient::SnapshotState::PENDING
166
183
  thread = start_log_streaming(snapshot, on_logs:)
167
184
  end
168
185
 
169
- on_logs.call("Creating snapshot #{snapshot.name} (#{snapshot.state})") if previous_state != snapshot.state
186
+ # Log state changes if callback provided
187
+ if on_logs && previous_state != snapshot.state
188
+ if snapshot.state != DaytonaApiClient::SnapshotState::PENDING && thread.nil?
189
+ thread = start_log_streaming(snapshot, on_logs:)
190
+ end
191
+ on_logs.call("Creating snapshot #{snapshot.name} (#{snapshot.state})")
192
+ end
170
193
 
171
194
  sleep(1)
172
195
  previous_state = snapshot.state
@@ -175,7 +198,7 @@ module Daytona
175
198
 
176
199
  thread&.join
177
200
 
178
- if snapshot.state == DaytonaApiClient::SnapshotState::ACTIVE
201
+ if on_logs && snapshot.state == DaytonaApiClient::SnapshotState::ACTIVE
179
202
  on_logs.call("Created snapshot #{snapshot.name} (#{snapshot.state})")
180
203
  end
181
204
 
@@ -186,9 +209,9 @@ module Daytona
186
209
  # @param on_logs [Proc]
187
210
  # @return [Thread]
188
211
  def start_log_streaming(snapshot, on_logs:)
189
- uri = URI.parse(snapshots_api.api_client.config.base_url)
190
- uri.path = "/api/snapshots/#{snapshot.id}/build-logs"
191
- uri.query = 'follow=true'
212
+ # Get build logs URL from API
213
+ build_logs_response = snapshots_api.get_snapshot_build_logs_url(snapshot.id)
214
+ uri = URI.parse("#{build_logs_response.url}?follow=true")
192
215
 
193
216
  headers = {}
194
217
  snapshots_api.api_client.update_params_for_auth!(headers, nil, ['bearer'])
data/lib/daytona.rb CHANGED
@@ -5,4 +5,3 @@
5
5
  # require 'daytona'
6
6
 
7
7
  require_relative 'daytona/sdk'
8
-
data/project.json CHANGED
@@ -24,7 +24,7 @@
24
24
  "executor": "nx:run-commands",
25
25
  "options": {
26
26
  "cwd": "{projectRoot}",
27
- "command": "if [ -n \"$RUBYGEMS_PKG_VERSION\" ] || [ -n \"$DEFAULT_PACKAGE_VERSION\" ]; then VER=${RUBYGEMS_PKG_VERSION:-$DEFAULT_PACKAGE_VERSION}; sed -i \"s/VERSION = '[^']*'/VERSION = '$VER'/\" lib/daytona/sdk/version.rb && echo \"Changed version to $VER\"; else echo \"Using version from version.rb\"; fi"
27
+ "command": "if [ -n \"$RUBYGEMS_PKG_VERSION\" ]; then sed -i \"s/VERSION = '[^']*'/VERSION = '$RUBYGEMS_PKG_VERSION'/\" lib/daytona/sdk/version.rb && echo \"Changed version to $RUBYGEMS_PKG_VERSION\"; else echo \"Using version from version.rb\"; fi"
28
28
  }
29
29
  },
30
30
  "lint": {
@@ -49,11 +49,22 @@
49
49
  "parallel": false
50
50
  }
51
51
  },
52
+ "docs": {
53
+ "executor": "nx:run-commands",
54
+ "options": {
55
+ "cwd": "{projectRoot}",
56
+ "command": "bash -O extglob -c 'rm -rf ../../apps/docs/src/content/docs/en/ruby-sdk/!(index.mdx)' && bundle exec ruby scripts/generate-docs.rb"
57
+ }
58
+ },
52
59
  "publish": {
53
60
  "executor": "nx:run-commands",
54
61
  "options": {
55
62
  "cwd": "{projectRoot}",
56
- "command": "gem push daytona-*.gem --key rubygems --host https://rubygems.org"
63
+ "commands": [
64
+ "mkdir -p ~/.gem && echo \":rubygems: $RUBYGEMS_API_KEY\" > ~/.gem/credentials && chmod 0600 ~/.gem/credentials",
65
+ "gem push daytona-*.gem --key rubygems --host https://rubygems.org"
66
+ ],
67
+ "parallel": false
57
68
  },
58
69
  "dependsOn": [
59
70
  {