arcgispro-cli 0.1.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.
- arcgispro_cli-0.1.2/.gitignore +21 -0
- arcgispro_cli-0.1.2/PKG-INFO +97 -0
- arcgispro_cli-0.1.2/README.md +71 -0
- arcgispro_cli-0.1.2/arcgispro_cli/__init__.py +3 -0
- arcgispro_cli-0.1.2/arcgispro_cli/addin/ProExporter.addin +0 -0
- arcgispro_cli-0.1.2/arcgispro_cli/cli.py +56 -0
- arcgispro_cli-0.1.2/arcgispro_cli/commands/__init__.py +1 -0
- arcgispro_cli-0.1.2/arcgispro_cli/commands/clean.py +119 -0
- arcgispro_cli-0.1.2/arcgispro_cli/commands/dump.py +87 -0
- arcgispro_cli-0.1.2/arcgispro_cli/commands/images.py +76 -0
- arcgispro_cli-0.1.2/arcgispro_cli/commands/inspect.py +126 -0
- arcgispro_cli-0.1.2/arcgispro_cli/commands/install.py +76 -0
- arcgispro_cli-0.1.2/arcgispro_cli/commands/open_project.py +99 -0
- arcgispro_cli-0.1.2/arcgispro_cli/commands/snapshot.py +124 -0
- arcgispro_cli-0.1.2/arcgispro_cli/paths.py +161 -0
- arcgispro_cli-0.1.2/pyproject.toml +50 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Visual Studio
|
|
2
|
+
.vs/
|
|
3
|
+
bin/
|
|
4
|
+
obj/
|
|
5
|
+
*.user
|
|
6
|
+
*.suo
|
|
7
|
+
|
|
8
|
+
# Python
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.pyc
|
|
11
|
+
*.egg-info/
|
|
12
|
+
dist/
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
|
|
16
|
+
# ArcGIS Pro
|
|
17
|
+
# *.esriAddinX # Commented out - we bundle the addin in the CLI package
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arcgispro-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: CLI tool for inspecting ArcGIS Pro session exports
|
|
5
|
+
Author: mcveydb
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: arcgis,cli,esri,gis
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: click>=8.0
|
|
21
|
+
Requires-Dist: rich>=13.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# ArcGIS Pro CLI
|
|
28
|
+
|
|
29
|
+
A command-line tool for inspecting and managing ArcGIS Pro session exports.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd cli
|
|
35
|
+
pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# View help
|
|
42
|
+
arcgispro --help
|
|
43
|
+
|
|
44
|
+
# Inspect exported context
|
|
45
|
+
arcgispro inspect
|
|
46
|
+
|
|
47
|
+
# Validate JSON exports
|
|
48
|
+
arcgispro dump
|
|
49
|
+
|
|
50
|
+
# Validate images
|
|
51
|
+
arcgispro images
|
|
52
|
+
|
|
53
|
+
# Assemble snapshot
|
|
54
|
+
arcgispro snapshot
|
|
55
|
+
|
|
56
|
+
# Clean up exports
|
|
57
|
+
arcgispro clean --all
|
|
58
|
+
|
|
59
|
+
# Select active project
|
|
60
|
+
arcgispro open
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Workflow
|
|
64
|
+
|
|
65
|
+
1. **In ArcGIS Pro:** Click "Snapshot" button in the ArcGIS Pro CLI ribbon
|
|
66
|
+
2. **In terminal:** Run `arcgispro inspect` to see what was exported
|
|
67
|
+
3. **Use context:** Read JSON files or markdown summary for AI analysis
|
|
68
|
+
|
|
69
|
+
## Folder Structure
|
|
70
|
+
|
|
71
|
+
The CLI reads from `.arcgispro/` folder created by the add-in:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
.arcgispro/
|
|
75
|
+
├── meta.json # Export metadata
|
|
76
|
+
├── active_project.txt # Path to active .aprx
|
|
77
|
+
├── context/
|
|
78
|
+
│ ├── project.json
|
|
79
|
+
│ ├── maps.json
|
|
80
|
+
│ ├── layers.json
|
|
81
|
+
│ ├── tables.json
|
|
82
|
+
│ ├── connections.json
|
|
83
|
+
│ └── layouts.json
|
|
84
|
+
├── snapshot/
|
|
85
|
+
│ ├── context.md
|
|
86
|
+
│ ├── CONTEXT_SKILL.md
|
|
87
|
+
│ └── AGENT_TOOL_SKILL.md
|
|
88
|
+
└── images/
|
|
89
|
+
├── map_*.png
|
|
90
|
+
└── layout_*.png
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Requirements
|
|
94
|
+
|
|
95
|
+
- Python 3.9+
|
|
96
|
+
- click
|
|
97
|
+
- rich
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ArcGIS Pro CLI
|
|
2
|
+
|
|
3
|
+
A command-line tool for inspecting and managing ArcGIS Pro session exports.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd cli
|
|
9
|
+
pip install -e .
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# View help
|
|
16
|
+
arcgispro --help
|
|
17
|
+
|
|
18
|
+
# Inspect exported context
|
|
19
|
+
arcgispro inspect
|
|
20
|
+
|
|
21
|
+
# Validate JSON exports
|
|
22
|
+
arcgispro dump
|
|
23
|
+
|
|
24
|
+
# Validate images
|
|
25
|
+
arcgispro images
|
|
26
|
+
|
|
27
|
+
# Assemble snapshot
|
|
28
|
+
arcgispro snapshot
|
|
29
|
+
|
|
30
|
+
# Clean up exports
|
|
31
|
+
arcgispro clean --all
|
|
32
|
+
|
|
33
|
+
# Select active project
|
|
34
|
+
arcgispro open
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Workflow
|
|
38
|
+
|
|
39
|
+
1. **In ArcGIS Pro:** Click "Snapshot" button in the ArcGIS Pro CLI ribbon
|
|
40
|
+
2. **In terminal:** Run `arcgispro inspect` to see what was exported
|
|
41
|
+
3. **Use context:** Read JSON files or markdown summary for AI analysis
|
|
42
|
+
|
|
43
|
+
## Folder Structure
|
|
44
|
+
|
|
45
|
+
The CLI reads from `.arcgispro/` folder created by the add-in:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
.arcgispro/
|
|
49
|
+
├── meta.json # Export metadata
|
|
50
|
+
├── active_project.txt # Path to active .aprx
|
|
51
|
+
├── context/
|
|
52
|
+
│ ├── project.json
|
|
53
|
+
│ ├── maps.json
|
|
54
|
+
│ ├── layers.json
|
|
55
|
+
│ ├── tables.json
|
|
56
|
+
│ ├── connections.json
|
|
57
|
+
│ └── layouts.json
|
|
58
|
+
├── snapshot/
|
|
59
|
+
│ ├── context.md
|
|
60
|
+
│ ├── CONTEXT_SKILL.md
|
|
61
|
+
│ └── AGENT_TOOL_SKILL.md
|
|
62
|
+
└── images/
|
|
63
|
+
├── map_*.png
|
|
64
|
+
└── layout_*.png
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Requirements
|
|
68
|
+
|
|
69
|
+
- Python 3.9+
|
|
70
|
+
- click
|
|
71
|
+
- rich
|
|
Binary file
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ArcGIS Pro CLI - Main entry point
|
|
3
|
+
|
|
4
|
+
Commands:
|
|
5
|
+
arcgispro install - Install the ProExporter add-in
|
|
6
|
+
arcgispro uninstall - Show uninstall instructions
|
|
7
|
+
arcgispro inspect - Print human-readable summary of exports
|
|
8
|
+
arcgispro dump - Validate context JSON files
|
|
9
|
+
arcgispro images - Validate exported images
|
|
10
|
+
arcgispro snapshot - Assemble full snapshot
|
|
11
|
+
arcgispro clean - Remove generated files
|
|
12
|
+
arcgispro open - Select active project
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
from . import __version__
|
|
19
|
+
from .commands import inspect, dump, images, snapshot, clean, open_project, install
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group()
|
|
25
|
+
@click.version_option(version=__version__, prog_name="arcgispro")
|
|
26
|
+
@click.pass_context
|
|
27
|
+
def main(ctx):
|
|
28
|
+
"""ArcGIS Pro CLI - Inspect and manage session exports.
|
|
29
|
+
|
|
30
|
+
This tool reads exports from the .arcgispro/ folder created by the
|
|
31
|
+
ProExporter add-in. Use it to validate exports, view summaries,
|
|
32
|
+
and assemble snapshots for AI agents.
|
|
33
|
+
|
|
34
|
+
\b
|
|
35
|
+
Quick start:
|
|
36
|
+
pip install arcgispro-cli
|
|
37
|
+
arcgispro install # Install add-in (one time)
|
|
38
|
+
# In ArcGIS Pro: Click "Snapshot" button
|
|
39
|
+
arcgispro inspect # View exported context
|
|
40
|
+
"""
|
|
41
|
+
ctx.ensure_object(dict)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Register commands
|
|
45
|
+
main.add_command(install.install_cmd, name="install")
|
|
46
|
+
main.add_command(install.uninstall_cmd, name="uninstall")
|
|
47
|
+
main.add_command(inspect.inspect_cmd, name="inspect")
|
|
48
|
+
main.add_command(dump.dump_cmd, name="dump")
|
|
49
|
+
main.add_command(images.images_cmd, name="images")
|
|
50
|
+
main.add_command(snapshot.snapshot_cmd, name="snapshot")
|
|
51
|
+
main.add_command(clean.clean_cmd, name="clean")
|
|
52
|
+
main.add_command(open_project.open_cmd, name="open")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command implementations for arcgispro CLI."""
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""clean command - Remove generated files."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import shutil
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..paths import (
|
|
9
|
+
find_arcgispro_folder,
|
|
10
|
+
get_context_folder,
|
|
11
|
+
get_images_folder,
|
|
12
|
+
get_snapshot_folder,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command("clean")
|
|
19
|
+
@click.option("--path", "-p", type=click.Path(exists=True), help="Path to search for .arcgispro folder")
|
|
20
|
+
@click.option("--images", "clean_images", is_flag=True, help="Remove images/ folder")
|
|
21
|
+
@click.option("--context", "clean_context", is_flag=True, help="Remove context/ folder")
|
|
22
|
+
@click.option("--snapshot", "clean_snapshot", is_flag=True, help="Remove snapshot/ folder")
|
|
23
|
+
@click.option("--all", "clean_all", is_flag=True, help="Remove everything in .arcgispro/")
|
|
24
|
+
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
|
|
25
|
+
def clean_cmd(path, clean_images, clean_context, clean_snapshot, clean_all, yes):
|
|
26
|
+
"""Remove generated files from .arcgispro/ folder.
|
|
27
|
+
|
|
28
|
+
Use flags to specify what to remove:
|
|
29
|
+
|
|
30
|
+
\b
|
|
31
|
+
--images Remove images/ folder
|
|
32
|
+
--context Remove context/ folder
|
|
33
|
+
--snapshot Remove snapshot/ folder
|
|
34
|
+
--all Remove everything
|
|
35
|
+
|
|
36
|
+
By default, asks for confirmation before deleting.
|
|
37
|
+
"""
|
|
38
|
+
start_path = Path(path) if path else None
|
|
39
|
+
arcgispro_path = find_arcgispro_folder(start_path)
|
|
40
|
+
|
|
41
|
+
if not arcgispro_path:
|
|
42
|
+
console.print("[red]✗[/red] No .arcgispro folder found")
|
|
43
|
+
raise SystemExit(1)
|
|
44
|
+
|
|
45
|
+
# If no flags specified, show help
|
|
46
|
+
if not any([clean_images, clean_context, clean_snapshot, clean_all]):
|
|
47
|
+
console.print("[yellow]No cleanup option specified.[/yellow]")
|
|
48
|
+
console.print()
|
|
49
|
+
console.print("Use one of:")
|
|
50
|
+
console.print(" --images Remove images/ folder")
|
|
51
|
+
console.print(" --context Remove context/ folder")
|
|
52
|
+
console.print(" --snapshot Remove snapshot/ folder")
|
|
53
|
+
console.print(" --all Remove everything in .arcgispro/")
|
|
54
|
+
raise SystemExit(1)
|
|
55
|
+
|
|
56
|
+
to_remove = []
|
|
57
|
+
|
|
58
|
+
if clean_all:
|
|
59
|
+
# Remove everything
|
|
60
|
+
for item in arcgispro_path.iterdir():
|
|
61
|
+
to_remove.append(item)
|
|
62
|
+
else:
|
|
63
|
+
if clean_images:
|
|
64
|
+
images_folder = get_images_folder(arcgispro_path)
|
|
65
|
+
if images_folder.exists():
|
|
66
|
+
to_remove.append(images_folder)
|
|
67
|
+
|
|
68
|
+
if clean_context:
|
|
69
|
+
context_folder = get_context_folder(arcgispro_path)
|
|
70
|
+
if context_folder.exists():
|
|
71
|
+
to_remove.append(context_folder)
|
|
72
|
+
# Also remove meta.json and active_project.txt
|
|
73
|
+
meta_file = arcgispro_path / "meta.json"
|
|
74
|
+
if meta_file.exists():
|
|
75
|
+
to_remove.append(meta_file)
|
|
76
|
+
active_file = arcgispro_path / "active_project.txt"
|
|
77
|
+
if active_file.exists():
|
|
78
|
+
to_remove.append(active_file)
|
|
79
|
+
|
|
80
|
+
if clean_snapshot:
|
|
81
|
+
snapshot_folder = get_snapshot_folder(arcgispro_path)
|
|
82
|
+
if snapshot_folder.exists():
|
|
83
|
+
to_remove.append(snapshot_folder)
|
|
84
|
+
|
|
85
|
+
if not to_remove:
|
|
86
|
+
console.print("[dim]Nothing to remove.[/dim]")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# Show what will be removed
|
|
90
|
+
console.print("[bold]Will remove:[/bold]")
|
|
91
|
+
for item in to_remove:
|
|
92
|
+
if item.is_dir():
|
|
93
|
+
count = sum(1 for _ in item.rglob("*") if _.is_file())
|
|
94
|
+
console.print(f" 📁 {item.name}/ ({count} files)")
|
|
95
|
+
else:
|
|
96
|
+
console.print(f" 📄 {item.name}")
|
|
97
|
+
|
|
98
|
+
# Confirm
|
|
99
|
+
if not yes:
|
|
100
|
+
console.print()
|
|
101
|
+
if not click.confirm("Proceed with deletion?"):
|
|
102
|
+
console.print("[dim]Cancelled.[/dim]")
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
# Delete
|
|
106
|
+
removed = 0
|
|
107
|
+
for item in to_remove:
|
|
108
|
+
try:
|
|
109
|
+
if item.is_dir():
|
|
110
|
+
shutil.rmtree(item)
|
|
111
|
+
else:
|
|
112
|
+
item.unlink()
|
|
113
|
+
removed += 1
|
|
114
|
+
console.print(f"[green]✓[/green] Removed {item.name}")
|
|
115
|
+
except Exception as e:
|
|
116
|
+
console.print(f"[red]✗[/red] Failed to remove {item.name}: {e}")
|
|
117
|
+
|
|
118
|
+
console.print()
|
|
119
|
+
console.print(f"[bold]Removed {removed} item(s)[/bold]")
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""dump command - Validate context JSON files."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..paths import find_arcgispro_folder, get_context_folder, load_json_file
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
EXPECTED_FILES = [
|
|
12
|
+
("meta.json", False), # (filename, is_in_context_folder)
|
|
13
|
+
("project.json", True),
|
|
14
|
+
("maps.json", True),
|
|
15
|
+
("layers.json", True),
|
|
16
|
+
("tables.json", True),
|
|
17
|
+
("connections.json", True),
|
|
18
|
+
("layouts.json", True),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command("dump")
|
|
23
|
+
@click.option("--path", "-p", type=click.Path(exists=True), help="Path to search for .arcgispro folder")
|
|
24
|
+
@click.option("--verbose", "-v", is_flag=True, help="Show file contents")
|
|
25
|
+
def dump_cmd(path, verbose):
|
|
26
|
+
"""Validate that context JSON files exist and are valid.
|
|
27
|
+
|
|
28
|
+
Checks for expected JSON files in the .arcgispro/context/ folder
|
|
29
|
+
and validates they contain valid JSON.
|
|
30
|
+
|
|
31
|
+
Exit code 0 if all files valid, 1 if any issues found.
|
|
32
|
+
"""
|
|
33
|
+
start_path = Path(path) if path else None
|
|
34
|
+
arcgispro_path = find_arcgispro_folder(start_path)
|
|
35
|
+
|
|
36
|
+
if not arcgispro_path:
|
|
37
|
+
console.print("[red]✗[/red] No .arcgispro folder found")
|
|
38
|
+
raise SystemExit(1)
|
|
39
|
+
|
|
40
|
+
context_folder = get_context_folder(arcgispro_path)
|
|
41
|
+
|
|
42
|
+
console.print(f"[bold]Validating context files in:[/bold] {arcgispro_path}")
|
|
43
|
+
console.print()
|
|
44
|
+
|
|
45
|
+
all_valid = True
|
|
46
|
+
valid_count = 0
|
|
47
|
+
|
|
48
|
+
for filename, in_context in EXPECTED_FILES:
|
|
49
|
+
if in_context:
|
|
50
|
+
file_path = context_folder / filename
|
|
51
|
+
else:
|
|
52
|
+
file_path = arcgispro_path / filename
|
|
53
|
+
|
|
54
|
+
if not file_path.exists():
|
|
55
|
+
console.print(f"[yellow]⚠[/yellow] {filename}: [yellow]missing[/yellow]")
|
|
56
|
+
all_valid = False
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
data = load_json_file(file_path)
|
|
60
|
+
if data is None:
|
|
61
|
+
console.print(f"[red]✗[/red] {filename}: [red]invalid JSON[/red]")
|
|
62
|
+
all_valid = False
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# Get some stats about the data
|
|
66
|
+
if isinstance(data, list):
|
|
67
|
+
info = f"{len(data)} items"
|
|
68
|
+
elif isinstance(data, dict):
|
|
69
|
+
info = f"{len(data)} keys"
|
|
70
|
+
else:
|
|
71
|
+
info = "valid"
|
|
72
|
+
|
|
73
|
+
console.print(f"[green]✓[/green] {filename}: {info}")
|
|
74
|
+
valid_count += 1
|
|
75
|
+
|
|
76
|
+
if verbose and isinstance(data, dict):
|
|
77
|
+
for key in list(data.keys())[:5]:
|
|
78
|
+
console.print(f" {key}: {type(data[key]).__name__}")
|
|
79
|
+
|
|
80
|
+
console.print()
|
|
81
|
+
console.print(f"[bold]Result:[/bold] {valid_count}/{len(EXPECTED_FILES)} files valid")
|
|
82
|
+
|
|
83
|
+
if not all_valid:
|
|
84
|
+
console.print("[yellow]Some files are missing or invalid. Re-run export from ArcGIS Pro.[/yellow]")
|
|
85
|
+
raise SystemExit(1)
|
|
86
|
+
|
|
87
|
+
console.print("[green]All context files valid![/green]")
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""images command - Validate exported images."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..paths import find_arcgispro_folder, list_image_files, get_images_folder
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command("images")
|
|
13
|
+
@click.option("--path", "-p", type=click.Path(exists=True), help="Path to search for .arcgispro folder")
|
|
14
|
+
def images_cmd(path):
|
|
15
|
+
"""Validate that exported images exist.
|
|
16
|
+
|
|
17
|
+
Checks for PNG files in the .arcgispro/images/ folder.
|
|
18
|
+
|
|
19
|
+
Exit code 0 if images exist, 1 if none found.
|
|
20
|
+
"""
|
|
21
|
+
start_path = Path(path) if path else None
|
|
22
|
+
arcgispro_path = find_arcgispro_folder(start_path)
|
|
23
|
+
|
|
24
|
+
if not arcgispro_path:
|
|
25
|
+
console.print("[red]✗[/red] No .arcgispro folder found")
|
|
26
|
+
raise SystemExit(1)
|
|
27
|
+
|
|
28
|
+
images_folder = get_images_folder(arcgispro_path)
|
|
29
|
+
|
|
30
|
+
console.print(f"[bold]Checking images in:[/bold] {images_folder}")
|
|
31
|
+
console.print()
|
|
32
|
+
|
|
33
|
+
if not images_folder.exists():
|
|
34
|
+
console.print("[yellow]⚠[/yellow] images/ folder does not exist")
|
|
35
|
+
console.print(" Run 'Export Images' or 'Snapshot' from ArcGIS Pro.")
|
|
36
|
+
raise SystemExit(1)
|
|
37
|
+
|
|
38
|
+
images = list_image_files(arcgispro_path)
|
|
39
|
+
|
|
40
|
+
if not images:
|
|
41
|
+
console.print("[yellow]⚠[/yellow] No PNG images found")
|
|
42
|
+
console.print(" Make sure a map view is active when exporting.")
|
|
43
|
+
raise SystemExit(1)
|
|
44
|
+
|
|
45
|
+
# Categorize images
|
|
46
|
+
map_images = [img for img in images if img.name.startswith("map_")]
|
|
47
|
+
layout_images = [img for img in images if img.name.startswith("layout_")]
|
|
48
|
+
other_images = [img for img in images if not img.name.startswith(("map_", "layout_"))]
|
|
49
|
+
|
|
50
|
+
console.print("[bold]Map images:[/bold]")
|
|
51
|
+
if map_images:
|
|
52
|
+
for img in map_images:
|
|
53
|
+
size_kb = img.stat().st_size / 1024
|
|
54
|
+
console.print(f" [green]✓[/green] {img.name} ({size_kb:.1f} KB)")
|
|
55
|
+
else:
|
|
56
|
+
console.print(" [dim]None[/dim]")
|
|
57
|
+
|
|
58
|
+
console.print()
|
|
59
|
+
console.print("[bold]Layout images:[/bold]")
|
|
60
|
+
if layout_images:
|
|
61
|
+
for img in layout_images:
|
|
62
|
+
size_kb = img.stat().st_size / 1024
|
|
63
|
+
console.print(f" [green]✓[/green] {img.name} ({size_kb:.1f} KB)")
|
|
64
|
+
else:
|
|
65
|
+
console.print(" [dim]None[/dim]")
|
|
66
|
+
|
|
67
|
+
if other_images:
|
|
68
|
+
console.print()
|
|
69
|
+
console.print("[bold]Other images:[/bold]")
|
|
70
|
+
for img in other_images:
|
|
71
|
+
size_kb = img.stat().st_size / 1024
|
|
72
|
+
console.print(f" [green]✓[/green] {img.name} ({size_kb:.1f} KB)")
|
|
73
|
+
|
|
74
|
+
console.print()
|
|
75
|
+
console.print(f"[bold]Total:[/bold] {len(images)} images found")
|
|
76
|
+
console.print("[green]Image validation passed![/green]")
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""inspect command - Print human-readable summary of exports."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich import box
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from ..paths import find_arcgispro_folder, load_context_files, list_image_files
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command("inspect")
|
|
16
|
+
@click.option("--path", "-p", type=click.Path(exists=True), help="Path to search for .arcgispro folder")
|
|
17
|
+
def inspect_cmd(path):
|
|
18
|
+
"""Print a human-readable summary of the exported context.
|
|
19
|
+
|
|
20
|
+
Shows project info, maps, layers, and export metadata in a
|
|
21
|
+
formatted display.
|
|
22
|
+
"""
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
start_path = Path(path) if path else None
|
|
26
|
+
arcgispro_path = find_arcgispro_folder(start_path)
|
|
27
|
+
|
|
28
|
+
if not arcgispro_path:
|
|
29
|
+
console.print("[red]✗[/red] No .arcgispro folder found")
|
|
30
|
+
console.print(" Run the Snapshot export from ArcGIS Pro first.")
|
|
31
|
+
raise SystemExit(1)
|
|
32
|
+
|
|
33
|
+
context = load_context_files(arcgispro_path)
|
|
34
|
+
images = list_image_files(arcgispro_path)
|
|
35
|
+
|
|
36
|
+
# Header
|
|
37
|
+
console.print()
|
|
38
|
+
console.print(Panel.fit(
|
|
39
|
+
"[bold blue]ArcGIS Pro Session Context[/bold blue]",
|
|
40
|
+
border_style="blue"
|
|
41
|
+
))
|
|
42
|
+
|
|
43
|
+
# Meta info
|
|
44
|
+
meta = context.get("meta")
|
|
45
|
+
if meta:
|
|
46
|
+
exported_at = meta.get("exportedAt", "Unknown")
|
|
47
|
+
if isinstance(exported_at, str) and "T" in exported_at:
|
|
48
|
+
try:
|
|
49
|
+
dt = datetime.fromisoformat(exported_at.replace("Z", "+00:00"))
|
|
50
|
+
exported_at = dt.strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
51
|
+
except ValueError:
|
|
52
|
+
pass
|
|
53
|
+
console.print(f"[dim]Exported: {exported_at}[/dim]")
|
|
54
|
+
console.print(f"[dim]Location: {arcgispro_path}[/dim]")
|
|
55
|
+
console.print()
|
|
56
|
+
|
|
57
|
+
# Project info
|
|
58
|
+
project = context.get("project")
|
|
59
|
+
if project:
|
|
60
|
+
console.print("[bold]📁 Project[/bold]")
|
|
61
|
+
console.print(f" Name: [cyan]{project.get('name', 'Unknown')}[/cyan]")
|
|
62
|
+
if project.get("path"):
|
|
63
|
+
console.print(f" Path: {project.get('path')}")
|
|
64
|
+
map_count = len(project.get("mapNames", []))
|
|
65
|
+
layout_count = len(project.get("layoutNames", []))
|
|
66
|
+
console.print(f" Maps: {map_count} | Layouts: {layout_count}")
|
|
67
|
+
console.print()
|
|
68
|
+
else:
|
|
69
|
+
console.print("[yellow]⚠ No project info found[/yellow]")
|
|
70
|
+
console.print()
|
|
71
|
+
|
|
72
|
+
# Maps
|
|
73
|
+
maps = context.get("maps") or []
|
|
74
|
+
if maps:
|
|
75
|
+
console.print("[bold]🗺️ Maps[/bold]")
|
|
76
|
+
for m in maps:
|
|
77
|
+
active = " [green]★ Active[/green]" if m.get("isActiveMap") else ""
|
|
78
|
+
console.print(f" • {m.get('name', 'Unknown')}{active}")
|
|
79
|
+
console.print(f" Type: {m.get('mapType', '-')} | Layers: {m.get('layerCount', 0)} | Tables: {m.get('standaloneTableCount', 0)}")
|
|
80
|
+
if m.get("scale"):
|
|
81
|
+
console.print(f" Scale: 1:{m.get('scale'):,.0f}")
|
|
82
|
+
console.print()
|
|
83
|
+
|
|
84
|
+
# Layers summary
|
|
85
|
+
layers = context.get("layers") or []
|
|
86
|
+
if layers:
|
|
87
|
+
console.print("[bold]📊 Layers[/bold]")
|
|
88
|
+
|
|
89
|
+
table = Table(box=box.SIMPLE, show_header=True, header_style="bold")
|
|
90
|
+
table.add_column("Layer", style="cyan")
|
|
91
|
+
table.add_column("Type")
|
|
92
|
+
table.add_column("Geometry")
|
|
93
|
+
table.add_column("Features", justify="right")
|
|
94
|
+
table.add_column("Visible")
|
|
95
|
+
|
|
96
|
+
for layer in layers[:15]: # Limit to first 15
|
|
97
|
+
visible = "✓" if layer.get("isVisible") else "✗"
|
|
98
|
+
broken = " [red]⚠[/red]" if layer.get("isBroken") else ""
|
|
99
|
+
features = f"{layer.get('featureCount', '-'):,}" if layer.get('featureCount') else "-"
|
|
100
|
+
|
|
101
|
+
table.add_row(
|
|
102
|
+
f"{layer.get('name', 'Unknown')}{broken}",
|
|
103
|
+
layer.get("layerType", "-"),
|
|
104
|
+
layer.get("geometryType", "-"),
|
|
105
|
+
features,
|
|
106
|
+
visible
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
console.print(table)
|
|
110
|
+
|
|
111
|
+
if len(layers) > 15:
|
|
112
|
+
console.print(f" [dim]...and {len(layers) - 15} more layers[/dim]")
|
|
113
|
+
console.print()
|
|
114
|
+
|
|
115
|
+
# Images
|
|
116
|
+
if images:
|
|
117
|
+
console.print("[bold]🖼️ Images[/bold]")
|
|
118
|
+
for img in images:
|
|
119
|
+
console.print(f" • {img.name}")
|
|
120
|
+
console.print()
|
|
121
|
+
|
|
122
|
+
# Summary
|
|
123
|
+
console.print("[bold]Summary[/bold]")
|
|
124
|
+
console.print(f" Context files: {sum(1 for v in context.values() if v is not None)}/7")
|
|
125
|
+
console.print(f" Images: {len(images)}")
|
|
126
|
+
console.print()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""install command - Install the ArcGIS Pro add-in."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_addin_path() -> Path:
|
|
15
|
+
"""Get the path to the bundled .addin file."""
|
|
16
|
+
return Path(__file__).parent.parent / "addin" / "ProExporter.addin"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.command("install")
|
|
20
|
+
def install_cmd():
|
|
21
|
+
"""Install the ProExporter add-in for ArcGIS Pro.
|
|
22
|
+
|
|
23
|
+
Extracts the bundled add-in and launches the installer.
|
|
24
|
+
You'll see the ArcGIS Pro Add-In Installation Utility dialog.
|
|
25
|
+
Click "Install Add-In" to complete installation.
|
|
26
|
+
"""
|
|
27
|
+
addin_source = get_addin_path()
|
|
28
|
+
|
|
29
|
+
if not addin_source.exists():
|
|
30
|
+
console.print("[red]✗[/red] Add-in file not found in package.")
|
|
31
|
+
console.print(f" Expected: {addin_source}")
|
|
32
|
+
raise SystemExit(1)
|
|
33
|
+
|
|
34
|
+
# Copy to temp folder (some systems have issues launching from site-packages)
|
|
35
|
+
temp_dir = Path(tempfile.gettempdir()) / "arcgispro_cli"
|
|
36
|
+
temp_dir.mkdir(exist_ok=True)
|
|
37
|
+
|
|
38
|
+
addin_dest = temp_dir / "ProExporter.esriAddinX"
|
|
39
|
+
shutil.copy2(addin_source, addin_dest)
|
|
40
|
+
|
|
41
|
+
console.print("[bold]Installing ProExporter add-in...[/bold]")
|
|
42
|
+
console.print()
|
|
43
|
+
console.print(f" Add-in: {addin_dest}")
|
|
44
|
+
console.print()
|
|
45
|
+
|
|
46
|
+
# Launch the installer (Windows shell "open" action)
|
|
47
|
+
try:
|
|
48
|
+
os.startfile(str(addin_dest))
|
|
49
|
+
console.print("[green]✓[/green] Add-in installer launched!")
|
|
50
|
+
console.print()
|
|
51
|
+
console.print(" [dim]Click 'Install Add-In' in the dialog that appeared.[/dim]")
|
|
52
|
+
console.print(" [dim]Then restart ArcGIS Pro to use ProExporter.[/dim]")
|
|
53
|
+
except OSError as e:
|
|
54
|
+
console.print(f"[red]✗[/red] Failed to launch installer: {e}")
|
|
55
|
+
console.print()
|
|
56
|
+
console.print(" Try double-clicking the file manually:")
|
|
57
|
+
console.print(f" {addin_dest}")
|
|
58
|
+
raise SystemExit(1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@click.command("uninstall")
|
|
62
|
+
def uninstall_cmd():
|
|
63
|
+
"""Show instructions for uninstalling the add-in.
|
|
64
|
+
|
|
65
|
+
ArcGIS Pro add-ins must be removed through ArcGIS Pro settings.
|
|
66
|
+
"""
|
|
67
|
+
console.print("[bold]Uninstalling ProExporter add-in[/bold]")
|
|
68
|
+
console.print()
|
|
69
|
+
console.print(" Add-ins are managed in ArcGIS Pro:")
|
|
70
|
+
console.print()
|
|
71
|
+
console.print(" 1. Open ArcGIS Pro")
|
|
72
|
+
console.print(" 2. Go to [cyan]Project → Add-In Manager[/cyan]")
|
|
73
|
+
console.print(" 3. Find [cyan]ProExporter[/cyan] in the list")
|
|
74
|
+
console.print(" 4. Click [cyan]Delete this Add-In[/cyan]")
|
|
75
|
+
console.print(" 5. Restart ArcGIS Pro")
|
|
76
|
+
console.print()
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""open command - Select active project."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ..paths import find_arcgispro_folder, find_aprx_files
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command("open")
|
|
13
|
+
@click.option("--path", "-p", type=click.Path(exists=True), help="Path to search for .aprx files")
|
|
14
|
+
@click.argument("project", required=False)
|
|
15
|
+
def open_cmd(path, project):
|
|
16
|
+
"""Select the active ArcGIS Pro project.
|
|
17
|
+
|
|
18
|
+
If PROJECT is not specified, searches for .aprx files in the
|
|
19
|
+
current directory and lets you choose.
|
|
20
|
+
|
|
21
|
+
The selected project path is written to .arcgispro/active_project.txt
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
Examples:
|
|
25
|
+
arcgispro open # Search and select
|
|
26
|
+
arcgispro open MyProject.aprx # Select specific project
|
|
27
|
+
"""
|
|
28
|
+
search_path = Path(path) if path else Path.cwd()
|
|
29
|
+
|
|
30
|
+
# If project specified directly, use it
|
|
31
|
+
if project:
|
|
32
|
+
project_path = Path(project)
|
|
33
|
+
if not project_path.exists():
|
|
34
|
+
# Try relative to search path
|
|
35
|
+
project_path = search_path / project
|
|
36
|
+
|
|
37
|
+
if not project_path.exists():
|
|
38
|
+
console.print(f"[red]✗[/red] Project not found: {project}")
|
|
39
|
+
raise SystemExit(1)
|
|
40
|
+
|
|
41
|
+
if not project_path.suffix.lower() == ".aprx":
|
|
42
|
+
console.print(f"[red]✗[/red] Not an ArcGIS Pro project file: {project}")
|
|
43
|
+
raise SystemExit(1)
|
|
44
|
+
|
|
45
|
+
_save_active_project(search_path, project_path)
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Search for .aprx files
|
|
49
|
+
console.print(f"[bold]Searching for .aprx files in:[/bold] {search_path}")
|
|
50
|
+
console.print()
|
|
51
|
+
|
|
52
|
+
aprx_files = find_aprx_files(search_path)
|
|
53
|
+
|
|
54
|
+
if not aprx_files:
|
|
55
|
+
console.print("[yellow]No .aprx files found[/yellow]")
|
|
56
|
+
console.print(" Navigate to a folder containing an ArcGIS Pro project.")
|
|
57
|
+
raise SystemExit(1)
|
|
58
|
+
|
|
59
|
+
# Show found projects
|
|
60
|
+
console.print(f"[bold]Found {len(aprx_files)} project(s):[/bold]")
|
|
61
|
+
console.print()
|
|
62
|
+
|
|
63
|
+
for i, aprx in enumerate(aprx_files, 1):
|
|
64
|
+
relative = aprx.relative_to(search_path) if aprx.is_relative_to(search_path) else aprx
|
|
65
|
+
console.print(f" {i}. {relative}")
|
|
66
|
+
|
|
67
|
+
console.print()
|
|
68
|
+
|
|
69
|
+
# Let user choose
|
|
70
|
+
if len(aprx_files) == 1:
|
|
71
|
+
choice = 1
|
|
72
|
+
console.print(f"[dim]Auto-selecting the only project found.[/dim]")
|
|
73
|
+
else:
|
|
74
|
+
choice = click.prompt(
|
|
75
|
+
"Select project",
|
|
76
|
+
type=click.IntRange(1, len(aprx_files)),
|
|
77
|
+
default=1
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
selected = aprx_files[choice - 1]
|
|
81
|
+
_save_active_project(search_path, selected)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _save_active_project(base_path: Path, project_path: Path):
|
|
85
|
+
"""Save the selected project to .arcgispro/active_project.txt"""
|
|
86
|
+
|
|
87
|
+
# Create .arcgispro folder if needed
|
|
88
|
+
arcgispro_folder = base_path / ".arcgispro"
|
|
89
|
+
arcgispro_folder.mkdir(exist_ok=True)
|
|
90
|
+
|
|
91
|
+
# Write active project
|
|
92
|
+
active_file = arcgispro_folder / "active_project.txt"
|
|
93
|
+
active_file.write_text(str(project_path.resolve()), encoding="utf-8")
|
|
94
|
+
|
|
95
|
+
console.print()
|
|
96
|
+
console.print(f"[green]✓[/green] Active project set to: [cyan]{project_path.name}[/cyan]")
|
|
97
|
+
console.print(f" Path: {project_path.resolve()}")
|
|
98
|
+
console.print()
|
|
99
|
+
console.print("[dim]Open this project in ArcGIS Pro and run Snapshot to export context.[/dim]")
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""snapshot command - Assemble full snapshot."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import shutil
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..paths import (
|
|
9
|
+
find_arcgispro_folder,
|
|
10
|
+
load_context_files,
|
|
11
|
+
list_image_files,
|
|
12
|
+
get_snapshot_folder,
|
|
13
|
+
get_images_folder,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.command("snapshot")
|
|
20
|
+
@click.option("--path", "-p", type=click.Path(exists=True), help="Path to search for .arcgispro folder")
|
|
21
|
+
@click.option("--force", "-f", is_flag=True, help="Overwrite existing snapshot")
|
|
22
|
+
def snapshot_cmd(path, force):
|
|
23
|
+
"""Assemble a complete snapshot from context and images.
|
|
24
|
+
|
|
25
|
+
Verifies that context JSON and images exist, then assembles
|
|
26
|
+
everything into the snapshot/ folder for AI consumption.
|
|
27
|
+
|
|
28
|
+
The snapshot includes:
|
|
29
|
+
- context.md: Human-readable summary
|
|
30
|
+
- CONTEXT_SKILL.md: How to use the exports
|
|
31
|
+
- AGENT_TOOL_SKILL.md: CLI usage guide
|
|
32
|
+
- images/: Copy of exported images
|
|
33
|
+
"""
|
|
34
|
+
start_path = Path(path) if path else None
|
|
35
|
+
arcgispro_path = find_arcgispro_folder(start_path)
|
|
36
|
+
|
|
37
|
+
if not arcgispro_path:
|
|
38
|
+
console.print("[red]✗[/red] No .arcgispro folder found")
|
|
39
|
+
console.print(" Run the Snapshot export from ArcGIS Pro first.")
|
|
40
|
+
raise SystemExit(1)
|
|
41
|
+
|
|
42
|
+
console.print(f"[bold]Assembling snapshot from:[/bold] {arcgispro_path}")
|
|
43
|
+
console.print()
|
|
44
|
+
|
|
45
|
+
# Verify context exists
|
|
46
|
+
context = load_context_files(arcgispro_path)
|
|
47
|
+
missing_context = [k for k, v in context.items() if v is None]
|
|
48
|
+
|
|
49
|
+
if missing_context:
|
|
50
|
+
console.print(f"[yellow]⚠[/yellow] Missing context files: {', '.join(missing_context)}")
|
|
51
|
+
console.print(" Run 'Dump Context' or 'Snapshot' from ArcGIS Pro.")
|
|
52
|
+
|
|
53
|
+
# Check for images
|
|
54
|
+
images = list_image_files(arcgispro_path)
|
|
55
|
+
if not images:
|
|
56
|
+
console.print("[yellow]⚠[/yellow] No images found")
|
|
57
|
+
console.print(" Run 'Export Images' or 'Snapshot' from ArcGIS Pro.")
|
|
58
|
+
|
|
59
|
+
# Check if snapshot already exists
|
|
60
|
+
snapshot_folder = get_snapshot_folder(arcgispro_path)
|
|
61
|
+
|
|
62
|
+
if snapshot_folder.exists():
|
|
63
|
+
existing_files = list(snapshot_folder.iterdir())
|
|
64
|
+
if existing_files and not force:
|
|
65
|
+
console.print(f"[yellow]⚠[/yellow] Snapshot folder already contains {len(existing_files)} items")
|
|
66
|
+
console.print(" Use --force to overwrite, or delete snapshot/ manually.")
|
|
67
|
+
raise SystemExit(1)
|
|
68
|
+
|
|
69
|
+
# Check if the add-in already created the snapshot files
|
|
70
|
+
context_md = snapshot_folder / "context.md"
|
|
71
|
+
context_skill = snapshot_folder / "CONTEXT_SKILL.md"
|
|
72
|
+
agent_skill = snapshot_folder / "AGENT_TOOL_SKILL.md"
|
|
73
|
+
|
|
74
|
+
files_created = 0
|
|
75
|
+
|
|
76
|
+
if context_md.exists():
|
|
77
|
+
console.print(f"[green]✓[/green] context.md exists")
|
|
78
|
+
files_created += 1
|
|
79
|
+
else:
|
|
80
|
+
console.print(f"[yellow]⚠[/yellow] context.md missing (should be created by add-in)")
|
|
81
|
+
|
|
82
|
+
if context_skill.exists():
|
|
83
|
+
console.print(f"[green]✓[/green] CONTEXT_SKILL.md exists")
|
|
84
|
+
files_created += 1
|
|
85
|
+
else:
|
|
86
|
+
console.print(f"[yellow]⚠[/yellow] CONTEXT_SKILL.md missing")
|
|
87
|
+
|
|
88
|
+
if agent_skill.exists():
|
|
89
|
+
console.print(f"[green]✓[/green] AGENT_TOOL_SKILL.md exists")
|
|
90
|
+
files_created += 1
|
|
91
|
+
else:
|
|
92
|
+
console.print(f"[yellow]⚠[/yellow] AGENT_TOOL_SKILL.md missing")
|
|
93
|
+
|
|
94
|
+
# Copy images to snapshot folder
|
|
95
|
+
snapshot_images_folder = snapshot_folder / "images"
|
|
96
|
+
|
|
97
|
+
if images:
|
|
98
|
+
snapshot_images_folder.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
|
|
100
|
+
copied = 0
|
|
101
|
+
for img in images:
|
|
102
|
+
dest = snapshot_images_folder / img.name
|
|
103
|
+
if not dest.exists() or force:
|
|
104
|
+
shutil.copy2(img, dest)
|
|
105
|
+
copied += 1
|
|
106
|
+
|
|
107
|
+
console.print(f"[green]✓[/green] Copied {copied} images to snapshot/images/")
|
|
108
|
+
|
|
109
|
+
console.print()
|
|
110
|
+
|
|
111
|
+
# Summary
|
|
112
|
+
if files_created >= 2 and images:
|
|
113
|
+
console.print("[green]✓ Snapshot is ready![/green]")
|
|
114
|
+
console.print()
|
|
115
|
+
console.print("[bold]Contents:[/bold]")
|
|
116
|
+
console.print(f" {snapshot_folder}/")
|
|
117
|
+
console.print(f" context.md - Human-readable summary")
|
|
118
|
+
console.print(f" CONTEXT_SKILL.md - How to use exports")
|
|
119
|
+
console.print(f" AGENT_TOOL_SKILL.md - CLI usage")
|
|
120
|
+
console.print(f" images/ - {len(images)} PNG files")
|
|
121
|
+
else:
|
|
122
|
+
console.print("[yellow]⚠ Snapshot incomplete[/yellow]")
|
|
123
|
+
console.print(" Run 'Snapshot' from ArcGIS Pro to generate all files.")
|
|
124
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Utility functions for finding and validating .arcgispro folders."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, Dict, Any, List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def find_arcgispro_folder(start_path: Optional[Path] = None) -> Optional[Path]:
|
|
9
|
+
"""
|
|
10
|
+
Find the .arcgispro folder by searching current directory and ancestors.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
start_path: Starting directory to search from. Defaults to cwd.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Path to .arcgispro folder, or None if not found.
|
|
17
|
+
"""
|
|
18
|
+
if start_path is None:
|
|
19
|
+
start_path = Path.cwd()
|
|
20
|
+
|
|
21
|
+
current = start_path.resolve()
|
|
22
|
+
|
|
23
|
+
# Search current directory and ancestors
|
|
24
|
+
while current != current.parent:
|
|
25
|
+
candidate = current / ".arcgispro"
|
|
26
|
+
if candidate.is_dir():
|
|
27
|
+
return candidate
|
|
28
|
+
current = current.parent
|
|
29
|
+
|
|
30
|
+
# Check root as well
|
|
31
|
+
candidate = current / ".arcgispro"
|
|
32
|
+
if candidate.is_dir():
|
|
33
|
+
return candidate
|
|
34
|
+
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_context_folder(arcgispro_path: Path) -> Path:
|
|
39
|
+
"""Get the context subfolder path."""
|
|
40
|
+
return arcgispro_path / "context"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_images_folder(arcgispro_path: Path) -> Path:
|
|
44
|
+
"""Get the images subfolder path."""
|
|
45
|
+
return arcgispro_path / "images"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_snapshot_folder(arcgispro_path: Path) -> Path:
|
|
49
|
+
"""Get the snapshot subfolder path."""
|
|
50
|
+
return arcgispro_path / "snapshot"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_json_file(path: Path) -> Optional[Dict[str, Any]]:
|
|
54
|
+
"""
|
|
55
|
+
Load and parse a JSON file.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
path: Path to JSON file
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Parsed JSON as dict, or None if file doesn't exist or is invalid.
|
|
62
|
+
"""
|
|
63
|
+
if not path.exists():
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
with open(path, "r", encoding="utf-8-sig") as f:
|
|
68
|
+
return json.load(f)
|
|
69
|
+
except (json.JSONDecodeError, IOError):
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def load_context_files(arcgispro_path: Path) -> Dict[str, Any]:
|
|
74
|
+
"""
|
|
75
|
+
Load all context JSON files.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
arcgispro_path: Path to .arcgispro folder
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Dict with keys: meta, project, maps, layers, tables, connections, layouts
|
|
82
|
+
Values are the parsed JSON or None if missing/invalid.
|
|
83
|
+
"""
|
|
84
|
+
context_dir = get_context_folder(arcgispro_path)
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
"meta": load_json_file(arcgispro_path / "meta.json"),
|
|
88
|
+
"project": load_json_file(context_dir / "project.json"),
|
|
89
|
+
"maps": load_json_file(context_dir / "maps.json"),
|
|
90
|
+
"layers": load_json_file(context_dir / "layers.json"),
|
|
91
|
+
"tables": load_json_file(context_dir / "tables.json"),
|
|
92
|
+
"connections": load_json_file(context_dir / "connections.json"),
|
|
93
|
+
"layouts": load_json_file(context_dir / "layouts.json"),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def list_image_files(arcgispro_path: Path) -> List[Path]:
|
|
98
|
+
"""
|
|
99
|
+
List all PNG files in the images folder.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
arcgispro_path: Path to .arcgispro folder
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List of paths to PNG files.
|
|
106
|
+
"""
|
|
107
|
+
images_dir = get_images_folder(arcgispro_path)
|
|
108
|
+
if not images_dir.exists():
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
return list(images_dir.glob("*.png"))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_active_project(arcgispro_path: Path) -> Optional[str]:
|
|
115
|
+
"""
|
|
116
|
+
Read the active project path from active_project.txt.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
arcgispro_path: Path to .arcgispro folder
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Active project path string, or None if not set.
|
|
123
|
+
"""
|
|
124
|
+
active_file = arcgispro_path / "active_project.txt"
|
|
125
|
+
if not active_file.exists():
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
return active_file.read_text(encoding="utf-8").strip()
|
|
130
|
+
except IOError:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def find_aprx_files(directory: Path, max_depth: int = 2) -> List[Path]:
|
|
135
|
+
"""
|
|
136
|
+
Find .aprx files in directory and subdirectories.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
directory: Starting directory
|
|
140
|
+
max_depth: Maximum depth to search
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
List of paths to .aprx files.
|
|
144
|
+
"""
|
|
145
|
+
aprx_files = []
|
|
146
|
+
|
|
147
|
+
def search(path: Path, depth: int):
|
|
148
|
+
if depth > max_depth:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
for item in path.iterdir():
|
|
153
|
+
if item.is_file() and item.suffix.lower() == ".aprx":
|
|
154
|
+
aprx_files.append(item)
|
|
155
|
+
elif item.is_dir() and not item.name.startswith("."):
|
|
156
|
+
search(item, depth + 1)
|
|
157
|
+
except PermissionError:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
search(directory, 0)
|
|
161
|
+
return aprx_files
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "arcgispro-cli"
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
description = "CLI tool for inspecting ArcGIS Pro session exports"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
license = {text = "MIT"}
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "mcveydb"}
|
|
10
|
+
]
|
|
11
|
+
keywords = ["arcgis", "gis", "cli", "esri"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: Microsoft :: Windows",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
dependencies = [
|
|
27
|
+
"click>=8.0",
|
|
28
|
+
"rich>=13.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest>=7.0",
|
|
34
|
+
"pytest-cov>=4.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
arcgispro = "arcgispro_cli.cli:main"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["hatchling"]
|
|
42
|
+
build-backend = "hatchling.build"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["arcgispro_cli"]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.sdist]
|
|
48
|
+
include = [
|
|
49
|
+
"arcgispro_cli/**/*",
|
|
50
|
+
]
|