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
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'tty-prompt'
|
|
4
|
-
require 'pastel'
|
|
5
|
-
require_relative '../cli/formatters/progress_formatter'
|
|
6
|
-
|
|
7
|
-
module LanguageOperator
|
|
8
|
-
module Ux
|
|
9
|
-
# Base class for interactive user experience flows
|
|
10
|
-
#
|
|
11
|
-
# Provides common infrastructure for TTY-based interactive wizards
|
|
12
|
-
# including cluster context validation and standard UI helpers.
|
|
13
|
-
#
|
|
14
|
-
# @example Creating a new UX flow
|
|
15
|
-
# class CreateModel < Base
|
|
16
|
-
# def execute
|
|
17
|
-
# show_welcome
|
|
18
|
-
# # ... flow logic
|
|
19
|
-
# end
|
|
20
|
-
# end
|
|
21
|
-
#
|
|
22
|
-
# @example Using a UX flow
|
|
23
|
-
# Ux::CreateModel.execute(ctx)
|
|
24
|
-
#
|
|
25
|
-
class Base
|
|
26
|
-
attr_reader :prompt, :pastel, :ctx
|
|
27
|
-
|
|
28
|
-
# Initialize the UX flow
|
|
29
|
-
#
|
|
30
|
-
# @param ctx [Object, nil] Cluster context (required unless overridden)
|
|
31
|
-
def initialize(ctx = nil)
|
|
32
|
-
@prompt = TTY::Prompt.new
|
|
33
|
-
@pastel = Pastel.new
|
|
34
|
-
@ctx = ctx
|
|
35
|
-
|
|
36
|
-
validate_cluster_context! if requires_cluster?
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Execute the UX flow
|
|
40
|
-
#
|
|
41
|
-
# Subclasses must override this method to implement their flow logic.
|
|
42
|
-
#
|
|
43
|
-
# @raise [NotImplementedError] if not overridden by subclass
|
|
44
|
-
def execute
|
|
45
|
-
raise NotImplementedError, "#{self.class.name} must implement #execute"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Execute the UX flow as a class method
|
|
49
|
-
#
|
|
50
|
-
# @param ctx [Object, nil] Cluster context
|
|
51
|
-
# @return [Object] Result of execute method
|
|
52
|
-
def self.execute(ctx = nil)
|
|
53
|
-
new(ctx).execute
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
# Whether this flow requires a cluster context
|
|
59
|
-
#
|
|
60
|
-
# Subclasses can override to disable cluster requirement (e.g., Quickstart).
|
|
61
|
-
#
|
|
62
|
-
# @return [Boolean] true if cluster is required
|
|
63
|
-
def requires_cluster?
|
|
64
|
-
true
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Validate that a cluster context is available
|
|
68
|
-
#
|
|
69
|
-
# Exits with error if cluster is required but not provided.
|
|
70
|
-
def validate_cluster_context!
|
|
71
|
-
return unless requires_cluster?
|
|
72
|
-
return if ctx
|
|
73
|
-
|
|
74
|
-
CLI::Formatters::ProgressFormatter.error(
|
|
75
|
-
'No cluster selected. Run "aictl cluster add" first.'
|
|
76
|
-
)
|
|
77
|
-
exit 1
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# Ux::Concerns
|
|
2
|
-
|
|
3
|
-
Reusable mixins for interactive UX flows.
|
|
4
|
-
|
|
5
|
-
## Available Concerns
|
|
6
|
-
|
|
7
|
-
### Headings
|
|
8
|
-
|
|
9
|
-
Provides consistent formatting for headings, step indicators, and banners.
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class MyFlow < Ux::Base
|
|
13
|
-
include Concerns::Headings
|
|
14
|
-
|
|
15
|
-
def execute
|
|
16
|
-
heading('Welcome!', emoji: '🎉')
|
|
17
|
-
step_heading(1, 3, 'First Step')
|
|
18
|
-
subheading('Configuration')
|
|
19
|
-
separator
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**Methods:**
|
|
25
|
-
- `heading(text, emoji: nil, width: 50)` - Display prominent heading with border
|
|
26
|
-
- `step_heading(current, total, title, width: 50)` - Display step indicator
|
|
27
|
-
- `subheading(text)` - Display simple subheading
|
|
28
|
-
- `separator(width: 50, char: '─')` - Display separator line
|
|
29
|
-
- `section(title, description: nil)` - Display section header with description
|
|
30
|
-
|
|
31
|
-
### ProviderHelpers
|
|
32
|
-
|
|
33
|
-
Common operations for LLM provider integration.
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
class MyFlow < Ux::Base
|
|
37
|
-
include Concerns::ProviderHelpers
|
|
38
|
-
|
|
39
|
-
def execute
|
|
40
|
-
result = test_provider_connection(:anthropic, api_key: 'sk-...')
|
|
41
|
-
models = fetch_provider_models(:openai, api_key: 'sk-...')
|
|
42
|
-
info = provider_info(:anthropic)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Methods:**
|
|
48
|
-
- `test_provider_connection(provider, api_key:, endpoint:)` - Test connection to provider
|
|
49
|
-
- `fetch_provider_models(provider, api_key:, endpoint:)` - Fetch available models
|
|
50
|
-
- `provider_info(provider)` - Get provider display info and documentation URLs
|
|
51
|
-
|
|
52
|
-
**Supported Providers:**
|
|
53
|
-
- `:anthropic` - Anthropic (Claude)
|
|
54
|
-
- `:openai` - OpenAI (GPT)
|
|
55
|
-
- `:openai_compatible` - OpenAI-compatible endpoints (Ollama, vLLM, LM Studio, etc)
|
|
56
|
-
|
|
57
|
-
### InputValidation
|
|
58
|
-
|
|
59
|
-
Common input validation and prompting helpers.
|
|
60
|
-
|
|
61
|
-
```ruby
|
|
62
|
-
class MyFlow < Ux::Base
|
|
63
|
-
include Concerns::InputValidation
|
|
64
|
-
|
|
65
|
-
def execute
|
|
66
|
-
url = ask_url('Enter endpoint URL:')
|
|
67
|
-
name = ask_k8s_name('Resource name:', default: 'my-resource')
|
|
68
|
-
email = ask_email('Your email:')
|
|
69
|
-
api_key = ask_secret('API key:')
|
|
70
|
-
port = ask_port('Port:', default: 8080)
|
|
71
|
-
confirmed = ask_yes_no('Continue?', default: true)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Methods:**
|
|
77
|
-
- `ask_url(question, default:, required:)` - Prompt for URL with validation
|
|
78
|
-
- `ask_k8s_name(question, default:)` - Prompt for Kubernetes resource name
|
|
79
|
-
- `ask_email(question, default:)` - Prompt for email address
|
|
80
|
-
- `ask_secret(question, required:)` - Prompt for masked input (API keys, passwords)
|
|
81
|
-
- `ask_port(question, default:)` - Prompt for port number (1-65535)
|
|
82
|
-
- `ask_yes_no(question, default:)` - Prompt for yes/no confirmation
|
|
83
|
-
- `ask_select(question, choices, per_page:)` - Prompt for selection from list
|
|
84
|
-
- `validate_k8s_name(name)` - Validate and normalize Kubernetes name
|
|
85
|
-
- `validate_url(url)` - Validate URL format
|
|
86
|
-
|
|
87
|
-
## Usage Guidelines
|
|
88
|
-
|
|
89
|
-
### When to Use Concerns
|
|
90
|
-
|
|
91
|
-
✅ **DO use concerns for:**
|
|
92
|
-
- Common formatting patterns used across multiple flows
|
|
93
|
-
- Repeated validation logic
|
|
94
|
-
- Shared provider/API operations
|
|
95
|
-
- Reusable UI components
|
|
96
|
-
|
|
97
|
-
❌ **DON'T use concerns for:**
|
|
98
|
-
- Flow-specific business logic
|
|
99
|
-
- One-off operations
|
|
100
|
-
- Complex state management
|
|
101
|
-
|
|
102
|
-
### Naming Convention
|
|
103
|
-
|
|
104
|
-
Concerns should be:
|
|
105
|
-
- Named as adjectives or capabilities (e.g., `Headings`, `ProviderHelpers`)
|
|
106
|
-
- Focused on a single responsibility
|
|
107
|
-
- Well-documented with examples
|
|
108
|
-
|
|
109
|
-
### Testing
|
|
110
|
-
|
|
111
|
-
Each concern should have corresponding specs in `spec/language_operator/ux/concerns/`.
|
|
112
|
-
|
|
113
|
-
## Creating New Concerns
|
|
114
|
-
|
|
115
|
-
1. Create file in `lib/language_operator/ux/concerns/my_concern.rb`
|
|
116
|
-
2. Define module under `LanguageOperator::Ux::Concerns`
|
|
117
|
-
3. Add YARD documentation with examples
|
|
118
|
-
4. Include in flows via `include Concerns::MyConcern`
|
|
119
|
-
5. Add tests in `spec/language_operator/ux/concerns/my_concern_spec.rb`
|
|
120
|
-
6. Update this README
|
|
121
|
-
|
|
122
|
-
## Example: Creating a New Concern
|
|
123
|
-
|
|
124
|
-
```ruby
|
|
125
|
-
# lib/language_operator/ux/concerns/kubernetes_helpers.rb
|
|
126
|
-
module LanguageOperator
|
|
127
|
-
module Ux
|
|
128
|
-
module Concerns
|
|
129
|
-
# Mixin for Kubernetes resource operations
|
|
130
|
-
module KubernetesHelpers
|
|
131
|
-
def resource_exists?(type, name)
|
|
132
|
-
ctx.client.get_resource(type, name, ctx.namespace)
|
|
133
|
-
true
|
|
134
|
-
rescue K8s::Error::NotFound
|
|
135
|
-
false
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Then use it:
|
|
144
|
-
|
|
145
|
-
```ruby
|
|
146
|
-
class CreateAgent < Base
|
|
147
|
-
include Concerns::KubernetesHelpers
|
|
148
|
-
|
|
149
|
-
def execute
|
|
150
|
-
if resource_exists?('LanguageAgent', 'my-agent')
|
|
151
|
-
puts "Agent already exists!"
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
```
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module LanguageOperator
|
|
4
|
-
module Ux
|
|
5
|
-
module Concerns
|
|
6
|
-
# Mixin for consistent heading and banner formatting in UX flows
|
|
7
|
-
#
|
|
8
|
-
# Provides helpers for creating section headers, step indicators,
|
|
9
|
-
# welcome banners, and separator lines.
|
|
10
|
-
#
|
|
11
|
-
# @example
|
|
12
|
-
# class MyFlow < Base
|
|
13
|
-
# include Concerns::Headings
|
|
14
|
-
#
|
|
15
|
-
# def execute
|
|
16
|
-
# heading('Welcome to My Flow', emoji: '🎉')
|
|
17
|
-
# step_heading(1, 5, 'First Step')
|
|
18
|
-
# # ... flow logic
|
|
19
|
-
# end
|
|
20
|
-
# end
|
|
21
|
-
module Headings
|
|
22
|
-
def title(text)
|
|
23
|
-
puts
|
|
24
|
-
puts pastel.bold.green("LANGUAGE OPERATOR v#{LanguageOperator::VERSION}")
|
|
25
|
-
puts pastel.dim("↪ #{text}")
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Display a prominent heading with optional emoji
|
|
29
|
-
#
|
|
30
|
-
# @param text [String] The heading text
|
|
31
|
-
# @param emoji [String, nil] Optional emoji to display
|
|
32
|
-
# @param width [Integer] Width of the banner (default: 50)
|
|
33
|
-
def heading(text, emoji: nil, width: 50)
|
|
34
|
-
border = "╭#{'─' * (width - 2)}╮"
|
|
35
|
-
bottom = "╰#{'─' * (width - 2)}╯"
|
|
36
|
-
|
|
37
|
-
display_text = emoji ? "#{text} #{emoji}" : text
|
|
38
|
-
padding = width - display_text.length - 4
|
|
39
|
-
|
|
40
|
-
puts
|
|
41
|
-
puts pastel.cyan(border)
|
|
42
|
-
puts "#{pastel.cyan('│')} #{display_text}#{' ' * padding}#{pastel.cyan('│')}"
|
|
43
|
-
puts pastel.cyan(bottom)
|
|
44
|
-
puts
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Display a step heading with step number
|
|
48
|
-
#
|
|
49
|
-
# @param current [Integer] Current step number
|
|
50
|
-
# @param total [Integer] Total number of steps
|
|
51
|
-
# @param title [String] Step title
|
|
52
|
-
# @param width [Integer] Width of the separator line (default: 50)
|
|
53
|
-
def step_heading(current, total, title, width: 50)
|
|
54
|
-
puts
|
|
55
|
-
puts '─' * width
|
|
56
|
-
puts pastel.cyan("Step #{current}/#{total}: #{title}")
|
|
57
|
-
puts '─' * width
|
|
58
|
-
puts
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Display a simple subheading
|
|
62
|
-
#
|
|
63
|
-
# @param text [String] The subheading text
|
|
64
|
-
def subheading(text)
|
|
65
|
-
puts
|
|
66
|
-
puts pastel.bold(text)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Display a separator line
|
|
70
|
-
#
|
|
71
|
-
# @param width [Integer] Width of the line (default: 50)
|
|
72
|
-
# @param char [String] Character to use for the line (default: '─')
|
|
73
|
-
def separator(width: 50, char: '─')
|
|
74
|
-
puts char * width
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Display a section header with description
|
|
78
|
-
#
|
|
79
|
-
# @param title [String] Section title
|
|
80
|
-
# @param description [String, nil] Optional description
|
|
81
|
-
def section(title, description: nil)
|
|
82
|
-
puts
|
|
83
|
-
puts pastel.cyan.bold(title)
|
|
84
|
-
puts pastel.dim(description) if description
|
|
85
|
-
puts
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
require_relative 'concerns/headings'
|
|
5
|
-
require_relative '../cli/helpers/schedule_builder'
|
|
6
|
-
|
|
7
|
-
module LanguageOperator
|
|
8
|
-
module Ux
|
|
9
|
-
# Interactive flow for creating agents
|
|
10
|
-
#
|
|
11
|
-
# Guides users through task description, scheduling, and tool configuration.
|
|
12
|
-
#
|
|
13
|
-
# @example
|
|
14
|
-
# description = Ux::CreateAgent.execute(ctx)
|
|
15
|
-
#
|
|
16
|
-
# rubocop:disable Metrics/ClassLength, Metrics/AbcSize
|
|
17
|
-
class CreateAgent < Base
|
|
18
|
-
include Concerns::Headings
|
|
19
|
-
|
|
20
|
-
# Execute the agent creation flow
|
|
21
|
-
#
|
|
22
|
-
# @return [String, nil] Generated description or nil if cancelled
|
|
23
|
-
def execute
|
|
24
|
-
show_welcome
|
|
25
|
-
|
|
26
|
-
# Step 1: Get task description
|
|
27
|
-
task = ask_task_description
|
|
28
|
-
return nil unless task
|
|
29
|
-
|
|
30
|
-
# Step 2: Determine schedule
|
|
31
|
-
schedule_info = ask_schedule
|
|
32
|
-
return nil unless schedule_info
|
|
33
|
-
|
|
34
|
-
# Step 3: Tool detection and configuration
|
|
35
|
-
tools_config = configure_tools(task)
|
|
36
|
-
|
|
37
|
-
# Step 4: Preview and confirm
|
|
38
|
-
description = build_description(task, schedule_info, tools_config)
|
|
39
|
-
|
|
40
|
-
show_preview(description, schedule_info, tools_config)
|
|
41
|
-
|
|
42
|
-
return nil unless confirm_creation?
|
|
43
|
-
|
|
44
|
-
description
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def show_welcome
|
|
50
|
-
heading('Model quick start')
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def ask_task_description
|
|
54
|
-
prompt.ask('What should your agent do?') do |q|
|
|
55
|
-
q.required true
|
|
56
|
-
q.validate(/\w+/)
|
|
57
|
-
q.messages[:valid?] = 'Please describe a task (cannot be empty)'
|
|
58
|
-
end
|
|
59
|
-
rescue TTY::Reader::InputInterrupt
|
|
60
|
-
CLI::Formatters::ProgressFormatter.error('Cancelled')
|
|
61
|
-
nil
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def ask_schedule
|
|
65
|
-
puts
|
|
66
|
-
schedule_type = prompt.select('How often should it run?') do |menu|
|
|
67
|
-
menu.choice 'Every day at a specific time', :daily
|
|
68
|
-
menu.choice 'Every few minutes/hours', :interval
|
|
69
|
-
menu.choice 'Continuously (whenever something changes)', :continuous
|
|
70
|
-
menu.choice 'Only when I trigger it manually', :manual
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
case schedule_type
|
|
74
|
-
when :daily
|
|
75
|
-
ask_daily_schedule
|
|
76
|
-
when :interval
|
|
77
|
-
ask_interval_schedule
|
|
78
|
-
when :continuous
|
|
79
|
-
{ type: :continuous, description: 'continuously' }
|
|
80
|
-
when :manual
|
|
81
|
-
{ type: :manual, description: 'on manual trigger' }
|
|
82
|
-
end
|
|
83
|
-
rescue TTY::Reader::InputInterrupt
|
|
84
|
-
CLI::Formatters::ProgressFormatter.error('Cancelled')
|
|
85
|
-
nil
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def ask_daily_schedule
|
|
89
|
-
puts
|
|
90
|
-
time_input = prompt.ask('What time each day? (e.g., 4pm, 9:30am, 16:00):') do |q|
|
|
91
|
-
q.required true
|
|
92
|
-
q.validate(lambda do |input|
|
|
93
|
-
CLI::Helpers::ScheduleBuilder.parse_time(input)
|
|
94
|
-
true
|
|
95
|
-
rescue ArgumentError
|
|
96
|
-
false
|
|
97
|
-
end)
|
|
98
|
-
q.messages[:valid?] = 'Invalid time format. Try: 4pm, 9:30am, or 16:00'
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
time_24h = CLI::Helpers::ScheduleBuilder.parse_time(time_input)
|
|
102
|
-
cron = CLI::Helpers::ScheduleBuilder.daily_cron(time_24h)
|
|
103
|
-
|
|
104
|
-
{
|
|
105
|
-
type: :daily,
|
|
106
|
-
time: time_24h,
|
|
107
|
-
cron: cron,
|
|
108
|
-
description: "daily at #{time_input}"
|
|
109
|
-
}
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def ask_interval_schedule
|
|
113
|
-
puts
|
|
114
|
-
interval = prompt.ask('How often?', convert: :int) do |q|
|
|
115
|
-
q.required true
|
|
116
|
-
q.validate(->(v) { v.to_i.positive? })
|
|
117
|
-
q.messages[:valid?] = 'Please enter a positive number'
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
unit = prompt.select('Minutes, hours, or days?', %w[minutes hours days])
|
|
121
|
-
|
|
122
|
-
cron = CLI::Helpers::ScheduleBuilder.interval_cron(interval, unit)
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
type: :interval,
|
|
126
|
-
interval: interval,
|
|
127
|
-
unit: unit,
|
|
128
|
-
cron: cron,
|
|
129
|
-
description: "every #{interval} #{unit}"
|
|
130
|
-
}
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def configure_tools(task_description)
|
|
134
|
-
detected = detect_tools(task_description)
|
|
135
|
-
config = {}
|
|
136
|
-
|
|
137
|
-
return config if detected.empty?
|
|
138
|
-
|
|
139
|
-
puts
|
|
140
|
-
puts "I detected these tools: #{pastel.yellow(detected.join(', '))}"
|
|
141
|
-
puts
|
|
142
|
-
|
|
143
|
-
detected.each do |tool|
|
|
144
|
-
case tool
|
|
145
|
-
when 'email'
|
|
146
|
-
config[:email] = ask_email_config
|
|
147
|
-
when 'google-sheets', 'spreadsheet'
|
|
148
|
-
config[:spreadsheet] = ask_spreadsheet_config
|
|
149
|
-
when 'slack'
|
|
150
|
-
config[:slack] = ask_slack_config
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
config[:tools] = detected
|
|
155
|
-
config
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def detect_tools(description)
|
|
159
|
-
tools = []
|
|
160
|
-
text = description.downcase
|
|
161
|
-
|
|
162
|
-
tools << 'email' if text.match?(/email|mail|send.*message/i)
|
|
163
|
-
tools << 'google-sheets' if text.match?(/spreadsheet|sheet|excel|csv/i)
|
|
164
|
-
tools << 'slack' if text.match?(/slack/i)
|
|
165
|
-
tools << 'github' if text.match?(/github|git|repo/i)
|
|
166
|
-
|
|
167
|
-
tools
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def ask_email_config
|
|
171
|
-
email = prompt.ask('Your email for notifications:') do |q|
|
|
172
|
-
q.required true
|
|
173
|
-
q.validate(/\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i)
|
|
174
|
-
q.messages[:valid?] = 'Please enter a valid email address'
|
|
175
|
-
end
|
|
176
|
-
{ address: email }
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def ask_spreadsheet_config
|
|
180
|
-
url = prompt.ask('Spreadsheet URL:') do |q|
|
|
181
|
-
q.required true
|
|
182
|
-
q.validate(%r{^https?://}i)
|
|
183
|
-
q.messages[:valid?] = 'Please enter a valid URL'
|
|
184
|
-
end
|
|
185
|
-
{ url: url }
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def ask_slack_config
|
|
189
|
-
channel = prompt.ask('Slack channel (e.g., #general):') do |q|
|
|
190
|
-
q.required true
|
|
191
|
-
end
|
|
192
|
-
{ channel: channel }
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def build_description(task, schedule_info, tools_config)
|
|
196
|
-
parts = [task]
|
|
197
|
-
|
|
198
|
-
# Add schedule information
|
|
199
|
-
case schedule_info[:type]
|
|
200
|
-
when :daily
|
|
201
|
-
parts << schedule_info[:description]
|
|
202
|
-
when :interval
|
|
203
|
-
parts << schedule_info[:description]
|
|
204
|
-
when :continuous
|
|
205
|
-
# "continuously" might already be implied in task
|
|
206
|
-
parts << 'continuously' unless task.downcase.include?('continuous')
|
|
207
|
-
when :manual
|
|
208
|
-
# Manual trigger doesn't need to be in description
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Add tool-specific details
|
|
212
|
-
parts << "and email me at #{tools_config[:email][:address]}" if tools_config[:email]
|
|
213
|
-
|
|
214
|
-
if tools_config[:spreadsheet] && !task.include?('http')
|
|
215
|
-
# Replace generic "spreadsheet" with specific URL if not already present
|
|
216
|
-
parts << "using spreadsheet at #{tools_config[:spreadsheet][:url]}"
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
parts << "and send results to #{tools_config[:slack][:channel]}" if tools_config[:slack]
|
|
220
|
-
|
|
221
|
-
parts.join(' ')
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def show_preview(description, schedule_info, tools_config)
|
|
225
|
-
puts
|
|
226
|
-
puts pastel.cyan('╭─ Preview ──────────────────────────────╮')
|
|
227
|
-
puts '│'
|
|
228
|
-
puts "│ #{pastel.bold('Task:')} #{description}"
|
|
229
|
-
|
|
230
|
-
if schedule_info[:type] == :manual
|
|
231
|
-
puts "│ #{pastel.bold('Mode:')} Manual trigger"
|
|
232
|
-
else
|
|
233
|
-
schedule_text = schedule_info[:description] || 'on demand'
|
|
234
|
-
puts "│ #{pastel.bold('Schedule:')} #{schedule_text}"
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
puts "│ #{pastel.bold('Cron:')} #{pastel.dim(schedule_info[:cron])}" if schedule_info[:cron]
|
|
238
|
-
|
|
239
|
-
puts "│ #{pastel.bold('Tools:')} #{tools_config[:tools].join(', ')}" if tools_config[:tools]&.any?
|
|
240
|
-
|
|
241
|
-
puts '│'
|
|
242
|
-
puts pastel.cyan('╰────────────────────────────────────────╯')
|
|
243
|
-
puts
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def confirm_creation?
|
|
247
|
-
puts
|
|
248
|
-
prompt.yes?('Create this agent?')
|
|
249
|
-
rescue TTY::Reader::InputInterrupt
|
|
250
|
-
false
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
# rubocop:enable Metrics/ClassLength, Metrics/AbcSize
|
|
254
|
-
end
|
|
255
|
-
end
|