Packages
fnord
0.9.7
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/frobs.ex
defmodule Cmd.Frobs do
@behaviour Cmd
@impl Cmd
def requires_project?(), do: false
@impl Cmd
def spec do
[
frobs: [
name: "frobs",
about: "Manages external tool call integrations (frobs)",
subcommands: [
create: [
name: "create",
about: "Creates a new frob",
options: [
name: [
value_name: "NAME",
long: "--name",
short: "-n",
help: "Name of the frob",
required: true
]
]
],
check: [
name: "check",
about: "Validates a frob",
options: [
name: [
value_name: "NAME",
long: "--name",
short: "-n",
help: "Name of the frob",
required: true
]
]
],
list: [
name: "list",
about: "Lists available frobs (use -a / --all to list all frobs)",
flags: [
all: [
long: "--all",
short: "-a",
help: "List all frobs, including disabled ones"
]
]
],
enable: [
name: "enable",
about: "Enables a frob",
options: [
name: [
value_name: "NAME",
long: "--name",
short: "-n",
help: "Name of the frob",
required: true
],
global: [
long: "--global",
help: "Apply to global settings (otherwise current project)",
takes_value: false
],
project: [
long: "--project",
value_name: "PROJECT",
help: "Apply to the named project (overrides selected project)"
]
]
],
disable: [
name: "disable",
about: "Disables a frob",
options: [
name: [
value_name: "NAME",
long: "--name",
short: "-n",
help: "Name of the frob",
required: true
],
global: [
long: "--global",
help: "Apply to global settings (otherwise current project)",
takes_value: false
],
project: [
long: "--project",
value_name: "PROJECT",
help: "Apply to the named project (overrides selected project)"
]
]
],
call: [
name: "call",
about: "Interactively test a frob by prompting for parameters and executing it",
options: [
name: [
value_name: "NAME",
long: "--name",
short: "-n",
help: "Name of the frob",
required: true
],
project: [
long: "--project",
value_name: "PROJECT",
help: "Project name (overrides auto-resolve)"
]
]
]
]
]
]
end
@impl Cmd
def run(opts, subcommands, _unknown) do
Services.Once.set(:frobs_cli_used, true)
try do
with {:ok, msg} <- call_subcommand(subcommands, opts) do
UI.puts(msg)
else
{:error, :invalid_subcommand} ->
UI.fatal("Invalid subcommand. Use `fnord help frobs` for available commands.")
{:error, reason} ->
UI.fatal("Error: #{reason}")
end
rescue
e in RuntimeError ->
UI.fatal("Error: #{e.message}")
end
end
defp call_subcommand(subcommands, opts) do
case subcommands do
[:create] -> create(opts)
[:check] -> check(opts)
[:list] -> list(opts)
[:enable] -> enable(opts)
[:disable] -> disable(opts)
[:call] -> call_frob(opts)
_ -> {:error, :invalid_subcommand}
end
end
defp call_frob(opts) do
name = opts[:name] || raise("--name is required")
project =
if is_binary(opts[:project]) and byte_size(opts[:project]) > 0 do
{:ok, opts[:project]}
else
ResolveProject.resolve()
end
with {:ok, _project} <- project,
{:ok, frob} <- Frobs.load(name) do
unless Settings.Frobs.enabled?(frob.name) do
{:error, "Frob '#{frob.name}' is disabled in settings"}
else
args_map =
UI.interact(fn ->
case Frobs.Prompt.prompt_for_params(frob.spec, UI) do
{:ok, m} -> m
other -> raise("Prompt error: #{inspect(other)}")
end
end)
case Frobs.perform_tool_call(name, Jason.encode!(args_map)) do
{:ok, output} -> {:ok, output}
{:error, exit_code, out} -> {:error, "exited with status #{exit_code}: #{out}"}
other -> {:error, "unexpected tool call result: #{inspect(other)}"}
end
end
else
{:error, :not_in_project} -> {:error, "Not in a project and --project not given"}
{:error, :frob_not_found} -> {:error, "Frob not found: #{name}"}
{:error, reason} -> {:error, reason}
end
end
defp create(%{name: name}) do
with {:ok, frob} <- Frobs.create(name) do
{:ok,
"""
--------------------------------------------------------------------------------
> Frob created
--------------------------------------------------------------------------------
- Name: `#{frob.name}`
- Home: `#{frob.home}`
- Spec: `#{frob.home}/spec.json`
- Main: `#{frob.home}/main`
--------------------------------------------------------------------------------
> Tips
--------------------------------------------------------------------------------
- Edit `main` to implement your integration.
- Update the spec in `spec.json` to define the frob's interface and parameters.
- Enable your frob via the settings CLI or the Settings.Frobs module.
Fnord communicates run-time information to the frob via environment variables:
- FNORD_PROJECT: The name of the currently selected project
- FNORD_CONFIG: JSON object of project config (see `$HOME/.fnord/settings.json`)
- FNORD_ARGS_JSON: JSON object of LLM-provided arguments (defined in your spec)
!!! You can remove the frob at any time by deleting the directory.
"""}
end
end
defp check(%{name: name}) do
with {:ok, _frob} <- Frobs.load(name) do
{:ok, "Frob #{name} appears to be valid!"}
end
end
defp list(opts) do
if opts[:all] do
Services.Once.run(
:frobs_migrate_registry_to_settings,
&Frobs.Migrate.maybe_migrate_registry_to_settings/0
)
base_dir = Path.join([Settings.get_user_home(), "fnord", "tools"])
frob_names =
case File.ls(base_dir) do
{:ok, entries} ->
entries |> Enum.filter(fn entry -> File.dir?(Path.join(base_dir, entry)) end)
_ ->
[]
end
global_list = Settings.Frobs.list(:global)
project_list =
case Settings.get_selected_project() do
{:ok, _} -> Settings.Frobs.list(:project)
_ -> []
end
frobs =
frob_names
|> Enum.map(fn name ->
spec_path = Path.join([base_dir, name, "spec.json"])
description =
case File.read(spec_path) do
{:ok, content} ->
case Jason.decode(content) do
{:ok, %{"description" => d}} when is_binary(d) -> d
_ -> "No description provided"
end
_ ->
"No description provided"
end
status =
cond do
name in global_list and name in project_list -> "Enabled (global and project)"
name in global_list -> "Enabled (global)"
name in project_list -> "Enabled (project)"
true -> "Disabled"
end
"""
- Name: #{name}
- Description: #{description}
- Location: #{Path.join([base_dir, name])}
- Status: #{status}
"""
end)
|> Enum.join("\n\n")
if frobs == "" do
{:ok, "No frobs found in tools directory"}
else
{:ok,
"""
--------------------------------------------------------------------------------
> Frobs
--------------------------------------------------------------------------------
#{frobs}
"""}
end
else
global_list = Settings.Frobs.list(:global)
project_list =
case Settings.get_selected_project() do
{:ok, _} -> Settings.Frobs.list(:project)
_ -> []
end
frobs =
Frobs.list()
|> Enum.map(fn frob ->
desc = Map.get(frob.spec, "description", "No description provided")
enabled = Settings.Frobs.enabled?(frob.name)
scope_info =
if enabled do
cond do
frob.name in global_list and frob.name in project_list ->
"Enabled (global and project)"
frob.name in global_list ->
"Enabled (global)"
frob.name in project_list ->
"Enabled (project)"
true ->
"Disabled"
end
else
"Disabled"
end
"""
- Name: #{frob.name}
- Description: #{desc}
- Location: #{frob.home}
- Status: #{scope_info}
"""
end)
|> Enum.join("\n\n")
if frobs == "" do
case Settings.get_selected_project() do
{:ok, project} ->
{:ok, "No frobs found for project #{project.name}"}
_ ->
{:ok, "No frobs found"}
end
else
{:ok,
"""
--------------------------------------------------------------------------------
> Frobs
--------------------------------------------------------------------------------
#{frobs}
"""}
end
end
end
defp enable(opts) do
name = opts[:name]
with {:ok, scope} <- resolve_scope(opts),
:ok <- Settings.Frobs.enable(scope, name) do
{:ok, "Frob #{name} enabled in #{scope_label(scope)} scope."}
end
end
defp disable(opts) do
name = opts[:name]
with {:ok, scope} <- resolve_scope(opts),
:ok <- Settings.Frobs.disable(scope, name) do
{:ok, "Frob #{name} disabled in #{scope_label(scope)} scope."}
end
end
defp resolve_scope(opts) do
cond do
opts[:global] ->
{:ok, :global}
is_binary(opts[:project]) and byte_size(opts[:project]) > 0 ->
{:ok, {:project, opts[:project]}}
true ->
case Settings.get_selected_project() do
{:ok, _pn} -> {:ok, :project}
_ -> raise "No project selected. Use --global or --project <name>."
end
end
end
defp scope_label(:global), do: "global"
defp scope_label({:project, name}), do: "project: #{name}"
defp scope_label(:project), do: "project (selected)"
end