aicli 0.3.1 → 0.4.0

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.
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pastel'
4
+ require 'tty-prompt'
5
+
6
+ module AiCli
7
+ module Helpers
8
+ module Theme
9
+ module_function
10
+
11
+ def accent(text)
12
+ pastel.public_send(Constants::ACCENT_COLOR, text)
13
+ end
14
+
15
+ def bold(text)
16
+ pastel.bold(text)
17
+ end
18
+
19
+ def dim(text)
20
+ pastel.dim(text)
21
+ end
22
+
23
+ def status(config)
24
+ "#{Constants::PROJECT_NAME}, #{config['PROVIDER']} / #{config['MODEL']}"
25
+ end
26
+
27
+ def show_status(config)
28
+ puts accent(status(config))
29
+ end
30
+
31
+ def prompt(**options)
32
+ TTY::Prompt.new(active_color: Constants::ACCENT_COLOR, **options)
33
+ end
34
+
35
+ def pastel
36
+ @pastel ||= Pastel.new
37
+ end
38
+ end
39
+ end
40
+ end
data/lib/aicli/prompt.rb CHANGED
@@ -1,102 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'open3'
4
3
  require 'clipboard'
5
- require 'pastel'
6
- require 'tty-prompt'
7
- require 'tty-spinner'
8
4
 
9
5
  module AiCli
10
6
  module Prompt
11
7
  module_function
12
8
 
13
9
  def run(use_prompt: nil, silent_mode: false)
14
- init_i18n
15
-
16
10
  config = Helpers::Config.get
17
- skip_explanation = silent_mode || config['SILENT_MODE']
18
-
19
- pastel = Pastel.new
20
- puts ''
21
- puts pastel.cyan(Helpers::Constants::PROJECT_NAME)
22
- puts pastel.dim("#{config['PROVIDER']} / #{config['MODEL']}")
23
-
24
- the_prompt = use_prompt.nil? || use_prompt.strip.empty? ? ask_prompt : use_prompt
11
+ prompt_text = use_prompt.to_s.strip.empty? ? ask_prompt : use_prompt
25
12
 
26
- spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('Loading...')}", format: :dots)
27
- spinner.auto_spin
28
-
29
- result = Helpers::Completion.get_script_and_info(
30
- prompt: the_prompt,
31
- config: config
32
- )
33
-
34
- spinner.success(Helpers::I18n.t('Your script') + ':')
35
- puts ''
36
- script = result[:read_script].call(->(chunk) { print chunk })
37
- puts ''
38
- puts ''
39
-
40
- explanation_text = ''
41
- unless skip_explanation
42
- spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('Getting explanation...')}", format: :dots)
43
- spinner.auto_spin
44
-
45
- info = result[:read_info].call(->(chunk) { print chunk })
46
- if info.nil? || info.empty?
47
- explanation = Helpers::Completion.get_explanation(
48
- script: script,
49
- config: config
50
- )
51
- spinner.success(Helpers::I18n.t('Explanation') + ':')
52
- puts ''
53
- explanation_text = explanation[:read_explanation].call(->(chunk) { print chunk })
54
- puts ''
55
- puts ''
56
- else
57
- explanation_text = info
58
- spinner.success(Helpers::I18n.t('Explanation') + ':')
59
- end
60
- end
61
-
62
- append_prompt_context(the_prompt, script, explanation_text)
63
- run_or_revise_flow(script, config, silent_mode)
13
+ Helpers::Theme.show_status(config)
14
+ script = Helpers::Completion.get_script(prompt: prompt_text, config: config)
15
+ save_context(prompt_text, script, '')
16
+ present_script_menu(script, config, silent_mode, leading_newline: true)
64
17
  end
65
18
 
66
- def init_i18n
67
- config = Helpers::Config.get
68
- Helpers::I18n.set_language(config['LANGUAGE'])
69
- rescue StandardError
70
- Helpers::I18n.set_language('en')
71
- end
72
-
73
- def examples
74
- [
75
- Helpers::I18n.t('delete all log files'),
76
- Helpers::I18n.t('list js files'),
77
- Helpers::I18n.t('fetch me a random joke'),
78
- Helpers::I18n.t('list all commits')
79
- ]
80
- end
81
-
82
- def ask_prompt(initial = nil)
83
- prompt = TTY::Prompt.new(interrupt: :error)
19
+ def ask_prompt
20
+ prompt = Helpers::Theme.prompt(interrupt: :error)
84
21
  Helpers::Context.seed_prompt_history(prompt)
85
22
 
86
23
  loop do
87
- begin
88
- return prompt.ask(Helpers::I18n.t('What would you like me to do?')) do |q|
89
- q.required true
90
- q.default initial || Helpers::I18n.t('Say hello')
91
- q.validate(/.+/, Helpers::I18n.t('Please enter a prompt.'))
92
- end
93
- rescue TTY::Reader::InputInterrupt
94
- print "\r\e[2K"
24
+ return prompt.ask(Helpers::I18n.t('What would you like me to do?')) do |q|
25
+ q.required true
26
+ q.default Helpers::I18n.t('Say hello')
27
+ q.validate(/.+/, Helpers::I18n.t('Please enter a prompt.'))
95
28
  end
29
+ rescue TTY::Reader::InputInterrupt
30
+ print "\r\e[2K"
31
+ end
32
+ end
33
+
34
+ def ask_revision
35
+ Helpers::Theme.prompt(interrupt: :exit).ask(
36
+ Helpers::I18n.t('What would you like me to change in this script?')
37
+ ) do |q|
38
+ q.required true
39
+ q.validate(/.+/, Helpers::I18n.t('Please enter a prompt.'))
96
40
  end
97
41
  end
98
42
 
99
- def append_prompt_context(user_prompt, script, explanation)
43
+ def save_context(user_prompt, script, explanation = '')
100
44
  assistant = +"```\n#{script}\n```"
101
45
  assistant << "\n\n#{explanation}" unless explanation.to_s.strip.empty?
102
46
 
@@ -106,43 +50,65 @@ module AiCli
106
50
  Helpers::Context.save_messages(messages)
107
51
  end
108
52
 
109
- def ask_revision
110
- prompt = TTY::Prompt.new(interrupt: :exit)
111
- prompt.ask(Helpers::I18n.t('What would you like me to change in this script?')) do |q|
112
- q.required true
113
- q.validate(/.+/, Helpers::I18n.t('Please enter a prompt.'))
114
- end
53
+ def append_explanation_to_context(explanation)
54
+ text = explanation.to_s.strip
55
+ return if text.empty?
56
+
57
+ messages = Helpers::Context.load_messages
58
+ return if messages.empty?
59
+
60
+ last = messages.last
61
+ return unless last['role'] == 'assistant' && !last['content'].include?(text)
62
+
63
+ last['content'] = "#{last['content']}\n\n#{text}"
64
+ Helpers::Context.save_messages(messages)
65
+ end
66
+
67
+ def display_script(script, leading_newline: false)
68
+ text = script.to_s.strip
69
+ return if text.empty?
70
+
71
+ puts '' if leading_newline
72
+ puts Helpers::Theme.bold(text)
115
73
  end
116
74
 
117
- def run_script(script)
118
- puts "#{Helpers::I18n.t('Running')}: #{script}"
75
+ def display_explanation(text)
76
+ stripped = text.to_s.strip
77
+ return if stripped.empty?
78
+
119
79
  puts ''
120
- system(ENV['SHELL'] || 'bash', '-c', script)
121
- Helpers::ShellHistory.record_executed(script)
80
+ puts Helpers::Theme.dim(stripped)
122
81
  end
123
82
 
124
- def run_or_revise_flow(script, config, silent_mode)
125
- prompt = TTY::Prompt.new(interrupt: :exit)
126
- empty_script = script.strip.empty?
83
+ def present_script_menu(script, config, silent_mode, leading_newline: false, show_script: true)
84
+ display_script(script, leading_newline: leading_newline) if show_script
85
+ run_action_menu(script, config, silent_mode)
86
+ end
127
87
 
128
- choices = []
129
- unless empty_script
130
- choices << { name: "✅ #{Helpers::I18n.t('Yes')}", value: :yes }
131
- choices << { name: "📝 #{Helpers::I18n.t('Edit')}", value: :edit }
132
- end
133
- choices << { name: "🔁 #{Helpers::I18n.t('Revise')}", value: :revise }
134
- choices << { name: "📋 #{Helpers::I18n.t('Copy')}", value: :copy }
135
- choices << { name: "❌ #{Helpers::I18n.t('Cancel')}", value: :cancel }
88
+ def run_action_menu(script, config, silent_mode)
89
+ empty = script.strip.empty?
90
+ message = empty ? Helpers::I18n.t('Revise this script?') : Helpers::I18n.t('Run this script?')
136
91
 
137
- message = empty_script ? Helpers::I18n.t('Revise this script?') : Helpers::I18n.t('Run this script?')
138
- answer = prompt.select(message, choices)
92
+ puts ''
93
+ answer = Helpers::ScriptActionMenu.select(
94
+ question: message,
95
+ config: config,
96
+ silent_mode: silent_mode,
97
+ empty_script: empty
98
+ )
139
99
 
140
100
  case answer
141
101
  when :yes
142
- run_script(script)
102
+ puts ''
103
+ Helpers::ShellExec.run(script)
143
104
  when :edit
144
- new_script = prompt.ask(Helpers::I18n.t('you can edit script here'), default: script)
145
- run_script(new_script) if new_script && !new_script.empty?
105
+ edited = Helpers::ScriptEditor.ask(script)
106
+ present_script_menu(edited, config, silent_mode, show_script: false) if edited && !edited.empty?
107
+ when :explain
108
+ text = Helpers::Completion.get_explanation(script: script, config: config)
109
+ display_explanation(text)
110
+ append_explanation_to_context(text)
111
+ present_script_menu(script, config, silent_mode, leading_newline: !text.to_s.strip.empty?)
146
112
  when :revise
147
113
  revision_flow(script, config, silent_mode)
148
114
  when :copy
@@ -154,45 +120,14 @@ module AiCli
154
120
  end
155
121
  end
156
122
 
157
- def revision_flow(current_script, config, silent_mode)
123
+ def revision_flow(script, config, silent_mode)
158
124
  revision = ask_revision
159
-
160
- spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('Loading...')}", format: :dots)
161
- spinner.auto_spin
162
-
163
- result = Helpers::Completion.get_revision(
164
- prompt: revision,
165
- code: current_script,
166
- config: config
167
- )
168
-
169
- spinner.success(Helpers::I18n.t('Your new script') + ':')
170
- puts ''
171
- script = result[:read_script].call(->(chunk) { print chunk })
172
- puts ''
173
- puts ''
174
-
175
- unless silent_mode
176
- info_spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('Getting explanation...')}", format: :dots)
177
- info_spinner.auto_spin
178
-
179
- explanation = Helpers::Completion.get_explanation(
180
- script: script,
181
- config: config
182
- )
183
-
184
- info_spinner.success(Helpers::I18n.t('Explanation') + ':')
185
- puts ''
186
- explanation[:read_explanation].call(->(chunk) { print chunk })
187
- puts ''
188
- puts ''
189
- end
190
-
191
- run_or_revise_flow(script, config, silent_mode)
125
+ revised = Helpers::Completion.get_revision(prompt: revision, code: script, config: config)
126
+ present_script_menu(revised, config, silent_mode, leading_newline: true)
192
127
  end
193
128
 
194
- private_class_method :init_i18n, :examples, :ask_prompt, :ask_revision,
195
- :run_script, :run_or_revise_flow, :revision_flow,
196
- :append_prompt_context
129
+ private_class_method :ask_prompt, :ask_revision, :save_context, :append_explanation_to_context,
130
+ :display_script, :display_explanation, :present_script_menu,
131
+ :run_action_menu, :revision_flow
197
132
  end
198
133
  end
data/lib/aicli/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AiCli
4
- VERSION = '0.3.1'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/aicli.rb CHANGED
@@ -2,11 +2,14 @@
2
2
 
3
3
  require_relative 'aicli/version'
4
4
  require_relative 'aicli/helpers/constants'
5
+ require_relative 'aicli/helpers/theme'
6
+ require_relative 'aicli/helpers/script_action_menu'
5
7
  require_relative 'aicli/helpers/error'
6
8
  require_relative 'aicli/helpers/i18n'
7
9
  require_relative 'aicli/helpers/os_detect'
8
- require_relative 'aicli/helpers/strip_regex_patterns'
9
10
  require_relative 'aicli/helpers/shell_history'
11
+ require_relative 'aicli/helpers/shell_exec'
12
+ require_relative 'aicli/helpers/script_editor'
10
13
  require_relative 'aicli/helpers/llm'
11
14
  require_relative 'aicli/helpers/completion'
12
15
  require_relative 'aicli/helpers/config'
data/locales/en.yml CHANGED
@@ -1,11 +1,7 @@
1
1
  ---
2
- Starting new conversation: Starting new conversation
3
2
  Goodbye!: Goodbye!
4
- send a message ('exit' to quit): send a message ('exit' to quit)
5
3
  Please enter a prompt.: Please enter a prompt.
6
4
  THINKING...: THINKING...
7
- Please set your OpenAI API key via `ai config set OPENAI_KEY=<your token>`: Please
8
- set your OpenAI API key via `ai config set OPENAI_KEY=<your token>`
9
5
  Set config: Set config
10
6
  Enter your OpenAI API key: Enter your OpenAI API key
11
7
  Enter your Anthropic API key: Enter your Anthropic API key
@@ -14,7 +10,6 @@ Provider: Provider
14
10
  Pick a provider: Pick a provider
15
11
  OpenAI Key: OpenAI Key
16
12
  Anthropic Key: Anthropic Key
17
- Please enter a key: Please enter a key
18
13
  OpenAI API Endpoint: OpenAI API Endpoint
19
14
  Enter your OpenAI API Endpoint: Enter your OpenAI API Endpoint
20
15
  Silent Mode: Silent Mode
@@ -22,51 +17,30 @@ Enable silent mode?: Enable silent mode?
22
17
  Model: Model
23
18
  Pick a model.: Pick a model.
24
19
  No models found for this provider.: No models found for this provider.
25
- Enter the model you want to use: Enter the model you want to use
26
20
  Language: Language
27
21
  Enter the language you want to use: Enter the language you want to use
28
22
  What would you like me to do?: What would you like me to do?
29
- delete all log files: delete all log files
30
- list js files: list js files
31
- fetch me a random joke: fetch me a random joke
32
- list all commits: list all commits
33
23
  Say hello: Say hello
34
- you can edit script here: you can edit script here
35
- What would you like me to change in this script?: What would you like me to change
36
- in this script?
37
- e.g.: e.g.
38
- e.g. change the folder name: e.g. change the folder name
39
- Your script: Your script
40
- Loading...: Loading...
41
- Getting explanation...: Getting explanation...
42
- Explanation: Explanation
24
+ Edit script: Edit script
25
+ Edit with arrow keys, Enter to confirm, Ctrl+C to cancel: Edit with arrow keys, Enter to confirm, Ctrl+C to cancel
26
+ What would you like me to change in this script?: What would you like me to change in this script?
43
27
  Run this script?: Run this script?
44
- Run it?: Run it?
45
28
  Revise this script?: Revise this script?
46
- 'Yes': 'Yes'
47
- Lets go!: Lets go!
48
- Edit: Edit
49
- Make some adjustments before running: Make some adjustments before running
29
+ Yes: Yes
30
+ Modify: Modify
31
+ Exit: Exit
32
+ Explain: Explain
50
33
  Revise: Revise
51
- Give feedback via prompt and get a new result: Give feedback via prompt and get a
52
- new result
53
34
  Copy: Copy
54
- Copy the generated script to your clipboard: Copy the generated script to your clipboard
55
35
  Cancel: Cancel
56
- Exit the program: Exit the program
57
36
  Running: Running
58
37
  Copied to clipboard!: Copied to clipboard!
59
- Your new script: Your new script
60
38
  Invalid config property: Invalid config property
39
+ Invalid mode: Invalid mode
61
40
  Shell detection failed unexpectedly: Shell detection failed unexpectedly
62
- Invalid model: Invalid model
63
41
  Error: Error
64
42
  Missing required parameter: Missing required parameter
65
- Please open a Bug report with the information above: Please open a Bug report with
66
- the information above
67
- Prompt to run: Prompt to run
43
+ Please open a Bug report with the information above: Please open a Bug report with the information above
68
44
  You: You
69
- Ask for a shell command. Type exit to quit.: Ask for a shell command. Type exit to quit.
70
- Restored context: Restored context
71
- messages: messages
72
45
  Shell integration installed.: Shell integration installed.
46
+ Run it?: Run it?
data/locales/it.yml CHANGED
@@ -1,11 +1,7 @@
1
1
  ---
2
- Starting new conversation: Avvia una nuova conversazione
3
2
  Goodbye!: Arrivederci!
4
- send a message ('exit' to quit): invia un messaggio ('exit' per uscire)
5
3
  Please enter a prompt.: Per favore inserisci un prompt.
6
4
  THINKING...: pensando...
7
- Please set your OpenAI API key via `ai config set OPENAI_KEY=<your token>`: Per favore
8
- imposta la tua chiave API OpenAI tramite `ai config set OPENAI_KEY=<your token>`
9
5
  Set config: Imposta la configurazione
10
6
  Enter your OpenAI API key: Inserisci la tua chiave API OpenAI
11
7
  Enter your Anthropic API key: Inserisci la tua chiave API Anthropic
@@ -14,7 +10,6 @@ Provider: Provider
14
10
  Pick a provider: Scegli un provider
15
11
  OpenAI Key: Chiave OpenAI
16
12
  Anthropic Key: Chiave Anthropic
17
- Please enter a key: Per favore inserisci una chiave
18
13
  OpenAI API Endpoint: Endpoint API OpenAI
19
14
  Enter your OpenAI API Endpoint: Inserisci il tuo endpoint API OpenAI
20
15
  Silent Mode: Modalità silenziosa
@@ -22,50 +17,30 @@ Enable silent mode?: Abilitare la modalità silenziosa?
22
17
  Model: Modello
23
18
  Pick a model.: Scegli un modello.
24
19
  No models found for this provider.: Nessun modello trovato per questo provider.
25
- Enter the model you want to use: Inserisci il modello che vuoi utilizzare
26
20
  Language: Lingua
27
21
  Enter the language you want to use: Inserisci la lingua che vuoi utilizzare
28
22
  What would you like me to do?: Cosa vorresti che facessi?
29
- delete all log files: elimina tutti i file di log
30
- list js files: elenca i file js
31
- fetch me a random joke: raccontami una barzelletta
32
- list all commits: elenca tutti i commit
33
23
  Say hello: Saluta
34
- you can edit script here: puoi modificare lo script qui
35
- What would you like me to change in this script?: Cosa vorresti che cambiassi in questo
36
- script?
37
- e.g.: es.
38
- e.g. change the folder name: es. cambia il nome della cartella
39
- Your script: Il tuo script
40
- Loading...: Caricamento...
41
- Getting explanation...: Ottieni spiegazione...
42
- Explanation: Spiegazione
24
+ Edit script: Modifica lo script
25
+ Edit with arrow keys, Enter to confirm, Ctrl+C to cancel: Modifica con le frecce, Invio per confermare, Ctrl+C per annullare
26
+ What would you like me to change in this script?: Cosa vorresti che cambiassi in questo script?
43
27
  Run this script?: Esegui questo script?
44
- Run it?: Lo eseguo?
45
28
  Revise this script?: Rivedi questo script?
46
- 'Yes': Si
47
- Lets go!: Vai!
48
- Edit: Modifica
49
- Make some adjustments before running: Fai alcune modifiche prima di eseguire
50
- Revise: Revisiona
51
- Give feedback via prompt and get a new result: Dai un feedback tramite prompt e ottieni
52
- un nuovo risultato
29
+ Yes:
30
+ Modify: Modifica
31
+ Exit: Esci
32
+ Explain: Spiega
33
+ Revise: Rivedi
53
34
  Copy: Copia
54
- Copy the generated script to your clipboard: Copia lo script generato negli appunti
55
35
  Cancel: Annulla
56
- Exit the program: Esci dal programma
57
36
  Running: Esecuzione
58
37
  Copied to clipboard!: Copiato negli appunti!
59
- Your new script: Il tuo nuovo script
60
38
  Invalid config property: Proprietà di configurazione non valida
61
- Shell detection failed unexpectedly: Rilevamento shell fallito inaspettatamente
62
- Invalid model: Modello non valido
39
+ Invalid mode: Modalità non valida
40
+ Shell detection failed unexpectedly: Rilevamento shell fallito in modo imprevisto
63
41
  Error: Errore
64
- Please open a Bug report with the information above: Apri un report di bug con le
65
- informazioni sopra
66
- Prompt to run: Prompt da eseguire
42
+ Missing required parameter: Parametro richiesto mancante
43
+ Please open a Bug report with the information above: Apri una segnalazione bug con le informazioni sopra
67
44
  You: Tu
68
- Ask for a shell command. Type exit to quit.: Chiedi un comando shell. Digita exit per uscire.
69
- Restored context: Contesto ripristinato
70
- messages: messaggi
71
45
  Shell integration installed.: Integrazione shell installata.
46
+ Run it?: Lo eseguo?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aicli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antonio Molinari
@@ -37,6 +37,20 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0.8'
40
+ - !ruby/object:Gem::Dependency
41
+ name: reline
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.3.0
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 0.3.0
40
54
  - !ruby/object:Gem::Dependency
41
55
  name: ruby_llm
42
56
  requirement: !ruby/object:Gem::Requirement
@@ -122,8 +136,11 @@ files:
122
136
  - lib/aicli/helpers/i18n.rb
123
137
  - lib/aicli/helpers/llm.rb
124
138
  - lib/aicli/helpers/os_detect.rb
139
+ - lib/aicli/helpers/script_action_menu.rb
140
+ - lib/aicli/helpers/script_editor.rb
141
+ - lib/aicli/helpers/shell_exec.rb
125
142
  - lib/aicli/helpers/shell_history.rb
126
- - lib/aicli/helpers/strip_regex_patterns.rb
143
+ - lib/aicli/helpers/theme.rb
127
144
  - lib/aicli/prompt.rb
128
145
  - lib/aicli/version.rb
129
146
  - locales/ar.yml
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AiCli
4
- module Helpers
5
- module StripRegexPatterns
6
- module_function
7
-
8
- def call(input_string, pattern_list)
9
- pattern_list.reduce(input_string) do |current, pattern|
10
- next current if pattern.nil?
11
-
12
- if pattern.is_a?(Regexp)
13
- current.gsub(pattern, '')
14
- else
15
- current.gsub(pattern.to_s, '')
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end