Packages
fnord
0.8.16
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/cmd/ask.ex
defmodule Cmd.Ask do
@default_rounds 1
@behaviour Cmd
@impl Cmd
def requires_project?(), do: true
@impl Cmd
def spec() do
[
ask: [
name: "ask",
about: "Ask the AI a question about the project",
options: [
project: Cmd.project_arg(),
workers: Cmd.workers_arg(),
question: [
value_name: "QUESTION",
long: "--question",
short: "-q",
help: "The prompt to ask the AI",
required: true
],
rounds: [
value_name: "ROUNDS",
long: "--rounds",
short: "-R",
help:
"The number of research rounds to perform. Additional rounds generally result in more thorough research.",
parser: :integer,
default: @default_rounds,
required: false
],
follow: [
value_name: "UUID",
long: "--follow",
short: "-f",
help: "Continue an existing conversation by UUID",
required: false
]
],
flags: [
replay: [
long: "--replay",
short: "-r",
help: "Replay a conversation (with --follow)"
],
edit: [
long: "--edit",
short: "-e",
help: "Permit the AI to edit files in the project"
]
]
]
]
end
@impl Cmd
def run(opts, _subcommands, _unknown) do
opts =
if opts[:edit] do
UI.warning_banner("EDITING MODE ENABLED! THE AI CAN MODIFY FILES. YOU MUST BE NUTS.")
opts
else
Map.put(opts, :edit, false)
end
start_time = System.monotonic_time(:second)
with {:ok, opts} <- validate(opts),
{:ok, msgs, conversation} <- restore_conversation(opts),
opts <- Map.put(opts, :msgs, msgs),
{:ok, msgs, usage, context} <- get_response(opts),
{:ok, conversation_id} <- save_conversation(conversation, msgs) do
end_time = System.monotonic_time(:second)
print_summary(start_time, end_time, usage, context, conversation_id)
else
{:error, :invalid_rounds} ->
UI.error("--rounds expects a positive integer")
{:error, :conversation_not_found} ->
UI.error("Conversation ID #{opts[:conversation]} not found")
{:error, other} ->
UI.error("An error occurred while generating the response:\n\n#{other}")
{:ok, :testing} ->
:ok
end
end
defp get_response(opts) do
with %{msgs: msgs, usage: usage, context: context} <- AI.Agent.Coordinator.get_response(opts) do
{:ok, msgs, usage, context}
end
end
defp print_summary(start_time, end_time, usage, context, conversation_id) do
time_taken = end_time - start_time
pct_context_used = Float.round(usage / context * 100, 2)
usage_str = Util.format_number(usage)
context_str = Util.format_number(context)
{:ok, project} = Store.get_project()
%{new: new, stale: stale, deleted: deleted} = Store.Project.index_status(project)
UI.say("""
### Response Summary:
- Response generated in #{time_taken} seconds
- Tokens used: #{usage_str} | #{pct_context_used}% of context window (#{context_str})
- Conversation saved with ID #{conversation_id} (_copied to clipboard_)
### Project Search Index Status:
- Stale: #{Enum.count(stale)}
- New: #{Enum.count(new)}
- Deleted: #{Enum.count(deleted)}
_Run `fnord index` to update the index._
""")
UI.flush()
end
defp validate(opts) do
with :ok <- validate_conversation(opts),
:ok <- validate_rounds(opts) do
{:ok, opts}
end
end
defp validate_rounds(%{rounds: rounds}) when rounds > 0, do: :ok
defp validate_rounds(_opts), do: {:error, :invalid_rounds}
defp validate_conversation(%{follow: id}) when is_binary(id) do
id
|> Store.Project.Conversation.new()
|> Store.Project.Conversation.exists?()
|> case do
true -> :ok
false -> {:error, :conversation_not_found}
end
end
defp validate_conversation(_opts), do: :ok
defp get_conversation(%{follow: id}) when is_binary(id) do
{:ok, Store.Project.Conversation.new(id)}
end
defp get_conversation(_opts) do
{:ok, Store.Project.Conversation.new()}
end
defp restore_conversation(opts) do
with {:ok, conversation} <- get_conversation(opts) do
messages =
if Store.Project.Conversation.exists?(conversation) do
{:ok, _ts, messages} = Store.Project.Conversation.read(conversation)
messages
else
[]
end
{:ok, messages, conversation}
end
end
defp save_conversation(conversation, messages) do
Store.Project.Conversation.write(conversation, messages)
UI.debug("Conversation saved to file", conversation.store_path)
UI.report_step("Conversation saved", conversation.id)
{:ok, conversation.id}
end
end