language-operator 0.0.1 → 0.1.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 +4 -4
- data/.rubocop.yml +125 -0
- data/CHANGELOG.md +88 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +284 -0
- data/LICENSE +229 -21
- data/Makefile +82 -0
- data/README.md +3 -11
- data/Rakefile +63 -0
- data/bin/aictl +7 -0
- data/completions/_aictl +232 -0
- data/completions/aictl.bash +121 -0
- data/completions/aictl.fish +114 -0
- data/docs/architecture/agent-runtime.md +585 -0
- data/docs/dsl/SCHEMA_VERSION.md +250 -0
- data/docs/dsl/agent-reference.md +604 -0
- data/docs/dsl/best-practices.md +1078 -0
- data/docs/dsl/chat-endpoints.md +895 -0
- data/docs/dsl/constraints.md +671 -0
- data/docs/dsl/mcp-integration.md +1177 -0
- data/docs/dsl/webhooks.md +932 -0
- data/docs/dsl/workflows.md +744 -0
- data/lib/language_operator/agent/base.rb +110 -0
- data/lib/language_operator/agent/executor.rb +440 -0
- data/lib/language_operator/agent/instrumentation.rb +54 -0
- data/lib/language_operator/agent/metrics_tracker.rb +183 -0
- data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
- data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
- data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
- data/lib/language_operator/agent/safety/content_filter.rb +93 -0
- data/lib/language_operator/agent/safety/manager.rb +207 -0
- data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
- data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
- data/lib/language_operator/agent/scheduler.rb +183 -0
- data/lib/language_operator/agent/telemetry.rb +116 -0
- data/lib/language_operator/agent/web_server.rb +610 -0
- data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
- data/lib/language_operator/agent.rb +149 -0
- data/lib/language_operator/cli/commands/agent.rb +1205 -0
- data/lib/language_operator/cli/commands/cluster.rb +371 -0
- data/lib/language_operator/cli/commands/install.rb +404 -0
- data/lib/language_operator/cli/commands/model.rb +266 -0
- data/lib/language_operator/cli/commands/persona.rb +393 -0
- data/lib/language_operator/cli/commands/quickstart.rb +22 -0
- data/lib/language_operator/cli/commands/status.rb +143 -0
- data/lib/language_operator/cli/commands/system.rb +772 -0
- data/lib/language_operator/cli/commands/tool.rb +537 -0
- data/lib/language_operator/cli/commands/use.rb +47 -0
- data/lib/language_operator/cli/errors/handler.rb +180 -0
- data/lib/language_operator/cli/errors/suggestions.rb +176 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
- data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
- data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
- data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
- data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
- data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
- data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
- data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
- data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
- data/lib/language_operator/cli/main.rb +236 -0
- data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
- data/lib/language_operator/client/base.rb +214 -0
- data/lib/language_operator/client/config.rb +136 -0
- data/lib/language_operator/client/cost_calculator.rb +37 -0
- data/lib/language_operator/client/mcp_connector.rb +123 -0
- data/lib/language_operator/client.rb +19 -0
- data/lib/language_operator/config/cluster_config.rb +101 -0
- data/lib/language_operator/config/tool_patterns.yaml +57 -0
- data/lib/language_operator/config/tool_registry.rb +96 -0
- data/lib/language_operator/config.rb +138 -0
- data/lib/language_operator/dsl/adapter.rb +124 -0
- data/lib/language_operator/dsl/agent_context.rb +90 -0
- data/lib/language_operator/dsl/agent_definition.rb +427 -0
- data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
- data/lib/language_operator/dsl/config.rb +119 -0
- data/lib/language_operator/dsl/context.rb +50 -0
- data/lib/language_operator/dsl/execution_context.rb +47 -0
- data/lib/language_operator/dsl/helpers.rb +109 -0
- data/lib/language_operator/dsl/http.rb +184 -0
- data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
- data/lib/language_operator/dsl/parameter_definition.rb +124 -0
- data/lib/language_operator/dsl/registry.rb +36 -0
- data/lib/language_operator/dsl/schema.rb +1102 -0
- data/lib/language_operator/dsl/shell.rb +125 -0
- data/lib/language_operator/dsl/tool_definition.rb +112 -0
- data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
- data/lib/language_operator/dsl/webhook_definition.rb +106 -0
- data/lib/language_operator/dsl/workflow_definition.rb +259 -0
- data/lib/language_operator/dsl.rb +161 -0
- data/lib/language_operator/errors.rb +60 -0
- data/lib/language_operator/kubernetes/client.rb +279 -0
- data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
- data/lib/language_operator/loggable.rb +47 -0
- data/lib/language_operator/logger.rb +141 -0
- data/lib/language_operator/retry.rb +123 -0
- data/lib/language_operator/retryable.rb +132 -0
- data/lib/language_operator/templates/README.md +23 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -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 +93 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
- data/lib/language_operator/tool_loader.rb +242 -0
- data/lib/language_operator/validators.rb +170 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +65 -3
- data/requirements/tasks/challenge.md +9 -0
- data/requirements/tasks/iterate.md +36 -0
- data/requirements/tasks/optimize.md +21 -0
- data/requirements/tasks/tag.md +5 -0
- data/test_agent_dsl.rb +108 -0
- metadata +507 -20
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pastel'
|
|
4
|
+
require_relative 'suggestions'
|
|
5
|
+
require_relative '../formatters/progress_formatter'
|
|
6
|
+
|
|
7
|
+
module LanguageOperator
|
|
8
|
+
module CLI
|
|
9
|
+
module Errors
|
|
10
|
+
# Central error handler with context-aware suggestions
|
|
11
|
+
class Handler
|
|
12
|
+
class << self
|
|
13
|
+
# Handle an error with context and provide helpful suggestions
|
|
14
|
+
def handle(error, context = {})
|
|
15
|
+
case error
|
|
16
|
+
when K8s::Error::NotFound
|
|
17
|
+
handle_not_found(error, context)
|
|
18
|
+
else
|
|
19
|
+
handle_generic(error, context)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Handle resource not found errors with fuzzy matching
|
|
24
|
+
def handle_not_found(error, context)
|
|
25
|
+
resource_type = context[:resource_type] || 'Resource'
|
|
26
|
+
resource_name = context[:resource_name] || 'unknown'
|
|
27
|
+
cluster = context[:cluster]
|
|
28
|
+
|
|
29
|
+
# Display main error message
|
|
30
|
+
message = if cluster
|
|
31
|
+
"#{format_resource_type(resource_type)} '#{resource_name}' not found in cluster '#{cluster}'"
|
|
32
|
+
else
|
|
33
|
+
"#{format_resource_type(resource_type)} '#{resource_name}' not found"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Formatters::ProgressFormatter.error(message)
|
|
37
|
+
puts
|
|
38
|
+
|
|
39
|
+
# Find and display similar resources
|
|
40
|
+
if context[:available_resources]
|
|
41
|
+
similar = Suggestions.find_similar(resource_name, context[:available_resources])
|
|
42
|
+
if similar.any?
|
|
43
|
+
puts pastel.yellow('Did you mean?')
|
|
44
|
+
similar.each { |name| puts " #{pastel.cyan('•')} #{name}" }
|
|
45
|
+
puts
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Display recovery suggestions
|
|
50
|
+
error_type = error_type_for_resource(resource_type)
|
|
51
|
+
suggestions = Suggestions.for_error(error_type, context)
|
|
52
|
+
display_suggestions(suggestions) if suggestions.any?
|
|
53
|
+
|
|
54
|
+
# Re-raise if in debug mode, otherwise exit
|
|
55
|
+
raise error if ENV['DEBUG']
|
|
56
|
+
|
|
57
|
+
exit 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Handle generic errors
|
|
61
|
+
def handle_generic(error, context)
|
|
62
|
+
operation = context[:operation] || 'operation'
|
|
63
|
+
|
|
64
|
+
Formatters::ProgressFormatter.error("Failed to #{operation}: #{error.message}")
|
|
65
|
+
puts
|
|
66
|
+
|
|
67
|
+
# Display suggestions if provided in context
|
|
68
|
+
display_suggestions(context[:suggestions]) if context[:suggestions]
|
|
69
|
+
|
|
70
|
+
# Re-raise if in debug mode, otherwise exit
|
|
71
|
+
raise error if ENV['DEBUG']
|
|
72
|
+
|
|
73
|
+
exit 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Handle specific error scenarios with custom suggestions
|
|
77
|
+
def handle_no_cluster_selected
|
|
78
|
+
Formatters::ProgressFormatter.error('No cluster selected')
|
|
79
|
+
puts
|
|
80
|
+
|
|
81
|
+
puts 'You must connect to a cluster first:'
|
|
82
|
+
puts
|
|
83
|
+
|
|
84
|
+
suggestions = Suggestions.for_error(:no_cluster_selected)
|
|
85
|
+
suggestions.each { |line| puts line }
|
|
86
|
+
|
|
87
|
+
exit 1
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def handle_no_models_available(context = {})
|
|
91
|
+
Formatters::ProgressFormatter.error('No models found in cluster')
|
|
92
|
+
puts
|
|
93
|
+
|
|
94
|
+
suggestions = Suggestions.for_error(:no_models_available, context)
|
|
95
|
+
suggestions.each { |line| puts line }
|
|
96
|
+
|
|
97
|
+
exit 1
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def handle_synthesis_failed(message)
|
|
101
|
+
Formatters::ProgressFormatter.error("Synthesis failed: #{message}")
|
|
102
|
+
puts
|
|
103
|
+
|
|
104
|
+
suggestions = Suggestions.for_error(:synthesis_failed)
|
|
105
|
+
suggestions.each { |line| puts line }
|
|
106
|
+
|
|
107
|
+
exit 1
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def handle_already_exists(context = {})
|
|
111
|
+
resource_type = context[:resource_type] || 'Resource'
|
|
112
|
+
resource_name = context[:resource_name] || 'unknown'
|
|
113
|
+
cluster = context[:cluster]
|
|
114
|
+
|
|
115
|
+
message = if cluster
|
|
116
|
+
"#{format_resource_type(resource_type)} '#{resource_name}' already exists in cluster '#{cluster}'"
|
|
117
|
+
else
|
|
118
|
+
"#{format_resource_type(resource_type)} '#{resource_name}' already exists"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
Formatters::ProgressFormatter.error(message)
|
|
122
|
+
puts
|
|
123
|
+
|
|
124
|
+
suggestions = Suggestions.for_error(:already_exists, context)
|
|
125
|
+
display_suggestions(suggestions) if suggestions.any?
|
|
126
|
+
|
|
127
|
+
exit 1
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def display_suggestions(suggestions)
|
|
133
|
+
return if suggestions.empty?
|
|
134
|
+
|
|
135
|
+
suggestions.each do |suggestion|
|
|
136
|
+
puts suggestion
|
|
137
|
+
end
|
|
138
|
+
puts
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def format_resource_type(resource_type)
|
|
142
|
+
case resource_type
|
|
143
|
+
when 'LanguageAgent'
|
|
144
|
+
'Agent'
|
|
145
|
+
when 'LanguageTool'
|
|
146
|
+
'Tool'
|
|
147
|
+
when 'LanguageModel'
|
|
148
|
+
'Model'
|
|
149
|
+
when 'LanguagePersona'
|
|
150
|
+
'Persona'
|
|
151
|
+
else
|
|
152
|
+
resource_type
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def error_type_for_resource(resource_type)
|
|
157
|
+
case resource_type
|
|
158
|
+
when 'LanguageAgent', 'agent'
|
|
159
|
+
:agent_not_found
|
|
160
|
+
when 'LanguageTool', 'tool'
|
|
161
|
+
:tool_not_found
|
|
162
|
+
when 'LanguageModel', 'model'
|
|
163
|
+
:model_not_found
|
|
164
|
+
when 'LanguagePersona', 'persona'
|
|
165
|
+
:persona_not_found
|
|
166
|
+
when 'cluster'
|
|
167
|
+
:cluster_not_found
|
|
168
|
+
else
|
|
169
|
+
:resource_not_found
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def pastel
|
|
174
|
+
@pastel ||= Pastel.new
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'did_you_mean'
|
|
4
|
+
require 'pastel'
|
|
5
|
+
|
|
6
|
+
module LanguageOperator
|
|
7
|
+
module CLI
|
|
8
|
+
module Errors
|
|
9
|
+
# Provides helpful suggestions for error recovery
|
|
10
|
+
class Suggestions
|
|
11
|
+
class << self
|
|
12
|
+
# Find similar resource names using fuzzy matching
|
|
13
|
+
def find_similar(input_name, available_names, limit: 3)
|
|
14
|
+
return [] if available_names.empty?
|
|
15
|
+
|
|
16
|
+
spell_checker = DidYouMean::SpellChecker.new(dictionary: available_names)
|
|
17
|
+
corrections = spell_checker.correct(input_name)
|
|
18
|
+
corrections.first(limit)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Generate context-aware suggestions based on error type
|
|
22
|
+
def for_error(error_type, context = {})
|
|
23
|
+
case error_type
|
|
24
|
+
when :agent_not_found
|
|
25
|
+
agent_not_found_suggestions(context)
|
|
26
|
+
when :tool_not_found
|
|
27
|
+
tool_not_found_suggestions(context)
|
|
28
|
+
when :model_not_found
|
|
29
|
+
model_not_found_suggestions(context)
|
|
30
|
+
when :persona_not_found
|
|
31
|
+
persona_not_found_suggestions(context)
|
|
32
|
+
when :cluster_not_found
|
|
33
|
+
cluster_not_found_suggestions(context)
|
|
34
|
+
when :no_cluster_selected
|
|
35
|
+
no_cluster_selected_suggestions
|
|
36
|
+
when :no_models_available
|
|
37
|
+
no_models_available_suggestions(context)
|
|
38
|
+
when :synthesis_failed
|
|
39
|
+
synthesis_failed_suggestions
|
|
40
|
+
when :already_exists
|
|
41
|
+
already_exists_suggestions(context)
|
|
42
|
+
else
|
|
43
|
+
[]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def agent_not_found_suggestions(context)
|
|
50
|
+
suggestions = []
|
|
51
|
+
suggestions << "List all agents: #{pastel.dim('aictl agent list')}"
|
|
52
|
+
suggestions << "Create a new agent: #{pastel.dim('aictl agent create \"description\"')}"
|
|
53
|
+
suggestions << "Use the wizard: #{pastel.dim('aictl agent create --wizard')}" if context[:suggest_wizard]
|
|
54
|
+
suggestions
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def tool_not_found_suggestions(context)
|
|
58
|
+
suggestions = []
|
|
59
|
+
tool_name = context[:tool_name]
|
|
60
|
+
|
|
61
|
+
if tool_name
|
|
62
|
+
suggestions << "Install from registry: #{pastel.dim("aictl tool install #{tool_name}")}"
|
|
63
|
+
suggestions << "Search for tools: #{pastel.dim('aictl tool search')}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
suggestions << "List installed tools: #{pastel.dim('aictl tool list')}"
|
|
67
|
+
suggestions
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def model_not_found_suggestions(_context)
|
|
71
|
+
suggestions = []
|
|
72
|
+
suggestions << "List all models: #{pastel.dim('aictl model list')}"
|
|
73
|
+
suggestions << "Create a new model: #{pastel.dim('aictl model create <name> --provider <provider>')}"
|
|
74
|
+
suggestions
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def persona_not_found_suggestions(_context)
|
|
78
|
+
suggestions = []
|
|
79
|
+
suggestions << "List all personas: #{pastel.dim('aictl persona list')}"
|
|
80
|
+
suggestions << "Create a new persona: #{pastel.dim('aictl persona create <name>')}"
|
|
81
|
+
suggestions
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def cluster_not_found_suggestions(_context)
|
|
85
|
+
suggestions = []
|
|
86
|
+
suggestions << "List available clusters: #{pastel.dim('aictl cluster list')}"
|
|
87
|
+
suggestions << "Create a new cluster: #{pastel.dim('aictl cluster create <name>')}"
|
|
88
|
+
suggestions
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def no_cluster_selected_suggestions
|
|
92
|
+
[
|
|
93
|
+
'Create a new cluster:',
|
|
94
|
+
" #{pastel.dim('aictl cluster create my-cluster')}",
|
|
95
|
+
'',
|
|
96
|
+
'Or select an existing cluster:',
|
|
97
|
+
" #{pastel.dim('aictl use my-cluster')}",
|
|
98
|
+
'',
|
|
99
|
+
'List available clusters:',
|
|
100
|
+
" #{pastel.dim('aictl cluster list')}"
|
|
101
|
+
]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def no_models_available_suggestions(context)
|
|
105
|
+
cluster = context[:cluster]
|
|
106
|
+
suggestions = []
|
|
107
|
+
|
|
108
|
+
if cluster
|
|
109
|
+
suggestions << "Create a model in cluster '#{cluster}':"
|
|
110
|
+
suggestions << " #{pastel.dim('aictl model create <name> --provider anthropic --model claude-3-5-sonnet')}"
|
|
111
|
+
else
|
|
112
|
+
suggestions << "Create a model: #{pastel.dim('aictl model create <name> --provider <provider>')}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
suggestions << ''
|
|
116
|
+
suggestions << "List available models: #{pastel.dim('aictl model list')}"
|
|
117
|
+
suggestions
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def synthesis_failed_suggestions
|
|
121
|
+
[
|
|
122
|
+
'The description may be too vague. Try the interactive wizard:',
|
|
123
|
+
" #{pastel.dim('aictl agent create --wizard')}",
|
|
124
|
+
'',
|
|
125
|
+
'Or provide a more detailed description with:',
|
|
126
|
+
' • What the agent should do specifically',
|
|
127
|
+
' • When it should run (schedule)',
|
|
128
|
+
' • What tools or resources it needs',
|
|
129
|
+
'',
|
|
130
|
+
'Examples:',
|
|
131
|
+
" #{pastel.green('✓')} #{pastel.dim('\"Check my email every hour and notify me of urgent messages\"')}",
|
|
132
|
+
" #{pastel.green('✓')} #{pastel.dim('\"Review my spreadsheet at 4pm daily and email me errors\"')}",
|
|
133
|
+
" #{pastel.red('✗')} #{pastel.dim('\"do the thing\"')} (too vague)"
|
|
134
|
+
]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def already_exists_suggestions(context)
|
|
138
|
+
resource_type = context[:resource_type]
|
|
139
|
+
resource_name = context[:resource_name]
|
|
140
|
+
|
|
141
|
+
suggestions = []
|
|
142
|
+
|
|
143
|
+
if resource_name
|
|
144
|
+
suggestions << "View existing #{resource_type}: #{pastel.dim("aictl #{command_for_resource(resource_type)} inspect #{resource_name}")}"
|
|
145
|
+
suggestions << "Delete and recreate: #{pastel.dim("aictl #{command_for_resource(resource_type)} delete #{resource_name}")}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
suggestions << 'Choose a different name'
|
|
149
|
+
suggestions
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def command_for_resource(resource_type)
|
|
153
|
+
case resource_type
|
|
154
|
+
when 'LanguageAgent', 'agent'
|
|
155
|
+
'agent'
|
|
156
|
+
when 'LanguageTool', 'tool'
|
|
157
|
+
'tool'
|
|
158
|
+
when 'LanguageModel', 'model'
|
|
159
|
+
'model'
|
|
160
|
+
when 'LanguagePersona', 'persona'
|
|
161
|
+
'persona'
|
|
162
|
+
when 'cluster'
|
|
163
|
+
'cluster'
|
|
164
|
+
else
|
|
165
|
+
resource_type.downcase
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def pastel
|
|
170
|
+
@pastel ||= Pastel.new
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rouge'
|
|
4
|
+
require_relative '../helpers/pastel_helper'
|
|
5
|
+
|
|
6
|
+
module LanguageOperator
|
|
7
|
+
module CLI
|
|
8
|
+
module Formatters
|
|
9
|
+
# Formatter for displaying syntax-highlighted code in the terminal
|
|
10
|
+
class CodeFormatter
|
|
11
|
+
class << self
|
|
12
|
+
include Helpers::PastelHelper
|
|
13
|
+
|
|
14
|
+
# Display Ruby code with syntax highlighting
|
|
15
|
+
#
|
|
16
|
+
# @param code_content [String] The Ruby code to display
|
|
17
|
+
# @param title [String, nil] Optional title to display above the code
|
|
18
|
+
# @param max_lines [Integer, nil] Maximum number of lines to display (nil for all)
|
|
19
|
+
def display_ruby_code(code_content, title: nil, max_lines: nil)
|
|
20
|
+
# Use Rouge to highlight the code
|
|
21
|
+
formatter = Rouge::Formatters::Terminal256.new
|
|
22
|
+
lexer = Rouge::Lexers::Ruby.new
|
|
23
|
+
|
|
24
|
+
# Truncate if max_lines specified
|
|
25
|
+
lines = code_content.lines
|
|
26
|
+
truncated = false
|
|
27
|
+
if max_lines && lines.length > max_lines
|
|
28
|
+
code_to_display = lines[0...max_lines].join
|
|
29
|
+
truncated = true
|
|
30
|
+
remaining_lines = lines.length - max_lines
|
|
31
|
+
else
|
|
32
|
+
code_to_display = code_content
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Print header
|
|
36
|
+
puts
|
|
37
|
+
puts pastel.cyan(title) if title
|
|
38
|
+
puts pastel.dim('─' * 80)
|
|
39
|
+
puts
|
|
40
|
+
|
|
41
|
+
# Highlight and print the code
|
|
42
|
+
highlighted = formatter.format(lexer.lex(code_to_display))
|
|
43
|
+
puts highlighted
|
|
44
|
+
|
|
45
|
+
# Show truncation notice if applicable
|
|
46
|
+
if truncated
|
|
47
|
+
puts
|
|
48
|
+
puts pastel.dim("... #{remaining_lines} more lines ...")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Print footer
|
|
52
|
+
puts
|
|
53
|
+
puts pastel.dim('─' * 80)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Display a code snippet with context
|
|
57
|
+
#
|
|
58
|
+
# @param code_content [String] The Ruby code
|
|
59
|
+
# @param description [String] Description of what the code does
|
|
60
|
+
def display_snippet(code_content, description: nil)
|
|
61
|
+
puts
|
|
62
|
+
puts pastel.cyan('Generated Code Preview:') if description.nil?
|
|
63
|
+
puts pastel.dim(description) if description
|
|
64
|
+
puts
|
|
65
|
+
|
|
66
|
+
formatter = Rouge::Formatters::Terminal256.new
|
|
67
|
+
lexer = Rouge::Lexers::Ruby.new
|
|
68
|
+
highlighted = formatter.format(lexer.lex(code_content))
|
|
69
|
+
|
|
70
|
+
puts highlighted
|
|
71
|
+
puts
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|