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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -8
- data/CHANGELOG.md +14 -0
- data/CI_STATUS.md +56 -0
- data/Gemfile.lock +2 -2
- data/Makefile +22 -6
- data/lib/language_operator/agent/base.rb +10 -6
- data/lib/language_operator/agent/executor.rb +19 -97
- data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
- data/lib/language_operator/agent/safety/safe_executor.rb +27 -2
- data/lib/language_operator/agent/scheduler.rb +60 -0
- data/lib/language_operator/agent/task_executor.rb +548 -0
- data/lib/language_operator/agent.rb +90 -27
- data/lib/language_operator/cli/base_command.rb +117 -0
- data/lib/language_operator/cli/commands/agent.rb +339 -407
- data/lib/language_operator/cli/commands/cluster.rb +274 -290
- data/lib/language_operator/cli/commands/install.rb +110 -119
- data/lib/language_operator/cli/commands/model.rb +284 -184
- data/lib/language_operator/cli/commands/persona.rb +218 -284
- data/lib/language_operator/cli/commands/quickstart.rb +4 -5
- data/lib/language_operator/cli/commands/status.rb +31 -35
- data/lib/language_operator/cli/commands/system.rb +221 -233
- data/lib/language_operator/cli/commands/tool.rb +356 -422
- data/lib/language_operator/cli/commands/use.rb +19 -22
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
- data/lib/language_operator/client/config.rb +20 -21
- data/lib/language_operator/config.rb +115 -3
- data/lib/language_operator/constants.rb +54 -0
- data/lib/language_operator/dsl/agent_context.rb +7 -7
- data/lib/language_operator/dsl/agent_definition.rb +111 -26
- data/lib/language_operator/dsl/config.rb +30 -66
- data/lib/language_operator/dsl/main_definition.rb +114 -0
- data/lib/language_operator/dsl/schema.rb +84 -43
- data/lib/language_operator/dsl/task_definition.rb +315 -0
- data/lib/language_operator/dsl.rb +0 -1
- data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
- data/lib/language_operator/logger.rb +4 -4
- data/lib/language_operator/synthesis_test_harness.rb +324 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +26 -8
- data/lib/language_operator/templates/schema/CHANGELOG.md +26 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +84 -42
- data/lib/language_operator/type_coercion.rb +250 -0
- data/lib/language_operator/ux/base.rb +81 -0
- data/lib/language_operator/ux/concerns/README.md +155 -0
- data/lib/language_operator/ux/concerns/headings.rb +90 -0
- data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
- data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
- data/lib/language_operator/ux/create_agent.rb +252 -0
- data/lib/language_operator/ux/create_model.rb +267 -0
- data/lib/language_operator/ux/quickstart.rb +594 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +2 -0
- data/requirements/ARCHITECTURE.md +1 -0
- data/requirements/SCRATCH.md +153 -0
- data/requirements/dsl.md +0 -0
- data/requirements/features +1 -0
- data/requirements/personas +1 -0
- data/requirements/proposals +1 -0
- data/requirements/tasks/iterate.md +14 -15
- data/requirements/tasks/optimize.md +13 -4
- data/synth/001/Makefile +90 -0
- data/synth/001/agent.rb +26 -0
- data/synth/001/agent.yaml +7 -0
- data/synth/001/output.log +44 -0
- data/synth/Makefile +39 -0
- data/synth/README.md +342 -0
- metadata +37 -10
- data/lib/language_operator/dsl/workflow_definition.rb +0 -259
- 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 <
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
return if personas.empty?
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
60
|
+
handle_command_error('show persona') do
|
|
61
|
+
persona = get_resource_or_exit('LanguagePersona', name)
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
exit 1
|
|
75
|
-
end
|
|
63
|
+
puts
|
|
64
|
+
puts "Persona: #{pastel.cyan.bold(name)}"
|
|
65
|
+
puts '═' * 80
|
|
66
|
+
puts
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
puts
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
Formatters::ProgressFormatter.
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
puts '=' * 80
|
|
176
|
-
puts
|
|
145
|
+
# Interactive prompts
|
|
146
|
+
require 'tty-prompt'
|
|
147
|
+
prompt = TTY::Prompt.new
|
|
177
148
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
149
|
+
puts
|
|
150
|
+
puts '=' * 80
|
|
151
|
+
puts ' Create New Persona'
|
|
152
|
+
puts '=' * 80
|
|
153
|
+
puts
|
|
181
154
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
274
|
+
# Update persona
|
|
275
|
+
Formatters::ProgressFormatter.with_spinner("Updating persona '#{name}'") do
|
|
276
|
+
ctx.client.update_resource(edited_persona)
|
|
277
|
+
end
|
|
311
278
|
|
|
312
|
-
|
|
279
|
+
Formatters::ProgressFormatter.success("Persona '#{name}' updated successfully")
|
|
313
280
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
3
|
+
require_relative '../base_command'
|
|
4
4
|
require_relative '../formatters/progress_formatter'
|
|
5
|
-
require_relative '
|
|
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 <
|
|
11
|
+
class Quickstart < BaseCommand
|
|
12
12
|
desc 'start', 'Interactive setup wizard for first-time users'
|
|
13
13
|
def start
|
|
14
|
-
|
|
15
|
-
wizard.run
|
|
14
|
+
Ux::Quickstart.execute
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
default_task :start
|