language-operator 0.1.30 → 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 +49 -0
- data/CI_STATUS.md +56 -0
- data/Gemfile.lock +2 -2
- data/Makefile +28 -7
- data/Rakefile +29 -0
- data/docs/dsl/SCHEMA_VERSION.md +250 -0
- data/docs/dsl/agent-reference.md +13 -0
- 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 +39 -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 +351 -466
- data/lib/language_operator/cli/commands/cluster.rb +276 -256
- 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 +220 -289
- data/lib/language_operator/cli/commands/quickstart.rb +4 -5
- data/lib/language_operator/cli/commands/status.rb +36 -53
- data/lib/language_operator/cli/commands/system.rb +760 -0
- 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/formatters/code_formatter.rb +3 -7
- data/lib/language_operator/cli/formatters/log_formatter.rb +3 -5
- data/lib/language_operator/cli/formatters/progress_formatter.rb +3 -7
- data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +10 -26
- data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
- data/lib/language_operator/cli/main.rb +4 -0
- 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 +1143 -0
- data/lib/language_operator/dsl/task_definition.rb +315 -0
- data/lib/language_operator/dsl.rb +1 -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/README.md +23 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +133 -0
- data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
- data/lib/language_operator/templates/schema/.gitkeep +0 -0
- data/lib/language_operator/templates/schema/CHANGELOG.md +119 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +494 -0
- 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 +49 -18
- data/examples/README.md +0 -569
- data/examples/agent_example.rb +0 -86
- data/examples/chat_endpoint_agent.rb +0 -118
- data/examples/github_webhook_agent.rb +0 -171
- data/examples/mcp_agent.rb +0 -158
- data/examples/oauth_callback_agent.rb +0 -296
- data/examples/stripe_webhook_agent.rb +0 -219
- data/examples/webhook_agent.rb +0 -80
- data/lib/language_operator/dsl/workflow_definition.rb +0 -259
- data/test_agent_dsl.rb +0 -108
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'thor'
|
|
4
3
|
require 'yaml'
|
|
5
|
-
|
|
4
|
+
require_relative '../base_command'
|
|
6
5
|
require_relative '../formatters/progress_formatter'
|
|
7
6
|
require_relative '../formatters/table_formatter'
|
|
8
7
|
require_relative '../helpers/cluster_validator'
|
|
9
|
-
require_relative '../helpers/cluster_context'
|
|
10
8
|
require_relative '../helpers/user_prompts'
|
|
11
9
|
require_relative '../helpers/resource_dependency_checker'
|
|
12
10
|
require_relative '../helpers/editor_helper'
|
|
11
|
+
require_relative '../helpers/pastel_helper'
|
|
13
12
|
require_relative '../../config/cluster_config'
|
|
14
13
|
require_relative '../../kubernetes/client'
|
|
15
14
|
require_relative '../../kubernetes/resource_builder'
|
|
@@ -18,112 +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
|
|
22
|
+
include Helpers::PastelHelper
|
|
23
23
|
|
|
24
24
|
desc 'list', 'List all personas in current cluster'
|
|
25
25
|
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
26
26
|
def list
|
|
27
|
-
|
|
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
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
return if personas.empty?
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
puts
|
|
34
|
-
puts 'Personas define the personality and capabilities of agents.'
|
|
35
|
-
puts
|
|
36
|
-
puts 'Create a persona with:'
|
|
37
|
-
puts ' aictl persona create <name>'
|
|
38
|
-
return
|
|
39
|
-
end
|
|
38
|
+
# Get agents to count usage
|
|
39
|
+
agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
table_data = personas.map do |persona|
|
|
42
|
+
name = persona.dig('metadata', 'name')
|
|
43
|
+
used_by = agents.count { |a| a.dig('spec', 'persona') == name }
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
name: name,
|
|
50
|
-
tone: persona.dig('spec', 'tone') || 'neutral',
|
|
51
|
-
used_by: used_by,
|
|
52
|
-
description: persona.dig('spec', 'description') || ''
|
|
53
|
-
}
|
|
53
|
+
Formatters::TableFormatter.personas(table_data)
|
|
54
54
|
end
|
|
55
|
-
|
|
56
|
-
Formatters::TableFormatter.personas(table_data)
|
|
57
|
-
rescue StandardError => e
|
|
58
|
-
Formatters::ProgressFormatter.error("Failed to list personas: #{e.message}")
|
|
59
|
-
raise if ENV['DEBUG']
|
|
60
|
-
|
|
61
|
-
exit 1
|
|
62
55
|
end
|
|
63
56
|
|
|
64
57
|
desc 'show NAME', 'Display full persona details'
|
|
65
58
|
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
66
59
|
def show(name)
|
|
67
|
-
|
|
60
|
+
handle_command_error('show persona') do
|
|
61
|
+
persona = get_resource_or_exit('LanguagePersona', name)
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
exit 1
|
|
74
|
-
end
|
|
63
|
+
puts
|
|
64
|
+
puts "Persona: #{pastel.cyan.bold(name)}"
|
|
65
|
+
puts '═' * 80
|
|
66
|
+
puts
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
puts "#{pastel.bold('Display Name:')} #{spec['displayName']}"
|
|
84
|
-
puts "#{pastel.bold('Tone:')} #{pastel.yellow(spec['tone'])}" if spec['tone']
|
|
85
|
-
puts
|
|
86
|
-
puts pastel.bold('Description:')
|
|
87
|
-
puts " #{spec['description']}"
|
|
88
|
-
puts
|
|
89
|
-
|
|
90
|
-
if spec['systemPrompt']
|
|
91
|
-
puts pastel.bold('System Prompt:')
|
|
92
|
-
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']}"
|
|
93
75
|
puts
|
|
94
|
-
end
|
|
95
76
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
puts
|
|
77
|
+
if spec['systemPrompt']
|
|
78
|
+
puts pastel.bold('System Prompt:')
|
|
79
|
+
puts " #{spec['systemPrompt']}"
|
|
80
|
+
puts
|
|
100
81
|
end
|
|
101
|
-
puts
|
|
102
|
-
end
|
|
103
82
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
108
89
|
end
|
|
109
|
-
puts
|
|
110
|
-
end
|
|
111
90
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
116
98
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
puts
|
|
122
|
-
rescue StandardError => e
|
|
123
|
-
Formatters::ProgressFormatter.error("Failed to show persona: #{e.message}")
|
|
124
|
-
raise if ENV['DEBUG']
|
|
99
|
+
if spec['responseFormat']
|
|
100
|
+
puts "#{pastel.bold('Response Format:')} #{spec['responseFormat']}"
|
|
101
|
+
puts
|
|
102
|
+
end
|
|
125
103
|
|
|
126
|
-
|
|
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
|
|
127
110
|
end
|
|
128
111
|
|
|
129
112
|
desc 'create NAME', 'Create a new persona'
|
|
@@ -137,124 +120,115 @@ module LanguageOperator
|
|
|
137
120
|
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
138
121
|
option :from, type: :string, desc: 'Copy from existing persona as starting point'
|
|
139
122
|
def create(name)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
# Check if persona already exists
|
|
143
|
-
begin
|
|
144
|
-
ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
|
|
145
|
-
Formatters::ProgressFormatter.error("Persona '#{name}' already exists in cluster '#{ctx.name}'")
|
|
146
|
-
puts
|
|
147
|
-
puts 'Use a different name or delete the existing persona first:'
|
|
148
|
-
puts " aictl persona delete #{name}"
|
|
149
|
-
exit 1
|
|
150
|
-
rescue K8s::Error::NotFound
|
|
151
|
-
# Good - persona doesn't exist yet
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# If --from flag provided, copy from existing persona
|
|
155
|
-
base_persona = nil
|
|
156
|
-
if options[:from]
|
|
123
|
+
handle_command_error('create persona') do
|
|
124
|
+
# Check if persona already exists
|
|
157
125
|
begin
|
|
158
|
-
|
|
159
|
-
Formatters::ProgressFormatter.
|
|
126
|
+
ctx.client.get_resource('LanguagePersona', name, ctx.namespace)
|
|
127
|
+
Formatters::ProgressFormatter.error("Persona '#{name}' already exists in cluster '#{ctx.name}'")
|
|
160
128
|
puts
|
|
161
|
-
|
|
162
|
-
|
|
129
|
+
puts 'Use a different name or delete the existing persona first:'
|
|
130
|
+
puts " aictl persona delete #{name}"
|
|
163
131
|
exit 1
|
|
132
|
+
rescue K8s::Error::NotFound
|
|
133
|
+
# Good - persona doesn't exist yet
|
|
164
134
|
end
|
|
165
|
-
end
|
|
166
135
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
170
144
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
puts '=' * 80
|
|
175
|
-
puts
|
|
145
|
+
# Interactive prompts
|
|
146
|
+
require 'tty-prompt'
|
|
147
|
+
prompt = TTY::Prompt.new
|
|
176
148
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
149
|
+
puts
|
|
150
|
+
puts '=' * 80
|
|
151
|
+
puts ' Create New Persona'
|
|
152
|
+
puts '=' * 80
|
|
153
|
+
puts
|
|
180
154
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
q.required true
|
|
185
|
-
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)
|
|
186
158
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
190
164
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
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)
|
|
195
168
|
|
|
196
|
-
|
|
197
|
-
|
|
169
|
+
# Get system prompt
|
|
170
|
+
puts
|
|
171
|
+
puts 'System Prompt (press Enter to open editor):'
|
|
172
|
+
prompt.keypress('Press any key to continue...')
|
|
198
173
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
exit 1
|
|
202
|
-
end
|
|
174
|
+
default_system_prompt = base_persona&.dig('spec', 'systemPrompt') || ''
|
|
175
|
+
system_prompt = Helpers::EditorHelper.edit_content(default_system_prompt, 'persona-system-prompt', '.txt')
|
|
203
176
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
prompt.keypress('Press any key to continue...')
|
|
209
|
-
|
|
210
|
-
default_capabilities = base_persona&.dig('spec', 'capabilities')&.join("\n") || ''
|
|
211
|
-
capabilities_text = edit_in_editor(default_capabilities, 'persona-capabilities')
|
|
212
|
-
capabilities = capabilities_text.strip.empty? ? [] : capabilities_text.strip.split("\n").map(&:strip).reject(&:empty?)
|
|
213
|
-
|
|
214
|
-
# Build persona resource
|
|
215
|
-
persona_spec = {
|
|
216
|
-
'displayName' => display_name,
|
|
217
|
-
'description' => description,
|
|
218
|
-
'tone' => tone,
|
|
219
|
-
'systemPrompt' => system_prompt.strip
|
|
220
|
-
}
|
|
221
|
-
persona_spec['capabilities'] = capabilities unless capabilities.empty?
|
|
222
|
-
|
|
223
|
-
persona_resource = Kubernetes::ResourceBuilder.build_persona(
|
|
224
|
-
name: name,
|
|
225
|
-
spec: persona_spec,
|
|
226
|
-
namespace: ctx.namespace
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
# Show preview
|
|
230
|
-
puts
|
|
231
|
-
puts '=' * 80
|
|
232
|
-
puts 'Preview:'
|
|
233
|
-
puts '=' * 80
|
|
234
|
-
puts YAML.dump(persona_resource)
|
|
235
|
-
puts '=' * 80
|
|
236
|
-
puts
|
|
237
|
-
|
|
238
|
-
# Confirm creation
|
|
239
|
-
unless prompt.yes?('Create this persona?')
|
|
240
|
-
puts 'Creation cancelled'
|
|
241
|
-
return
|
|
242
|
-
end
|
|
177
|
+
if system_prompt.strip.empty?
|
|
178
|
+
Formatters::ProgressFormatter.error('System prompt cannot be empty')
|
|
179
|
+
exit 1
|
|
180
|
+
end
|
|
243
181
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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?
|
|
248
200
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
Formatters::ProgressFormatter.error("Failed to create persona: #{e.message}")
|
|
255
|
-
raise if ENV['DEBUG']
|
|
201
|
+
persona_resource = Kubernetes::ResourceBuilder.build_persona(
|
|
202
|
+
name: name,
|
|
203
|
+
spec: persona_spec,
|
|
204
|
+
namespace: ctx.namespace
|
|
205
|
+
)
|
|
256
206
|
|
|
257
|
-
|
|
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
|
|
221
|
+
|
|
222
|
+
# Create persona
|
|
223
|
+
Formatters::ProgressFormatter.with_spinner("Creating persona '#{name}'") do
|
|
224
|
+
ctx.client.create_resource(persona_resource)
|
|
225
|
+
end
|
|
226
|
+
|
|
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
|
|
258
232
|
end
|
|
259
233
|
|
|
260
234
|
desc 'edit NAME', 'Edit an existing persona'
|
|
@@ -269,126 +243,83 @@ module LanguageOperator
|
|
|
269
243
|
DESC
|
|
270
244
|
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
271
245
|
def edit(name)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
# Check if changed
|
|
287
|
-
if edited_yaml.strip == original_yaml.strip
|
|
288
|
-
Formatters::ProgressFormatter.info('No changes made')
|
|
289
|
-
return
|
|
290
|
-
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
|
|
291
259
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
299
267
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
|
305
273
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
274
|
+
# Update persona
|
|
275
|
+
Formatters::ProgressFormatter.with_spinner("Updating persona '#{name}'") do
|
|
276
|
+
ctx.client.update_resource(edited_persona)
|
|
277
|
+
end
|
|
310
278
|
|
|
311
|
-
|
|
279
|
+
Formatters::ProgressFormatter.success("Persona '#{name}' updated successfully")
|
|
312
280
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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)
|
|
316
284
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
|
322
291
|
end
|
|
323
292
|
end
|
|
324
|
-
rescue StandardError => e
|
|
325
|
-
Formatters::ProgressFormatter.error("Failed to edit persona: #{e.message}")
|
|
326
|
-
raise if ENV['DEBUG']
|
|
327
|
-
|
|
328
|
-
exit 1
|
|
329
293
|
end
|
|
330
294
|
|
|
331
295
|
desc 'delete NAME', 'Delete a persona'
|
|
332
296
|
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
333
297
|
option :force, type: :boolean, default: false, desc: 'Skip confirmation'
|
|
334
298
|
def delete(name)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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")
|
|
355
321
|
end
|
|
356
|
-
puts
|
|
357
|
-
puts 'Delete these agents first, or use --force to delete anyway.'
|
|
358
|
-
puts
|
|
359
|
-
return unless Helpers::UserPrompts.confirm('Are you sure?')
|
|
360
322
|
end
|
|
361
|
-
|
|
362
|
-
# Confirm deletion unless --force
|
|
363
|
-
unless options[:force] || agents_using.any?
|
|
364
|
-
puts "This will delete persona '#{name}' from cluster '#{ctx.name}':"
|
|
365
|
-
puts " Tone: #{persona.dig('spec', 'tone')}"
|
|
366
|
-
puts " Description: #{persona.dig('spec', 'description')}"
|
|
367
|
-
puts
|
|
368
|
-
return unless Helpers::UserPrompts.confirm('Are you sure?')
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
# Delete persona
|
|
372
|
-
Formatters::ProgressFormatter.with_spinner("Deleting persona '#{name}'") do
|
|
373
|
-
ctx.client.delete_resource('LanguagePersona', name, ctx.namespace)
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
Formatters::ProgressFormatter.success("Persona '#{name}' deleted successfully")
|
|
377
|
-
rescue StandardError => e
|
|
378
|
-
Formatters::ProgressFormatter.error("Failed to delete persona: #{e.message}")
|
|
379
|
-
raise if ENV['DEBUG']
|
|
380
|
-
|
|
381
|
-
exit 1
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
private
|
|
385
|
-
|
|
386
|
-
def edit_in_editor(content, filename_prefix)
|
|
387
|
-
Helpers::EditorHelper.edit_content(content, filename_prefix, '.txt')
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
def pastel
|
|
391
|
-
@pastel ||= Pastel.new
|
|
392
323
|
end
|
|
393
324
|
end
|
|
394
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
|