language-operator 0.1.31 → 0.1.35

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -8
  3. data/CHANGELOG.md +14 -0
  4. data/CI_STATUS.md +56 -0
  5. data/Gemfile.lock +2 -2
  6. data/Makefile +22 -6
  7. data/lib/language_operator/agent/base.rb +10 -6
  8. data/lib/language_operator/agent/executor.rb +19 -97
  9. data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
  10. data/lib/language_operator/agent/safety/safe_executor.rb +27 -2
  11. data/lib/language_operator/agent/scheduler.rb +60 -0
  12. data/lib/language_operator/agent/task_executor.rb +548 -0
  13. data/lib/language_operator/agent.rb +90 -27
  14. data/lib/language_operator/cli/base_command.rb +117 -0
  15. data/lib/language_operator/cli/commands/agent.rb +339 -407
  16. data/lib/language_operator/cli/commands/cluster.rb +274 -290
  17. data/lib/language_operator/cli/commands/install.rb +110 -119
  18. data/lib/language_operator/cli/commands/model.rb +284 -184
  19. data/lib/language_operator/cli/commands/persona.rb +218 -284
  20. data/lib/language_operator/cli/commands/quickstart.rb +4 -5
  21. data/lib/language_operator/cli/commands/status.rb +31 -35
  22. data/lib/language_operator/cli/commands/system.rb +221 -233
  23. data/lib/language_operator/cli/commands/tool.rb +356 -422
  24. data/lib/language_operator/cli/commands/use.rb +19 -22
  25. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
  26. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
  27. data/lib/language_operator/client/config.rb +20 -21
  28. data/lib/language_operator/config.rb +115 -3
  29. data/lib/language_operator/constants.rb +54 -0
  30. data/lib/language_operator/dsl/agent_context.rb +7 -7
  31. data/lib/language_operator/dsl/agent_definition.rb +111 -26
  32. data/lib/language_operator/dsl/config.rb +30 -66
  33. data/lib/language_operator/dsl/main_definition.rb +114 -0
  34. data/lib/language_operator/dsl/schema.rb +84 -43
  35. data/lib/language_operator/dsl/task_definition.rb +315 -0
  36. data/lib/language_operator/dsl.rb +0 -1
  37. data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
  38. data/lib/language_operator/logger.rb +4 -4
  39. data/lib/language_operator/synthesis_test_harness.rb +324 -0
  40. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +26 -8
  41. data/lib/language_operator/templates/schema/CHANGELOG.md +26 -0
  42. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  43. data/lib/language_operator/templates/schema/agent_dsl_schema.json +84 -42
  44. data/lib/language_operator/type_coercion.rb +250 -0
  45. data/lib/language_operator/ux/base.rb +81 -0
  46. data/lib/language_operator/ux/concerns/README.md +155 -0
  47. data/lib/language_operator/ux/concerns/headings.rb +90 -0
  48. data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
  49. data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
  50. data/lib/language_operator/ux/create_agent.rb +252 -0
  51. data/lib/language_operator/ux/create_model.rb +267 -0
  52. data/lib/language_operator/ux/quickstart.rb +594 -0
  53. data/lib/language_operator/version.rb +1 -1
  54. data/lib/language_operator.rb +2 -0
  55. data/requirements/ARCHITECTURE.md +1 -0
  56. data/requirements/SCRATCH.md +153 -0
  57. data/requirements/dsl.md +0 -0
  58. data/requirements/features +1 -0
  59. data/requirements/personas +1 -0
  60. data/requirements/proposals +1 -0
  61. data/requirements/tasks/iterate.md +14 -15
  62. data/requirements/tasks/optimize.md +13 -4
  63. data/synth/001/Makefile +90 -0
  64. data/synth/001/agent.rb +26 -0
  65. data/synth/001/agent.yaml +7 -0
  66. data/synth/001/output.log +44 -0
  67. data/synth/Makefile +39 -0
  68. data/synth/README.md +342 -0
  69. metadata +37 -10
  70. data/lib/language_operator/dsl/workflow_definition.rb +0 -259
  71. data/test_agent_dsl.rb +0 -108
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
4
3
  require 'yaml'
4
+ require_relative '../base_command'
5
5
  require_relative '../formatters/progress_formatter'
6
6
  require_relative '../formatters/table_formatter'
7
7
  require_relative '../helpers/cluster_validator'
8
- require_relative '../helpers/cluster_context'
9
8
  require_relative '../helpers/user_prompts'
10
9
  require_relative '../helpers/resource_dependency_checker'
11
10
  require_relative '../helpers/editor_helper'
@@ -18,113 +17,96 @@ module LanguageOperator
18
17
  module CLI
19
18
  module Commands
20
19
  # Persona management commands
21
- class Persona < Thor
20
+ class Persona < BaseCommand
22
21
  include Helpers::ClusterValidator
23
22
  include Helpers::PastelHelper
24
23
 
25
24
  desc 'list', 'List all personas in current cluster'
26
25
  option :cluster, type: :string, desc: 'Override current cluster context'
27
26
  def list
28
- ctx = Helpers::ClusterContext.from_options(options)
27
+ handle_command_error('list personas') do
28
+ personas = list_resources_or_empty('LanguagePersona') do
29
+ puts
30
+ puts 'Personas define the personality and capabilities of agents.'
31
+ puts
32
+ puts 'Create a persona with:'
33
+ puts ' aictl persona create <name>'
34
+ end
29
35
 
30
- personas = ctx.client.list_resources('LanguagePersona', namespace: ctx.namespace)
36
+ return if personas.empty?
31
37
 
32
- if personas.empty?
33
- Formatters::ProgressFormatter.info("No personas found in cluster '#{ctx.name}'")
34
- puts
35
- puts 'Personas define the personality and capabilities of agents.'
36
- puts
37
- puts 'Create a persona with:'
38
- puts ' aictl persona create <name>'
39
- return
40
- end
38
+ # Get agents to count usage
39
+ agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
41
40
 
42
- # Get agents to count usage
43
- agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
41
+ table_data = personas.map do |persona|
42
+ name = persona.dig('metadata', 'name')
43
+ used_by = agents.count { |a| a.dig('spec', 'persona') == name }
44
44
 
45
- table_data = personas.map do |persona|
46
- name = persona.dig('metadata', 'name')
47
- used_by = agents.count { |a| a.dig('spec', 'persona') == name }
45
+ {
46
+ name: name,
47
+ tone: persona.dig('spec', 'tone') || 'neutral',
48
+ used_by: used_by,
49
+ description: persona.dig('spec', 'description') || ''
50
+ }
51
+ end
48
52
 
49
- {
50
- name: name,
51
- tone: persona.dig('spec', 'tone') || 'neutral',
52
- used_by: used_by,
53
- description: persona.dig('spec', 'description') || ''
54
- }
53
+ Formatters::TableFormatter.personas(table_data)
55
54
  end
56
-
57
- Formatters::TableFormatter.personas(table_data)
58
- rescue StandardError => e
59
- Formatters::ProgressFormatter.error("Failed to list personas: #{e.message}")
60
- raise if ENV['DEBUG']
61
-
62
- exit 1
63
55
  end
64
56
 
65
57
  desc 'show NAME', 'Display full persona details'
66
58
  option :cluster, type: :string, desc: 'Override current cluster context'
67
59
  def show(name)
68
- ctx = Helpers::ClusterContext.from_options(options)
60
+ handle_command_error('show persona') do
61
+ persona = get_resource_or_exit('LanguagePersona', name)
69
62
 
70
- begin
71
- persona = ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
72
- rescue K8s::Error::NotFound
73
- Formatters::ProgressFormatter.error("Persona '#{name}' not found in cluster '#{ctx.name}'")
74
- exit 1
75
- end
63
+ puts
64
+ puts "Persona: #{pastel.cyan.bold(name)}"
65
+ puts '═' * 80
66
+ puts
76
67
 
77
- puts
78
- puts "Persona: #{pastel.cyan.bold(name)}"
79
- puts '' * 80
80
- puts
81
-
82
- # Format and display key persona details
83
- spec = persona['spec'] || {}
84
- puts "#{pastel.bold('Display Name:')} #{spec['displayName']}"
85
- puts "#{pastel.bold('Tone:')} #{pastel.yellow(spec['tone'])}" if spec['tone']
86
- puts
87
- puts pastel.bold('Description:')
88
- puts " #{spec['description']}"
89
- puts
90
-
91
- if spec['systemPrompt']
92
- puts pastel.bold('System Prompt:')
93
- puts " #{spec['systemPrompt']}"
68
+ # Format and display key persona details
69
+ spec = persona['spec'] || {}
70
+ puts "#{pastel.bold('Display Name:')} #{spec['displayName']}"
71
+ puts "#{pastel.bold('Tone:')} #{pastel.yellow(spec['tone'])}" if spec['tone']
72
+ puts
73
+ puts pastel.bold('Description:')
74
+ puts " #{spec['description']}"
94
75
  puts
95
- end
96
76
 
97
- if spec['capabilities']&.any?
98
- puts pastel.bold('Capabilities:')
99
- spec['capabilities'].each do |cap|
100
- puts " #{pastel.green('•')} #{cap}"
77
+ if spec['systemPrompt']
78
+ puts pastel.bold('System Prompt:')
79
+ puts " #{spec['systemPrompt']}"
80
+ puts
101
81
  end
102
- puts
103
- end
104
82
 
105
- if spec['toolPreferences']&.any?
106
- puts pastel.bold('Tool Preferences:')
107
- spec['toolPreferences'].each do |pref|
108
- puts " #{pastel.green('•')} #{pref}"
83
+ if spec['capabilities']&.any?
84
+ puts pastel.bold('Capabilities:')
85
+ spec['capabilities'].each do |cap|
86
+ puts " #{pastel.green('•')} #{cap}"
87
+ end
88
+ puts
109
89
  end
110
- puts
111
- end
112
90
 
113
- if spec['responseFormat']
114
- puts "#{pastel.bold('Response Format:')} #{spec['responseFormat']}"
115
- puts
116
- end
91
+ if spec['toolPreferences']&.any?
92
+ puts pastel.bold('Tool Preferences:')
93
+ spec['toolPreferences'].each do |pref|
94
+ puts " #{pastel.green('•')} #{pref}"
95
+ end
96
+ puts
97
+ end
117
98
 
118
- puts '' * 80
119
- puts
120
- Formatters::ProgressFormatter.info('Use this persona when creating agents:')
121
- puts " aictl agent create \"description\" --persona #{name}"
122
- puts
123
- rescue StandardError => e
124
- Formatters::ProgressFormatter.error("Failed to show persona: #{e.message}")
125
- raise if ENV['DEBUG']
99
+ if spec['responseFormat']
100
+ puts "#{pastel.bold('Response Format:')} #{spec['responseFormat']}"
101
+ puts
102
+ end
126
103
 
127
- exit 1
104
+ puts '═' * 80
105
+ puts
106
+ Formatters::ProgressFormatter.info('Use this persona when creating agents:')
107
+ puts " aictl agent create \"description\" --persona #{name}"
108
+ puts
109
+ end
128
110
  end
129
111
 
130
112
  desc 'create NAME', 'Create a new persona'
@@ -138,124 +120,115 @@ module LanguageOperator
138
120
  option :cluster, type: :string, desc: 'Override current cluster context'
139
121
  option :from, type: :string, desc: 'Copy from existing persona as starting point'
140
122
  def create(name)
141
- ctx = Helpers::ClusterContext.from_options(options)
142
-
143
- # Check if persona already exists
144
- begin
145
- ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
146
- Formatters::ProgressFormatter.error("Persona '#{name}' already exists in cluster '#{ctx.name}'")
147
- puts
148
- puts 'Use a different name or delete the existing persona first:'
149
- puts " aictl persona delete #{name}"
150
- exit 1
151
- rescue K8s::Error::NotFound
152
- # Good - persona doesn't exist yet
153
- end
154
-
155
- # If --from flag provided, copy from existing persona
156
- base_persona = nil
157
- if options[:from]
123
+ handle_command_error('create persona') do
124
+ # Check if persona already exists
158
125
  begin
159
- base_persona = ctx.client.get_resource('LanguagePersona', options[:from], ctx.namespace)
160
- Formatters::ProgressFormatter.info("Copying from persona '#{options[:from]}'")
126
+ ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
127
+ Formatters::ProgressFormatter.error("Persona '#{name}' already exists in cluster '#{ctx.name}'")
161
128
  puts
162
- rescue K8s::Error::NotFound
163
- Formatters::ProgressFormatter.error("Source persona '#{options[:from]}' not found")
129
+ puts 'Use a different name or delete the existing persona first:'
130
+ puts " aictl persona delete #{name}"
164
131
  exit 1
132
+ rescue K8s::Error::NotFound
133
+ # Good - persona doesn't exist yet
165
134
  end
166
- end
167
135
 
168
- # Interactive prompts
169
- require 'tty-prompt'
170
- prompt = TTY::Prompt.new
136
+ # If --from flag provided, copy from existing persona
137
+ base_persona = nil
138
+ if options[:from]
139
+ base_persona = get_resource_or_exit('LanguagePersona', options[:from],
140
+ error_message: "Source persona '#{options[:from]}' not found")
141
+ Formatters::ProgressFormatter.info("Copying from persona '#{options[:from]}'")
142
+ puts
143
+ end
171
144
 
172
- puts
173
- puts '=' * 80
174
- puts ' Create New Persona'
175
- puts '=' * 80
176
- puts
145
+ # Interactive prompts
146
+ require 'tty-prompt'
147
+ prompt = TTY::Prompt.new
177
148
 
178
- # Get display name
179
- default_display_name = base_persona&.dig('spec', 'displayName') || name.split('-').map(&:capitalize).join(' ')
180
- display_name = prompt.ask('Display Name:', default: default_display_name)
149
+ puts
150
+ puts '=' * 80
151
+ puts ' Create New Persona'
152
+ puts '=' * 80
153
+ puts
181
154
 
182
- # Get description
183
- default_description = base_persona&.dig('spec', 'description') || ''
184
- description = prompt.ask('Description:', default: default_description) do |q|
185
- q.required true
186
- end
155
+ # Get display name
156
+ default_display_name = base_persona&.dig('spec', 'displayName') || name.split('-').map(&:capitalize).join(' ')
157
+ display_name = prompt.ask('Display Name:', default: default_display_name)
187
158
 
188
- # Get tone
189
- default_tone = base_persona&.dig('spec', 'tone') || 'neutral'
190
- tone = prompt.select('Tone:', %w[neutral friendly professional technical creative], default: default_tone)
159
+ # Get description
160
+ default_description = base_persona&.dig('spec', 'description') || ''
161
+ description = prompt.ask('Description:', default: default_description) do |q|
162
+ q.required true
163
+ end
191
164
 
192
- # Get system prompt
193
- puts
194
- puts 'System Prompt (press Enter to open editor):'
195
- prompt.keypress('Press any key to continue...')
165
+ # Get tone
166
+ default_tone = base_persona&.dig('spec', 'tone') || 'neutral'
167
+ tone = prompt.select('Tone:', %w[neutral friendly professional technical creative], default: default_tone)
196
168
 
197
- default_system_prompt = base_persona&.dig('spec', 'systemPrompt') || ''
198
- system_prompt = edit_in_editor(default_system_prompt, 'persona-system-prompt')
169
+ # Get system prompt
170
+ puts
171
+ puts 'System Prompt (press Enter to open editor):'
172
+ prompt.keypress('Press any key to continue...')
199
173
 
200
- if system_prompt.strip.empty?
201
- Formatters::ProgressFormatter.error('System prompt cannot be empty')
202
- exit 1
203
- end
174
+ default_system_prompt = base_persona&.dig('spec', 'systemPrompt') || ''
175
+ system_prompt = Helpers::EditorHelper.edit_content(default_system_prompt, 'persona-system-prompt', '.txt')
204
176
 
205
- # Get capabilities
206
- puts
207
- puts 'Capabilities (optional, press Enter to open editor, or leave empty to skip):'
208
- puts 'Describe what this persona can do, one per line.'
209
- prompt.keypress('Press any key to continue...')
210
-
211
- default_capabilities = base_persona&.dig('spec', 'capabilities')&.join("\n") || ''
212
- capabilities_text = edit_in_editor(default_capabilities, 'persona-capabilities')
213
- capabilities = capabilities_text.strip.empty? ? [] : capabilities_text.strip.split("\n").map(&:strip).reject(&:empty?)
214
-
215
- # Build persona resource
216
- persona_spec = {
217
- 'displayName' => display_name,
218
- 'description' => description,
219
- 'tone' => tone,
220
- 'systemPrompt' => system_prompt.strip
221
- }
222
- persona_spec['capabilities'] = capabilities unless capabilities.empty?
223
-
224
- persona_resource = Kubernetes::ResourceBuilder.build_persona(
225
- name: name,
226
- spec: persona_spec,
227
- namespace: ctx.namespace
228
- )
229
-
230
- # Show preview
231
- puts
232
- puts '=' * 80
233
- puts 'Preview:'
234
- puts '=' * 80
235
- puts YAML.dump(persona_resource)
236
- puts '=' * 80
237
- puts
238
-
239
- # Confirm creation
240
- unless prompt.yes?('Create this persona?')
241
- puts 'Creation cancelled'
242
- return
243
- end
177
+ if system_prompt.strip.empty?
178
+ Formatters::ProgressFormatter.error('System prompt cannot be empty')
179
+ exit 1
180
+ end
244
181
 
245
- # Create persona
246
- Formatters::ProgressFormatter.with_spinner("Creating persona '#{name}'") do
247
- ctx.client.create_resource(persona_resource)
248
- end
182
+ # Get capabilities
183
+ puts
184
+ puts 'Capabilities (optional, press Enter to open editor, or leave empty to skip):'
185
+ puts 'Describe what this persona can do, one per line.'
186
+ prompt.keypress('Press any key to continue...')
187
+
188
+ default_capabilities = base_persona&.dig('spec', 'capabilities')&.join("\n") || ''
189
+ capabilities_text = Helpers::EditorHelper.edit_content(default_capabilities, 'persona-capabilities', '.txt')
190
+ capabilities = capabilities_text.strip.empty? ? [] : capabilities_text.strip.split("\n").map(&:strip).reject(&:empty?)
191
+
192
+ # Build persona resource
193
+ persona_spec = {
194
+ 'displayName' => display_name,
195
+ 'description' => description,
196
+ 'tone' => tone,
197
+ 'systemPrompt' => system_prompt.strip
198
+ }
199
+ persona_spec['capabilities'] = capabilities unless capabilities.empty?
200
+
201
+ persona_resource = Kubernetes::ResourceBuilder.build_persona(
202
+ name: name,
203
+ spec: persona_spec,
204
+ namespace: ctx.namespace
205
+ )
206
+
207
+ # Show preview
208
+ puts
209
+ puts '=' * 80
210
+ puts 'Preview:'
211
+ puts '=' * 80
212
+ puts YAML.dump(persona_resource)
213
+ puts '=' * 80
214
+ puts
215
+
216
+ # Confirm creation
217
+ unless prompt.yes?('Create this persona?')
218
+ puts 'Creation cancelled'
219
+ return
220
+ end
249
221
 
250
- Formatters::ProgressFormatter.success("Persona '#{name}' created successfully")
251
- puts
252
- puts 'Use this persona when creating agents:'
253
- puts " aictl agent create \"description\" --persona #{name}"
254
- rescue StandardError => e
255
- Formatters::ProgressFormatter.error("Failed to create persona: #{e.message}")
256
- raise if ENV['DEBUG']
222
+ # Create persona
223
+ Formatters::ProgressFormatter.with_spinner("Creating persona '#{name}'") do
224
+ ctx.client.create_resource(persona_resource)
225
+ end
257
226
 
258
- exit 1
227
+ Formatters::ProgressFormatter.success("Persona '#{name}' created successfully")
228
+ puts
229
+ puts 'Use this persona when creating agents:'
230
+ puts " aictl agent create \"description\" --persona #{name}"
231
+ end
259
232
  end
260
233
 
261
234
  desc 'edit NAME', 'Edit an existing persona'
@@ -270,122 +243,83 @@ module LanguageOperator
270
243
  DESC
271
244
  option :cluster, type: :string, desc: 'Override current cluster context'
272
245
  def edit(name)
273
- ctx = Helpers::ClusterContext.from_options(options)
274
-
275
- # Get current persona
276
- begin
277
- persona = ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
278
- rescue K8s::Error::NotFound
279
- Formatters::ProgressFormatter.error("Persona '#{name}' not found in cluster '#{ctx.name}'")
280
- exit 1
281
- end
282
-
283
- # Open in editor
284
- original_yaml = YAML.dump(persona)
285
- edited_yaml = edit_in_editor(original_yaml, "persona-#{name}")
286
-
287
- # Check if changed
288
- if edited_yaml.strip == original_yaml.strip
289
- Formatters::ProgressFormatter.info('No changes made')
290
- return
291
- end
246
+ handle_command_error('edit persona') do
247
+ # Get current persona
248
+ persona = get_resource_or_exit('LanguagePersona', name)
249
+
250
+ # Open in editor
251
+ original_yaml = YAML.dump(persona)
252
+ edited_yaml = Helpers::EditorHelper.edit_content(original_yaml, "persona-#{name}", '.txt')
253
+
254
+ # Check if changed
255
+ if edited_yaml.strip == original_yaml.strip
256
+ Formatters::ProgressFormatter.info('No changes made')
257
+ return
258
+ end
292
259
 
293
- # Parse edited YAML
294
- begin
295
- edited_persona = YAML.safe_load(edited_yaml)
296
- rescue Psych::SyntaxError => e
297
- Formatters::ProgressFormatter.error("Invalid YAML: #{e.message}")
298
- exit 1
299
- end
260
+ # Parse edited YAML
261
+ begin
262
+ edited_persona = YAML.safe_load(edited_yaml)
263
+ rescue Psych::SyntaxError => e
264
+ Formatters::ProgressFormatter.error("Invalid YAML: #{e.message}")
265
+ exit 1
266
+ end
300
267
 
301
- # Validate structure
302
- unless edited_persona.is_a?(Hash) && edited_persona['spec']
303
- Formatters::ProgressFormatter.error('Invalid persona structure: missing spec')
304
- exit 1
305
- end
268
+ # Validate structure
269
+ unless edited_persona.is_a?(Hash) && edited_persona['spec']
270
+ Formatters::ProgressFormatter.error('Invalid persona structure: missing spec')
271
+ exit 1
272
+ end
306
273
 
307
- # Update persona
308
- Formatters::ProgressFormatter.with_spinner("Updating persona '#{name}'") do
309
- ctx.client.update_resource(edited_persona)
310
- end
274
+ # Update persona
275
+ Formatters::ProgressFormatter.with_spinner("Updating persona '#{name}'") do
276
+ ctx.client.update_resource(edited_persona)
277
+ end
311
278
 
312
- Formatters::ProgressFormatter.success("Persona '#{name}' updated successfully")
279
+ Formatters::ProgressFormatter.success("Persona '#{name}' updated successfully")
313
280
 
314
- # Check for agents using this persona
315
- agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
316
- agents_using = Helpers::ResourceDependencyChecker.agents_using_persona(agents, name)
281
+ # Check for agents using this persona
282
+ agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
283
+ agents_using = Helpers::ResourceDependencyChecker.agents_using_persona(agents, name)
317
284
 
318
- if agents_using.any?
319
- puts
320
- Formatters::ProgressFormatter.info("#{agents_using.count} agent(s) will be re-synthesized automatically:")
321
- agents_using.each do |agent|
322
- puts " - #{agent.dig('metadata', 'name')}"
285
+ if agents_using.any?
286
+ puts
287
+ Formatters::ProgressFormatter.info("#{agents_using.count} agent(s) will be re-synthesized automatically:")
288
+ agents_using.each do |agent|
289
+ puts " - #{agent.dig('metadata', 'name')}"
290
+ end
323
291
  end
324
292
  end
325
- rescue StandardError => e
326
- Formatters::ProgressFormatter.error("Failed to edit persona: #{e.message}")
327
- raise if ENV['DEBUG']
328
-
329
- exit 1
330
293
  end
331
294
 
332
295
  desc 'delete NAME', 'Delete a persona'
333
296
  option :cluster, type: :string, desc: 'Override current cluster context'
334
297
  option :force, type: :boolean, default: false, desc: 'Skip confirmation'
335
298
  def delete(name)
336
- ctx = Helpers::ClusterContext.from_options(options)
337
-
338
- # Get persona
339
- begin
340
- persona = ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
341
- rescue K8s::Error::NotFound
342
- Formatters::ProgressFormatter.error("Persona '#{name}' not found in cluster '#{ctx.name}'")
343
- exit 1
344
- end
345
-
346
- # Check for agents using this persona
347
- agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
348
- agents_using = Helpers::ResourceDependencyChecker.agents_using_persona(agents, name)
349
-
350
- if agents_using.any? && !options[:force]
351
- Formatters::ProgressFormatter.warn("Persona '#{name}' is in use by #{agents_using.count} agent(s)")
352
- puts
353
- puts 'Agents using this persona:'
354
- agents_using.each do |agent|
355
- puts " - #{agent.dig('metadata', 'name')}"
299
+ handle_command_error('delete persona') do
300
+ # Get persona
301
+ persona = get_resource_or_exit('LanguagePersona', name)
302
+
303
+ # Check dependencies and get confirmation
304
+ return unless check_dependencies_and_confirm('persona', name, force: options[:force])
305
+
306
+ # Confirm deletion unless --force
307
+ if confirm_deletion(
308
+ 'persona', name, ctx.name,
309
+ details: {
310
+ 'Tone' => persona.dig('spec', 'tone'),
311
+ 'Description' => persona.dig('spec', 'description')
312
+ },
313
+ force: options[:force]
314
+ )
315
+ # Delete persona
316
+ Formatters::ProgressFormatter.with_spinner("Deleting persona '#{name}'") do
317
+ ctx.client.delete_resource('LanguagePersona', name, ctx.namespace)
318
+ end
319
+
320
+ Formatters::ProgressFormatter.success("Persona '#{name}' deleted successfully")
356
321
  end
357
- puts
358
- puts 'Delete these agents first, or use --force to delete anyway.'
359
- puts
360
- return unless Helpers::UserPrompts.confirm('Are you sure?')
361
- end
362
-
363
- # Confirm deletion unless --force
364
- unless options[:force] || agents_using.any?
365
- puts "This will delete persona '#{name}' from cluster '#{ctx.name}':"
366
- puts " Tone: #{persona.dig('spec', 'tone')}"
367
- puts " Description: #{persona.dig('spec', 'description')}"
368
- puts
369
- return unless Helpers::UserPrompts.confirm('Are you sure?')
370
- end
371
-
372
- # Delete persona
373
- Formatters::ProgressFormatter.with_spinner("Deleting persona '#{name}'") do
374
- ctx.client.delete_resource('LanguagePersona', name, ctx.namespace)
375
322
  end
376
-
377
- Formatters::ProgressFormatter.success("Persona '#{name}' deleted successfully")
378
- rescue StandardError => e
379
- Formatters::ProgressFormatter.error("Failed to delete persona: #{e.message}")
380
- raise if ENV['DEBUG']
381
-
382
- exit 1
383
- end
384
-
385
- private
386
-
387
- def edit_in_editor(content, filename_prefix)
388
- Helpers::EditorHelper.edit_content(content, filename_prefix, '.txt')
389
323
  end
390
324
  end
391
325
  end
@@ -1,18 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
3
+ require_relative '../base_command'
4
4
  require_relative '../formatters/progress_formatter'
5
- require_relative '../wizards/quickstart_wizard'
5
+ require_relative '../../ux/quickstart'
6
6
 
7
7
  module LanguageOperator
8
8
  module CLI
9
9
  module Commands
10
10
  # Quickstart wizard for first-time users
11
- class Quickstart < Thor
11
+ class Quickstart < BaseCommand
12
12
  desc 'start', 'Interactive setup wizard for first-time users'
13
13
  def start
14
- wizard = Wizards::QuickstartWizard.new
15
- wizard.run
14
+ Ux::Quickstart.execute
16
15
  end
17
16
 
18
17
  default_task :start