fantasy-cli 1.2.6
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +456 -0
- data/bin/gsd +8 -0
- data/bin/gsd-core-darwin-amd64 +0 -0
- data/bin/gsd-core-darwin-arm64 +0 -0
- data/bin/gsd-core-linux-amd64 +0 -0
- data/bin/gsd-core-linux-arm64 +0 -0
- data/bin/gsd-core-windows-amd64.exe +0 -0
- data/bin/gsd-core-windows-arm64.exe +0 -0
- data/bin/gsd-core.exe +0 -0
- data/lib/gsd/agents/coordinator.rb +195 -0
- data/lib/gsd/agents/task_manager.rb +158 -0
- data/lib/gsd/agents/worker.rb +162 -0
- data/lib/gsd/agents.rb +30 -0
- data/lib/gsd/ai/chat.rb +486 -0
- data/lib/gsd/ai/cli.rb +248 -0
- data/lib/gsd/ai/command_parser.rb +97 -0
- data/lib/gsd/ai/commands/base.rb +42 -0
- data/lib/gsd/ai/commands/clear.rb +20 -0
- data/lib/gsd/ai/commands/context.rb +30 -0
- data/lib/gsd/ai/commands/cost.rb +30 -0
- data/lib/gsd/ai/commands/export.rb +42 -0
- data/lib/gsd/ai/commands/help.rb +61 -0
- data/lib/gsd/ai/commands/model.rb +67 -0
- data/lib/gsd/ai/commands/reset.rb +22 -0
- data/lib/gsd/ai/config.rb +256 -0
- data/lib/gsd/ai/context.rb +324 -0
- data/lib/gsd/ai/cost_tracker.rb +361 -0
- data/lib/gsd/ai/git_context.rb +169 -0
- data/lib/gsd/ai/history.rb +384 -0
- data/lib/gsd/ai/providers/anthropic.rb +429 -0
- data/lib/gsd/ai/providers/base.rb +282 -0
- data/lib/gsd/ai/providers/lmstudio.rb +279 -0
- data/lib/gsd/ai/providers/ollama.rb +336 -0
- data/lib/gsd/ai/providers/openai.rb +396 -0
- data/lib/gsd/ai/providers/openrouter.rb +429 -0
- data/lib/gsd/ai/reference_resolver.rb +225 -0
- data/lib/gsd/ai/repl.rb +349 -0
- data/lib/gsd/ai/streaming.rb +438 -0
- data/lib/gsd/ai/ui.rb +429 -0
- data/lib/gsd/buddy/cli.rb +284 -0
- data/lib/gsd/buddy/gacha.rb +148 -0
- data/lib/gsd/buddy/renderer.rb +108 -0
- data/lib/gsd/buddy/species.rb +190 -0
- data/lib/gsd/buddy/stats.rb +156 -0
- data/lib/gsd/buddy.rb +28 -0
- data/lib/gsd/cli.rb +455 -0
- data/lib/gsd/commands.rb +198 -0
- data/lib/gsd/config.rb +183 -0
- data/lib/gsd/error.rb +188 -0
- data/lib/gsd/frontmatter.rb +123 -0
- data/lib/gsd/go/bridge.rb +173 -0
- data/lib/gsd/history.rb +76 -0
- data/lib/gsd/milestone.rb +75 -0
- data/lib/gsd/output.rb +184 -0
- data/lib/gsd/phase.rb +102 -0
- data/lib/gsd/plugins/base.rb +92 -0
- data/lib/gsd/plugins/cli.rb +330 -0
- data/lib/gsd/plugins/config.rb +164 -0
- data/lib/gsd/plugins/hooks.rb +132 -0
- data/lib/gsd/plugins/installer.rb +158 -0
- data/lib/gsd/plugins/loader.rb +122 -0
- data/lib/gsd/plugins/manager.rb +187 -0
- data/lib/gsd/plugins/marketplace.rb +142 -0
- data/lib/gsd/plugins/sandbox.rb +114 -0
- data/lib/gsd/plugins/search.rb +131 -0
- data/lib/gsd/plugins/validator.rb +157 -0
- data/lib/gsd/plugins.rb +48 -0
- data/lib/gsd/profile.rb +127 -0
- data/lib/gsd/research.rb +85 -0
- data/lib/gsd/roadmap.rb +90 -0
- data/lib/gsd/skills/bundled/commit.md +58 -0
- data/lib/gsd/skills/bundled/debug.md +28 -0
- data/lib/gsd/skills/bundled/explain.md +41 -0
- data/lib/gsd/skills/bundled/plan.md +42 -0
- data/lib/gsd/skills/bundled/verify.md +26 -0
- data/lib/gsd/skills/loader.rb +189 -0
- data/lib/gsd/state.rb +102 -0
- data/lib/gsd/template.rb +106 -0
- data/lib/gsd/tools/ask_user_question.rb +179 -0
- data/lib/gsd/tools/base.rb +204 -0
- data/lib/gsd/tools/bash.rb +246 -0
- data/lib/gsd/tools/file_edit.rb +297 -0
- data/lib/gsd/tools/file_read.rb +199 -0
- data/lib/gsd/tools/file_write.rb +153 -0
- data/lib/gsd/tools/glob.rb +202 -0
- data/lib/gsd/tools/grep.rb +227 -0
- data/lib/gsd/tools/gsd_frontmatter.rb +165 -0
- data/lib/gsd/tools/gsd_phase.rb +140 -0
- data/lib/gsd/tools/gsd_roadmap.rb +108 -0
- data/lib/gsd/tools/gsd_state.rb +143 -0
- data/lib/gsd/tools/gsd_template.rb +157 -0
- data/lib/gsd/tools/gsd_verify.rb +159 -0
- data/lib/gsd/tools/registry.rb +103 -0
- data/lib/gsd/tools/task.rb +235 -0
- data/lib/gsd/tools/todo_write.rb +290 -0
- data/lib/gsd/tools/web.rb +260 -0
- data/lib/gsd/tui/app.rb +366 -0
- data/lib/gsd/tui/auto_complete.rb +79 -0
- data/lib/gsd/tui/colors.rb +111 -0
- data/lib/gsd/tui/command_palette.rb +126 -0
- data/lib/gsd/tui/header.rb +38 -0
- data/lib/gsd/tui/input_box.rb +199 -0
- data/lib/gsd/tui/spinner.rb +40 -0
- data/lib/gsd/tui/status_bar.rb +51 -0
- data/lib/gsd/tui.rb +17 -0
- data/lib/gsd/validator.rb +216 -0
- data/lib/gsd/verify.rb +175 -0
- data/lib/gsd/version.rb +5 -0
- data/lib/gsd/workstream.rb +91 -0
- metadata +231 -0
data/lib/gsd/ai/repl.rb
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
5
|
+
module Gsd
|
|
6
|
+
module AI
|
|
7
|
+
# REPL Interface - Interface de linha de comando interativa
|
|
8
|
+
#
|
|
9
|
+
# Responsável por:
|
|
10
|
+
# - Input do usuário com readline básico
|
|
11
|
+
# - Output formatado com cores
|
|
12
|
+
# - Comandos especiais do REPL
|
|
13
|
+
class Repl
|
|
14
|
+
attr_reader :chat, :config
|
|
15
|
+
|
|
16
|
+
# Comandos disponíveis no REPL
|
|
17
|
+
COMMANDS = {
|
|
18
|
+
'quit' => 'Sair do chat',
|
|
19
|
+
'exit' => 'Sair do chat',
|
|
20
|
+
'clear' => 'Limpar histórico',
|
|
21
|
+
'help' => 'Mostrar ajuda',
|
|
22
|
+
'stats' => 'Mostrar estatísticas',
|
|
23
|
+
'context' => 'Mostrar contexto atual',
|
|
24
|
+
'history' => 'Mostrar histórico',
|
|
25
|
+
'model' => 'Mudar modelo',
|
|
26
|
+
'provider' => 'Mudar provider',
|
|
27
|
+
'cost' => 'Mostrar custo acumulado',
|
|
28
|
+
'cache' => 'Mostrar informações do cache',
|
|
29
|
+
'reload' => 'Recarregar contexto',
|
|
30
|
+
'clear-cache' => 'Limpar cache de contexto'
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
# Inicializa o REPL
|
|
34
|
+
#
|
|
35
|
+
# @param chat [Chat] Instância do chat
|
|
36
|
+
# @param config [Hash] Configurações
|
|
37
|
+
def initialize(chat, config = {})
|
|
38
|
+
@chat = chat
|
|
39
|
+
@config = config
|
|
40
|
+
@verbose = config.fetch(:verbose, false)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Inicia o loop do REPL
|
|
44
|
+
#
|
|
45
|
+
# @return [void]
|
|
46
|
+
def start
|
|
47
|
+
print_banner
|
|
48
|
+
print_help if @config.fetch(:help, false)
|
|
49
|
+
|
|
50
|
+
loop do
|
|
51
|
+
begin
|
|
52
|
+
print_prompt
|
|
53
|
+
input = read_input
|
|
54
|
+
|
|
55
|
+
break if input.nil? || handle_command(input)
|
|
56
|
+
rescue Interrupt
|
|
57
|
+
puts "\n^C"
|
|
58
|
+
next
|
|
59
|
+
rescue => e
|
|
60
|
+
puts "\n❌ Erro: #{e.message}"
|
|
61
|
+
puts e.backtrace.first(5) if @verbose
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
cleanup
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Imprime o banner inicial
|
|
71
|
+
#
|
|
72
|
+
# @return [void]
|
|
73
|
+
def print_banner
|
|
74
|
+
puts "\n" + '=' * 60
|
|
75
|
+
puts '🤖 GSD AI Chat - Interface de Conversa'
|
|
76
|
+
puts '=' * 60
|
|
77
|
+
puts "Provider: #{@chat.provider.class}"
|
|
78
|
+
puts "Modelo: #{@chat.provider.model}"
|
|
79
|
+
puts "Debug: #{@chat.instance_variable_get(:@debug) ? 'ON' : 'OFF'}"
|
|
80
|
+
puts '=' * 60
|
|
81
|
+
puts "Digite 'help' para ver comandos disponíveis"
|
|
82
|
+
puts '=' * 60 + "\n"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Imprime o prompt de input
|
|
86
|
+
#
|
|
87
|
+
# @return [void]
|
|
88
|
+
def print_prompt
|
|
89
|
+
print "\n👤 "
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Lê input do usuário
|
|
93
|
+
#
|
|
94
|
+
# @return [String] Input do usuário
|
|
95
|
+
def read_input
|
|
96
|
+
$stdin.gets&.chomp
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Processa comandos especiais
|
|
100
|
+
#
|
|
101
|
+
# @param input [String] Input do usuário
|
|
102
|
+
# @return [Boolean] true se deve sair do REPL
|
|
103
|
+
def handle_command(input)
|
|
104
|
+
# Verifica se é um comando especial
|
|
105
|
+
if input.start_with?('/')
|
|
106
|
+
return handle_slash_command(input)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Comandos sem slash
|
|
110
|
+
parts = input.split(/\s+/, 2)
|
|
111
|
+
cmd = parts[0]&.downcase
|
|
112
|
+
args = parts[1]
|
|
113
|
+
|
|
114
|
+
case cmd
|
|
115
|
+
when 'quit', 'exit'
|
|
116
|
+
return true
|
|
117
|
+
when 'clear'
|
|
118
|
+
@chat.clear_history
|
|
119
|
+
puts "🧹 Histórico limpo!"
|
|
120
|
+
when 'help'
|
|
121
|
+
print_help
|
|
122
|
+
when 'stats'
|
|
123
|
+
print_stats
|
|
124
|
+
when 'context'
|
|
125
|
+
print_context
|
|
126
|
+
when 'history'
|
|
127
|
+
print_history
|
|
128
|
+
when 'cache'
|
|
129
|
+
print_cache_info
|
|
130
|
+
when 'reload'
|
|
131
|
+
reload_context
|
|
132
|
+
when 'clear-cache'
|
|
133
|
+
clear_cache
|
|
134
|
+
when 'model'
|
|
135
|
+
change_model(args) if args
|
|
136
|
+
when 'cost'
|
|
137
|
+
print_cost
|
|
138
|
+
when nil, ''
|
|
139
|
+
# Input vazio, ignora
|
|
140
|
+
else
|
|
141
|
+
# Não é um comando, envia para o chat
|
|
142
|
+
handle_chat_message(input)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Processa comandos com slash
|
|
149
|
+
#
|
|
150
|
+
# @param input [String] Input começando com /
|
|
151
|
+
# @return [Boolean] true se deve sair do REPL
|
|
152
|
+
def handle_slash_command(input)
|
|
153
|
+
parts = input[1..-1].split(/\s+/, 2)
|
|
154
|
+
cmd = parts[0]
|
|
155
|
+
args = parts[1]
|
|
156
|
+
|
|
157
|
+
case cmd
|
|
158
|
+
when 'q', 'quit', 'exit'
|
|
159
|
+
return true
|
|
160
|
+
when 'c', 'clear'
|
|
161
|
+
@chat.clear_history
|
|
162
|
+
puts "🧹 Histórico limpo!"
|
|
163
|
+
when 'h', 'help'
|
|
164
|
+
print_help
|
|
165
|
+
when 's', 'stats'
|
|
166
|
+
print_stats
|
|
167
|
+
when 'ctx', 'context'
|
|
168
|
+
print_context
|
|
169
|
+
when 'hist', 'history'
|
|
170
|
+
print_history
|
|
171
|
+
when 'm', 'model'
|
|
172
|
+
change_model(args) if args
|
|
173
|
+
when 'p', 'provider'
|
|
174
|
+
change_provider(args) if args
|
|
175
|
+
when 'cost'
|
|
176
|
+
print_cost
|
|
177
|
+
when 'v', 'verbose'
|
|
178
|
+
toggle_verbose
|
|
179
|
+
else
|
|
180
|
+
# Não é um comando conhecido, trata como mensagem
|
|
181
|
+
handle_chat_message(input)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
false
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Envia mensagem para o chat e exibe resposta
|
|
188
|
+
#
|
|
189
|
+
# @param message [String] Mensagem do usuário
|
|
190
|
+
# @return [void]
|
|
191
|
+
def handle_chat_message(message)
|
|
192
|
+
print "\n🤖 "
|
|
193
|
+
|
|
194
|
+
# Streaming da resposta
|
|
195
|
+
response = @chat.send(message, stream: true)
|
|
196
|
+
|
|
197
|
+
puts response
|
|
198
|
+
puts "\n" + '-' * 40
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Imprime ajuda
|
|
202
|
+
#
|
|
203
|
+
# @return [void]
|
|
204
|
+
def print_help
|
|
205
|
+
puts "\n📖 Comandos disponíveis:"
|
|
206
|
+
puts '-' * 40
|
|
207
|
+
|
|
208
|
+
COMMANDS.each do |cmd, desc|
|
|
209
|
+
puts " /#{cmd.ljust(10)} #{desc}"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
puts "\nDicas:"
|
|
213
|
+
puts " - Mensagens normais são enviadas para a IA"
|
|
214
|
+
puts " - Use / para comandos especiais"
|
|
215
|
+
puts " - Ctrl+C cancela a resposta atual"
|
|
216
|
+
puts " - Ctrl+D ou /quit sai do chat"
|
|
217
|
+
puts '-' * 40 + "\n"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Imprime estatísticas
|
|
221
|
+
#
|
|
222
|
+
# @return [void]
|
|
223
|
+
def print_stats
|
|
224
|
+
stats = @chat.stats
|
|
225
|
+
puts "\n📊 Estatísticas:"
|
|
226
|
+
puts '-' * 40
|
|
227
|
+
puts " Mensagens: #{stats[:messages_count]}"
|
|
228
|
+
puts " Tokens totais: #{stats[:total_tokens].to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1.').reverse}"
|
|
229
|
+
puts " Custo total: $#{stats[:total_cost].round(4)}"
|
|
230
|
+
puts " Tamanho do contexto: #{stats[:context_size]} KB"
|
|
231
|
+
puts '-' * 40 + "\n"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Imprime contexto atual
|
|
235
|
+
#
|
|
236
|
+
# @return [void]
|
|
237
|
+
def print_context
|
|
238
|
+
context = @chat.context
|
|
239
|
+
puts "\n📁 Contexto atual:"
|
|
240
|
+
puts '-' * 40
|
|
241
|
+
|
|
242
|
+
if context.state
|
|
243
|
+
puts " State:"
|
|
244
|
+
puts " - Current Phase: #{context.state['current_phase']}"
|
|
245
|
+
puts " - Progress: #{context.state['progress']}"
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
puts '-' * 40 + "\n"
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Imprime histórico
|
|
252
|
+
#
|
|
253
|
+
# @return [void]
|
|
254
|
+
def print_history
|
|
255
|
+
history = @chat.history
|
|
256
|
+
puts "\n📜 Histórico (últimas 10 mensagens):"
|
|
257
|
+
puts '-' * 40
|
|
258
|
+
|
|
259
|
+
history.messages.last(10).each_with_index do |msg, i|
|
|
260
|
+
role = msg[:role] == 'user' ? '👤' : '🤖'
|
|
261
|
+
content = msg[:content].to_s[0..100]
|
|
262
|
+
content += '...' if msg[:content].to_s.length > 100
|
|
263
|
+
puts " #{i + 1}. #{role} #{content}"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
puts '-' * 40 + "\n"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Muda o modelo
|
|
270
|
+
#
|
|
271
|
+
# @param model [String] Nome do modelo
|
|
272
|
+
# @return [void]
|
|
273
|
+
def change_model(model)
|
|
274
|
+
@chat.provider.model = model
|
|
275
|
+
puts "✅ Modelo alterado para: #{model}"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Muda o provider
|
|
279
|
+
#
|
|
280
|
+
# @param provider [String] Nome do provider
|
|
281
|
+
# @return [void]
|
|
282
|
+
def change_provider(provider)
|
|
283
|
+
puts "⚠️ Mudança de provider requer reinicialização"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Toggle verbose mode
|
|
287
|
+
#
|
|
288
|
+
# @return [void]
|
|
289
|
+
def toggle_verbose
|
|
290
|
+
@verbose = !@verbose
|
|
291
|
+
puts "✅ Verbose: #{@verbose ? 'ON' : 'OFF'}"
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Imprime informações do cache
|
|
295
|
+
#
|
|
296
|
+
# @return [void]
|
|
297
|
+
def print_cache_info
|
|
298
|
+
cache_info = @chat.cache_info
|
|
299
|
+
puts "\n💾 Informações do Cache:"
|
|
300
|
+
puts '-' * 40
|
|
301
|
+
puts " Carregado em: #{cache_info[:loaded_at] || 'N/A'}"
|
|
302
|
+
puts " Idade: #{cache_info[:age_seconds]}s"
|
|
303
|
+
puts " TTL: #{cache_info[:ttl_seconds]}s"
|
|
304
|
+
puts " Expirado: #{cache_info[:expired] ? 'Sim' : 'Não'}"
|
|
305
|
+
puts " Hash: #{cache_info[:hash] ? cache_info[:hash][0..12] + '...' : 'N/A'}"
|
|
306
|
+
puts " Tamanho: #{cache_info[:size_kb].round(2)} KB"
|
|
307
|
+
puts '-' * 40 + "\n"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Recarrega o contexto
|
|
311
|
+
#
|
|
312
|
+
# @return [void]
|
|
313
|
+
def reload_context
|
|
314
|
+
puts "🔄 Recarregando contexto..."
|
|
315
|
+
@chat.reload_context
|
|
316
|
+
puts "✅ Contexto recarregado!"
|
|
317
|
+
print_cache_info
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Limpa o cache
|
|
321
|
+
#
|
|
322
|
+
# @return [void]
|
|
323
|
+
def clear_cache
|
|
324
|
+
puts "🧹 Limpando cache..."
|
|
325
|
+
@chat.clear_cache
|
|
326
|
+
puts "✅ Cache limpo!"
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Imprime custo
|
|
330
|
+
#
|
|
331
|
+
# @return [void]
|
|
332
|
+
def print_cost
|
|
333
|
+
stats = @chat.stats
|
|
334
|
+
puts "\n💰 Custo acumulado:"
|
|
335
|
+
puts '-' * 40
|
|
336
|
+
puts " Tokens: #{stats[:total_tokens]}"
|
|
337
|
+
puts " Total: $#{stats[:total_cost].round(6)}"
|
|
338
|
+
puts '-' * 40 + "\n"
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Cleanup antes de sair
|
|
342
|
+
#
|
|
343
|
+
# @return [void]
|
|
344
|
+
def cleanup
|
|
345
|
+
puts "\n👋 Até logo!"
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|