Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Thumbs.db

# Local scratch
/tmp/
/cli/tmp/
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Give AI agents eyes into ArcGIS Pro.

```bash
pip install arcgispro-cli

# Optional: install TUI dependencies
pip install arcgispro-cli[tui]

arcgis install
```

Expand Down
4 changes: 2 additions & 2 deletions cli/arcgispro_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

from . import __version__
from .commands import clean, open_project, install, query, launch, notebooks, tui, diagram
from .tui.banner import _colorize_logo
from .logo import colorize_logo

# Ensure Unicode output on Windows
if sys.stdout.encoding != "utf-8":
Expand Down Expand Up @@ -62,7 +62,7 @@ def main(ctx):
"""
ctx.ensure_object(dict)
if ctx.invoked_subcommand is None:
console.print(_colorize_logo())
console.print(colorize_logo())
console.print()
console.print(ctx.get_help())

Expand Down
10 changes: 9 additions & 1 deletion cli/arcgispro_cli/commands/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
@click.option("--no-banner", is_flag=True, help="Disable the ASCII banner")
def tui_cmd(repo: str, no_banner: bool) -> None:
"""Start the ArcGIS Pro TUI."""
from arcgispro_cli.tui.app import ArcGISProCLIApp
try:
from arcgispro_cli.tui.app import ArcGISProCLIApp
except ModuleNotFoundError as e:
# The most common case is missing optional TUI deps (textual).
if (e.name or "").startswith("textual"):
raise click.ClickException(
"TUI dependencies are not installed. Install with: pip install arcgispro-cli[tui]"
)
raise

ArcGISProCLIApp(repo_path=repo, show_banner=(not no_banner)).run()
50 changes: 50 additions & 0 deletions cli/arcgispro_cli/logo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from __future__ import annotations

from rich.text import Text

LOGO_LINES = [
" █████╗ ██████╗ ██████╗ ██████╗ ██╗███████╗",
"██╔══██╗██╔══██╗██╔════╝ ██╔════╝ ██║██╔════╝",
"███████║██████╔╝██║ ██║ ███╗██║███████╗",
"██╔══██║██╔══██╗██║ ██║ ██║██║╚════██║",
"██║ ██║██║ ██║╚██████╗ ╚██████╔╝██║███████║",
"╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝",
]

BLUES = [
"#9dd8ff",
"#87cefa",
"#6fbff6",
"#55aef0",
"#3f9be8",
"#2f88e0",
]

GRAYS = [
"color(250)",
"color(248)",
"color(245)",
"color(243)",
"color(240)",
"color(238)",
]

SHADOW_CHARS = set("╔╗╚╝═║")


def colorize_logo() -> Text:
"""Render ARCGIS logo with blue gradient fills and gray shadow strokes."""
text = Text()
for row_index, line in enumerate(LOGO_LINES):
block_color = BLUES[min(row_index, len(BLUES) - 1)]
shadow_color = GRAYS[min(row_index, len(GRAYS) - 1)]
for ch in line:
if ch in SHADOW_CHARS:
text.append(ch, style=shadow_color)
elif ch == " ":
text.append(ch)
else:
text.append(ch, style=block_color)
if row_index < len(LOGO_LINES) - 1:
text.append("\n")
return text
51 changes: 2 additions & 49 deletions cli/arcgispro_cli/tui/banner.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,9 @@
from __future__ import annotations

from rich.text import Text
from textual.widget import Widget
from textual.widgets import Static

LOGO_LINES = [
" █████╗ ██████╗ ██████╗ ██████╗ ██╗███████╗",
"██╔══██╗██╔══██╗██╔════╝ ██╔════╝ ██║██╔════╝",
"███████║██████╔╝██║ ██║ ███╗██║███████╗",
"██╔══██║██╔══██╗██║ ██║ ██║██║╚════██║",
"██║ ██║██║ ██║╚██████╗ ╚██████╔╝██║███████║",
"╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝",
]

BLUES = [
"#9dd8ff",
"#87cefa",
"#6fbff6",
"#55aef0",
"#3f9be8",
"#2f88e0",
]

GRAYS = [
"color(250)",
"color(248)",
"color(245)",
"color(243)",
"color(240)",
"color(238)",
]

SHADOW_CHARS = set("╔╗╚╝═║")


def _colorize_logo() -> Text:
"""Render ARCGIS logo with blue gradient fills and gray shadow strokes."""
text = Text()
for row_index, line in enumerate(LOGO_LINES):
block_color = BLUES[min(row_index, len(BLUES) - 1)]
shadow_color = GRAYS[min(row_index, len(GRAYS) - 1)]
for ch in line:
if ch in SHADOW_CHARS:
text.append(ch, style=shadow_color)
elif ch == " ":
text.append(ch)
else:
text.append(ch, style=block_color)
if row_index < len(LOGO_LINES) - 1:
text.append("\n")
return text

from arcgispro_cli.logo import colorize_logo

class Banner(Widget):
"""Top-of-screen banner with oh-my-logo rendering."""
Expand Down Expand Up @@ -80,4 +33,4 @@ def _refresh(self) -> None:
self._body.update("")
return

self._body.update(_colorize_logo())
self._body.update(colorize_logo())
7 changes: 6 additions & 1 deletion cli/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ dependencies = [
"click>=8.0",
"pillow>=10.0.0",
"rich>=13.0",
"textual>=0.56",
]

[project.optional-dependencies]
# The Textual UI is optional so `pip install arcgispro-cli` stays lightweight
# and avoids pulling in heavy TUI deps unless you want them.
tui = [
"textual>=0.56",
]

dev = [
"pytest>=7.0",
"pytest-cov>=4.0",
Expand Down
10 changes: 10 additions & 0 deletions cli/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ def test_install_help():
assert "ProExporter" in result.output


def test_tui_without_optional_deps_gives_helpful_error():
runner = CliRunner()
result = runner.invoke(main, ["tui"])

# Without the optional Textual dependency, this should fail fast with guidance.
# (In dev envs where textual is installed, this test may need updating.)
assert result.exit_code != 0
assert "arcgispro-cli[tui]" in result.output


def test_layers_no_folder():
"""Test layers command when no .arcgispro folder exists."""
runner = CliRunner()
Expand Down
Loading