vectorgov-cli 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/.gitignore +4 -3
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/PKG-INFO +17 -2
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/README.md +16 -1
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/pyproject.toml +1 -1
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/__init__.py +1 -1
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/__init__.py +4 -1
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/ask.py +1 -3
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/context.py +0 -2
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/docs.py +2 -2
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/explain.py +3 -5
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/fs_search.py +4 -7
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/grep_cmd.py +3 -6
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/hybrid.py +4 -6
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/init.py +225 -227
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/lookup.py +2 -4
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/merged.py +3 -6
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/quota.py +130 -132
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/read.py +0 -2
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/search.py +0 -2
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/smart_search.py +3 -5
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/tokens.py +0 -2
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/main.py +21 -16
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/utils/output.py +131 -22
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/audit.py +0 -0
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/auth.py +0 -0
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/config.py +0 -0
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/feedback.py +0 -0
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/commands/prompts.py +0 -0
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/utils/__init__.py +0 -0
- {vectorgov_cli-0.3.0 → vectorgov_cli-0.3.2}/src/vectorgov/cli/utils/config.py +0 -0
|
@@ -12,6 +12,7 @@ __pycache__/
|
|
|
12
12
|
# Virtual environments
|
|
13
13
|
venv/
|
|
14
14
|
.venv/
|
|
15
|
+
.venv-test/
|
|
15
16
|
env/
|
|
16
17
|
|
|
17
18
|
# IDE
|
|
@@ -23,6 +24,6 @@ env/
|
|
|
23
24
|
# OS
|
|
24
25
|
.DS_Store
|
|
25
26
|
Thumbs.db
|
|
26
|
-
|
|
27
|
-
# Test notebooks (may contain API keys)
|
|
28
|
-
test_cli_commands.ipynb
|
|
27
|
+
|
|
28
|
+
# Test notebooks (may contain API keys)
|
|
29
|
+
test_cli_commands.ipynb
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vectorgov-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: CLI para a API VectorGov - Busca semântica em legislação brasileira
|
|
5
5
|
Project-URL: Homepage, https://vectorgov.io
|
|
6
6
|
Project-URL: Documentation, https://vectorgov.io/documentacao
|
|
@@ -60,7 +60,17 @@ Cliente de linha de comando para a API VectorGov - Busca semântica em legislaç
|
|
|
60
60
|
[](https://pypi.org/project/vectorgov-cli/)
|
|
61
61
|
[](https://opensource.org/licenses/MIT)
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
> **Novo em 0.3.2** — zero truncamento de conteúdo em todos os formatos
|
|
64
|
+
> de saída + TTY detection automática: quando stdout não é terminal
|
|
65
|
+
> (pipe, LLM, CI/CD), o formato padrão vira `llm` automaticamente. A
|
|
66
|
+
> tabela do `search` ganhou coluna `Norma` para identificar de qual lei
|
|
67
|
+
> cada artigo veio. Veja o [CHANGELOG](CHANGELOG.md#032---2026-04-15).
|
|
68
|
+
>
|
|
69
|
+
> **Novo em 0.3.1** — parsing GNU-style: flags e argumentos funcionam em
|
|
70
|
+
> qualquer ordem (`search "ETP" --top-k 3` e `search --top-k 3 "ETP"`
|
|
71
|
+
> fazem a mesma coisa).
|
|
72
|
+
|
|
73
|
+
## Para LLMs e agentes de IA (v0.3.2+)
|
|
64
74
|
|
|
65
75
|
Se voce e um LLM ou agente de IA usando o CLI do VectorGov em sessoes
|
|
66
76
|
de vibe coding, estas features foram projetadas para voce:
|
|
@@ -198,6 +208,11 @@ export VECTORGOV_API_KEY="vg_sua_chave"
|
|
|
198
208
|
|
|
199
209
|
## Uso
|
|
200
210
|
|
|
211
|
+
> **Ordem de argumentos**: o CLI aceita flags e argumentos posicionais em
|
|
212
|
+
> qualquer ordem (estilo GNU, igual a `git`, `curl`, `kubectl`, `npm`).
|
|
213
|
+
> `vectorgov search "ETP" --top-k 3` e `vectorgov search --top-k 3 "ETP"`
|
|
214
|
+
> produzem o mesmo resultado. Use o estilo que preferir.
|
|
215
|
+
|
|
201
216
|
### Busca
|
|
202
217
|
|
|
203
218
|
```bash
|
|
@@ -7,7 +7,17 @@ Cliente de linha de comando para a API VectorGov - Busca semântica em legislaç
|
|
|
7
7
|
[](https://pypi.org/project/vectorgov-cli/)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
> **Novo em 0.3.2** — zero truncamento de conteúdo em todos os formatos
|
|
11
|
+
> de saída + TTY detection automática: quando stdout não é terminal
|
|
12
|
+
> (pipe, LLM, CI/CD), o formato padrão vira `llm` automaticamente. A
|
|
13
|
+
> tabela do `search` ganhou coluna `Norma` para identificar de qual lei
|
|
14
|
+
> cada artigo veio. Veja o [CHANGELOG](CHANGELOG.md#032---2026-04-15).
|
|
15
|
+
>
|
|
16
|
+
> **Novo em 0.3.1** — parsing GNU-style: flags e argumentos funcionam em
|
|
17
|
+
> qualquer ordem (`search "ETP" --top-k 3` e `search --top-k 3 "ETP"`
|
|
18
|
+
> fazem a mesma coisa).
|
|
19
|
+
|
|
20
|
+
## Para LLMs e agentes de IA (v0.3.2+)
|
|
11
21
|
|
|
12
22
|
Se voce e um LLM ou agente de IA usando o CLI do VectorGov em sessoes
|
|
13
23
|
de vibe coding, estas features foram projetadas para voce:
|
|
@@ -145,6 +155,11 @@ export VECTORGOV_API_KEY="vg_sua_chave"
|
|
|
145
155
|
|
|
146
156
|
## Uso
|
|
147
157
|
|
|
158
|
+
> **Ordem de argumentos**: o CLI aceita flags e argumentos posicionais em
|
|
159
|
+
> qualquer ordem (estilo GNU, igual a `git`, `curl`, `kubectl`, `npm`).
|
|
160
|
+
> `vectorgov search "ETP" --top-k 3` e `vectorgov search --top-k 3 "ETP"`
|
|
161
|
+
> produzem o mesmo resultado. Use o estilo que preferir.
|
|
162
|
+
|
|
148
163
|
### Busca
|
|
149
164
|
|
|
150
165
|
```bash
|
|
@@ -4,7 +4,7 @@ Comandos do CLI VectorGov.
|
|
|
4
4
|
|
|
5
5
|
from . import auth, search, ask, feedback, docs, config, tokens
|
|
6
6
|
from . import smart_search, hybrid, lookup, grep_cmd, merged, audit, quota, prompts
|
|
7
|
-
from . import context, init
|
|
7
|
+
from . import context, init, fs_search, read, explain
|
|
8
8
|
|
|
9
9
|
__all__ = [
|
|
10
10
|
"auth",
|
|
@@ -24,4 +24,7 @@ __all__ = [
|
|
|
24
24
|
"prompts",
|
|
25
25
|
"context",
|
|
26
26
|
"init",
|
|
27
|
+
"fs_search",
|
|
28
|
+
"read",
|
|
29
|
+
"explain",
|
|
27
30
|
]
|
|
@@ -17,7 +17,6 @@ from rich.syntax import Syntax
|
|
|
17
17
|
from ..utils.config import ConfigManager
|
|
18
18
|
from ..utils.output import OutputFormat, extract_request_id, print_request_id_footer
|
|
19
19
|
|
|
20
|
-
app = typer.Typer()
|
|
21
20
|
console = Console()
|
|
22
21
|
config_manager = ConfigManager()
|
|
23
22
|
|
|
@@ -43,7 +42,6 @@ def _check_admin(api_key: str) -> bool:
|
|
|
43
42
|
return False
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
@app.callback(invoke_without_command=True)
|
|
47
45
|
def ask(
|
|
48
46
|
query: str = typer.Argument(..., help="Pergunta em linguagem natural"),
|
|
49
47
|
top_k: int = typer.Option(5, "--top-k", "-k", help="Número de chunks para contexto"),
|
|
@@ -150,7 +148,7 @@ def ask(
|
|
|
150
148
|
for i, hit in enumerate(results.hits, 1):
|
|
151
149
|
title = f"[bold]#{i}[/bold] Art. {hit.article_number} ({getattr(hit, 'document_id', None) or getattr(hit, 'source', '')})"
|
|
152
150
|
console.print(Panel(
|
|
153
|
-
hit.text
|
|
151
|
+
hit.text,
|
|
154
152
|
title=title,
|
|
155
153
|
border_style="blue"
|
|
156
154
|
))
|
|
@@ -8,12 +8,10 @@ from rich.console import Console
|
|
|
8
8
|
from ..utils.config import ConfigManager
|
|
9
9
|
from ..utils.output import extract_request_id, print_request_id_footer
|
|
10
10
|
|
|
11
|
-
app = typer.Typer()
|
|
12
11
|
console = Console()
|
|
13
12
|
config_manager = ConfigManager()
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
@app.callback(invoke_without_command=True)
|
|
17
15
|
def context(
|
|
18
16
|
query: str = typer.Argument(..., help="Pergunta jurídica"),
|
|
19
17
|
top_k: int = typer.Option(5, "--top-k", "-k", help="Quantidade de resultados (1-20)"),
|
|
@@ -120,7 +120,7 @@ def list_docs(
|
|
|
120
120
|
else:
|
|
121
121
|
table = Table(title="Documentos Disponíveis")
|
|
122
122
|
table.add_column("ID", style="cyan")
|
|
123
|
-
table.add_column("Título")
|
|
123
|
+
table.add_column("Título", overflow="fold")
|
|
124
124
|
table.add_column("Tipo", style="green")
|
|
125
125
|
table.add_column("Ano", justify="right")
|
|
126
126
|
table.add_column("Chunks", justify="right")
|
|
@@ -135,7 +135,7 @@ def list_docs(
|
|
|
135
135
|
chunks = getattr(d, "chunks_count", 0)
|
|
136
136
|
table.add_row(
|
|
137
137
|
str(doc_id),
|
|
138
|
-
|
|
138
|
+
str(titulo),
|
|
139
139
|
str(tipo),
|
|
140
140
|
str(ano),
|
|
141
141
|
str(chunks),
|
|
@@ -13,12 +13,10 @@ from ..utils.output import (
|
|
|
13
13
|
render_llm_output,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
app = typer.Typer()
|
|
17
16
|
console = Console()
|
|
18
17
|
config_manager = ConfigManager()
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
@app.callback(invoke_without_command=True)
|
|
22
20
|
def explain(
|
|
23
21
|
reference: str = typer.Argument(..., help="Referencia legal (ex: 'Art. 75 da Lei 14.133')"),
|
|
24
22
|
output: str = typer.Option("llm", "--output", "-o", help="Formato: llm, text, json, raw"),
|
|
@@ -158,7 +156,7 @@ def explain(
|
|
|
158
156
|
lines.append(main_text)
|
|
159
157
|
if parent_text:
|
|
160
158
|
lines.append("")
|
|
161
|
-
lines.append(f"PARENT: {parent_text
|
|
159
|
+
lines.append(f"PARENT: {parent_text}")
|
|
162
160
|
else:
|
|
163
161
|
lines.append("")
|
|
164
162
|
lines.append("PARENT: (nenhum -- dispositivo de primeiro nivel)")
|
|
@@ -199,10 +197,10 @@ def explain(
|
|
|
199
197
|
console.print(f"\nStatus: [green]{status}[/green]")
|
|
200
198
|
console.print(Panel(main_text, title=title, border_style="cyan"))
|
|
201
199
|
if parent_text:
|
|
202
|
-
console.print(
|
|
200
|
+
console.print(Panel(parent_text, title="Parent", border_style="dim"))
|
|
203
201
|
console.print(f"[dim]Filhos: {children_count} dispositivos[/dim]")
|
|
204
202
|
if nota:
|
|
205
|
-
console.print(f"\n[bold]Nota do especialista:[/bold] {nota
|
|
203
|
+
console.print(f"\n[bold]Nota do especialista:[/bold] {nota}")
|
|
206
204
|
if evidence_url:
|
|
207
205
|
console.print(f"[dim] Ver trecho: {evidence_url}[/dim]")
|
|
208
206
|
if document_url:
|
|
@@ -14,12 +14,10 @@ from ..utils.output import (
|
|
|
14
14
|
resolve_output_format,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
app = typer.Typer()
|
|
18
17
|
console = Console()
|
|
19
18
|
config_manager = ConfigManager()
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
@app.callback(invoke_without_command=True)
|
|
23
21
|
def fs_search(
|
|
24
22
|
query: str = typer.Argument(..., help="Pergunta ou termo para buscar"),
|
|
25
23
|
document_id: str = typer.Option(None, "--doc", "-d", help="Filtrar por documento (ex: LEI-14133-2021)"),
|
|
@@ -117,7 +115,7 @@ def fs_search(
|
|
|
117
115
|
{
|
|
118
116
|
"source": getattr(h, "source", ""),
|
|
119
117
|
"score": getattr(h, "score", 0),
|
|
120
|
-
"text": getattr(h, "text", str(h))
|
|
118
|
+
"text": getattr(h, "text", str(h)),
|
|
121
119
|
"breadcrumb": getattr(h, "breadcrumb", ""),
|
|
122
120
|
}
|
|
123
121
|
for h in hits
|
|
@@ -128,17 +126,16 @@ def fs_search(
|
|
|
128
126
|
table = Table(title=f"Filesystem Search ({len(hits)} resultados)")
|
|
129
127
|
table.add_column("#", style="dim", width=3)
|
|
130
128
|
table.add_column("Fonte", style="cyan", width=30)
|
|
131
|
-
table.add_column("Texto",
|
|
129
|
+
table.add_column("Texto", overflow="fold")
|
|
132
130
|
table.add_column("Score", justify="right", width=6)
|
|
133
131
|
table.add_column("Motivo", width=10)
|
|
134
132
|
for i, h in enumerate(hits, 1):
|
|
135
133
|
source = getattr(h, "source", getattr(h, "document_id", ""))
|
|
136
134
|
score = getattr(h, "score", 0)
|
|
137
135
|
text = getattr(h, "text", str(h))
|
|
138
|
-
text = text[:160] + "..." if len(text) > 160 else text
|
|
139
136
|
reason = getattr(h, "match_reason", "")
|
|
140
137
|
table.add_row(
|
|
141
|
-
str(i), source, text, f"{score:.2f}" if score else "-", reason
|
|
138
|
+
str(i), source, text, f"{score:.2f}" if score else "-", reason
|
|
142
139
|
)
|
|
143
140
|
console.print(table)
|
|
144
141
|
else:
|
|
@@ -148,7 +145,7 @@ def fs_search(
|
|
|
148
145
|
console.print(f"\n[cyan][{i}][/cyan] [bold]{source}[/bold]")
|
|
149
146
|
if breadcrumb:
|
|
150
147
|
console.print(f" [dim]{breadcrumb}[/dim]")
|
|
151
|
-
console.print(f" {getattr(h, 'text', str(h))
|
|
148
|
+
console.print(f" {getattr(h, 'text', str(h))}")
|
|
152
149
|
for line in render_evidence_lines_text(h):
|
|
153
150
|
console.print(f"[dim]{line}[/dim]")
|
|
154
151
|
|
|
@@ -14,12 +14,10 @@ from ..utils.output import (
|
|
|
14
14
|
resolve_output_format,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
app = typer.Typer()
|
|
18
17
|
console = Console()
|
|
19
18
|
config_manager = ConfigManager()
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
@app.callback(invoke_without_command=True)
|
|
23
21
|
def grep(
|
|
24
22
|
query: str = typer.Argument(..., help="Texto exato para buscar (ex: 'dispensa de licitação')"),
|
|
25
23
|
document_id: str = typer.Option(None, "--doc", "-d", help="Filtrar por documento (ex: LEI-14.133-2021)"),
|
|
@@ -109,7 +107,7 @@ def grep(
|
|
|
109
107
|
"request_id": extract_request_id(result),
|
|
110
108
|
"hits": [
|
|
111
109
|
{"source": getattr(h, "source", ""), "span_id": getattr(h, "span_id", ""),
|
|
112
|
-
"text": getattr(h, "text", str(h))
|
|
110
|
+
"text": getattr(h, "text", str(h))}
|
|
113
111
|
for h in hits
|
|
114
112
|
],
|
|
115
113
|
}
|
|
@@ -118,18 +116,17 @@ def grep(
|
|
|
118
116
|
table = Table(title=f"grep: '{query}'")
|
|
119
117
|
table.add_column("#", style="dim", width=3)
|
|
120
118
|
table.add_column("Fonte", style="cyan", width=30)
|
|
121
|
-
table.add_column("Trecho",
|
|
119
|
+
table.add_column("Trecho", overflow="fold")
|
|
122
120
|
for i, h in enumerate(hits, 1):
|
|
123
121
|
source = getattr(h, "source", getattr(h, "document_id", ""))
|
|
124
122
|
text = getattr(h, "text", str(h))
|
|
125
|
-
text = text[:200] + "..." if len(text) > 200 else text
|
|
126
123
|
table.add_row(str(i), source, text)
|
|
127
124
|
console.print(table)
|
|
128
125
|
else:
|
|
129
126
|
for i, h in enumerate(hits, 1):
|
|
130
127
|
source = getattr(h, "source", getattr(h, "document_id", ""))
|
|
131
128
|
console.print(f"[cyan][{i}][/cyan] [bold]{source}[/bold]")
|
|
132
|
-
console.print(f" {getattr(h, 'text', str(h))
|
|
129
|
+
console.print(f" {getattr(h, 'text', str(h))}")
|
|
133
130
|
for line in render_evidence_lines_text(h):
|
|
134
131
|
console.print(f"[dim]{line}[/dim]")
|
|
135
132
|
console.print()
|
|
@@ -14,12 +14,10 @@ from ..utils.output import (
|
|
|
14
14
|
resolve_output_format,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
app = typer.Typer()
|
|
18
17
|
console = Console()
|
|
19
18
|
config_manager = ConfigManager()
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
@app.callback(invoke_without_command=True)
|
|
23
21
|
def hybrid(
|
|
24
22
|
query: str = typer.Argument(..., help="Pergunta jurídica"),
|
|
25
23
|
top_k: int = typer.Option(10, "--top-k", "-k", help="Quantidade de resultados (1-50)"),
|
|
@@ -143,7 +141,7 @@ def hybrid(
|
|
|
143
141
|
{
|
|
144
142
|
"source": getattr(h, "source", ""),
|
|
145
143
|
"score": getattr(h, "score", 0),
|
|
146
|
-
"text": h.text
|
|
144
|
+
"text": h.text,
|
|
147
145
|
"graph": getattr(h, "is_graph_expanded", False),
|
|
148
146
|
}
|
|
149
147
|
for h in hits
|
|
@@ -153,13 +151,13 @@ def hybrid(
|
|
|
153
151
|
table = Table(title=f"Busca Híbrida ({len(hits)} resultados)")
|
|
154
152
|
table.add_column("#", style="dim", width=3)
|
|
155
153
|
table.add_column("Fonte", style="cyan", width=30)
|
|
156
|
-
table.add_column("Texto",
|
|
154
|
+
table.add_column("Texto", overflow="fold")
|
|
157
155
|
table.add_column("Score", justify="right", width=6)
|
|
158
156
|
table.add_column("Grafo", width=5)
|
|
159
157
|
for i, h in enumerate(hits, 1):
|
|
160
158
|
source = getattr(h, "source", getattr(h, "document_id", ""))
|
|
161
159
|
score = getattr(h, "score", 0)
|
|
162
|
-
text = h.text
|
|
160
|
+
text = h.text
|
|
163
161
|
graph = "+" if getattr(h, "is_graph_expanded", False) else ""
|
|
164
162
|
table.add_row(
|
|
165
163
|
str(i), source, text, f"{score:.2f}" if score else "-", graph
|
|
@@ -174,7 +172,7 @@ def hybrid(
|
|
|
174
172
|
else ""
|
|
175
173
|
)
|
|
176
174
|
console.print(f"\n[cyan][{i}][/cyan] [bold]{source}[/bold]{graph_tag}")
|
|
177
|
-
console.print(f" {h.text
|
|
175
|
+
console.print(f" {h.text}")
|
|
178
176
|
for line in render_evidence_lines_text(h):
|
|
179
177
|
console.print(f"[dim]{line}[/dim]")
|
|
180
178
|
|