panorama-super-cli 0.1.0__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.
Files changed (86) hide show
  1. panorama_super_cli-0.1.0/.github/CODEOWNERS +2 -0
  2. panorama_super_cli-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +42 -0
  3. panorama_super_cli-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +28 -0
  4. panorama_super_cli-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  5. panorama_super_cli-0.1.0/.github/dependabot.yml +13 -0
  6. panorama_super_cli-0.1.0/.github/workflows/docs.yml +52 -0
  7. panorama_super_cli-0.1.0/.github/workflows/lint.yml +41 -0
  8. panorama_super_cli-0.1.0/.github/workflows/release.yml +112 -0
  9. panorama_super_cli-0.1.0/.github/workflows/test.yml +27 -0
  10. panorama_super_cli-0.1.0/.gitignore +38 -0
  11. panorama_super_cli-0.1.0/.pre-commit-config.yaml +31 -0
  12. panorama_super_cli-0.1.0/.python-version +1 -0
  13. panorama_super_cli-0.1.0/AGENTS.md +97 -0
  14. panorama_super_cli-0.1.0/CHANGELOG.md +51 -0
  15. panorama_super_cli-0.1.0/CLAUDE.md +90 -0
  16. panorama_super_cli-0.1.0/LICENSE +202 -0
  17. panorama_super_cli-0.1.0/PKG-INFO +110 -0
  18. panorama_super_cli-0.1.0/README.md +79 -0
  19. panorama_super_cli-0.1.0/docs/contributing/branching.md +39 -0
  20. panorama_super_cli-0.1.0/docs/contributing/development.md +60 -0
  21. panorama_super_cli-0.1.0/docs/contributing/release-process.md +40 -0
  22. panorama_super_cli-0.1.0/docs/getting-started/concepts.md +48 -0
  23. panorama_super_cli-0.1.0/docs/getting-started/first-run.md +66 -0
  24. panorama_super_cli-0.1.0/docs/getting-started/install.md +50 -0
  25. panorama_super_cli-0.1.0/docs/guides/duplicates-and-merging.md +87 -0
  26. panorama_super_cli-0.1.0/docs/guides/finding-objects.md +67 -0
  27. panorama_super_cli-0.1.0/docs/guides/live-vs-offline.md +56 -0
  28. panorama_super_cli-0.1.0/docs/guides/naming.md +58 -0
  29. panorama_super_cli-0.1.0/docs/guides/output-formats.md +62 -0
  30. panorama_super_cli-0.1.0/docs/guides/references-and-audit.md +65 -0
  31. panorama_super_cli-0.1.0/docs/guides/safety.md +61 -0
  32. panorama_super_cli-0.1.0/docs/guides/using-with-ai-agents.md +59 -0
  33. panorama_super_cli-0.1.0/docs/index.md +46 -0
  34. panorama_super_cli-0.1.0/docs/reference/cli.md +78 -0
  35. panorama_super_cli-0.1.0/docs/reference/config.md +61 -0
  36. panorama_super_cli-0.1.0/docs/reference/exit-codes.md +45 -0
  37. panorama_super_cli-0.1.0/justfile +49 -0
  38. panorama_super_cli-0.1.0/mkdocs.yml +72 -0
  39. panorama_super_cli-0.1.0/psc/__init__.py +5 -0
  40. panorama_super_cli-0.1.0/psc/__main__.py +6 -0
  41. panorama_super_cli-0.1.0/psc/_version.py +7 -0
  42. panorama_super_cli-0.1.0/psc/cli/__init__.py +1 -0
  43. panorama_super_cli-0.1.0/psc/cli/_plan.py +62 -0
  44. panorama_super_cli-0.1.0/psc/cli/app.py +136 -0
  45. panorama_super_cli-0.1.0/psc/cli/dedup_cmds.py +94 -0
  46. panorama_super_cli-0.1.0/psc/cli/find_cmds.py +92 -0
  47. panorama_super_cli-0.1.0/psc/cli/name_cmds.py +101 -0
  48. panorama_super_cli-0.1.0/psc/cli/profile_cmds.py +68 -0
  49. panorama_super_cli-0.1.0/psc/cli/refs_cmds.py +95 -0
  50. panorama_super_cli-0.1.0/psc/cli/runtime.py +87 -0
  51. panorama_super_cli-0.1.0/psc/config/__init__.py +6 -0
  52. panorama_super_cli-0.1.0/psc/config/loader.py +49 -0
  53. panorama_super_cli-0.1.0/psc/config/models.py +41 -0
  54. panorama_super_cli-0.1.0/psc/core/__init__.py +6 -0
  55. panorama_super_cli-0.1.0/psc/core/apply_xml.py +176 -0
  56. panorama_super_cli-0.1.0/psc/core/changeset.py +127 -0
  57. panorama_super_cli-0.1.0/psc/core/dedup.py +203 -0
  58. panorama_super_cli-0.1.0/psc/core/models.py +235 -0
  59. panorama_super_cli-0.1.0/psc/core/naming.py +176 -0
  60. panorama_super_cli-0.1.0/psc/core/normalize.py +184 -0
  61. panorama_super_cli-0.1.0/psc/core/parse.py +270 -0
  62. panorama_super_cli-0.1.0/psc/core/refs.py +370 -0
  63. panorama_super_cli-0.1.0/psc/core/resolve.py +186 -0
  64. panorama_super_cli-0.1.0/psc/core/setcmd.py +180 -0
  65. panorama_super_cli-0.1.0/psc/core/source.py +119 -0
  66. panorama_super_cli-0.1.0/psc/output/__init__.py +6 -0
  67. panorama_super_cli-0.1.0/psc/output/errors.py +62 -0
  68. panorama_super_cli-0.1.0/psc/output/format.py +142 -0
  69. panorama_super_cli-0.1.0/pyproject.toml +115 -0
  70. panorama_super_cli-0.1.0/scripts/sync_agents_md.py +47 -0
  71. panorama_super_cli-0.1.0/skills/panorama-super-cli/SKILL.md +150 -0
  72. panorama_super_cli-0.1.0/tests/__init__.py +0 -0
  73. panorama_super_cli-0.1.0/tests/conftest.py +26 -0
  74. panorama_super_cli-0.1.0/tests/fixtures/panorama-config.xml +121 -0
  75. panorama_super_cli-0.1.0/tests/test_apply_xml.py +63 -0
  76. panorama_super_cli-0.1.0/tests/test_cli.py +104 -0
  77. panorama_super_cli-0.1.0/tests/test_dedup.py +74 -0
  78. panorama_super_cli-0.1.0/tests/test_hardening.py +121 -0
  79. panorama_super_cli-0.1.0/tests/test_naming.py +91 -0
  80. panorama_super_cli-0.1.0/tests/test_normalize.py +50 -0
  81. panorama_super_cli-0.1.0/tests/test_output.py +35 -0
  82. panorama_super_cli-0.1.0/tests/test_parse.py +48 -0
  83. panorama_super_cli-0.1.0/tests/test_refs.py +42 -0
  84. panorama_super_cli-0.1.0/tests/test_resolve.py +39 -0
  85. panorama_super_cli-0.1.0/tests/test_setcmd.py +61 -0
  86. panorama_super_cli-0.1.0/uv.lock +1202 -0
@@ -0,0 +1,2 @@
1
+ # Default owner for everything in the repo.
2
+ * @thomaschristory
@@ -0,0 +1,42 @@
1
+ name: Bug report
2
+ description: Something `psc` did wrong
3
+ labels: ["bug"]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened?
9
+ description: What you ran and what `psc` did vs. what you expected.
10
+ placeholder: |
11
+ $ psc -c export.xml dedup merge --keep a --remove b
12
+ ...
13
+ validations:
14
+ required: true
15
+ - type: input
16
+ id: version
17
+ attributes:
18
+ label: psc version
19
+ description: Output of `psc --version`.
20
+ validations:
21
+ required: true
22
+ - type: dropdown
23
+ id: source
24
+ attributes:
25
+ label: Source
26
+ options:
27
+ - Offline (--config file.xml)
28
+ - Live (--profile)
29
+ validations:
30
+ required: true
31
+ - type: textarea
32
+ id: env
33
+ attributes:
34
+ label: Environment
35
+ description: OS, Python version, install method (uv/pipx/pip).
36
+ - type: checkboxes
37
+ id: sanitized
38
+ attributes:
39
+ label: Confirmation
40
+ options:
41
+ - label: I have removed any real IPs, hostnames, API keys, or other sensitive config from this report.
42
+ required: true
@@ -0,0 +1,28 @@
1
+ name: Feature request
2
+ description: A new subtool, flag, or capability
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What object-management problem are you trying to solve?
9
+ placeholder: "I want to find every object referenced only by disabled rules so I can clean them up."
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: proposal
14
+ attributes:
15
+ label: Proposed command / behaviour
16
+ placeholder: "psc refs unused --only-disabled-rules"
17
+ - type: dropdown
18
+ id: module
19
+ attributes:
20
+ label: Which area?
21
+ options:
22
+ - find / resolve
23
+ - dedup / merge
24
+ - refs / audit
25
+ - naming
26
+ - output / formats
27
+ - live / API
28
+ - other
@@ -0,0 +1,20 @@
1
+ <!-- Conventional Commit title, e.g. feat(dedup): merge service objects -->
2
+
3
+ ## What & why
4
+
5
+ <!-- What does this change and why? Link the issue: Closes #N -->
6
+
7
+ ## Safety
8
+
9
+ <!-- For any change to writes/merge/rename/apply: -->
10
+ - [ ] Dry-run remains the default; writes still require `--apply`.
11
+ - [ ] References are repointed before deletes/renames.
12
+ - [ ] Unsafe cases add a `blocker` rather than doing something surprising.
13
+
14
+ ## Checklist
15
+
16
+ - [ ] `just test` green
17
+ - [ ] `just lint` clean (ruff + mypy --strict)
18
+ - [ ] Tests added for safety-critical paths
19
+ - [ ] Docs / CHANGELOG updated if user-facing
20
+ - [ ] `just sync-agents` run if `CLAUDE.md` changed
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ groups:
8
+ python-deps:
9
+ patterns: ["*"]
10
+ - package-ecosystem: "github-actions"
11
+ directory: "/"
12
+ schedule:
13
+ interval: "weekly"
@@ -0,0 +1,52 @@
1
+ name: docs
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - "docs/**"
7
+ - "mkdocs.yml"
8
+ - ".github/workflows/docs.yml"
9
+ push:
10
+ tags:
11
+ - "v*"
12
+
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ concurrency:
19
+ group: pages
20
+ cancel-in-progress: false
21
+
22
+ jobs:
23
+ build:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - name: Install uv
28
+ uses: astral-sh/setup-uv@v3
29
+ with:
30
+ enable-cache: true
31
+ - name: Install Python
32
+ run: uv python install 3.12
33
+ - name: Sync deps (runtime + docs)
34
+ run: uv sync --frozen --group docs
35
+ - name: Build site (strict)
36
+ run: uv run mkdocs build --strict
37
+ - name: Upload Pages artifact
38
+ if: github.ref_type == 'tag'
39
+ uses: actions/upload-pages-artifact@v3
40
+ with:
41
+ path: site
42
+
43
+ deploy:
44
+ needs: build
45
+ if: github.ref_type == 'tag'
46
+ runs-on: ubuntu-latest
47
+ environment:
48
+ name: github-pages
49
+ url: ${{ steps.deployment.outputs.page_url }}
50
+ steps:
51
+ - id: deployment
52
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,41 @@
1
+ name: lint
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - name: Install uv
14
+ uses: astral-sh/setup-uv@v3
15
+ with:
16
+ enable-cache: true
17
+ - name: Install Python
18
+ run: uv python install 3.12
19
+ - name: Sync deps
20
+ run: uv sync --frozen
21
+ - name: Ruff check
22
+ run: uv run ruff check psc tests
23
+ - name: Ruff format
24
+ run: uv run ruff format --check psc tests
25
+ - name: Mypy strict
26
+ run: uv run mypy --strict psc
27
+
28
+ agents-md-fresh:
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@v3
34
+ with:
35
+ enable-cache: true
36
+ - name: Install Python
37
+ run: uv python install 3.12
38
+ - name: Sync deps
39
+ run: uv sync --frozen
40
+ - name: Check AGENTS.md is in sync with CLAUDE.md
41
+ run: uv run python scripts/sync_agents_md.py --check
@@ -0,0 +1,112 @@
1
+ name: release
2
+
3
+ # Tag-only trigger. NEVER on pull_request or branch push — the trusted
4
+ # publisher is bound to this workflow filename + the v* tag pattern.
5
+ on:
6
+ push:
7
+ tags:
8
+ - "v[0-9]*.[0-9]*.[0-9]*"
9
+
10
+ # Trusted publishing requires id-token: write so the OIDC token issued to
11
+ # pypa/gh-action-pypi-publish is signed.
12
+ permissions:
13
+ contents: write # gh release create
14
+ id-token: write # PyPI trusted publishing OIDC
15
+
16
+ concurrency:
17
+ group: release
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ release:
22
+ runs-on: ubuntu-latest
23
+ # No `environment:` block — adding one changes the OIDC subject claim and
24
+ # would break a trusted-publisher binding registered against the
25
+ # no-environment form.
26
+ steps:
27
+ - name: Checkout (full history for changelog notes)
28
+ uses: actions/checkout@v4
29
+ with:
30
+ fetch-depth: 0
31
+
32
+ - name: Verify tag is on main
33
+ run: |
34
+ set -euo pipefail
35
+ git fetch origin main
36
+ if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
37
+ echo "Tag $GITHUB_REF_NAME points at $GITHUB_SHA which is not reachable from main." >&2
38
+ exit 1
39
+ fi
40
+ echo "Tag $GITHUB_REF_NAME ($GITHUB_SHA) is on main."
41
+
42
+ - name: Install uv
43
+ uses: astral-sh/setup-uv@v3
44
+ with:
45
+ enable-cache: true
46
+
47
+ - name: Install Python
48
+ run: uv python install 3.12
49
+
50
+ - name: Sync runtime deps (so the next step can import psc._version)
51
+ run: uv sync --frozen
52
+
53
+ - name: Verify tag matches package version
54
+ run: |
55
+ set -euo pipefail
56
+ tag_version="${GITHUB_REF_NAME#v}"
57
+ pkg_version="$(uv run python -c 'from psc._version import __version__; print(__version__)')"
58
+ if [ "$tag_version" != "$pkg_version" ]; then
59
+ echo "Tag $GITHUB_REF_NAME -> $tag_version does not match psc/_version.py $pkg_version" >&2
60
+ exit 1
61
+ fi
62
+ echo "Tag $GITHUB_REF_NAME matches package version $pkg_version"
63
+
64
+ - name: Build sdist + wheel
65
+ run: |
66
+ rm -rf dist
67
+ uv build --out-dir dist
68
+ ls -la dist/
69
+
70
+ - name: Verify wheel + sdist filenames match tag
71
+ run: |
72
+ set -euo pipefail
73
+ tag_version="${GITHUB_REF_NAME#v}"
74
+ if ! ls "dist/panorama_super_cli-${tag_version}-py3-none-any.whl" >/dev/null 2>&1; then
75
+ echo "Wheel for ${tag_version} not found in dist/" >&2; ls dist/ >&2; exit 1
76
+ fi
77
+ if ! ls "dist/panorama_super_cli-${tag_version}.tar.gz" >/dev/null 2>&1; then
78
+ echo "Sdist for ${tag_version} not found in dist/" >&2; ls dist/ >&2; exit 1
79
+ fi
80
+
81
+ - name: Publish to PyPI (trusted publishing)
82
+ uses: pypa/gh-action-pypi-publish@release/v1
83
+ with:
84
+ packages-dir: dist
85
+ verbose: true
86
+ skip-existing: true
87
+
88
+ - name: Extract changelog section for release notes
89
+ run: |
90
+ set -euo pipefail
91
+ tag_version="${GITHUB_REF_NAME#v}"
92
+ awk -v ver="$tag_version" '
93
+ $0 ~ "^## v" ver " " { capture=1; print; next }
94
+ capture && /^## / { exit }
95
+ capture { print }
96
+ ' CHANGELOG.md > /tmp/release-notes.md
97
+ if [ ! -s /tmp/release-notes.md ]; then
98
+ echo "Release notes for v${tag_version} not found in CHANGELOG.md" >&2
99
+ exit 1
100
+ fi
101
+
102
+ - name: Create GitHub Release
103
+ env:
104
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105
+ run: |
106
+ set -euo pipefail
107
+ tag_version="${GITHUB_REF_NAME#v}"
108
+ gh release create "$GITHUB_REF_NAME" \
109
+ --title "$GITHUB_REF_NAME" \
110
+ --notes-file /tmp/release-notes.md \
111
+ "dist/panorama_super_cli-${tag_version}-py3-none-any.whl" \
112
+ "dist/panorama_super_cli-${tag_version}.tar.gz"
@@ -0,0 +1,27 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest, macos-latest]
15
+ python: ["3.12", "3.13"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v3
20
+ with:
21
+ enable-cache: true
22
+ - name: Install Python
23
+ run: uv python install ${{ matrix.python }}
24
+ - name: Sync deps
25
+ run: uv sync --frozen
26
+ - name: Run tests
27
+ run: uv run pytest -v
@@ -0,0 +1,38 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.egg
9
+
10
+ # venv / uv
11
+ .venv/
12
+ .python-version.local
13
+
14
+ # tooling caches
15
+ .pytest_cache/
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+ .coverage
19
+ htmlcov/
20
+ coverage.xml
21
+
22
+ # mkdocs
23
+ site/
24
+
25
+ # editors / OS
26
+ .idea/
27
+ .vscode/
28
+ .DS_Store
29
+
30
+ # local scratch
31
+ /scratch/
32
+ *.local.*
33
+
34
+ # never commit real Panorama exports or secrets
35
+ *.panrc
36
+ secrets.*
37
+ *.config.xml
38
+ !tests/**/*.xml
@@ -0,0 +1,31 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: end-of-file-fixer
6
+ - id: trailing-whitespace
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-merge-conflict
10
+ - id: check-added-large-files
11
+ args: [--maxkb=1024]
12
+ - repo: https://github.com/astral-sh/ruff-pre-commit
13
+ rev: v0.15.12
14
+ hooks:
15
+ - id: ruff
16
+ args: [--fix]
17
+ - id: ruff-format
18
+ - repo: https://github.com/pre-commit/mirrors-mypy
19
+ rev: v1.20.2
20
+ hooks:
21
+ - id: mypy
22
+ additional_dependencies:
23
+ - pydantic>=2.7
24
+ - typer>=0.12
25
+ - rich>=13.7
26
+ - "ruamel.yaml>=0.18"
27
+ - structlog>=24.1
28
+ - platformdirs>=4.2
29
+ - types-PyYAML>=6.0
30
+ args: [--strict]
31
+ files: ^(psc|scripts)/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,97 @@
1
+ <!--
2
+ AGENTS.md is auto-generated from CLAUDE.md by scripts/sync_agents_md.py.
3
+ Do NOT edit this file directly — edit CLAUDE.md and run
4
+ `uv run python scripts/sync_agents_md.py` to regenerate. CI fails the PR
5
+ if this file drifts from CLAUDE.md (see .github/workflows/lint.yml).
6
+ -->
7
+
8
+ # Working on panorama-super-cli (for AI agents)
9
+
10
+ Contributor guide for AI agents (and humans) modifying this repo. The
11
+ *end-user* agent guide is the bundled Skill at
12
+ `skills/panorama-super-cli/SKILL.md` — that one is about *using* `psc`; this one
13
+ is about *changing* it.
14
+
15
+ ## Architecture cheat sheet
16
+
17
+ The hard split is **backend (`psc/core`) vs frontend (`psc/cli`)**. A future web
18
+ UI would import `psc.core` directly and never touch `psc.cli`.
19
+
20
+ - `psc/core/models.py` — framework-free Pydantic domain model (`Address`,
21
+ `AddressGroup`, `Service`, `ServiceGroup`, `Tag`, `SecurityRule`, `NatRule`,
22
+ `Snapshot`, `Location`). The lingua franca; imports nothing from the rest.
23
+ - `psc/core/parse.py` — Panorama config XML → `Snapshot` (via `defusedxml`).
24
+ - `psc/core/normalize.py` — IP/value canonicalization + matching (the numeric
25
+ heart of `find` and `dedup`).
26
+ - `psc/core/refs.py` — the reference graph: where-used, unused (recursive),
27
+ dangling. Models PAN-OS name resolution (DG-local shadows shared).
28
+ - `psc/core/resolve.py` — `find` engine (IP/value/name → objects).
29
+ - `psc/core/dedup.py` — duplicate detection + safe merge planning.
30
+ - `psc/core/naming.py` — opt-in naming templates + reference-aware rename.
31
+ - `psc/core/changeset.py` — the inspectable mutation plan every write produces.
32
+ - `psc/core/setcmd.py` — render objects/changesets as PAN-OS `set` commands.
33
+ - `psc/core/apply_xml.py` — apply a `ChangeSet` to config XML (offline `--apply`).
34
+ - `psc/core/source.py` — `OfflineSource` (file) / `LiveSource` (pan-os-python).
35
+ - `psc/output/` — formatters (table/json/jsonl/yaml/csv/set) + error envelope.
36
+ - `psc/config/` — profiles + defaults (ruamel round-trip).
37
+ - `psc/cli/` — the Typer app; one thin command module per feature group.
38
+
39
+ The hard rule: **`psc/core/` imports nothing from `psc/cli/` or any UI
40
+ framework.** Engines return models; the CLI formats them. If you reach for
41
+ `typer`/`rich` inside `core/`, you're in the wrong layer.
42
+
43
+ Features are deliberately independent: `find_cmds`, `dedup_cmds`, `refs_cmds`,
44
+ `name_cmds` each map to one `core` engine and can be deleted without touching
45
+ the others. Reuse lives in `core` (models, refs, changeset, setcmd).
46
+
47
+ ## Common commands
48
+
49
+ - `just sync` — install/refresh deps.
50
+ - `just test` — run all tests.
51
+ - `just lint` — ruff + mypy --strict.
52
+ - `just fix` — auto-fix ruff issues.
53
+ - `just psc <args>` — run the local CLI.
54
+ - `just sync-agents` — regenerate AGENTS.md from CLAUDE.md.
55
+
56
+ ## Conventions
57
+
58
+ - Python 3.12+, full type annotations, `mypy --strict`.
59
+ - Pydantic v2 for all structured data.
60
+ - Conventional Commits; squash-merge; `main` is always releasable.
61
+ - TDD: write the failing test first. The safety-critical paths (merge
62
+ repointing, blockers, apply round-trip, shadow-rename refusal) MUST have tests.
63
+ - Comments explain non-obvious *why*, never *what*.
64
+
65
+ ## Safety model (do not regress)
66
+
67
+ - **Dry-run is the default.** Mutating commands print a plan and exit without
68
+ writing unless `--apply` is passed.
69
+ - **Repoint before delete.** A merge/rename rewrites every referencing group,
70
+ security rule, and NAT rule *before* removing the object.
71
+ - **`ChangeSet.blockers` is a hard gate.** A non-empty `blockers` list means the
72
+ executor refuses to apply, even with `--apply`. Add a blocker rather than
73
+ silently doing something surprising (e.g. a cross-scope reference that can't
74
+ be repointed, or a shared-rename that would shadow a device-group object).
75
+ - **Offline `--apply` never overwrites the source export** — it writes to `--out`.
76
+
77
+ ## Error contract
78
+
79
+ Expected failures raise `PscError(message, ErrorType.…)`. Exit codes are part
80
+ of the public contract (`psc/output/errors.py::EXIT_CODES`) — don't renumber
81
+ without a major bump. Machine output is never rich-wrapped (`soft_wrap=True`).
82
+
83
+ ## Branching
84
+
85
+ `main` is protected; work on short-lived `feat/…`, `fix/…`, `docs/…` branches,
86
+ open a PR, get CI (`lint`, `test`) green, squash-merge. Releases are `vX.Y.Z`
87
+ *tags* on `main` (never branches); the tag triggers `release.yml` →
88
+ PyPI (trusted publishing) + the docs site.
89
+
90
+ Keep `psc/_version.py` and `pyproject.toml` `version` in agreement; the release
91
+ workflow validates the tag against `psc/_version.py`.
92
+
93
+ ## Coordinating work on issues and PRs
94
+
95
+ When you start on an issue or PR, comment so other agents see it's claimed
96
+ (`gh issue comment <n> -b "…"`). Post terse updates on claim, milestones
97
+ (branch pushed, PR opened, CI green), blockers, and completion.
@@ -0,0 +1,51 @@
1
+ # Changelog
2
+
3
+ All notable changes to `panorama-super-cli` are documented here. The format is
4
+ based on [Keep a Changelog](https://keepachangelog.com/), and from v1.0.0 the
5
+ project will follow [Semantic Versioning](https://semver.org/). While on
6
+ `0.x`, minor versions may include breaking changes.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## v0.1.0 — 2026-06-03
11
+
12
+ First public release. Agent-friendly Panorama object management, offline or live.
13
+
14
+ ### Added
15
+
16
+ - **Offline + live sources.** Read an exported config with `--config file.xml`,
17
+ or a live Panorama via a profile (`psc profile add`). The same XML parser
18
+ feeds both paths.
19
+ - **`psc find ip`** — resolve an IP / CIDR / range / FQDN (or a `--file` list)
20
+ to the address objects that match it: exact, containing (broader), and
21
+ within (narrower), plus the address-groups that carry them.
22
+ - **`psc find object`** — locate any object by exact name across all kinds and
23
+ locations.
24
+ - **`psc dedup addresses|services`** — find objects that share an identical
25
+ value under different names (e.g. `10.0.0.10` as `h-web1` *and* `web-primary`).
26
+ - **`psc dedup merge`** — collapse one object into another, repointing every
27
+ group/security-rule/NAT reference *before* deleting it. Refuses (blocks)
28
+ value-mismatch merges and references that can't be safely repointed.
29
+ - **`psc refs used|unused|dangling`** — where-used pre-flight, recursive unused
30
+ detection (objects no rule reaches even through groups), and dangling-reference
31
+ audit.
32
+ - **`psc name lint|rename|apply`** — opt-in naming templates; report drift and
33
+ perform reference-aware renames that refuse the shared-vs-device-group shadow
34
+ collision.
35
+ - **Safety model.** Dry-run by default; `--apply` is the only path to a write.
36
+ Offline `--apply --out fixed.xml` produces a loadable, cleaned config without
37
+ touching the source export. `ChangeSet.blockers` is a hard refusal gate.
38
+ - **Output formats** `table, json, jsonl, yaml, csv, set` with a stable JSON
39
+ error envelope and typed exit codes; non-TTY stdout auto-switches to JSON.
40
+ `-o set` emits ready-to-paste PAN-OS `set` commands (member edits render as
41
+ delete-then-set, so they're idempotent rather than additive).
42
+ - **Agent Skill** bundled at `skills/panorama-super-cli/SKILL.md`.
43
+
44
+ ### Known limitations (tracked for v0.2)
45
+
46
+ - Live `--apply` is not yet implemented; use `-o set` or offline `--apply`.
47
+ - Where-used covers address-groups, security rules, and NAT (match +
48
+ translation). PBF, decryption, authentication, QoS, and other rulebases are
49
+ not yet scanned.
50
+ - Nested device-group hierarchies are flattened to the leaf; only
51
+ device-group-shadows-shared inheritance is modelled.
@@ -0,0 +1,90 @@
1
+ # Working on panorama-super-cli (for AI agents)
2
+
3
+ Contributor guide for AI agents (and humans) modifying this repo. The
4
+ *end-user* agent guide is the bundled Skill at
5
+ `skills/panorama-super-cli/SKILL.md` — that one is about *using* `psc`; this one
6
+ is about *changing* it.
7
+
8
+ ## Architecture cheat sheet
9
+
10
+ The hard split is **backend (`psc/core`) vs frontend (`psc/cli`)**. A future web
11
+ UI would import `psc.core` directly and never touch `psc.cli`.
12
+
13
+ - `psc/core/models.py` — framework-free Pydantic domain model (`Address`,
14
+ `AddressGroup`, `Service`, `ServiceGroup`, `Tag`, `SecurityRule`, `NatRule`,
15
+ `Snapshot`, `Location`). The lingua franca; imports nothing from the rest.
16
+ - `psc/core/parse.py` — Panorama config XML → `Snapshot` (via `defusedxml`).
17
+ - `psc/core/normalize.py` — IP/value canonicalization + matching (the numeric
18
+ heart of `find` and `dedup`).
19
+ - `psc/core/refs.py` — the reference graph: where-used, unused (recursive),
20
+ dangling. Models PAN-OS name resolution (DG-local shadows shared).
21
+ - `psc/core/resolve.py` — `find` engine (IP/value/name → objects).
22
+ - `psc/core/dedup.py` — duplicate detection + safe merge planning.
23
+ - `psc/core/naming.py` — opt-in naming templates + reference-aware rename.
24
+ - `psc/core/changeset.py` — the inspectable mutation plan every write produces.
25
+ - `psc/core/setcmd.py` — render objects/changesets as PAN-OS `set` commands.
26
+ - `psc/core/apply_xml.py` — apply a `ChangeSet` to config XML (offline `--apply`).
27
+ - `psc/core/source.py` — `OfflineSource` (file) / `LiveSource` (pan-os-python).
28
+ - `psc/output/` — formatters (table/json/jsonl/yaml/csv/set) + error envelope.
29
+ - `psc/config/` — profiles + defaults (ruamel round-trip).
30
+ - `psc/cli/` — the Typer app; one thin command module per feature group.
31
+
32
+ The hard rule: **`psc/core/` imports nothing from `psc/cli/` or any UI
33
+ framework.** Engines return models; the CLI formats them. If you reach for
34
+ `typer`/`rich` inside `core/`, you're in the wrong layer.
35
+
36
+ Features are deliberately independent: `find_cmds`, `dedup_cmds`, `refs_cmds`,
37
+ `name_cmds` each map to one `core` engine and can be deleted without touching
38
+ the others. Reuse lives in `core` (models, refs, changeset, setcmd).
39
+
40
+ ## Common commands
41
+
42
+ - `just sync` — install/refresh deps.
43
+ - `just test` — run all tests.
44
+ - `just lint` — ruff + mypy --strict.
45
+ - `just fix` — auto-fix ruff issues.
46
+ - `just psc <args>` — run the local CLI.
47
+ - `just sync-agents` — regenerate AGENTS.md from CLAUDE.md.
48
+
49
+ ## Conventions
50
+
51
+ - Python 3.12+, full type annotations, `mypy --strict`.
52
+ - Pydantic v2 for all structured data.
53
+ - Conventional Commits; squash-merge; `main` is always releasable.
54
+ - TDD: write the failing test first. The safety-critical paths (merge
55
+ repointing, blockers, apply round-trip, shadow-rename refusal) MUST have tests.
56
+ - Comments explain non-obvious *why*, never *what*.
57
+
58
+ ## Safety model (do not regress)
59
+
60
+ - **Dry-run is the default.** Mutating commands print a plan and exit without
61
+ writing unless `--apply` is passed.
62
+ - **Repoint before delete.** A merge/rename rewrites every referencing group,
63
+ security rule, and NAT rule *before* removing the object.
64
+ - **`ChangeSet.blockers` is a hard gate.** A non-empty `blockers` list means the
65
+ executor refuses to apply, even with `--apply`. Add a blocker rather than
66
+ silently doing something surprising (e.g. a cross-scope reference that can't
67
+ be repointed, or a shared-rename that would shadow a device-group object).
68
+ - **Offline `--apply` never overwrites the source export** — it writes to `--out`.
69
+
70
+ ## Error contract
71
+
72
+ Expected failures raise `PscError(message, ErrorType.…)`. Exit codes are part
73
+ of the public contract (`psc/output/errors.py::EXIT_CODES`) — don't renumber
74
+ without a major bump. Machine output is never rich-wrapped (`soft_wrap=True`).
75
+
76
+ ## Branching
77
+
78
+ `main` is protected; work on short-lived `feat/…`, `fix/…`, `docs/…` branches,
79
+ open a PR, get CI (`lint`, `test`) green, squash-merge. Releases are `vX.Y.Z`
80
+ *tags* on `main` (never branches); the tag triggers `release.yml` →
81
+ PyPI (trusted publishing) + the docs site.
82
+
83
+ Keep `psc/_version.py` and `pyproject.toml` `version` in agreement; the release
84
+ workflow validates the tag against `psc/_version.py`.
85
+
86
+ ## Coordinating work on issues and PRs
87
+
88
+ When you start on an issue or PR, comment so other agents see it's claimed
89
+ (`gh issue comment <n> -b "…"`). Post terse updates on claim, milestones
90
+ (branch pushed, PR opened, CI green), blockers, and completion.