antigravity-sdk 0.5.0 → 0.5.4

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: effdabdb0588788c4b168749a2578e2e1f07aa877a54bfd80fe1969d42f497d2
4
- data.tar.gz: 3debe5e4295f8c2e2bcd4c37ec7ea52ea11a77d034370320d4064b52aa08cc29
3
+ metadata.gz: 25e15da1171b05a2bcc0be36f79e4f1f6e6e0e6135d520d3776282007071ee05
4
+ data.tar.gz: 3ddfcb833bb6d1339573b81e5910f33c37cd4113bfb28c753367386f0d67d88e
5
5
  SHA512:
6
- metadata.gz: 4123cbc798ddbfaa82ea0f854e89adb122491d9a99b52f501c15350401e660109be3ca778b6142720f0259256992dbbb73d4d2371d8bdefcda266bd4d98591cf
7
- data.tar.gz: d80349e13c564c74bb81c5c8ba14887f39af36507d03b3c6a5883ed3a885359570210d10b9fc1f8cc3e8a0c193408de176f3316129a58e1d6303352c5527ed01
6
+ metadata.gz: 70112ba04d101d659b82372f4f49f03a301d9d1e8fb4c89de871ccf2abdee7bed7017b3fe8440eff5c935902f0c545238081d2439e7aff1e4ca5a63d76f87e83
7
+ data.tar.gz: feb64a14685fefe355772238556f05e27a08aad6f9ad44ae74db4fd0c16e1821eda88f78dbcc24cf990cb90d744139697b06eb883da4fe1f447d67a9d486de7c
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.4
@@ -5,14 +5,14 @@ module Antigravity
5
5
 
6
6
  attr_accessor :model, :system_instruction, :api_key
7
7
  attr_reader :tools, :skills, :hooks, :sidecars, :client, :logger_guard,
8
- :workspace, :connection, :conversation
8
+ :workspace, :connection, :conversation, :policy, :policies, :born_at
9
9
 
10
10
  def initialize(model: nil, system_instruction: nil, tools: [],
11
11
  skills: [], policies: [], policy: nil, workspace: nil, auto_logger: true, log_file: nil, &block)
12
12
  @model = model || Antigravity.config.default_model
13
13
  @api_key = Antigravity.config.api_key
14
14
  @system_instruction = system_instruction
15
- @workspace = workspace ? File.expand_path(workspace) : nil
15
+ @workspace = resolve_workspace(workspace)
16
16
  @tools = tools.dup
17
17
  @skills = []
18
18
  @policies = []
@@ -23,6 +23,7 @@ module Antigravity
23
23
  @connection = nil
24
24
  @conversation = nil
25
25
  @connected = false
26
+ @born_at = Time.now
26
27
 
27
28
  # Register pre-provided tools into the tool runner
28
29
  @tool_runner = ToolRunner.new
@@ -68,6 +69,10 @@ module Antigravity
68
69
  end
69
70
  end
70
71
 
72
+ def workspace=(path)
73
+ @workspace = resolve_workspace(path)
74
+ end
75
+
71
76
  # --- Connection Lifecycle ---
72
77
 
73
78
  def connect!
@@ -89,9 +94,19 @@ module Antigravity
89
94
  )
90
95
 
91
96
  harness_config = build_harness_config
97
+
98
+ # Emit indexing hooks — workspace indexing happens during session init
99
+ index_t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) if @workspace
100
+ hooks.emit(:indexing_start, { workspace: @workspace }) if @workspace
101
+
92
102
  @conversation.initialize_session!(harness_config: harness_config)
93
103
  @connected = true
94
104
 
105
+ if @workspace
106
+ index_elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - index_t0
107
+ hooks.emit(:indexing_done, { workspace: @workspace, elapsed: index_elapsed.round(2) })
108
+ end
109
+
95
110
  # Emit session_start hook
96
111
  hooks.emit(:session_start, {
97
112
  model: @model,
@@ -160,6 +175,21 @@ module Antigravity
160
175
  @conversation.session_summary(model: @model)
161
176
  end
162
177
 
178
+ # Seconds since agent was created
179
+ def uptime
180
+ Time.now - @born_at
181
+ end
182
+
183
+ # Human-readable uptime: "1m 23.4s", "7.3s"
184
+ def uptime_human
185
+ secs = uptime
186
+ if secs >= 60
187
+ "#{(secs / 60).to_i}m #{(secs % 60).round(1)}s"
188
+ else
189
+ "#{secs.round(1)}s"
190
+ end
191
+ end
192
+
163
193
  # --- Tool Registration ---
164
194
 
165
195
  def register_tool(tool_or_name = nil, description: "", &block)
@@ -266,11 +296,11 @@ module Antigravity
266
296
  end
267
297
 
268
298
  def lifecycle_logger_enabled?
269
- # Explicit opt-in
270
- return true if ENV['ANTIGRAVITY_LIFECYCLE'] == '1'
299
+ # Explicit opt-in (strip handles trailing spaces in .env files)
300
+ return true if ENV['ANTIGRAVITY_LIFECYCLE']&.strip == '1'
271
301
  # Rails/Rack test or development
272
- return true if %w[test development].include?(ENV['RAILS_ENV']&.downcase)
273
- return true if %w[test development].include?(ENV['RACK_ENV']&.downcase)
302
+ return true if %w[test development].include?(ENV['RAILS_ENV']&.strip&.downcase)
303
+ return true if %w[test development].include?(ENV['RACK_ENV']&.strip&.downcase)
274
304
  # Explicit opt-out
275
305
  false
276
306
  end
@@ -361,8 +391,21 @@ module Antigravity
361
391
  skill
362
392
  end
363
393
 
364
- def lifecycle_logger_enabled?
365
- ENV['ANTIGRAVITY_LIFECYCLE'] == '1'
394
+ def resolve_workspace(val)
395
+ return nil if val.nil? || val == false
396
+
397
+ raw_path = case val
398
+ when :here, :current, true, '.'
399
+ '.'
400
+ else
401
+ val.to_s
402
+ end
403
+
404
+ expanded = File.expand_path(raw_path)
405
+ expanded += '/' unless expanded.end_with?('/')
406
+
407
+ $stderr.puts "šŸ“ Setting workspace to \e[34m#{expanded}\e[0m" rescue nil
408
+ expanded
366
409
  end
367
410
  end
368
411
  end
@@ -87,7 +87,7 @@ module Antigravity
87
87
 
88
88
  unless wheel
89
89
  # Try all versions for the platform
90
- versions = data['releases']&.keys&.sort_by { |v| Gem::Version.new(v) rescue v }&.reverse
90
+ versions = data['releases']&.keys&.sort_by { |v| Gem::Version.correct?(v) ? Gem::Version.new(v) : Gem::Version.new('0') }&.reverse
91
91
  versions&.each do |ver|
92
92
  files = data.dig('releases', ver) || []
93
93
  wheel = files.find { |u| u['filename']&.include?(platform) && u['filename']&.end_with?('.whl') }
@@ -134,7 +134,11 @@ module Antigravity
134
134
  # Wheels are just ZIP files. Extract the binary.
135
135
  # Look for: google/antigravity/bin/localharness (or language_server)
136
136
  extract_dir = Dir.mktmpdir('agy-extract-')
137
- system('unzip', '-q', '-o', wheel_path, '-d', extract_dir)
137
+ success = system('unzip', '-q', '-o', wheel_path, '-d', extract_dir)
138
+ unless success
139
+ FileUtils.rm_rf(extract_dir)
140
+ raise HarnessNotFoundError, "Failed to extract wheel using unzip command"
141
+ end
138
142
 
139
143
  # Search for the binary inside (prefer localharness over language_server)
140
144
  candidates = Dir.glob("#{extract_dir}/**/localharness") +
@@ -121,8 +121,7 @@ module Antigravity
121
121
  end
122
122
 
123
123
  def perform_handshake!
124
- storage_dir = File.join(Dir.tmpdir, "antigravity-ruby-#{$$}")
125
- FileUtils.mkdir_p(storage_dir)
124
+ storage_dir = Dir.mktmpdir('antigravity-ruby-')
126
125
 
127
126
  # Send InputConfig via stdin
128
127
  input_config = Protocol.encode_input_config(
@@ -131,10 +131,9 @@ module Antigravity
131
131
 
132
132
  if step[:textDelta] && !step[:textDelta].empty? && is_model_step && is_target_user && !is_error_step
133
133
  text_parts << step[:textDelta]
134
- chunk = Message.new(
134
+ chunk = Chunk.new(
135
135
  content: step[:textDelta],
136
- role: :assistant,
137
- delta: true
136
+ role: :assistant
138
137
  )
139
138
  block&.call(chunk)
140
139
  end
@@ -170,37 +169,44 @@ module Antigravity
170
169
  handle_tool_call(tool_call)
171
170
  end
172
171
 
173
- # Usage update
172
+ # Usage update — do NOT set seen_any_step here!
173
+ # usageUpdate can leak from the previous turn and trick the GHI #18
174
+ # stale-FULLY_IDLE guard into accepting a stale FULLY_IDLE as real.
174
175
  if (usage = msg[:usageUpdate])
175
176
  update_usage(usage)
176
- seen_any_step = true
177
177
  end
178
178
 
179
179
  # Trajectory state: STATE_FULLY_IDLE / STATE_CANCELLED = turn complete (authoritative signal from harness)
180
- # GHI #18 FIX: Only honor FULLY_IDLE if we've seen at least one stepUpdate/toolCall/usageUpdate
181
- # from this turn, OR if enough time has elapsed (1s) that this can't be a stale leftover.
182
- # A stale FULLY_IDLE from a previous turn sitting in the WebSocket buffer was causing
183
- # collect_response to return immediately with zero steps/text.
180
+ # GHI #18 + #24 FIX: Only honor FULLY_IDLE if we've seen at least one stepUpdate or toolCall
181
+ # (NOT usageUpdate — it leaks across turns) from this turn, OR if enough time has elapsed
182
+ # (2s) that this can't be a stale leftover. A stale FULLY_IDLE from a previous turn sitting
183
+ # in the WebSocket buffer was causing collect_response to return immediately with 0B text.
184
184
  if (traj = msg[:trajectoryStateUpdate])
185
- elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - turn_started_at
186
185
  if traj[:state].to_s =~ /FULLY_IDLE|CANCELLED/
187
- if seen_any_step || elapsed > 1.0
186
+ if seen_any_step
188
187
  finished = true
189
188
  finished_at = Time.now
190
189
  else
191
190
  # Stale FULLY_IDLE — skip it (likely leftover from previous turn or init)
192
- @hooks&.emit(:ws_message, { _debug: 'skipped_stale_fully_idle', elapsed: elapsed.round(3) })
191
+ @hooks&.emit(:ws_message, { _debug: 'skipped_stale_fully_idle', seen_any_step: seen_any_step })
193
192
  end
194
193
  end
195
194
  end
196
195
 
197
196
  # Stop conditions (in priority order):
198
- # 1. Session end — always stop
199
- # 2. Model response DONE or trajectory FULLY_IDLE / CANCELLED — turn complete
200
- if msg.key?(:sessionEndResponse) || finished
197
+ # 1. Session end — always hard-stop immediately
198
+ # 2. Finished (DONE or FULLY_IDLE) WITH text — short drain for trailing usage
199
+ # 3. Finished WITHOUT text but with steps — model was working (thinking),
200
+ # text is likely still in-flight. Drain longer (3s) to catch it. (GHI #24)
201
+ # 4. Not finished but text started — idle timeout for more text
202
+ if msg.key?(:sessionEndResponse)
201
203
  :stop
204
+ elsif finished && !text_parts.empty?
205
+ [:idle_timeout, 0.5]
206
+ elsif finished && text_parts.empty?
207
+ # FULLY_IDLE arrived but no text yet — wait longer for trailing text
208
+ [:idle_timeout, 3.0]
202
209
  elsif !text_parts.empty?
203
- # If assistant has sent text response, allow 3s idle timeout for trailing metadata
204
210
  [:idle_timeout, 3.0]
205
211
  end
206
212
  end
@@ -241,10 +247,40 @@ module Antigravity
241
247
  # Symbolize keys for Ruby kwargs
242
248
  kwargs = args.transform_keys(&:to_sym)
243
249
 
244
- begin
245
- result = @tool_runner.execute(tool_name, **kwargs)
246
- rescue ToolNotFoundError => e
247
- result = { error: e.message }
250
+ # Emit tool_call hook BEFORE execution
251
+ @hooks&.emit(:tool_call, { tool_name: tool_name, params: args, tool_id: tool_id })
252
+
253
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
254
+ policy_check = @hooks ? @hooks.run_pre_tool(tool_name, args) : { allowed: true }
255
+
256
+ if !policy_check[:allowed]
257
+ reason = policy_check[:reason]
258
+ # Same format as client.rb sidecar emission
259
+ @hooks&.emit(:tool_blocked, { tool: tool_name, reason: reason })
260
+ result = "āŒ TOOL BLOCKED: #{reason}"
261
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
262
+
263
+ # We run the post_tool hook here as well to satisfy AgentLogger's pairing
264
+ result = @hooks ? @hooks.run_post_tool(tool_name, args, result) : result
265
+ @hooks&.emit(:tool_result, { tool_name: tool_name, result: result.to_s, duration: duration, tool_id: tool_id })
266
+ else
267
+ begin
268
+ raw_result = @tool_runner.execute(tool_name, **kwargs)
269
+
270
+ # Run post_tool filters/maskers
271
+ result = @hooks ? @hooks.run_post_tool(tool_name, args, raw_result) : raw_result
272
+
273
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
274
+
275
+ # Emit tool_result hook AFTER execution
276
+ @hooks&.emit(:tool_result, { tool_name: tool_name, result: result.to_s, duration: duration, tool_id: tool_id })
277
+ rescue ToolNotFoundError => e
278
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
279
+ result = { error: e.message }
280
+
281
+ # Emit tool_error hook on failure
282
+ @hooks&.emit(:tool_error, { tool_name: tool_name, error: e.message, duration: duration, tool_id: tool_id })
283
+ end
248
284
  end
249
285
 
250
286
  # Send tool response back (protobuf InputEvent.tool_response format)
@@ -22,8 +22,7 @@ module Antigravity
22
22
  return true
23
23
  end
24
24
 
25
- cmd = "#{@bin_path} --port=#{@port}"
26
- @stdin, @stdout, @stderr, wait_thr = Open3.popen3(cmd)
25
+ @stdin, @stdout, @stderr, wait_thr = Open3.popen3(@bin_path, "--port=#{@port}")
27
26
  @pid = wait_thr.pid
28
27
 
29
28
  at_exit { stop! }
@@ -20,8 +20,9 @@ module Antigravity
20
20
  def self.status_line(agent)
21
21
  turns = agent.turn_count rescue 0
22
22
  summary = agent.session_summary rescue {}
23
- tokens = summary.dig(:tokens, :total) || 0
23
+ tokens = summary[:total_tokens] || 0
24
24
  tok_str = tokens > 999 ? "#{(tokens / 1000.0).round(1)}k" : tokens.to_s
25
+ tok_str = "šŸŖ™#{tok_str}"
25
26
  model = summary[:model] || agent.model || '?'
26
27
  conv_id = (summary[:conversation_id] || '?')[0..7]
27
28
 
@@ -54,7 +55,18 @@ module Antigravity
54
55
  logger.instance_variable_set(:@session_start_time, Process.clock_gettime(Process::CLOCK_MONOTONIC))
55
56
  model = info[:model] || agent.model || '?'
56
57
  conv_id = (info[:conversation_id] || '?')[0..11]
57
- puts C.gray("🟢 #{C.dim("session_start")} | model=#{C.cyan(model)} | conv=#{C.cyan(conv_id)}")
58
+ puts C.gray("\nšŸŖšŸŸ¢ #{C.dim("session_start")} | model=#{C.cyan(model)} | conv=#{C.cyan(conv_id)}")
59
+ end
60
+
61
+ agent.hooks.on(:indexing_start) do |info|
62
+ ws = info[:workspace] || '?'
63
+ puts C.gray("šŸŖ šŸ“‚ #{C.dim("indexing")} | #{C.blue(ws)}")
64
+ end
65
+
66
+ agent.hooks.on(:indexing_done) do |info|
67
+ ws = info[:workspace] || '?'
68
+ elapsed = info[:elapsed] ? "#{info[:elapsed]}s" : '?'
69
+ puts C.gray("šŸŖ āœ… #{C.dim("indexed")} | #{C.blue(ws)} | #{C.green(elapsed)}")
58
70
  end
59
71
 
60
72
  agent.hooks.on(:session_end) do |info|
@@ -65,9 +77,9 @@ module Antigravity
65
77
  end
66
78
  turns = info[:turn_count] || agent.turn_count rescue 0
67
79
  summary = agent.session_summary rescue {}
68
- tokens = summary.dig(:tokens, :total) || 0
80
+ tokens = summary[:total_tokens] || 0
69
81
  tok_str = tokens > 999 ? "#{(tokens / 1000.0).round(1)}k" : tokens.to_s
70
- puts C.gray("šŸ”“ #{C.dim("session_end")} | #{C.bold("#{turns} turns")} | #{tok_str} tok | #{elapsed}s")
82
+ puts C.gray("šŸŖšŸ”“ #{C.dim("session_end")} | #{C.bold("#{turns} turns")} | šŸŖ™#{tok_str} | #{elapsed}s")
71
83
  end
72
84
  end
73
85
 
@@ -81,7 +93,7 @@ module Antigravity
81
93
  preview = text.to_s[0..60].gsub("\n", ' ')
82
94
  preview += '...' if text.to_s.length > 60
83
95
  status = self.class.status_line(agent) rescue C.dim("T#{count}")
84
- puts C.gray(" āž”ļø #{C.dim("pre_turn")} T#{count} | #{C.yellow("\"#{preview}\"")} | #{status}")
96
+ puts C.gray("šŸŖ āž”ļø #{C.dim("pre_turn")} T#{count} | #{C.yellow("\"#{preview}\"")} | #{status}")
85
97
  end
86
98
 
87
99
  agent.hooks.after_response do |response|
@@ -99,12 +111,12 @@ module Antigravity
99
111
  tool_count = response.respond_to?(:tool_calls_count) ? (response.tool_calls_count || 0) : 0
100
112
  status = self.class.status_line(agent) rescue ''
101
113
 
102
- parts = ["#{C.green("#{chars}ch")} #{C.dim("#{lines}L")}"]
103
- parts << "#{C.magenta("#{thinking_len}ch")} think" if thinking_len > 0
114
+ parts = ["#{C.green("#{chars}B")} #{C.dim("#{lines}L")}"]
115
+ parts << "#{C.magenta("#{thinking_len}B")} think" if thinking_len > 0
104
116
  parts << "#{C.cyan("#{tool_count}")} tools" if tool_count > 0
105
117
  parts << "#{C.blue("#{elapsed}s")}"
106
118
 
107
- puts C.gray(" ā¬…ļø #{C.dim("post_turn")} T#{logger.instance_variable_get(:@turn_count)} | #{parts.join(' | ')} | #{status}")
119
+ puts C.gray("\nšŸŖ ā¬…ļø #{C.dim("post_turn")} T#{logger.instance_variable_get(:@turn_count)} | #{parts.join(' | ')} | #{status}")
108
120
  if logger.instance_variable_get(:@verbose)
109
121
  puts C.dim(" \"#{preview}\"")
110
122
  end
@@ -117,28 +129,32 @@ module Antigravity
117
129
  agent.hooks.on(:tool_call) do |info|
118
130
  name = info[:tool_name] || info[:name] || '?'
119
131
  params_preview = (info[:params] || {}).keys.join(', ')
120
- puts C.gray(" šŸ”§ #{C.dim("tool_call")} | #{C.cyan(name)}(#{C.dim(params_preview)})")
132
+ puts C.gray(" šŸŖ šŸ”§ #{C.dim("tool_call")} | #{C.cyan(name)}(#{C.dim(params_preview)})")
121
133
  end
122
134
 
123
135
  agent.hooks.on(:tool_result) do |info|
124
136
  name = info[:tool_name] || info[:name] || '?'
125
- result_len = info[:result].to_s.length rescue 0
137
+ result_str = info[:result].to_s
138
+ result_len = result_str.bytesize rescue 0
139
+ preview = result_str[0..120].gsub("\n", ' ')
140
+ preview += '...' if result_str.length > 120
126
141
  duration = info[:duration] ? "#{info[:duration].round(2)}s" : nil
127
- parts = [C.cyan(name), "#{result_len}ch"]
142
+ parts = [C.cyan(name), "#{result_len}B"]
128
143
  parts << duration if duration
129
- puts C.gray(" āœ… #{C.dim("tool_done")} | #{parts.join(' | ')}")
144
+ puts C.gray(" šŸŖ āœ… #{C.dim("tool_done")} | #{parts.join(' | ')}")
145
+ puts C.dim(" → #{C.yellow(preview)}") if result_len > 0
130
146
  end
131
147
 
132
148
  agent.hooks.on(:tool_blocked) do |info|
133
149
  name = info[:tool] || '?'
134
150
  reason = info[:reason] || 'policy'
135
- puts C.red(" 🚫 #{C.dim("tool_deny")} | #{C.red(name)} — #{reason}")
151
+ puts C.red(" šŸŖ 🚫 #{C.dim("tool_deny")} | #{C.red(name)} — #{reason}")
136
152
  end
137
153
 
138
154
  agent.hooks.on(:tool_error) do |info|
139
155
  name = info[:tool_name] || info[:name] || '?'
140
156
  error = info[:error] || info[:message] || '?'
141
- puts C.red(" šŸ’„ #{C.dim("tool_error")} | #{C.red(name)} — #{error.to_s[0..80]}")
157
+ puts C.red(" šŸŖ šŸ’„ #{C.dim("tool_error")} | #{C.red(name)} — #{error.to_s[0..80]}")
142
158
  end
143
159
  end
144
160
  end
@@ -71,7 +71,11 @@ module Antigravity
71
71
 
72
72
  content = File.read(skill_file, encoding: 'UTF-8')
73
73
  if content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
74
- front_matter = YAML.safe_load(Regexp.last_match(1)) || {}
74
+ front_matter = begin
75
+ YAML.safe_load(Regexp.last_match(1)) || {}
76
+ rescue Psych::SyntaxError => e
77
+ raise ArgumentError, "Invalid YAML frontmatter in #{skill_file}: #{e.message}"
78
+ end
75
79
  @name = front_matter["name"] || File.basename(@path)
76
80
  @description = front_matter["description"] || ""
77
81
  @metadata = front_matter.fetch("metadata", {})
@@ -1,5 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Antigravity
4
- VERSION = File.read(File.expand_path("../../VERSION", __dir__)).strip rescue "0.1.0"
4
+ VERSION = begin
5
+ version_file = [
6
+ File.expand_path("../../VERSION", __dir__),
7
+ File.expand_path("../VERSION", __dir__)
8
+ ].find { |f| File.exist?(f) }
9
+ version_file ? File.read(version_file).strip : "0.5.0"
10
+ end
5
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: antigravity-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Riccardo Carlesso
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.5'
54
+ - !ruby/object:Gem::Dependency
55
+ name: dotenv
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
54
68
  description: An elegant, Ruby-like SDK for building autonomous AI agents with Google
55
69
  Antigravity.
56
70
  email:
@@ -59,6 +73,7 @@ executables: []
59
73
  extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
76
+ - VERSION
62
77
  - lib/antigravity.rb
63
78
  - lib/antigravity/agent.rb
64
79
  - lib/antigravity/base.rb