Packages

fnord

0.4.4

AI code archaeology

Current section

221 Versions

Jump to

Compare versions

5 files changed
+109 additions
-5 deletions
  @@ -49,6 +49,12 @@ You can **reindex** the project, forcing it to reindex all files:
49 49 fnord index --project foo --dir /path/to/foo --reindex
50 50 ```
51 51
52 + You can also watch the project for changes and reindex them as they happen:
53 +
54 + ```bash
55 + fnord watch --project foo --dir /path/to/foo
56 + ```
57 +
52 58 ### Searching
53 59
54 60 ```bash
  @@ -72,6 +78,4 @@ Note that deleting a project only deletes from the index, not the actual files.
72 78
73 79 ## TODO
74 80
75 - - Daemon mode that watches for file changes and updates the index
76 - - Handle deleted files
77 81 - Progress bar
  @@ -1,6 +1,6 @@
1 1 {<<"links">>,[{<<"GitHub">>,<<"https://github.com/sysread/fnord">>}]}.
2 2 {<<"name">>,<<"fnord">>}.
3 - {<<"version">>,<<"0.4.3">>}.
3 + {<<"version">>,<<"0.4.4">>}.
4 4 {<<"description">>,
5 5 <<"Index and search your code base with OpenAI's embeddings API">>}.
6 6 {<<"elixir">>,<<"~> 1.17">>}.
  @@ -29,7 +29,7 @@
29 29 {<<"repository">>,<<"hexpm">>}]]}.
30 30 {<<"files">>,
31 31 [<<"lib">>,<<"lib/ai.ex">>,<<"lib/store.ex">>,<<"lib/queue.ex">>,
32 - <<"lib/git.ex">>,<<"lib/scanner.ex">>,<<"lib/fnord.ex">>,
32 + <<"lib/git.ex">>,<<"lib/scanner.ex">>,<<"lib/watch.ex">>,<<"lib/fnord.ex">>,
33 33 <<"lib/summary.ex">>,<<"lib/indexer.ex">>,<<"lib/search.ex">>,
34 34 <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>]}.
35 35 {<<"build_tools">>,[<<"mix">>]}.
  @@ -17,6 +17,7 @@ defmodule Fnord do
17 17 :torch -> Indexer.new(opts) |> Indexer.delete_project()
18 18 :projects -> Store.list_projects() |> Enum.each(&IO.puts(&1))
19 19 :files -> Store.new(opts.project) |> Store.list_files() |> Enum.each(&IO.puts(&1))
20 + :watch -> Watch.run(opts)
20 21 end
21 22 else
22 23 {:error, reason} -> IO.puts("Error: #{reason}")
  @@ -93,6 +94,7 @@ defmodule Fnord do
93 94 description: "fnord - intelligent code index and search",
94 95 about: "Index and search code files",
95 96 allow_unknown_args: false,
97 + version: get_version(),
96 98 subcommands: [
97 99 projects: [
98 100 name: "projects",
  @@ -145,6 +147,14 @@ defmodule Fnord do
145 147 project: project,
146 148 file: file
147 149 ]
150 + ],
151 + watch: [
152 + name: "watch",
153 + about: "Watch for changes in the project directory using Watchman",
154 + options: [
155 + directory: directory,
156 + project: project
157 + ]
148 158 ]
149 159 ]
150 160 )
  @@ -160,4 +170,9 @@ defmodule Fnord do
160 170 _ -> {:error, "missing or unknown subcommand"}
161 171 end
162 172 end
173 +
174 + defp get_version do
175 + {:ok, vsn} = :application.get_key(:fnord, :vsn)
176 + to_string(vsn)
177 + end
163 178 end
  @@ -0,0 +1,85 @@
1 + defmodule Watch do
2 + @moduledoc """
3 + Watch a directory for changes and run the fnord index command when changes
4 + are detected.
5 + """
6 +
7 + def run(opts) do
8 + with :ok <- check_dependencies() do
9 + cmd("watchman", ["watch", opts.directory])
10 + cmd("watchman", ["--log-level", "info"])
11 +
12 + IO.puts("Watching #{opts.directory} for changes")
13 + setup_trigger(opts.directory, opts.project)
14 +
15 + IO.puts("Press Ctrl+C to stop watching")
16 + wait_for_changes()
17 + end
18 + end
19 +
20 + defp cmd(command, args) do
21 + IO.puts("> #{command} #{Enum.join(args, " ")}")
22 + System.cmd(command, args, stderr_to_stdout: true)
23 + end
24 +
25 + defp shell(command) do
26 + IO.puts("> #{command}")
27 + System.shell(command)
28 + end
29 +
30 + defp check_dependencies do
31 + case System.find_executable("watchman") do
32 + nil -> {:error, "Watchman is required to run this module. Please install it and try again."}
33 + _ -> :ok
34 + end
35 + end
36 +
37 + defp setup_trigger(directory, project) do
38 + command_json =
39 + Jason.encode!([
40 + "trigger",
41 + directory,
42 + %{
43 + "name" => "fnord-trigger",
44 + "expression" => ["true"],
45 + "command" => ["fnord", "index", "-p", project, "-d", directory],
46 + "settle" => 5_000
47 + }
48 + ])
49 +
50 + shell("watchman -j <<EOF\n#{command_json}\nEOF")
51 + end
52 +
53 + defp wait_for_changes do
54 + log_path =
55 + cmd("watchman", ["get-log"])
56 + |> elem(0)
57 + |> Jason.decode!()
58 + |> get_in(["log"])
59 +
60 + IO.puts("> tail -f #{log_path}")
61 +
62 + # Open a port to execute `tail -f` and stream the output to the console
63 + port =
64 + Port.open({:spawn_executable, System.find_executable("tail")}, [
65 + :exit_status,
66 + :stderr_to_stdout,
67 + :stream,
68 + args: ["-f", log_path]
69 + ])
70 +
71 + # Continuously read from the port and print output to the console
72 + listen_to_port(port)
73 + end
74 +
75 + defp listen_to_port(port) do
76 + receive do
77 + {^port, {:data, data}} ->
78 + IO.write(data)
79 + listen_to_port(port)
80 +
81 + {^port, {:exit_status, status}} ->
82 + IO.puts("tail process exited with status: #{status}")
83 + end
84 + end
85 + end
  @@ -4,7 +4,7 @@ defmodule Fnord.MixProject do
4 4 def project do
5 5 [
6 6 app: :fnord,
7 - version: "0.4.3",
7 + version: "0.4.4",
8 8 elixir: "~> 1.17",
9 9 start_permanent: Mix.env() == :prod,
10 10 description: "Index and search your code base with OpenAI's embeddings API",