Packages
fnord
0.4.42
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/fnord.ex
defmodule Fnord do
@moduledoc """
Fnord is a code search tool that uses OpenAI's embeddings API to index and
search code files.
"""
@default_concurrency 8
@default_search_limit 10
@doc """
Main entry point for the application. Parses command line arguments and
dispatches to the appropriate subcommand.
"""
def main(args) do
{:ok, _} = Application.ensure_all_started(:briefly)
configure_logger()
with {:ok, subcommand, opts} <- parse_options(args) do
set_globals(opts)
case subcommand do
:index -> Cmd.Indexer.new(opts) |> Cmd.Indexer.run()
:ask -> Cmd.Ask.run(opts)
:search -> Cmd.Search.run(opts)
:summary -> Cmd.Summary.run(opts)
:torch -> Cmd.Torch.run(opts)
:projects -> Cmd.Projects.run(opts)
:files -> Cmd.Files.run(opts)
:upgrade -> Cmd.Upgrade.run(opts)
:review -> Cmd.Review.run(opts)
end
else
{:error, reason} -> IO.puts("Error: #{reason}")
end
end
defp parse_options(args) do
project = [
value_name: "PROJECT",
long: "--project",
short: "-p",
help: "Project name",
required: true
]
directory = [
value_name: "DIR",
long: "--dir",
short: "-d",
help: "Directory to index (required for first index or reindex after moving the project)",
required: false
]
reindex = [
long: "--reindex",
short: "-r",
help: "Reindex the project",
default: false
]
quiet = [
long: "--quiet",
short: "-q",
help: "Suppress interactive output; automatically enabled when executed in a pipe",
required: false
]
query = [
value_name: "QUERY",
long: "--query",
short: "-q",
help: "Search query",
required: true
]
limit = [
value_name: "LIMIT",
long: "--limit",
short: "-l",
help: "Limit the number of results",
default: @default_search_limit
]
detail = [
long: "--detail",
help: "Include AI-generated file summary",
default: false
]
file = [
value_name: "FILE",
long: "--file",
short: "-f",
help: "File to summarize",
required: true
]
concurrency = [
value_name: "WORKERS",
long: "--concurrency",
short: "-c",
help: "Number of concurrent threads to use",
parser: :integer,
default: @default_concurrency
]
question = [
value_name: "QUESTION",
long: "--question",
short: "-q",
help: "The prompt to ask the AI",
required: true
]
relpath = [
long: "--relpath",
short: "-r",
help: "Print paths relative to $CWD",
default: false
]
yes = [
long: "--yes",
short: "-y",
help: "Automatically answer 'yes' to all prompts",
default: false
]
include = [
value_name: "FILE",
long: "--include",
short: "-i",
help: "Include a file in your prompt",
multiple: true
]
exclude = [
value_name: "FILE",
long: "--exclude",
short: "-x",
help:
"Exclude a file, directory, or glob from being indexed; this is stored in the project's configuration and used on subsequent indexes",
multiple: true
]
topic = [
value_name: "TOPIC_BRANCH",
long: "--topic",
short: "-t",
help: "The topic branch",
required: true
]
base = [
value_name: "BASE_BRANCH",
long: "--base",
short: "-b",
help: "The base branch (default: main)",
default: "main",
required: true
]
show_work = [
long: "--show-work",
short: "-s",
help: "Display more of the research performed by the agent; disabled if --quiet is set"
]
parser =
Optimus.new!(
name: "fnord",
description: "fnord - intelligent code index and search",
allow_unknown_args: false,
version: get_version(),
subcommands: [
index: [
name: "index",
about: "Index a project",
options: [
directory: directory,
project: project,
concurrency: concurrency,
exclude: exclude
],
flags: [reindex: reindex, quiet: quiet]
],
ask: [
name: "ask",
about: "Ask the AI a question about the project",
options: [concurrency: concurrency, include: include],
args: [project: project, question: question],
flags: [quiet: quiet, show_work: show_work]
],
review: [
name: "review",
about:
"Review a topic branch against another branch. Note that this always uses the remote branches on origin.",
options: [project: project, concurrency: concurrency, topic: topic, base: base],
flags: [quiet: quiet, show_work: show_work]
],
search: [
name: "search",
about: "Perform a semantic search within a project",
options: [project: project, query: query, limit: limit, concurrency: concurrency],
flags: [detail: detail]
],
summary: [
name: "summary",
about: "Retrieve the AI-generated file summary used when indexing the file",
options: [project: project, file: file]
],
projects: [
name: "projects",
about: "Lists all projects",
options: []
],
files: [
name: "files",
about: "Lists all indexed files in a project",
options: [project: project],
flags: [relpath: relpath]
],
torch: [
name: "torch",
about: "Deletes a previously indexed project from the database",
options: [project: project]
],
upgrade: [
name: "upgrade",
about: "Upgrade fnord to the latest version",
flags: [yes: yes]
]
]
)
with {[subcommand], result} <- Optimus.parse!(parser, args) do
options =
result.args
|> Map.merge(result.options)
|> Map.merge(result.flags)
|> maybe_override_quiet()
{:ok, subcommand, options}
else
_ -> {:error, "missing or unknown subcommand"}
end
end
defp get_version do
{:ok, vsn} = :application.get_key(:fnord, :vsn)
to_string(vsn)
end
# Overrides the --quiet flag if it was not already specified by the user and
# the escript is not connected to a tty.
defp maybe_override_quiet(opts) do
cond do
opts[:quiet] -> opts
IO.ANSI.enabled?() -> opts
true -> Map.put(opts, :quiet, true)
end
end
def configure_logger do
{:ok, handler_config} = :logger.get_handler_config(:default)
updated_config = Map.update!(handler_config, :config, &Map.put(&1, :type, :standard_error))
:ok = :logger.remove_handler(:default)
:ok = :logger.add_handler(:default, :logger_std_h, updated_config)
:ok =
:logger.update_formatter_config(
:default,
:template,
["[", :level, "] ", :message, "\n"]
)
logger_level =
System.get_env("LOGGER_LEVEL", "info")
|> String.to_existing_atom()
:ok = :logger.set_primary_config(:level, logger_level)
end
defp set_globals(args) do
args
|> Enum.each(fn
{:concurrency, concurrency} -> Application.put_env(:fnord, :concurrency, concurrency)
{:project, project} -> Application.put_env(:fnord, :project, project)
{:quiet, quiet} -> Application.put_env(:fnord, :quiet, quiet)
_ -> :ok
end)
end
end