e2b 0.3.4 → 0.3.5
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.
- checksums.yaml +4 -4
- data/README.md +104 -20
- data/lib/e2b/api/http_client.rb +5 -1
- data/lib/e2b/client.rb +3 -5
- data/lib/e2b/configuration.rb +6 -6
- data/lib/e2b/models/process_result.rb +17 -17
- data/lib/e2b/models/template_log_entry.rb +1 -1
- data/lib/e2b/paginator.rb +1 -3
- data/lib/e2b/sandbox.rb +5 -10
- data/lib/e2b/sandbox_helpers.rb +5 -7
- data/lib/e2b/services/base_service.rb +75 -56
- data/lib/e2b/services/command_handle.rb +10 -22
- data/lib/e2b/services/commands.rb +26 -27
- data/lib/e2b/services/filesystem.rb +29 -32
- data/lib/e2b/services/git.rb +8 -8
- data/lib/e2b/services/live_streamable.rb +25 -25
- data/lib/e2b/services/pty.rb +15 -17
- data/lib/e2b/template.rb +32 -29
- data/lib/e2b/version.rb +1 -1
- metadata +39 -10
|
@@ -22,31 +22,31 @@ module E2B
|
|
|
22
22
|
|
|
23
23
|
begin
|
|
24
24
|
envd_rpc("process.Process", rpc_method,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
25
|
+
body: body,
|
|
26
|
+
timeout: timeout,
|
|
27
|
+
headers: headers,
|
|
28
|
+
on_event: lambda { |event_data|
|
|
29
|
+
stream_event = event_data[:event]
|
|
30
|
+
stream.push(stream_event) if stream_event
|
|
31
|
+
|
|
32
|
+
stdout_chunk = event_data[:stdout]
|
|
33
|
+
stderr_chunk = event_data[:stderr]
|
|
34
|
+
|
|
35
|
+
on_stdout&.call(stdout_chunk) if stdout_chunk && !stdout_chunk.empty?
|
|
36
|
+
on_stderr&.call(stderr_chunk) if stderr_chunk && !stderr_chunk.empty?
|
|
37
|
+
|
|
38
|
+
stream_block&.call(:stdout, stdout_chunk) if stdout_chunk && !stdout_chunk.empty?
|
|
39
|
+
stream_block&.call(:stderr, stderr_chunk) if stderr_chunk && !stderr_chunk.empty?
|
|
40
|
+
|
|
41
|
+
unless start_signal_sent
|
|
42
|
+
extracted_pid = extract_pid_from_event(stream_event)
|
|
43
|
+
if extracted_pid
|
|
44
|
+
pid = extracted_pid
|
|
45
|
+
start_signal_sent = true
|
|
46
|
+
start_queue << [:pid, pid]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
})
|
|
50
50
|
|
|
51
51
|
unless start_signal_sent
|
|
52
52
|
start_signal_sent = true
|
data/lib/e2b/services/pty.rb
CHANGED
|
@@ -54,6 +54,7 @@ module E2B
|
|
|
54
54
|
# handle = pty.connect(pid)
|
|
55
55
|
class Pty < BaseService
|
|
56
56
|
include LiveStreamable
|
|
57
|
+
|
|
57
58
|
# Default shell to use for PTY sessions
|
|
58
59
|
DEFAULT_SHELL = "/bin/bash"
|
|
59
60
|
|
|
@@ -155,9 +156,9 @@ module E2B
|
|
|
155
156
|
def send_stdin(pid, data, headers: nil)
|
|
156
157
|
encoded = Base64.strict_encode64(data.is_a?(String) ? data : data.to_s)
|
|
157
158
|
envd_rpc("process.Process", "SendInput", body: {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
process: { pid: pid },
|
|
160
|
+
input: { pty: encoded }
|
|
161
|
+
}, headers: headers)
|
|
161
162
|
end
|
|
162
163
|
|
|
163
164
|
# Kill a PTY process with SIGKILL.
|
|
@@ -169,9 +170,9 @@ module E2B
|
|
|
169
170
|
# sandbox.pty.kill(12345)
|
|
170
171
|
def kill(pid, headers: nil)
|
|
171
172
|
envd_rpc("process.Process", "SendSignal", body: {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
process: { pid: pid },
|
|
174
|
+
signal: 9 # SIGKILL
|
|
175
|
+
}, headers: headers)
|
|
175
176
|
true
|
|
176
177
|
rescue E2B::E2BError
|
|
177
178
|
false
|
|
@@ -191,11 +192,11 @@ module E2B
|
|
|
191
192
|
# sandbox.pty.resize(pid, PtySize.new(cols: 120, rows: 40))
|
|
192
193
|
def resize(pid, size)
|
|
193
194
|
envd_rpc("process.Process", "Update", body: {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
process: { pid: pid },
|
|
196
|
+
pty: {
|
|
197
|
+
size: size.to_h
|
|
198
|
+
}
|
|
199
|
+
})
|
|
199
200
|
end
|
|
200
201
|
|
|
201
202
|
# Close the stdin of a PTY process.
|
|
@@ -208,8 +209,8 @@ module E2B
|
|
|
208
209
|
# @raise [E2B::E2BError] if the process is not found
|
|
209
210
|
def close_stdin(pid)
|
|
210
211
|
envd_rpc("process.Process", "CloseStdin", body: {
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
process: { pid: pid }
|
|
213
|
+
})
|
|
213
214
|
end
|
|
214
215
|
|
|
215
216
|
# List running processes in the sandbox.
|
|
@@ -235,13 +236,10 @@ module E2B
|
|
|
235
236
|
result["LANG"] = "C.UTF-8"
|
|
236
237
|
result["LC_ALL"] = "C.UTF-8"
|
|
237
238
|
|
|
238
|
-
if envs.is_a?(Hash)
|
|
239
|
-
envs.each { |k, v| result[k.to_s] = v.to_s }
|
|
240
|
-
end
|
|
239
|
+
envs.each { |k, v| result[k.to_s] = v.to_s } if envs.is_a?(Hash)
|
|
241
240
|
|
|
242
241
|
result
|
|
243
242
|
end
|
|
244
|
-
|
|
245
243
|
end
|
|
246
244
|
end
|
|
247
245
|
end
|
data/lib/e2b/template.rb
CHANGED
|
@@ -45,9 +45,9 @@ module E2B
|
|
|
45
45
|
credentials = resolve_credentials(api_key: api_key, access_token: access_token)
|
|
46
46
|
http_client = build_http_client(**credentials, domain: resolve_domain(domain))
|
|
47
47
|
response = http_client.post("/templates/tags", body: {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
target: target_name,
|
|
49
|
+
tags: normalize_tags(tags)
|
|
50
|
+
})
|
|
51
51
|
|
|
52
52
|
Models::TemplateTagInfo.from_hash(response)
|
|
53
53
|
end
|
|
@@ -56,9 +56,9 @@ module E2B
|
|
|
56
56
|
credentials = resolve_credentials(api_key: api_key, access_token: access_token)
|
|
57
57
|
http_client = build_http_client(**credentials, domain: resolve_domain(domain))
|
|
58
58
|
http_client.delete("/templates/tags", body: {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
name: name,
|
|
60
|
+
tags: normalize_tags(tags)
|
|
61
|
+
})
|
|
62
62
|
nil
|
|
63
63
|
end
|
|
64
64
|
|
|
@@ -179,7 +179,7 @@ module E2B
|
|
|
179
179
|
credentials = resolve_credentials(api_key: api_key, access_token: access_token)
|
|
180
180
|
http_client = build_http_client(**credentials, domain: resolve_domain(domain))
|
|
181
181
|
|
|
182
|
-
tags_message = Array(tags).any? ? " with tags #{Array(tags).join(
|
|
182
|
+
tags_message = Array(tags).any? ? " with tags #{Array(tags).join(", ")}" : ""
|
|
183
183
|
on_build_logs&.call(log_entry("Requesting build for template: #{resolved_name}#{tags_message}"))
|
|
184
184
|
|
|
185
185
|
body = {
|
|
@@ -338,11 +338,11 @@ module E2B
|
|
|
338
338
|
|
|
339
339
|
if build_info.is_a?(Hash)
|
|
340
340
|
resolved_template_id = build_info[:template_id] || build_info["template_id"] ||
|
|
341
|
-
|
|
341
|
+
build_info[:templateID] || build_info["templateID"]
|
|
342
342
|
resolved_build_id = build_info[:build_id] || build_info["build_id"] ||
|
|
343
|
-
|
|
343
|
+
build_info[:buildID] || build_info["buildID"]
|
|
344
344
|
resolved_build_step_origins ||= build_info[:build_step_origins] || build_info["build_step_origins"] ||
|
|
345
|
-
|
|
345
|
+
build_info[:buildStepOrigins] || build_info["buildStepOrigins"]
|
|
346
346
|
|
|
347
347
|
return [resolved_template_id, resolved_build_id, Array(resolved_build_step_origins).compact]
|
|
348
348
|
end
|
|
@@ -404,12 +404,12 @@ module E2B
|
|
|
404
404
|
end
|
|
405
405
|
|
|
406
406
|
def resolve_credentials(api_key:, access_token:)
|
|
407
|
-
resolved_api_key = api_key || E2B.configuration&.api_key || ENV
|
|
408
|
-
resolved_access_token = access_token || E2B.configuration&.access_token || ENV
|
|
407
|
+
resolved_api_key = api_key || E2B.configuration&.api_key || ENV.fetch("E2B_API_KEY", nil)
|
|
408
|
+
resolved_access_token = access_token || E2B.configuration&.access_token || ENV.fetch("E2B_ACCESS_TOKEN", nil)
|
|
409
409
|
|
|
410
410
|
unless (resolved_api_key && !resolved_api_key.empty?) || (resolved_access_token && !resolved_access_token.empty?)
|
|
411
411
|
raise ConfigurationError,
|
|
412
|
-
|
|
412
|
+
"E2B credentials are required. Set E2B_API_KEY or E2B_ACCESS_TOKEN, or pass api_key:/access_token:."
|
|
413
413
|
end
|
|
414
414
|
|
|
415
415
|
{ api_key: resolved_api_key, access_token: resolved_access_token }
|
|
@@ -638,7 +638,7 @@ module E2B
|
|
|
638
638
|
|
|
639
639
|
def pip_install(packages = nil, g: true)
|
|
640
640
|
package_list = packages.nil? ? nil : Array(packages).map(&:to_s)
|
|
641
|
-
args = [
|
|
641
|
+
args = %w[pip install]
|
|
642
642
|
args << "--user" unless g
|
|
643
643
|
args.concat(package_list || ["."])
|
|
644
644
|
run_cmd(args.join(" "), user: g ? "root" : nil)
|
|
@@ -646,7 +646,7 @@ module E2B
|
|
|
646
646
|
|
|
647
647
|
def npm_install(packages = nil, g: false, dev: false)
|
|
648
648
|
package_list = packages.nil? ? nil : Array(packages).map(&:to_s)
|
|
649
|
-
args = [
|
|
649
|
+
args = %w[npm install]
|
|
650
650
|
args << "-g" if g
|
|
651
651
|
args << "--save-dev" if dev
|
|
652
652
|
args.concat(package_list) if package_list
|
|
@@ -655,7 +655,7 @@ module E2B
|
|
|
655
655
|
|
|
656
656
|
def bun_install(packages = nil, g: false, dev: false)
|
|
657
657
|
package_list = packages.nil? ? nil : Array(packages).map(&:to_s)
|
|
658
|
-
args = [
|
|
658
|
+
args = %w[bun install]
|
|
659
659
|
args << "-g" if g
|
|
660
660
|
args << "--dev" if dev
|
|
661
661
|
args.concat(package_list) if package_list
|
|
@@ -668,7 +668,7 @@ module E2B
|
|
|
668
668
|
run_cmd(
|
|
669
669
|
[
|
|
670
670
|
"apt-get update",
|
|
671
|
-
"DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes apt-get install -y #{install_flags}#{package_list.join(
|
|
671
|
+
"DEBIAN_FRONTEND=noninteractive DEBCONF_NOWARNINGS=yes apt-get install -y #{install_flags}#{package_list.join(" ")}"
|
|
672
672
|
],
|
|
673
673
|
user: "root"
|
|
674
674
|
)
|
|
@@ -683,7 +683,7 @@ module E2B
|
|
|
683
683
|
end
|
|
684
684
|
|
|
685
685
|
server_list = Array(servers).map(&:to_s)
|
|
686
|
-
run_cmd("mcp-gateway pull #{server_list.join(
|
|
686
|
+
run_cmd("mcp-gateway pull #{server_list.join(" ")}", user: "root")
|
|
687
687
|
end
|
|
688
688
|
|
|
689
689
|
def git_clone(url, path = nil, branch: nil, depth: nil, user: nil)
|
|
@@ -730,7 +730,7 @@ module E2B
|
|
|
730
730
|
|
|
731
731
|
def make_dir(path, mode: nil, user: nil)
|
|
732
732
|
args = ["mkdir", "-p"]
|
|
733
|
-
args << "-m #{format(
|
|
733
|
+
args << "-m #{format("%04o", mode)}" if mode
|
|
734
734
|
args.concat(Array(path).map(&:to_s))
|
|
735
735
|
run_cmd(args.join(" "), user: user)
|
|
736
736
|
end
|
|
@@ -777,9 +777,12 @@ module E2B
|
|
|
777
777
|
)
|
|
778
778
|
end
|
|
779
779
|
|
|
780
|
-
|
|
780
|
+
unless @base_image
|
|
781
|
+
raise template_error("No base image specified for template",
|
|
782
|
+
source_location: capture_source_location)
|
|
783
|
+
end
|
|
781
784
|
|
|
782
|
-
dockerfile =
|
|
785
|
+
dockerfile = "FROM #{@base_image}\n"
|
|
783
786
|
@instructions.each do |instruction|
|
|
784
787
|
case instruction[:type]
|
|
785
788
|
when "RUN"
|
|
@@ -788,9 +791,9 @@ module E2B
|
|
|
788
791
|
dockerfile << "COPY #{instruction[:args][0]} #{instruction[:args][1]}\n"
|
|
789
792
|
when "ENV"
|
|
790
793
|
values = instruction[:args].each_slice(2).map { |key, value| "#{key}=#{value}" }
|
|
791
|
-
dockerfile << "ENV #{values.join(
|
|
794
|
+
dockerfile << "ENV #{values.join(" ")}\n"
|
|
792
795
|
else
|
|
793
|
-
dockerfile << "#{instruction[:type]} #{instruction[:args].join(
|
|
796
|
+
dockerfile << "#{instruction[:type]} #{instruction[:args].join(" ")}\n"
|
|
794
797
|
end
|
|
795
798
|
end
|
|
796
799
|
dockerfile << "ENTRYPOINT #{@start_cmd}\n" if @start_cmd
|
|
@@ -913,7 +916,7 @@ module E2B
|
|
|
913
916
|
|
|
914
917
|
def collect_files(src)
|
|
915
918
|
matches = Dir.glob(src, base: @file_context_path, flags: File::FNM_DOTMATCH)
|
|
916
|
-
|
|
919
|
+
.reject { |entry| [".", ".."].include?(entry) }
|
|
917
920
|
|
|
918
921
|
files = []
|
|
919
922
|
matches.each do |match|
|
|
@@ -991,14 +994,14 @@ module E2B
|
|
|
991
994
|
end
|
|
992
995
|
|
|
993
996
|
def normalize_ignore_pattern(pattern)
|
|
994
|
-
normalized = pattern.to_s.tr(File::SEPARATOR, "/").sub(
|
|
995
|
-
return "/#{normalized.sub(%r{\A/+},
|
|
997
|
+
normalized = pattern.to_s.tr(File::SEPARATOR, "/").sub(%r{\A\./}, "")
|
|
998
|
+
return "/#{normalized.sub(%r{\A/+}, "")}" if normalized.start_with?("/")
|
|
996
999
|
|
|
997
1000
|
normalized.sub(%r{\A/+}, "")
|
|
998
1001
|
end
|
|
999
1002
|
|
|
1000
1003
|
def normalize_ignore_path(path)
|
|
1001
|
-
path.to_s.tr(File::SEPARATOR, "/").sub(
|
|
1004
|
+
path.to_s.tr(File::SEPARATOR, "/").sub(%r{\A\./}, "").sub(%r{\A/+}, "")
|
|
1002
1005
|
end
|
|
1003
1006
|
|
|
1004
1007
|
def build_step_origins
|
|
@@ -1013,8 +1016,8 @@ module E2B
|
|
|
1013
1016
|
return [] unless File.exist?(dockerignore_path)
|
|
1014
1017
|
|
|
1015
1018
|
File.readlines(dockerignore_path, chomp: true)
|
|
1016
|
-
|
|
1017
|
-
|
|
1019
|
+
.map(&:strip)
|
|
1020
|
+
.reject { |line| line.empty? || line.start_with?("#") }
|
|
1018
1021
|
end
|
|
1019
1022
|
|
|
1020
1023
|
def read_gcp_service_account_json(path_or_content)
|
data/lib/e2b/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: e2b
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tao Luo
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-05-
|
|
10
|
+
date: 2026-05-10 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.2'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: faraday
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -44,35 +58,49 @@ dependencies:
|
|
|
44
58
|
- !ruby/object:Gem::Version
|
|
45
59
|
version: '1.0'
|
|
46
60
|
- !ruby/object:Gem::Dependency
|
|
47
|
-
name:
|
|
61
|
+
name: rake
|
|
48
62
|
requirement: !ruby/object:Gem::Requirement
|
|
49
63
|
requirements:
|
|
50
64
|
- - "~>"
|
|
51
65
|
- !ruby/object:Gem::Version
|
|
52
|
-
version: '0
|
|
53
|
-
type: :
|
|
66
|
+
version: '13.0'
|
|
67
|
+
type: :development
|
|
54
68
|
prerelease: false
|
|
55
69
|
version_requirements: !ruby/object:Gem::Requirement
|
|
56
70
|
requirements:
|
|
57
71
|
- - "~>"
|
|
58
72
|
- !ruby/object:Gem::Version
|
|
59
|
-
version: '0
|
|
73
|
+
version: '13.0'
|
|
60
74
|
- !ruby/object:Gem::Dependency
|
|
61
|
-
name:
|
|
75
|
+
name: rspec
|
|
62
76
|
requirement: !ruby/object:Gem::Requirement
|
|
63
77
|
requirements:
|
|
64
78
|
- - "~>"
|
|
65
79
|
- !ruby/object:Gem::Version
|
|
66
|
-
version: '
|
|
80
|
+
version: '3.0'
|
|
67
81
|
type: :development
|
|
68
82
|
prerelease: false
|
|
69
83
|
version_requirements: !ruby/object:Gem::Requirement
|
|
70
84
|
requirements:
|
|
71
85
|
- - "~>"
|
|
72
86
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: '
|
|
87
|
+
version: '3.0'
|
|
74
88
|
- !ruby/object:Gem::Dependency
|
|
75
|
-
name:
|
|
89
|
+
name: rubocop
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '1.72'
|
|
95
|
+
type: :development
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - "~>"
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '1.72'
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: rubocop-rspec
|
|
76
104
|
requirement: !ruby/object:Gem::Requirement
|
|
77
105
|
requirements:
|
|
78
106
|
- - "~>"
|
|
@@ -151,6 +179,7 @@ metadata:
|
|
|
151
179
|
source_code_uri: https://github.com/ya-luotao/e2b-ruby
|
|
152
180
|
documentation_uri: https://e2b.dev/docs
|
|
153
181
|
changelog_uri: https://github.com/ya-luotao/e2b-ruby/blob/main/CHANGELOG.md
|
|
182
|
+
rubygems_mfa_required: 'true'
|
|
154
183
|
rdoc_options: []
|
|
155
184
|
require_paths:
|
|
156
185
|
- lib
|