cortexapps-cli 1.30.0__tar.gz → 1.32.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 (209) hide show
  1. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/PKG-INFO +2 -1
  2. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations.py +2 -0
  3. cortexapps_cli-1.32.0/cortexapps_cli/commands/integrations_commands/harness.py +160 -0
  4. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/solutions.py +114 -17
  5. cortexapps_cli-1.32.0/cortexapps_cli/solutions/_lib/__init__.py +0 -0
  6. cortexapps_cli-1.32.0/cortexapps_cli/solutions/_lib/setup_base.py +183 -0
  7. cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/README.md +97 -0
  8. cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-async-callback.yml +39 -0
  9. cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-deploy.yml +51 -0
  10. cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/_templates/cortex-register-deploy.yml +51 -0
  11. cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/_templates/github-actions-deploy.yaml +132 -0
  12. cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/catalog/github-actions-demo.yaml +9 -0
  13. cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/scorecards/deploy-health.yaml +51 -0
  14. cortexapps_cli-1.32.0/cortexapps_cli/solutions/github-actions-deploy/setup.py +508 -0
  15. cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/README.md +112 -0
  16. cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/_templates/cortex-async-callback-template.yaml +59 -0
  17. cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/_templates/cortex-deploy-pipeline.yaml +110 -0
  18. cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/_templates/cortex-record-deploy-template.yaml +55 -0
  19. cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/_templates/trigger-harness-deploy.yaml +177 -0
  20. cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/catalog/harness-demo.yaml +14 -0
  21. cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/scorecards/deploy-health.yaml +51 -0
  22. cortexapps_cli-1.32.0/cortexapps_cli/solutions/harness-deploy/setup.py +520 -0
  23. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/pyproject.toml +2 -1
  24. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/LICENSE +0 -0
  25. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/README.rst +0 -0
  26. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/__init__.py +0 -0
  27. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/cli.py +0 -0
  28. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/command_options.py +0 -0
  29. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/ai_skills.py +0 -0
  30. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/api_keys.py +0 -0
  31. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/audit_logs.py +0 -0
  32. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/backup.py +0 -0
  33. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/backup_commands/cortex_export.py +0 -0
  34. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/catalog.py +0 -0
  35. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/custom_data.py +0 -0
  36. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/custom_events.py +0 -0
  37. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/custom_metrics.py +0 -0
  38. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/dependencies.py +0 -0
  39. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/deploys.py +0 -0
  40. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/discovery_audit.py +0 -0
  41. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/docs.py +0 -0
  42. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/entity_relationship_types.py +0 -0
  43. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/entity_relationships.py +0 -0
  44. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/entity_types.py +0 -0
  45. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/gitops_logs.py +0 -0
  46. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/groups.py +0 -0
  47. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/initiatives.py +0 -0
  48. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/apiiro.py +0 -0
  49. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/argocd.py +0 -0
  50. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/aws.py +0 -0
  51. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/azure_active_directory.py +0 -0
  52. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/azure_devops.py +0 -0
  53. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/azure_resources.py +0 -0
  54. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/bamboohr.py +0 -0
  55. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/bitbucket.py +0 -0
  56. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/bugsnag.py +0 -0
  57. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/buildkite.py +0 -0
  58. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/checkmarx_sast.py +0 -0
  59. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/circleci.py +0 -0
  60. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/clickup.py +0 -0
  61. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/codecov.py +0 -0
  62. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/coralogix.py +0 -0
  63. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/datadog.py +0 -0
  64. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/dynatrace.py +0 -0
  65. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/firehydrant.py +0 -0
  66. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/github.py +0 -0
  67. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/gitlab.py +0 -0
  68. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/incidentio.py +0 -0
  69. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/instana.py +0 -0
  70. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/jenkins.py +0 -0
  71. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/jira.py +0 -0
  72. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/launchdarkly.py +0 -0
  73. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/mend_sast.py +0 -0
  74. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/mend_sca.py +0 -0
  75. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/newrelic.py +0 -0
  76. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/okta.py +0 -0
  77. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/opsgenie.py +0 -0
  78. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/pagerduty.py +0 -0
  79. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/prometheus.py +0 -0
  80. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/rollbar.py +0 -0
  81. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/rootly.py +0 -0
  82. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/semgrep.py +0 -0
  83. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/sentry.py +0 -0
  84. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/servicenow.py +0 -0
  85. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/servicenow_cloud_observability.py +0 -0
  86. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/snyk.py +0 -0
  87. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/sonarqube.py +0 -0
  88. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/splunk_observability_cloud.py +0 -0
  89. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/splunk_on_call.py +0 -0
  90. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/sumologic.py +0 -0
  91. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/veracode.py +0 -0
  92. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/wiz.py +0 -0
  93. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/workday.py +0 -0
  94. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/integrations_commands/xmatters.py +0 -0
  95. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/ip_allowlist.py +0 -0
  96. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/on_call.py +0 -0
  97. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages.py +0 -0
  98. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/go.py +0 -0
  99. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/java.py +0 -0
  100. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/node.py +0 -0
  101. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/nuget.py +0 -0
  102. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/packages_commands/python.py +0 -0
  103. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/plugins.py +0 -0
  104. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/queries.py +0 -0
  105. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/rest.py +0 -0
  106. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/scim.py +0 -0
  107. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/scorecards.py +0 -0
  108. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/scorecards_commands/exemptions.py +0 -0
  109. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/secrets.py +0 -0
  110. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/teams.py +0 -0
  111. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/users.py +0 -0
  112. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/users_commands/roles.py +0 -0
  113. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/commands/workflows.py +0 -0
  114. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/cortex_client.py +0 -0
  115. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/models/team.py +0 -0
  116. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/__init__.py +0 -0
  117. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/README.md +0 -0
  118. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/address-reviews.yaml +0 -0
  119. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/audit-flags.yaml +0 -0
  120. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/code-review.yaml +0 -0
  121. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/fix-issue.yaml +0 -0
  122. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/incident-trends.yaml +0 -0
  123. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/jira-to-kb.yaml +0 -0
  124. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/release-to-sha.yaml +0 -0
  125. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/search-troubleshooting-kb.yaml +0 -0
  126. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/catalog/simplify-test.yaml +0 -0
  127. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/entity-types/ai-agent.json +0 -0
  128. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-agents/scorecards/ai-agent-governance.yaml +0 -0
  129. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/README.md +0 -0
  130. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/catalog/api-doc-sync.yaml +0 -0
  131. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/catalog/changelog-writer.yaml +0 -0
  132. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/catalog/docs-generator.yaml +0 -0
  133. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/catalog/docs-portal.yaml +0 -0
  134. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-plugin-service.json +0 -0
  135. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-plugin-skills.json +0 -0
  136. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-relationship-types/ai-skill-service.json +0 -0
  137. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-types/ai-plugin.json +0 -0
  138. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/entity-types/ai-skill.json +0 -0
  139. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-skills/scorecards/ai-skills-quality.yaml +0 -0
  140. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/.github/workflows/sync-claude-spend.yaml +0 -0
  141. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/README.md +0 -0
  142. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-alice-chen.yaml +0 -0
  143. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-bob-martinez.yaml +0 -0
  144. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-carol-kim.yaml +0 -0
  145. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-david-osei.yaml +0 -0
  146. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/employee-emma-johnson.yaml +0 -0
  147. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/team-data.yaml +0 -0
  148. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/team-engineering.yaml +0 -0
  149. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/team-frontend.yaml +0 -0
  150. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/catalog/team-platform.yaml +0 -0
  151. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/custom-metrics/ai-spend.json +0 -0
  152. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/entity-relationship-types/team-member.json +0 -0
  153. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/entity-types/employee.json +0 -0
  154. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/plugins/team-ai-spend.json +0 -0
  155. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/scorecards/ai-spend-scorecard.yaml +0 -0
  156. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/ai-spend/scripts/sync-claude-spend.py +0 -0
  157. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/.github/workflows/cortex-service-version.yaml +0 -0
  158. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/README.md +0 -0
  159. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.0.1.yaml +0 -0
  160. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.1.0.yaml +0 -0
  161. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/api-gateway-3.1.1.yaml +0 -0
  162. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/api-gateway.yaml +0 -0
  163. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/auth-2.1.0.yaml +0 -0
  164. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/auth-2.1.1.yaml +0 -0
  165. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/auth-2.2.0.yaml +0 -0
  166. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/auth.yaml +0 -0
  167. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/aws-dev-us-west-2.yaml +0 -0
  168. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/billing-1.1.0.yaml +0 -0
  169. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/billing-1.2.0.yaml +0 -0
  170. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/billing.yaml +0 -0
  171. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/cart-1.0.2.yaml +0 -0
  172. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/cart.yaml +0 -0
  173. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.2.0.yaml +0 -0
  174. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.3.0.yaml +0 -0
  175. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/checkout-2.4.0.yaml +0 -0
  176. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/checkout.yaml +0 -0
  177. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/gcp-prod-us-east-1.yaml +0 -0
  178. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/gcp-prod-us-west-1.yaml +0 -0
  179. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/gcp-staging-us-east-1.yaml +0 -0
  180. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.0.0.yaml +0 -0
  181. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.0.1.yaml +0 -0
  182. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/notifications-1.1.0.yaml +0 -0
  183. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/notifications.yaml +0 -0
  184. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/order-service-2.0.1.yaml +0 -0
  185. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/order-service-2.1.0.yaml +0 -0
  186. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/order-service.yaml +0 -0
  187. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/payments-1.6.1.yaml +0 -0
  188. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/payments-1.7.0.yaml +0 -0
  189. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/payments-1.7.1.yaml +0 -0
  190. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/payments.yaml +0 -0
  191. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/release-2026-06-20.yaml +0 -0
  192. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-01.yaml +0 -0
  193. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-09.yaml +0 -0
  194. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/release-2026-07-10.yaml +0 -0
  195. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/search-4.0.0.yaml +0 -0
  196. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/search-4.1.0.yaml +0 -0
  197. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/search.yaml +0 -0
  198. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/user-service-1.2.0.yaml +0 -0
  199. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/user-service-1.3.0.yaml +0 -0
  200. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/catalog/user-service.yaml +0 -0
  201. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-relationship-types/environments.json +0 -0
  202. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-relationship-types/versions.json +0 -0
  203. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-types/environment.json +0 -0
  204. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-types/release.json +0 -0
  205. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/entity-types/service-version.json +0 -0
  206. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/workflows/create-environment.yaml +0 -0
  207. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/workflows/create-release.yaml +0 -0
  208. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/solutions/environments/workflows/trigger-github-release.yaml +0 -0
  209. {cortexapps_cli-1.30.0 → cortexapps_cli-1.32.0}/cortexapps_cli/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cortexapps-cli
3
- Version: 1.30.0
3
+ Version: 1.32.0
4
4
  Summary: Command Line Interface for cortexapps
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: PyNaCl (>=1.5.0)
17
18
  Requires-Dist: pyyaml (>=6.0.1,<7)
18
19
  Requires-Dist: requests (>=2.33.0,<3.0.0)
19
20
  Requires-Dist: typer (>=0.15,<1.0)
@@ -23,6 +23,7 @@ import cortexapps_cli.commands.integrations_commands.dynatrace as dynatrace
23
23
  import cortexapps_cli.commands.integrations_commands.firehydrant as firehydrant
24
24
  import cortexapps_cli.commands.integrations_commands.github as github
25
25
  import cortexapps_cli.commands.integrations_commands.gitlab as gitlab
26
+ import cortexapps_cli.commands.integrations_commands.harness as harness
26
27
  import cortexapps_cli.commands.integrations_commands.incidentio as incidentio
27
28
  import cortexapps_cli.commands.integrations_commands.instana as instana
28
29
  import cortexapps_cli.commands.integrations_commands.jenkins as jenkins
@@ -72,6 +73,7 @@ app.add_typer(dynatrace.app, name="dynatrace")
72
73
  app.add_typer(firehydrant.app, name="firehydrant")
73
74
  app.add_typer(github.app, name="github")
74
75
  app.add_typer(gitlab.app, name="gitlab")
76
+ app.add_typer(harness.app, name="harness")
75
77
  app.add_typer(incidentio.app, name="incidentio")
76
78
  app.add_typer(instana.app, name="instana")
77
79
  app.add_typer(jenkins.app, name="jenkins")
@@ -0,0 +1,160 @@
1
+ import json
2
+ from rich import print_json
3
+ import typer
4
+ from typing_extensions import Annotated
5
+
6
+ app = typer.Typer(help="Harness commands", no_args_is_help=True)
7
+
8
+
9
+ @app.command()
10
+ def add(
11
+ ctx: typer.Context,
12
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias for this configuration"),
13
+ api_key: str = typer.Option(..., "--api-key", "-k", help="Harness API key"),
14
+ account_id: str = typer.Option(..., "--account-id", "-id", help="Harness account ID"),
15
+ host: str = typer.Option(None, "--host", "-h", help="Harness host URL (optional; defaults to https://app.harness.io)"),
16
+ is_default: bool = typer.Option(False, "--is-default", "-i", help="Set as the default configuration"),
17
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing configuration; use - for stdin")] = None,
18
+ ):
19
+ """
20
+ Add a Harness configuration
21
+ """
22
+ client = ctx.obj["client"]
23
+
24
+ if file_input:
25
+ if alias or api_key or account_id or host or is_default:
26
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
27
+ data = json.loads("".join([line for line in file_input]))
28
+ else:
29
+ data = {
30
+ "alias": alias,
31
+ "apiKey": api_key,
32
+ "accountId": account_id,
33
+ "isDefault": is_default,
34
+ }
35
+ if host is not None:
36
+ data["host"] = host
37
+
38
+ r = client.post("api/v1/harness/configuration", data=data)
39
+ print_json(data=r)
40
+
41
+
42
+ @app.command()
43
+ def get(
44
+ ctx: typer.Context,
45
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias of the configuration to retrieve"),
46
+ ):
47
+ """
48
+ Get a single Harness configuration by alias
49
+ """
50
+ client = ctx.obj["client"]
51
+ r = client.get(f"api/v1/harness/configuration/{alias}")
52
+ print_json(data=r)
53
+
54
+
55
+ @app.command()
56
+ def list(
57
+ ctx: typer.Context,
58
+ ):
59
+ """
60
+ List all Harness configurations
61
+ """
62
+ client = ctx.obj["client"]
63
+ r = client.get("api/v1/harness/configurations")
64
+ print_json(data=r)
65
+
66
+
67
+ @app.command()
68
+ def get_default(
69
+ ctx: typer.Context,
70
+ ):
71
+ """
72
+ Get the default Harness configuration
73
+ """
74
+ client = ctx.obj["client"]
75
+ r = client.get("api/v1/harness/default-configuration")
76
+ print_json(data=r)
77
+
78
+
79
+ @app.command()
80
+ def update(
81
+ ctx: typer.Context,
82
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias of the configuration to update"),
83
+ api_key: str = typer.Option(None, "--api-key", "-k", help="New Harness API key"),
84
+ account_id: str = typer.Option(None, "--account-id", "-id", help="New Harness account ID"),
85
+ host: str = typer.Option(None, "--host", "-h", help="New Harness host URL"),
86
+ is_default: bool = typer.Option(None, "--is-default", "-i", help="Set as the default configuration"),
87
+ file_input: Annotated[typer.FileText, typer.Option("--file", "-f", help="JSON file containing update fields; use - for stdin")] = None,
88
+ ):
89
+ """
90
+ Update a Harness configuration
91
+ """
92
+ client = ctx.obj["client"]
93
+
94
+ if file_input:
95
+ if alias or api_key or account_id or host or is_default is not None:
96
+ raise typer.BadParameter("When providing a configuration file, do not specify any other attributes")
97
+ data = json.loads("".join([line for line in file_input]))
98
+ else:
99
+ data = {}
100
+ if api_key is not None:
101
+ data["apiKey"] = api_key
102
+ if account_id is not None:
103
+ data["accountId"] = account_id
104
+ if host is not None:
105
+ data["host"] = host
106
+ if is_default is not None:
107
+ data["isDefault"] = is_default
108
+
109
+ r = client.put(f"api/v1/harness/configuration/{alias}", data=data)
110
+ print_json(data=r)
111
+
112
+
113
+ @app.command()
114
+ def delete(
115
+ ctx: typer.Context,
116
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias of the configuration to delete"),
117
+ ):
118
+ """
119
+ Delete a single Harness configuration
120
+ """
121
+ client = ctx.obj["client"]
122
+ r = client.delete(f"api/v1/harness/configuration/{alias}")
123
+ print_json(data=r)
124
+
125
+
126
+ @app.command()
127
+ def delete_all(
128
+ ctx: typer.Context,
129
+ ):
130
+ """
131
+ Delete all Harness configurations
132
+ """
133
+ client = ctx.obj["client"]
134
+ r = client.delete("api/v1/harness/configurations")
135
+ print_json(data=r)
136
+
137
+
138
+ @app.command()
139
+ def validate(
140
+ ctx: typer.Context,
141
+ alias: str = typer.Option(..., "--alias", "-a", help="Alias of the configuration to validate"),
142
+ ):
143
+ """
144
+ Validate a single Harness configuration
145
+ """
146
+ client = ctx.obj["client"]
147
+ r = client.post(f"api/v1/harness/configuration/validate/{alias}")
148
+ print_json(data=r)
149
+
150
+
151
+ @app.command()
152
+ def validate_all(
153
+ ctx: typer.Context,
154
+ ):
155
+ """
156
+ Validate all Harness configurations
157
+ """
158
+ client = ctx.obj["client"]
159
+ r = client.post("api/v1/harness/configuration/validate")
160
+ print_json(data=r)
@@ -74,13 +74,26 @@ class _ToggleableCapture:
74
74
 
75
75
 
76
76
  def _run_import_with_toggle(fn) -> str:
77
- """Run fn() capturing stdout. On a TTY, Ctrl+o toggles live output."""
77
+ """Run fn() capturing stdout. On a TTY, Ctrl+o toggles live output.
78
+
79
+ If fn() raises for any reason, all buffered output is flushed to the
80
+ terminal before the exception propagates — no silent failures.
81
+ """
78
82
  real_stdout = sys.stdout
79
83
  capture = _ToggleableCapture(real_stdout)
80
84
 
81
85
  if not (_TTY_SUPPORT and sys.stdin.isatty()):
82
- with contextlib.redirect_stdout(capture):
83
- fn()
86
+ success = False
87
+ try:
88
+ with contextlib.redirect_stdout(capture):
89
+ fn()
90
+ success = True
91
+ finally:
92
+ if not success:
93
+ buffered = capture.getvalue()
94
+ if buffered:
95
+ real_stdout.write(buffered)
96
+ real_stdout.flush()
84
97
  return capture.getvalue()
85
98
 
86
99
  done = threading.Event()
@@ -112,9 +125,11 @@ def _run_import_with_toggle(fn) -> str:
112
125
 
113
126
  t = threading.Thread(target=_listen, daemon=True)
114
127
  t.start()
128
+ success = False
115
129
  try:
116
130
  with contextlib.redirect_stdout(capture):
117
131
  fn()
132
+ success = True
118
133
  finally:
119
134
  done.set()
120
135
  t.join(timeout=0.5)
@@ -122,6 +137,11 @@ def _run_import_with_toggle(fn) -> str:
122
137
  _termios.tcsetattr(fd, _termios.TCSADRAIN, old_settings)
123
138
  except Exception:
124
139
  pass
140
+ if not success:
141
+ buffered = capture.getvalue()
142
+ if buffered:
143
+ real_stdout.write(buffered)
144
+ real_stdout.flush()
125
145
 
126
146
  return capture.getvalue()
127
147
 
@@ -175,6 +195,53 @@ def _get_readme(tag: str, path: str | None = None) -> str | None:
175
195
  return None
176
196
 
177
197
 
198
+ def _has_post_install(tag: str, path: str | None = None) -> bool:
199
+ """Return True if this solution has a post-install setup.py."""
200
+ try:
201
+ (_solutions_root(path) / tag / "setup.py").read_bytes()
202
+ return True
203
+ except Exception:
204
+ return False
205
+
206
+
207
+ def _load_setup_module(solution_tag: str, solutions_dir: str | None = None):
208
+ """Load a solution's setup.py module. Returns the module or None if not found."""
209
+ import importlib.util
210
+
211
+ root = _solutions_root(solutions_dir)
212
+ try:
213
+ with as_file(root / solution_tag / "setup.py") as setup_path:
214
+ spec = importlib.util.spec_from_file_location(f"{solution_tag}_setup", setup_path)
215
+ module = importlib.util.module_from_spec(spec)
216
+ spec.loader.exec_module(module)
217
+ return module
218
+ except FileNotFoundError:
219
+ return None
220
+
221
+
222
+ def _get_setup_description(solution_tag: str, solutions_dir: str | None = None) -> str:
223
+ """Return the SETUP_DESCRIPTION from a solution's setup.py, or a generic fallback."""
224
+ module = _load_setup_module(solution_tag, solutions_dir)
225
+ if module:
226
+ return getattr(module, "SETUP_DESCRIPTION", "This solution includes a post-install setup script.")
227
+ return "This solution includes a post-install setup script."
228
+
229
+
230
+ def _run_post_install_script(solution_tag: str, solutions_dir: str | None = None, ctx=None, no_prompt: bool = False) -> None:
231
+ """Find and invoke the solution's setup.py main() function."""
232
+ module = _load_setup_module(solution_tag, solutions_dir)
233
+ if module is None:
234
+ typer.echo("No post-install setup available for this solution.")
235
+ return
236
+ kwargs = {}
237
+ if ctx and ctx.obj and "client" in ctx.obj:
238
+ client = ctx.obj["client"]
239
+ kwargs["cortex_api_key"] = client.api_key
240
+ kwargs["cortex_base_url"] = client.base_url
241
+ kwargs["no_prompt"] = no_prompt
242
+ module.main(**kwargs)
243
+
244
+
178
245
  def _extract_first_codeblock(text: str) -> str | None:
179
246
  """Return content of the first fenced code block."""
180
247
  m = re.search(r"```[^\n]*\n(.*?)```", text, re.DOTALL)
@@ -333,10 +400,10 @@ def _run_uninstall(client, path: Path, yes: bool) -> None:
333
400
  total = sum(len(v) for v in resources.values())
334
401
 
335
402
  if total == 0:
336
- typer.echo("No resources found to remove.")
403
+ typer.echo("No entities found to remove.")
337
404
  return
338
405
 
339
- typer.echo("\nThis will remove the following resources:")
406
+ typer.echo("\nThis will remove the following entities:")
340
407
  for kind in ("workflows", "scorecards", "plugins", "catalog", "entity-relationship-types", "entity-types"):
341
408
  count = len(resources[kind])
342
409
  if count:
@@ -538,17 +605,6 @@ def _show_next_steps(readme: str) -> None:
538
605
  section = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'\1 (\2)', section)
539
606
  console.print()
540
607
  console.print(Markdown(section))
541
- console.print()
542
- console.print(
543
- "[magenta]Planned for Q4 2027:[/magenta] CQL metadata traversal will enable scorecard rules "
544
- "across relationship chains — for example, a Vulnerability Scorecard checking that no "
545
- "deployed service-version has open Snyk issues:"
546
- )
547
- console.print(
548
- " [dim]entity.destinations(relationshipType = \"environments\", depth = 3)\n"
549
- " .filter((d) => d.type == \"service-version\")\n"
550
- " .all((sv) => sv.snyk.issues == 0)[/dim]"
551
- )
552
608
 
553
609
 
554
610
  def _post_install_menu(
@@ -604,6 +660,12 @@ def install(
604
660
  ctx: typer.Context,
605
661
  solution: str = typer.Option(..., "--solution", "-s", help="Solution tag"),
606
662
  no_prompt: bool = typer.Option(False, "--no-prompt", help="Skip the post-install interactive menu"),
663
+ skip_post_install_setup: bool = typer.Option(
664
+ False,
665
+ "--skip-post-install-setup",
666
+ help="Skip the post-install setup script prompt",
667
+ is_flag=True,
668
+ ),
607
669
  ):
608
670
  """Install a solution."""
609
671
  solutions_dir = ctx.obj.get("solutions_dir") if ctx.obj else None
@@ -639,10 +701,24 @@ def install(
639
701
  typer.echo(failed_m.group(0))
640
702
  typer.echo(f"\n {total_imported} imported, {total_failed} failed")
641
703
  else:
642
- typer.echo(f" {total_imported} resources imported")
704
+ typer.echo(f" {total_imported} entities imported")
643
705
  else:
644
706
  typer.echo(output)
645
707
 
708
+ # Post-install setup hook — runs before the informational menu
709
+ if not no_prompt and not skip_post_install_setup and _has_post_install(solution, solutions_dir):
710
+ state_file = Path.home() / ".cortex" / "solutions" / f"{solution}.json"
711
+ if state_file.exists():
712
+ typer.echo(f"\nRetrieving previous responses from: {state_file}")
713
+ desc = _get_setup_description(solution, solutions_dir)
714
+ typer.echo(f"\n{desc}")
715
+ if typer.confirm("Run setup now?", default=True):
716
+ _run_post_install_script(solution, solutions_dir=solutions_dir, ctx=ctx)
717
+ else:
718
+ typer.echo(f"\nRun setup later with: cortex solutions post-install -s {solution}")
719
+ elif skip_post_install_setup and _has_post_install(solution, solutions_dir):
720
+ typer.echo(f"\nRun setup later with: cortex solutions post-install -s {solution}")
721
+
646
722
  if not no_prompt:
647
723
  readme = _get_readme(solution, solutions_dir)
648
724
  if readme:
@@ -660,6 +736,27 @@ def install(
660
736
  _post_install_menu(readme, import_report=output, entity_tags=entity_tags, ui_url=ui_url)
661
737
 
662
738
 
739
+ @app.command(name="post-install")
740
+ def post_install(
741
+ ctx: typer.Context,
742
+ solution: str = typer.Option(..., "--solution", "-s", help="Solution tag"),
743
+ no_prompt: bool = typer.Option(
744
+ False,
745
+ "--no-prompt",
746
+ "-N",
747
+ help="Use saved answers from ~/.cortex/solutions/<tag>.json without prompting.",
748
+ ),
749
+ ):
750
+ """Run post-install setup for a solution."""
751
+ solutions_dir = ctx.obj.get("solutions_dir") if ctx.obj else None
752
+ if solution not in _list_solution_tags(solutions_dir):
753
+ avail = ", ".join(_list_solution_tags(solutions_dir))
754
+ typer.echo(f"Error: Solution '{solution}' not found. Available: {avail}")
755
+ raise typer.Exit(1)
756
+ ctx.obj["client"] = _build_client(ctx)
757
+ _run_post_install_script(solution, solutions_dir=solutions_dir, ctx=ctx, no_prompt=no_prompt)
758
+
759
+
663
760
  @app.command()
664
761
  def uninstall(
665
762
  ctx: typer.Context,
@@ -0,0 +1,183 @@
1
+ import getpass
2
+ import json
3
+ import os
4
+ import sys
5
+ from abc import ABC, abstractmethod
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+
10
+ class SolutionSetup(ABC):
11
+ """
12
+ Base class for solution post-install setup scripts.
13
+ Subclasses define solution_tag, collect_prompts(), and steps().
14
+ """
15
+
16
+ solution_tag: str # must be set by subclass
17
+
18
+ def __init__(self, state_dir: Optional[Path] = None, no_prompt: bool = False):
19
+ self._no_prompt = no_prompt
20
+ self._save_answers = True
21
+ self._secret_keys: set = set()
22
+ self._answers: dict = {}
23
+
24
+ solutions_dir = state_dir or Path.home() / ".cortex" / "solutions"
25
+ solutions_dir.mkdir(parents=True, exist_ok=True)
26
+ self._state_file = solutions_dir / f"{self.solution_tag}.json"
27
+
28
+ data = self._load_file()
29
+ self._answers = data.get("answers", {})
30
+ self._state: dict = data.get("state", {})
31
+
32
+ self._migrate_old_state()
33
+
34
+ def _load_file(self) -> dict:
35
+ if self._state_file.exists():
36
+ try:
37
+ return json.loads(self._state_file.read_text())
38
+ except (json.JSONDecodeError, OSError):
39
+ return {}
40
+ return {}
41
+
42
+ def _save_file(self) -> None:
43
+ saved_answers = (
44
+ {k: v for k, v in self._answers.items() if k not in self._secret_keys}
45
+ if self._save_answers
46
+ else {}
47
+ )
48
+ data = {"answers": saved_answers, "state": self._state}
49
+ self._state_file.write_text(json.dumps(data, indent=2))
50
+
51
+ def _save_state(self) -> None:
52
+ self._save_file()
53
+
54
+ def _migrate_old_state(self) -> None:
55
+ """Move state from the old flat ~/.cortex/setup-<tag>.json into the new file."""
56
+ old_file = Path.home() / ".cortex" / f"setup-{self.solution_tag}.json"
57
+ if not old_file.exists():
58
+ return
59
+ try:
60
+ old_data = json.loads(old_file.read_text())
61
+ if old_data and not self._state:
62
+ self._state.update(old_data)
63
+ self._save_file()
64
+ old_file.unlink()
65
+ except Exception:
66
+ pass
67
+
68
+ def prompt(
69
+ self,
70
+ key: str,
71
+ message: str,
72
+ env_var: Optional[str] = None,
73
+ default: Optional[str] = None,
74
+ secret: bool = False,
75
+ hidden: bool = False,
76
+ ) -> str:
77
+ """Prompt for a value. Uses saved answer or env var when available.
78
+
79
+ secret=True — mask input AND exclude from saved JSON
80
+ hidden=True — mask input only; value is still saved to JSON
81
+ """
82
+ use_getpass = secret or hidden
83
+ if secret:
84
+ self._secret_keys.add(key)
85
+
86
+ # Use saved answer when --no-prompt (works for both plain and hidden keys)
87
+ if self._no_prompt and not secret and key in self._answers:
88
+ return self._answers[key]
89
+
90
+ # Saved answer takes precedence over any derived default
91
+ if not secret and key in self._answers:
92
+ default = self._answers[key]
93
+
94
+ if env_var:
95
+ env_val = os.environ.get(env_var)
96
+ if env_val:
97
+ masked = "********" if use_getpass else env_val
98
+ if self._no_prompt or self.confirm(f"{message} [{masked} from {env_var}]", default=True):
99
+ self._answers[key] = env_val
100
+ return env_val
101
+
102
+ # Secrets in --no-prompt mode still need a prompt if no env var provided
103
+ if self._no_prompt and secret and key not in self._answers:
104
+ print(f" (secret required — no env var set for {key})", file=sys.stderr)
105
+
106
+ if use_getpass and default:
107
+ hint = f"********{default[-4:]}"
108
+ elif default:
109
+ hint = default
110
+ else:
111
+ hint = None
112
+ prompt_str = f"{message} [{hint}]: " if hint else f"{message}: "
113
+
114
+ if use_getpass:
115
+ value = getpass.getpass(prompt_str).strip()
116
+ else:
117
+ value = input(prompt_str).strip()
118
+ if not value:
119
+ value = default or ""
120
+ self._answers[key] = value
121
+ return value
122
+
123
+ def confirm(self, message: str, default: bool = True) -> bool:
124
+ """Y|N confirmation prompt. Auto-accepts default when --no-prompt."""
125
+ if self._no_prompt:
126
+ return default
127
+ hint = "[Y/n]" if default else "[y/N]"
128
+ response = input(f"{message} {hint}: ").strip().lower()
129
+ if not response:
130
+ return default
131
+ return response in ("y", "yes")
132
+
133
+ def already_done(self, key: str) -> bool:
134
+ """Return True if this step was previously completed."""
135
+ return self._state.get(key, False)
136
+
137
+ def mark_done(self, key: str) -> None:
138
+ """Mark a step as completed in the persistent state file."""
139
+ self._state[key] = True
140
+ self._save_file()
141
+
142
+ @abstractmethod
143
+ def collect_prompts(self) -> None:
144
+ """Collect all user inputs upfront before executing steps."""
145
+
146
+ @abstractmethod
147
+ def steps(self) -> list[tuple[str, callable]]:
148
+ """Return ordered list of (label, callable) tuples."""
149
+
150
+ def post_steps(self) -> None:
151
+ """Optional hook called after all steps complete. Override in subclass."""
152
+
153
+ def run(self) -> None:
154
+ """Collect prompts then execute steps with progress display."""
155
+ if self._no_prompt and self._answers:
156
+ saved = {k: v for k, v in self._answers.items() if k not in self._secret_keys}
157
+ if saved:
158
+ print("Using saved configuration:")
159
+ for k, v in saved.items():
160
+ print(f" {k}: {v}")
161
+ print()
162
+ elif not self._no_prompt:
163
+ self._save_answers = self.confirm(f"Save answers for future runs to {self._state_file}", default=True)
164
+
165
+ self.collect_prompts()
166
+ self._save_file()
167
+
168
+ print()
169
+ step_list = self.steps()
170
+ total = len(step_list)
171
+ for i, (label, fn) in enumerate(step_list, 1):
172
+ try:
173
+ detail = fn()
174
+ print(f"[{i}/{total}] {label}... \u2713")
175
+ if detail:
176
+ lines = [detail] if isinstance(detail, str) else detail
177
+ for line in lines:
178
+ print(f" {line}")
179
+ print()
180
+ except Exception as e:
181
+ print(f"[{i}/{total}] {label}... \u2717 {e}", file=sys.stderr)
182
+ raise SystemExit(1)
183
+ self.post_steps()
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: GitHub Actions Deploy Tracking
3
+ description: Track deployments from GitHub Actions in Cortex, with a deploy health scorecard measuring delivery cadence.
4
+ ---
5
+
6
+ # GitHub Actions Deploy Tracking
7
+
8
+ Trigger deploys from Cortex, track them as they run in GitHub Actions, and surface deploy health back in your service catalog.
9
+
10
+ ```
11
+ ┌─────────────────────────────────┐
12
+ │ Cortex Catalog │
13
+ │ │
14
+ │ github-actions-demo (service) │
15
+ │ ├── x-cortex-git.github │
16
+ │ │ repository: owner/repo │
17
+ │ └── Scorecard: Deploy Health │
18
+ │ Bronze / Silver / Gold │
19
+ └──────────────┬──────────────────┘
20
+
21
+ │ Run workflow from entity page
22
+ │ (or: cortex workflows run -t
23
+ │ github-actions-deploy
24
+ │ --scope ENTITY --entity <tag>)
25
+
26
+ ┌─────────────────────────────────┐
27
+ │ Cortex Workflow │
28
+ │ Solution: Add Cortex Deploy │
29
+ │ from GitHub Actions │
30
+ │ │
31
+ │ 1. Read linked repo from │
32
+ │ entity catalog config │
33
+ │ 2. POST workflow_dispatch │
34
+ │ to GitHub Actions │
35
+ │ 3. Wait for callback │
36
+ └──────────────┬──────────────────┘
37
+ │ POST /dispatches (GitHub integration)
38
+
39
+ ┌─────────────────────────────────┐
40
+ │ GitHub Actions │
41
+ │ cortex-deploy.yml │
42
+ │ │
43
+ │ job: build │
44
+ │ └── run your deploy steps │
45
+ │ │
46
+ │ job: cortex-callback │
47
+ │ ├── POST /deploys │◄── registers deploy event
48
+ │ │ (entity: github-actions- │ on the Cortex entity
49
+ │ │ demo) │
50
+ │ └── POST callbackUrl ───────┼──► Cortex marks workflow
51
+ │ status: SUCCESS/FAILURE │ run complete
52
+ └─────────────────────────────────┘
53
+ ```
54
+
55
+ ## What's Included
56
+
57
+ - **Entity:** `github-actions-demo` service — a sample entity to receive deploy events
58
+ - **Scorecard:** Deploy Health — Bronze/Silver/Gold based on deploy frequency
59
+ - **GitHub Actions workflow:** `cortex-deploy.yml` — a two-job workflow (build → notify) seeded into your GitHub repo
60
+ - **Cortex workflow:** `github-actions-deploy` — reads the linked repo from the entity, triggers the GitHub Actions deploy, and waits for the result
61
+ - **Setup script:** Interactive wizard that creates and seeds a GitHub repo end-to-end
62
+
63
+ ## Quick Start
64
+
65
+ 1. Install the solution:
66
+
67
+ ```
68
+ cortex solutions install -s github-actions-deploy
69
+ ```
70
+
71
+ 2. Follow the post-install setup prompts, or run later:
72
+
73
+ ```
74
+ cortex solutions post-install -s github-actions-deploy
75
+ ```
76
+
77
+ ## How It Works
78
+
79
+ The Cortex workflow reads `x-cortex-git.github.repository` from the entity's catalog config to determine which GitHub repo to deploy. It triggers `cortex-deploy.yml` via `workflow_dispatch` and waits asynchronously for a callback.
80
+
81
+ GitHub Actions runs the build, then notifies Cortex twice on completion:
82
+ - **Deploy registration** (`POST /api/v1/catalog/{tag}/deploys`) — records the deploy event on the entity, feeding the Deploy Health scorecard
83
+ - **Workflow callback** — signals the Cortex workflow run as SUCCESS or FAILURE
84
+
85
+ ## After Installing
86
+
87
+ If you ran the post-install setup, you're already done — it created the GitHub repo, seeded the workflow, set secrets, linked the entity, and triggered a test deploy.
88
+
89
+ To roll the pattern out to your own services, add `cortex-deploy.yml` to any GitHub repo and link that repo to its Cortex entity. The same **Solution: Add Cortex Deploy from GitHub Actions** workflow will work across all of them — it reads the linked repo from the entity automatically.
90
+
91
+ ## Customizing for Production
92
+
93
+ - Point the workflow at your real entity by replacing `github-actions-demo` with your service tag
94
+ - Add `CORTEX_API_KEY` and `CORTEX_BASE_URL` secrets to your real repos
95
+ - The Deploy Health scorecard is scoped to `demo-github-actions-deploys` to avoid affecting your
96
+ existing services. To roll it out broadly, remove the group filter from the scorecard.
97
+ To opt in individual services, add the `demo-github-actions-deploys` group to them.