aicli 0.2.2 → 0.2.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49c27ca0316a3ca251344ea8a63589d603d67a6a89e77287b6615f9314650ed1
4
- data.tar.gz: c44be18b27d259f0fd8f93f2133246775d042ab09576be64dc8138353d53d095
3
+ metadata.gz: c38b286746d1ffdef26b15f61a628e2a5ef9dcc774f5fc7b3b925d2b76df1a45
4
+ data.tar.gz: 2c0a48abc6fbe2f279ca644d69f3de61e4df895d9950b209268996f4f66fc7b5
5
5
  SHA512:
6
- metadata.gz: becb34f20ed09c6d7de4f68c72b81aa6ae83cb72bf158e401c35bdabc291a2339d2fa16d845abf6934c826d1be0cbb50f48f45e9bec08a7c554a3e79bf8ba5c3
7
- data.tar.gz: 9dc02259b8ea17da0d3a73aaade312cc88a0c234b9c6418014ac840025f8b440c9c735ac605165b898b00fe1e53e37f14365c60943652b6561e8ffa0b603f0ad
6
+ metadata.gz: dc70046850d2544dd0f3c4c4af1827bc40c1fe04a5b24e88b36e873115f7f9adcc1e89c5565e79f9d6205bd65effb3ad3e0365a003cfcac99566f58d5395b50a
7
+ data.tar.gz: 274a2632ce615915704e9aec158a82fcddbdc2ee589a3d2fbb194a39fc053f376e10f5cbed4375c019ce784461b534d9d357c735a4cd2b856a8ae78485ce5ff0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.2.6
2
+
3
+ - Ctrl+C clears the current input line instead of exiting chat/prompt
4
+
5
+ ## 0.2.5
6
+
7
+ - Fix chat `Run it? (y/n)` input (read from `/dev/tty`; only `y` executes)
8
+
9
+ ## 0.2.4
10
+
11
+ - Persist last 40 chat/prompt messages in `~/.aicli/context` and reload on next run
12
+ - Migrate flat `~/.aicli` config file to `~/.aicli/config`
13
+
14
+ ## 0.2.3
15
+
16
+ - In `ai chat`, detect suggested shell commands and ask to run them, then continue the conversation
17
+
1
18
  ## 0.2.2
2
19
 
3
20
  - Improve `ai update`: compare installed version with RubyGems, install latest explicitly, clearer status messages
data/README.md CHANGED
@@ -6,18 +6,18 @@
6
6
  </h2>
7
7
 
8
8
  <h4 align="center">
9
- A CLI that converts natural language to shell commands.
9
+ Natural language to live shell commands OpenAI &amp; Anthropic.
10
10
  </h4>
11
11
 
12
12
  <p align="center">
13
- Inspired by the <a href="https://githubnext.com/projects/copilot-cli">GitHub Copilot X CLI</a>, but open source for everyone.
13
+ Chat with the model and run suggested commands in your shell in one seamless flow.
14
14
  </p>
15
15
 
16
16
  <br>
17
17
 
18
18
  # aicli
19
19
 
20
- Ruby port by [Antonio Molinari](https://github.com/magnum), inspired by the original [BuilderIO/ai-shell](https://github.com/BuilderIO/ai-shell) CLI.
20
+ CLI by [Antonio Molinari](https://github.com/magnum). Converts prompts into shell commands, with optional chat mode that can execute them live.
21
21
 
22
22
  ## Setup
23
23
 
@@ -57,7 +57,7 @@ Ruby port by [Antonio Molinari](https://github.com/magnum), inspired by the orig
57
57
  Or use the interactive UI (`aicli config`) to pick provider, key, and model.
58
58
  Model lists come from the [RubyLLM](https://rubyllm.com/models/) registry.
59
59
 
60
- This will create a `.aicli` file in your home directory.
60
+ Config lives in `~/.aicli/config`. Chat/prompt history is stored in `~/.aicli/context` (last 40 messages) and reloaded on the next run.
61
61
 
62
62
  ## Usage
63
63
 
@@ -89,7 +89,7 @@ aicli 'what is my ip address'
89
89
  aicli chat
90
90
  ```
91
91
 
92
- Engage in a conversation with the AI directly through the CLI.
92
+ Ask for shell commands in a multi-turn conversation. When the assistant suggests a command in a code fence, you are asked whether to run it; after it runs (or you decline), chat continues. Press `Ctrl+d` to quit.
93
93
 
94
94
  ### Silent mode (skip explanations)
95
95
 
@@ -195,7 +195,7 @@ bundle exec bin/ai config set OPENAI_KEY=<your token>
195
195
 
196
196
  ### Rate limit / quota errors
197
197
 
198
- Usually billing or quota on the configured provider (OpenAI or Anthropic). Check that provider’s console billing page and that the matching API key is set in `~/.aicli`.
198
+ Usually billing or quota on the configured provider (OpenAI or Anthropic). Check that provider’s console billing page and that the matching API key is set in `~/.aicli/config`.
199
199
 
200
200
  ## Motivation
201
201
 
@@ -203,8 +203,4 @@ I am not a bash wizard, and am dying for access to the copilot CLI, and got impa
203
203
 
204
204
  ## Credit
205
205
 
206
- - Ruby port by [Antonio Molinari](https://github.com/magnum)
207
- - Inspired by [BuilderIO/ai-shell](https://github.com/BuilderIO/ai-shell)
208
- - Thanks to GitHub Copilot for their amazing tools and the idea for this
209
- - Thanks to Hassan and his work on [aicommits](https://github.com/Nutlope/aicommits), which inspired the workflow and some parts of the code and flows
210
- - Original Node.js implementation by [Builder.io](https://www.builder.io)
206
+ - [Antonio Molinari](https://github.com/magnum)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'io/console'
3
4
  require 'pastel'
4
5
  require 'tty-prompt'
5
6
  require 'tty-spinner'
@@ -12,50 +13,109 @@ module AiCli
12
13
  def run
13
14
  config = Helpers::Config.get
14
15
  chat = Helpers::Completion.start_chat(config)
16
+ restored = Helpers::Context.load_messages
17
+ Helpers::Context.apply_to_chat(chat) if restored.any?
15
18
 
16
19
  pastel = Pastel.new
17
- prompt = TTY::Prompt.new(interrupt: :exit)
20
+ prompt = TTY::Prompt.new(interrupt: :error)
21
+ Helpers::Context.seed_prompt_history(prompt)
18
22
 
19
23
  puts ''
20
- puts " #{Helpers::I18n.t('Starting new conversation')}"
21
- puts pastel.dim(" #{config['PROVIDER']} / #{config['MODEL']}")
24
+ puts "#{config['PROVIDER']} / #{config['MODEL']} #{pastel.dim('Ctrl+d to quit')}"
22
25
 
23
- loop do
24
- user_prompt = prompt.ask(pastel.cyan("#{Helpers::I18n.t('You')}:")) do |q|
25
- q.required true
26
- q.validate(/.+/, Helpers::I18n.t('Please enter a prompt.'))
27
- end
28
-
29
- if user_prompt.nil? || user_prompt == 'exit'
30
- puts "└ #{Helpers::I18n.t('Goodbye!')}"
31
- exit 0
32
- end
26
+ catch(:chat_quit) do
27
+ prompt.on(:keyctrl_d) { throw :chat_quit }
33
28
 
34
- spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('THINKING...')}", format: :dots)
35
- spinner.auto_spin
36
- started = false
37
-
38
- Helpers::Completion.stream_chat_message(
39
- chat,
40
- user_prompt,
41
- writer: lambda { |chunk|
42
- unless started
43
- spinner.success(pastel.green('aicli:'))
44
- puts ''
45
- started = true
29
+ loop do
30
+ begin
31
+ user_prompt = prompt.ask(pastel.cyan("#{Helpers::I18n.t('You')}:")) do |q|
32
+ q.required true
33
+ q.validate(/.+/, Helpers::I18n.t('Please enter a prompt.'))
46
34
  end
47
- print chunk
48
- }
49
- )
35
+ rescue TTY::Reader::InputInterrupt
36
+ # Ctrl+C clears the current input line and re-prompts.
37
+ print "\r\e[2K"
38
+ next
39
+ end
40
+
41
+ if user_prompt.nil? || user_prompt.strip.downcase == 'exit'
42
+ throw :chat_quit
43
+ end
44
+
45
+ spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('THINKING...')}", format: :dots)
46
+ spinner.auto_spin
47
+ started = false
48
+
49
+ response = Helpers::Completion.stream_chat_message(
50
+ chat,
51
+ user_prompt,
52
+ writer: lambda { |chunk|
53
+ unless started
54
+ spinner.success(pastel.green('aicli:'))
55
+ puts ''
56
+ started = true
57
+ end
58
+ print chunk
59
+ }
60
+ )
50
61
 
51
- unless started
52
- spinner.success(pastel.green('aicli:'))
62
+ unless started
63
+ spinner.success(pastel.green('aicli:'))
64
+ puts ''
65
+ end
53
66
  puts ''
67
+ puts ''
68
+
69
+ Helpers::Context.save_from_chat(chat)
70
+ offer_to_run_commands(response, pastel)
54
71
  end
55
- puts ''
72
+ end
73
+
74
+ Helpers::Context.save_from_chat(chat)
75
+ puts ''
76
+ puts Helpers::I18n.t('Goodbye!')
77
+ end
78
+
79
+ def offer_to_run_commands(response, pastel)
80
+ commands = Helpers::Completion.extract_commands(response)
81
+ return if commands.empty?
82
+
83
+ commands.each do |command|
84
+ puts pastel.dim(command)
85
+ next unless ask_to_run?
86
+
87
+ run_command(command)
56
88
  puts ''
57
89
  end
58
90
  end
91
+
92
+ # Only explicit `y` runs the command. Enter, Esc, n, Ctrl-C → skip.
93
+ def ask_to_run?
94
+ tty = File.open('/dev/tty', 'r+')
95
+ tty.print "#{Helpers::I18n.t('Run it?')} (y/n) "
96
+ tty.flush
97
+ char = tty.getch
98
+ tty.puts
99
+ char.to_s.downcase == 'y'
100
+ rescue Interrupt
101
+ begin
102
+ tty&.puts
103
+ rescue StandardError
104
+ puts
105
+ end
106
+ false
107
+ ensure
108
+ tty&.close
109
+ end
110
+
111
+ def run_command(command)
112
+ puts "#{Helpers::I18n.t('Running')}: #{command}"
113
+ puts ''
114
+ system(ENV['SHELL'] || 'bash', '-c', command)
115
+ Helpers::ShellHistory.append(command)
116
+ end
117
+
118
+ private_class_method :offer_to_run_commands, :ask_to_run?, :run_command
59
119
  end
60
120
  end
61
121
  end
@@ -42,7 +42,41 @@ module AiCli
42
42
 
43
43
  # Multi-turn chat helper used by `aicli chat`.
44
44
  def start_chat(config)
45
- Llm.build_chat(config)
45
+ chat = Llm.build_chat(config)
46
+ chat.with_instructions(chat_system_prompt)
47
+ chat
48
+ end
49
+
50
+ def chat_system_prompt
51
+ <<~PROMPT
52
+ You are aicli, a terminal assistant. The user is asking for shell commands.
53
+
54
+ Target OS: #{OsDetect.operating_system_name}
55
+ Target shell: #{OsDetect.detect_shell}
56
+
57
+ Whenever you suggest a command the user can run, put the exact runnable command
58
+ alone inside a markdown fenced code block, for example:
59
+
60
+ ```
61
+ ls -la
62
+ ```
63
+
64
+ Prefer a single command. You may briefly explain outside the fence.
65
+ Do not wrap non-runnable prose in fences.
66
+ PROMPT
67
+ end
68
+
69
+ def extract_commands(text)
70
+ return [] if text.nil? || text.empty?
71
+
72
+ blocks = text.scan(/```(?:[a-zA-Z0-9_+-]*)\s*\n?(.*?)```/m)
73
+ .flatten
74
+ .map { |block| block.strip.gsub(/\A`+|`+\z/, '') }
75
+ .map(&:strip)
76
+ .reject(&:empty?)
77
+
78
+ # Prefer the last fenced command if several were suggested.
79
+ blocks
46
80
  end
47
81
 
48
82
  def stream_chat_message(chat, message, writer:)
@@ -7,7 +7,9 @@ require 'tty-prompt'
7
7
  module AiCli
8
8
  module Helpers
9
9
  module Config
10
- CONFIG_PATH = File.join(Dir.home, '.aicli')
10
+ HOME_DIR = File.join(Dir.home, '.aicli')
11
+ CONFIG_PATH = File.join(HOME_DIR, 'config')
12
+ LEGACY_FLAT_CONFIG_PATH = File.join(Dir.home, '.aicli')
11
13
  LEGACY_CONFIG_PATH = File.join(Dir.home, '.ai-shell')
12
14
 
13
15
  CONFIG_PARSERS = {
@@ -36,6 +38,32 @@ module AiCli
36
38
 
37
39
  module_function
38
40
 
41
+ def home_dir
42
+ ensure_home!
43
+ HOME_DIR
44
+ end
45
+
46
+ def ensure_home!
47
+ if File.file?(LEGACY_FLAT_CONFIG_PATH) && !File.directory?(LEGACY_FLAT_CONFIG_PATH)
48
+ migrate_flat_config!
49
+ else
50
+ FileUtils.mkdir_p(HOME_DIR)
51
+ end
52
+ HOME_DIR
53
+ end
54
+
55
+ def migrate_flat_config!
56
+ backup = "#{LEGACY_FLAT_CONFIG_PATH}.migrating.#{Process.pid}"
57
+ File.rename(LEGACY_FLAT_CONFIG_PATH, backup)
58
+ FileUtils.mkdir_p(HOME_DIR)
59
+ FileUtils.mv(backup, CONFIG_PATH)
60
+ rescue StandardError
61
+ FileUtils.mkdir_p(HOME_DIR)
62
+ if File.exist?(backup)
63
+ FileUtils.mv(backup, CONFIG_PATH) unless File.exist?(CONFIG_PATH)
64
+ end
65
+ end
66
+
39
67
  def get(cli_config = nil)
40
68
  config = read_config_file
41
69
  parsed = {}
@@ -87,6 +115,7 @@ module AiCli
87
115
  end
88
116
 
89
117
  updates.each { |key, value| config[key] = value }
118
+ ensure_home!
90
119
  File.write(CONFIG_PATH, stringify_ini(config))
91
120
  end
92
121
 
@@ -178,9 +207,11 @@ module AiCli
178
207
  end
179
208
 
180
209
  def read_config_file
181
- path = if File.exist?(CONFIG_PATH)
210
+ ensure_home!
211
+
212
+ path = if File.file?(CONFIG_PATH)
182
213
  CONFIG_PATH
183
- elsif File.exist?(LEGACY_CONFIG_PATH)
214
+ elsif File.file?(LEGACY_CONFIG_PATH)
184
215
  LEGACY_CONFIG_PATH
185
216
  end
186
217
  return {} unless path
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+ require 'time'
6
+
7
+ module AiCli
8
+ module Helpers
9
+ module Context
10
+ # Keep the last N user/assistant messages across sessions.
11
+ MAX_MESSAGES = 40
12
+ CONTEXT_FILENAME = 'context'
13
+
14
+ module_function
15
+
16
+ def path
17
+ File.join(Config.home_dir, CONTEXT_FILENAME)
18
+ end
19
+
20
+ def load_messages
21
+ Config.ensure_home!
22
+ return [] unless File.file?(path)
23
+
24
+ data = JSON.parse(File.read(path))
25
+ Array(data['messages'])
26
+ .select { |m| m.is_a?(Hash) && %w[user assistant].include?(m['role'].to_s) }
27
+ .map { |m| { 'role' => m['role'].to_s, 'content' => m['content'].to_s } }
28
+ .reject { |m| m['content'].strip.empty? }
29
+ .last(MAX_MESSAGES)
30
+ rescue StandardError
31
+ []
32
+ end
33
+
34
+ def save_messages(messages)
35
+ Config.ensure_home!
36
+ normalized = Array(messages)
37
+ .map { |m| normalize_message(m) }
38
+ .compact
39
+ .last(MAX_MESSAGES)
40
+
41
+ payload = {
42
+ 'version' => 1,
43
+ 'updated_at' => Time.now.utc.iso8601,
44
+ 'messages' => normalized
45
+ }
46
+ File.write(path, "#{JSON.pretty_generate(payload)}\n")
47
+ rescue StandardError
48
+ # Ignore persistence failures; chat should still work.
49
+ end
50
+
51
+ def save_from_chat(chat)
52
+ save_messages(chat.messages)
53
+ end
54
+
55
+ def apply_to_chat(chat)
56
+ load_messages.each do |message|
57
+ chat.add_message(role: message['role'].to_sym, content: message['content'])
58
+ end
59
+ end
60
+
61
+ def user_prompts
62
+ load_messages.select { |m| m['role'] == 'user' }.map { |m| m['content'] }
63
+ end
64
+
65
+ def seed_prompt_history(prompt)
66
+ return unless prompt.respond_to?(:reader)
67
+
68
+ user_prompts.each do |line|
69
+ prompt.reader.add_to_history(line)
70
+ end
71
+ rescue StandardError
72
+ nil
73
+ end
74
+
75
+ def normalize_message(message)
76
+ if message.respond_to?(:role) && message.respond_to?(:content)
77
+ role = message.role.to_s
78
+ content = message.content.to_s
79
+ elsif message.is_a?(Hash)
80
+ role = (message[:role] || message['role']).to_s
81
+ content = (message[:content] || message['content']).to_s
82
+ else
83
+ return nil
84
+ end
85
+
86
+ return nil unless %w[user assistant].include?(role)
87
+ return nil if content.strip.empty?
88
+
89
+ { 'role' => role, 'content' => content }
90
+ end
91
+ private_class_method :normalize_message
92
+ end
93
+ end
94
+ end
data/lib/aicli/prompt.rb CHANGED
@@ -18,8 +18,8 @@ module AiCli
18
18
 
19
19
  pastel = Pastel.new
20
20
  puts ''
21
- puts "┌ #{pastel.cyan(Helpers::Constants::PROJECT_NAME)}"
22
- puts pastel.dim(" #{config['PROVIDER']} / #{config['MODEL']}")
21
+ puts pastel.cyan(Helpers::Constants::PROJECT_NAME)
22
+ puts pastel.dim("#{config['PROVIDER']} / #{config['MODEL']}")
23
23
 
24
24
  the_prompt = use_prompt.nil? || use_prompt.strip.empty? ? ask_prompt : use_prompt
25
25
 
@@ -36,8 +36,8 @@ module AiCli
36
36
  script = result[:read_script].call(->(chunk) { print chunk })
37
37
  puts ''
38
38
  puts ''
39
- puts pastel.dim('•')
40
39
 
40
+ explanation_text = ''
41
41
  unless skip_explanation
42
42
  spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('Getting explanation...')}", format: :dots)
43
43
  spinner.auto_spin
@@ -50,15 +50,16 @@ module AiCli
50
50
  )
51
51
  spinner.success(Helpers::I18n.t('Explanation') + ':')
52
52
  puts ''
53
- explanation[:read_explanation].call(->(chunk) { print chunk })
53
+ explanation_text = explanation[:read_explanation].call(->(chunk) { print chunk })
54
54
  puts ''
55
55
  puts ''
56
- puts pastel.dim('•')
57
56
  else
57
+ explanation_text = info
58
58
  spinner.success(Helpers::I18n.t('Explanation') + ':')
59
59
  end
60
60
  end
61
61
 
62
+ append_prompt_context(the_prompt, script, explanation_text)
62
63
  run_or_revise_flow(script, config, silent_mode)
63
64
  end
64
65
 
@@ -79,14 +80,32 @@ module AiCli
79
80
  end
80
81
 
81
82
  def ask_prompt(initial = nil)
82
- prompt = TTY::Prompt.new(interrupt: :exit)
83
- prompt.ask(Helpers::I18n.t('What would you like me to do?')) do |q|
84
- q.required true
85
- q.default initial || Helpers::I18n.t('Say hello')
86
- q.validate(/.+/, Helpers::I18n.t('Please enter a prompt.'))
83
+ prompt = TTY::Prompt.new(interrupt: :error)
84
+ Helpers::Context.seed_prompt_history(prompt)
85
+
86
+ 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"
95
+ end
87
96
  end
88
97
  end
89
98
 
99
+ def append_prompt_context(user_prompt, script, explanation)
100
+ assistant = +"```\n#{script}\n```"
101
+ assistant << "\n\n#{explanation}" unless explanation.to_s.strip.empty?
102
+
103
+ messages = Helpers::Context.load_messages
104
+ messages << { 'role' => 'user', 'content' => user_prompt.to_s }
105
+ messages << { 'role' => 'assistant', 'content' => assistant }
106
+ Helpers::Context.save_messages(messages)
107
+ end
108
+
90
109
  def ask_revision
91
110
  prompt = TTY::Prompt.new(interrupt: :exit)
92
111
  prompt.ask(Helpers::I18n.t('What would you like me to change in this script?')) do |q|
@@ -96,7 +115,7 @@ module AiCli
96
115
  end
97
116
 
98
117
  def run_script(script)
99
- puts "#{Helpers::I18n.t('Running')}: #{script}"
118
+ puts "#{Helpers::I18n.t('Running')}: #{script}"
100
119
  puts ''
101
120
  system(ENV['SHELL'] || 'bash', '-c', script)
102
121
  Helpers::ShellHistory.append(script)
@@ -128,15 +147,14 @@ module AiCli
128
147
  revision_flow(script, config, silent_mode)
129
148
  when :copy
130
149
  Clipboard.copy(script)
131
- puts "└ #{Helpers::I18n.t('Copied to clipboard!')}"
150
+ puts Helpers::I18n.t('Copied to clipboard!')
132
151
  when :cancel
133
- puts "└ #{Helpers::I18n.t('Goodbye!')}"
152
+ puts Helpers::I18n.t('Goodbye!')
134
153
  exit 0
135
154
  end
136
155
  end
137
156
 
138
157
  def revision_flow(current_script, config, silent_mode)
139
- pastel = Pastel.new
140
158
  revision = ask_revision
141
159
 
142
160
  spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('Loading...')}", format: :dots)
@@ -153,7 +171,6 @@ module AiCli
153
171
  script = result[:read_script].call(->(chunk) { print chunk })
154
172
  puts ''
155
173
  puts ''
156
- puts pastel.dim('•')
157
174
 
158
175
  unless silent_mode
159
176
  info_spinner = TTY::Spinner.new("[:spinner] #{Helpers::I18n.t('Getting explanation...')}", format: :dots)
@@ -169,13 +186,13 @@ module AiCli
169
186
  explanation[:read_explanation].call(->(chunk) { print chunk })
170
187
  puts ''
171
188
  puts ''
172
- puts pastel.dim('•')
173
189
  end
174
190
 
175
191
  run_or_revise_flow(script, config, silent_mode)
176
192
  end
177
193
 
178
194
  private_class_method :init_i18n, :examples, :ask_prompt, :ask_revision,
179
- :run_script, :run_or_revise_flow, :revision_flow
195
+ :run_script, :run_or_revise_flow, :revision_flow,
196
+ :append_prompt_context
180
197
  end
181
198
  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.2.2'
4
+ VERSION = '0.2.7'
5
5
  end
data/lib/aicli.rb CHANGED
@@ -10,6 +10,7 @@ require_relative 'aicli/helpers/shell_history'
10
10
  require_relative 'aicli/helpers/llm'
11
11
  require_relative 'aicli/helpers/completion'
12
12
  require_relative 'aicli/helpers/config'
13
+ require_relative 'aicli/helpers/context'
13
14
  require_relative 'aicli/prompt'
14
15
  require_relative 'aicli/commands/chat'
15
16
  require_relative 'aicli/commands/config'
data/locales/en.yml CHANGED
@@ -41,6 +41,7 @@ Loading...: Loading...
41
41
  Getting explanation...: Getting explanation...
42
42
  Explanation: Explanation
43
43
  Run this script?: Run this script?
44
+ Run it?: Run it?
44
45
  Revise this script?: Revise this script?
45
46
  'Yes': 'Yes'
46
47
  Lets go!: Lets go!
@@ -65,3 +66,6 @@ Please open a Bug report with the information above: Please open a Bug report wi
65
66
  the information above
66
67
  Prompt to run: Prompt to run
67
68
  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
data/locales/it.yml CHANGED
@@ -41,6 +41,7 @@ Loading...: Caricamento...
41
41
  Getting explanation...: Ottieni spiegazione...
42
42
  Explanation: Spiegazione
43
43
  Run this script?: Esegui questo script?
44
+ Run it?: Lo eseguo?
44
45
  Revise this script?: Rivedi questo script?
45
46
  'Yes': Si
46
47
  Lets go!: Vai!
@@ -64,3 +65,6 @@ Please open a Bug report with the information above: Apri un report di bug con l
64
65
  informazioni sopra
65
66
  Prompt to run: Prompt da eseguire
66
67
  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
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.2.2
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antonio Molinari
@@ -93,8 +93,9 @@ dependencies:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0.9'
96
- description: aicli turns natural language prompts into runnable shell commands via
97
- the OpenAI API. Ruby port inspired by BuilderIO/ai-shell.
96
+ description: aicli turns natural language into runnable shell commands using OpenAI
97
+ or Anthropic. Chat with the model and execute suggested commands live in your shell
98
+ in one seamless flow.
98
99
  email:
99
100
  - antoniomolinari@me.com
100
101
  executables:
@@ -116,6 +117,7 @@ files:
116
117
  - lib/aicli/helpers/completion.rb
117
118
  - lib/aicli/helpers/config.rb
118
119
  - lib/aicli/helpers/constants.rb
120
+ - lib/aicli/helpers/context.rb
119
121
  - lib/aicli/helpers/error.rb
120
122
  - lib/aicli/helpers/i18n.rb
121
123
  - lib/aicli/helpers/llm.rb
@@ -165,5 +167,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
167
  requirements: []
166
168
  rubygems_version: 4.0.17
167
169
  specification_version: 4
168
- summary: A CLI that converts natural language to shell commands.
170
+ summary: Natural language to live shell commands, with OpenAI and Anthropic.
169
171
  test_files: []