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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/persona.md +9 -0
  3. data/.claude/commands/task.md +46 -1
  4. data/.rubocop.yml +13 -0
  5. data/.rubocop_custom/use_ux_helper.rb +44 -0
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile.lock +12 -1
  8. data/Makefile +26 -7
  9. data/Makefile.common +50 -0
  10. data/bin/aictl +8 -1
  11. data/components/agent/Gemfile +1 -1
  12. data/components/agent/bin/langop-agent +7 -0
  13. data/docs/README.md +58 -0
  14. data/docs/{dsl/best-practices.md → best-practices.md} +4 -4
  15. data/docs/cli-reference.md +274 -0
  16. data/docs/{dsl/constraints.md → constraints.md} +5 -5
  17. data/docs/how-agents-work.md +156 -0
  18. data/docs/installation.md +218 -0
  19. data/docs/quickstart.md +299 -0
  20. data/docs/understanding-generated-code.md +265 -0
  21. data/docs/using-tools.md +457 -0
  22. data/docs/webhooks.md +509 -0
  23. data/examples/ux_helpers_demo.rb +296 -0
  24. data/lib/language_operator/agent/base.rb +11 -1
  25. data/lib/language_operator/agent/executor.rb +23 -6
  26. data/lib/language_operator/agent/safety/safe_executor.rb +41 -39
  27. data/lib/language_operator/agent/task_executor.rb +346 -63
  28. data/lib/language_operator/agent/web_server.rb +110 -14
  29. data/lib/language_operator/agent/webhook_authenticator.rb +39 -5
  30. data/lib/language_operator/agent.rb +88 -2
  31. data/lib/language_operator/cli/base_command.rb +17 -11
  32. data/lib/language_operator/cli/command_loader.rb +72 -0
  33. data/lib/language_operator/cli/commands/agent/base.rb +837 -0
  34. data/lib/language_operator/cli/commands/agent/code_operations.rb +102 -0
  35. data/lib/language_operator/cli/commands/agent/helpers/cluster_llm_client.rb +116 -0
  36. data/lib/language_operator/cli/commands/agent/helpers/code_parser.rb +115 -0
  37. data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +96 -0
  38. data/lib/language_operator/cli/commands/agent/learning.rb +289 -0
  39. data/lib/language_operator/cli/commands/agent/lifecycle.rb +102 -0
  40. data/lib/language_operator/cli/commands/agent/logs.rb +125 -0
  41. data/lib/language_operator/cli/commands/agent/workspace.rb +327 -0
  42. data/lib/language_operator/cli/commands/cluster.rb +129 -84
  43. data/lib/language_operator/cli/commands/install.rb +1 -1
  44. data/lib/language_operator/cli/commands/model/base.rb +215 -0
  45. data/lib/language_operator/cli/commands/model/test.rb +165 -0
  46. data/lib/language_operator/cli/commands/persona.rb +16 -34
  47. data/lib/language_operator/cli/commands/quickstart.rb +3 -2
  48. data/lib/language_operator/cli/commands/status.rb +40 -67
  49. data/lib/language_operator/cli/commands/system/base.rb +44 -0
  50. data/lib/language_operator/cli/commands/system/exec.rb +147 -0
  51. data/lib/language_operator/cli/commands/system/helpers/llm_synthesis.rb +183 -0
  52. data/lib/language_operator/cli/commands/system/helpers/pod_manager.rb +212 -0
  53. data/lib/language_operator/cli/commands/system/helpers/template_loader.rb +57 -0
  54. data/lib/language_operator/cli/commands/system/helpers/template_validator.rb +174 -0
  55. data/lib/language_operator/cli/commands/system/schema.rb +92 -0
  56. data/lib/language_operator/cli/commands/system/synthesis_template.rb +151 -0
  57. data/lib/language_operator/cli/commands/system/synthesize.rb +224 -0
  58. data/lib/language_operator/cli/commands/system/validate_template.rb +130 -0
  59. data/lib/language_operator/cli/commands/tool/base.rb +271 -0
  60. data/lib/language_operator/cli/commands/tool/install.rb +255 -0
  61. data/lib/language_operator/cli/commands/tool/search.rb +69 -0
  62. data/lib/language_operator/cli/commands/tool/test.rb +115 -0
  63. data/lib/language_operator/cli/commands/use.rb +29 -6
  64. data/lib/language_operator/cli/errors/handler.rb +20 -17
  65. data/lib/language_operator/cli/errors/suggestions.rb +3 -5
  66. data/lib/language_operator/cli/errors/thor_errors.rb +55 -0
  67. data/lib/language_operator/cli/formatters/code_formatter.rb +4 -11
  68. data/lib/language_operator/cli/formatters/log_formatter.rb +8 -15
  69. data/lib/language_operator/cli/formatters/progress_formatter.rb +6 -8
  70. data/lib/language_operator/cli/formatters/status_formatter.rb +26 -7
  71. data/lib/language_operator/cli/formatters/table_formatter.rb +47 -36
  72. data/lib/language_operator/cli/formatters/value_formatter.rb +75 -0
  73. data/lib/language_operator/cli/helpers/cluster_context.rb +5 -3
  74. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +2 -1
  75. data/lib/language_operator/cli/helpers/label_utils.rb +97 -0
  76. data/lib/language_operator/{ux/concerns/provider_helpers.rb → cli/helpers/provider_helper.rb} +10 -29
  77. data/lib/language_operator/cli/helpers/schedule_builder.rb +21 -1
  78. data/lib/language_operator/cli/helpers/user_prompts.rb +19 -11
  79. data/lib/language_operator/cli/helpers/ux_helper.rb +538 -0
  80. data/lib/language_operator/{ux/concerns/input_validation.rb → cli/helpers/validation_helper.rb} +13 -66
  81. data/lib/language_operator/cli/main.rb +50 -40
  82. data/lib/language_operator/cli/templates/tools/generic.yaml +3 -0
  83. data/lib/language_operator/cli/wizards/agent_wizard.rb +12 -20
  84. data/lib/language_operator/cli/wizards/model_wizard.rb +271 -0
  85. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +8 -34
  86. data/lib/language_operator/client/base.rb +28 -0
  87. data/lib/language_operator/client/config.rb +4 -1
  88. data/lib/language_operator/client/mcp_connector.rb +1 -1
  89. data/lib/language_operator/config/cluster_config.rb +3 -2
  90. data/lib/language_operator/config.rb +38 -11
  91. data/lib/language_operator/constants/kubernetes_labels.rb +80 -0
  92. data/lib/language_operator/constants.rb +13 -0
  93. data/lib/language_operator/dsl/http.rb +127 -10
  94. data/lib/language_operator/dsl.rb +153 -6
  95. data/lib/language_operator/errors.rb +50 -0
  96. data/lib/language_operator/kubernetes/client.rb +11 -6
  97. data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
  98. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  99. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  100. data/lib/language_operator/type_coercion.rb +118 -34
  101. data/lib/language_operator/utils/secure_path.rb +74 -0
  102. data/lib/language_operator/utils.rb +7 -0
  103. data/lib/language_operator/validators.rb +54 -2
  104. data/lib/language_operator/version.rb +1 -1
  105. data/synth/001/Makefile +10 -2
  106. data/synth/001/agent.rb +16 -15
  107. data/synth/001/output.log +27 -10
  108. data/synth/002/Makefile +10 -2
  109. data/synth/003/Makefile +1 -1
  110. data/synth/003/README.md +205 -133
  111. data/synth/003/agent.optimized.rb +66 -0
  112. data/synth/003/agent.synthesized.rb +41 -0
  113. metadata +111 -35
  114. data/docs/dsl/agent-reference.md +0 -604
  115. data/docs/dsl/mcp-integration.md +0 -1177
  116. data/docs/dsl/webhooks.md +0 -932
  117. data/docs/dsl/workflows.md +0 -744
  118. data/lib/language_operator/cli/commands/agent.rb +0 -1712
  119. data/lib/language_operator/cli/commands/model.rb +0 -366
  120. data/lib/language_operator/cli/commands/system.rb +0 -1259
  121. data/lib/language_operator/cli/commands/tool.rb +0 -654
  122. data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
  123. data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
  124. data/lib/language_operator/learning/adapters/base_adapter.rb +0 -149
  125. data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -221
  126. data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -435
  127. data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -239
  128. data/lib/language_operator/learning/optimizer.rb +0 -319
  129. data/lib/language_operator/learning/pattern_detector.rb +0 -260
  130. data/lib/language_operator/learning/task_synthesizer.rb +0 -288
  131. data/lib/language_operator/learning/trace_analyzer.rb +0 -285
  132. data/lib/language_operator/templates/task_synthesis.tmpl +0 -98
  133. data/lib/language_operator/ux/base.rb +0 -81
  134. data/lib/language_operator/ux/concerns/README.md +0 -155
  135. data/lib/language_operator/ux/concerns/headings.rb +0 -90
  136. data/lib/language_operator/ux/create_agent.rb +0 -255
  137. data/lib/language_operator/ux/create_model.rb +0 -267
  138. data/lib/language_operator/ux/quickstart.rb +0 -594
  139. data/synth/003/agent.rb +0 -41
  140. data/synth/003/output.log +0 -68
  141. /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
  142. /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
  143. /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