procesio-cli 0.1.1__py3-none-any.whl
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.
- procesio_cli/__init__.py +18 -0
- procesio_cli/_cli.py +71 -0
- procesio_cli/agents/__init__.py +0 -0
- procesio_cli/agents/_lib/__init__.py +0 -0
- procesio_cli/agents/_lib/actiondef.py +24 -0
- procesio_cli/agents/_lib/errors.py +40 -0
- procesio_cli/agents/_lib/reversibility.py +236 -0
- procesio_cli/agents/_lib/toolrunner.py +141 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-PLAYBOOK.md +62 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-PROCESIO-INTEROP.md +76 -0
- procesio_cli/agents/connector-builder/CONNECTOR-BUILDER-TROUBLESHOOTING.md +60 -0
- procesio_cli/agents/connector-builder/README.md +51 -0
- procesio_cli/agents/connector-builder/agent.yaml +61 -0
- procesio_cli/agents/connector-builder/context.py +19 -0
- procesio_cli/agents/connector-builder/handlers/__init__.py +1 -0
- procesio_cli/agents/connector-builder/handlers/checklist.py +71 -0
- procesio_cli/agents/connector-builder/handlers/guidance.py +37 -0
- procesio_cli/agents/connector-builder/handlers/nextstep.py +221 -0
- procesio_cli/agents/connector-builder/handlers/status.py +35 -0
- procesio_cli/agents/connector-builder/knowledge.py +47 -0
- procesio_cli/agents/connector-builder/main.py +93 -0
- procesio_cli/agents/connector-builder/tests/conftest.py +95 -0
- procesio_cli/agents/connector-builder/tests/test_cba_agent.py +148 -0
- procesio_cli/agents/orchestrator/__init__.py +0 -0
- procesio_cli/agents/orchestrator/bootstraplib.py +166 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/00-INDEX.md +60 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/01-PROCESSES.md +230 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/02-FORMS.md +287 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/03-INTEGRATIONS.md +384 -0
- procesio_cli/agents/procesio/IMPLEMENTATION-PATTERNS/04-DATABASE.md +293 -0
- procesio_cli/agents/procesio/PROCESIO-API-RELIABILITY-DOCTRINE.md +113 -0
- procesio_cli/agents/procesio/PROCESIO-BEST-PRACTICES.md +316 -0
- procesio_cli/agents/procesio/PROCESIO-BUILD-AND-TEST-PLAYBOOK.md +217 -0
- procesio_cli/agents/procesio/PROCESIO-DATASTORE.md +72 -0
- procesio_cli/agents/procesio/PROCESIO-ENVIRONMENTS.md +62 -0
- procesio_cli/agents/procesio/PROCESIO-SCHEDULING.md +47 -0
- procesio_cli/agents/procesio/PROCESIO-VISUAL-ORGANIZATION.md +175 -0
- procesio_cli/agents/procesio/README.md +97 -0
- procesio_cli/agents/procesio/__init__.py +0 -0
- procesio_cli/agents/procesio/agent.yaml +73 -0
- procesio_cli/agents/procesio/audit.py +226 -0
- procesio_cli/agents/procesio/checklist.py +135 -0
- procesio_cli/agents/procesio/context.py +19 -0
- procesio_cli/agents/procesio/handlers/__init__.py +0 -0
- procesio_cli/agents/procesio/handlers/audit.py +39 -0
- procesio_cli/agents/procesio/handlers/checklist.py +34 -0
- procesio_cli/agents/procesio/handlers/guidance.py +36 -0
- procesio_cli/agents/procesio/handlers/status.py +33 -0
- procesio_cli/agents/procesio/handlers/verify.py +39 -0
- procesio_cli/agents/procesio/knowledge.py +115 -0
- procesio_cli/agents/procesio/main.py +89 -0
- procesio_cli/agents/procesio/tests/__init__.py +0 -0
- procesio_cli/agents/procesio/tests/conftest.py +62 -0
- procesio_cli/agents/procesio/tests/test_audit.py +91 -0
- procesio_cli/agents/procesio/tests/test_checklist.py +28 -0
- procesio_cli/agents/procesio/tests/test_guidance.py +78 -0
- procesio_cli/agents/procesio/tests/test_manifest_sync.py +48 -0
- procesio_cli/agents/procesio/tests/test_verify.py +134 -0
- procesio_cli/agents/procesio/tests/test_workspace_scope.py +97 -0
- procesio_cli/agents/procesio/verifylib.py +222 -0
- procesio_cli/dashboard/README.md +117 -0
- procesio_cli/dashboard/__init__.py +18 -0
- procesio_cli/dashboard/serve.py +98 -0
- procesio_cli/dashboard/server/__init__.py +1 -0
- procesio_cli/dashboard/server/config_schemas.py +92 -0
- procesio_cli/dashboard/server/google.py +103 -0
- procesio_cli/dashboard/server/handler.py +160 -0
- procesio_cli/dashboard/server/inventory.py +213 -0
- procesio_cli/dashboard/server/jobs.py +118 -0
- procesio_cli/dashboard/server/llmtest.py +181 -0
- procesio_cli/dashboard/server/procesio_env.py +94 -0
- procesio_cli/dashboard/server/routes.py +74 -0
- procesio_cli/dashboard/server/runner.py +150 -0
- procesio_cli/dashboard/server/security.py +29 -0
- procesio_cli/dashboard/server/setup.py +211 -0
- procesio_cli/dashboard/server/validate.py +152 -0
- procesio_cli/dashboard/tests/conftest.py +16 -0
- procesio_cli/dashboard/tests/test_dashboard.py +225 -0
- procesio_cli/dashboard/tests/test_procesio_env.py +159 -0
- procesio_cli/dashboard/web/actions.js +274 -0
- procesio_cli/dashboard/web/api.js +79 -0
- procesio_cli/dashboard/web/app.js +385 -0
- procesio_cli/dashboard/web/copilot.js +72 -0
- procesio_cli/dashboard/web/google.js +121 -0
- procesio_cli/dashboard/web/index.html +67 -0
- procesio_cli/dashboard/web/procesio_env.js +187 -0
- procesio_cli/dashboard/web/styles.css +207 -0
- procesio_cli/registry.py +370 -0
- procesio_cli/scripts/build-router.py +249 -0
- procesio_cli/scripts/build-tool-skill.py +202 -0
- procesio_cli/scripts/check-requirements.py +131 -0
- procesio_cli/scripts/check-skill-governance.py +174 -0
- procesio_cli/scripts/evaluate-skill-routing.py +217 -0
- procesio_cli/scripts/evaluate-skills.py +316 -0
- procesio_cli/scripts/get-skill.py +122 -0
- procesio_cli/scripts/list-skills.py +68 -0
- procesio_cli/scripts/list-tools.py +73 -0
- procesio_cli/scripts/run-agent.py +59 -0
- procesio_cli/scripts/run-tool.py +123 -0
- procesio_cli/scripts/secret_scan.py +584 -0
- procesio_cli/scripts/set-credential.py +126 -0
- procesio_cli/scripts/validate-skills.py +530 -0
- procesio_cli/skills/PORTFOLIO.md +134 -0
- procesio_cli/skills/__init__.py +6 -0
- procesio_cli/skills/agent-skill-engineer/SKILL.md +226 -0
- procesio_cli/skills/agent-skill-engineer/evals/evals.json +422 -0
- procesio_cli/skills/agent-skill-engineer/references/authoring-standard.md +178 -0
- procesio_cli/skills/agent-skill-engineer/references/decision-framework.md +107 -0
- procesio_cli/skills/agent-skill-engineer/references/domain-evidence-protocol.md +248 -0
- procesio_cli/skills/agent-skill-engineer/references/evaluation-standard.md +191 -0
- procesio_cli/skills/agent-skill-engineer/references/expert-lenses.md +167 -0
- procesio_cli/skills/agent-skill-engineer/references/field-gate-standard.md +231 -0
- procesio_cli/skills/agent-skill-engineer/references/optimization-protocol.md +233 -0
- procesio_cli/skills/agent-skill-engineer/references/optimizer-contract.md +268 -0
- procesio_cli/skills/agent-skill-engineer/references/portability-standard.md +99 -0
- procesio_cli/skills/agent-skill-engineer/references/research-basis.md +128 -0
- procesio_cli/skills/agent-skill-engineer/references/review-standard.md +143 -0
- procesio_cli/skills/agent-skill-engineer/scripts/audit_skill.py +959 -0
- procesio_cli/skills/agent-skill-engineer/scripts/optimize_skill.py +891 -0
- procesio_cli/skills/agent-skill-engineer/scripts/scaffold_skill.py +397 -0
- procesio_cli/skills/evals/baseline-catalog.json +14 -0
- procesio_cli/skills/evals/baseline-routing-v2.json +38 -0
- procesio_cli/skills/evals/baseline.json +37 -0
- procesio_cli/skills/evals/behavioral.json +367 -0
- procesio_cli/skills/evals/field-boundary-development-v1.json +126 -0
- procesio_cli/skills/evals/field-closeout-correction-development-v1.json +23 -0
- procesio_cli/skills/evals/field-closeout-development-v1.json +89 -0
- procesio_cli/skills/evals/portfolio-pressure-training-v1.json +83 -0
- procesio_cli/skills/evals/routing.json +363 -0
- procesio_cli/skills/evals/validation-baseline.json +4 -0
- procesio_cli/skills/procesio-cli/SKILL.md +134 -0
- procesio_cli/skills/procesio-cli/evals/evals.json +111 -0
- procesio_cli/skills/procesio-cli/evals/workflows.json +16 -0
- procesio_cli/skills/procesio-cli/references/connector-lifecycle.md +47 -0
- procesio_cli/skills/procesio-cli/references/credentials-admin.md +113 -0
- procesio_cli/skills/procesio-cli/references/data-verification.md +71 -0
- procesio_cli/skills/procesio-cli/references/documents-files.md +44 -0
- procesio_cli/skills/procesio-cli/references/form-e2e.md +57 -0
- procesio_cli/skills/procesio-cli/references/operation-contract.md +138 -0
- procesio_cli/skills/procesio-cli/references/process-debugging.md +65 -0
- procesio_cli/skills/procesio-cli/references/process-lifecycle.md +88 -0
- procesio_cli/skills/procesio-cli/references/schedules-webhooks.md +58 -0
- procesio_cli/skills/procesio-cli/references/transport-environments.md +53 -0
- procesio_cli/skills/procesio-cli-maintainer/SKILL.md +84 -0
- procesio_cli/skills/procesio-cli-maintainer/evals/evals.json +116 -0
- procesio_cli/skills/procesio-cli-maintainer/references/change-contract.md +34 -0
- procesio_cli/skills/procesio-cli-maintainer/references/mcp-compatibility.md +13 -0
- procesio_cli/skills/procesio-cli-maintainer/references/skill-authoring.md +51 -0
- procesio_cli/skills/procesio-platform-advisor/SKILL.md +82 -0
- procesio_cli/skills/procesio-platform-advisor/evals/evals.json +116 -0
- procesio_cli/skills/procesio-platform-advisor/references/capability-map.md +22 -0
- procesio_cli/skills/procesio-platform-advisor/references/fit-framework.md +35 -0
- procesio_cli/skills/procesio-platform-advisor/references/freshness-policy.md +25 -0
- procesio_cli/skills/procesio-platform-advisor/references/sizing-method.md +70 -0
- procesio_cli/skills/procesio-platform-advisor/scripts/estimate_capacity.py +99 -0
- procesio_cli/skills/procesio-setup/SKILL.md +180 -0
- procesio_cli/skills/sql-server-optimizer/SKILL.md +86 -0
- procesio_cli/skills/sql-server-optimizer/evals/evals.json +116 -0
- procesio_cli/skills/sql-server-optimizer/references/db-context.md +44 -0
- procesio_cli/skills/sql-server-optimizer/references/generic-mode.md +32 -0
- procesio_cli/skills/sql-server-optimizer/references/procesio-mode.md +40 -0
- procesio_cli/skills/sql-server-optimizer/references/sql-rules.md +43 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-indexes.sql +178 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-procs-and-functions.sql +118 -0
- procesio_cli/skills/sql-server-optimizer/scripts/export-tables.sql +367 -0
- procesio_cli/skills/tests/__init__.py +0 -0
- procesio_cli/skills/tests/test_agent_skill_engineer.py +240 -0
- procesio_cli/skills/tests/test_auth_failure_stop.py +25 -0
- procesio_cli/skills/tests/test_capacity_estimator.py +85 -0
- procesio_cli/skills/tests/test_field_boundary_learning.py +100 -0
- procesio_cli/skills/tests/test_field_closeout_learning.py +123 -0
- procesio_cli/skills/tests/test_field_gate_standard.py +76 -0
- procesio_cli/skills/tests/test_operational_playbooks.py +51 -0
- procesio_cli/skills/tests/test_portfolio_pressure_training.py +35 -0
- procesio_cli/skills/tests/test_process_lifecycle_single_run.py +25 -0
- procesio_cli/skills/tests/test_skill_evaluator.py +67 -0
- procesio_cli/skills/tests/test_skill_optimizer.py +473 -0
- procesio_cli/skills/tests/test_skill_portfolio_review.py +70 -0
- procesio_cli/skills/tests/test_skill_release_binding.py +204 -0
- procesio_cli/skills/tests/test_skill_routing.py +74 -0
- procesio_cli/skills/tests/test_skill_scaffold_v2.py +116 -0
- procesio_cli/skills/tests/test_validate_skills.py +115 -0
- procesio_cli/tools/__init__.py +0 -0
- procesio_cli/tools/_lib/README.md +119 -0
- procesio_cli/tools/_lib/__init__.py +0 -0
- procesio_cli/tools/_lib/accounting.py +120 -0
- procesio_cli/tools/_lib/contacts.py +544 -0
- procesio_cli/tools/_lib/creds.py +83 -0
- procesio_cli/tools/_lib/creds_backends.py +578 -0
- procesio_cli/tools/_lib/dbprofiles.py +147 -0
- procesio_cli/tools/_lib/dbquery.py +286 -0
- procesio_cli/tools/_lib/dbschema.py +225 -0
- procesio_cli/tools/_lib/io.py +54 -0
- procesio_cli/tools/_lib/manifest.py +297 -0
- procesio_cli/tools/_lib/skill_release.py +127 -0
- procesio_cli/tools/_lib/skill_resources.py +118 -0
- procesio_cli/tools/_lib/userdata.py +228 -0
- procesio_cli/tools/_lib/webserialize/__init__.py +16 -0
- procesio_cli/tools/_lib/webserialize/admin.py +61 -0
- procesio_cli/tools/_lib/webserialize/client.py +264 -0
- procesio_cli/tools/_lib/webserialize/config.py +96 -0
- procesio_cli/tools/_lib/webserialize/errors.py +27 -0
- procesio_cli/tools/_lib/webserialize/framing.py +45 -0
- procesio_cli/tools/_lib/webserialize/registry.py +47 -0
- procesio_cli/tools/_lib/webserialize/runtime.py +61 -0
- procesio_cli/tools/_lib/webserialize/server.py +510 -0
- procesio_cli/tools/_lib/websession.py +194 -0
- procesio_cli/tools/connector-builder/CONNECTOR-BUILDER-API-NOTES.md +91 -0
- procesio_cli/tools/connector-builder/README.md +101 -0
- procesio_cli/tools/connector-builder/SKILL.md +248 -0
- procesio_cli/tools/connector-builder/__init__.py +7 -0
- procesio_cli/tools/connector-builder/actiondef.py +24 -0
- procesio_cli/tools/connector-builder/client.py +251 -0
- procesio_cli/tools/connector-builder/errors.py +44 -0
- procesio_cli/tools/connector-builder/gen_manifest.py +142 -0
- procesio_cli/tools/connector-builder/handlers/__init__.py +1 -0
- procesio_cli/tools/connector-builder/handlers/admin.py +182 -0
- procesio_cli/tools/connector-builder/handlers/auth.py +74 -0
- procesio_cli/tools/connector-builder/handlers/builds.py +396 -0
- procesio_cli/tools/connector-builder/handlers/common.py +104 -0
- procesio_cli/tools/connector-builder/handlers/files.py +111 -0
- procesio_cli/tools/connector-builder/handlers/raw.py +38 -0
- procesio_cli/tools/connector-builder/handlers/telemetry.py +39 -0
- procesio_cli/tools/connector-builder/main.py +94 -0
- procesio_cli/tools/connector-builder/tests/conftest.py +146 -0
- procesio_cli/tools/connector-builder/tests/test_cb_client.py +86 -0
- procesio_cli/tools/connector-builder/tests/test_cb_dispatch.py +105 -0
- procesio_cli/tools/connector-builder/tests/test_cb_manifest_sync.py +76 -0
- procesio_cli/tools/connector-builder/tests/test_cb_wait.py +123 -0
- procesio_cli/tools/connector-builder/tool.yaml +671 -0
- procesio_cli/tools/framework-map/.gitattributes +2 -0
- procesio_cli/tools/framework-map/README.md +98 -0
- procesio_cli/tools/framework-map/SKILL.md +30 -0
- procesio_cli/tools/framework-map/app.js +188 -0
- procesio_cli/tools/framework-map/fm_builder.py +476 -0
- procesio_cli/tools/framework-map/fm_catalog.py +19 -0
- procesio_cli/tools/framework-map/fm_ro_catalog.py +94 -0
- procesio_cli/tools/framework-map/fm_strings.py +101 -0
- procesio_cli/tools/framework-map/main.py +95 -0
- procesio_cli/tools/framework-map/styles.css +214 -0
- procesio_cli/tools/framework-map/tests/conftest.py +33 -0
- procesio_cli/tools/framework-map/tests/test_fm_build.py +126 -0
- procesio_cli/tools/framework-map/tests/test_fm_manifest_sync.py +63 -0
- procesio_cli/tools/framework-map/tests/test_fm_store_tree.py +75 -0
- procesio_cli/tools/framework-map/tool.yaml +61 -0
- procesio_cli/tools/mysql/MOVED-TO-CENTRAL.md +29 -0
- procesio_cli/tools/mysql/README.md +196 -0
- procesio_cli/tools/mysql/SKILL.md +37 -0
- procesio_cli/tools/mysql/__init__.py +0 -0
- procesio_cli/tools/mysql/connection.py +191 -0
- procesio_cli/tools/mysql/main.py +487 -0
- procesio_cli/tools/mysql/profiles.template.json +1 -0
- procesio_cli/tools/mysql/schema.py +434 -0
- procesio_cli/tools/mysql/tests/__init__.py +0 -0
- procesio_cli/tools/mysql/tests/conftest.py +21 -0
- procesio_cli/tools/mysql/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/mysql/tests/test_mysql.py +201 -0
- procesio_cli/tools/mysql/tests/test_mysql_schema.py +337 -0
- procesio_cli/tools/mysql/tool.yaml +112 -0
- procesio_cli/tools/procesio/DTO-SUBTOOLS-NOTE.md +360 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/00-INDEX.md +67 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/01-ANATOMY.md +209 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/02-CODE-INJECTION.md +309 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/03-DOM-CONTRACT.md +370 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/04-INTERACTION-RECIPES.md +600 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/05-STEPPER-AND-MOTION.md +426 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/06-PROCESS-INTEGRATION.md +347 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/07-DEPLOY-WORKFLOW.md +294 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/08-PITFALLS.md +569 -0
- procesio_cli/tools/procesio/FORM-DEV-GUIDE/09-NAVIGATION-AND-HOSTING.md +308 -0
- procesio_cli/tools/procesio/PHASE4-E2E-NOTES.md +594 -0
- procesio_cli/tools/procesio/PROCESIO-API-CORRECTIONS.md +295 -0
- procesio_cli/tools/procesio/PROCESIO-API-NOTES.md +6976 -0
- procesio_cli/tools/procesio/PROCESIO-AUTH-NOTES.md +226 -0
- procesio_cli/tools/procesio/PROCESIO-CARD-BUILD-NOTES.md +183 -0
- procesio_cli/tools/procesio/PROCESIO-CONNECTORS-NOTES.md +141 -0
- procesio_cli/tools/procesio/PROCESIO-CUSTOM-ACTION-NOTES.md +179 -0
- procesio_cli/tools/procesio/PROCESIO-DOCS-FIX-REPORT.md +123 -0
- procesio_cli/tools/procesio/PROCESIO-ENVIRONMENTS-NOTES.md +94 -0
- procesio_cli/tools/procesio/PROCESIO-FE-VALIDATION-NOTES.md +107 -0
- procesio_cli/tools/procesio/PROCESIO-FOREACH-NOTES.md +199 -0
- procesio_cli/tools/procesio/PROCESIO-FORM-API-HANG-NOTE.md +101 -0
- procesio_cli/tools/procesio/PROCESIO-FORM-SUBMISSION-NOTES.md +172 -0
- procesio_cli/tools/procesio/PROCESIO-HTML-TO-DOCX-NOTES.md +110 -0
- procesio_cli/tools/procesio/PROCESIO-INSTANCE-HISTORY-NOTES.md +242 -0
- procesio_cli/tools/procesio/PROCESIO-METERING-NOTES.md +201 -0
- procesio_cli/tools/procesio/PROCESIO-NODE-CODE-NOTES.md +279 -0
- procesio_cli/tools/procesio/PROCESIO-NODE-MODULE-WHITELIST.md +113 -0
- procesio_cli/tools/procesio/PROCESIO-PYTHON-ACTION-NOTES.md +165 -0
- procesio_cli/tools/procesio/PROCESIO-QUERY-STORE-NOTES.md +826 -0
- procesio_cli/tools/procesio/PROCESIO-RECONCILIATION-PATTERNS.md +104 -0
- procesio_cli/tools/procesio/PROCESIO-RESOURCE-MODEL-NOTES.md +1100 -0
- procesio_cli/tools/procesio/PROCESIO-SEND-EMAIL-CRED-OPTIONALITY.md +45 -0
- procesio_cli/tools/procesio/PROCESIO-SEND-EMAIL-NOTES.md +252 -0
- procesio_cli/tools/procesio/PROCESIO-SQL-ACTIONS-NOTES.md +284 -0
- procesio_cli/tools/procesio/PROCESIO-USAGE-GUIDE.md +219 -0
- procesio_cli/tools/procesio/README.md +341 -0
- procesio_cli/tools/procesio/SCHEDULE-INPUT-SECURITY-NOTES.md +63 -0
- procesio_cli/tools/procesio/SKILL.md +694 -0
- procesio_cli/tools/procesio/__init__.py +1 -0
- procesio_cli/tools/procesio/actiondef.py +27 -0
- procesio_cli/tools/procesio/assets/html2docx/html2docx.js +2155 -0
- procesio_cli/tools/procesio/auth.py +354 -0
- procesio_cli/tools/procesio/autogen.py +121 -0
- procesio_cli/tools/procesio/client.py +355 -0
- procesio_cli/tools/procesio/config.py +96 -0
- procesio_cli/tools/procesio/data/endpoints.json +2901 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/01-authentication.md +180 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/02-conventions.md +116 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/ANONYMOUS-SURFACE.md +51 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/README.md +104 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/SYMBOL-INDEX.md +444 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/03-auth-users-workspaces.md +731 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/04-processes.md +913 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/05-actions.md +605 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/06-files.md +237 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/07-forms-documents.md +985 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/08-credentials.md +532 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/09-datastore-datatypes.md +830 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/10-custom-urls.md +319 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/11-subscriptions-resources-analytics.md +455 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/12-webhooks-notifications-misc.md +519 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/13-realtime-signalr.md +153 -0
- procesio_cli/tools/procesio/docs_info/API-DOCUMENTATION/endpoints/99-internal-and-other-services.md +52 -0
- procesio_cli/tools/procesio/docs_info/FormBuilder mock.ts +5725 -0
- procesio_cli/tools/procesio/docs_info/Procesio Platform Actions.json +30219 -0
- procesio_cli/tools/procesio/docs_info/form-config-enums.ts +92 -0
- procesio_cli/tools/procesio/docs_info/form-theme.ts +1603 -0
- procesio_cli/tools/procesio/docs_info/process-validation-reference-1.md +2739 -0
- procesio_cli/tools/procesio/dto/__init__.py +13 -0
- procesio_cli/tools/procesio/dto/credential/builder.py +179 -0
- procesio_cli/tools/procesio/dto/credential/config.schema.json +19 -0
- procesio_cli/tools/procesio/dto/credential/description.md +193 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.config.json +11 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.dto.json +28 -0
- procesio_cli/tools/procesio/dto/credential/fixtures/rest.template.json +44 -0
- procesio_cli/tools/procesio/dto/credential/template.dto.json +16 -0
- procesio_cli/tools/procesio/dto/data/_refresh.py +147 -0
- procesio_cli/tools/procesio/dto/data/action_catalog.json +30825 -0
- procesio_cli/tools/procesio/dto/data/action_families.json +55 -0
- procesio_cli/tools/procesio/dto/data/platform_types.json +182 -0
- procesio_cli/tools/procesio/dto/datatype/builder.py +223 -0
- procesio_cli/tools/procesio/dto/datatype/config.schema.json +47 -0
- procesio_cli/tools/procesio/dto/datatype/description.md +85 -0
- procesio_cli/tools/procesio/dto/datatype/fixtures/customer.config.json +41 -0
- procesio_cli/tools/procesio/dto/datatype/fixtures/customer.dto.json +164 -0
- procesio_cli/tools/procesio/dto/datatype/template.dto.json +37 -0
- procesio_cli/tools/procesio/dto/document/builder.py +193 -0
- procesio_cli/tools/procesio/dto/document/config.schema.json +33 -0
- procesio_cli/tools/procesio/dto/document/description.md +124 -0
- procesio_cli/tools/procesio/dto/document/fixtures/invoice.config.json +16 -0
- procesio_cli/tools/procesio/dto/document/fixtures/invoice.dto.json +32 -0
- procesio_cli/tools/procesio/dto/document/template.dto.json +14 -0
- procesio_cli/tools/procesio/dto/form/FORM-STYLING-NOTES.md +142 -0
- procesio_cli/tools/procesio/dto/form/GOLDEN-DRIFT-NOTES.md +72 -0
- procesio_cli/tools/procesio/dto/form/RUN-PROCESS-RESULT-NOTES.md +80 -0
- procesio_cli/tools/procesio/dto/form/addelement.py +277 -0
- procesio_cli/tools/procesio/dto/form/builder.py +1009 -0
- procesio_cli/tools/procesio/dto/form/capture_goldens.py +106 -0
- procesio_cli/tools/procesio/dto/form/code_cipher.py +67 -0
- procesio_cli/tools/procesio/dto/form/config.schema.json +76 -0
- procesio_cli/tools/procesio/dto/form/data_shell.json +1 -0
- procesio_cli/tools/procesio/dto/form/description.md +346 -0
- procesio_cli/tools/procesio/dto/form/elements/approval.json +259 -0
- procesio_cli/tools/procesio/dto/form/elements/assignee.json +137 -0
- procesio_cli/tools/procesio/dto/form/elements/button.json +125 -0
- procesio_cli/tools/procesio/dto/form/elements/chart.json +341 -0
- procesio_cli/tools/procesio/dto/form/elements/chat.json +199 -0
- procesio_cli/tools/procesio/dto/form/elements/checkbox.json +153 -0
- procesio_cli/tools/procesio/dto/form/elements/column.json +77 -0
- procesio_cli/tools/procesio/dto/form/elements/columns.json +78 -0
- procesio_cli/tools/procesio/dto/form/elements/datetime-input.json +304 -0
- procesio_cli/tools/procesio/dto/form/elements/divider.json +54 -0
- procesio_cli/tools/procesio/dto/form/elements/dropdown.json +130 -0
- procesio_cli/tools/procesio/dto/form/elements/dynamic-table-row.json +253 -0
- procesio_cli/tools/procesio/dto/form/elements/file-upload.json +196 -0
- procesio_cli/tools/procesio/dto/form/elements/file-viewer.json +169 -0
- procesio_cli/tools/procesio/dto/form/elements/heading.json +79 -0
- procesio_cli/tools/procesio/dto/form/elements/icon.json +107 -0
- procesio_cli/tools/procesio/dto/form/elements/image.json +174 -0
- procesio_cli/tools/procesio/dto/form/elements/input.json +206 -0
- procesio_cli/tools/procesio/dto/form/elements/list.json +202 -0
- procesio_cli/tools/procesio/dto/form/elements/number-input.json +202 -0
- procesio_cli/tools/procesio/dto/form/elements/paragraph.json +79 -0
- procesio_cli/tools/procesio/dto/form/elements/radiobox.json +174 -0
- procesio_cli/tools/procesio/dto/form/elements/section.json +114 -0
- procesio_cli/tools/procesio/dto/form/elements/select.json +217 -0
- procesio_cli/tools/procesio/dto/form/elements/side-panel.json +146 -0
- procesio_cli/tools/procesio/dto/form/elements/signature-pad.json +141 -0
- procesio_cli/tools/procesio/dto/form/elements/static-table-row.json +84 -0
- procesio_cli/tools/procesio/dto/form/elements/step.json +105 -0
- procesio_cli/tools/procesio/dto/form/elements/stepper.json +126 -0
- procesio_cli/tools/procesio/dto/form/elements/tab.json +77 -0
- procesio_cli/tools/procesio/dto/form/elements/table.json +279 -0
- procesio_cli/tools/procesio/dto/form/elements/tabs.json +114 -0
- procesio_cli/tools/procesio/dto/form/elements/textarea.json +213 -0
- procesio_cli/tools/procesio/dto/form/enum_map.json +68 -0
- procesio_cli/tools/procesio/dto/form/fieldpath.py +284 -0
- procesio_cli/tools/procesio/dto/form/fixtures/reg.config.json +32 -0
- procesio_cli/tools/procesio/dto/form/fixtures/reg.elements.json +638 -0
- procesio_cli/tools/procesio/dto/form/sync_from_mock.py +386 -0
- procesio_cli/tools/procesio/dto/framework.py +125 -0
- procesio_cli/tools/procesio/dto/process/builder.py +1991 -0
- procesio_cli/tools/procesio/dto/process/config.schema.json +159 -0
- procesio_cli/tools/procesio/dto/process/description.md +218 -0
- procesio_cli/tools/procesio/dto/process/fixtures/greet.config.json +32 -0
- procesio_cli/tools/procesio/dto/process/fixtures/greet.dto.json +317 -0
- procesio_cli/tools/procesio/dto/process/naming.py +305 -0
- procesio_cli/tools/procesio/dto/process/normalize.py +74 -0
- procesio_cli/tools/procesio/dto/process/template.dto.json +32 -0
- procesio_cli/tools/procesio/dto/refdata.py +87 -0
- procesio_cli/tools/procesio/dto/registry.py +27 -0
- procesio_cli/tools/procesio/dto/webhook/builder.py +142 -0
- procesio_cli/tools/procesio/dto/webhook/config.schema.json +25 -0
- procesio_cli/tools/procesio/dto/webhook/description.md +92 -0
- procesio_cli/tools/procesio/dto/webhook/fixtures/order.config.json +10 -0
- procesio_cli/tools/procesio/dto/webhook/fixtures/order.dto.json +25 -0
- procesio_cli/tools/procesio/dto/webhook/template.dto.json +14 -0
- procesio_cli/tools/procesio/environments.py +264 -0
- procesio_cli/tools/procesio/errors.py +166 -0
- procesio_cli/tools/procesio/flowmodel/README.md +37 -0
- procesio_cli/tools/procesio/flowmodel/__init__.py +14 -0
- procesio_cli/tools/procesio/flowmodel/digest.py +223 -0
- procesio_cli/tools/procesio/flowmodel/families.py +34 -0
- procesio_cli/tools/procesio/flowmodel/fevalidation.py +1098 -0
- procesio_cli/tools/procesio/flowmodel/formdigest.py +232 -0
- procesio_cli/tools/procesio/flowmodel/inspect.py +78 -0
- procesio_cli/tools/procesio/flowmodel/model.py +82 -0
- procesio_cli/tools/procesio/flowmodel/nodeparam.py +871 -0
- procesio_cli/tools/procesio/flowmodel/reader.py +191 -0
- procesio_cli/tools/procesio/flowmodel/sqlparam.py +410 -0
- procesio_cli/tools/procesio/form_code_keys.py +50 -0
- procesio_cli/tools/procesio/formlint.py +292 -0
- procesio_cli/tools/procesio/gen_manifest.py +118 -0
- procesio_cli/tools/procesio/handlers/__init__.py +1 -0
- procesio_cli/tools/procesio/handlers/auth_actions.py +103 -0
- procesio_cli/tools/procesio/handlers/catalog.py +147 -0
- procesio_cli/tools/procesio/handlers/common.py +90 -0
- procesio_cli/tools/procesio/handlers/customaction_ops.py +146 -0
- procesio_cli/tools/procesio/handlers/datastore_ops.py +482 -0
- procesio_cli/tools/procesio/handlers/datatype_ops.py +275 -0
- procesio_cli/tools/procesio/handlers/dto_actions.py +258 -0
- procesio_cli/tools/procesio/handlers/environment_admin.py +146 -0
- procesio_cli/tools/procesio/handlers/fevalidate.py +236 -0
- procesio_cli/tools/procesio/handlers/files.py +141 -0
- procesio_cli/tools/procesio/handlers/flowgraph.py +229 -0
- procesio_cli/tools/procesio/handlers/flowlint.py +269 -0
- procesio_cli/tools/procesio/handlers/form_chains.py +336 -0
- procesio_cli/tools/procesio/handlers/form_code.py +277 -0
- procesio_cli/tools/procesio/handlers/form_element.py +199 -0
- procesio_cli/tools/procesio/handlers/form_events.py +441 -0
- procesio_cli/tools/procesio/handlers/form_update.py +270 -0
- procesio_cli/tools/procesio/handlers/formprocess.py +211 -0
- procesio_cli/tools/procesio/handlers/generic.py +135 -0
- procesio_cli/tools/procesio/handlers/layout.py +137 -0
- procesio_cli/tools/procesio/handlers/nodeparams.py +1018 -0
- procesio_cli/tools/procesio/handlers/process_layout.py +189 -0
- procesio_cli/tools/procesio/handlers/process_naming.py +213 -0
- procesio_cli/tools/procesio/handlers/processes.py +416 -0
- procesio_cli/tools/procesio/handlers/profile_admin.py +184 -0
- procesio_cli/tools/procesio/handlers/resource_ops.py +447 -0
- procesio_cli/tools/procesio/handlers/schedules.py +320 -0
- procesio_cli/tools/procesio/handlers/sqlactions.py +232 -0
- procesio_cli/tools/procesio/handlers/transport.py +463 -0
- procesio_cli/tools/procesio/handlers/usageguide.py +222 -0
- procesio_cli/tools/procesio/layout/README.md +196 -0
- procesio_cli/tools/procesio/layout/__init__.py +11 -0
- procesio_cli/tools/procesio/layout/adapter.py +736 -0
- procesio_cli/tools/procesio/layout/cycles.py +186 -0
- procesio_cli/tools/procesio/layout/dispatch.py +39 -0
- procesio_cli/tools/procesio/layout/elk/elk_runner.mjs +50 -0
- procesio_cli/tools/procesio/layout/elk/package-lock.json +21 -0
- procesio_cli/tools/procesio/layout/elk/package.json +11 -0
- procesio_cli/tools/procesio/layout/elk_engine.py +568 -0
- procesio_cli/tools/procesio/layout/engine.py +1311 -0
- procesio_cli/tools/procesio/layout/report.py +187 -0
- procesio_cli/tools/procesio/main.py +179 -0
- procesio_cli/tools/procesio/profiles.py +193 -0
- procesio_cli/tools/procesio/reliability.py +193 -0
- procesio_cli/tools/procesio/sqlformat.py +167 -0
- procesio_cli/tools/procesio/swagger.py +51 -0
- procesio_cli/tools/procesio/tests/__init__.py +0 -0
- procesio_cli/tools/procesio/tests/conftest.py +129 -0
- procesio_cli/tools/procesio/tests/test_auth.py +200 -0
- procesio_cli/tools/procesio/tests/test_autogen.py +78 -0
- procesio_cli/tools/procesio/tests/test_builder_attrpath_sqlbind.py +180 -0
- procesio_cli/tools/procesio/tests/test_builder_custom_action_catalog.py +72 -0
- procesio_cli/tools/procesio/tests/test_client.py +114 -0
- procesio_cli/tools/procesio/tests/test_client_reliability.py +139 -0
- procesio_cli/tools/procesio/tests/test_customaction.py +103 -0
- procesio_cli/tools/procesio/tests/test_customaction_test.py +80 -0
- procesio_cli/tools/procesio/tests/test_cycles.py +69 -0
- procesio_cli/tools/procesio/tests/test_datastore.py +378 -0
- procesio_cli/tools/procesio/tests/test_datastore_node.py +94 -0
- procesio_cli/tools/procesio/tests/test_dispatch.py +170 -0
- procesio_cli/tools/procesio/tests/test_document_page_size.py +19 -0
- procesio_cli/tools/procesio/tests/test_dto.py +734 -0
- procesio_cli/tools/procesio/tests/test_edit_dryrun_preview.py +96 -0
- procesio_cli/tools/procesio/tests/test_elk_layout.py +221 -0
- procesio_cli/tools/procesio/tests/test_environments.py +214 -0
- procesio_cli/tools/procesio/tests/test_fevalidate_gate.py +104 -0
- procesio_cli/tools/procesio/tests/test_fevalidation.py +317 -0
- procesio_cli/tools/procesio/tests/test_fieldpath_variables.py +87 -0
- procesio_cli/tools/procesio/tests/test_files.py +72 -0
- procesio_cli/tools/procesio/tests/test_flow_digest.py +202 -0
- procesio_cli/tools/procesio/tests/test_flowlint.py +114 -0
- procesio_cli/tools/procesio/tests/test_flowmodel.py +158 -0
- procesio_cli/tools/procesio/tests/test_form_add_element.py +256 -0
- procesio_cli/tools/procesio/tests/test_form_approval_gaps.py +319 -0
- procesio_cli/tools/procesio/tests/test_form_auto_names.py +60 -0
- procesio_cli/tools/procesio/tests/test_form_chains.py +217 -0
- procesio_cli/tools/procesio/tests/test_form_code_actions.py +188 -0
- procesio_cli/tools/procesio/tests/test_form_code_keys.py +92 -0
- procesio_cli/tools/procesio/tests/test_form_code_style.py +178 -0
- procesio_cli/tools/procesio/tests/test_form_delete_element.py +99 -0
- procesio_cli/tools/procesio/tests/test_form_element_config.py +141 -0
- procesio_cli/tools/procesio/tests/test_form_event_maps.py +135 -0
- procesio_cli/tools/procesio/tests/test_form_event_ops.py +376 -0
- procesio_cli/tools/procesio/tests/test_form_event_replace.py +86 -0
- procesio_cli/tools/procesio/tests/test_form_events.py +92 -0
- procesio_cli/tools/procesio/tests/test_form_parity.py +171 -0
- procesio_cli/tools/procesio/tests/test_form_select_source.py +74 -0
- procesio_cli/tools/procesio/tests/test_form_sync.py +35 -0
- procesio_cli/tools/procesio/tests/test_form_update.py +133 -0
- procesio_cli/tools/procesio/tests/test_formlint.py +155 -0
- procesio_cli/tools/procesio/tests/test_formlint_css_scope.py +38 -0
- procesio_cli/tools/procesio/tests/test_formlint_field_names.py +83 -0
- procesio_cli/tools/procesio/tests/test_guards.py +110 -0
- procesio_cli/tools/procesio/tests/test_hidden_setting_types.py +39 -0
- procesio_cli/tools/procesio/tests/test_html2docx_asset.py +132 -0
- procesio_cli/tools/procesio/tests/test_key_handle_terminology.py +45 -0
- procesio_cli/tools/procesio/tests/test_layout.py +574 -0
- procesio_cli/tools/procesio/tests/test_layout_report.py +88 -0
- procesio_cli/tools/procesio/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/procesio/tests/test_nodedelete.py +151 -0
- procesio_cli/tools/procesio/tests/test_nodeparam.py +826 -0
- procesio_cli/tools/procesio/tests/test_normalize.py +70 -0
- procesio_cli/tools/procesio/tests/test_payload_docs.py +86 -0
- procesio_cli/tools/procesio/tests/test_process_action_naming.py +170 -0
- procesio_cli/tools/procesio/tests/test_process_call_api_seed.py +46 -0
- procesio_cli/tools/procesio/tests/test_process_ext.py +373 -0
- procesio_cli/tools/procesio/tests/test_process_invariants.py +183 -0
- procesio_cli/tools/procesio/tests/test_process_layout.py +117 -0
- procesio_cli/tools/procesio/tests/test_process_naming.py +188 -0
- procesio_cli/tools/procesio/tests/test_process_query_store.py +172 -0
- procesio_cli/tools/procesio/tests/test_processinfo_variable.py +56 -0
- procesio_cli/tools/procesio/tests/test_profiles.py +127 -0
- procesio_cli/tools/procesio/tests/test_put_projects.py +41 -0
- procesio_cli/tools/procesio/tests/test_reliability.py +107 -0
- procesio_cli/tools/procesio/tests/test_removal_gate.py +95 -0
- procesio_cli/tools/procesio/tests/test_resource_ops.py +173 -0
- procesio_cli/tools/procesio/tests/test_save_validity.py +207 -0
- procesio_cli/tools/procesio/tests/test_schedule_cron_shape.py +111 -0
- procesio_cli/tools/procesio/tests/test_schedules.py +309 -0
- procesio_cli/tools/procesio/tests/test_sqlconvert.py +234 -0
- procesio_cli/tools/procesio/tests/test_sqlformat.py +83 -0
- procesio_cli/tools/procesio/tests/test_sqlparam.py +187 -0
- procesio_cli/tools/procesio/tests/test_superseded_actions.py +54 -0
- procesio_cli/tools/procesio/tests/test_surgical_concurrency.py +156 -0
- procesio_cli/tools/procesio/tests/test_tier4.py +123 -0
- procesio_cli/tools/procesio/tests/test_transport.py +147 -0
- procesio_cli/tools/procesio/tests/test_transport_data_stores.py +168 -0
- procesio_cli/tools/procesio/tests/test_transport_mapper_repair.py +150 -0
- procesio_cli/tools/procesio/tests/test_transport_webhook_strip.py +253 -0
- procesio_cli/tools/procesio/tests/test_usage_guide.py +96 -0
- procesio_cli/tools/procesio/tool.yaml +12486 -0
- procesio_cli/tools/sqlserver/MOVED-TO-CENTRAL.md +29 -0
- procesio_cli/tools/sqlserver/README.md +192 -0
- procesio_cli/tools/sqlserver/SKILL.md +37 -0
- procesio_cli/tools/sqlserver/SQLSERVER-SCHEMA-NOTES.md +284 -0
- procesio_cli/tools/sqlserver/__init__.py +0 -0
- procesio_cli/tools/sqlserver/connection.py +242 -0
- procesio_cli/tools/sqlserver/main.py +521 -0
- procesio_cli/tools/sqlserver/profiles.template.json +1 -0
- procesio_cli/tools/sqlserver/schema.py +600 -0
- procesio_cli/tools/sqlserver/tests/__init__.py +0 -0
- procesio_cli/tools/sqlserver/tests/conftest.py +21 -0
- procesio_cli/tools/sqlserver/tests/test_connection.py +75 -0
- procesio_cli/tools/sqlserver/tests/test_dbprofiles.py +92 -0
- procesio_cli/tools/sqlserver/tests/test_dbquery.py +220 -0
- procesio_cli/tools/sqlserver/tests/test_manifest_sync.py +68 -0
- procesio_cli/tools/sqlserver/tests/test_sqlserver.py +326 -0
- procesio_cli/tools/sqlserver/tests/test_sqlserver_schema.py +488 -0
- procesio_cli/tools/sqlserver/tool.yaml +123 -0
- procesio_cli/tools/web/README.md +381 -0
- procesio_cli/tools/web/SKILL.md +37 -0
- procesio_cli/tools/web/WEB-AUTOMATION-NOTES.md +277 -0
- procesio_cli/tools/web/__init__.py +21 -0
- procesio_cli/tools/web/actiondef.py +24 -0
- procesio_cli/tools/web/api.py +444 -0
- procesio_cli/tools/web/brokered.py +107 -0
- procesio_cli/tools/web/driver.py +839 -0
- procesio_cli/tools/web/drivercontract.py +44 -0
- procesio_cli/tools/web/errors.py +54 -0
- procesio_cli/tools/web/handlers/__init__.py +1 -0
- procesio_cli/tools/web/handlers/archive.py +367 -0
- procesio_cli/tools/web/handlers/browse.py +182 -0
- procesio_cli/tools/web/handlers/contacts.py +74 -0
- procesio_cli/tools/web/handlers/sessmgmt.py +120 -0
- procesio_cli/tools/web/main.py +128 -0
- procesio_cli/tools/web/sessions.py +253 -0
- procesio_cli/tools/web/steps.py +162 -0
- procesio_cli/tools/web/tests/__init__.py +0 -0
- procesio_cli/tools/web/tests/conftest.py +197 -0
- procesio_cli/tools/web/tests/test_api.py +330 -0
- procesio_cli/tools/web/tests/test_archive.py +190 -0
- procesio_cli/tools/web/tests/test_brokered.py +138 -0
- procesio_cli/tools/web/tests/test_dispatch.py +132 -0
- procesio_cli/tools/web/tests/test_errors.py +60 -0
- procesio_cli/tools/web/tests/test_manifest_sync.py +76 -0
- procesio_cli/tools/web/tests/test_page_contacts.py +68 -0
- procesio_cli/tools/web/tests/test_sessions.py +145 -0
- procesio_cli/tools/web/tests/test_steps.py +210 -0
- procesio_cli/tools/web/tool.yaml +184 -0
- procesio_cli/tools/xlsx/README.md +34 -0
- procesio_cli/tools/xlsx/SKILL.md +28 -0
- procesio_cli/tools/xlsx/__init__.py +0 -0
- procesio_cli/tools/xlsx/main.py +129 -0
- procesio_cli/tools/xlsx/reader.py +91 -0
- procesio_cli/tools/xlsx/tests/__init__.py +0 -0
- procesio_cli/tools/xlsx/tests/conftest.py +39 -0
- procesio_cli/tools/xlsx/tests/test_xlsx.py +103 -0
- procesio_cli/tools/xlsx/tool.yaml +30 -0
- procesio_cli/webplatform/aat_mcp/AAT-MCP-NOTES.md +244 -0
- procesio_cli/webplatform/aat_mcp/README.md +78 -0
- procesio_cli/webplatform/aat_mcp/bridge.py +705 -0
- procesio_cli/webplatform/aat_mcp/chat_server.py +194 -0
- procesio_cli/webplatform/aat_mcp/chat_surface.snapshot.json +668 -0
- procesio_cli/webplatform/aat_mcp/chat_surface.yaml +372 -0
- procesio_cli/webplatform/aat_mcp/gate.py +68 -0
- procesio_cli/webplatform/aat_mcp/http_server.py +139 -0
- procesio_cli/webplatform/aat_mcp/protocol.py +175 -0
- procesio_cli/webplatform/aat_mcp/server.py +306 -0
- procesio_cli/webplatform/aat_mcp/tests/conftest.py +8 -0
- procesio_cli/webplatform/aat_mcp/tests/test_aat_mcp.py +255 -0
- procesio_cli/webplatform/aat_mcp/tests/test_chat_surface.py +247 -0
- procesio_cli/webplatform/aat_mcp/tests/test_discovery.py +489 -0
- procesio_cli/webplatform/aat_mcp/tests/test_multiuser.py +86 -0
- procesio_cli/webplatform/aat_mcp/tests/test_progressive_discovery.py +89 -0
- procesio_cli/webplatform/aat_mcp/tests/test_protocol.py +180 -0
- procesio_cli/webplatform/aat_mcp/tests/test_skill_discovery.py +23 -0
- procesio_cli-0.1.1.dist-info/METADATA +467 -0
- procesio_cli-0.1.1.dist-info/RECORD +647 -0
- procesio_cli-0.1.1.dist-info/WHEEL +4 -0
- procesio_cli-0.1.1.dist-info/entry_points.txt +7 -0
- procesio_cli-0.1.1.dist-info/licenses/LICENSE +201 -0
- procesio_cli-0.1.1.dist-info/licenses/NOTICE +5 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Named connection-profile store shared by the `sqlserver` and `mysql` tools.
|
|
2
|
+
|
|
3
|
+
Split storage, by design (see CLAUDE.md Hard rule 1 — no secrets in files):
|
|
4
|
+
|
|
5
|
+
* NON-SECRET config (profile name, server/host, port, database, username,
|
|
6
|
+
driver, TLS flags, timeouts) lives in a ``profiles.json`` file INSIDE the
|
|
7
|
+
tool directory. It never contains a password.
|
|
8
|
+
* The PASSWORD lives ONLY in Windows Credential Manager, under the target
|
|
9
|
+
``agents-and-tools:<tool>:<profile-name>`` — i.e. the secret name IS the
|
|
10
|
+
profile name. Read at runtime via ``tools._lib.creds.get(tool, profile)``.
|
|
11
|
+
|
|
12
|
+
So a profile named ``default`` on the ``sqlserver`` tool keeps its host/db/etc.
|
|
13
|
+
in ``tools/sqlserver/profiles.json`` and its password under the credential
|
|
14
|
+
target ``agents-and-tools:sqlserver:default``. Set it with:
|
|
15
|
+
|
|
16
|
+
python scripts/set-credential.py sqlserver default
|
|
17
|
+
|
|
18
|
+
This module is a thin, dependency-light wrapper (only stdlib + creds). It does
|
|
19
|
+
not import any DB driver, so it stays unit-testable and import-safe.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from tools._lib import creds
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProfileError(Exception):
|
|
31
|
+
"""Bad profile usage (unknown name, duplicate, missing field). -> invalid_argument."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AuthRequiredError(Exception):
|
|
35
|
+
"""A profile exists but its password is not in Credential Manager. -> auth_required."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ProfileStore:
|
|
39
|
+
"""Reads/writes the per-tool ``profiles.json`` and resolves passwords.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
tool : str
|
|
44
|
+
The tool name (``"sqlserver"`` or ``"mysql"``). Used as the Credential
|
|
45
|
+
Manager namespace AND must match the manifest ``name``.
|
|
46
|
+
path : Path
|
|
47
|
+
Absolute path to this tool's ``profiles.json``.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, tool: str, path: Path):
|
|
51
|
+
self.tool = tool
|
|
52
|
+
self.path = Path(path)
|
|
53
|
+
|
|
54
|
+
# -- raw file I/O -------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
def _read(self) -> dict[str, dict]:
|
|
57
|
+
if not self.path.exists():
|
|
58
|
+
return {}
|
|
59
|
+
try:
|
|
60
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
61
|
+
except (json.JSONDecodeError, OSError):
|
|
62
|
+
return {}
|
|
63
|
+
return data if isinstance(data, dict) else {}
|
|
64
|
+
|
|
65
|
+
def _write(self, data: dict[str, dict]) -> None:
|
|
66
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
self.path.write_text(
|
|
68
|
+
json.dumps(data, indent=2, ensure_ascii=False, sort_keys=True) + "\n",
|
|
69
|
+
encoding="utf-8",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# -- CRUD ---------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def names(self) -> list[str]:
|
|
75
|
+
return sorted(self._read().keys())
|
|
76
|
+
|
|
77
|
+
def exists(self, name: str) -> bool:
|
|
78
|
+
return name in self._read()
|
|
79
|
+
|
|
80
|
+
def get(self, name: str) -> dict:
|
|
81
|
+
"""Return one profile's NON-SECRET config dict (raises if unknown)."""
|
|
82
|
+
data = self._read()
|
|
83
|
+
if name not in data:
|
|
84
|
+
known = ", ".join(sorted(data)) or "(none)"
|
|
85
|
+
raise ProfileError(
|
|
86
|
+
f"no profile named '{name}'. Known: {known}. "
|
|
87
|
+
f"Add one with: run-tool {self.tool} add-profile --name {name} ..."
|
|
88
|
+
)
|
|
89
|
+
blob = dict(data[name])
|
|
90
|
+
blob["name"] = name
|
|
91
|
+
return blob
|
|
92
|
+
|
|
93
|
+
def add(self, name: str, config: dict[str, Any], *, overwrite: bool = False) -> dict:
|
|
94
|
+
"""Store a NON-SECRET profile config. Strips any password-like field."""
|
|
95
|
+
if not name:
|
|
96
|
+
raise ProfileError("profile --name is required")
|
|
97
|
+
data = self._read()
|
|
98
|
+
if name in data and not overwrite:
|
|
99
|
+
raise ProfileError(
|
|
100
|
+
f"profile '{name}' already exists. Pass --overwrite to replace it."
|
|
101
|
+
)
|
|
102
|
+
clean = {
|
|
103
|
+
k: v for k, v in config.items()
|
|
104
|
+
if v is not None and k not in ("name", "password", "pwd", "pass")
|
|
105
|
+
}
|
|
106
|
+
data[name] = clean
|
|
107
|
+
self._write(data)
|
|
108
|
+
view = dict(clean)
|
|
109
|
+
view["name"] = name
|
|
110
|
+
view["has_password"] = creds.has_for_report(self.tool, name)
|
|
111
|
+
return view
|
|
112
|
+
|
|
113
|
+
def remove(self, name: str) -> None:
|
|
114
|
+
data = self._read()
|
|
115
|
+
if name not in data:
|
|
116
|
+
raise ProfileError(f"no profile named '{name}'")
|
|
117
|
+
del data[name]
|
|
118
|
+
self._write(data)
|
|
119
|
+
|
|
120
|
+
# -- password (Credential Manager only) ---------------------------------
|
|
121
|
+
|
|
122
|
+
def password(self, name: str) -> str:
|
|
123
|
+
"""Fetch the profile password from Credential Manager.
|
|
124
|
+
|
|
125
|
+
Raises ``AuthRequiredError`` (mapped to ``auth_required``) when absent,
|
|
126
|
+
pointing the user at ``scripts/set-credential.py``.
|
|
127
|
+
"""
|
|
128
|
+
pw = creds.get_optional(self.tool, name)
|
|
129
|
+
if pw is None:
|
|
130
|
+
raise AuthRequiredError(
|
|
131
|
+
f"no password stored for profile '{name}'. Store it with:\n"
|
|
132
|
+
f" python scripts/set-credential.py {self.tool} {name}\n"
|
|
133
|
+
f"(saved to Windows Credential Manager target "
|
|
134
|
+
f"agents-and-tools:{self.tool}:{name})"
|
|
135
|
+
)
|
|
136
|
+
return pw
|
|
137
|
+
|
|
138
|
+
# -- public, password-free views ---------------------------------------
|
|
139
|
+
|
|
140
|
+
def public_view(self, name: str) -> dict:
|
|
141
|
+
"""A profile dict safe to return/log: no password, presence flagged."""
|
|
142
|
+
blob = self.get(name)
|
|
143
|
+
blob["has_password"] = creds.has_for_report(self.tool, name)
|
|
144
|
+
return blob
|
|
145
|
+
|
|
146
|
+
def list_public(self) -> list[dict]:
|
|
147
|
+
return [self.public_view(n) for n in self.names()]
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Engine-agnostic SQL helpers shared by the `sqlserver` and `mysql` tools.
|
|
2
|
+
|
|
3
|
+
PURE module: it imports no database driver and opens no connection, so every
|
|
4
|
+
function here is unit-testable with zero infrastructure. The two tools wire it
|
|
5
|
+
to pyodbc / pymysql at the edges.
|
|
6
|
+
|
|
7
|
+
What lives here:
|
|
8
|
+
- ``assert_read_only`` / ``is_write_sql`` — a regex write-statement guard that
|
|
9
|
+
blocks INSERT/UPDATE/DELETE/MERGE/UPSERT/TRUNCATE/ALTER/CREATE/DROP/GRANT/
|
|
10
|
+
EXEC/CALL/... and multi-statement batches, unless ``write=True``.
|
|
11
|
+
- ``rows_to_dicts`` — DBAPI cursor rows -> ``list[dict]`` with JSON-safe
|
|
12
|
+
coercion (datetime/date/time/timedelta/Decimal/bytes/UUID/memoryview/...).
|
|
13
|
+
- ``cap_rows`` — apply a max-rows cap and report a ``truncated`` flag.
|
|
14
|
+
- ``mask_dsn`` — redact a password inside any connection string for logging.
|
|
15
|
+
|
|
16
|
+
The guard is intentionally conservative: it strips string literals, quoted
|
|
17
|
+
identifiers and comments first, then looks for write keywords as whole words at
|
|
18
|
+
statement level. It is a guard, not a SQL parser — the real read-only guarantee
|
|
19
|
+
for SQL Server also leans on ApplicationIntent=ReadOnly + a read-only login when
|
|
20
|
+
configured; for MySQL (no ApplicationIntent equivalent) THIS guard is the
|
|
21
|
+
enforcement, so its keyword list is the superset of both engines.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import datetime as _dt
|
|
26
|
+
import decimal as _decimal
|
|
27
|
+
import re
|
|
28
|
+
import uuid as _uuid
|
|
29
|
+
from typing import Any, Sequence
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WriteGuardError(Exception):
|
|
33
|
+
"""Raised when a read-only query receives a write/DDL/multi-statement SQL.
|
|
34
|
+
|
|
35
|
+
Tools map this to the framework error code ``write_blocked`` (exit 2).
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Write / schema-change / side-effecting keywords. Superset across SQL Server
|
|
40
|
+
# and MySQL so the single guard protects both engines:
|
|
41
|
+
# - DML: INSERT, UPDATE, DELETE, MERGE, REPLACE (MySQL upsert), UPSERT
|
|
42
|
+
# - DDL: CREATE, ALTER, DROP, TRUNCATE, RENAME
|
|
43
|
+
# - permissions: GRANT, REVOKE, DENY
|
|
44
|
+
# - procedural: EXEC, EXECUTE, CALL
|
|
45
|
+
# - bulk / locks: BULK, LOAD, LOCK, UNLOCK, HANDLER, DO
|
|
46
|
+
# - tx / backup: BACKUP, RESTORE, COMMIT, ROLLBACK, SET, USE
|
|
47
|
+
# SET/USE/COMMIT/ROLLBACK are blocked because they change session/tx state and
|
|
48
|
+
# have no place in a read-only ad-hoc query path.
|
|
49
|
+
WRITE_KEYWORDS: tuple[str, ...] = (
|
|
50
|
+
"INSERT", "UPDATE", "DELETE", "MERGE", "UPSERT", "REPLACE",
|
|
51
|
+
"CREATE", "ALTER", "DROP", "TRUNCATE", "RENAME",
|
|
52
|
+
"GRANT", "REVOKE", "DENY",
|
|
53
|
+
"EXEC", "EXECUTE", "CALL",
|
|
54
|
+
"BULK", "LOAD", "LOCK", "UNLOCK", "HANDLER", "DO",
|
|
55
|
+
"BACKUP", "RESTORE", "COMMIT", "ROLLBACK", "SET", "USE",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
_WRITE_KW_RE = re.compile(r"\b(" + "|".join(WRITE_KEYWORDS) + r")\b", re.IGNORECASE)
|
|
59
|
+
|
|
60
|
+
# Read-only DDL-inspection idioms whose CREATE keyword does not create anything.
|
|
61
|
+
_SHOW_CREATE_RE = re.compile(
|
|
62
|
+
r"\bSHOW\s+CREATE\s+"
|
|
63
|
+
r"(?:TABLE|VIEW|PROCEDURE|FUNCTION|TRIGGER|EVENT|DATABASE|SCHEMA)\b",
|
|
64
|
+
re.IGNORECASE,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Regexes that strip out non-statement text before keyword scanning so that a
|
|
68
|
+
# keyword inside a literal / identifier / comment never trips the guard.
|
|
69
|
+
_STRIP_RES: tuple[re.Pattern[str], ...] = (
|
|
70
|
+
re.compile(r"'(?:[^'\\]|\\.|'')*'", re.DOTALL), # 'single-quoted' ('' or \' escaped)
|
|
71
|
+
re.compile(r'"(?:[^"\\]|\\.|"")*"', re.DOTALL), # "double-quoted"
|
|
72
|
+
re.compile(r"`(?:[^`\\]|\\.)*`", re.DOTALL), # `backtick identifier` (MySQL)
|
|
73
|
+
re.compile(r"\[[^\]]*\]"), # [bracketed identifier] (T-SQL)
|
|
74
|
+
re.compile(r"/\*.*?\*/", re.DOTALL), # /* block comment */
|
|
75
|
+
re.compile(r"--[^\n]*"), # -- line comment
|
|
76
|
+
re.compile(r"#[^\n]*"), # # line comment (MySQL)
|
|
77
|
+
_SHOW_CREATE_RE, # SHOW CREATE ... (read-only)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _strip_noise(sql: str) -> str:
|
|
82
|
+
"""Remove literals, quoted identifiers, comments, SHOW CREATE prefixes."""
|
|
83
|
+
out = sql
|
|
84
|
+
for rx in _STRIP_RES:
|
|
85
|
+
out = rx.sub(" ", out)
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _split_statements(stripped: str) -> list[str]:
|
|
90
|
+
"""Split a noise-stripped SQL on semicolons into non-empty statements.
|
|
91
|
+
|
|
92
|
+
Operates on already-stripped text, so semicolons inside literals/comments
|
|
93
|
+
are gone and can't create phantom statements.
|
|
94
|
+
"""
|
|
95
|
+
return [s.strip() for s in stripped.split(";") if s.strip()]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def find_write_keywords(sql: str) -> list[str]:
|
|
99
|
+
"""Return write keywords found at statement level (after noise-stripping)."""
|
|
100
|
+
stripped = _strip_noise(sql)
|
|
101
|
+
return [m.group(1).upper() for m in _WRITE_KW_RE.finditer(stripped)]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_multi_statement(sql: str) -> bool:
|
|
105
|
+
"""True if the SQL is a batch of more than one statement (post-strip)."""
|
|
106
|
+
return len(_split_statements(_strip_noise(sql))) > 1
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_write_sql(sql: str) -> bool:
|
|
110
|
+
"""True if `sql` would be rejected under read-only rules (write or batch)."""
|
|
111
|
+
return bool(find_write_keywords(sql)) or is_multi_statement(sql)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def assert_read_only(sql: str, *, write: bool = False) -> None:
|
|
115
|
+
"""Raise ``WriteGuardError`` unless `sql` is a single read-only statement.
|
|
116
|
+
|
|
117
|
+
When ``write=True`` the guard is bypassed entirely (the tool has already
|
|
118
|
+
confirmed the caller passed ``--write``). When ``write=False`` (default),
|
|
119
|
+
any write/DDL/EXEC keyword OR a multi-statement batch is rejected.
|
|
120
|
+
"""
|
|
121
|
+
if write:
|
|
122
|
+
return
|
|
123
|
+
hits = find_write_keywords(sql)
|
|
124
|
+
if hits:
|
|
125
|
+
# de-duplicate but keep order for a stable message
|
|
126
|
+
seen: list[str] = []
|
|
127
|
+
for h in hits:
|
|
128
|
+
if h not in seen:
|
|
129
|
+
seen.append(h)
|
|
130
|
+
raise WriteGuardError(
|
|
131
|
+
f"read-only guard blocked write keyword(s) {seen}. "
|
|
132
|
+
f"Pass --write to allow a non-SELECT statement."
|
|
133
|
+
)
|
|
134
|
+
if is_multi_statement(sql):
|
|
135
|
+
raise WriteGuardError(
|
|
136
|
+
"read-only guard blocked a multi-statement batch (';' separates "
|
|
137
|
+
"statements). Run one statement per query, or pass --write."
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# Line-ending normalization for DDL / script text
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
def normalize_eol(sql: str, style: str = "crlf") -> str:
|
|
146
|
+
"""Normalize every line ending in `sql` to one consistent style.
|
|
147
|
+
|
|
148
|
+
Collapses CRLF and lone CR to LF first, then expands to the target style,
|
|
149
|
+
so the result never contains a mixed CR/LF run. Default ``"crlf"`` matches
|
|
150
|
+
SQL Server / SSMS: when a ``CREATE``/``ALTER`` of a PROCEDURE, VIEW,
|
|
151
|
+
FUNCTION or TRIGGER is sent with LF-only newlines, SQL Server stores the
|
|
152
|
+
object source verbatim, and SSMS then reports *"Inconsistent Line Endings"*
|
|
153
|
+
on script-out because it prepends a CRLF ``SET ANSI_NULLS…GO`` header.
|
|
154
|
+
Emitting CRLF keeps stored definitions internally consistent.
|
|
155
|
+
|
|
156
|
+
T-SQL is whitespace-insensitive between tokens, so this never changes query
|
|
157
|
+
semantics *outside* of string literals. The one case to preserve a bare LF
|
|
158
|
+
is inside a string literal that must stay LF — callers expose an opt-out
|
|
159
|
+
(the sqlserver tool's ``--no-normalize-eol``) for that. Parameter values are
|
|
160
|
+
bound separately (``?`` placeholders) and are never passed through here.
|
|
161
|
+
"""
|
|
162
|
+
lf = sql.replace("\r\n", "\n").replace("\r", "\n")
|
|
163
|
+
if style == "lf":
|
|
164
|
+
return lf
|
|
165
|
+
if style != "crlf":
|
|
166
|
+
raise ValueError(f"unknown eol style: {style!r} (use 'crlf' or 'lf')")
|
|
167
|
+
return lf.replace("\n", "\r\n")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
# Row formatting: DBAPI rows -> JSON-safe list[dict]
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
def json_safe(value: Any) -> Any:
|
|
175
|
+
"""Coerce one DBAPI value into something ``json.dumps`` can serialise.
|
|
176
|
+
|
|
177
|
+
datetime/date/time -> ISO 8601 string; timedelta -> total seconds (float);
|
|
178
|
+
Decimal -> str (preserves precision); bytes/bytearray/memoryview -> hex str;
|
|
179
|
+
UUID -> str. Primitives (str/int/float/bool/None) pass through unchanged.
|
|
180
|
+
Anything else falls back to ``str(value)``.
|
|
181
|
+
"""
|
|
182
|
+
if value is None or isinstance(value, (str, int, float, bool)):
|
|
183
|
+
return value
|
|
184
|
+
if isinstance(value, (_dt.datetime, _dt.date, _dt.time)):
|
|
185
|
+
return value.isoformat()
|
|
186
|
+
if isinstance(value, _dt.timedelta):
|
|
187
|
+
return value.total_seconds()
|
|
188
|
+
if isinstance(value, _decimal.Decimal):
|
|
189
|
+
return str(value)
|
|
190
|
+
if isinstance(value, (bytes, bytearray)):
|
|
191
|
+
return bytes(value).hex()
|
|
192
|
+
if isinstance(value, memoryview):
|
|
193
|
+
return value.tobytes().hex()
|
|
194
|
+
if isinstance(value, _uuid.UUID):
|
|
195
|
+
return str(value)
|
|
196
|
+
return str(value)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def column_names(cursor) -> list[str]:
|
|
200
|
+
"""Column names from a DBAPI ``cursor.description`` (empty if no result set)."""
|
|
201
|
+
if not getattr(cursor, "description", None):
|
|
202
|
+
return []
|
|
203
|
+
return [d[0] for d in cursor.description]
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def first_result_columns(cursor) -> list[str]:
|
|
207
|
+
"""Columns of the first result that has any, stepping past row-count-only results.
|
|
208
|
+
|
|
209
|
+
A multi-statement batch run without SET NOCOUNT ON opens with "N rows affected"
|
|
210
|
+
results that carry no columns; the rows the caller wants come after them.
|
|
211
|
+
"""
|
|
212
|
+
columns = column_names(cursor)
|
|
213
|
+
nextset = getattr(cursor, "nextset", None)
|
|
214
|
+
while not columns and nextset is not None and nextset():
|
|
215
|
+
columns = column_names(cursor)
|
|
216
|
+
return columns
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def drain_results(cursor) -> None:
|
|
220
|
+
"""Step through every remaining result of the last ``execute`` so its errors surface.
|
|
221
|
+
|
|
222
|
+
The server returns a batch's results in order and the driver raises a statement's
|
|
223
|
+
error only when the cursor reaches it. A batch whose INSERT reported "1 row affected"
|
|
224
|
+
and whose next statement failed looks successful until ``nextset()`` gets there, so
|
|
225
|
+
a caller that must not lose an error drains after every execute.
|
|
226
|
+
"""
|
|
227
|
+
nextset = getattr(cursor, "nextset", None)
|
|
228
|
+
if nextset is None: # test doubles without result sets
|
|
229
|
+
return
|
|
230
|
+
while nextset():
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def rows_to_dicts(
|
|
235
|
+
columns: Sequence[str],
|
|
236
|
+
raw_rows: Sequence[Any],
|
|
237
|
+
) -> list[dict]:
|
|
238
|
+
"""Map DBAPI rows to JSON-safe dicts keyed by `columns`.
|
|
239
|
+
|
|
240
|
+
Accepts both sequence rows (pyodbc tuples) and mapping rows (pymysql
|
|
241
|
+
DictCursor dicts) — for a dict row we read each column by name, for a
|
|
242
|
+
sequence row by position. Every value is passed through ``json_safe``.
|
|
243
|
+
"""
|
|
244
|
+
out: list[dict] = []
|
|
245
|
+
for row in raw_rows:
|
|
246
|
+
if isinstance(row, dict):
|
|
247
|
+
out.append({c: json_safe(row.get(c)) for c in columns})
|
|
248
|
+
else:
|
|
249
|
+
out.append({columns[i]: json_safe(row[i]) for i in range(len(columns))})
|
|
250
|
+
return out
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def cap_rows(rows: list[Any], max_rows: int | None) -> tuple[list[Any], bool]:
|
|
254
|
+
"""Trim `rows` to at most `max_rows`; return ``(rows, truncated)``.
|
|
255
|
+
|
|
256
|
+
``max_rows is None`` or ``<= 0`` means no cap. ``truncated`` is True only
|
|
257
|
+
when rows were actually dropped.
|
|
258
|
+
"""
|
|
259
|
+
if max_rows is None or max_rows <= 0:
|
|
260
|
+
return rows, False
|
|
261
|
+
if len(rows) > max_rows:
|
|
262
|
+
return rows[:max_rows], True
|
|
263
|
+
return rows, False
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# Connection-string masking (for safe logging / test-connection output)
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
# pyodbc uses PWD=...; pymysql-style DSNs may use password=...; URL DSNs use
|
|
271
|
+
# scheme://user:password@host. Cover all three.
|
|
272
|
+
_MASK_RES: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
273
|
+
(re.compile(r"(?i)(PWD\s*=)[^;]*"), r"\1***"),
|
|
274
|
+
(re.compile(r"(?i)(PASSWORD\s*=)[^;]*"), r"\1***"),
|
|
275
|
+
(re.compile(r"(?i)(PASSWORD\s*:\s*)[^,}\s]+"), r"\1***"),
|
|
276
|
+
# scheme://user:password@host -> scheme://user:***@host
|
|
277
|
+
(re.compile(r"(://[^:/@\s]+:)[^@/\s]+(@)"), r"\1***\2"),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def mask_dsn(dsn: str) -> str:
|
|
282
|
+
"""Replace any password value in a connection string with ``***``."""
|
|
283
|
+
out = dsn
|
|
284
|
+
for rx, repl in _MASK_RES:
|
|
285
|
+
out = rx.sub(repl, out)
|
|
286
|
+
return out
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Engine-agnostic schema-mirror, diff, and migration helpers.
|
|
2
|
+
|
|
3
|
+
Shared by the `sqlserver` and `mysql` tools' `schema-extract`, `schema-diff`, and
|
|
4
|
+
`migrate` actions. PURE: no DB driver import, no network — every function is a
|
|
5
|
+
deterministic transform of its inputs, so it is fully unit-testable without a
|
|
6
|
+
database. The engine-specific parts (catalog queries, tracking-table SQL, apply
|
|
7
|
+
path) live in each tool's `schema.py`; this module is the common machinery.
|
|
8
|
+
|
|
9
|
+
Mirror layout (one file per object):
|
|
10
|
+
<out>/schema/<Type>/<schema>.<name>.sql (or <name>.sql when schema is "")
|
|
11
|
+
<out>/_manifest.json {relpath: sha256}
|
|
12
|
+
|
|
13
|
+
A "record" is a dict: {"type": <dir label>, "schema": <str|"">, "name": <str>,
|
|
14
|
+
"definition": <sql text>}.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import difflib
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import re
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# hashing
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
def sha256_text(text: str) -> str:
|
|
29
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sha256_file(path: str | Path) -> str:
|
|
33
|
+
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# mirror layout
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
_UNSAFE = re.compile(r'[<>:"/\\|?*\x00-\x1f]')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _safe(component: str) -> str:
|
|
44
|
+
"""Make one path component filesystem-safe (Windows + POSIX)."""
|
|
45
|
+
s = _UNSAFE.sub("_", str(component)).strip().rstrip(".")
|
|
46
|
+
return s or "_"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def mirror_relpath(obj_type: str, schema: str, name: str) -> str:
|
|
50
|
+
"""Deterministic POSIX relpath under the mirror root for one object."""
|
|
51
|
+
stem = f"{schema}.{name}" if schema else str(name)
|
|
52
|
+
return f"schema/{_safe(obj_type)}/{_safe(stem)}.sql"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def write_mirror(out_dir: str | Path, records, *, full: bool = False,
|
|
56
|
+
dry_run: bool = False) -> dict:
|
|
57
|
+
"""Write/update the local mirror from `records`. Incremental by default:
|
|
58
|
+
only changed/new objects are written, orphaned files are removed, and a
|
|
59
|
+
`_manifest.json` of relpath->sha256 tracks state. Returns a summary dict.
|
|
60
|
+
"""
|
|
61
|
+
out = Path(out_dir)
|
|
62
|
+
schema_root = out / "schema"
|
|
63
|
+
manifest_path = out / "_manifest.json"
|
|
64
|
+
|
|
65
|
+
desired: dict[str, str] = {} # relpath -> definition text
|
|
66
|
+
by_type: dict[str, int] = {}
|
|
67
|
+
for r in records:
|
|
68
|
+
rel = mirror_relpath(r["type"], r.get("schema", "") or "", r["name"])
|
|
69
|
+
desired[rel] = r["definition"]
|
|
70
|
+
by_type[r["type"]] = by_type.get(r["type"], 0) + 1
|
|
71
|
+
|
|
72
|
+
prev: dict[str, str] = {}
|
|
73
|
+
if manifest_path.exists() and not full:
|
|
74
|
+
try:
|
|
75
|
+
prev = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
76
|
+
except (json.JSONDecodeError, OSError):
|
|
77
|
+
prev = {}
|
|
78
|
+
|
|
79
|
+
if full and not dry_run and schema_root.exists():
|
|
80
|
+
import shutil
|
|
81
|
+
shutil.rmtree(schema_root)
|
|
82
|
+
prev = {}
|
|
83
|
+
|
|
84
|
+
written = unchanged = removed = 0
|
|
85
|
+
new_manifest: dict[str, str] = {}
|
|
86
|
+
for rel, definition in sorted(desired.items()):
|
|
87
|
+
digest = sha256_text(definition)
|
|
88
|
+
new_manifest[rel] = digest
|
|
89
|
+
target = out / rel
|
|
90
|
+
is_same = prev.get(rel) == digest and (dry_run or target.exists())
|
|
91
|
+
if is_same and not full:
|
|
92
|
+
unchanged += 1
|
|
93
|
+
continue
|
|
94
|
+
written += 1
|
|
95
|
+
if not dry_run:
|
|
96
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
target.write_text(definition, encoding="utf-8")
|
|
98
|
+
|
|
99
|
+
# remove orphans (in the old manifest but no longer desired)
|
|
100
|
+
for rel in sorted(set(prev) - set(desired)):
|
|
101
|
+
removed += 1
|
|
102
|
+
if not dry_run:
|
|
103
|
+
try:
|
|
104
|
+
(out / rel).unlink()
|
|
105
|
+
except OSError:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
if not dry_run:
|
|
109
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
manifest_path.write_text(
|
|
111
|
+
json.dumps(new_manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
112
|
+
|
|
113
|
+
return {"out": str(out), "written": written, "unchanged": unchanged,
|
|
114
|
+
"removed": removed, "total": len(desired), "by_type": by_type,
|
|
115
|
+
"dry_run": dry_run, "manifest": str(manifest_path)}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# diff
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def _mirror_files(mirror_dir: str | Path) -> dict[str, str]:
|
|
123
|
+
"""relpath (posix, 'schema/...') -> file text, for every .sql under schema/."""
|
|
124
|
+
root = Path(mirror_dir) / "schema"
|
|
125
|
+
out: dict[str, str] = {}
|
|
126
|
+
if not root.is_dir():
|
|
127
|
+
return out
|
|
128
|
+
for p in root.rglob("*.sql"):
|
|
129
|
+
rel = p.relative_to(Path(mirror_dir)).as_posix()
|
|
130
|
+
try:
|
|
131
|
+
out[rel] = p.read_text(encoding="utf-8")
|
|
132
|
+
except OSError:
|
|
133
|
+
out[rel] = ""
|
|
134
|
+
return out
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def diff_mirrors(left_dir: str | Path, right_dir: str | Path, *,
|
|
138
|
+
include_diff: bool = True) -> dict:
|
|
139
|
+
"""Compare two schema mirrors. added = only in right, removed = only in left,
|
|
140
|
+
changed = present in both with differing content (with a unified diff)."""
|
|
141
|
+
left = _mirror_files(left_dir)
|
|
142
|
+
right = _mirror_files(right_dir)
|
|
143
|
+
added = sorted(set(right) - set(left))
|
|
144
|
+
removed = sorted(set(left) - set(right))
|
|
145
|
+
changed = []
|
|
146
|
+
for rel in sorted(set(left) & set(right)):
|
|
147
|
+
if left[rel] != right[rel]:
|
|
148
|
+
entry = {"relpath": rel}
|
|
149
|
+
if include_diff:
|
|
150
|
+
entry["diff"] = "".join(difflib.unified_diff(
|
|
151
|
+
left[rel].splitlines(keepends=True),
|
|
152
|
+
right[rel].splitlines(keepends=True),
|
|
153
|
+
fromfile=f"left/{rel}", tofile=f"right/{rel}"))
|
|
154
|
+
changed.append(entry)
|
|
155
|
+
unchanged = len(set(left) & set(right)) - len(changed)
|
|
156
|
+
return {"added": added, "removed": removed, "changed": changed,
|
|
157
|
+
"unchanged": unchanged,
|
|
158
|
+
"identical": not added and not removed and not changed,
|
|
159
|
+
"summary": {"added": len(added), "removed": len(removed),
|
|
160
|
+
"changed": len(changed)}}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# migrations
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
MIGRATION_FILE_RE = re.compile(r"^(\d{3,})_([a-z0-9][a-z0-9_\-]*)\.sql$", re.IGNORECASE)
|
|
168
|
+
|
|
169
|
+
_DANGER_RE = re.compile(
|
|
170
|
+
r"\b(DROP\s+(TABLE|DATABASE|SCHEMA|COLUMN)|TRUNCATE\s+TABLE)\b", re.IGNORECASE)
|
|
171
|
+
_UNQUALIFIED_RE = re.compile(
|
|
172
|
+
r"\b(UPDATE|DELETE)\b(?:(?!\bWHERE\b).)*?(;|\Z)", re.IGNORECASE | re.DOTALL)
|
|
173
|
+
_GO_RE = re.compile(r"^\s*GO\s*$", re.IGNORECASE)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def discover_migrations(migrations_dir: str | Path):
|
|
177
|
+
"""Return [(mig_id, Path)] for NNNN_<slug>.sql files, ordered by the numeric
|
|
178
|
+
prefix. mig_id is the filename stem. Non-matching files are skipped (returned
|
|
179
|
+
separately so the caller can warn)."""
|
|
180
|
+
d = Path(migrations_dir)
|
|
181
|
+
found, skipped = [], []
|
|
182
|
+
if d.is_dir():
|
|
183
|
+
for p in sorted(d.iterdir()):
|
|
184
|
+
if p.is_file() and p.suffix.lower() == ".sql":
|
|
185
|
+
m = MIGRATION_FILE_RE.match(p.name)
|
|
186
|
+
(found if m else skipped).append((p.stem if m else p.name, p))
|
|
187
|
+
found.sort(key=lambda t: int(t[0].split("_", 1)[0]))
|
|
188
|
+
return found, [name for name, _ in skipped]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def pending(applied_ids, migrations):
|
|
192
|
+
"""migrations = [(mig_id, path)]; return those whose id is not in applied_ids."""
|
|
193
|
+
applied = set(applied_ids)
|
|
194
|
+
return [(mid, p) for (mid, p) in migrations if mid not in applied]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def danger_warnings(sql: str) -> list[str]:
|
|
198
|
+
out = []
|
|
199
|
+
m = _DANGER_RE.search(sql)
|
|
200
|
+
if m:
|
|
201
|
+
out.append(f"destructive DDL: {m.group(0)}")
|
|
202
|
+
if _UNQUALIFIED_RE.search(sql):
|
|
203
|
+
out.append("possible UPDATE/DELETE without a WHERE clause")
|
|
204
|
+
return out
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def split_go_batches(sql: str) -> list[str]:
|
|
208
|
+
"""Split a SQL Server script on lines that are exactly `GO` (case-insensitive).
|
|
209
|
+
Known limitation: a `GO` inside a string literal or block comment that sits
|
|
210
|
+
alone on a line is still treated as a separator."""
|
|
211
|
+
# Lines keep their own endings: SQL Server stores a CREATE/ALTER of a module verbatim, so
|
|
212
|
+
# rejoining with "\n" turned every CRLF-normalized migration into an LF-only definition.
|
|
213
|
+
batches, cur = [], []
|
|
214
|
+
for line in sql.splitlines(keepends=True):
|
|
215
|
+
if _GO_RE.match(line.rstrip("\r\n")):
|
|
216
|
+
batch = "".join(cur).strip()
|
|
217
|
+
if batch:
|
|
218
|
+
batches.append(batch)
|
|
219
|
+
cur = []
|
|
220
|
+
else:
|
|
221
|
+
cur.append(line)
|
|
222
|
+
batch = "".join(cur).strip()
|
|
223
|
+
if batch:
|
|
224
|
+
batches.append(batch)
|
|
225
|
+
return batches
|