breadcrumb-cli 0.1.0__py3-none-any.whl
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.
- breadcrumb/__init__.py +7 -0
- breadcrumb/ai/__init__.py +1 -0
- breadcrumb/ai/prompts.py +60 -0
- breadcrumb/ai/router.py +187 -0
- breadcrumb/cli.py +144 -0
- breadcrumb/commands/__init__.py +1 -0
- breadcrumb/commands/ask.py +98 -0
- breadcrumb/commands/audit.py +77 -0
- breadcrumb/commands/chat.py +123 -0
- breadcrumb/commands/commit.py +87 -0
- breadcrumb/commands/diff.py +90 -0
- breadcrumb/commands/digest.py +80 -0
- breadcrumb/commands/explain_error.py +63 -0
- breadcrumb/commands/init.py +67 -0
- breadcrumb/commands/share.py +209 -0
- breadcrumb/config.py +84 -0
- breadcrumb/history.py +110 -0
- breadcrumb/ingest.py +163 -0
- breadcrumb_cli-0.1.0.dist-info/METADATA +342 -0
- breadcrumb_cli-0.1.0.dist-info/RECORD +23 -0
- breadcrumb_cli-0.1.0.dist-info/WHEEL +4 -0
- breadcrumb_cli-0.1.0.dist-info/entry_points.txt +2 -0
- breadcrumb_cli-0.1.0.dist-info/licenses/LICENSE +23 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive chat with the codebase using a TUI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
from rich.rule import Rule
|
|
12
|
+
|
|
13
|
+
from breadcrumb.ai.prompts import get_system_prompt
|
|
14
|
+
from breadcrumb.ai.router import AIRouter
|
|
15
|
+
from breadcrumb.history import SessionManager
|
|
16
|
+
from breadcrumb.ingest import FileIngester
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cmd_chat(
|
|
22
|
+
repo_path: Path,
|
|
23
|
+
provider: Optional[str] = None,
|
|
24
|
+
session_name: str = "default",
|
|
25
|
+
) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Interactive chat session with the codebase.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
repo_path: Repository path
|
|
31
|
+
provider: AI provider (overrides config)
|
|
32
|
+
session_name: Name of the chat session
|
|
33
|
+
"""
|
|
34
|
+
# Initialize
|
|
35
|
+
ingester = FileIngester(repo_path)
|
|
36
|
+
router = AIRouter(provider)
|
|
37
|
+
session = SessionManager(repo_path, session_name)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
context = ingester.get_content()
|
|
41
|
+
except Exception as e:
|
|
42
|
+
console.print(f"[red]Error reading repository: {e}[/red]")
|
|
43
|
+
context = ""
|
|
44
|
+
|
|
45
|
+
console.print(
|
|
46
|
+
Panel.fit(
|
|
47
|
+
"[bold cyan]🍞 Bread Crumb[/bold cyan]\n"
|
|
48
|
+
f"[dim]Chat with your codebase using {router.provider}[/dim]",
|
|
49
|
+
border_style="cyan",
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
console.print(
|
|
53
|
+
f"[dim]Session: {session_name} | Provider: {router.provider} | Model: {router.model}[/dim]"
|
|
54
|
+
)
|
|
55
|
+
console.print("[dim]Type 'exit' to quit, 'clear' to clear history[/dim]")
|
|
56
|
+
console.print(Rule())
|
|
57
|
+
|
|
58
|
+
# Chat loop
|
|
59
|
+
while True:
|
|
60
|
+
try:
|
|
61
|
+
question = Prompt.ask("[cyan]You")
|
|
62
|
+
except KeyboardInterrupt:
|
|
63
|
+
console.print("\n[dim]Goodbye![/dim]")
|
|
64
|
+
break
|
|
65
|
+
except EOFError:
|
|
66
|
+
console.print("\n[dim]Goodbye![/dim]")
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
if not question.strip():
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
if question.lower() == "exit":
|
|
73
|
+
console.print("[dim]Goodbye![/dim]")
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
if question.lower() == "clear":
|
|
77
|
+
session.clear()
|
|
78
|
+
console.print("[yellow]History cleared[/yellow]")
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
if question.lower() == "sessions":
|
|
82
|
+
sessions = SessionManager.list_sessions(repo_path)
|
|
83
|
+
if sessions:
|
|
84
|
+
console.print("[cyan]Available sessions:[/cyan]")
|
|
85
|
+
for s in sessions:
|
|
86
|
+
console.print(f" • {s}")
|
|
87
|
+
else:
|
|
88
|
+
console.print("[dim]No other sessions[/dim]")
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
# Add user message to history
|
|
92
|
+
session.add_message("user", question)
|
|
93
|
+
|
|
94
|
+
# Build messages for API
|
|
95
|
+
system = get_system_prompt("chat")
|
|
96
|
+
|
|
97
|
+
# Include context only in first message of session
|
|
98
|
+
if len(session.messages) <= 1:
|
|
99
|
+
api_messages = [
|
|
100
|
+
{"role": "user", "content": f"Repo context:\n{context}\n\nQuestion: {question}"}
|
|
101
|
+
]
|
|
102
|
+
else:
|
|
103
|
+
api_messages = session.get_messages_for_api()
|
|
104
|
+
|
|
105
|
+
# Stream response
|
|
106
|
+
try:
|
|
107
|
+
response_text = ""
|
|
108
|
+
console.print("[cyan]Assistant[/cyan]", end=" ")
|
|
109
|
+
for chunk in router.stream(api_messages, system):
|
|
110
|
+
console.print(chunk, end="", highlight=False)
|
|
111
|
+
response_text += chunk
|
|
112
|
+
console.print() # Newline after response
|
|
113
|
+
except Exception as e:
|
|
114
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
# Add assistant response to history
|
|
118
|
+
session.add_message("assistant", response_text)
|
|
119
|
+
|
|
120
|
+
# Show stats
|
|
121
|
+
tokens = session.count_tokens()
|
|
122
|
+
console.print(f"[dim]~{tokens:,} tokens in session[/dim]")
|
|
123
|
+
console.print(Rule())
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate conventional commit messages from staged changes.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from breadcrumb.ai.prompts import get_system_prompt
|
|
12
|
+
from breadcrumb.ai.router import AIRouter
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_git_diff_staged(repo_path: Path) -> str:
|
|
18
|
+
"""Get staged changes via git diff --staged."""
|
|
19
|
+
try:
|
|
20
|
+
result = subprocess.run(
|
|
21
|
+
["git", "diff", "--staged"],
|
|
22
|
+
cwd=repo_path,
|
|
23
|
+
capture_output=True,
|
|
24
|
+
text=True,
|
|
25
|
+
)
|
|
26
|
+
return result.stdout
|
|
27
|
+
except Exception as e:
|
|
28
|
+
console.print(f"[red]Error running git diff: {e}[/red]")
|
|
29
|
+
return ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def cmd_commit(
|
|
33
|
+
repo_path: Path,
|
|
34
|
+
provider: Optional[str] = None,
|
|
35
|
+
silent: bool = False,
|
|
36
|
+
) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Generate a conventional commit message from staged changes.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
repo_path: Repository path
|
|
42
|
+
provider: AI provider (overrides config)
|
|
43
|
+
silent: If True, only print the message (no other output)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
The generated commit message
|
|
47
|
+
"""
|
|
48
|
+
# Get staged diff
|
|
49
|
+
diff = get_git_diff_staged(repo_path)
|
|
50
|
+
|
|
51
|
+
if not diff:
|
|
52
|
+
if not silent:
|
|
53
|
+
console.print("[yellow]No staged changes[/yellow]")
|
|
54
|
+
return ""
|
|
55
|
+
|
|
56
|
+
# Setup AI
|
|
57
|
+
router = AIRouter(provider)
|
|
58
|
+
system = get_system_prompt("commit")
|
|
59
|
+
|
|
60
|
+
prompt = f"""Analyze these staged changes and generate a conventional commit message.
|
|
61
|
+
|
|
62
|
+
{diff}
|
|
63
|
+
|
|
64
|
+
Generate ONLY the commit message in conventional format (type(scope): description).
|
|
65
|
+
No explanation, no markdown, just the message."""
|
|
66
|
+
|
|
67
|
+
messages = [{"role": "user", "content": prompt}]
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
if not silent:
|
|
71
|
+
with console.status("[bold cyan]Generating commit message...", spinner="dots"):
|
|
72
|
+
message = router.chat(messages, system)
|
|
73
|
+
else:
|
|
74
|
+
message = router.chat(messages, system)
|
|
75
|
+
|
|
76
|
+
message = message.strip()
|
|
77
|
+
|
|
78
|
+
if not silent:
|
|
79
|
+
console.print(f"\n[green]Suggested commit:[/green]\n{message}")
|
|
80
|
+
else:
|
|
81
|
+
print(message)
|
|
82
|
+
|
|
83
|
+
return message
|
|
84
|
+
except Exception as e:
|
|
85
|
+
if not silent:
|
|
86
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
87
|
+
return ""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Review git diffs with AI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.markdown import Markdown
|
|
11
|
+
|
|
12
|
+
from breadcrumb.ai.prompts import get_system_prompt
|
|
13
|
+
from breadcrumb.ai.router import AIRouter
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_git_diff(repo_path: Path, revision: str = "") -> str:
|
|
19
|
+
"""Get git diff for a revision."""
|
|
20
|
+
try:
|
|
21
|
+
if revision:
|
|
22
|
+
result = subprocess.run(
|
|
23
|
+
["git", "diff", revision],
|
|
24
|
+
cwd=repo_path,
|
|
25
|
+
capture_output=True,
|
|
26
|
+
text=True,
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
result = subprocess.run(
|
|
30
|
+
["git", "diff"],
|
|
31
|
+
cwd=repo_path,
|
|
32
|
+
capture_output=True,
|
|
33
|
+
text=True,
|
|
34
|
+
)
|
|
35
|
+
return result.stdout
|
|
36
|
+
except Exception as e:
|
|
37
|
+
console.print(f"[red]Error running git diff: {e}[/red]")
|
|
38
|
+
return ""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def cmd_diff(
|
|
42
|
+
repo_path: Path,
|
|
43
|
+
revision: str = "",
|
|
44
|
+
provider: Optional[str] = None,
|
|
45
|
+
) -> str:
|
|
46
|
+
"""
|
|
47
|
+
Review a git diff with AI.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
repo_path: Repository path
|
|
51
|
+
revision: Git revision (e.g., 'HEAD~1', 'main..feature/auth')
|
|
52
|
+
provider: AI provider (overrides config)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The review
|
|
56
|
+
"""
|
|
57
|
+
diff = get_git_diff(repo_path, revision)
|
|
58
|
+
|
|
59
|
+
if not diff:
|
|
60
|
+
console.print("[yellow]No changes found for the specified revision[/yellow]")
|
|
61
|
+
return ""
|
|
62
|
+
|
|
63
|
+
# Limit diff size
|
|
64
|
+
if len(diff) > 50000:
|
|
65
|
+
diff = diff[:50000] + "\n... [diff truncated]"
|
|
66
|
+
|
|
67
|
+
router = AIRouter(provider)
|
|
68
|
+
system = get_system_prompt("diff")
|
|
69
|
+
|
|
70
|
+
prompt = f"""Review this git diff and provide:
|
|
71
|
+
1. Summary of changes
|
|
72
|
+
2. Any potential issues or bugs
|
|
73
|
+
3. Suggestions for improvement
|
|
74
|
+
4. Impact assessment
|
|
75
|
+
|
|
76
|
+
{diff}"""
|
|
77
|
+
|
|
78
|
+
messages = [{"role": "user", "content": prompt}]
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
response_text = ""
|
|
82
|
+
with console.status("[bold cyan]Reviewing diff...", spinner="dots"):
|
|
83
|
+
for chunk in router.stream(messages, system):
|
|
84
|
+
response_text += chunk
|
|
85
|
+
|
|
86
|
+
console.print(Markdown(response_text))
|
|
87
|
+
return response_text
|
|
88
|
+
except Exception as e:
|
|
89
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
90
|
+
return ""
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate a daily digest of changes from git commits.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.markdown import Markdown
|
|
12
|
+
|
|
13
|
+
from breadcrumb.ai.prompts import get_system_prompt
|
|
14
|
+
from breadcrumb.ai.router import AIRouter
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_commits_since(repo_path: Path, hours: int = 24) -> str:
|
|
20
|
+
"""Get git log for the past N hours."""
|
|
21
|
+
try:
|
|
22
|
+
since = datetime.now() - timedelta(hours=hours)
|
|
23
|
+
since_str = since.strftime("%Y-%m-%d %H:%M:%S")
|
|
24
|
+
|
|
25
|
+
result = subprocess.run(
|
|
26
|
+
["git", "log", f"--since={since_str}", "--oneline"],
|
|
27
|
+
cwd=repo_path,
|
|
28
|
+
capture_output=True,
|
|
29
|
+
text=True,
|
|
30
|
+
)
|
|
31
|
+
return result.stdout
|
|
32
|
+
except Exception as e:
|
|
33
|
+
console.print(f"[red]Error getting commits: {e}[/red]")
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def cmd_digest(
|
|
38
|
+
repo_path: Path,
|
|
39
|
+
hours: int = 24,
|
|
40
|
+
provider: Optional[str] = None,
|
|
41
|
+
) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Generate a digest of recent commits.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
repo_path: Repository path
|
|
47
|
+
hours: How many hours back to look
|
|
48
|
+
provider: AI provider (overrides config)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The digest
|
|
52
|
+
"""
|
|
53
|
+
commits = get_commits_since(repo_path, hours)
|
|
54
|
+
|
|
55
|
+
if not commits:
|
|
56
|
+
console.print("[yellow]No commits in the past 24 hours[/yellow]")
|
|
57
|
+
return ""
|
|
58
|
+
|
|
59
|
+
router = AIRouter(provider)
|
|
60
|
+
system = get_system_prompt("digest")
|
|
61
|
+
|
|
62
|
+
prompt = f"""Summarize these commits into a concise digest.
|
|
63
|
+
What changed? What was fixed? Notable improvements?
|
|
64
|
+
|
|
65
|
+
Commits:
|
|
66
|
+
{commits}"""
|
|
67
|
+
|
|
68
|
+
messages = [{"role": "user", "content": prompt}]
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
response_text = ""
|
|
72
|
+
with console.status("[bold cyan]Generating digest...", spinner="dots"):
|
|
73
|
+
for chunk in router.stream(messages, system):
|
|
74
|
+
response_text += chunk
|
|
75
|
+
|
|
76
|
+
console.print(Markdown(response_text))
|
|
77
|
+
return response_text
|
|
78
|
+
except Exception as e:
|
|
79
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
80
|
+
return ""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Explain errors and stack traces.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.markdown import Markdown
|
|
10
|
+
|
|
11
|
+
from breadcrumb.ai.prompts import get_system_prompt
|
|
12
|
+
from breadcrumb.ai.router import AIRouter
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cmd_explain_error(
|
|
18
|
+
error_text: Optional[str] = None,
|
|
19
|
+
provider: Optional[str] = None,
|
|
20
|
+
) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Explain an error or stack trace.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
error_text: Error text (reads from stdin if not provided)
|
|
26
|
+
provider: AI provider (overrides config)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The explanation
|
|
30
|
+
"""
|
|
31
|
+
if not error_text:
|
|
32
|
+
error_text = sys.stdin.read()
|
|
33
|
+
|
|
34
|
+
if not error_text:
|
|
35
|
+
console.print("[red]Error: No error text provided[/red]")
|
|
36
|
+
return ""
|
|
37
|
+
|
|
38
|
+
error_text = error_text.strip()
|
|
39
|
+
|
|
40
|
+
router = AIRouter(provider)
|
|
41
|
+
system = get_system_prompt("explain-error")
|
|
42
|
+
|
|
43
|
+
prompt = f"""Analyze this error and provide:
|
|
44
|
+
1. Plain English explanation
|
|
45
|
+
2. Root cause
|
|
46
|
+
3. How to fix it
|
|
47
|
+
|
|
48
|
+
Error:
|
|
49
|
+
{error_text}"""
|
|
50
|
+
|
|
51
|
+
messages = [{"role": "user", "content": prompt}]
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
response_text = ""
|
|
55
|
+
with console.status("[bold cyan]Analyzing error...", spinner="dots"):
|
|
56
|
+
for chunk in router.stream(messages, system):
|
|
57
|
+
response_text += chunk
|
|
58
|
+
|
|
59
|
+
console.print(Markdown(response_text))
|
|
60
|
+
return response_text
|
|
61
|
+
except Exception as e:
|
|
62
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
63
|
+
return ""
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Initialize a .breadcrumb.yaml config file for a repository.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
BREADCRUMB_YAML_TEMPLATE = """# Bread Crumb Configuration
|
|
12
|
+
# Place this file in your repository root to customize Bread Crumb behavior
|
|
13
|
+
|
|
14
|
+
# AI Provider: anthropic, openai, gemini, or ollama
|
|
15
|
+
provider: anthropic
|
|
16
|
+
|
|
17
|
+
# Model name for the chosen provider
|
|
18
|
+
model: claude-3-5-sonnet-20241022
|
|
19
|
+
|
|
20
|
+
# Patterns to ignore (like .gitignore)
|
|
21
|
+
# These files won't be included in context
|
|
22
|
+
ignore_patterns:
|
|
23
|
+
- "*.min.js"
|
|
24
|
+
- "*.min.css"
|
|
25
|
+
- "dist/"
|
|
26
|
+
- "build/"
|
|
27
|
+
- "node_modules/"
|
|
28
|
+
|
|
29
|
+
# Custom system prompt for this repository
|
|
30
|
+
# Gets prepended to all conversations
|
|
31
|
+
system_prompt: |
|
|
32
|
+
You are analyzing a [PROJECT_TYPE] project.
|
|
33
|
+
Key technologies: [TECH_STACK]
|
|
34
|
+
Important context: [ANY_SPECIAL_RULES]
|
|
35
|
+
|
|
36
|
+
# Temperature for AI responses (0.0 - 1.0)
|
|
37
|
+
temperature: 0.7
|
|
38
|
+
|
|
39
|
+
# Maximum tokens per request
|
|
40
|
+
max_tokens: 4096
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def cmd_init(repo_path: Path) -> bool:
|
|
45
|
+
"""
|
|
46
|
+
Initialize a .breadcrumb.yaml config file in the repository.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
repo_path: Repository path
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
True if successful
|
|
53
|
+
"""
|
|
54
|
+
config_file = repo_path / ".breadcrumb.yaml"
|
|
55
|
+
|
|
56
|
+
if config_file.exists():
|
|
57
|
+
console.print("[yellow].breadcrumb.yaml already exists[/yellow]")
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
config_file.write_text(BREADCRUMB_YAML_TEMPLATE)
|
|
62
|
+
console.print(f"[green]✓ Created {config_file}[/green]")
|
|
63
|
+
console.print("[dim]Edit it to customize Bread Crumb for your project[/dim]")
|
|
64
|
+
return True
|
|
65
|
+
except Exception as e:
|
|
66
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
67
|
+
return False
|