netbox-super-cli 1.0.8__tar.gz → 1.2.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 (301) hide show
  1. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.github/workflows/claude.yml +7 -5
  2. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.github/workflows/release.yml +14 -11
  3. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.gitignore +1 -0
  4. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.pre-commit-config.yaml +1 -0
  5. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/CHANGELOG.md +86 -0
  6. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/PKG-INFO +24 -2
  7. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/README.md +21 -0
  8. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/architecture/http-client.md +8 -5
  9. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/architecture/schema-loading.md +5 -1
  10. netbox_super_cli-1.2.0/docs/assets/tui/detail.svg +190 -0
  11. netbox_super_cli-1.2.0/docs/assets/tui/filter.svg +199 -0
  12. netbox_super_cli-1.2.0/docs/assets/tui/list.svg +185 -0
  13. netbox_super_cli-1.2.0/docs/assets/tui/picker.svg +188 -0
  14. netbox_super_cli-1.2.0/docs/assets/tui/search.svg +192 -0
  15. netbox_super_cli-1.2.0/docs/guides/interactive-tui.md +197 -0
  16. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/guides/writes-and-safety.md +9 -1
  17. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/index.md +1 -0
  18. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/mkdocs.yml +1 -0
  19. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/_version.py +1 -1
  20. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/builder/build.py +1 -1
  21. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/app.py +10 -6
  22. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/registration.py +6 -2
  23. netbox_super_cli-1.2.0/nsc/cli/tui_commands.py +69 -0
  24. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/http/audit.py +31 -4
  25. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/headers.py +1 -1
  26. netbox_super_cli-1.2.0/nsc/schema/loader.py +143 -0
  27. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schema/models.py +19 -1
  28. netbox_super_cli-1.2.0/nsc/tui/__init__.py +30 -0
  29. netbox_super_cli-1.2.0/nsc/tui/_bindings.py +17 -0
  30. netbox_super_cli-1.2.0/nsc/tui/app.py +109 -0
  31. netbox_super_cli-1.2.0/nsc/tui/bulk.py +113 -0
  32. netbox_super_cli-1.2.0/nsc/tui/catalog.py +51 -0
  33. netbox_super_cli-1.2.0/nsc/tui/columns.py +62 -0
  34. netbox_super_cli-1.2.0/nsc/tui/errors.py +44 -0
  35. netbox_super_cli-1.2.0/nsc/tui/filters.py +130 -0
  36. netbox_super_cli-1.2.0/nsc/tui/fk.py +128 -0
  37. netbox_super_cli-1.2.0/nsc/tui/forms.py +111 -0
  38. netbox_super_cli-1.2.0/nsc/tui/keymap.py +87 -0
  39. netbox_super_cli-1.2.0/nsc/tui/nav.py +21 -0
  40. netbox_super_cli-1.2.0/nsc/tui/relations.py +65 -0
  41. netbox_super_cli-1.2.0/nsc/tui/screens/bulk_edit_form.py +238 -0
  42. netbox_super_cli-1.2.0/nsc/tui/screens/columns.py +76 -0
  43. netbox_super_cli-1.2.0/nsc/tui/screens/detail.py +371 -0
  44. netbox_super_cli-1.2.0/nsc/tui/screens/edit_form.py +248 -0
  45. netbox_super_cli-1.2.0/nsc/tui/screens/filter.py +252 -0
  46. netbox_super_cli-1.2.0/nsc/tui/screens/global_search.py +175 -0
  47. netbox_super_cli-1.2.0/nsc/tui/screens/list.py +290 -0
  48. netbox_super_cli-1.2.0/nsc/tui/screens/picker.py +102 -0
  49. netbox_super_cli-1.2.0/nsc/tui/screens/record_picker.py +116 -0
  50. netbox_super_cli-1.2.0/nsc/tui/search.py +65 -0
  51. netbox_super_cli-1.2.0/nsc/tui/selection.py +37 -0
  52. netbox_super_cli-1.2.0/nsc/tui/styles.tcss +169 -0
  53. netbox_super_cli-1.2.0/nsc/tui/view.py +46 -0
  54. netbox_super_cli-1.2.0/nsc/tui/widgets/_modal.py +40 -0
  55. netbox_super_cli-1.2.0/nsc/tui/widgets/bulk_diff.py +43 -0
  56. netbox_super_cli-1.2.0/nsc/tui/widgets/bulk_summary.py +56 -0
  57. netbox_super_cli-1.2.0/nsc/tui/widgets/confirm.py +22 -0
  58. netbox_super_cli-1.2.0/nsc/tui/widgets/diff.py +36 -0
  59. netbox_super_cli-1.2.0/nsc/tui/widgets/help.py +66 -0
  60. netbox_super_cli-1.2.0/nsc/tui/widgets/nav_tree.py +34 -0
  61. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/pyproject.toml +12 -6
  62. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/scripts/gen_docs.py +30 -2
  63. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/scripts/sync_agents_md.py +27 -0
  64. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/skills/netbox-super-cli/SKILL.md +3 -0
  65. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/builder/test_request_body_shape.py +32 -0
  66. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_commands_dump.py +27 -20
  67. netbox_super_cli-1.2.0/tests/cli/test_lazy_textual_import.py +59 -0
  68. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_registration.py +12 -1
  69. netbox_super_cli-1.2.0/tests/cli/test_tui_command.py +111 -0
  70. netbox_super_cli-1.2.0/tests/http/test_audit_permissions.py +79 -0
  71. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/http/test_audit_redaction.py +19 -0
  72. netbox_super_cli-1.2.0/tests/schema/test_loader.py +167 -0
  73. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/schema/test_models.py +24 -1
  74. netbox_super_cli-1.2.0/tests/scripts/__init__.py +0 -0
  75. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/scripts/test_gen_docs.py +33 -0
  76. netbox_super_cli-1.2.0/tests/scripts/test_sync_agents_md.py +63 -0
  77. netbox_super_cli-1.2.0/tests/skill/__init__.py +0 -0
  78. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/skill/test_bundle.py +1 -6
  79. netbox_super_cli-1.2.0/tests/tui/__init__.py +0 -0
  80. netbox_super_cli-1.2.0/tests/tui/test_app.py +137 -0
  81. netbox_super_cli-1.2.0/tests/tui/test_bindings.py +12 -0
  82. netbox_super_cli-1.2.0/tests/tui/test_bulk_apply.py +122 -0
  83. netbox_super_cli-1.2.0/tests/tui/test_bulk_diff.py +109 -0
  84. netbox_super_cli-1.2.0/tests/tui/test_bulk_diff_modal.py +242 -0
  85. netbox_super_cli-1.2.0/tests/tui/test_bulk_edit_form.py +576 -0
  86. netbox_super_cli-1.2.0/tests/tui/test_bulk_import_isolation.py +44 -0
  87. netbox_super_cli-1.2.0/tests/tui/test_bulk_summary.py +47 -0
  88. netbox_super_cli-1.2.0/tests/tui/test_catalog.py +99 -0
  89. netbox_super_cli-1.2.0/tests/tui/test_column_chooser.py +117 -0
  90. netbox_super_cli-1.2.0/tests/tui/test_columns.py +50 -0
  91. netbox_super_cli-1.2.0/tests/tui/test_detail_screen.py +543 -0
  92. netbox_super_cli-1.2.0/tests/tui/test_diff.py +99 -0
  93. netbox_super_cli-1.2.0/tests/tui/test_diff_modal.py +96 -0
  94. netbox_super_cli-1.2.0/tests/tui/test_edit_form.py +555 -0
  95. netbox_super_cli-1.2.0/tests/tui/test_errors.py +29 -0
  96. netbox_super_cli-1.2.0/tests/tui/test_filter_screen.py +373 -0
  97. netbox_super_cli-1.2.0/tests/tui/test_filters.py +139 -0
  98. netbox_super_cli-1.2.0/tests/tui/test_fk_resolve.py +130 -0
  99. netbox_super_cli-1.2.0/tests/tui/test_forms.py +90 -0
  100. netbox_super_cli-1.2.0/tests/tui/test_global_search.py +193 -0
  101. netbox_super_cli-1.2.0/tests/tui/test_help_overlay.py +97 -0
  102. netbox_super_cli-1.2.0/tests/tui/test_keymap.py +197 -0
  103. netbox_super_cli-1.2.0/tests/tui/test_list_screen.py +696 -0
  104. netbox_super_cli-1.2.0/tests/tui/test_picker.py +158 -0
  105. netbox_super_cli-1.2.0/tests/tui/test_record_picker.py +223 -0
  106. netbox_super_cli-1.2.0/tests/tui/test_relations.py +138 -0
  107. netbox_super_cli-1.2.0/tests/tui/test_search.py +69 -0
  108. netbox_super_cli-1.2.0/tests/tui/test_selection.py +99 -0
  109. netbox_super_cli-1.2.0/tests/tui/test_view.py +27 -0
  110. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/uv.lock +84 -12
  111. netbox_super_cli-1.0.8/.github/workflows/agents-md-sync.yml +0 -35
  112. netbox_super_cli-1.0.8/nsc/schema/loader.py +0 -66
  113. netbox_super_cli-1.0.8/tests/schema/test_loader.py +0 -81
  114. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.github/workflows/bench.yml +0 -0
  115. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.github/workflows/docs.yml +0 -0
  116. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.github/workflows/e2e.yml +0 -0
  117. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.github/workflows/lint.yml +0 -0
  118. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.github/workflows/test.yml +0 -0
  119. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/.python-version +0 -0
  120. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/AGENTS.md +0 -0
  121. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/CLAUDE.md +0 -0
  122. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/LICENSE +0 -0
  123. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/architecture/caching.md +0 -0
  124. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/architecture/command-generation.md +0 -0
  125. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/architecture/overview.md +0 -0
  126. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/contributing/adding-bundled-schemas.md +0 -0
  127. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/contributing/branching.md +0 -0
  128. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/contributing/development.md +0 -0
  129. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/contributing/release-process.md +0 -0
  130. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/getting-started/concepts.md +0 -0
  131. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/getting-started/first-run.md +0 -0
  132. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/getting-started/install.md +0 -0
  133. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/guides/ci-and-automation.md +0 -0
  134. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/guides/managing-profiles.md +0 -0
  135. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/guides/output-formats.md +0 -0
  136. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/guides/using-with-ai-agents.md +0 -0
  137. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/guides/working-with-plugins.md +0 -0
  138. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/reference/cli.md +0 -0
  139. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/reference/config.md +0 -0
  140. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/reference/exit-codes.md +0 -0
  141. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/docs/reference/schemas.md +0 -0
  142. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/justfile +0 -0
  143. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/__init__.py +0 -0
  144. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/__main__.py +0 -0
  145. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/aliases/__init__.py +0 -0
  146. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/aliases/resolver.py +0 -0
  147. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/auth/__init__.py +0 -0
  148. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/auth/verify.py +0 -0
  149. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/builder/__init__.py +0 -0
  150. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cache/__init__.py +0 -0
  151. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cache/store.py +0 -0
  152. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/__init__.py +0 -0
  153. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/aliases_commands.py +0 -0
  154. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/cache_commands.py +0 -0
  155. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/commands_dump.py +0 -0
  156. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/config_commands.py +0 -0
  157. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/globals.py +0 -0
  158. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/handlers.py +0 -0
  159. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/init_commands.py +0 -0
  160. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/login_commands.py +0 -0
  161. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/profiles_commands.py +0 -0
  162. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/runtime.py +0 -0
  163. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/skill_commands.py +0 -0
  164. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/writes/__init__.py +0 -0
  165. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/writes/apply.py +0 -0
  166. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/writes/bulk.py +0 -0
  167. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/writes/coercion.py +0 -0
  168. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/writes/confirmation.py +0 -0
  169. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/writes/input.py +0 -0
  170. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/cli/writes/preflight.py +0 -0
  171. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/config/__init__.py +0 -0
  172. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/config/loader.py +0 -0
  173. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/config/models.py +0 -0
  174. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/config/settings.py +0 -0
  175. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/config/writer.py +0 -0
  176. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/http/__init__.py +0 -0
  177. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/http/client.py +0 -0
  178. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/http/errors.py +0 -0
  179. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/http/retry.py +0 -0
  180. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/model/__init__.py +0 -0
  181. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/model/command_model.py +0 -0
  182. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/__init__.py +0 -0
  183. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/_console.py +0 -0
  184. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/csv_.py +0 -0
  185. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/errors.py +0 -0
  186. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/explain.py +0 -0
  187. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/flatten.py +0 -0
  188. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/json_.py +0 -0
  189. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/jsonl.py +0 -0
  190. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/render.py +0 -0
  191. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/table.py +0 -0
  192. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/output/yaml_.py +0 -0
  193. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schema/__init__.py +0 -0
  194. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schema/hashing.py +0 -0
  195. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schema/source.py +0 -0
  196. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schemas/__init__.py +0 -0
  197. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schemas/bundled/__init__.py +0 -0
  198. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schemas/bundled/manifest.yaml +0 -0
  199. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schemas/bundled/netbox-4.5.10.json.gz +0 -0
  200. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/schemas/bundled/netbox-4.6.0.json.gz +0 -0
  201. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/nsc/skill/__init__.py +0 -0
  202. {netbox_super_cli-1.0.8/tests → netbox_super_cli-1.2.0/nsc/tui/screens}/__init__.py +0 -0
  203. {netbox_super_cli-1.0.8/tests/aliases → netbox_super_cli-1.2.0/nsc/tui/widgets}/__init__.py +0 -0
  204. {netbox_super_cli-1.0.8/tests/auth → netbox_super_cli-1.2.0/tests}/__init__.py +0 -0
  205. {netbox_super_cli-1.0.8/tests/benchmarks → netbox_super_cli-1.2.0/tests/aliases}/__init__.py +0 -0
  206. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/aliases/test_resolver.py +0 -0
  207. {netbox_super_cli-1.0.8/tests/builder → netbox_super_cli-1.2.0/tests/auth}/__init__.py +0 -0
  208. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/auth/test_verify.py +0 -0
  209. {netbox_super_cli-1.0.8/tests/cache → netbox_super_cli-1.2.0/tests/benchmarks}/__init__.py +0 -0
  210. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/benchmarks/test_startup.py +0 -0
  211. {netbox_super_cli-1.0.8/tests/cli → netbox_super_cli-1.2.0/tests/builder}/__init__.py +0 -0
  212. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/builder/test_build.py +0 -0
  213. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/builder/test_default_columns.py +0 -0
  214. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/builder/test_redaction_marking.py +0 -0
  215. {netbox_super_cli-1.0.8/tests/cli/writes → netbox_super_cli-1.2.0/tests/cache}/__init__.py +0 -0
  216. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cache/test_prune.py +0 -0
  217. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cache/test_store.py +0 -0
  218. {netbox_super_cli-1.0.8/tests/config → netbox_super_cli-1.2.0/tests/cli}/__init__.py +0 -0
  219. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_aliases_commands.py +0 -0
  220. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_app_smoke.py +0 -0
  221. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_cache_commands.py +0 -0
  222. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_completion_smoke.py +0 -0
  223. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_config_commands.py +0 -0
  224. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_explain.py +0 -0
  225. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_globals_color.py +0 -0
  226. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_handlers.py +0 -0
  227. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_init_commands.py +0 -0
  228. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_login_commands.py +0 -0
  229. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_meta_subcommands_under_broken_config.py +0 -0
  230. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_ndjson_input.py +0 -0
  231. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_profiles_commands.py +0 -0
  232. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_respx_integration.py +0 -0
  233. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_runtime.py +0 -0
  234. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_runtime_color.py +0 -0
  235. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_skill_commands.py +0 -0
  236. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_stdin_sniffer.py +0 -0
  237. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/test_writes_respx.py +0 -0
  238. {netbox_super_cli-1.0.8/tests/http → netbox_super_cli-1.2.0/tests/cli/writes}/__init__.py +0 -0
  239. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/writes/test_apply.py +0 -0
  240. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/writes/test_bulk.py +0 -0
  241. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/writes/test_confirmation.py +0 -0
  242. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/writes/test_handler_helpers.py +0 -0
  243. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/writes/test_handlers_audit.py +0 -0
  244. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/writes/test_input.py +0 -0
  245. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/cli/writes/test_preflight.py +0 -0
  246. {netbox_super_cli-1.0.8/tests/model → netbox_super_cli-1.2.0/tests/config}/__init__.py +0 -0
  247. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/config/test_loader.py +0 -0
  248. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/config/test_models.py +0 -0
  249. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/config/test_writer_atomicity.py +0 -0
  250. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/config/test_writer_dotted_paths.py +0 -0
  251. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/config/test_writer_roundtrip.py +0 -0
  252. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/conftest.py +0 -0
  253. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/README.md +0 -0
  254. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/__init__.py +0 -0
  255. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/conftest.py +0 -0
  256. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/docker-compose.yml +0 -0
  257. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/test_audit_redaction.py +0 -0
  258. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/test_auth_error_envelope.py +0 -0
  259. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/test_bulk_create_with_loop_fallback.py +0 -0
  260. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/test_full_cycle.py +0 -0
  261. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/test_login.py +0 -0
  262. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/test_ndjson.py +0 -0
  263. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/test_preflight_blocks_apply.py +0 -0
  264. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/test_validation_error_envelope_from_netbox.py +0 -0
  265. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/e2e/wait_for_netbox.sh +0 -0
  266. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/fixtures/profiles/single_profile.yaml +0 -0
  267. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/fixtures/responses/auth_401.json +0 -0
  268. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/fixtures/responses/circuits_providers_list.json +0 -0
  269. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/fixtures/responses/dcim_devices_get.json +0 -0
  270. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/fixtures/responses/dcim_devices_list_p1.json +0 -0
  271. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/fixtures/responses/dcim_devices_list_p2.json +0 -0
  272. {netbox_super_cli-1.0.8/tests/output → netbox_super_cli-1.2.0/tests/http}/__init__.py +0 -0
  273. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/http/test_audit.py +0 -0
  274. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/http/test_client.py +0 -0
  275. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/http/test_client_redaction_threading.py +0 -0
  276. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/http/test_errors.py +0 -0
  277. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/http/test_retry.py +0 -0
  278. {netbox_super_cli-1.0.8/tests/schema → netbox_super_cli-1.2.0/tests/model}/__init__.py +0 -0
  279. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/model/test_command_model.py +0 -0
  280. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/model/test_request_body_shape.py +0 -0
  281. {netbox_super_cli-1.0.8/tests/scripts → netbox_super_cli-1.2.0/tests/output}/__init__.py +0 -0
  282. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_console.py +0 -0
  283. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_csv.py +0 -0
  284. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_errors.py +0 -0
  285. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_errors_aliases.py +0 -0
  286. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_errors_color.py +0 -0
  287. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_explain.py +0 -0
  288. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_explain_color.py +0 -0
  289. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_flatten.py +0 -0
  290. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_input_error_envelope.py +0 -0
  291. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_json.py +0 -0
  292. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_jsonl.py +0 -0
  293. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_render_dispatch.py +0 -0
  294. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_table.py +0 -0
  295. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_table_color.py +0 -0
  296. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/output/test_yaml.py +0 -0
  297. {netbox_super_cli-1.0.8/tests/skill → netbox_super_cli-1.2.0/tests/schema}/__init__.py +0 -0
  298. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/schema/test_hashing.py +0 -0
  299. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/schema/test_source.py +0 -0
  300. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/schema/test_source_ttl.py +0 -0
  301. {netbox_super_cli-1.0.8 → netbox_super_cli-1.2.0}/tests/test_packaging.py +0 -0
@@ -12,11 +12,14 @@ on:
12
12
 
13
13
  jobs:
14
14
  claude:
15
+ # Only trusted authors (repo owner/members/collaborators) can invoke the
16
+ # secret-bearing agent; the @claude substring alone is satisfiable by any
17
+ # commenter on a public repo and would bill drive-by API usage (audit L3).
15
18
  if: |
16
- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
- (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
- (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
- (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
19
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) ||
20
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)) ||
21
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.review.author_association)) ||
22
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association))
20
23
  runs-on: ubuntu-latest
21
24
  permissions:
22
25
  contents: read
@@ -47,4 +50,3 @@ jobs:
47
50
  # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
48
51
  # or https://code.claude.com/docs/en/cli-reference for available options
49
52
  # claude_args: '--allowed-tools Bash(gh pr *)'
50
-
@@ -76,6 +76,9 @@ jobs:
76
76
  run: |
77
77
  set -euo pipefail
78
78
  tag_version="${GITHUB_REF_NAME#v}"
79
+ # Derive the version once and export it for the later steps (wheel
80
+ # filename, changelog, GitHub Release) instead of re-deriving it.
81
+ echo "TAG_VERSION=${tag_version}" >> "$GITHUB_ENV"
79
82
  pkg_version="$(uv run python -c 'from nsc._version import __version__; print(__version__)')"
80
83
  if [ "$tag_version" != "$pkg_version" ]; then
81
84
  echo "Tag $GITHUB_REF_NAME -> version $tag_version does not match nsc/_version.py $pkg_version" >&2
@@ -87,19 +90,21 @@ jobs:
87
90
  run: |
88
91
  rm -rf dist
89
92
  uv build --out-dir dist
93
+ # Breadcrumb: leave the artifact listing in the success log so a
94
+ # future post-mortem can confirm exactly what was built (the
95
+ # verify-filename step below already lists dist/ on failure).
90
96
  ls -la dist/
91
97
 
92
98
  - name: Verify wheel filename matches tag
93
99
  run: |
94
100
  set -euo pipefail
95
- tag_version="${GITHUB_REF_NAME#v}"
96
- if ! ls "dist/netbox_super_cli-${tag_version}-py3-none-any.whl" >/dev/null 2>&1; then
97
- echo "Wheel for ${tag_version} not found in dist/" >&2
101
+ if ! ls "dist/netbox_super_cli-${TAG_VERSION}-py3-none-any.whl" >/dev/null 2>&1; then
102
+ echo "Wheel for ${TAG_VERSION} not found in dist/" >&2
98
103
  ls dist/ >&2
99
104
  exit 1
100
105
  fi
101
- if ! ls "dist/netbox_super_cli-${tag_version}.tar.gz" >/dev/null 2>&1; then
102
- echo "Sdist for ${tag_version} not found in dist/" >&2
106
+ if ! ls "dist/netbox_super_cli-${TAG_VERSION}.tar.gz" >/dev/null 2>&1; then
107
+ echo "Sdist for ${TAG_VERSION} not found in dist/" >&2
103
108
  ls dist/ >&2
104
109
  exit 1
105
110
  fi
@@ -130,18 +135,17 @@ jobs:
130
135
  id: notes
131
136
  run: |
132
137
  set -euo pipefail
133
- tag_version="${GITHUB_REF_NAME#v}"
134
138
  # Match `## v<ver><space>...` and capture until the next `## `
135
139
  # heading. The trailing space after <ver> in the regex is what
136
140
  # disambiguates `v1.0` from `v1.0.1`; what follows (typically an
137
141
  # em-dash, date, sub-phase name) is intentionally unconstrained.
138
- awk -v ver="$tag_version" '
142
+ awk -v ver="$TAG_VERSION" '
139
143
  $0 ~ "^## v" ver " " { capture=1; print; next }
140
144
  capture && /^## / { exit }
141
145
  capture { print }
142
146
  ' CHANGELOG.md > /tmp/release-notes.md
143
147
  if [ ! -s /tmp/release-notes.md ]; then
144
- echo "Release notes for v${tag_version} not found in CHANGELOG.md" >&2
148
+ echo "Release notes for v${TAG_VERSION} not found in CHANGELOG.md" >&2
145
149
  exit 1
146
150
  fi
147
151
  echo "Extracted $(wc -l < /tmp/release-notes.md) lines of release notes."
@@ -151,7 +155,6 @@ jobs:
151
155
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
152
156
  run: |
153
157
  set -euo pipefail
154
- tag_version="${GITHUB_REF_NAME#v}"
155
158
  # Use exact filenames (the verify-wheel-filename step proved both
156
159
  # exist) rather than glob-expanding `dist/*.whl dist/*.tar.gz` —
157
160
  # that glob would silently attach any stray artifact a future
@@ -159,5 +162,5 @@ jobs:
159
162
  gh release create "$GITHUB_REF_NAME" \
160
163
  --title "$GITHUB_REF_NAME" \
161
164
  --notes-file /tmp/release-notes.md \
162
- "dist/netbox_super_cli-${tag_version}-py3-none-any.whl" \
163
- "dist/netbox_super_cli-${tag_version}.tar.gz"
165
+ "dist/netbox_super_cli-${TAG_VERSION}-py3-none-any.whl" \
166
+ "dist/netbox_super_cli-${TAG_VERSION}.tar.gz"
@@ -38,3 +38,4 @@ site/
38
38
 
39
39
  # git worktrees
40
40
  .worktrees/
41
+ .claude/
@@ -30,6 +30,7 @@ repos:
30
30
  - "ruamel.yaml>=0.18"
31
31
  - structlog>=24.1
32
32
  - platformdirs>=4.2
33
+ - textual>=0.50
33
34
  - types-PyYAML>=6.0
34
35
  args: [--strict]
35
36
  files: ^(nsc|scripts)/
@@ -2,6 +2,92 @@
2
2
 
3
3
  All notable changes to netbox-super-cli are tracked here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loosely. From v1.0.0 onward, releases follow [Semantic Versioning](https://semver.org/) and the version in `pyproject.toml` matches the git tag. Pre-1.0 milestones (Phase 1-5) were pinned by tag while `pyproject.toml` stayed at `0.0.1`.
4
4
 
5
+ ## v1.2.0 — 2026-06-24
6
+
7
+ Minor release. Adds an **interactive terminal UI** — a keyboard-driven, fully
8
+ schema-generic front end over the same command model the CLI uses.
9
+
10
+ ### Added
11
+
12
+ - **`nsc tui` — interactive TUI** (aliases: `nsc interactive`, `nsc i`). A
13
+ Textual app to browse, filter, edit, bulk-edit and search NetBox from the
14
+ keyboard, generated entirely from the OpenAPI-derived command model (works for
15
+ every resource and plugin, no per-model wiring). Textual is imported lazily so
16
+ the regular CLI's startup path is unchanged. See the
17
+ [Interactive TUI guide](https://thomaschristory.github.io/netbox-super-cli/guides/interactive-tui/).
18
+ - **Resource picker** — a collapsible tree of resources grouped by tag, with
19
+ fuzzy filtering (matching groups auto-expand) and expand/collapse-all.
20
+ - **List view** — paginated tables with vim/arrow navigation, multi-select,
21
+ refresh, and an async loading wheel that keeps the input responsive.
22
+ - **Filter builder** (`/`) — a web-UI-like form of curated common fields
23
+ (enum dropdowns, conventional field names), a search box over every query
24
+ parameter, foreign-key record pickers (applied as `…_id`), a raw
25
+ `key=value` line, and removable active-filter chips.
26
+ - **Inline record editing** — the detail view is the edit surface: edit a
27
+ field in place, stage changes, and save them all in one `PATCH` after a
28
+ confirmation diff. Create and delete are supported; foreign keys open a
29
+ searchable picker.
30
+ - **Relationship drill-down** — schema-derived relationship tabs (a device's
31
+ interfaces, modules, cables, …) open a pre-filtered list.
32
+ - **Bulk edit** — set chosen fields across a multi-selection (prepopulated
33
+ from the records' shared value), preview a per-record diff, and apply with a
34
+ progress bar and per-record partial-failure reporting.
35
+ - **Column chooser** (`f`) — toggle and reorder visible columns; the choice
36
+ persists per resource in `~/.nsc/config.yaml` (shared with the CLI's
37
+ `--columns`).
38
+ - **Global search** (`Ctrl`+`F`) — search across a curated set of common
39
+ object types at once, with results grouped by type and a live spinner.
40
+ - **Help overlay** (`?`) — a per-context keymap generated from the live
41
+ bindings, so it can never drift.
42
+ - New dependency: `textual` (loaded only by the TUI).
43
+
44
+ ### Documentation
45
+
46
+ - New [Interactive TUI guide](https://thomaschristory.github.io/netbox-super-cli/guides/interactive-tui/)
47
+ with screenshots, linked from the Home page and the README.
48
+
49
+ ## v1.1.0 — 2026-06-18
50
+
51
+ Minor release. Hardens the audit log and schema loader against the security
52
+ audit (#88), adopts typer 0.26's vendored click (#82), and lands a batch of
53
+ CI/workflow cleanups.
54
+
55
+ ### Security
56
+
57
+ - **Audit logs are owner-only** (#88, M4/L1). `~/.nsc/logs/audit.jsonl` is now
58
+ created `0600` and its directory `0700` (a pre-existing world-readable log is
59
+ clamped back on the next append; the rotated `.1` inherits `0600`), mirroring
60
+ the `0600` treatment of `config.yaml`. Response headers are now redacted like
61
+ request headers, and `set-cookie` joins the sensitive-header set, so NetBox
62
+ session cookies no longer land in the log in cleartext.
63
+ - **Bounded schema fetch and decompression** (#88, L2). The schema loader caps
64
+ both the fetched HTTP body and any gzip-decompressed output at 64 MB. It reads
65
+ raw wire bytes under that cap and requests `Accept-Encoding: identity` so a
66
+ `Content-Encoding` bomb can't be inflated to gigabytes before the check, and
67
+ it rejects truncated or multi-member gzip streams.
68
+ - **Dependency bumps** (#88, M1/M2): `idna` 3.13 → 3.18 (CVE-2026-45409) and the
69
+ docs-build-only `pymdown-extensions` 10.21.2 → 10.21.3 (CVE-2026-46338).
70
+ - **`@claude` workflow author gating** (#88, L3): the secret-bearing Claude Code
71
+ workflow now requires an `OWNER`/`MEMBER`/`COLLABORATOR` author association in
72
+ addition to the `@claude` mention, so drive-by comments can't invoke it.
73
+
74
+ ### Changed
75
+
76
+ - **typer 0.26+ vendored click** (#82). typer 0.26 vendored click as
77
+ `typer._click`; nsc's `TyperGroup` overrides are now decoupled from a specific
78
+ click (`Any`-typed) and the choice param type comes from typer's vendored
79
+ `TyperChoice`. The `typer<0.26` stopgap cap from v1.0.7 is lifted and the
80
+ dependency is pinned `typer>=0.26,<0.27`. Runtime behavior (dynamic command
81
+ tree, choice validation, error envelopes) is unchanged.
82
+
83
+ ### Internal
84
+
85
+ - CI/workflow cleanups: drop the duplicate `agents-md-sync` workflow now that
86
+ `lint`'s `agents-md-fresh` job covers it (#19), DRY the release `TAG_VERSION`
87
+ derivation (#14), assert (rather than skip) the wheel `SKILL.md` layout (#20),
88
+ print a `difflib` unified diff from the drift-check scripts on `--check`
89
+ failure (#11), and doc-comment the `ls -la dist/` release breadcrumb (#15).
90
+
5
91
  ## v1.0.8 — 2026-06-10
6
92
 
7
93
  Patch release. Table/CSV columns for choice fields now show their human label
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-super-cli
3
- Version: 1.0.8
3
+ Version: 1.2.0
4
4
  Summary: Dynamic NetBox CLI generated from the live OpenAPI schema.
5
5
  Project-URL: Homepage, https://github.com/thomaschristory/netbox-super-cli
6
6
  Project-URL: Issues, https://github.com/thomaschristory/netbox-super-cli/issues
@@ -23,7 +23,8 @@ Requires-Dist: pydantic>=2.7
23
23
  Requires-Dist: rich>=13.7
24
24
  Requires-Dist: ruamel-yaml>=0.18
25
25
  Requires-Dist: structlog>=24.1
26
- Requires-Dist: typer<0.26,>=0.12
26
+ Requires-Dist: textual>=0.50
27
+ Requires-Dist: typer<0.27,>=0.26
27
28
  Description-Content-Type: text/markdown
28
29
 
29
30
  # netbox-super-cli (`nsc`)
@@ -38,6 +39,7 @@ A Python CLI for [NetBox](https://netbox.dev/) that builds its command tree dyna
38
39
  - **Multi-instance.** Named profiles per NetBox instance, plus env-var overrides.
39
40
  - **Safe by default.** POST/PATCH/PUT/DELETE preview as dry-runs unless you pass `--apply`.
40
41
  - **Agent-friendly.** Deterministic command shape, machine-readable JSON output, stable error envelope with documented exit codes.
42
+ - **Interactive TUI.** `nsc tui` opens a keyboard-driven terminal UI — see below.
41
43
 
42
44
  ## Install
43
45
 
@@ -55,6 +57,26 @@ uv sync
55
57
  uv run nsc --version
56
58
  ```
57
59
 
60
+ ## Interactive TUI
61
+
62
+ `nsc tui` (aliases `nsc interactive`, `nsc i`) opens a full-screen,
63
+ keyboard-driven UI over your NetBox — browse, filter, edit, bulk-edit and search
64
+ across object types without writing a command. Like the CLI it is generated from
65
+ the live schema, so it works for every resource and plugin.
66
+
67
+ ![The nsc interactive TUI](docs/assets/tui/list.svg)
68
+
69
+ ```sh
70
+ nsc tui # land on the resource picker
71
+ nsc tui devices # jump straight into the devices list
72
+ ```
73
+
74
+ Highlights: a collapsible resource picker, a web-UI-style filter builder (`/`),
75
+ inline record editing with a confirm-diff save, schema-derived relationship
76
+ drill-down, bulk edit with a per-record preview, a persisted column chooser
77
+ (`f`), and global search (`Ctrl`+`F`). Full walkthrough in the
78
+ [Interactive TUI guide](https://thomaschristory.github.io/netbox-super-cli/guides/interactive-tui/).
79
+
58
80
  ## Reading
59
81
 
60
82
  ```sh
@@ -10,6 +10,7 @@ A Python CLI for [NetBox](https://netbox.dev/) that builds its command tree dyna
10
10
  - **Multi-instance.** Named profiles per NetBox instance, plus env-var overrides.
11
11
  - **Safe by default.** POST/PATCH/PUT/DELETE preview as dry-runs unless you pass `--apply`.
12
12
  - **Agent-friendly.** Deterministic command shape, machine-readable JSON output, stable error envelope with documented exit codes.
13
+ - **Interactive TUI.** `nsc tui` opens a keyboard-driven terminal UI — see below.
13
14
 
14
15
  ## Install
15
16
 
@@ -27,6 +28,26 @@ uv sync
27
28
  uv run nsc --version
28
29
  ```
29
30
 
31
+ ## Interactive TUI
32
+
33
+ `nsc tui` (aliases `nsc interactive`, `nsc i`) opens a full-screen,
34
+ keyboard-driven UI over your NetBox — browse, filter, edit, bulk-edit and search
35
+ across object types without writing a command. Like the CLI it is generated from
36
+ the live schema, so it works for every resource and plugin.
37
+
38
+ ![The nsc interactive TUI](docs/assets/tui/list.svg)
39
+
40
+ ```sh
41
+ nsc tui # land on the resource picker
42
+ nsc tui devices # jump straight into the devices list
43
+ ```
44
+
45
+ Highlights: a collapsible resource picker, a web-UI-style filter builder (`/`),
46
+ inline record editing with a confirm-diff save, schema-derived relationship
47
+ drill-down, bulk edit with a per-record preview, a persisted column chooser
48
+ (`f`), and global search (`Ctrl`+`F`). Full walkthrough in the
49
+ [Interactive TUI guide](https://thomaschristory.github.io/netbox-super-cli/guides/interactive-tui/).
50
+
30
51
  ## Reading
31
52
 
32
53
  ```sh
@@ -42,19 +42,22 @@ Each line of `audit.jsonl` is a JSON object with:
42
42
 
43
43
  - `schema_version`, `timestamp` (UTC ISO8601, `…Z`)
44
44
  - `operation_id`, `method`, `url`
45
- - `request.headers` (sensitive headers, incl. `Authorization`, rewritten
46
- to `"<redacted>"` — the key stays, the value is masked)
45
+ - `request.headers` (sensitive headers `Authorization`, `Cookie`,
46
+ `Set-Cookie`, `X-API-Key`, `Proxy-Authorization`rewritten to
47
+ `"<redacted>"`; the key stays, the value is masked)
47
48
  - `request.query`, `request.body` (sensitive `sensitive_paths` fields
48
49
  rewritten to `"<redacted>"`); bodies over 256 KB collapse to
49
50
  `{"_truncated": true, "_size_bytes": N}` with `request.body_truncated`
50
- - `response.status_code`, `response.headers`, `response.body` (same
51
+ - `response.status_code`, `response.headers` (redacted with the same
52
+ sensitive-header set as the request side), `response.body` (same
51
53
  256 KB truncation via `response.body_truncated`); `response` is `null`
52
54
  on a transport failure
53
55
  - `duration_ms`, `attempt_n`, `final_attempt`, `error_kind`
54
56
  - `dry_run`, `preflight_blocked`, `record_indices`, `applied`, `explain`
55
57
 
56
- The audit file is append-only and rotates to `audit.jsonl.1` at 10 MB;
57
- failed writes do NOT unredact. See
58
+ The audit file is append-only and rotates to `audit.jsonl.1` at 10 MB; it is
59
+ created owner-only (`0600`) inside a `0700` logs directory, and failed writes do
60
+ NOT unredact. See
58
61
  [Writes and safety](../guides/writes-and-safety.md) for the full redaction
59
62
  contract.
60
63
 
@@ -17,7 +17,11 @@ and an optional `--refresh-schema` override:
17
17
  3. Live fetch from the active profile's `schema_url` (explicit) or
18
18
  `{profile.url}/api/schema/?format=json`. The fetched body is hashed;
19
19
  if a cache file already exists at that hash it's loaded directly,
20
- otherwise the command-model is rebuilt and saved.
20
+ otherwise the command-model is rebuilt and saved. The fetch is bounded:
21
+ the loader streams raw wire bytes under a 64 MB cap, requests
22
+ `Accept-Encoding: identity`, and any `.gz`/`Content-Encoding` body is
23
+ decompressed incrementally to the same cap — so an oversized or
24
+ decompression-bomb schema is rejected rather than exhausting memory.
21
25
  4. On fetch failure: the newest valid cached entry for the profile
22
26
  (warns once on stderr), sorted by mtime.
23
27
  5. Bundled snapshot — the **last (newest) entry** in
@@ -0,0 +1,190 @@
1
+ <svg class="rich-terminal" viewBox="0 0 1385 830.8" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- Generated with Rich https://www.textualize.io -->
3
+ <style>
4
+
5
+ @font-face {
6
+ font-family: "Fira Code";
7
+ src: local("FiraCode-Regular"),
8
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
9
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
10
+ font-style: normal;
11
+ font-weight: 400;
12
+ }
13
+ @font-face {
14
+ font-family: "Fira Code";
15
+ src: local("FiraCode-Bold"),
16
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
17
+ url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
18
+ font-style: bold;
19
+ font-weight: 700;
20
+ }
21
+
22
+ .terminal-3309028997-matrix {
23
+ font-family: Fira Code, monospace;
24
+ font-size: 20px;
25
+ line-height: 24.4px;
26
+ font-variant-east-asian: full-width;
27
+ }
28
+
29
+ .terminal-3309028997-title {
30
+ font-size: 18px;
31
+ font-weight: bold;
32
+ font-family: arial;
33
+ }
34
+
35
+ .terminal-3309028997-r1 { fill: #c5c8c6 }
36
+ .terminal-3309028997-r2 { fill: #e0e0e0 }
37
+ .terminal-3309028997-r3 { fill: #e0e0e0;font-weight: bold }
38
+ .terminal-3309028997-r4 { fill: #272727 }
39
+ .terminal-3309028997-r5 { fill: #ddedf9;font-weight: bold }
40
+ .terminal-3309028997-r6 { fill: #000000 }
41
+ .terminal-3309028997-r7 { fill: #797979 }
42
+ .terminal-3309028997-r8 { fill: #262626 }
43
+ .terminal-3309028997-r9 { fill: #0178d4 }
44
+ .terminal-3309028997-r10 { fill: #ffa62b;font-weight: bold }
45
+ </style>
46
+
47
+ <defs>
48
+ <clipPath id="terminal-3309028997-clip-terminal">
49
+ <rect x="0" y="0" width="1365.3999999999999" height="779.8" />
50
+ </clipPath>
51
+ <clipPath id="terminal-3309028997-line-0">
52
+ <rect x="0" y="1.5" width="1366.4" height="24.65"/>
53
+ </clipPath>
54
+ <clipPath id="terminal-3309028997-line-1">
55
+ <rect x="0" y="25.9" width="1366.4" height="24.65"/>
56
+ </clipPath>
57
+ <clipPath id="terminal-3309028997-line-2">
58
+ <rect x="0" y="50.3" width="1366.4" height="24.65"/>
59
+ </clipPath>
60
+ <clipPath id="terminal-3309028997-line-3">
61
+ <rect x="0" y="74.7" width="1366.4" height="24.65"/>
62
+ </clipPath>
63
+ <clipPath id="terminal-3309028997-line-4">
64
+ <rect x="0" y="99.1" width="1366.4" height="24.65"/>
65
+ </clipPath>
66
+ <clipPath id="terminal-3309028997-line-5">
67
+ <rect x="0" y="123.5" width="1366.4" height="24.65"/>
68
+ </clipPath>
69
+ <clipPath id="terminal-3309028997-line-6">
70
+ <rect x="0" y="147.9" width="1366.4" height="24.65"/>
71
+ </clipPath>
72
+ <clipPath id="terminal-3309028997-line-7">
73
+ <rect x="0" y="172.3" width="1366.4" height="24.65"/>
74
+ </clipPath>
75
+ <clipPath id="terminal-3309028997-line-8">
76
+ <rect x="0" y="196.7" width="1366.4" height="24.65"/>
77
+ </clipPath>
78
+ <clipPath id="terminal-3309028997-line-9">
79
+ <rect x="0" y="221.1" width="1366.4" height="24.65"/>
80
+ </clipPath>
81
+ <clipPath id="terminal-3309028997-line-10">
82
+ <rect x="0" y="245.5" width="1366.4" height="24.65"/>
83
+ </clipPath>
84
+ <clipPath id="terminal-3309028997-line-11">
85
+ <rect x="0" y="269.9" width="1366.4" height="24.65"/>
86
+ </clipPath>
87
+ <clipPath id="terminal-3309028997-line-12">
88
+ <rect x="0" y="294.3" width="1366.4" height="24.65"/>
89
+ </clipPath>
90
+ <clipPath id="terminal-3309028997-line-13">
91
+ <rect x="0" y="318.7" width="1366.4" height="24.65"/>
92
+ </clipPath>
93
+ <clipPath id="terminal-3309028997-line-14">
94
+ <rect x="0" y="343.1" width="1366.4" height="24.65"/>
95
+ </clipPath>
96
+ <clipPath id="terminal-3309028997-line-15">
97
+ <rect x="0" y="367.5" width="1366.4" height="24.65"/>
98
+ </clipPath>
99
+ <clipPath id="terminal-3309028997-line-16">
100
+ <rect x="0" y="391.9" width="1366.4" height="24.65"/>
101
+ </clipPath>
102
+ <clipPath id="terminal-3309028997-line-17">
103
+ <rect x="0" y="416.3" width="1366.4" height="24.65"/>
104
+ </clipPath>
105
+ <clipPath id="terminal-3309028997-line-18">
106
+ <rect x="0" y="440.7" width="1366.4" height="24.65"/>
107
+ </clipPath>
108
+ <clipPath id="terminal-3309028997-line-19">
109
+ <rect x="0" y="465.1" width="1366.4" height="24.65"/>
110
+ </clipPath>
111
+ <clipPath id="terminal-3309028997-line-20">
112
+ <rect x="0" y="489.5" width="1366.4" height="24.65"/>
113
+ </clipPath>
114
+ <clipPath id="terminal-3309028997-line-21">
115
+ <rect x="0" y="513.9" width="1366.4" height="24.65"/>
116
+ </clipPath>
117
+ <clipPath id="terminal-3309028997-line-22">
118
+ <rect x="0" y="538.3" width="1366.4" height="24.65"/>
119
+ </clipPath>
120
+ <clipPath id="terminal-3309028997-line-23">
121
+ <rect x="0" y="562.7" width="1366.4" height="24.65"/>
122
+ </clipPath>
123
+ <clipPath id="terminal-3309028997-line-24">
124
+ <rect x="0" y="587.1" width="1366.4" height="24.65"/>
125
+ </clipPath>
126
+ <clipPath id="terminal-3309028997-line-25">
127
+ <rect x="0" y="611.5" width="1366.4" height="24.65"/>
128
+ </clipPath>
129
+ <clipPath id="terminal-3309028997-line-26">
130
+ <rect x="0" y="635.9" width="1366.4" height="24.65"/>
131
+ </clipPath>
132
+ <clipPath id="terminal-3309028997-line-27">
133
+ <rect x="0" y="660.3" width="1366.4" height="24.65"/>
134
+ </clipPath>
135
+ <clipPath id="terminal-3309028997-line-28">
136
+ <rect x="0" y="684.7" width="1366.4" height="24.65"/>
137
+ </clipPath>
138
+ <clipPath id="terminal-3309028997-line-29">
139
+ <rect x="0" y="709.1" width="1366.4" height="24.65"/>
140
+ </clipPath>
141
+ <clipPath id="terminal-3309028997-line-30">
142
+ <rect x="0" y="733.5" width="1366.4" height="24.65"/>
143
+ </clipPath>
144
+ </defs>
145
+
146
+ <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1383" height="828.8" rx="8"/><text class="terminal-3309028997-title" fill="#c5c8c6" text-anchor="middle" x="691" y="27">nsc&#160;tui</text>
147
+ <g transform="translate(26,22)">
148
+ <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
149
+ <circle cx="22" cy="0" r="7" fill="#febc2e"/>
150
+ <circle cx="44" cy="0" r="7" fill="#28c840"/>
151
+ </g>
152
+
153
+ <g transform="translate(9, 41)" clip-path="url(#terminal-3309028997-clip-terminal)">
154
+ <rect fill="#242f38" x="0" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="12.2" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="24.4" y="1.5" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="85.4" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="97.6" y="1.5" width="500.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="597.8" y="1.5" width="146.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="744.2" y="1.5" width="500.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1244.4" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1256.6" y="1.5" width="0" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1256.6" y="1.5" width="97.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1354.2" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#2d3740" x="0" y="25.9" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#2d3740" x="244" y="25.9" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#2d3740" x="475.8" y="25.9" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="25.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="0" y="50.3" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="244" y="50.3" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#0178d4" x="475.8" y="50.3" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="50.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="74.7" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="74.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="74.7" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="74.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="99.1" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="99.1" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="99.1" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="99.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="123.5" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="123.5" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="123.5" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="123.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="147.9" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="147.9" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="147.9" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="147.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="172.3" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="172.3" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="172.3" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="172.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="196.7" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="196.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="196.7" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="196.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="221.1" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="221.1" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="221.1" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="221.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="245.5" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="245.5" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="245.5" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="245.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="269.9" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="269.9" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="269.9" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="269.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="294.3" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="294.3" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="294.3" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="294.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="318.7" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="318.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="318.7" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="318.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="343.1" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="343.1" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="343.1" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="343.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="367.5" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="367.5" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="367.5" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="367.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="391.9" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="391.9" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="391.9" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="391.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="416.3" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="416.3" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="416.3" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="416.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="440.7" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="440.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="440.7" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="440.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="465.1" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="465.1" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="465.1" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="465.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="489.5" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="489.5" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="489.5" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="489.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="513.9" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="513.9" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="513.9" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="513.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="538.3" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="538.3" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="538.3" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="538.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="562.7" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="562.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="562.7" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="562.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="587.1" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="587.1" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="587.1" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#003054" x="1342" y="587.1" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="611.5" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="611.5" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="611.5" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#000000" x="1342" y="611.5" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="635.9" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="635.9" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="635.9" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#000000" x="1342" y="635.9" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="660.3" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="660.3" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="660.3" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#000000" x="1342" y="660.3" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="0" y="684.7" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="244" y="684.7" width="231.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#272727" x="475.8" y="684.7" width="866.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#000000" x="1342" y="684.7" width="24.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="12.2" y="709.1" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="85.4" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="97.6" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="109.8" y="709.1" width="158.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="268.4" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="280.6" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="292.8" y="709.1" width="244" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="536.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="549" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="561.2" y="709.1" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="695.4" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="707.6" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="719.8" y="709.1" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="854" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="866.2" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="878.4" y="709.1" width="122" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="1000.4" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="1012.6" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="1024.8" y="709.1" width="183" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="1207.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="1220" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="1232.2" y="709.1" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="0" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="12.2" y="733.5" width="73.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="85.4" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#121212" x="97.6" y="733.5" width="1268.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="0" y="757.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="61" y="757.9" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="170.8" y="757.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="305" y="757.9" width="109.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="414.8" y="757.9" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="451.4" y="757.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="512.4" y="757.9" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="549" y="757.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="610" y="757.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="658.8" y="757.9" width="170.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="829.6" y="757.9" width="48.8" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="878.4" y="757.9" width="85.4" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="963.8" y="757.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1024.8" y="757.9" width="61" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1085.8" y="757.9" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1122.4" y="757.9" width="134.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1256.6" y="757.9" width="36.6" height="24.65" shape-rendering="crispEdges"/><rect fill="#242f38" x="1293.2" y="757.9" width="73.2" height="24.65" shape-rendering="crispEdges"/>
155
+ <g class="terminal-3309028997-matrix">
156
+ <text class="terminal-3309028997-r2" x="12.2" y="20" textLength="12.2" clip-path="url(#terminal-3309028997-line-0)">⭘</text><text class="terminal-3309028997-r2" x="597.8" y="20" textLength="146.4" clip-path="url(#terminal-3309028997-line-0)">devices&#160;#111</text><text class="terminal-3309028997-r1" x="1366.4" y="20" textLength="12.2" clip-path="url(#terminal-3309028997-line-0)">
157
+ </text><text class="terminal-3309028997-r3" x="0" y="44.4" textLength="244" clip-path="url(#terminal-3309028997-line-1)">&#160;field&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r3" x="244" y="44.4" textLength="231.8" clip-path="url(#terminal-3309028997-line-1)">&#160;value&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="44.4" textLength="12.2" clip-path="url(#terminal-3309028997-line-1)">
158
+ </text><text class="terminal-3309028997-r5" x="0" y="68.8" textLength="244" clip-path="url(#terminal-3309028997-line-2)">&#160;name&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r5" x="244" y="68.8" textLength="231.8" clip-path="url(#terminal-3309028997-line-2)">&#160;dmi01-akron-pdu01&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="68.8" textLength="12.2" clip-path="url(#terminal-3309028997-line-2)">
159
+ </text><text class="terminal-3309028997-r2" x="0" y="93.2" textLength="244" clip-path="url(#terminal-3309028997-line-3)">&#160;device_type&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="93.2" textLength="12.2" clip-path="url(#terminal-3309028997-line-3)">
160
+ </text><text class="terminal-3309028997-r2" x="0" y="117.6" textLength="244" clip-path="url(#terminal-3309028997-line-4)">&#160;role&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r2" x="244" y="117.6" textLength="231.8" clip-path="url(#terminal-3309028997-line-4)">&#160;PDU&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="117.6" textLength="12.2" clip-path="url(#terminal-3309028997-line-4)">
161
+ </text><text class="terminal-3309028997-r2" x="0" y="142" textLength="244" clip-path="url(#terminal-3309028997-line-5)">&#160;tenant&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="142" textLength="12.2" clip-path="url(#terminal-3309028997-line-5)">
162
+ </text><text class="terminal-3309028997-r2" x="0" y="166.4" textLength="244" clip-path="url(#terminal-3309028997-line-6)">&#160;platform&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="166.4" textLength="12.2" clip-path="url(#terminal-3309028997-line-6)">
163
+ </text><text class="terminal-3309028997-r2" x="0" y="190.8" textLength="244" clip-path="url(#terminal-3309028997-line-7)">&#160;serial&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r2" x="244" y="190.8" textLength="231.8" clip-path="url(#terminal-3309028997-line-7)">&#160;6457-2371&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="190.8" textLength="12.2" clip-path="url(#terminal-3309028997-line-7)">
164
+ </text><text class="terminal-3309028997-r2" x="0" y="215.2" textLength="244" clip-path="url(#terminal-3309028997-line-8)">&#160;asset_tag&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="215.2" textLength="12.2" clip-path="url(#terminal-3309028997-line-8)">
165
+ </text><text class="terminal-3309028997-r2" x="0" y="239.6" textLength="244" clip-path="url(#terminal-3309028997-line-9)">&#160;site&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r2" x="244" y="239.6" textLength="231.8" clip-path="url(#terminal-3309028997-line-9)">&#160;DM-Akron&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="239.6" textLength="12.2" clip-path="url(#terminal-3309028997-line-9)">
166
+ </text><text class="terminal-3309028997-r2" x="0" y="264" textLength="244" clip-path="url(#terminal-3309028997-line-10)">&#160;location&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="264" textLength="12.2" clip-path="url(#terminal-3309028997-line-10)">
167
+ </text><text class="terminal-3309028997-r2" x="0" y="288.4" textLength="244" clip-path="url(#terminal-3309028997-line-11)">&#160;rack&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="288.4" textLength="12.2" clip-path="url(#terminal-3309028997-line-11)">
168
+ </text><text class="terminal-3309028997-r2" x="0" y="312.8" textLength="244" clip-path="url(#terminal-3309028997-line-12)">&#160;position&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="312.8" textLength="12.2" clip-path="url(#terminal-3309028997-line-12)">
169
+ </text><text class="terminal-3309028997-r2" x="0" y="337.2" textLength="244" clip-path="url(#terminal-3309028997-line-13)">&#160;face&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="337.2" textLength="12.2" clip-path="url(#terminal-3309028997-line-13)">
170
+ </text><text class="terminal-3309028997-r2" x="0" y="361.6" textLength="244" clip-path="url(#terminal-3309028997-line-14)">&#160;latitude&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="361.6" textLength="12.2" clip-path="url(#terminal-3309028997-line-14)">
171
+ </text><text class="terminal-3309028997-r2" x="0" y="386" textLength="244" clip-path="url(#terminal-3309028997-line-15)">&#160;longitude&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="386" textLength="12.2" clip-path="url(#terminal-3309028997-line-15)">
172
+ </text><text class="terminal-3309028997-r2" x="0" y="410.4" textLength="244" clip-path="url(#terminal-3309028997-line-16)">&#160;status&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="410.4" textLength="12.2" clip-path="url(#terminal-3309028997-line-16)">
173
+ </text><text class="terminal-3309028997-r2" x="0" y="434.8" textLength="244" clip-path="url(#terminal-3309028997-line-17)">&#160;airflow&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="434.8" textLength="12.2" clip-path="url(#terminal-3309028997-line-17)">
174
+ </text><text class="terminal-3309028997-r2" x="0" y="459.2" textLength="244" clip-path="url(#terminal-3309028997-line-18)">&#160;primary_ip4&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="459.2" textLength="12.2" clip-path="url(#terminal-3309028997-line-18)">
175
+ </text><text class="terminal-3309028997-r2" x="0" y="483.6" textLength="244" clip-path="url(#terminal-3309028997-line-19)">&#160;primary_ip6&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="483.6" textLength="12.2" clip-path="url(#terminal-3309028997-line-19)">
176
+ </text><text class="terminal-3309028997-r2" x="0" y="508" textLength="244" clip-path="url(#terminal-3309028997-line-20)">&#160;oob_ip&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="508" textLength="12.2" clip-path="url(#terminal-3309028997-line-20)">
177
+ </text><text class="terminal-3309028997-r2" x="0" y="532.4" textLength="244" clip-path="url(#terminal-3309028997-line-21)">&#160;cluster&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="532.4" textLength="12.2" clip-path="url(#terminal-3309028997-line-21)">
178
+ </text><text class="terminal-3309028997-r2" x="0" y="556.8" textLength="244" clip-path="url(#terminal-3309028997-line-22)">&#160;virtual_chassis&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="556.8" textLength="12.2" clip-path="url(#terminal-3309028997-line-22)">
179
+ </text><text class="terminal-3309028997-r2" x="0" y="581.2" textLength="244" clip-path="url(#terminal-3309028997-line-23)">&#160;vc_position&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="581.2" textLength="12.2" clip-path="url(#terminal-3309028997-line-23)">
180
+ </text><text class="terminal-3309028997-r2" x="0" y="605.6" textLength="244" clip-path="url(#terminal-3309028997-line-24)">&#160;vc_priority&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r6" x="1342" y="605.6" textLength="24.4" clip-path="url(#terminal-3309028997-line-24)">▁▁</text><text class="terminal-3309028997-r1" x="1366.4" y="605.6" textLength="12.2" clip-path="url(#terminal-3309028997-line-24)">
181
+ </text><text class="terminal-3309028997-r2" x="0" y="630" textLength="244" clip-path="url(#terminal-3309028997-line-25)">&#160;description&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="630" textLength="12.2" clip-path="url(#terminal-3309028997-line-25)">
182
+ </text><text class="terminal-3309028997-r2" x="0" y="654.4" textLength="244" clip-path="url(#terminal-3309028997-line-26)">&#160;owner&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="654.4" textLength="12.2" clip-path="url(#terminal-3309028997-line-26)">
183
+ </text><text class="terminal-3309028997-r2" x="0" y="678.8" textLength="244" clip-path="url(#terminal-3309028997-line-27)">&#160;comments&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="678.8" textLength="12.2" clip-path="url(#terminal-3309028997-line-27)">
184
+ </text><text class="terminal-3309028997-r2" x="0" y="703.2" textLength="244" clip-path="url(#terminal-3309028997-line-28)">&#160;config_template&#160;&#160;&#160;&#160;</text><text class="terminal-3309028997-r1" x="1366.4" y="703.2" textLength="12.2" clip-path="url(#terminal-3309028997-line-28)">
185
+ </text><text class="terminal-3309028997-r2" x="12.2" y="727.6" textLength="73.2" clip-path="url(#terminal-3309028997-line-29)">cables</text><text class="terminal-3309028997-r7" x="109.8" y="727.6" textLength="158.6" clip-path="url(#terminal-3309028997-line-29)">console-ports</text><text class="terminal-3309028997-r7" x="292.8" y="727.6" textLength="244" clip-path="url(#terminal-3309028997-line-29)">console-server-ports</text><text class="terminal-3309028997-r7" x="561.2" y="727.6" textLength="134.2" clip-path="url(#terminal-3309028997-line-29)">device-bays</text><text class="terminal-3309028997-r7" x="719.8" y="727.6" textLength="134.2" clip-path="url(#terminal-3309028997-line-29)">front-ports</text><text class="terminal-3309028997-r7" x="878.4" y="727.6" textLength="122" clip-path="url(#terminal-3309028997-line-29)">interfaces</text><text class="terminal-3309028997-r7" x="1024.8" y="727.6" textLength="183" clip-path="url(#terminal-3309028997-line-29)">inventory-items</text><text class="terminal-3309028997-r7" x="1232.2" y="727.6" textLength="134.2" clip-path="url(#terminal-3309028997-line-29)">mac-address</text><text class="terminal-3309028997-r1" x="1366.4" y="727.6" textLength="12.2" clip-path="url(#terminal-3309028997-line-29)">
186
+ </text><text class="terminal-3309028997-r8" x="0" y="752" textLength="12.2" clip-path="url(#terminal-3309028997-line-30)">╸</text><text class="terminal-3309028997-r9" x="12.2" y="752" textLength="73.2" clip-path="url(#terminal-3309028997-line-30)">━━━━━━</text><text class="terminal-3309028997-r8" x="85.4" y="752" textLength="12.2" clip-path="url(#terminal-3309028997-line-30)">╺</text><text class="terminal-3309028997-r8" x="97.6" y="752" textLength="1268.8" clip-path="url(#terminal-3309028997-line-30)">━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</text><text class="terminal-3309028997-r1" x="1366.4" y="752" textLength="12.2" clip-path="url(#terminal-3309028997-line-30)">
187
+ </text><text class="terminal-3309028997-r10" x="0" y="776.4" textLength="61" clip-path="url(#terminal-3309028997-line-31)">&#160;tab&#160;</text><text class="terminal-3309028997-r2" x="61" y="776.4" textLength="109.8" clip-path="url(#terminal-3309028997-line-31)">Next&#160;tab&#160;</text><text class="terminal-3309028997-r10" x="170.8" y="776.4" textLength="134.2" clip-path="url(#terminal-3309028997-line-31)">&#160;shift+tab&#160;</text><text class="terminal-3309028997-r2" x="305" y="776.4" textLength="109.8" clip-path="url(#terminal-3309028997-line-31)">Prev&#160;tab&#160;</text><text class="terminal-3309028997-r10" x="414.8" y="776.4" textLength="36.6" clip-path="url(#terminal-3309028997-line-31)">&#160;q&#160;</text><text class="terminal-3309028997-r2" x="451.4" y="776.4" textLength="61" clip-path="url(#terminal-3309028997-line-31)">Quit&#160;</text><text class="terminal-3309028997-r10" x="512.4" y="776.4" textLength="36.6" clip-path="url(#terminal-3309028997-line-31)">&#160;?&#160;</text><text class="terminal-3309028997-r2" x="549" y="776.4" textLength="61" clip-path="url(#terminal-3309028997-line-31)">Help&#160;</text><text class="terminal-3309028997-r10" x="610" y="776.4" textLength="48.8" clip-path="url(#terminal-3309028997-line-31)">&#160;^p&#160;</text><text class="terminal-3309028997-r2" x="658.8" y="776.4" textLength="170.8" clip-path="url(#terminal-3309028997-line-31)">Find&#160;resource&#160;</text><text class="terminal-3309028997-r10" x="829.6" y="776.4" textLength="48.8" clip-path="url(#terminal-3309028997-line-31)">&#160;^f&#160;</text><text class="terminal-3309028997-r2" x="878.4" y="776.4" textLength="85.4" clip-path="url(#terminal-3309028997-line-31)">Search&#160;</text><text class="terminal-3309028997-r10" x="963.8" y="776.4" textLength="61" clip-path="url(#terminal-3309028997-line-31)">&#160;esc&#160;</text><text class="terminal-3309028997-r2" x="1024.8" y="776.4" textLength="61" clip-path="url(#terminal-3309028997-line-31)">Back&#160;</text><text class="terminal-3309028997-r10" x="1085.8" y="776.4" textLength="36.6" clip-path="url(#terminal-3309028997-line-31)">&#160;e&#160;</text><text class="terminal-3309028997-r2" x="1122.4" y="776.4" textLength="134.2" clip-path="url(#terminal-3309028997-line-31)">Edit&#160;field&#160;</text><text class="terminal-3309028997-r10" x="1256.6" y="776.4" textLength="36.6" clip-path="url(#terminal-3309028997-line-31)">&#160;s&#160;</text><text class="terminal-3309028997-r2" x="1293.2" y="776.4" textLength="73.2" clip-path="url(#terminal-3309028997-line-31)">Save&#160;c</text>
188
+ </g>
189
+ </g>
190
+ </svg>