ctxmesh 0.0.1.pre → 0.1.0.pre.beta.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95d2cc68599cb93ce35ccc3d92d5724c7e2e741b701416f4011c76386af63cb1
4
- data.tar.gz: 97e316d31984b7cff9ce4cb62be2e7ae39321a407d4368e60f4aa542fed338b7
3
+ metadata.gz: ce25dc8c1240107783738a291b4288ceb2dc75420130bfff83d42613af875838
4
+ data.tar.gz: edbf4b51e49bcd272cde852c9ae84654d09d7b416925cdbe35ab4f835f0a7f31
5
5
  SHA512:
6
- metadata.gz: e8da9df2e5ec8633dff60acdb23ac254343bba2492e280d0e260e3e975c874d031330167799ea3f3d738e43c96ed3cef013b35d8e3a55143c6ce9c0c681ce442
7
- data.tar.gz: b4cd9f4202a4247f6b818a5bd469fbbd448f012452fc5c62b70ca774cd6e427caf9feed2858e0a8b3c5d2f2facc2678af724a680e1ac7a033fca360fe1677445
6
+ metadata.gz: d7c147f0e15b649eedb21b2beb00eb17cafb4f2d6b68884733765c48ffd382225f650b6590a0c19b90911b4a3519734c24d6f4c4441ed481a7b23311714efb4b
7
+ data.tar.gz: 06c25966b09429cc589a27a56d95538ee52780ef9e42f134c2b0932cd5154f9e686112fa910d55411cadee945bf493e45325804af177d8973e02c17a4e5eb18f
data/README.md CHANGED
@@ -1,11 +1,55 @@
1
- # ctxmesh (Ruby)
1
+ # ctxmesh Ruby SDK
2
2
 
3
- **Pre-release.** This gem reserves the `ctxmesh` name for the official Ruby SDK, published by the
4
- ctxmesh project. **It has no functionality yet.**
3
+ Typed clients for agents running on [ctxmesh](https://ctxmesh.github.io), the Kubernetes-native
4
+ control plane for AI agents.
5
5
 
6
- Working SDKs today:
6
+ Your agent runs in a pod beside the platform's sidecars. This gem is the typed way to reach
7
+ them — conversation memory, long-term memory, knowledge bases, skills, feedback, agent-to-agent
8
+ calls, delegation and handoff.
7
9
 
8
- - **Python** `pip install ctxmesh`
9
- - **TypeScript** `npm install ctxmesh`
10
+ **Your code never holds credentials.** Endpoints and identity arrive in the environment the
11
+ platform injects, so there is no API key to manage and no base URL to configure.
10
12
 
11
- Source and issues: https://github.com/ctxmesh/ctxmesh
13
+ ```ruby
14
+ gem "ctxmesh"
15
+ ```
16
+
17
+ ## Use it
18
+
19
+ ```ruby
20
+ require "ctxmesh"
21
+
22
+ cx = Ctxmesh::Client.from_env # reads MEMORY_PORT, CONVERSATION_ID, …
23
+
24
+ cx.memory_append(role: "user", content: "What changed in the deploy?")
25
+ history = cx.memory_get
26
+
27
+ cx.remember("The customer prefers email.", { "topic" => "prefs" })
28
+ facts = cx.search_agent("contact preference", top_k: 5)
29
+
30
+ hits = cx.knowledge_search("rollback procedure", top_k: 5)
31
+ cx.feedback(trace_id, "helpfulness", 1.0, "clear")
32
+ ```
33
+
34
+ `Ctxmesh::Client.from_env` raises `NotInPodError` outside the platform.
35
+
36
+ ## Errors say which kind of "no" it was
37
+
38
+ - **`NotWiredError`** — the platform did not grant this capability. The port is absent because
39
+ nothing is listening; a configuration answer, not a failure.
40
+ - **`DeniedError`** — a 403. The plane understood the call and refused it (a guardrail, a
41
+ budget, the delegate fence), and it carries the reason.
42
+ - **`ApiError`** — any other non-2xx, with the body.
43
+
44
+ ## No runtime dependencies
45
+
46
+ `net/http` and the stdlib `json`. A gem dependency in an SDK becomes a version conflict in every
47
+ application that already has it.
48
+
49
+ ## Conformance tier
50
+
51
+ **plane-client** — every launcher route is reachable here. The managed agent loop and
52
+ model client are authoring-tier and live in the Python and TypeScript SDKs. Every capability is
53
+ also a plain HTTP endpoint, so this gem is convenience, never a requirement.
54
+
55
+ Apache-2.0.
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ctxmesh
4
+ # The resolved plane for one agent process.
5
+ class Config
6
+ DEFAULT_MEMORY_PORT = 2998
7
+ DEFAULT_FEEDBACK_PORT = 2995
8
+ DEFAULT_AMP_PORT = 2997
9
+ DEFAULT_DELEGATE_PORT = 2994
10
+
11
+ attr_reader :memory_port, :feedback_port, :amp_port, :delegate_port, :agent_name, :conversation_id
12
+
13
+ def initialize(memory_port:, feedback_port:, amp_port:, delegate_port: DEFAULT_DELEGATE_PORT,
14
+ agent_name: "", conversation_id: "",
15
+ memory_wired: true, feedback_wired: true, long_term_enabled: true,
16
+ knowledge_enabled: true)
17
+ @memory_port = memory_port
18
+ @feedback_port = feedback_port
19
+ @amp_port = amp_port
20
+ @delegate_port = delegate_port
21
+ @agent_name = agent_name
22
+ @conversation_id = conversation_id
23
+ @memory_wired = memory_wired
24
+ @feedback_wired = feedback_wired
25
+ @long_term_enabled = long_term_enabled
26
+ @knowledge_enabled = knowledge_enabled
27
+ end
28
+
29
+ # False when MEMORY_PORT was absent: the platform did not grant memory to this agent.
30
+ def memory_wired? = @memory_wired
31
+ def feedback_wired? = @feedback_wired
32
+ def long_term_enabled? = @long_term_enabled
33
+ def knowledge_enabled? = @knowledge_enabled
34
+
35
+ def memory_base = "http://127.0.0.1:#{@memory_port}"
36
+ def feedback_base = "http://127.0.0.1:#{@feedback_port}"
37
+ def amp_base = "http://127.0.0.1:#{@amp_port}"
38
+ # Its OWN listener. /delegate and /handoff on the memory port are a 404.
39
+ def delegate_base = "http://127.0.0.1:#{@delegate_port}"
40
+
41
+ # Reads the launcher environment.
42
+ def self.from_env(env = ENV)
43
+ in_pod = %w[MEMORY_PORT FEEDBACK_PORT AGENT_NAME MODEL_GATEWAY_URL].any? { |k| env.key?(k) }
44
+ raise NotInPodError, "ctxmesh: not running in a ctxmesh pod (no launcher environment)" unless in_pod
45
+
46
+ mem, mem_set = port(env, "MEMORY_PORT", DEFAULT_MEMORY_PORT)
47
+ fb, fb_set = port(env, "FEEDBACK_PORT", DEFAULT_FEEDBACK_PORT)
48
+ # A2A_PORT is what the launcher publishes; AMP_PORT was invented.
49
+ amp, = port(env, "A2A_PORT", DEFAULT_AMP_PORT)
50
+ del, = port(env, "DELEGATE_PORT", DEFAULT_DELEGATE_PORT)
51
+
52
+ new(
53
+ memory_port: mem, feedback_port: fb, amp_port: amp, delegate_port: del,
54
+ agent_name: env.fetch("AGENT_NAME", "").strip,
55
+ conversation_id: env.fetch("CONVERSATION_ID", "").strip,
56
+ memory_wired: mem_set, feedback_wired: fb_set,
57
+ long_term_enabled: env.fetch("MEMORY_LONGTERM_ENABLED", "").strip == "true",
58
+ knowledge_enabled: env.fetch("KNOWLEDGE_BASE_ENABLED", "").strip == "true"
59
+ )
60
+ end
61
+
62
+ # Returns [value, was_explicitly_set]. The caller needs the difference: an unset port means
63
+ # the capability is not wired, not that it is on the default.
64
+ def self.port(env, name, default)
65
+ raw = env[name]
66
+ return [default, false] if raw.nil? || raw.strip.empty?
67
+
68
+ n = Integer(raw.strip, exception: false)
69
+ unless n && n.between?(1, 65_535)
70
+ raise NotInPodError, "ctxmesh: #{name}=#{raw.inspect} is not a valid port"
71
+ end
72
+
73
+ [n, true]
74
+ end
75
+ private_class_method :port
76
+ end
77
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ctxmesh
4
+ # Base for everything this SDK raises.
5
+ class Error < StandardError; end
6
+
7
+ # The launcher environment is absent: this process is not running as a ctxmesh agent.
8
+ # Raised rather than guessing ports, so running an agent on a laptop says so plainly instead
9
+ # of failing later with a connection refused to localhost:2998.
10
+ class NotInPodError < Error; end
11
+
12
+ # The platform did not grant this capability to this agent. A configuration answer, not a
13
+ # failure: the port is absent because nothing is listening.
14
+ class NotWiredError < Error; end
15
+
16
+ # A non-2xx response from the plane.
17
+ class ApiError < Error
18
+ attr_reader :status, :path, :body
19
+
20
+ def initialize(status, path, body)
21
+ @status = status
22
+ @path = path
23
+ @body = body
24
+ super("ctxmesh: #{path} returned #{status}: #{body}")
25
+ end
26
+ end
27
+
28
+ # A 403 — the plane understood the call and refused it. Guardrails, budgets and the delegate
29
+ # fence surface here, and the reason survives: a refusal that says only "403" turns a policy
30
+ # decision into a bare status code.
31
+ class DeniedError < ApiError
32
+ def initialize(path, body)
33
+ super(403, path, body)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ctxmesh
4
+ # Stamped from the product tag at release (ADR 0135). Every SDK ships at one version.
5
+ VERSION = "0.1.0-beta.3"
6
+ end
data/lib/ctxmesh.rb CHANGED
@@ -1,9 +1,265 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Ctxmesh is the official Ruby namespace for ctxmesh.
3
+ require "json"
4
+ require "net/http"
5
+ require "uri"
6
+
7
+ require_relative "ctxmesh/version"
8
+ require_relative "ctxmesh/errors"
9
+ require_relative "ctxmesh/config"
10
+
11
+ # Ruby SDK for agents running on ctxmesh, the Kubernetes-native control plane for AI agents.
12
+ #
13
+ # An agent runs in a pod beside the platform's sidecars, and this gem is the typed way to reach
14
+ # them over localhost: conversation memory, long-term memory, knowledge bases, skills, feedback,
15
+ # agent-to-agent calls, delegation and handoff.
4
16
  #
5
- # The Ruby SDK is not implemented yet. Python and TypeScript SDKs are
6
- # available today; see https://github.com/ctxmesh/ctxmesh.
17
+ # It holds no credentials. Endpoints and identity arrive in the environment the platform
18
+ # injects, so there is no API key to manage and no base URL to configure.
19
+ #
20
+ # Conformance tier: plane-client (ADR 0139). The managed agent loop and model client are
21
+ # authoring-tier and live in the Python and TypeScript SDKs.
7
22
  module Ctxmesh
8
- VERSION = "0.0.1.pre"
23
+ # Carries the run capability. Delegation, handoff, per-user session memory, per-user long-term
24
+ # memory and per-user knowledge bases all key on it. Session memory fails SAFE without it --
25
+ # every user silently shares the agent-wide bucket instead of their own -- so omitting it
26
+ # defeats an isolation control with no error to notice.
27
+ CAPABILITY_HEADER = "X-Ctxmesh-Run-Capability"
28
+
29
+ # The entry point.
30
+ class Client
31
+ DEFAULT_TIMEOUT = 15
32
+ # Search may wait on an embedding call through the token-service.
33
+ SEARCH_TIMEOUT = 60
34
+
35
+ attr_reader :config
36
+
37
+ def initialize(config)
38
+ @config = config
39
+ end
40
+
41
+ # Reads the launcher environment. Raises NotInPodError outside a ctxmesh pod.
42
+ def self.from_env(env = ENV) = new(Config.from_env(env))
43
+
44
+ # Builds a client from an explicit config — for tests and offline work.
45
+ def self.from_config(config) = new(config)
46
+
47
+ # ── memory: /memory and /memory/agent ───────────────────────────────────
48
+
49
+ # Returns the conversation so far.
50
+ def memory_get(conversation_id = nil)
51
+ require_memory!
52
+ body = request("GET", "#{@config.memory_base}/memory/#{escape(conv(conversation_id))}")
53
+ body.is_a?(Array) ? body : []
54
+ end
55
+
56
+ # Adds one entry to the conversation.
57
+ def memory_append(role:, content:, conversation_id: nil)
58
+ require_memory!
59
+ request("POST", "#{@config.memory_base}/memory/#{escape(conv(conversation_id))}/append",
60
+ { role: role, content: content })
61
+ nil
62
+ end
63
+
64
+ # Searches this conversation's memory. capability is optional: without it a per-user agent
65
+ # silently reads the agent-wide bucket rather than the caller's own.
66
+ def memory_search(query, conversation_id: nil, capability: nil)
67
+ require_memory!
68
+ url = "#{@config.memory_base}/memory/#{escape(conv(conversation_id))}/search?q=#{escape(query.to_s)}"
69
+ headers = capability && !capability.empty? ? { CAPABILITY_HEADER => capability } : {}
70
+ body = request("GET", url, nil, DEFAULT_TIMEOUT, headers)
71
+ body.is_a?(Array) ? body : []
72
+ end
73
+
74
+ # Replaces the conversation wholesale.
75
+ def memory_put(entries, conversation_id: nil)
76
+ require_memory!
77
+ request("PUT", "#{@config.memory_base}/memory/#{escape(conv(conversation_id))}", entries)
78
+ nil
79
+ end
80
+
81
+ # Writes a fact to this agent's long-term memory.
82
+ def remember(content, tags = {})
83
+ require_long_term!
84
+ payload = { content: content }
85
+ payload[:tags] = tags unless tags.nil? || tags.empty?
86
+ request("POST", "#{@config.memory_base}/memory/agent/remember", payload)
87
+ nil
88
+ end
89
+
90
+ # Retrieves facts from long-term memory; min_score drops weak matches.
91
+ def search_agent(query, top_k: 5, min_score: 0.0)
92
+ require_long_term!
93
+ body = request("POST", "#{@config.memory_base}/memory/agent/search",
94
+ { query: query, topK: top_k.positive? ? top_k : 5 })
95
+ results(body).select { |f| f.fetch("score", 0.0) >= min_score }
96
+ end
97
+
98
+ # ── knowledge: /knowledge/search ────────────────────────────────────────
99
+
100
+ # Retrieval over the granted knowledge bases; knowledge_base may be nil for all of them.
101
+ def knowledge_search(query, knowledge_base: nil, top_k: 5)
102
+ unless @config.knowledge_enabled?
103
+ raise NotWiredError, "ctxmesh: knowledge is not enabled (KNOWLEDGE_BASE_ENABLED)"
104
+ end
105
+
106
+ if knowledge_base.nil? || knowledge_base.strip.empty?
107
+ raise Error, "ctxmesh: knowledgeBase is required (the launcher 400s without it)"
108
+ end
109
+
110
+ payload = { query: query, topK: top_k.positive? ? top_k : 5, knowledgeBase: knowledge_base }
111
+ results(request("POST", "#{@config.memory_base}/knowledge/search", payload, SEARCH_TIMEOUT))
112
+ end
113
+
114
+ # ── skills: /skills and /skills/load ────────────────────────────────────
115
+
116
+ # The skills the platform attached to this agent.
117
+ def skills
118
+ body = request("GET", "#{@config.memory_base}/skills")
119
+ body.is_a?(Hash) ? body.fetch("skills", []) : []
120
+ end
121
+
122
+ # Fetches a skill's body by name.
123
+ def skill_load(name)
124
+ body = request("POST", "#{@config.memory_base}/skills/load", { name: name })
125
+ # The launcher answers {"body": "..."}. Reading "content" gave "" with NO error,
126
+ # so a skill loaded as nothing and the model carried on without it.
127
+ body.is_a?(Hash) ? body.fetch("body", "") : ""
128
+ end
129
+
130
+ # ── feedback: /feedback ─────────────────────────────────────────────────
131
+
132
+ # Records a score against a trace — the signal that drives evals and canary promotion.
133
+ def feedback(trace_id, dimension, score, comment = nil)
134
+ unless @config.feedback_wired?
135
+ raise NotWiredError, "ctxmesh: feedback is not wired (FEEDBACK_PORT unset)"
136
+ end
137
+
138
+ # name/value, not dimension/score: the wrong keys returned 202 while writing a
139
+ # nameless zero score, silently corrupting the eval signal.
140
+ payload = { traceId: trace_id, name: dimension, value: score }
141
+ payload[:comment] = comment if comment && !comment.empty?
142
+ request("POST", "#{@config.feedback_base}/feedback", payload)
143
+ nil
144
+ end
145
+
146
+ # ── mesh: /amp and /a2a ─────────────────────────────────────────────────
147
+
148
+ # Invokes another agent through AMP.
149
+ def call_agent(target_agent, payload)
150
+ require_target!(target_agent)
151
+ request("POST", "#{@config.amp_base}/amp/#{escape(target_agent)}", payload)
152
+ end
153
+
154
+ # Invokes another agent through the retired /a2a path. Prefer #call_agent.
155
+ #
156
+ # Kept because the launcher still serves it (ADR 0138); an SDK that pretends a served route
157
+ # does not exist is the drift the contract gate exists to prevent.
158
+ def call_agent_legacy(target_agent, payload)
159
+ require_target!(target_agent)
160
+ request("POST", "#{@config.amp_base}/a2a/#{escape(target_agent)}", payload)
161
+ end
162
+
163
+ # ── runs: /delegate and /handoff ────────────────────────────────────────
164
+
165
+ # Spawns a sub-run on another agent.
166
+ #
167
+ # step and call_id are the idempotency key the launcher hard-requires -- the supervisor's loop
168
+ # iteration and the model's tool-call id -- so a reclaimed supervisor resolves to the SAME
169
+ # sub-run rather than spawning a second. capability is the run capability.
170
+ #
171
+ # The launcher answers 200 for EVERY outcome and signals success in "ok", so check that: a
172
+ # refusal decoded as a transport success is silent data loss, and "answer" is the entire point.
173
+ def delegate(sub_agent, step:, call_id:, capability:, input: nil)
174
+ raise Error, "ctxmesh: sub-agent is required" if sub_agent.nil? || sub_agent.strip.empty?
175
+ if step.to_s.strip.empty? || call_id.to_s.strip.empty?
176
+ raise Error, "ctxmesh: step and callId are required (the idempotency key)"
177
+ end
178
+ if capability.to_s.strip.empty?
179
+ raise NotWiredError, "ctxmesh: delegation needs the run capability (#{CAPABILITY_HEADER})"
180
+ end
181
+
182
+ request("POST", "#{@config.delegate_base}/delegate",
183
+ { subAgent: sub_agent, input: input, step: step, callId: call_id },
184
+ DEFAULT_TIMEOUT, { CAPABILITY_HEADER => capability }) || {}
185
+ end
186
+
187
+ # Transfers the conversation to another agent.
188
+ #
189
+ # The launcher treats an ABSENT includeHistory as TRUE, so it is always sent explicitly --
190
+ # defaulting to false handed the receiver nothing. message is its opening note.
191
+ def handoff(target_agent, capability:, message: nil, include_history: true)
192
+ require_target!(target_agent)
193
+ if capability.to_s.strip.empty?
194
+ raise NotWiredError, "ctxmesh: handoff needs the run capability (#{CAPABILITY_HEADER})"
195
+ end
196
+
197
+ payload = { targetAgent: target_agent, includeHistory: include_history }
198
+ payload[:message] = message if message && !message.empty?
199
+ request("POST", "#{@config.delegate_base}/handoff", payload,
200
+ DEFAULT_TIMEOUT, { CAPABILITY_HEADER => capability }) || {}
201
+ end
202
+
203
+ private
204
+
205
+ def require_memory!
206
+ return if @config.memory_wired?
207
+
208
+ raise NotWiredError, "ctxmesh: memory is not wired (MEMORY_PORT unset)"
209
+ end
210
+
211
+ def require_long_term!
212
+ require_memory!
213
+ return if @config.long_term_enabled?
214
+
215
+ raise NotWiredError, "ctxmesh: long-term memory is not enabled (MEMORY_LONGTERM_ENABLED)"
216
+ end
217
+
218
+ def require_target!(t)
219
+ raise Error, "ctxmesh: target agent is required" if t.nil? || t.strip.empty?
220
+ end
221
+
222
+ # Explicit id first, else the injected CONVERSATION_ID. Empty is an error rather than a
223
+ # silent write to a shared bucket.
224
+ def conv(id)
225
+ v = id.nil? || id.to_s.strip.empty? ? @config.conversation_id : id.to_s
226
+ raise Error, "ctxmesh: no conversation id (pass one, or set CONVERSATION_ID)" if v.strip.empty?
227
+ if v.include?("/") || v.match?(/\s/)
228
+ raise Error, "ctxmesh: conversation id #{v.inspect} contains a separator or whitespace"
229
+ end
230
+
231
+ v
232
+ end
233
+
234
+ def results(body) = body.is_a?(Hash) ? body.fetch("results", []) : []
235
+
236
+ def escape(s) = URI.encode_www_form_component(s)
237
+
238
+ def request(method, url, payload = nil, timeout = DEFAULT_TIMEOUT, headers = {})
239
+ uri = URI.parse(url)
240
+ req = Net::HTTP.const_get(method.capitalize).new(uri)
241
+ req["Accept"] = "application/json"
242
+ headers.each { |k, v| req[k] = v if v && !v.to_s.empty? }
243
+ if payload
244
+ req["Content-Type"] = "application/json"
245
+ req.body = JSON.generate(payload)
246
+ end
247
+
248
+ resp = Net::HTTP.start(uri.hostname, uri.port, open_timeout: 5, read_timeout: timeout) do |http|
249
+ http.request(req)
250
+ end
251
+
252
+ code = resp.code.to_i
253
+ text = (resp.body || "").strip
254
+ raise DeniedError.new(uri.path, text) if code == 403
255
+ raise ApiError.new(code, uri.path, text) unless (200..299).cover?(code)
256
+ return nil if text.empty?
257
+
258
+ JSON.parse(text)
259
+ rescue JSON::ParserError => e
260
+ raise Error, "ctxmesh: decode response from #{uri.path}: #{e.message}"
261
+ rescue SystemCallError, Timeout::Error, IOError => e
262
+ raise Error, "ctxmesh: #{uri.path}: #{e.message}"
263
+ end
264
+ end
9
265
  end
metadata CHANGED
@@ -1,33 +1,39 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ctxmesh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.pre
4
+ version: 0.1.0.pre.beta.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - ctxmesh
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-06 00:00:00.000000000 Z
11
+ date: 2026-09-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Reserves the ctxmesh name for the official Ruby SDK. No functionality
14
- yet; the Python and TypeScript SDKs are available today. See https://github.com/ctxmesh/ctxmesh.
15
- email:
13
+ description: 'Typed clients for agents running on ctxmesh: conversation memory, long-term
14
+ memory, knowledge bases, skills, feedback and agent-to-agent calls. Reads its endpoints
15
+ from the environment the platform injects, so your code never holds credentials.'
16
+ email:
16
17
  executables: []
17
18
  extensions: []
18
19
  extra_rdoc_files: []
19
20
  files:
20
21
  - README.md
21
22
  - lib/ctxmesh.rb
22
- homepage: https://github.com/ctxmesh/ctxmesh
23
+ - lib/ctxmesh/config.rb
24
+ - lib/ctxmesh/errors.rb
25
+ - lib/ctxmesh/version.rb
26
+ homepage: https://ctxmesh.github.io
23
27
  licenses:
24
28
  - Apache-2.0
25
29
  metadata:
30
+ homepage_uri: https://ctxmesh.github.io
26
31
  source_code_uri: https://github.com/ctxmesh/ctxmesh
27
32
  bug_tracker_uri: https://github.com/ctxmesh/ctxmesh/issues
28
- homepage_uri: https://github.com/ctxmesh/ctxmesh
33
+ documentation_uri: https://ctxmesh.github.io/sdk/
34
+ changelog_uri: https://github.com/ctxmesh/ctxmesh/blob/main/CHANGELOG.md
29
35
  rubygems_mfa_required: 'true'
30
- post_install_message:
36
+ post_install_message:
31
37
  rdoc_options: []
32
38
  require_paths:
33
39
  - lib
@@ -38,12 +44,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
38
44
  version: '3.1'
39
45
  required_rubygems_version: !ruby/object:Gem::Requirement
40
46
  requirements:
41
- - - ">"
47
+ - - ">="
42
48
  - !ruby/object:Gem::Version
43
- version: 1.3.1
49
+ version: '0'
44
50
  requirements: []
45
- rubygems_version: 3.0.3.1
46
- signing_key:
51
+ rubygems_version: 3.5.22
52
+ signing_key:
47
53
  specification_version: 4
48
- summary: Official Ruby namespace for ctxmesh
54
+ summary: Ruby SDK for ctxmesh agents
49
55
  test_files: []