brainiac-fizzy 0.0.29 → 0.0.31

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: 93dfc73c8a3984f834883fbc59057446f220cf640ef14feecdc3882eab0f5e79
4
- data.tar.gz: 16dccf767e981ab8b7ec4f4c9ad570a6af713fe9deb410f0e3163f8745def891
3
+ metadata.gz: 99e73a13d858f22832a0b44acfd689e902e3e35dfc416e891792610f13b42d9f
4
+ data.tar.gz: bfc325433038c58471ebe821c9a27b5554e1c73aa3da8f6974813a9af5769233
5
5
  SHA512:
6
- metadata.gz: dfabf4b7239f9fa9181fb327c24660e7c39c0a1558151362330ff67c8c334ba9565239e5da35c1335c67bd5dfdd246cacf8295509e6e61c86b9afb0f2f6be43d
7
- data.tar.gz: ebafbeceb274b07e70f23f69d8b7123af573d646e58d92eb756b693bf2cf4d87a12ab7fc79ddca3ced785c54d3beca9c8c602a4c655d0158067cc952d441a90f
6
+ metadata.gz: 69e3d38e6aba9c10c394c2606020981c414a20bdf766712d07faed4ef2c33012cb1f87abd4b60bc6699c5879bd6cefdc555215dcab6af593e7917e6dfa4f1f62
7
+ data.tar.gz: 76c41f2d36849f4226dfb33a1cbf501f223551fccc1831b9baa9eeef7ff2f03ff2020dbb66b07ce4190430c50448981b1c659e9ec51ba38db388dc7f47ba6b8c
@@ -27,6 +27,8 @@ module Brainiac
27
27
  cmd_setup
28
28
  when "board"
29
29
  cmd_board(args)
30
+ when "env"
31
+ cmd_env(args)
30
32
  else
31
33
  print_help
32
34
  end
@@ -126,6 +128,66 @@ module Brainiac
126
128
  end
127
129
  end
128
130
 
131
+ # --- ephemeral env management ---
132
+
133
+ # `brainiac fizzy env setup <card_number>`
134
+ #
135
+ # Manually set up (or refresh tracking for) an ephemeral Belt env for a
136
+ # card that was already worked — no agent, no tokens. Once tracked, PR
137
+ # updates auto-redeploy it via brainiac-github.
138
+ def cmd_env(args)
139
+ sub = args.shift
140
+ case sub
141
+ when "setup", "add"
142
+ env_setup(args)
143
+ else
144
+ puts "Usage: brainiac fizzy env setup <card_number>"
145
+ puts ""
146
+ puts "Commands:"
147
+ puts " setup <card_number> Configure + deploy an ephemeral Belt env for a"
148
+ puts " card that was already worked (no agent dispatch)."
149
+ end
150
+ end
151
+
152
+ def env_setup(args)
153
+ card_number = args.shift
154
+ if card_number.nil? || card_number.strip.empty?
155
+ puts "Usage: brainiac fizzy env setup <card_number>"
156
+ return
157
+ end
158
+
159
+ require "net/http"
160
+ require "uri"
161
+
162
+ server_url = detect_server_url
163
+ uri = URI("#{server_url}/api/fizzy/ephemeral-env/#{card_number}")
164
+
165
+ puts "Setting up ephemeral env for card ##{card_number} (no agent)..."
166
+ begin
167
+ http = Net::HTTP.new(uri.host, uri.port)
168
+ http.read_timeout = 600 # belt deploy can be slow
169
+ response = http.post(uri.path, "")
170
+ data = begin
171
+ JSON.parse(response.body)
172
+ rescue StandardError
173
+ {}
174
+ end
175
+
176
+ if response.is_a?(Net::HTTPSuccess) && data["status"] == "ok"
177
+ puts "✓ Ephemeral env '#{data["env"]}' ready#{" (was already configured)" if data["already_configured"]}"
178
+ puts " Worktree: #{data["worktree"]}" if data["worktree"]
179
+ puts " URL: #{data["url"]}" if data["url"]
180
+ puts ""
181
+ puts "Tracked in ephemeral_envs.json — PR updates will auto-redeploy it."
182
+ else
183
+ puts "✗ Failed: #{data["reason"] || response.message}"
184
+ end
185
+ rescue StandardError => e
186
+ puts "Could not reach server at #{server_url}: #{e.message}"
187
+ puts "Is the server running? Check with: brainiac status"
188
+ end
189
+ end
190
+
129
191
  # --- board setup (split into phases) ---
130
192
 
131
193
  def board_setup
@@ -629,6 +691,7 @@ module Brainiac
629
691
  board list List configured boards and their columns
630
692
  board assign <project> <board_key> Assign a project to a board
631
693
  board columns <board_key> Refresh columns for a board from Fizzy
694
+ env setup <card_number> Set up an ephemeral Belt env for an already-worked card (no agent)
632
695
 
633
696
  Fizzy handles card assignment, comments, @mentions, cross-agent reviews,
634
697
  duplicate detection, and planning mode via webhooks.
@@ -662,11 +725,13 @@ module Brainiac
662
725
  # When called with args (the words typed so far after the plugin name),
663
726
  # returns context-sensitive completions for nested subcommands.
664
727
  def self.completions(args = [])
665
- return %w[board config setup status] if args.empty?
728
+ return %w[board config env setup status] if args.empty?
666
729
 
667
730
  case args[0]
668
731
  when "board"
669
732
  board_completions(args[1..])
733
+ when "env"
734
+ args.length <= 1 ? %w[setup add] : []
670
735
  else
671
736
  []
672
737
  end
@@ -49,6 +49,9 @@ def handle_card_assigned(payload, board_key: nil)
49
49
  initial_cli = detect_cli_provider(tags: tags)
50
50
  initial_model = detect_model(project_config, tags: tags)
51
51
  initial_effort = detect_effort(project_config, tags: tags)
52
+ # Profile ([profile:X]/[p:X]) can be specified inline in the card title.
53
+ # It's a named env bundle (e.g. an alternate kiro-cli account) — see core profiles.rb.
54
+ initial_profile = parse_inline_tags(title.to_s)[:profile]
52
55
 
53
56
  # Persist initial overrides from card tags to the work item
54
57
  resolve_work_item_overrides(
@@ -66,7 +69,7 @@ def handle_card_assigned(payload, board_key: nil)
66
69
  card_number: card_number, card_internal_id: card_internal_id, title: title, tags: tags,
67
70
  branch: branch, worktree_path: worktree_path, project_config: project_config, project_key: project_key,
68
71
  agent_name: assigned_agent, model: initial_model,
69
- effort: initial_effort, cli_provider_override: initial_cli, board_key: board_key
72
+ effort: initial_effort, cli_provider_override: initial_cli, profile: initial_profile, board_key: board_key
70
73
  )
71
74
  end
72
75
 
@@ -136,7 +139,8 @@ def setup_assigned_worktree(repo_path, branch, card_internal_id, card_number, pr
136
139
  end
137
140
 
138
141
  def dispatch_assigned_card(card_number:, card_internal_id:, title:, tags:, branch:, worktree_path:,
139
- project_config:, project_key:, agent_name:, model:, effort:, cli_provider_override:, board_key: nil)
142
+ project_config:, project_key:, agent_name:, model:, effort:, cli_provider_override:,
143
+ profile: nil, board_key: nil)
140
144
  card_context = prefetch_card_context(card_number, repo_path: project_config["repo_path"], agent_name: agent_name)
141
145
  planning_info = detect_planning_mode(text: title, tags: tags, card_internal_id: card_internal_id, card_number: card_number)
142
146
 
@@ -180,7 +184,7 @@ def dispatch_assigned_card(card_number:, card_internal_id:, title:, tags:, branc
180
184
  log_name: "assigned-#{card_number}", model: model, effort: effort,
181
185
  agent_name: agent_name, card_number: card_number, source: :fizzy,
182
186
  source_context: { card_number: card_number, board_key: board_key, dispatched_at: Time.now },
183
- cli_provider: cli_provider_override, env: agent_github_env)
187
+ cli_provider: cli_provider_override, profile: profile, env: agent_github_env)
184
188
  register_session(card_key, pid, log_file: log_file, supersede_key: card_key, agent_name: agent_name)
185
189
 
186
190
  Thread.new { move_card_to_column(card_number, "right_now", project_config: project_config, agent_name: agent_name, board_key: board_key) }
@@ -194,8 +198,13 @@ end
194
198
  #
195
199
  # `belt g environment` is synchronous so the worktree is configured before the
196
200
  # agent starts. The actual `belt deploy` runs in the background.
201
+ #
202
+ # When `force: true`, the `deploy` tag requirement is bypassed — the caller is
203
+ # explicitly requesting env setup (e.g. the `brainiac fizzy env setup` CLI or
204
+ # the `/api/fizzy/ephemeral-env/:card` endpoint), so no tag is needed.
197
205
  def maybe_create_ephemeral_belt_env(worktree_path:, card_number:, project_key:, tags: nil,
198
- repo_path: nil, agent_name: nil, fetch_live_tags: false)
206
+ repo_path: nil, agent_name: nil, fetch_live_tags: false,
207
+ force: false)
199
208
  unless defined?(BeltConfig) && defined?(BeltEnvironment)
200
209
  LOG.debug "[EphemeralEnv] Belt utilities not available — skipping"
201
210
  return
@@ -216,14 +225,16 @@ def maybe_create_ephemeral_belt_env(worktree_path:, card_number:, project_key:,
216
225
  return
217
226
  end
218
227
 
219
- resolved_tags = resolve_ephemeral_env_tags(
220
- tags, card_number: card_number, repo_path: repo_path || worktree_path,
221
- agent_name: agent_name, fetch_live_tags: fetch_live_tags
222
- )
223
- tag_list = tag_names(resolved_tags)
224
- unless tag_list.include?("deploy")
225
- LOG.debug "[EphemeralEnv] Card ##{card_number} tags=#{tag_list.inspect} — no deploy tag, skipping"
226
- return
228
+ unless force
229
+ resolved_tags = resolve_ephemeral_env_tags(
230
+ tags, card_number: card_number, repo_path: repo_path || worktree_path,
231
+ agent_name: agent_name, fetch_live_tags: fetch_live_tags
232
+ )
233
+ tag_list = tag_names(resolved_tags)
234
+ unless tag_list.include?("deploy")
235
+ LOG.debug "[EphemeralEnv] Card ##{card_number} tags=#{tag_list.inspect} — no deploy tag, skipping"
236
+ return
237
+ end
227
238
  end
228
239
 
229
240
  parent_env = BeltConfig.parent_env_for(project_key)
@@ -266,6 +277,71 @@ def ensure_ephemeral_env_for_comment(ctx, card_number, worktree)
266
277
  )
267
278
  end
268
279
 
280
+ # Manually set up an ephemeral Belt env for a card that was already worked,
281
+ # without dispatching an agent. Resolves the card's existing worktree from the
282
+ # work item map, configures + deploys the env synchronously, and tracks it in
283
+ # ephemeral_envs.json so PR-update auto-redeploys (brainiac-github) work.
284
+ #
285
+ # Bypasses the `deploy` tag requirement (force: true) — the caller is asking
286
+ # for this explicitly. Returns a result hash suitable for a JSON API response.
287
+ #
288
+ # @param card_number [Integer, String] Fizzy card number
289
+ # @return [Hash] { status:, env:, url:, worktree:, reason: }
290
+ def setup_ephemeral_env_for_card(card_number)
291
+ return { status: "error", reason: "card_number required" } unless card_number
292
+
293
+ return { status: "error", reason: "Belt utilities not available" } unless defined?(BeltConfig) && defined?(BeltEnvironment)
294
+
295
+ card_number = card_number.to_s
296
+ entry = work_item_entry_for_card(card_number)
297
+ return { status: "error", reason: "no work item found for card ##{card_number}" } unless entry
298
+
299
+ worktree = entry["worktree"]
300
+ project_key = entry["project"]
301
+ return { status: "error", reason: "worktree missing for card ##{card_number} (#{worktree.inspect})" } unless worktree && File.directory?(worktree)
302
+
303
+ env_name = BeltConfig.ephemeral_env_for_card(card_number)
304
+ already = BeltEnvironment.environment_configured?(worktree: worktree, env_name: env_name)
305
+
306
+ maybe_create_ephemeral_belt_env(
307
+ worktree_path: worktree, card_number: card_number,
308
+ project_key: project_key, force: true
309
+ )
310
+
311
+ # After setup, resolve the public URL the same way deployment lookups do.
312
+ url = defined?(deployment_url_for_card) ? (deployment_url_for_card(card_number) || {})[:url] : nil
313
+
314
+ {
315
+ status: "ok",
316
+ env: env_name,
317
+ url: url,
318
+ worktree: worktree,
319
+ already_configured: already
320
+ }
321
+ rescue StandardError => e
322
+ LOG.error "[EphemeralEnv] Manual setup failed for card ##{card_number}: #{e.message}"
323
+ { status: "error", reason: e.message }
324
+ end
325
+
326
+ # Resolve the work item entry for a Fizzy card number from work_items.json.
327
+ # Returns a flattened hash with worktree/branch/project or nil.
328
+ def work_item_entry_for_card(card_number)
329
+ map = load_work_item_map
330
+ match = map.values.find do |entry|
331
+ entry.dig("sources", "fizzy", "card_number").to_s == card_number.to_s
332
+ end
333
+ return nil unless match
334
+
335
+ {
336
+ "worktree" => match["worktree"],
337
+ "branch" => match["branch"],
338
+ "project" => match["project"]
339
+ }
340
+ rescue StandardError => e
341
+ LOG.error "[EphemeralEnv] Could not load work item for card ##{card_number}: #{e.message}" if defined?(LOG)
342
+ nil
343
+ end
344
+
269
345
  def belt_app_for_ephemeral?(worktree_path)
270
346
  return BeltEnvironment.belt_app?(worktree_path) if defined?(BeltEnvironment) && BeltEnvironment.respond_to?(:belt_app?)
271
347
 
@@ -18,7 +18,7 @@ CommentContext = Struct.new(
18
18
  :project_config, :project_key, :card_number, :worktree,
19
19
  :model, :effort, :deploy_intent, :cli_provider_override,
20
20
  :comment_vars, :card_tags, :worktree_override, :fresh,
21
- :board_key,
21
+ :board_key, :profile,
22
22
  keyword_init: true
23
23
  )
24
24
 
@@ -146,6 +146,7 @@ def build_comment_context(eventable:, plain_text:, tags:, card_internal_id:, car
146
146
  card_tags: card_tags,
147
147
  worktree_override: resolve_worktree_override(tags, project_config),
148
148
  fresh: tags[:fresh],
149
+ profile: tags[:profile],
149
150
  comment_vars: {
150
151
  "COMMENT_CREATOR" => creator_name || "Unknown",
151
152
  "COMMENT_ID" => comment_id.to_s,
@@ -411,7 +412,7 @@ def dispatch_cross_agent_review(ctx, card_key:, card_number:, card_assigned_agen
411
412
  model: ctx.model, effort: ctx.effort, agent_name: ctx.agent_name,
412
413
  card_number: card_number, comment_id: ctx.comment_id,
413
414
  source: :fizzy, source_context: { card_number: card_number, dispatched_at: Time.now },
414
- cli_provider: ctx.cli_provider_override)
415
+ cli_provider: ctx.cli_provider_override, profile: ctx.profile)
415
416
  return [200, { status: "dispatch_failed", agent: ctx.agent_name, card: card_number }.to_json] unless pid
416
417
 
417
418
  register_session(card_key, pid, log_file: log_file, supersede_key: card_key, agent_name: ctx.agent_name)
@@ -525,6 +526,7 @@ def dispatch_new_mention(ctx, card_key:, card_number:, card_title:, branch:, wor
525
526
  model: ctx.model, effort: ctx.effort, agent_name: ctx.agent_name,
526
527
  card_number: card_number, comment_id: ctx.comment_id,
527
528
  source: :fizzy, cli_provider: ctx.cli_provider_override,
529
+ profile: ctx.profile,
528
530
  source_context: { card_number: card_number, dispatched_at: Time.now })
529
531
  return [200, { status: "dispatch_failed", agent: ctx.agent_name, card: card_number }.to_json] unless pid
530
532
 
@@ -563,6 +565,7 @@ def dispatch_followup_comment(ctx, card_key:, card_number:, work_dir:)
563
565
  model: ctx.model, effort: effort, agent_name: ctx.agent_name,
564
566
  card_number: card_number, comment_id: ctx.comment_id,
565
567
  source: :fizzy, cli_provider: ctx.cli_provider_override, resume: should_resume,
568
+ profile: ctx.profile,
566
569
  source_context: {
567
570
  card_number: card_number, card_internal_id: ctx.card_internal_id,
568
571
  deploy_intent: ctx.deploy_intent, board_key: ctx.board_key, dispatched_at: Time.now
@@ -3,7 +3,7 @@
3
3
  module Brainiac
4
4
  module Plugins
5
5
  module Fizzy
6
- VERSION = "0.0.29"
6
+ VERSION = "0.0.31"
7
7
  end
8
8
  end
9
9
  end
@@ -116,6 +116,21 @@ module Brainiac
116
116
  end
117
117
  end
118
118
 
119
+ # Manually set up an ephemeral Belt env for a card that was already
120
+ # worked — no agent dispatch, no tokens. Configures + deploys the env
121
+ # in the card's existing worktree and tracks it so PR-update
122
+ # auto-redeploys keep it fresh.
123
+ app.post "/api/fizzy/ephemeral-env/:card" do
124
+ content_type :json
125
+ card_number = params["card"]
126
+ result = setup_ephemeral_env_for_card(card_number)
127
+ if result[:status] == "ok"
128
+ result.to_json
129
+ else
130
+ halt 422, result.to_json
131
+ end
132
+ end
133
+
119
134
  # Deployment API routes (if deployments are configured)
120
135
  return unless defined?(DEPLOYMENTS_CONFIG)
121
136
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainiac-fizzy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.29
4
+ version: 0.0.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis