toolkami 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c1ae1f752dd302a8ae60dad1caff7c12f0c474130d3f46cfec87ab6eaa4a70ed
4
+ data.tar.gz: 21c8360aeeb363b08ebe5383e0cea9d61706858a9fcc08bf0c6b2587ffbedeae
5
+ SHA512:
6
+ metadata.gz: 14de59f15537365e4f0e5723333c61adbe330be47353a03b27c9c384543565dfae5ebe16b4686b6124767112d0082b2721fb37c6b8dd0158ff5bc2f70a54c776
7
+ data.tar.gz: 4e88c77d9c663be3ac70416847ae7d428bde4b1a772fe3e2f937ada8f0ada33883bd280bae4ba7c15eaf54263ac5838ba7915375f5cd01c59c4241119eed4f5b
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aperoc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # toolkami
2
+
3
+ Ruby gRPC client for the local `toolkami` daemon.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ gem install toolkami
9
+ ```
10
+
11
+ The packaged gem downloads the matching Linux x64 daemon from the GitHub release for the gem version on first client start. Repository checkouts do not download a daemon; they use the local Cargo debug binary instead.
12
+
13
+ ## Usage
14
+
15
+ ```ruby
16
+ require "toolkami"
17
+
18
+ client = Toolkami::Client.open(tcp_address: "127.0.0.1:50061")
19
+ puts client.get_version.version
20
+ puts client.ping("ruby").message
21
+ client.close
22
+ ```
23
+
24
+ ## Development
25
+
26
+ ```bash
27
+ bundle exec rake proto:generate
28
+ bundle exec rake test
29
+ bundle exec rake release:build
30
+ bundle exec rake integration:smoke_local
31
+ ```
32
+
33
+ `release:verify` runs the pre-publish checks with the locally built gem and fails if the version is already on RubyGems.
34
+ `release:verify_published` runs the post-publish smoke test against RubyGems and fails until that version is visible there.
@@ -0,0 +1,8 @@
1
+ module Toolkami
2
+ module Assert
3
+ def self.that(value, message)
4
+ raise ArgumentError, message unless value
5
+ true
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,109 @@
1
+ require_relative "assert"
2
+ require_relative "daemon"
3
+ require_relative "../toolkami/v1/ping_services_pb"
4
+
5
+ module Toolkami
6
+ class Client
7
+ def self.open(**options)
8
+ client = new(**options)
9
+
10
+ client.start
11
+ client
12
+ end
13
+
14
+ def initialize(
15
+ tcp_address: nil,
16
+ startup_attempts_max: nil,
17
+ startup_delay_s: nil
18
+ )
19
+ @tcp_address = Daemon.resolve_tcp_address(tcp_address)
20
+ @startup_attempts_max = Daemon.resolve_startup_attempts_max(startup_attempts_max)
21
+ @startup_delay_s = Daemon.resolve_startup_delay_s(startup_delay_s)
22
+ @daemon_launch = nil
23
+ @stub = build_stub(@tcp_address)
24
+ end
25
+
26
+ def close
27
+ Daemon.terminate(@daemon_launch) unless @daemon_launch.nil?
28
+ @daemon_launch = nil
29
+ true
30
+ end
31
+
32
+ def get_version
33
+ @stub.get_version(Toolkami::V1::GetVersionRequest.new)
34
+ end
35
+
36
+ def ping(name)
37
+ Assert.that(name.is_a?(String), "name must be a String")
38
+ Assert.that(!name.empty?, "name must not be empty")
39
+ @stub.ping(Toolkami::V1::PingRequest.new(name: name))
40
+ end
41
+
42
+ def start
43
+ return self if ready?
44
+
45
+ daemon_bin_path = Daemon.resolve_bin_path
46
+ @daemon_launch = Daemon.launch(
47
+ daemon_bin_path: daemon_bin_path,
48
+ tcp_address: @tcp_address
49
+ )
50
+ @stub = build_stub(@tcp_address)
51
+ wait_for_ready
52
+ self
53
+ rescue StandardError => error
54
+ close
55
+ raise error
56
+ end
57
+
58
+ private
59
+
60
+ def build_stub(tcp_address)
61
+ stub = Toolkami::V1::ToolkamiService::Stub.new(
62
+ tcp_address,
63
+ :this_channel_is_insecure
64
+ )
65
+
66
+ Assert.that(!stub.nil?, "stub must be initialized")
67
+ Assert.that(tcp_address.include?(":"), "tcp_address must include a port")
68
+ stub
69
+ end
70
+
71
+ def ready?
72
+ @stub = build_stub(@tcp_address)
73
+ get_version
74
+ true
75
+ rescue GRPC::BadStatus, Errno::ENOENT
76
+ false
77
+ end
78
+
79
+ def wait_for_ready
80
+ attempt_index = 0
81
+
82
+ Assert.that(@startup_attempts_max.positive?, "startup_attempts_max must be positive")
83
+ Assert.that(@startup_delay_s.positive?, "startup_delay_s must be positive")
84
+ while attempt_index < @startup_attempts_max
85
+ return true if ready?
86
+
87
+ daemon_failure_raise
88
+ break if attempt_index + 1 >= @startup_attempts_max
89
+
90
+ sleep(@startup_delay_s)
91
+ attempt_index += 1
92
+ end
93
+
94
+ daemon_failure_raise(wait_timeout_s: @startup_delay_s)
95
+ raise "toolkami daemon did not become ready"
96
+ end
97
+
98
+ def daemon_failure_raise(wait_timeout_s: 0)
99
+ Assert.that(wait_timeout_s.is_a?(Numeric), "wait_timeout_s must be Numeric")
100
+ Assert.that(wait_timeout_s >= 0, "wait_timeout_s must be non-negative")
101
+ return true if @daemon_launch.nil?
102
+
103
+ daemon_failure = Daemon.failure(@daemon_launch, wait_timeout_s: wait_timeout_s)
104
+ return true if daemon_failure.nil?
105
+
106
+ raise daemon_failure
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,131 @@
1
+ require_relative "assert"
2
+ require_relative "install"
3
+ require_relative "release"
4
+
5
+ module Toolkami
6
+ module Daemon
7
+ LaunchResult = Data.define(:daemon_pid, :daemon_stderr_read, :daemon_wait_thread)
8
+
9
+ STARTUP_ATTEMPTS_MAX_DEFAULT = 20
10
+ STARTUP_DELAY_S_DEFAULT = 0.1
11
+ STDERR_CHUNKS_MAX = 8
12
+ TCP_ADDRESS_DEFAULT = "127.0.0.1:50061"
13
+
14
+ def self.assert_bin_path(daemon_bin_path)
15
+ Assert.that(daemon_bin_path.is_a?(String), "daemon_bin_path must be a String")
16
+ Assert.that(!daemon_bin_path.empty?, "daemon_bin_path must not be empty")
17
+ Assert.that(File.executable?(daemon_bin_path), "daemon binary must be executable")
18
+ true
19
+ end
20
+
21
+ def self.launch(daemon_bin_path:, tcp_address:)
22
+ assert_bin_path(daemon_bin_path)
23
+ daemon_stderr_read, daemon_stderr_write = IO.pipe
24
+ daemon_pid = Process.spawn(
25
+ { "TOOLKAMI_TCP_ADDRESS" => tcp_address },
26
+ daemon_bin_path,
27
+ out: File::NULL,
28
+ err: daemon_stderr_write
29
+ )
30
+ daemon_stderr_write.close
31
+
32
+ Assert.that(daemon_pid.positive?, "daemon pid must be positive")
33
+ daemon_wait_thread = Process.detach(daemon_pid)
34
+ LaunchResult.new(daemon_pid, daemon_stderr_read, daemon_wait_thread)
35
+ rescue StandardError
36
+ daemon_stderr_read&.close unless daemon_stderr_read.nil? || daemon_stderr_read.closed?
37
+ daemon_stderr_write&.close unless daemon_stderr_write.nil? || daemon_stderr_write.closed?
38
+ raise
39
+ end
40
+
41
+ def self.failure(daemon_launch, wait_timeout_s: 0)
42
+ Assert.that(!daemon_launch.nil?, "daemon_launch must not be nil")
43
+ Assert.that(wait_timeout_s.is_a?(Numeric), "wait_timeout_s must be Numeric")
44
+ Assert.that(wait_timeout_s >= 0, "wait_timeout_s must be non-negative")
45
+ return nil if daemon_launch.daemon_wait_thread.join(wait_timeout_s).nil?
46
+
47
+ daemon_status = daemon_launch.daemon_wait_thread.value
48
+ daemon_stderr = daemon_stderr_read(daemon_launch.daemon_stderr_read)
49
+ daemon_failure_message(daemon_status, daemon_stderr)
50
+ end
51
+
52
+ def self.terminate(daemon_launch)
53
+ Assert.that(!daemon_launch.nil?, "daemon_launch must not be nil")
54
+ unless daemon_launch.daemon_wait_thread.join(0)
55
+ Process.kill("TERM", daemon_launch.daemon_pid)
56
+ end
57
+ true
58
+ rescue Errno::ESRCH
59
+ true
60
+ ensure
61
+ daemon_launch.daemon_stderr_read.close unless daemon_launch.daemon_stderr_read.closed?
62
+ end
63
+
64
+ def self.resolve_bin_path
65
+ daemon_bin_path =
66
+ if Release.repository_checkout_detect
67
+ Release.daemon_bin_path_checkout_resolve
68
+ else
69
+ Install.daemon_bin_path_install
70
+ end
71
+
72
+ assert_bin_path(daemon_bin_path)
73
+ daemon_bin_path
74
+ end
75
+
76
+ def self.resolve_tcp_address(tcp_address_override)
77
+ tcp_address = tcp_address_override || ENV["TOOLKAMI_TCP_ADDRESS"] || TCP_ADDRESS_DEFAULT
78
+
79
+ Assert.that(!tcp_address.to_s.empty?, "tcp_address must not be empty")
80
+ Assert.that(tcp_address.include?(":"), "tcp_address must include a port")
81
+ tcp_address
82
+ end
83
+
84
+ def self.resolve_startup_attempts_max(startup_attempts_max)
85
+ attempts_max = startup_attempts_max || STARTUP_ATTEMPTS_MAX_DEFAULT
86
+
87
+ Assert.that(attempts_max.is_a?(Integer), "startup_attempts_max must be an Integer")
88
+ Assert.that(attempts_max.positive?, "startup_attempts_max must be positive")
89
+ attempts_max
90
+ end
91
+
92
+ def self.resolve_startup_delay_s(startup_delay_s)
93
+ delay_s = startup_delay_s || STARTUP_DELAY_S_DEFAULT
94
+
95
+ Assert.that(delay_s.is_a?(Numeric), "startup_delay_s must be Numeric")
96
+ Assert.that(delay_s.positive?, "startup_delay_s must be positive")
97
+ delay_s
98
+ end
99
+
100
+ def self.daemon_failure_message(daemon_status, daemon_stderr)
101
+ Assert.that(!daemon_status.nil?, "daemon_status must not be nil")
102
+ daemon_status_text =
103
+ if daemon_status.exitstatus.nil?
104
+ "signal #{daemon_status.termsig || 'unknown'}"
105
+ else
106
+ "code #{daemon_status.exitstatus}"
107
+ end
108
+ daemon_stderr_text = daemon_stderr.strip
109
+ daemon_message = "toolkami daemon exited before becoming ready (#{daemon_status_text})"
110
+
111
+ return daemon_message if daemon_stderr_text.empty?
112
+
113
+ "#{daemon_message} stderr: #{daemon_stderr_text}"
114
+ end
115
+
116
+ def self.daemon_stderr_read(daemon_stderr_read)
117
+ Assert.that(!daemon_stderr_read.nil?, "daemon_stderr_read must not be nil")
118
+ daemon_chunks = []
119
+ daemon_index = 0
120
+
121
+ until daemon_stderr_read.eof? || daemon_index >= STDERR_CHUNKS_MAX
122
+ daemon_chunks << daemon_stderr_read.readpartial(256)
123
+ daemon_index += 1
124
+ end
125
+
126
+ daemon_chunks.join
127
+ rescue EOFError
128
+ daemon_chunks.join
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,19 @@
1
+ module Toolkami
2
+ module Generated
3
+ GENERATED_FILES = [
4
+ File.expand_path("v1/ping_pb.rb", __dir__),
5
+ File.expand_path("v1/ping_services_pb.rb", __dir__),
6
+ ].freeze
7
+
8
+ def self.assert_ready
9
+ missing_paths = GENERATED_FILES.reject { |generated_path| File.file?(generated_path) }
10
+
11
+ return true if missing_paths.empty?
12
+
13
+ raise LoadError,
14
+ "generated Ruby gRPC files are missing; run `bundle exec rake proto:generate` in sdk/toolkami/ruby"
15
+ end
16
+ end
17
+ end
18
+
19
+ Toolkami::Generated.assert_ready
@@ -0,0 +1,126 @@
1
+ require "fileutils"
2
+ require "net/http"
3
+ require "rbconfig"
4
+ require "uri"
5
+
6
+ require_relative "assert"
7
+ require_relative "release"
8
+
9
+ module Toolkami
10
+ module Install
11
+ DOWNLOAD_OPEN_TIMEOUT_S = 15
12
+ DOWNLOAD_READ_TIMEOUT_S = 15
13
+ DOWNLOAD_REDIRECTS_MAX = 5
14
+ DOWNLOAD_SIZE_BYTES_MAX = 50 * 1024 * 1024
15
+
16
+ def self.daemon_bin_path_install
17
+ platform_assert
18
+ daemon_bin_path = Release.daemon_bin_path_packaged_resolve
19
+ return daemon_bin_path if daemon_bin_path_ready?(daemon_bin_path)
20
+
21
+ daemon_bytes = daemon_bytes_download
22
+ daemon_bin_file_write(daemon_bin_path, daemon_bytes)
23
+ daemon_bin_path_assert(daemon_bin_path)
24
+ daemon_bin_path
25
+ end
26
+
27
+ def self.platform_assert
28
+ host_cpu = RbConfig::CONFIG["host_cpu"]
29
+ host_os = RbConfig::CONFIG["host_os"]
30
+
31
+ Assert.that(host_cpu.is_a?(String), "host_cpu must be a String")
32
+ Assert.that(host_os.is_a?(String), "host_os must be a String")
33
+ return true if host_os.include?("linux") && ["x86_64", "amd64"].include?(host_cpu)
34
+
35
+ raise "toolkami supports only linux x64 for automatic daemon installation"
36
+ end
37
+
38
+ def self.daemon_bytes_download
39
+ package_version = Release.package_version_resolve
40
+ release_artifact_url = Release.release_artifact_url_resolve(package_version)
41
+ response = http_response_fetch(URI(release_artifact_url))
42
+ daemon_bytes = response.body
43
+
44
+ Assert.that(daemon_bytes.is_a?(String), "daemon_bytes must be a String")
45
+ Assert.that(!daemon_bytes.empty?, "daemon_bytes must not be empty")
46
+ Assert.that(daemon_bytes.bytesize <= DOWNLOAD_SIZE_BYTES_MAX, "daemon_bytes must be bounded")
47
+ daemon_bytes
48
+ end
49
+
50
+ def self.http_response_fetch(release_artifact_uri)
51
+ redirect_index = 0
52
+ current_uri = release_artifact_uri
53
+
54
+ Assert.that(current_uri.is_a?(URI::HTTPS), "release_artifact_uri must be HTTPS")
55
+ while redirect_index <= DOWNLOAD_REDIRECTS_MAX
56
+ response = http_response_request(current_uri)
57
+ return response if response.is_a?(Net::HTTPSuccess)
58
+
59
+ current_uri = http_response_redirect_uri(current_uri, response)
60
+ redirect_index += 1
61
+ end
62
+
63
+ raise "toolkami daemon download exceeded redirect limit"
64
+ end
65
+
66
+ def self.http_response_request(release_artifact_uri)
67
+ response = nil
68
+
69
+ Assert.that(release_artifact_uri.is_a?(URI::HTTP), "release_artifact_uri must be HTTP or HTTPS")
70
+ Assert.that(!release_artifact_uri.host.to_s.empty?, "release_artifact_uri host must not be empty")
71
+ Net::HTTP.start(
72
+ release_artifact_uri.host,
73
+ release_artifact_uri.port,
74
+ use_ssl: release_artifact_uri.scheme == "https",
75
+ open_timeout: DOWNLOAD_OPEN_TIMEOUT_S,
76
+ read_timeout: DOWNLOAD_READ_TIMEOUT_S
77
+ ) do |http_client|
78
+ request = Net::HTTP::Get.new(release_artifact_uri)
79
+ response = http_client.request(request)
80
+ end
81
+
82
+ Assert.that(!response.nil?, "response must be present")
83
+ Assert.that(response.code.to_i.positive?, "response code must be positive")
84
+ return response if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
85
+
86
+ raise "toolkami daemon download failed: #{response.code} #{response.message}"
87
+ end
88
+
89
+ def self.http_response_redirect_uri(release_artifact_uri, response)
90
+ location_header = response["location"]
91
+
92
+ Assert.that(response.is_a?(Net::HTTPRedirection), "response must be a redirect")
93
+ Assert.that(location_header.is_a?(String), "redirect location must be a String")
94
+ redirect_uri = URI.join(release_artifact_uri.to_s, location_header)
95
+ Assert.that(redirect_uri.is_a?(URI::HTTP), "redirect_uri must be HTTP or HTTPS")
96
+ redirect_uri
97
+ end
98
+
99
+ def self.daemon_bin_file_write(daemon_bin_path, daemon_bytes)
100
+ daemon_directory_path = File.dirname(daemon_bin_path)
101
+ daemon_temp_path = "#{daemon_bin_path}.tmp"
102
+
103
+ Assert.that(daemon_bin_path.end_with?("/toolkami"), "daemon_bin_path must end with /toolkami")
104
+ Assert.that(daemon_bytes.bytesize.positive?, "daemon_bytes must not be empty")
105
+ FileUtils.mkdir_p(daemon_directory_path)
106
+ File.binwrite(daemon_temp_path, daemon_bytes)
107
+ File.chmod(0o755, daemon_temp_path)
108
+ File.rename(daemon_temp_path, daemon_bin_path)
109
+ daemon_bin_path
110
+ ensure
111
+ FileUtils.rm_f(daemon_temp_path) unless daemon_temp_path.nil?
112
+ end
113
+
114
+ def self.daemon_bin_path_ready?(daemon_bin_path)
115
+ Assert.that(daemon_bin_path.is_a?(String), "daemon_bin_path must be a String")
116
+ Assert.that(!daemon_bin_path.empty?, "daemon_bin_path must not be empty")
117
+ File.file?(daemon_bin_path) && File.executable?(daemon_bin_path)
118
+ end
119
+
120
+ def self.daemon_bin_path_assert(daemon_bin_path)
121
+ Assert.that(File.file?(daemon_bin_path), "daemon binary must exist")
122
+ Assert.that(File.executable?(daemon_bin_path), "daemon binary must be executable")
123
+ true
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,63 @@
1
+ require "uri"
2
+
3
+ require_relative "assert"
4
+ require_relative "version"
5
+
6
+ module Toolkami
7
+ module Release
8
+ RELEASE_ASSET_NAME = "toolkami-linux-x64"
9
+ RELEASE_BASE_URL = "https://github.com/aperoc/toolkami/releases"
10
+
11
+ def self.daemon_bin_path_checkout_resolve
12
+ daemon_bin_path = File.expand_path("../../../../../src/toolkami/target/debug/toolkami", __dir__)
13
+
14
+ Assert.that(daemon_bin_path.end_with?("/toolkami"), "checkout daemon path must end with /toolkami")
15
+ Assert.that(daemon_bin_path.include?("/src/toolkami/target/debug/"), "checkout daemon path must target cargo debug output")
16
+ daemon_bin_path
17
+ end
18
+
19
+ def self.daemon_bin_path_packaged_resolve
20
+ daemon_bin_path = File.expand_path("../../bin/toolkami", __dir__)
21
+
22
+ Assert.that(daemon_bin_path.end_with?("/bin/toolkami"), "packaged daemon path must end with /bin/toolkami")
23
+ Assert.that(daemon_bin_path.include?("/sdk/toolkami/ruby/") || daemon_bin_path.include?("/gems/toolkami-"), "packaged daemon path must target sdk or gem install")
24
+ daemon_bin_path
25
+ end
26
+
27
+ def self.package_root_path_resolve
28
+ package_root_path = File.expand_path("../..", __dir__)
29
+
30
+ Assert.that(File.absolute_path(package_root_path) == package_root_path, "package_root_path must be absolute")
31
+ Assert.that(package_root_path.include?("toolkami"), "package_root_path must include toolkami")
32
+ package_root_path
33
+ end
34
+
35
+ def self.package_version_resolve
36
+ package_version = Toolkami::VERSION
37
+
38
+ Assert.that(package_version.is_a?(String), "package_version must be a String")
39
+ Assert.that(!package_version.empty?, "package_version must not be empty")
40
+ Assert.that(!package_version.include?(" "), "package_version must not contain spaces")
41
+ package_version
42
+ end
43
+
44
+ def self.release_artifact_url_resolve(package_version)
45
+ release_artifact_url = URI.join(
46
+ "#{RELEASE_BASE_URL}/",
47
+ "download/v#{package_version}/#{RELEASE_ASSET_NAME}"
48
+ ).to_s
49
+
50
+ Assert.that(package_version.is_a?(String), "package_version must be a String")
51
+ Assert.that(release_artifact_url.include?("/v#{package_version}/"), "release_artifact_url must include version")
52
+ release_artifact_url
53
+ end
54
+
55
+ def self.repository_checkout_detect
56
+ cargo_toml_path = File.expand_path("../../../../../src/toolkami/Cargo.toml", __dir__)
57
+
58
+ Assert.that(cargo_toml_path.end_with?("/Cargo.toml"), "cargo_toml_path must end with /Cargo.toml")
59
+ Assert.that(cargo_toml_path.include?("/src/toolkami/"), "cargo_toml_path must point to the toolkami crate")
60
+ File.readable?(cargo_toml_path)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: toolkami/v1/ping.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+
8
+ descriptor_data = "\n\x16toolkami/v1/ping.proto\x12\x0btoolkami.v1\"\x13\n\x11GetVersionRequest\":\n\x12GetVersionResponse\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x13\n\x0btcp_address\x18\x02 \x01(\t\"\x1b\n\x0bPingRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1f\n\x0cPingResponse\x12\x0f\n\x07message\x18\x01 \x01(\t2\x9d\x01\n\x0fToolkamiService\x12M\n\nGetVersion\x12\x1e.toolkami.v1.GetVersionRequest\x1a\x1f.toolkami.v1.GetVersionResponse\x12;\n\x04Ping\x12\x18.toolkami.v1.PingRequest\x1a\x19.toolkami.v1.PingResponseb\x06proto3"
9
+
10
+ pool = ::Google::Protobuf::DescriptorPool.generated_pool
11
+ pool.add_serialized_file(descriptor_data)
12
+
13
+ module Toolkami
14
+ module V1
15
+ GetVersionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("toolkami.v1.GetVersionRequest").msgclass
16
+ GetVersionResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("toolkami.v1.GetVersionResponse").msgclass
17
+ PingRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("toolkami.v1.PingRequest").msgclass
18
+ PingResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("toolkami.v1.PingResponse").msgclass
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: toolkami/v1/ping.proto for package 'toolkami.v1'
3
+
4
+ require 'grpc'
5
+ require 'toolkami/v1/ping_pb'
6
+
7
+ module Toolkami
8
+ module V1
9
+ module ToolkamiService
10
+ class Service
11
+
12
+ include ::GRPC::GenericService
13
+
14
+ self.marshal_class_method = :encode
15
+ self.unmarshal_class_method = :decode
16
+ self.service_name = 'toolkami.v1.ToolkamiService'
17
+
18
+ rpc :GetVersion, ::Toolkami::V1::GetVersionRequest, ::Toolkami::V1::GetVersionResponse
19
+ rpc :Ping, ::Toolkami::V1::PingRequest, ::Toolkami::V1::PingResponse
20
+ end
21
+
22
+ Stub = Service.rpc_stub_class
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Toolkami
2
+ VERSION = "0.1.2"
3
+ end
data/lib/toolkami.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative "toolkami/version"
2
+ require_relative "toolkami/assert"
3
+ require_relative "toolkami/generated"
4
+ require_relative "toolkami/release"
5
+ require_relative "toolkami/install"
6
+ require_relative "toolkami/client"
7
+
8
+ module Toolkami
9
+ end
data/toolkami.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ lib_path = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
3
+ require "toolkami/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "toolkami"
7
+ spec.version = Toolkami::VERSION
8
+ spec.authors = ["Aperoc"]
9
+ spec.email = ["aperoc@users.noreply.github.com"]
10
+ spec.summary = "Toolkami Ruby SDK for the local Rust daemon"
11
+ spec.description = "Ruby gRPC SDK for the local Toolkami daemon with automatic packaged daemon installation."
12
+ spec.homepage = "https://github.com/aperoc/toolkami"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.3.0"
15
+ spec.metadata["source_code_uri"] = "https://github.com/aperoc/toolkami"
16
+ spec.metadata["bug_tracker_uri"] = "https://github.com/aperoc/toolkami/issues"
17
+ spec.metadata["rubygems_mfa_required"] = "true"
18
+ spec.files = Dir.chdir(__dir__) do
19
+ Dir[
20
+ "LICENSE",
21
+ "README.md",
22
+ "lib/**/*.rb",
23
+ "toolkami.gemspec"
24
+ ]
25
+ end
26
+ spec.require_paths = ["lib"]
27
+ spec.add_dependency "google-protobuf", "4.34.0"
28
+ spec.add_dependency "grpc", "1.78.1"
29
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toolkami
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Aperoc
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: google-protobuf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 4.34.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 4.34.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: grpc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.78.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.78.1
41
+ description: Ruby gRPC SDK for the local Toolkami daemon with automatic packaged daemon
42
+ installation.
43
+ email:
44
+ - aperoc@users.noreply.github.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - LICENSE
50
+ - README.md
51
+ - lib/toolkami.rb
52
+ - lib/toolkami/assert.rb
53
+ - lib/toolkami/client.rb
54
+ - lib/toolkami/daemon.rb
55
+ - lib/toolkami/generated.rb
56
+ - lib/toolkami/install.rb
57
+ - lib/toolkami/release.rb
58
+ - lib/toolkami/v1/ping_pb.rb
59
+ - lib/toolkami/v1/ping_services_pb.rb
60
+ - lib/toolkami/version.rb
61
+ - toolkami.gemspec
62
+ homepage: https://github.com/aperoc/toolkami
63
+ licenses:
64
+ - MIT
65
+ metadata:
66
+ source_code_uri: https://github.com/aperoc/toolkami
67
+ bug_tracker_uri: https://github.com/aperoc/toolkami/issues
68
+ rubygems_mfa_required: 'true'
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 3.3.0
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.5.22
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Toolkami Ruby SDK for the local Rust daemon
88
+ test_files: []