Navegador

feat: enhanced navegador init with storage, LLM, and cluster config Generates .navegador/config.toml with [storage], [llm], and [cluster] sections. New CLI flags: --llm-provider, --llm-model, --cluster. Closes #24

lmata 2026-03-23 04:52 trunk
Commit c4536b2a00c4b4012e38dc58e657b9caebe4afa3a95b98b85087cf8b12ddf8fd
--- navegador/cli/commands.py
+++ navegador/cli/commands.py
@@ -65,26 +65,44 @@
6565
"--redis",
6666
"redis_url",
6767
default="",
6868
help="Redis URL for centralized/production mode (e.g. redis://host:6379).",
6969
)
70
-def init(path: str, redis_url: str):
70
+@click.option(
71
+ "--llm-provider",
72
+ default="",
73
+ help="LLM provider (e.g. anthropic, openai, ollama).",
74
+)
75
+@click.option("--llm-model", default="", help="LLM model name.")
76
+@click.option("--cluster", is_flag=True, help="Enable cluster/swarm mode.")
77
+def init(path: str, redis_url: str, llm_provider: str, llm_model: str, cluster: bool):
7178
"""Initialise navegador in a project directory.
7279
73
- Creates .navegador/ (gitignored), writes .env.example with storage options.
80
+ Creates .navegador/ (gitignored), writes config.toml with storage,
81
+ LLM, and cluster settings.
7482
7583
\b
7684
Local SQLite (default — zero infra):
7785
navegador init
7886
7987
Centralized Redis (production / multi-agent):
8088
navegador init --redis redis://host:6379
81
- # Then: export NAVEGADOR_REDIS_URL=redis://host:6379
89
+
90
+ With LLM:
91
+ navegador init --llm-provider anthropic --llm-model claude-sonnet-4-6
8292
"""
8393
from navegador.config import init_project
8494
85
- nav_dir = init_project(path)
95
+ storage = "redis" if redis_url else "sqlite"
96
+ nav_dir = init_project(
97
+ path,
98
+ storage=storage,
99
+ redis_url=redis_url,
100
+ llm_provider=llm_provider,
101
+ llm_model=llm_model,
102
+ cluster=cluster,
103
+ )
86104
console.print(f"[green]Initialised navegador[/green] → {nav_dir}")
87105
88106
if redis_url:
89107
console.print(
90108
f"\n[bold]Redis mode:[/bold] set [cyan]NAVEGADOR_REDIS_URL={redis_url}[/cyan] "
@@ -94,10 +112,16 @@
94112
console.print(
95113
"\n[bold]Local SQLite mode[/bold] (default). "
96114
"To use a shared Redis graph set [cyan]NAVEGADOR_REDIS_URL[/cyan]."
97115
)
98116
117
+ if llm_provider:
118
+ console.print(f"\n[bold]LLM:[/bold] {llm_provider} / {llm_model or '(default)'}")
119
+
120
+ if cluster:
121
+ console.print("\n[bold]Cluster mode:[/bold] enabled")
122
+
99123
console.print("\nNext: [bold]navegador ingest .[/bold]")
100124
101125
102126
# ── CODE: ingest ──────────────────────────────────────────────────────────────
103127
104128
--- navegador/cli/commands.py
+++ navegador/cli/commands.py
@@ -65,26 +65,44 @@
65 "--redis",
66 "redis_url",
67 default="",
68 help="Redis URL for centralized/production mode (e.g. redis://host:6379).",
69 )
70 def init(path: str, redis_url: str):
 
 
 
 
 
 
 
71 """Initialise navegador in a project directory.
72
73 Creates .navegador/ (gitignored), writes .env.example with storage options.
 
74
75 \b
76 Local SQLite (default — zero infra):
77 navegador init
78
79 Centralized Redis (production / multi-agent):
80 navegador init --redis redis://host:6379
81 # Then: export NAVEGADOR_REDIS_URL=redis://host:6379
 
 
82 """
83 from navegador.config import init_project
84
85 nav_dir = init_project(path)
 
 
 
 
 
 
 
 
86 console.print(f"[green]Initialised navegador[/green] → {nav_dir}")
87
88 if redis_url:
89 console.print(
90 f"\n[bold]Redis mode:[/bold] set [cyan]NAVEGADOR_REDIS_URL={redis_url}[/cyan] "
@@ -94,10 +112,16 @@
94 console.print(
95 "\n[bold]Local SQLite mode[/bold] (default). "
96 "To use a shared Redis graph set [cyan]NAVEGADOR_REDIS_URL[/cyan]."
97 )
98
 
 
 
 
 
 
99 console.print("\nNext: [bold]navegador ingest .[/bold]")
100
101
102 # ── CODE: ingest ──────────────────────────────────────────────────────────────
103
104
--- navegador/cli/commands.py
+++ navegador/cli/commands.py
@@ -65,26 +65,44 @@
65 "--redis",
66 "redis_url",
67 default="",
68 help="Redis URL for centralized/production mode (e.g. redis://host:6379).",
69 )
70 @click.option(
71 "--llm-provider",
72 default="",
73 help="LLM provider (e.g. anthropic, openai, ollama).",
74 )
75 @click.option("--llm-model", default="", help="LLM model name.")
76 @click.option("--cluster", is_flag=True, help="Enable cluster/swarm mode.")
77 def init(path: str, redis_url: str, llm_provider: str, llm_model: str, cluster: bool):
78 """Initialise navegador in a project directory.
79
80 Creates .navegador/ (gitignored), writes config.toml with storage,
81 LLM, and cluster settings.
82
83 \b
84 Local SQLite (default — zero infra):
85 navegador init
86
87 Centralized Redis (production / multi-agent):
88 navegador init --redis redis://host:6379
89
90 With LLM:
91 navegador init --llm-provider anthropic --llm-model claude-sonnet-4-6
92 """
93 from navegador.config import init_project
94
95 storage = "redis" if redis_url else "sqlite"
96 nav_dir = init_project(
97 path,
98 storage=storage,
99 redis_url=redis_url,
100 llm_provider=llm_provider,
101 llm_model=llm_model,
102 cluster=cluster,
103 )
104 console.print(f"[green]Initialised navegador[/green] → {nav_dir}")
105
106 if redis_url:
107 console.print(
108 f"\n[bold]Redis mode:[/bold] set [cyan]NAVEGADOR_REDIS_URL={redis_url}[/cyan] "
@@ -94,10 +112,16 @@
112 console.print(
113 "\n[bold]Local SQLite mode[/bold] (default). "
114 "To use a shared Redis graph set [cyan]NAVEGADOR_REDIS_URL[/cyan]."
115 )
116
117 if llm_provider:
118 console.print(f"\n[bold]LLM:[/bold] {llm_provider} / {llm_model or '(default)'}")
119
120 if cluster:
121 console.print("\n[bold]Cluster mode:[/bold] enabled")
122
123 console.print("\nNext: [bold]navegador ingest .[/bold]")
124
125
126 # ── CODE: ingest ──────────────────────────────────────────────────────────────
127
128
--- navegador/config.py
+++ navegador/config.py
@@ -53,17 +53,25 @@
5353
5454
# 4. Default SQLite path (or explicit --db default)
5555
return GraphStore.sqlite(db_path or DEFAULT_DB_PATH)
5656
5757
58
-def init_project(project_dir: str | Path = ".") -> Path:
58
+def init_project(
59
+ project_dir: str | Path = ".",
60
+ storage: str = "sqlite",
61
+ redis_url: str = "",
62
+ llm_provider: str = "",
63
+ llm_model: str = "",
64
+ cluster: bool = False,
65
+) -> Path:
5966
"""
6067
Initialise a .navegador/ directory in the project.
6168
6269
Creates:
63
- .navegador/ — DB and config directory (should be gitignored)
64
- .navegador/.env.example — example env file showing config options
70
+ .navegador/ — DB and config directory (should be gitignored)
71
+ .navegador/.env.example — example env file showing config options
72
+ .navegador/config.toml — project-specific configuration
6573
"""
6674
project_dir = Path(project_dir).resolve()
6775
nav_dir = project_dir / ".navegador"
6876
nav_dir.mkdir(parents=True, exist_ok=True)
6977
@@ -77,10 +85,35 @@
7785
"# Redis/FalkorDB (centralized — production, multi-agent)\n"
7886
"# NAVEGADOR_REDIS_URL=redis://localhost:6379\n",
7987
encoding="utf-8",
8088
)
8189
90
+ # Write config.toml
91
+ config_path = nav_dir / "config.toml"
92
+ config_lines = [
93
+ "# Navegador project configuration",
94
+ "# Generated by: navegador init",
95
+ "",
96
+ "[storage]",
97
+ f'backend = "{storage}"',
98
+ ]
99
+ if storage == "redis" and redis_url:
100
+ config_lines.append(f'redis_url = "{redis_url}"')
101
+ else:
102
+ config_lines.append(f'db_path = "{DEFAULT_DB_PATH}"')
103
+
104
+ config_lines += [
105
+ "",
106
+ "[llm]",
107
+ f'provider = "{llm_provider}"',
108
+ f'model = "{llm_model}"',
109
+ "",
110
+ "[cluster]",
111
+ f"enabled = {'true' if cluster else 'false'}",
112
+ ]
113
+ config_path.write_text("\n".join(config_lines) + "\n", encoding="utf-8")
114
+
82115
gitignore = project_dir / ".gitignore"
83116
if gitignore.exists():
84117
content = gitignore.read_text(encoding="utf-8")
85118
if ".navegador/" not in content:
86119
with gitignore.open("a", encoding="utf-8") as f:
87120
--- navegador/config.py
+++ navegador/config.py
@@ -53,17 +53,25 @@
53
54 # 4. Default SQLite path (or explicit --db default)
55 return GraphStore.sqlite(db_path or DEFAULT_DB_PATH)
56
57
58 def init_project(project_dir: str | Path = ".") -> Path:
 
 
 
 
 
 
 
59 """
60 Initialise a .navegador/ directory in the project.
61
62 Creates:
63 .navegador/ — DB and config directory (should be gitignored)
64 .navegador/.env.example — example env file showing config options
 
65 """
66 project_dir = Path(project_dir).resolve()
67 nav_dir = project_dir / ".navegador"
68 nav_dir.mkdir(parents=True, exist_ok=True)
69
@@ -77,10 +85,35 @@
77 "# Redis/FalkorDB (centralized — production, multi-agent)\n"
78 "# NAVEGADOR_REDIS_URL=redis://localhost:6379\n",
79 encoding="utf-8",
80 )
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82 gitignore = project_dir / ".gitignore"
83 if gitignore.exists():
84 content = gitignore.read_text(encoding="utf-8")
85 if ".navegador/" not in content:
86 with gitignore.open("a", encoding="utf-8") as f:
87
--- navegador/config.py
+++ navegador/config.py
@@ -53,17 +53,25 @@
53
54 # 4. Default SQLite path (or explicit --db default)
55 return GraphStore.sqlite(db_path or DEFAULT_DB_PATH)
56
57
58 def init_project(
59 project_dir: str | Path = ".",
60 storage: str = "sqlite",
61 redis_url: str = "",
62 llm_provider: str = "",
63 llm_model: str = "",
64 cluster: bool = False,
65 ) -> Path:
66 """
67 Initialise a .navegador/ directory in the project.
68
69 Creates:
70 .navegador/ — DB and config directory (should be gitignored)
71 .navegador/.env.example — example env file showing config options
72 .navegador/config.toml — project-specific configuration
73 """
74 project_dir = Path(project_dir).resolve()
75 nav_dir = project_dir / ".navegador"
76 nav_dir.mkdir(parents=True, exist_ok=True)
77
@@ -77,10 +85,35 @@
85 "# Redis/FalkorDB (centralized — production, multi-agent)\n"
86 "# NAVEGADOR_REDIS_URL=redis://localhost:6379\n",
87 encoding="utf-8",
88 )
89
90 # Write config.toml
91 config_path = nav_dir / "config.toml"
92 config_lines = [
93 "# Navegador project configuration",
94 "# Generated by: navegador init",
95 "",
96 "[storage]",
97 f'backend = "{storage}"',
98 ]
99 if storage == "redis" and redis_url:
100 config_lines.append(f'redis_url = "{redis_url}"')
101 else:
102 config_lines.append(f'db_path = "{DEFAULT_DB_PATH}"')
103
104 config_lines += [
105 "",
106 "[llm]",
107 f'provider = "{llm_provider}"',
108 f'model = "{llm_model}"',
109 "",
110 "[cluster]",
111 f"enabled = {'true' if cluster else 'false'}",
112 ]
113 config_path.write_text("\n".join(config_lines) + "\n", encoding="utf-8")
114
115 gitignore = project_dir / ".gitignore"
116 if gitignore.exists():
117 content = gitignore.read_text(encoding="utf-8")
118 if ".navegador/" not in content:
119 with gitignore.open("a", encoding="utf-8") as f:
120
--- tests/test_cli.py
+++ tests/test_cli.py
@@ -48,10 +48,24 @@
4848
with runner.isolated_filesystem():
4949
result = runner.invoke(main, ["init", "."])
5050
assert result.exit_code == 0
5151
assert "Local SQLite" in result.output
5252
53
+ def test_llm_provider_shown(self):
54
+ runner = CliRunner()
55
+ with runner.isolated_filesystem():
56
+ result = runner.invoke(main, ["init", ".", "--llm-provider", "anthropic"])
57
+ assert result.exit_code == 0
58
+ assert "anthropic" in result.output
59
+
60
+ def test_cluster_flag_shown(self):
61
+ runner = CliRunner()
62
+ with runner.isolated_filesystem():
63
+ result = runner.invoke(main, ["init", ".", "--cluster"])
64
+ assert result.exit_code == 0
65
+ assert "Cluster mode" in result.output
66
+
5367
5468
# ── ingest ────────────────────────────────────────────────────────────────────
5569
5670
class TestIngestCommand:
5771
def test_outputs_table_on_success(self):
5872
--- tests/test_cli.py
+++ tests/test_cli.py
@@ -48,10 +48,24 @@
48 with runner.isolated_filesystem():
49 result = runner.invoke(main, ["init", "."])
50 assert result.exit_code == 0
51 assert "Local SQLite" in result.output
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
54 # ── ingest ────────────────────────────────────────────────────────────────────
55
56 class TestIngestCommand:
57 def test_outputs_table_on_success(self):
58
--- tests/test_cli.py
+++ tests/test_cli.py
@@ -48,10 +48,24 @@
48 with runner.isolated_filesystem():
49 result = runner.invoke(main, ["init", "."])
50 assert result.exit_code == 0
51 assert "Local SQLite" in result.output
52
53 def test_llm_provider_shown(self):
54 runner = CliRunner()
55 with runner.isolated_filesystem():
56 result = runner.invoke(main, ["init", ".", "--llm-provider", "anthropic"])
57 assert result.exit_code == 0
58 assert "anthropic" in result.output
59
60 def test_cluster_flag_shown(self):
61 runner = CliRunner()
62 with runner.isolated_filesystem():
63 result = runner.invoke(main, ["init", ".", "--cluster"])
64 assert result.exit_code == 0
65 assert "Cluster mode" in result.output
66
67
68 # ── ingest ────────────────────────────────────────────────────────────────────
69
70 class TestIngestCommand:
71 def test_outputs_table_on_success(self):
72
--- tests/test_config.py
+++ tests/test_config.py
@@ -124,5 +124,54 @@
124124
with tempfile.TemporaryDirectory() as tmpdir:
125125
from navegador.config import init_project
126126
result = init_project(tmpdir)
127127
assert isinstance(result, Path)
128128
assert result == Path(tmpdir).resolve() / ".navegador"
129
+
130
+ def test_creates_config_toml(self):
131
+ with tempfile.TemporaryDirectory() as tmpdir:
132
+ from navegador.config import init_project
133
+ nav_dir = init_project(tmpdir)
134
+ config = nav_dir / "config.toml"
135
+ assert config.exists()
136
+ content = config.read_text()
137
+ assert "[storage]" in content
138
+ assert "[llm]" in content
139
+ assert "[cluster]" in content
140
+
141
+ def test_config_toml_sqlite_defaults(self):
142
+ with tempfile.TemporaryDirectory() as tmpdir:
143
+ from navegador.config import init_project
144
+ nav_dir = init_project(tmpdir)
145
+ content = (nav_dir / "config.toml").read_text()
146
+ assert 'backend = "sqlite"' in content
147
+ assert "db_path" in content
148
+
149
+ def test_config_toml_redis_mode(self):
150
+ with tempfile.TemporaryDirectory() as tmpdir:
151
+ from navegador.config import init_project
152
+ nav_dir = init_project(tmpdir, storage="redis", redis_url="redis://host:6379")
153
+ content = (nav_dir / "config.toml").read_text()
154
+ assert 'backend = "redis"' in content
155
+ assert 'redis_url = "redis://host:6379"' in content
156
+
157
+ def test_config_toml_llm_settings(self):
158
+ with tempfile.TemporaryDirectory() as tmpdir:
159
+ from navegador.config import init_project
160
+ nav_dir = init_project(tmpdir, llm_provider="anthropic", llm_model="claude-sonnet-4-6")
161
+ content = (nav_dir / "config.toml").read_text()
162
+ assert 'provider = "anthropic"' in content
163
+ assert 'model = "claude-sonnet-4-6"' in content
164
+
165
+ def test_config_toml_cluster_enabled(self):
166
+ with tempfile.TemporaryDirectory() as tmpdir:
167
+ from navegador.config import init_project
168
+ nav_dir = init_project(tmpdir, cluster=True)
169
+ content = (nav_dir / "config.toml").read_text()
170
+ assert "enabled = true" in content
171
+
172
+ def test_config_toml_cluster_disabled_by_default(self):
173
+ with tempfile.TemporaryDirectory() as tmpdir:
174
+ from navegador.config import init_project
175
+ nav_dir = init_project(tmpdir)
176
+ content = (nav_dir / "config.toml").read_text()
177
+ assert "enabled = false" in content
129178
--- tests/test_config.py
+++ tests/test_config.py
@@ -124,5 +124,54 @@
124 with tempfile.TemporaryDirectory() as tmpdir:
125 from navegador.config import init_project
126 result = init_project(tmpdir)
127 assert isinstance(result, Path)
128 assert result == Path(tmpdir).resolve() / ".navegador"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
--- tests/test_config.py
+++ tests/test_config.py
@@ -124,5 +124,54 @@
124 with tempfile.TemporaryDirectory() as tmpdir:
125 from navegador.config import init_project
126 result = init_project(tmpdir)
127 assert isinstance(result, Path)
128 assert result == Path(tmpdir).resolve() / ".navegador"
129
130 def test_creates_config_toml(self):
131 with tempfile.TemporaryDirectory() as tmpdir:
132 from navegador.config import init_project
133 nav_dir = init_project(tmpdir)
134 config = nav_dir / "config.toml"
135 assert config.exists()
136 content = config.read_text()
137 assert "[storage]" in content
138 assert "[llm]" in content
139 assert "[cluster]" in content
140
141 def test_config_toml_sqlite_defaults(self):
142 with tempfile.TemporaryDirectory() as tmpdir:
143 from navegador.config import init_project
144 nav_dir = init_project(tmpdir)
145 content = (nav_dir / "config.toml").read_text()
146 assert 'backend = "sqlite"' in content
147 assert "db_path" in content
148
149 def test_config_toml_redis_mode(self):
150 with tempfile.TemporaryDirectory() as tmpdir:
151 from navegador.config import init_project
152 nav_dir = init_project(tmpdir, storage="redis", redis_url="redis://host:6379")
153 content = (nav_dir / "config.toml").read_text()
154 assert 'backend = "redis"' in content
155 assert 'redis_url = "redis://host:6379"' in content
156
157 def test_config_toml_llm_settings(self):
158 with tempfile.TemporaryDirectory() as tmpdir:
159 from navegador.config import init_project
160 nav_dir = init_project(tmpdir, llm_provider="anthropic", llm_model="claude-sonnet-4-6")
161 content = (nav_dir / "config.toml").read_text()
162 assert 'provider = "anthropic"' in content
163 assert 'model = "claude-sonnet-4-6"' in content
164
165 def test_config_toml_cluster_enabled(self):
166 with tempfile.TemporaryDirectory() as tmpdir:
167 from navegador.config import init_project
168 nav_dir = init_project(tmpdir, cluster=True)
169 content = (nav_dir / "config.toml").read_text()
170 assert "enabled = true" in content
171
172 def test_config_toml_cluster_disabled_by_default(self):
173 with tempfile.TemporaryDirectory() as tmpdir:
174 from navegador.config import init_project
175 nav_dir = init_project(tmpdir)
176 content = (nav_dir / "config.toml").read_text()
177 assert "enabled = false" in content
178

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button