language-operator 0.1.61 → 0.1.62
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/.claude/commands/persona.md +9 -0
- data/.claude/commands/task.md +46 -1
- data/.rubocop.yml +13 -0
- data/.rubocop_custom/use_ux_helper.rb +44 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +12 -1
- data/Makefile +26 -7
- data/Makefile.common +50 -0
- data/bin/aictl +8 -1
- data/components/agent/Gemfile +1 -1
- data/components/agent/bin/langop-agent +7 -0
- data/docs/README.md +58 -0
- data/docs/{dsl/best-practices.md → best-practices.md} +4 -4
- data/docs/cli-reference.md +274 -0
- data/docs/{dsl/constraints.md → constraints.md} +5 -5
- data/docs/how-agents-work.md +156 -0
- data/docs/installation.md +218 -0
- data/docs/quickstart.md +299 -0
- data/docs/understanding-generated-code.md +265 -0
- data/docs/using-tools.md +457 -0
- data/docs/webhooks.md +509 -0
- data/examples/ux_helpers_demo.rb +296 -0
- data/lib/language_operator/agent/base.rb +11 -1
- data/lib/language_operator/agent/executor.rb +23 -6
- data/lib/language_operator/agent/safety/safe_executor.rb +41 -39
- data/lib/language_operator/agent/task_executor.rb +346 -63
- data/lib/language_operator/agent/web_server.rb +110 -14
- data/lib/language_operator/agent/webhook_authenticator.rb +39 -5
- data/lib/language_operator/agent.rb +88 -2
- data/lib/language_operator/cli/base_command.rb +17 -11
- data/lib/language_operator/cli/command_loader.rb +72 -0
- data/lib/language_operator/cli/commands/agent/base.rb +837 -0
- data/lib/language_operator/cli/commands/agent/code_operations.rb +102 -0
- data/lib/language_operator/cli/commands/agent/helpers/cluster_llm_client.rb +116 -0
- data/lib/language_operator/cli/commands/agent/helpers/code_parser.rb +115 -0
- data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +96 -0
- data/lib/language_operator/cli/commands/agent/learning.rb +289 -0
- data/lib/language_operator/cli/commands/agent/lifecycle.rb +102 -0
- data/lib/language_operator/cli/commands/agent/logs.rb +125 -0
- data/lib/language_operator/cli/commands/agent/workspace.rb +327 -0
- data/lib/language_operator/cli/commands/cluster.rb +129 -84
- data/lib/language_operator/cli/commands/install.rb +1 -1
- data/lib/language_operator/cli/commands/model/base.rb +215 -0
- data/lib/language_operator/cli/commands/model/test.rb +165 -0
- data/lib/language_operator/cli/commands/persona.rb +16 -34
- data/lib/language_operator/cli/commands/quickstart.rb +3 -2
- data/lib/language_operator/cli/commands/status.rb +40 -67
- data/lib/language_operator/cli/commands/system/base.rb +44 -0
- data/lib/language_operator/cli/commands/system/exec.rb +147 -0
- data/lib/language_operator/cli/commands/system/helpers/llm_synthesis.rb +183 -0
- data/lib/language_operator/cli/commands/system/helpers/pod_manager.rb +212 -0
- data/lib/language_operator/cli/commands/system/helpers/template_loader.rb +57 -0
- data/lib/language_operator/cli/commands/system/helpers/template_validator.rb +174 -0
- data/lib/language_operator/cli/commands/system/schema.rb +92 -0
- data/lib/language_operator/cli/commands/system/synthesis_template.rb +151 -0
- data/lib/language_operator/cli/commands/system/synthesize.rb +224 -0
- data/lib/language_operator/cli/commands/system/validate_template.rb +130 -0
- data/lib/language_operator/cli/commands/tool/base.rb +271 -0
- data/lib/language_operator/cli/commands/tool/install.rb +255 -0
- data/lib/language_operator/cli/commands/tool/search.rb +69 -0
- data/lib/language_operator/cli/commands/tool/test.rb +115 -0
- data/lib/language_operator/cli/commands/use.rb +29 -6
- data/lib/language_operator/cli/errors/handler.rb +20 -17
- data/lib/language_operator/cli/errors/suggestions.rb +3 -5
- data/lib/language_operator/cli/errors/thor_errors.rb +55 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +4 -11
- data/lib/language_operator/cli/formatters/log_formatter.rb +8 -15
- data/lib/language_operator/cli/formatters/progress_formatter.rb +6 -8
- data/lib/language_operator/cli/formatters/status_formatter.rb +26 -7
- data/lib/language_operator/cli/formatters/table_formatter.rb +47 -36
- data/lib/language_operator/cli/formatters/value_formatter.rb +75 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +5 -3
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +2 -1
- data/lib/language_operator/cli/helpers/label_utils.rb +97 -0
- data/lib/language_operator/{ux/concerns/provider_helpers.rb → cli/helpers/provider_helper.rb} +10 -29
- data/lib/language_operator/cli/helpers/schedule_builder.rb +21 -1
- data/lib/language_operator/cli/helpers/user_prompts.rb +19 -11
- data/lib/language_operator/cli/helpers/ux_helper.rb +538 -0
- data/lib/language_operator/{ux/concerns/input_validation.rb → cli/helpers/validation_helper.rb} +13 -66
- data/lib/language_operator/cli/main.rb +50 -40
- data/lib/language_operator/cli/templates/tools/generic.yaml +3 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +12 -20
- data/lib/language_operator/cli/wizards/model_wizard.rb +271 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +8 -34
- data/lib/language_operator/client/base.rb +28 -0
- data/lib/language_operator/client/config.rb +4 -1
- data/lib/language_operator/client/mcp_connector.rb +1 -1
- data/lib/language_operator/config/cluster_config.rb +3 -2
- data/lib/language_operator/config.rb +38 -11
- data/lib/language_operator/constants/kubernetes_labels.rb +80 -0
- data/lib/language_operator/constants.rb +13 -0
- data/lib/language_operator/dsl/http.rb +127 -10
- data/lib/language_operator/dsl.rb +153 -6
- data/lib/language_operator/errors.rb +50 -0
- data/lib/language_operator/kubernetes/client.rb +11 -6
- data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
- data/lib/language_operator/type_coercion.rb +118 -34
- data/lib/language_operator/utils/secure_path.rb +74 -0
- data/lib/language_operator/utils.rb +7 -0
- data/lib/language_operator/validators.rb +54 -2
- data/lib/language_operator/version.rb +1 -1
- data/synth/001/Makefile +10 -2
- data/synth/001/agent.rb +16 -15
- data/synth/001/output.log +27 -10
- data/synth/002/Makefile +10 -2
- data/synth/003/Makefile +1 -1
- data/synth/003/README.md +205 -133
- data/synth/003/agent.optimized.rb +66 -0
- data/synth/003/agent.synthesized.rb +41 -0
- metadata +111 -35
- data/docs/dsl/agent-reference.md +0 -604
- data/docs/dsl/mcp-integration.md +0 -1177
- data/docs/dsl/webhooks.md +0 -932
- data/docs/dsl/workflows.md +0 -744
- data/lib/language_operator/cli/commands/agent.rb +0 -1712
- data/lib/language_operator/cli/commands/model.rb +0 -366
- data/lib/language_operator/cli/commands/system.rb +0 -1259
- data/lib/language_operator/cli/commands/tool.rb +0 -654
- data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
- data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
- data/lib/language_operator/learning/adapters/base_adapter.rb +0 -149
- data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -221
- data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -435
- data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -239
- data/lib/language_operator/learning/optimizer.rb +0 -319
- data/lib/language_operator/learning/pattern_detector.rb +0 -260
- data/lib/language_operator/learning/task_synthesizer.rb +0 -288
- data/lib/language_operator/learning/trace_analyzer.rb +0 -285
- data/lib/language_operator/templates/task_synthesis.tmpl +0 -98
- data/lib/language_operator/ux/base.rb +0 -81
- data/lib/language_operator/ux/concerns/README.md +0 -155
- data/lib/language_operator/ux/concerns/headings.rb +0 -90
- data/lib/language_operator/ux/create_agent.rb +0 -255
- data/lib/language_operator/ux/create_model.rb +0 -267
- data/lib/language_operator/ux/quickstart.rb +0 -594
- data/synth/003/agent.rb +0 -41
- data/synth/003/output.log +0 -68
- /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
- /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
- /data/docs/{dsl/SCHEMA_VERSION.md → schema-versioning.md} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
module LanguageOperator
|
|
7
|
+
module CLI
|
|
8
|
+
module Commands
|
|
9
|
+
module System
|
|
10
|
+
# DSL schema export command
|
|
11
|
+
module Schema
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.class_eval do
|
|
14
|
+
desc 'schema', 'Export the DSL schema in various formats'
|
|
15
|
+
long_desc <<-DESC
|
|
16
|
+
Export the Language Operator Agent DSL schema in various formats.
|
|
17
|
+
|
|
18
|
+
The schema documents all available DSL methods, parameters, validation
|
|
19
|
+
patterns, and structure. Useful for template validation, documentation
|
|
20
|
+
generation, and IDE autocomplete.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
# Export JSON schema (default)
|
|
24
|
+
aictl system schema
|
|
25
|
+
|
|
26
|
+
# Export as YAML
|
|
27
|
+
aictl system schema --format yaml
|
|
28
|
+
|
|
29
|
+
# Export OpenAPI 3.0 specification
|
|
30
|
+
aictl system schema --format openapi
|
|
31
|
+
|
|
32
|
+
# Show schema version only
|
|
33
|
+
aictl system schema --version
|
|
34
|
+
|
|
35
|
+
# Save to file
|
|
36
|
+
aictl system schema > schema.json
|
|
37
|
+
aictl system schema --format openapi > openapi.json
|
|
38
|
+
DESC
|
|
39
|
+
option :format, type: :string, default: 'json', desc: 'Output format (json, yaml, openapi)'
|
|
40
|
+
option :version, type: :boolean, default: false, desc: 'Show schema version only'
|
|
41
|
+
def schema
|
|
42
|
+
handle_command_error('generate schema') do
|
|
43
|
+
# Handle version flag
|
|
44
|
+
if options[:version]
|
|
45
|
+
puts Dsl::Schema.version
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Generate schema based on format
|
|
50
|
+
format = options[:format].downcase
|
|
51
|
+
case format
|
|
52
|
+
when 'json'
|
|
53
|
+
output_json_schema
|
|
54
|
+
when 'yaml'
|
|
55
|
+
output_yaml_schema
|
|
56
|
+
when 'openapi'
|
|
57
|
+
output_openapi_schema
|
|
58
|
+
else
|
|
59
|
+
Formatters::ProgressFormatter.error("Invalid format: #{format}")
|
|
60
|
+
puts
|
|
61
|
+
puts 'Supported formats: json, yaml, openapi'
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# Output JSON Schema v7
|
|
70
|
+
def output_json_schema
|
|
71
|
+
schema = Dsl::Schema.to_json_schema
|
|
72
|
+
puts JSON.pretty_generate(schema)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Output YAML Schema
|
|
76
|
+
def output_yaml_schema
|
|
77
|
+
schema = Dsl::Schema.to_json_schema
|
|
78
|
+
puts YAML.dump(schema.transform_keys(&:to_s))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Output OpenAPI 3.0 specification
|
|
82
|
+
def output_openapi_schema
|
|
83
|
+
spec = Dsl::Schema.to_openapi
|
|
84
|
+
puts JSON.pretty_generate(spec)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
module LanguageOperator
|
|
7
|
+
module CLI
|
|
8
|
+
module Commands
|
|
9
|
+
module System
|
|
10
|
+
# Synthesis template export command
|
|
11
|
+
module SynthesisTemplate
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.class_eval do
|
|
14
|
+
desc 'synthesis-template', 'Export synthesis templates for agent code generation'
|
|
15
|
+
long_desc <<-DESC
|
|
16
|
+
Export the synthesis templates used by the Language Operator to generate
|
|
17
|
+
agent code from natural language instructions.
|
|
18
|
+
|
|
19
|
+
These templates are used by the operator's synthesis engine to convert
|
|
20
|
+
user instructions into executable Ruby DSL code.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
# Export agent synthesis template (default)
|
|
24
|
+
aictl system synthesis-template
|
|
25
|
+
|
|
26
|
+
# Export persona distillation template
|
|
27
|
+
aictl system synthesis-template --type persona
|
|
28
|
+
|
|
29
|
+
# Export as JSON with schema included
|
|
30
|
+
aictl system synthesis-template --format json --with-schema
|
|
31
|
+
|
|
32
|
+
# Export as YAML
|
|
33
|
+
aictl system synthesis-template --format yaml
|
|
34
|
+
|
|
35
|
+
# Validate template syntax
|
|
36
|
+
aictl system synthesis-template --validate
|
|
37
|
+
|
|
38
|
+
# Save to file
|
|
39
|
+
aictl system synthesis-template > agent_synthesis.tmpl
|
|
40
|
+
DESC
|
|
41
|
+
option :format, type: :string, default: 'template', desc: 'Output format (template, json, yaml)'
|
|
42
|
+
option :type, type: :string, default: 'agent', desc: 'Template type (agent, persona)'
|
|
43
|
+
option :with_schema, type: :boolean, default: false, desc: 'Include DSL schema in output'
|
|
44
|
+
option :validate, type: :boolean, default: false, desc: 'Validate template syntax'
|
|
45
|
+
def synthesis_template
|
|
46
|
+
handle_command_error('load template') do
|
|
47
|
+
# Validate type
|
|
48
|
+
template_type = options[:type].downcase
|
|
49
|
+
unless %w[agent persona].include?(template_type)
|
|
50
|
+
Formatters::ProgressFormatter.error("Invalid template type: #{template_type}")
|
|
51
|
+
puts
|
|
52
|
+
puts 'Supported types: agent, persona'
|
|
53
|
+
exit 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Load template
|
|
57
|
+
template_content = load_template(template_type)
|
|
58
|
+
|
|
59
|
+
# Validate if requested
|
|
60
|
+
if options[:validate]
|
|
61
|
+
validation_result = validate_template_content(template_content, template_type)
|
|
62
|
+
|
|
63
|
+
# Display warnings if any
|
|
64
|
+
unless validation_result[:warnings].empty?
|
|
65
|
+
Formatters::ProgressFormatter.warn('Template validation warnings:')
|
|
66
|
+
validation_result[:warnings].each do |warning|
|
|
67
|
+
puts " ⚠ #{warning}"
|
|
68
|
+
end
|
|
69
|
+
puts
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Display errors and exit if validation failed
|
|
73
|
+
if validation_result[:valid]
|
|
74
|
+
Formatters::ProgressFormatter.success('Template validation passed')
|
|
75
|
+
return
|
|
76
|
+
else
|
|
77
|
+
Formatters::ProgressFormatter.error('Template validation failed:')
|
|
78
|
+
validation_result[:errors].each do |error|
|
|
79
|
+
puts " ✗ #{error}"
|
|
80
|
+
end
|
|
81
|
+
exit 1
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Generate output based on format
|
|
86
|
+
format = options[:format].downcase
|
|
87
|
+
case format
|
|
88
|
+
when 'template'
|
|
89
|
+
output_template_format(template_content)
|
|
90
|
+
when 'json'
|
|
91
|
+
output_json_format(template_content, template_type)
|
|
92
|
+
when 'yaml'
|
|
93
|
+
output_yaml_format(template_content, template_type)
|
|
94
|
+
else
|
|
95
|
+
Formatters::ProgressFormatter.error("Invalid format: #{format}")
|
|
96
|
+
puts
|
|
97
|
+
puts 'Supported formats: template, json, yaml'
|
|
98
|
+
exit 1
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# Output raw template format
|
|
106
|
+
def output_template_format(content)
|
|
107
|
+
puts content
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Output JSON format with metadata
|
|
111
|
+
def output_json_format(content, type)
|
|
112
|
+
data = {
|
|
113
|
+
version: Dsl::Schema.version,
|
|
114
|
+
template_type: type,
|
|
115
|
+
template: content
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if options[:with_schema]
|
|
119
|
+
data[:schema] = Dsl::Schema.to_json_schema
|
|
120
|
+
data[:safe_agent_methods] = Dsl::Schema.safe_agent_methods
|
|
121
|
+
data[:safe_tool_methods] = Dsl::Schema.safe_tool_methods
|
|
122
|
+
data[:safe_helper_methods] = Dsl::Schema.safe_helper_methods
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
puts JSON.pretty_generate(data)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Output YAML format with metadata
|
|
129
|
+
def output_yaml_format(content, type)
|
|
130
|
+
data = {
|
|
131
|
+
'version' => Dsl::Schema.version,
|
|
132
|
+
'template_type' => type,
|
|
133
|
+
'template' => content
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if options[:with_schema]
|
|
137
|
+
data['schema'] = Dsl::Schema.to_json_schema.transform_keys(&:to_s)
|
|
138
|
+
data['safe_agent_methods'] = Dsl::Schema.safe_agent_methods
|
|
139
|
+
data['safe_tool_methods'] = Dsl::Schema.safe_tool_methods
|
|
140
|
+
data['safe_helper_methods'] = Dsl::Schema.safe_helper_methods
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
puts YAML.dump(data)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module System
|
|
7
|
+
# Agent code synthesis command
|
|
8
|
+
module Synthesize
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.class_eval do
|
|
11
|
+
desc 'synthesize [INSTRUCTIONS]', 'Synthesize agent code from natural language instructions'
|
|
12
|
+
long_desc <<-DESC
|
|
13
|
+
Synthesize agent code by converting natural language instructions
|
|
14
|
+
into Ruby DSL code without creating an actual agent.
|
|
15
|
+
|
|
16
|
+
This command uses a LanguageModel resource from your cluster to generate
|
|
17
|
+
agent code. If --model is not specified, the first available model will
|
|
18
|
+
be auto-selected.
|
|
19
|
+
|
|
20
|
+
Instructions can be provided either as a command argument or via STDIN.
|
|
21
|
+
If no argument is provided, the command will read from STDIN.
|
|
22
|
+
|
|
23
|
+
This command helps you validate your instructions and understand how the
|
|
24
|
+
synthesis engine interprets them. Use --dry-run to see the prompt that
|
|
25
|
+
would be sent to the LLM, or run without it to generate actual code.
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
# Test with dry-run (show prompt only)
|
|
29
|
+
aictl system synthesize "Monitor GitHub issues daily" --dry-run
|
|
30
|
+
|
|
31
|
+
# Generate code from instructions (auto-selects first available model)
|
|
32
|
+
aictl system synthesize "Send daily reports to Slack"
|
|
33
|
+
|
|
34
|
+
# Use a specific cluster model
|
|
35
|
+
aictl system synthesize "Process webhooks from GitHub" --model my-claude
|
|
36
|
+
|
|
37
|
+
# Output raw code without formatting (useful for piping to files)
|
|
38
|
+
aictl system synthesize "Monitor logs" --raw > agent.rb
|
|
39
|
+
|
|
40
|
+
# Read instructions from STDIN
|
|
41
|
+
cat instructions.txt | aictl system synthesize > agent.rb
|
|
42
|
+
|
|
43
|
+
# Read from STDIN with pipe
|
|
44
|
+
echo "Monitor GitHub issues" | aictl system synthesize --raw
|
|
45
|
+
|
|
46
|
+
# Specify custom agent name and tools
|
|
47
|
+
aictl system synthesize "Process webhooks from GitHub" \\
|
|
48
|
+
--agent-name github-processor \\
|
|
49
|
+
--tools github,slack \\
|
|
50
|
+
--model my-gpt4
|
|
51
|
+
DESC
|
|
52
|
+
option :agent_name, type: :string, default: 'test-agent', desc: 'Name for the test agent'
|
|
53
|
+
option :tools, type: :string, desc: 'Comma-separated list of available tools'
|
|
54
|
+
option :models, type: :string, desc: 'Comma-separated list of available models (from cluster)'
|
|
55
|
+
option :model, type: :string, desc: 'Model to use for synthesis (defaults to first available in cluster)'
|
|
56
|
+
option :dry_run, type: :boolean, default: false, desc: 'Show prompt without calling LLM'
|
|
57
|
+
option :raw, type: :boolean, default: false, desc: 'Output only the raw code without formatting'
|
|
58
|
+
|
|
59
|
+
def synthesize(instructions = nil)
|
|
60
|
+
handle_command_error('synthesize agent') do
|
|
61
|
+
# Read instructions from STDIN if not provided as argument
|
|
62
|
+
if instructions.nil? || instructions.strip.empty?
|
|
63
|
+
if $stdin.tty?
|
|
64
|
+
Formatters::ProgressFormatter.error('No instructions provided')
|
|
65
|
+
puts
|
|
66
|
+
puts 'Provide instructions either as an argument or via STDIN:'
|
|
67
|
+
puts ' aictl system synthesize "Your instructions here"'
|
|
68
|
+
puts ' cat instructions.txt | aictl system synthesize'
|
|
69
|
+
exit 1
|
|
70
|
+
else
|
|
71
|
+
instructions = $stdin.read.strip
|
|
72
|
+
if instructions.empty?
|
|
73
|
+
Formatters::ProgressFormatter.error('No instructions provided')
|
|
74
|
+
puts
|
|
75
|
+
puts 'Provide instructions either as an argument or via STDIN:'
|
|
76
|
+
puts ' aictl system synthesize "Your instructions here"'
|
|
77
|
+
puts ' cat instructions.txt | aictl system synthesize'
|
|
78
|
+
exit 1
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
# Select model to use for synthesis
|
|
83
|
+
selected_model = select_synthesis_model
|
|
84
|
+
|
|
85
|
+
# Load synthesis template
|
|
86
|
+
template_content = load_bundled_template('agent')
|
|
87
|
+
|
|
88
|
+
# Detect temporal intent from instructions
|
|
89
|
+
temporal_intent = detect_temporal_intent(instructions)
|
|
90
|
+
|
|
91
|
+
# Prepare template data
|
|
92
|
+
template_data = {
|
|
93
|
+
'Instructions' => instructions,
|
|
94
|
+
'AgentName' => options[:agent_name],
|
|
95
|
+
'ToolsList' => format_tools_list(options[:tools]),
|
|
96
|
+
'ModelsList' => format_models_list(options[:models]),
|
|
97
|
+
'TemporalIntent' => temporal_intent,
|
|
98
|
+
'PersonaSection' => '',
|
|
99
|
+
'ScheduleSection' => temporal_intent == 'scheduled' ? ' schedule "0 */1 * * *" # Example hourly schedule' : '',
|
|
100
|
+
'ScheduleRules' => temporal_intent == 'scheduled' ? "\n2. Include schedule with cron expression\n3. Set mode to :scheduled\n4. " : "\n2. ",
|
|
101
|
+
'ConstraintsSection' => '',
|
|
102
|
+
'ErrorContext' => nil
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Render template (Go-style template syntax)
|
|
106
|
+
rendered_prompt = render_go_template(template_content, template_data)
|
|
107
|
+
|
|
108
|
+
if options[:dry_run]
|
|
109
|
+
# Show the prompt that would be sent
|
|
110
|
+
puts 'Synthesis Prompt Preview'
|
|
111
|
+
puts '=' * 80
|
|
112
|
+
puts
|
|
113
|
+
puts rendered_prompt
|
|
114
|
+
puts
|
|
115
|
+
puts '=' * 80
|
|
116
|
+
Formatters::ProgressFormatter.success('Dry-run complete - prompt displayed above')
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Call LLM to generate code (no output - just do it)
|
|
121
|
+
llm_response = call_llm_for_synthesis(rendered_prompt, selected_model)
|
|
122
|
+
|
|
123
|
+
# Extract Ruby code from response
|
|
124
|
+
generated_code = extract_ruby_code(llm_response)
|
|
125
|
+
|
|
126
|
+
if generated_code.nil?
|
|
127
|
+
Formatters::ProgressFormatter.error('Failed to extract Ruby code from LLM response')
|
|
128
|
+
puts
|
|
129
|
+
puts 'LLM Response:'
|
|
130
|
+
puts llm_response
|
|
131
|
+
exit 1
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Handle raw output
|
|
135
|
+
if options[:raw]
|
|
136
|
+
puts generated_code
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Display formatted code
|
|
141
|
+
highlighted_code = highlight_ruby_code(generated_code)
|
|
142
|
+
|
|
143
|
+
puts highlighted_code
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
# Detect temporal intent from instructions (scheduled vs autonomous)
|
|
150
|
+
def detect_temporal_intent(instructions)
|
|
151
|
+
temporal_keywords = {
|
|
152
|
+
scheduled: %w[daily weekly hourly monthly schedule cron every day week hour minute],
|
|
153
|
+
autonomous: %w[monitor watch continuously constantly always loop]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
instructions_lower = instructions.downcase
|
|
157
|
+
|
|
158
|
+
# Check for scheduled keywords
|
|
159
|
+
scheduled_matches = temporal_keywords[:scheduled].count { |keyword| instructions_lower.include?(keyword) }
|
|
160
|
+
autonomous_matches = temporal_keywords[:autonomous].count { |keyword| instructions_lower.include?(keyword) }
|
|
161
|
+
|
|
162
|
+
scheduled_matches > autonomous_matches ? 'scheduled' : 'autonomous'
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Format tools list for template
|
|
166
|
+
def format_tools_list(tools_str)
|
|
167
|
+
return 'No tools specified' if tools_str.nil? || tools_str.strip.empty?
|
|
168
|
+
|
|
169
|
+
tools = tools_str.split(',').map(&:strip)
|
|
170
|
+
tools.map { |tool| "- #{tool}" }.join("\n")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Format models list for template
|
|
174
|
+
def format_models_list(models_str)
|
|
175
|
+
# If not specified, try to detect from cluster
|
|
176
|
+
if models_str.nil? || models_str.strip.empty?
|
|
177
|
+
models = detect_available_models
|
|
178
|
+
return models.map { |model| "- #{model}" }.join("\n") unless models.empty?
|
|
179
|
+
|
|
180
|
+
return 'No models available (run: aictl model list)'
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
models = models_str.split(',').map(&:strip)
|
|
184
|
+
models.map { |model| "- #{model}" }.join("\n")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Detect available models from cluster
|
|
188
|
+
def detect_available_models
|
|
189
|
+
models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
|
|
190
|
+
models.map { |m| m.dig('metadata', 'name') }
|
|
191
|
+
rescue StandardError => e
|
|
192
|
+
Formatters::ProgressFormatter.error("Failed to list models from cluster: #{e.message}")
|
|
193
|
+
[]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Select model to use for synthesis
|
|
197
|
+
def select_synthesis_model
|
|
198
|
+
# If --model option specified, use it
|
|
199
|
+
return options[:model] if options[:model]
|
|
200
|
+
|
|
201
|
+
# Otherwise, auto-select from available cluster models
|
|
202
|
+
available_models = detect_available_models
|
|
203
|
+
|
|
204
|
+
if available_models.empty?
|
|
205
|
+
Formatters::ProgressFormatter.error('No models available in cluster')
|
|
206
|
+
puts
|
|
207
|
+
puts 'Please create a model first:'
|
|
208
|
+
puts ' aictl model create'
|
|
209
|
+
puts
|
|
210
|
+
puts 'Or list existing models:'
|
|
211
|
+
puts ' aictl model list'
|
|
212
|
+
exit 1
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Auto-select first available model (silently)
|
|
216
|
+
available_models.first
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module CLI
|
|
5
|
+
module Commands
|
|
6
|
+
module System
|
|
7
|
+
# Template validation command
|
|
8
|
+
module ValidateTemplate
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.class_eval do
|
|
11
|
+
desc 'validate_template', 'Validate synthesis template against DSL schema'
|
|
12
|
+
long_desc <<-DESC
|
|
13
|
+
Validate a synthesis template file against the DSL schema.
|
|
14
|
+
|
|
15
|
+
Extracts Ruby code examples from the template and validates each example
|
|
16
|
+
against the Language Operator Agent DSL schema. Checks for dangerous
|
|
17
|
+
methods, syntax errors, and compliance with safe coding practices.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
# Validate a custom template file
|
|
21
|
+
aictl system validate_template --template /path/to/template.tmpl
|
|
22
|
+
|
|
23
|
+
# Validate the bundled agent template (default)
|
|
24
|
+
aictl system validate_template
|
|
25
|
+
|
|
26
|
+
# Validate the bundled persona template
|
|
27
|
+
aictl system validate_template --type persona
|
|
28
|
+
|
|
29
|
+
# Verbose output with all violations
|
|
30
|
+
aictl system validate_template --template mytemplate.tmpl --verbose
|
|
31
|
+
DESC
|
|
32
|
+
option :template, type: :string, desc: 'Path to template file (defaults to bundled template)'
|
|
33
|
+
option :type, type: :string, default: 'agent', desc: 'Template type if using bundled template (agent, persona)'
|
|
34
|
+
option :verbose, type: :boolean, default: false, desc: 'Show detailed violation information'
|
|
35
|
+
def validate_template
|
|
36
|
+
handle_command_error('validate template') do
|
|
37
|
+
# Determine template source
|
|
38
|
+
if options[:template]
|
|
39
|
+
# Load custom template from file
|
|
40
|
+
unless File.exist?(options[:template])
|
|
41
|
+
Formatters::ProgressFormatter.error("Template file not found: #{options[:template]}")
|
|
42
|
+
exit 1
|
|
43
|
+
end
|
|
44
|
+
template_content = File.read(options[:template])
|
|
45
|
+
template_name = File.basename(options[:template])
|
|
46
|
+
else
|
|
47
|
+
# Load bundled template
|
|
48
|
+
template_type = options[:type].downcase
|
|
49
|
+
unless %w[agent persona].include?(template_type)
|
|
50
|
+
Formatters::ProgressFormatter.error("Invalid template type: #{template_type}")
|
|
51
|
+
puts
|
|
52
|
+
puts 'Supported types: agent, persona'
|
|
53
|
+
exit 1
|
|
54
|
+
end
|
|
55
|
+
template_content = load_bundled_template(template_type)
|
|
56
|
+
template_name = "bundled #{template_type} template"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Display header
|
|
60
|
+
puts "Validating template: #{template_name}"
|
|
61
|
+
puts '=' * 60
|
|
62
|
+
puts
|
|
63
|
+
|
|
64
|
+
# Extract code examples
|
|
65
|
+
code_examples = extract_code_examples(template_content)
|
|
66
|
+
|
|
67
|
+
if code_examples.empty?
|
|
68
|
+
Formatters::ProgressFormatter.warn('No Ruby code examples found in template')
|
|
69
|
+
puts
|
|
70
|
+
puts 'Templates should contain Ruby code blocks like:'
|
|
71
|
+
puts '```ruby'
|
|
72
|
+
puts 'agent "my-agent" do'
|
|
73
|
+
puts ' # ...'
|
|
74
|
+
puts 'end'
|
|
75
|
+
puts '```'
|
|
76
|
+
exit 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
puts "Found #{code_examples.size} code example(s)"
|
|
80
|
+
puts
|
|
81
|
+
|
|
82
|
+
# Validate each example
|
|
83
|
+
all_valid = true
|
|
84
|
+
code_examples.each_with_index do |example, idx|
|
|
85
|
+
puts "Example #{idx + 1} (starting at line #{example[:start_line]}):"
|
|
86
|
+
puts '-' * 40
|
|
87
|
+
|
|
88
|
+
result = validate_code_against_schema(example[:code])
|
|
89
|
+
|
|
90
|
+
if result[:valid] && result[:warnings].empty?
|
|
91
|
+
Formatters::ProgressFormatter.success('Valid - No issues found')
|
|
92
|
+
elsif result[:valid]
|
|
93
|
+
Formatters::ProgressFormatter.success('Valid - With warnings')
|
|
94
|
+
result[:warnings].each do |warn|
|
|
95
|
+
line = example[:start_line] + (warn[:location] || 0)
|
|
96
|
+
puts " ⚠ Line #{line}: #{warn[:message]}"
|
|
97
|
+
end
|
|
98
|
+
else
|
|
99
|
+
Formatters::ProgressFormatter.error('Invalid - Violations detected')
|
|
100
|
+
result[:errors].each do |err|
|
|
101
|
+
line = example[:start_line] + (err[:location] || 0)
|
|
102
|
+
puts " ✗ Line #{line}: #{err[:message]}"
|
|
103
|
+
puts " Type: #{err[:type]}" if options[:verbose]
|
|
104
|
+
end
|
|
105
|
+
all_valid = false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
puts
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Final summary
|
|
112
|
+
puts '=' * 60
|
|
113
|
+
if all_valid
|
|
114
|
+
Formatters::ProgressFormatter.success('All examples are valid')
|
|
115
|
+
exit 0
|
|
116
|
+
else
|
|
117
|
+
Formatters::ProgressFormatter.error('Validation failed')
|
|
118
|
+
puts
|
|
119
|
+
puts 'Fix the violations above and run validation again.'
|
|
120
|
+
exit 1
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|