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,544 @@
|
|
|
1
|
+
"""Pull published contact details out of free text or an HTML page.
|
|
2
|
+
|
|
3
|
+
Shared by the tools that read what a person publishes about themselves (a YouTube
|
|
4
|
+
channel description, a video description, a link-in-bio page, a personal site):
|
|
5
|
+
the email they list for business inquiries, their profiles on other platforms, and
|
|
6
|
+
any affiliate-shaped links (which say "this person already earns commission").
|
|
7
|
+
|
|
8
|
+
Pure functions, no I/O. Callers attach their own ``source`` to what comes back.
|
|
9
|
+
|
|
10
|
+
What is deliberately NOT done here: guessing addresses (first.last@domain),
|
|
11
|
+
resolving shortened links, or following redirects. Only what is literally on the
|
|
12
|
+
page counts, so every contact can be traced back to where it was published.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import html as _html
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
import unicodedata
|
|
20
|
+
from html.parser import HTMLParser
|
|
21
|
+
from urllib.parse import parse_qsl, unquote, urljoin, urlsplit
|
|
22
|
+
|
|
23
|
+
# --- emails -------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
EMAIL_RE = re.compile(
|
|
26
|
+
r"(?<![\w.+%-])([A-Za-z0-9][A-Za-z0-9._%+-]*@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,24})(?![\w-])"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# "name [at] domain [dot] com", "name (at) domain (dot) com", "name{at}domain{dot}com".
|
|
30
|
+
# A bare "name at domain dot com" is NOT matched: it is too common in normal prose.
|
|
31
|
+
_AT = r"\s*[\[\(\{<]\s*at\s*[\]\)\}>]\s*"
|
|
32
|
+
_DOT = r"\s*(?:[\[\(\{<]\s*dot\s*[\]\)\}>]|\.)\s*"
|
|
33
|
+
OBFUSCATED_EMAIL_RE = re.compile(
|
|
34
|
+
rf"([A-Za-z0-9][A-Za-z0-9._%+-]*){_AT}([A-Za-z0-9-]+(?:{_DOT}[A-Za-z0-9-]+)+)",
|
|
35
|
+
re.IGNORECASE,
|
|
36
|
+
)
|
|
37
|
+
_DOT_TOKEN_RE = re.compile(_DOT, re.IGNORECASE)
|
|
38
|
+
|
|
39
|
+
# File extensions that look like a TLD in retina asset names (an "@2x" suffix before ".png").
|
|
40
|
+
_NOT_TLDS = {"png", "jpg", "jpeg", "gif", "webp", "svg", "css", "js", "ico", "mp4", "pdf"}
|
|
41
|
+
# Mailboxes nobody reads: never a contact, wherever they appear.
|
|
42
|
+
_NO_REPLY = re.compile(r"^(no-?reply|do-?not-?reply|mailer-daemon|postmaster|bounces?)([+.-].*)?$")
|
|
43
|
+
# Personal mailbox providers: an address here belongs to a person, not to the site
|
|
44
|
+
# it was read from.
|
|
45
|
+
FREE_MAIL_DOMAINS = {
|
|
46
|
+
"gmail.com", "googlemail.com", "outlook.com", "hotmail.com", "live.com", "msn.com",
|
|
47
|
+
"yahoo.com", "icloud.com", "me.com", "proton.me", "protonmail.com", "aol.com",
|
|
48
|
+
"gmx.com", "gmx.de", "gmx.net", "yandex.com", "zoho.com", "mail.com",
|
|
49
|
+
}
|
|
50
|
+
# Placeholder / infrastructure domains that are never a person's inbox.
|
|
51
|
+
_JUNK_EMAIL_DOMAINS = {
|
|
52
|
+
"example.com", "example.org", "example.net", "domain.com", "yourdomain.com",
|
|
53
|
+
"email.example", "sentry.io", "wixpress.com", "sentry-next.wixpress.com",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _clean_email(raw: str) -> str | None:
|
|
58
|
+
email = raw.strip().strip(".").lower()
|
|
59
|
+
if email.count("@") != 1:
|
|
60
|
+
return None
|
|
61
|
+
local, domain = email.split("@")
|
|
62
|
+
if not local or not domain or ".." in email:
|
|
63
|
+
return None
|
|
64
|
+
if domain.rsplit(".", 1)[-1] in _NOT_TLDS:
|
|
65
|
+
return None
|
|
66
|
+
if domain in _JUNK_EMAIL_DOMAINS or domain.endswith(".example"):
|
|
67
|
+
return None
|
|
68
|
+
if _NO_REPLY.match(local):
|
|
69
|
+
return None
|
|
70
|
+
return email
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def drop_shadowed(emails: list[str]) -> list[str]:
|
|
74
|
+
"""Drop an address that is a cut-off copy of another one in the same list: same
|
|
75
|
+
local part, and one domain a strict prefix of the other's (a truncated top-level
|
|
76
|
+
domain such as ``.ne`` beside the full ``.net``). Creators mistype or truncate their
|
|
77
|
+
address in one place and get it right everywhere else; the complete one is the one
|
|
78
|
+
that delivers."""
|
|
79
|
+
out = []
|
|
80
|
+
for e in emails:
|
|
81
|
+
local, dom = e.split("@", 1)
|
|
82
|
+
if any(f != e and f.split("@", 1)[0] == local and f.split("@", 1)[1].startswith(dom)
|
|
83
|
+
for f in emails):
|
|
84
|
+
continue
|
|
85
|
+
out.append(e)
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def email_fits_page(email: str, page_url: str) -> bool:
|
|
90
|
+
"""Whether an address read off ``page_url`` plausibly belongs to that page's owner.
|
|
91
|
+
|
|
92
|
+
Any address on a link-in-bio page counts (the person put it there). On any other
|
|
93
|
+
page only an address on the page's own site, or a personal free-mail address, does:
|
|
94
|
+
a creator's video links to tools and vendors, and those pages carry the VENDOR's
|
|
95
|
+
support inbox, which is not a way to reach the creator.
|
|
96
|
+
"""
|
|
97
|
+
if classify_url(page_url) == "link_in_bio":
|
|
98
|
+
return True
|
|
99
|
+
domain = email.rsplit("@", 1)[-1].lower()
|
|
100
|
+
if domain in FREE_MAIL_DOMAINS:
|
|
101
|
+
return True
|
|
102
|
+
return _site(domain) == _site(_host(page_url))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def find_emails(text: str) -> list[str]:
|
|
106
|
+
"""Every distinct email in ``text``, in order of appearance (lowercased)."""
|
|
107
|
+
found: list[str] = []
|
|
108
|
+
for m in EMAIL_RE.finditer(text or ""):
|
|
109
|
+
e = _clean_email(m.group(1))
|
|
110
|
+
if e and e not in found:
|
|
111
|
+
found.append(e)
|
|
112
|
+
for m in OBFUSCATED_EMAIL_RE.finditer(text or ""):
|
|
113
|
+
domain = _DOT_TOKEN_RE.sub(".", m.group(2))
|
|
114
|
+
e = _clean_email(f"{m.group(1)}@{domain}")
|
|
115
|
+
if e and "." in domain and e not in found:
|
|
116
|
+
found.append(e)
|
|
117
|
+
return found
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# --- links --------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
URL_RE = re.compile(r"https?://[^\s<>\"'`\]\[|]+", re.IGNORECASE)
|
|
123
|
+
# Profile links people often write without a scheme.
|
|
124
|
+
BARE_PROFILE_RE = re.compile(
|
|
125
|
+
r"(?<![\w./-])((?:www\.)?(?:linkedin\.com/(?:in|company)/|x\.com/|twitter\.com/)[\w%./-]+)",
|
|
126
|
+
re.IGNORECASE,
|
|
127
|
+
)
|
|
128
|
+
_TRAILING = ".,;:!?)]}'\"*"
|
|
129
|
+
|
|
130
|
+
LINK_IN_BIO_HOSTS = {
|
|
131
|
+
"linktr.ee", "beacons.ai", "stan.store", "bio.link", "lnk.bio", "linkin.bio",
|
|
132
|
+
"taplink.cc", "campsite.bio", "solo.to", "hoo.be", "msha.ke", "linkpop.com",
|
|
133
|
+
"koji.to", "withkoji.com", "bento.me", "allmylinks.com", "linkr.bio", "contactin.bio",
|
|
134
|
+
}
|
|
135
|
+
_PLATFORM_HOSTS = {
|
|
136
|
+
"linkedin": ("linkedin.com", "lnkd.in"),
|
|
137
|
+
"x": ("x.com", "twitter.com"),
|
|
138
|
+
"youtube": ("youtube.com", "youtu.be"),
|
|
139
|
+
"instagram": ("instagram.com",),
|
|
140
|
+
"tiktok": ("tiktok.com",),
|
|
141
|
+
"facebook": ("facebook.com", "fb.com", "fb.me"),
|
|
142
|
+
"github": ("github.com",),
|
|
143
|
+
"booking": ("calendly.com", "cal.com", "tidycal.com", "savvycal.com", "zcal.co"),
|
|
144
|
+
"newsletter": ("substack.com", "beehiiv.com", "convertkit.com", "kit.com"),
|
|
145
|
+
}
|
|
146
|
+
# Shorteners and stores: a link, but neither the person's site nor a profile.
|
|
147
|
+
_OTHER_HOSTS = (
|
|
148
|
+
"bit.ly", "tinyurl.com", "geni.us", "amzn.to", "amazon.com", "t.co", "goo.gl",
|
|
149
|
+
"ow.ly", "rebrand.ly", "shorturl.at", "buff.ly", "discord.gg", "discord.com",
|
|
150
|
+
"patreon.com", "buymeacoffee.com", "ko-fi.com", "gumroad.com", "udemy.com",
|
|
151
|
+
"apple.com", "spotify.com", "google.com", "goo.gl", "forms.gle",
|
|
152
|
+
)
|
|
153
|
+
LINK_KINDS = ("linkedin", "x", "youtube", "instagram", "tiktok", "facebook", "github",
|
|
154
|
+
"booking", "newsletter", "link_in_bio", "website", "other")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _host(url: str) -> str:
|
|
158
|
+
try:
|
|
159
|
+
host = (urlsplit(url).hostname or "").lower()
|
|
160
|
+
except ValueError:
|
|
161
|
+
return ""
|
|
162
|
+
return host[4:] if host.startswith("www.") else host
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _host_matches(host: str, domains) -> bool:
|
|
166
|
+
return any(host == d or host.endswith("." + d) for d in domains)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def normalize_url(raw: str) -> str:
|
|
170
|
+
url = raw.strip().rstrip(_TRAILING)
|
|
171
|
+
if not re.match(r"https?://", url, re.IGNORECASE):
|
|
172
|
+
url = "https://" + url
|
|
173
|
+
return url
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Static files: never a profile, a site or a contact route.
|
|
177
|
+
_ASSET_RE = re.compile(
|
|
178
|
+
r"\.(?:js|mjs|css|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|json|webmanifest|map|mp4|webm)$",
|
|
179
|
+
re.IGNORECASE)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def is_asset(url: str) -> bool:
|
|
183
|
+
try:
|
|
184
|
+
return bool(_ASSET_RE.search(urlsplit(url).path))
|
|
185
|
+
except ValueError:
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def find_urls(text: str) -> list[str]:
|
|
190
|
+
"""Distinct http(s) URLs plus scheme-less LinkedIn / X profile links.
|
|
191
|
+
Static assets (scripts, styles, images, fonts) are skipped."""
|
|
192
|
+
text = (text or "").replace("\\/", "/")
|
|
193
|
+
out: list[str] = []
|
|
194
|
+
for m in URL_RE.finditer(text):
|
|
195
|
+
u = normalize_url(m.group(0))
|
|
196
|
+
if _host(u) and not is_asset(u) and u not in out:
|
|
197
|
+
out.append(u)
|
|
198
|
+
for m in BARE_PROFILE_RE.finditer(text):
|
|
199
|
+
u = normalize_url(m.group(1))
|
|
200
|
+
if u not in out:
|
|
201
|
+
out.append(u)
|
|
202
|
+
return out
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def classify_url(url: str) -> str:
|
|
206
|
+
"""Which kind of link this is (one of LINK_KINDS)."""
|
|
207
|
+
host = _host(url)
|
|
208
|
+
if not host:
|
|
209
|
+
return "other"
|
|
210
|
+
if _host_matches(host, LINK_IN_BIO_HOSTS) or host.endswith(".carrd.co"):
|
|
211
|
+
return "link_in_bio"
|
|
212
|
+
for kind, domains in _PLATFORM_HOSTS.items():
|
|
213
|
+
if _host_matches(host, domains):
|
|
214
|
+
return kind
|
|
215
|
+
if _host_matches(host, _OTHER_HOSTS):
|
|
216
|
+
return "other"
|
|
217
|
+
return "website"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# --- affiliate-shaped links ---------------------------------------------------
|
|
221
|
+
|
|
222
|
+
# Query parameters affiliate / referral programs put on a link, and the network
|
|
223
|
+
# that uses each (None = generic referral parameter used by many programs).
|
|
224
|
+
AFFILIATE_PARAMS = {
|
|
225
|
+
"via": "rewardful",
|
|
226
|
+
"fpr": "firstpromoter",
|
|
227
|
+
"fp_ref": "firstpromoter",
|
|
228
|
+
"tap_a": "tapfiliate",
|
|
229
|
+
"irclickid": "impact",
|
|
230
|
+
"aff": None, "affiliate": None, "aff_id": None, "affid": None, "affiliate_id": None,
|
|
231
|
+
"ref": None, "referral": None, "referral_code": None, "ref_id": None,
|
|
232
|
+
"pc": None, "partner": None, "partner_id": None, "promo": None,
|
|
233
|
+
"tag": "amazon",
|
|
234
|
+
}
|
|
235
|
+
# Hosts that only exist to track affiliate clicks.
|
|
236
|
+
AFFILIATE_HOSTS = {
|
|
237
|
+
"partnerlinks.io": "partnerstack",
|
|
238
|
+
"sjv.io": "impact",
|
|
239
|
+
"pxf.io": "impact",
|
|
240
|
+
"ojrq.net": "impact",
|
|
241
|
+
"7eer.net": "impact",
|
|
242
|
+
"shareasale.com": "shareasale",
|
|
243
|
+
"awin1.com": "awin",
|
|
244
|
+
"dpbolvw.net": "cj",
|
|
245
|
+
"anrdoezrs.net": "cj",
|
|
246
|
+
"kqzyfj.com": "cj",
|
|
247
|
+
"tkqlhce.com": "cj",
|
|
248
|
+
"jdoqocy.com": "cj",
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _program_from_host(host: str) -> str:
|
|
253
|
+
labels = [x for x in host.split(".") if x]
|
|
254
|
+
if len(labels) >= 2:
|
|
255
|
+
return labels[-2]
|
|
256
|
+
return host
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def affiliate_info(url: str) -> dict | None:
|
|
260
|
+
"""Describe ``url`` as an affiliate link, or None when it does not look like one.
|
|
261
|
+
|
|
262
|
+
``program`` is the product the link promotes: the subdomain on a tracking host
|
|
263
|
+
(``acme.partnerlinks.io`` -> ``acme``), else the site's own name
|
|
264
|
+
(``www.acme.com/?via=x`` -> ``acme``).
|
|
265
|
+
"""
|
|
266
|
+
host = _host(url)
|
|
267
|
+
if not host:
|
|
268
|
+
return None
|
|
269
|
+
for tracker, network in AFFILIATE_HOSTS.items():
|
|
270
|
+
if host == tracker or host.endswith("." + tracker):
|
|
271
|
+
sub = host[: -len(tracker)].rstrip(".")
|
|
272
|
+
program = sub.split(".")[-1] if sub else tracker
|
|
273
|
+
return {"url": url, "host": host, "network": network, "program": program, "param": None}
|
|
274
|
+
try:
|
|
275
|
+
params = parse_qsl(urlsplit(url).query, keep_blank_values=False)
|
|
276
|
+
except ValueError:
|
|
277
|
+
params = []
|
|
278
|
+
for key, value in params:
|
|
279
|
+
k = key.lower()
|
|
280
|
+
if k in AFFILIATE_PARAMS and value.strip():
|
|
281
|
+
network = AFFILIATE_PARAMS[k]
|
|
282
|
+
if k == "tag" and "amazon." not in host:
|
|
283
|
+
continue
|
|
284
|
+
return {"url": url, "host": host, "network": network,
|
|
285
|
+
"program": _program_from_host(host), "param": k}
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# --- one-shot extraction ------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
def empty_result() -> dict:
|
|
292
|
+
return {"emails": [], "links": {k: [] for k in LINK_KINDS}, "affiliate_links": []}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def extract(text: str) -> dict:
|
|
296
|
+
"""Emails, links grouped by kind, and affiliate-shaped links found in ``text``."""
|
|
297
|
+
out = empty_result()
|
|
298
|
+
out["emails"] = find_emails(text)
|
|
299
|
+
for url in find_urls(text):
|
|
300
|
+
kind = classify_url(url)
|
|
301
|
+
aff = affiliate_info(url)
|
|
302
|
+
if aff:
|
|
303
|
+
out["affiliate_links"].append(aff)
|
|
304
|
+
# An affiliate link points at someone else's product, not the
|
|
305
|
+
# person's own site: never let it pose as their website.
|
|
306
|
+
if kind == "website":
|
|
307
|
+
kind = "other"
|
|
308
|
+
out["links"][kind].append(url)
|
|
309
|
+
return out
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def merge(*results: dict) -> dict:
|
|
313
|
+
"""Union of several ``extract`` results, order-preserving and de-duplicated."""
|
|
314
|
+
out = empty_result()
|
|
315
|
+
for r in results:
|
|
316
|
+
for e in r.get("emails", []):
|
|
317
|
+
if e not in out["emails"]:
|
|
318
|
+
out["emails"].append(e)
|
|
319
|
+
for kind, urls in (r.get("links") or {}).items():
|
|
320
|
+
bucket = out["links"].setdefault(kind, [])
|
|
321
|
+
for u in urls:
|
|
322
|
+
if u not in bucket:
|
|
323
|
+
bucket.append(u)
|
|
324
|
+
seen = {a["url"] for a in out["affiliate_links"]}
|
|
325
|
+
for a in r.get("affiliate_links", []):
|
|
326
|
+
if a["url"] not in seen:
|
|
327
|
+
out["affiliate_links"].append(a)
|
|
328
|
+
seen.add(a["url"])
|
|
329
|
+
return out
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# --- HTML pages ---------------------------------------------------------------
|
|
333
|
+
|
|
334
|
+
class _PageParser(HTMLParser):
|
|
335
|
+
_SKIP = {"script", "style", "noscript", "svg", "template"}
|
|
336
|
+
|
|
337
|
+
def __init__(self):
|
|
338
|
+
super().__init__(convert_charrefs=True)
|
|
339
|
+
self.hrefs: list[str] = []
|
|
340
|
+
self.text: list[str] = []
|
|
341
|
+
self.title = ""
|
|
342
|
+
self._skip_depth = 0
|
|
343
|
+
self._in_title = False
|
|
344
|
+
|
|
345
|
+
def handle_starttag(self, tag, attrs):
|
|
346
|
+
if tag in self._SKIP:
|
|
347
|
+
self._skip_depth += 1
|
|
348
|
+
if tag == "title":
|
|
349
|
+
self._in_title = True
|
|
350
|
+
if tag == "a":
|
|
351
|
+
for name, value in attrs:
|
|
352
|
+
if name == "href" and value:
|
|
353
|
+
self.hrefs.append(value.strip())
|
|
354
|
+
|
|
355
|
+
def handle_endtag(self, tag):
|
|
356
|
+
if tag in self._SKIP and self._skip_depth:
|
|
357
|
+
self._skip_depth -= 1
|
|
358
|
+
if tag == "title":
|
|
359
|
+
self._in_title = False
|
|
360
|
+
|
|
361
|
+
def handle_data(self, data):
|
|
362
|
+
if self._in_title:
|
|
363
|
+
self.title += data
|
|
364
|
+
elif not self._skip_depth:
|
|
365
|
+
self.text.append(data)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# Kinds safe to take from anywhere in a page's source: a profile URL is a profile
|
|
369
|
+
# whether it sits in an anchor or in a JSON blob. Plain sites, link-in-bio pages and
|
|
370
|
+
# affiliate links are NOT taken from raw source: link-in-bio services embed their
|
|
371
|
+
# own assets and sponsored offers there, and those are not the creator's.
|
|
372
|
+
_RAW_SAFE_KINDS = ("linkedin", "x", "youtube", "instagram", "tiktok", "facebook",
|
|
373
|
+
"github", "booking", "newsletter")
|
|
374
|
+
|
|
375
|
+
_NEXT_DATA_RE = re.compile(
|
|
376
|
+
r"""<script[^>]*id=["']__NEXT_DATA__["'][^>]*>(.*?)</script>""",
|
|
377
|
+
re.IGNORECASE | re.DOTALL)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _site(host: str) -> str:
|
|
381
|
+
labels = [x for x in (host or "").split(".") if x]
|
|
382
|
+
return ".".join(labels[-2:])
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def next_data_links(page: str) -> tuple[list[str], list[str]]:
|
|
386
|
+
"""Links a creator chose, from a Next.js page's ``__NEXT_DATA__`` JSON.
|
|
387
|
+
|
|
388
|
+
Link-in-bio services built on Next.js (Linktree among them) render no anchors
|
|
389
|
+
server-side: the creator's buttons live in ``props.pageProps.links[].url``, the
|
|
390
|
+
social icons in ``socialLinks[].url`` (``pinnedLinks`` too) and the bio in
|
|
391
|
+
``pageProps.description``. Returns ``(urls, texts)``, empty when the page has no
|
|
392
|
+
such blob. Sponsored offers the platform injects sit elsewhere in the same JSON
|
|
393
|
+
and are deliberately not read.
|
|
394
|
+
"""
|
|
395
|
+
m = _NEXT_DATA_RE.search(page or "")
|
|
396
|
+
if not m:
|
|
397
|
+
return [], []
|
|
398
|
+
try:
|
|
399
|
+
data = json.loads(m.group(1))
|
|
400
|
+
except ValueError:
|
|
401
|
+
return [], []
|
|
402
|
+
props = data.get("props") if isinstance(data, dict) else None
|
|
403
|
+
pp = props.get("pageProps") if isinstance(props, dict) else None
|
|
404
|
+
if not isinstance(pp, dict):
|
|
405
|
+
return [], []
|
|
406
|
+
urls: list[str] = []
|
|
407
|
+
for k in ("links", "socialLinks", "pinnedLinks"):
|
|
408
|
+
items = pp.get(k)
|
|
409
|
+
for item in items if isinstance(items, list) else []:
|
|
410
|
+
if isinstance(item, dict) and isinstance(item.get("url"), str):
|
|
411
|
+
urls.append(item["url"].strip())
|
|
412
|
+
texts = [pp["description"]] if isinstance(pp.get("description"), str) else []
|
|
413
|
+
return urls, texts
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _split_hrefs(hrefs: list[str], base_url: str) -> tuple[list[str], list[str]]:
|
|
417
|
+
"""(emails from mailto: links, absolute http(s) URLs)."""
|
|
418
|
+
mailto: list[str] = []
|
|
419
|
+
urls: list[str] = []
|
|
420
|
+
for h in hrefs:
|
|
421
|
+
h = _html.unescape(h).strip()
|
|
422
|
+
low = h.lower()
|
|
423
|
+
if low.startswith("mailto:"):
|
|
424
|
+
mailto.extend(find_emails(h[7:].split("?", 1)[0]))
|
|
425
|
+
elif low.startswith(("http://", "https://")):
|
|
426
|
+
urls.append(h)
|
|
427
|
+
elif h.startswith("/") and base_url:
|
|
428
|
+
urls.append(urljoin(base_url, h))
|
|
429
|
+
return mailto, urls
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def extract_html(page: str, base_url: str = "") -> dict:
|
|
433
|
+
"""Contacts on an HTML page, from three places:
|
|
434
|
+
|
|
435
|
+
1. ``mailto:`` links, anchor hrefs and visible text (what a visitor sees);
|
|
436
|
+
2. the links a creator chose on a Next.js link-in-bio page (``next_data_links``);
|
|
437
|
+
3. profile URLs (LinkedIn, X, Instagram, ...) anywhere in the raw source.
|
|
438
|
+
|
|
439
|
+
Links back to the page's own site are dropped from the link-in-bio / website /
|
|
440
|
+
other buckets: on a link-in-bio page they are navigation and assets, not the
|
|
441
|
+
creator's destinations.
|
|
442
|
+
"""
|
|
443
|
+
parser = _PageParser()
|
|
444
|
+
try:
|
|
445
|
+
parser.feed(page or "")
|
|
446
|
+
parser.close()
|
|
447
|
+
except Exception: # noqa: BLE001 - a malformed page still yields what was parsed
|
|
448
|
+
pass
|
|
449
|
+
|
|
450
|
+
mailto, hrefs = _split_hrefs(parser.hrefs, base_url)
|
|
451
|
+
visible = " ".join(t.strip() for t in parser.text if t.strip())
|
|
452
|
+
shown = merge({"emails": mailto, "links": {}, "affiliate_links": []},
|
|
453
|
+
extract(visible + "\n" + "\n".join(hrefs)))
|
|
454
|
+
|
|
455
|
+
nd_urls, nd_texts = next_data_links(page)
|
|
456
|
+
nd_mailto, nd_hrefs = _split_hrefs(nd_urls, base_url)
|
|
457
|
+
chosen = merge({"emails": nd_mailto, "links": {}, "affiliate_links": []},
|
|
458
|
+
extract("\n".join(nd_texts + nd_hrefs)))
|
|
459
|
+
|
|
460
|
+
raw_all = extract(_html.unescape(page or ""))
|
|
461
|
+
raw = {"emails": [], "affiliate_links": [],
|
|
462
|
+
"links": {k: (v if k in _RAW_SAFE_KINDS else []) for k, v in raw_all["links"].items()}}
|
|
463
|
+
|
|
464
|
+
result = merge(shown, chosen, raw)
|
|
465
|
+
site = _site(_host(base_url)) if base_url else ""
|
|
466
|
+
if site:
|
|
467
|
+
for kind in ("link_in_bio", "website", "other"):
|
|
468
|
+
result["links"][kind] = [u for u in result["links"].get(kind, [])
|
|
469
|
+
if _site(_host(u)) != site]
|
|
470
|
+
result["title"] = " ".join(parser.title.split())
|
|
471
|
+
return result
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def collect(sources: list[tuple[str, str]]) -> dict:
|
|
475
|
+
"""Extract from several labelled texts, keeping where each find came from.
|
|
476
|
+
|
|
477
|
+
``sources`` is ``[(source_label, text), ...]`` (e.g. ``("video:abc", desc)``).
|
|
478
|
+
Returns emails as ``[{"email", "sources": [...]}]``, links grouped by kind, and
|
|
479
|
+
affiliate links each carrying the ``source`` it was first seen in.
|
|
480
|
+
"""
|
|
481
|
+
emails: dict[str, list[str]] = {}
|
|
482
|
+
merged = empty_result()
|
|
483
|
+
affiliates: list[dict] = []
|
|
484
|
+
seen: set[str] = set()
|
|
485
|
+
for source, text in sources:
|
|
486
|
+
r = extract(text or "")
|
|
487
|
+
for e in r["emails"]:
|
|
488
|
+
emails.setdefault(e, [])
|
|
489
|
+
if source not in emails[e]:
|
|
490
|
+
emails[e].append(source)
|
|
491
|
+
merged = merge(merged, {"emails": [], "links": r["links"], "affiliate_links": []})
|
|
492
|
+
for a in r["affiliate_links"]:
|
|
493
|
+
if a["url"] not in seen:
|
|
494
|
+
affiliates.append({**a, "source": source})
|
|
495
|
+
seen.add(a["url"])
|
|
496
|
+
kept = drop_shadowed(list(emails))
|
|
497
|
+
return {
|
|
498
|
+
"emails": [{"email": e, "sources": emails[e]} for e in kept],
|
|
499
|
+
"links": merged["links"],
|
|
500
|
+
"affiliate_links": affiliates,
|
|
501
|
+
"sources_scanned": len(sources),
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# --- LinkedIn profiles found by a person's name -------------------------------
|
|
506
|
+
|
|
507
|
+
def fold(text: str) -> str:
|
|
508
|
+
"""Lowercase ASCII fold: accents dropped, so 'Ștefan' and 'stefan' compare equal."""
|
|
509
|
+
return "".join(c for c in unicodedata.normalize("NFKD", text or "")
|
|
510
|
+
if not unicodedata.combining(c)).lower()
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def linkedin_slug(url: str) -> str:
|
|
514
|
+
"""The profile id in a linkedin.com/in/<slug> URL ('' when it is not one)."""
|
|
515
|
+
try:
|
|
516
|
+
parts = urlsplit(url if "://" in url else "https://" + url)
|
|
517
|
+
except ValueError:
|
|
518
|
+
return ""
|
|
519
|
+
if not _host_matches(_host(url if "://" in url else "https://" + url), ("linkedin.com",)):
|
|
520
|
+
return ""
|
|
521
|
+
segs = [x for x in parts.path.split("/") if x]
|
|
522
|
+
if len(segs) >= 2 and segs[0].lower() == "in":
|
|
523
|
+
return unquote(segs[1])
|
|
524
|
+
return ""
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def normalize_linkedin_profile(url: str) -> str:
|
|
528
|
+
slug = linkedin_slug(url)
|
|
529
|
+
return f"https://www.linkedin.com/in/{slug}" if slug else ""
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def linkedin_slug_has_name(url: str, name: str) -> bool:
|
|
533
|
+
"""True when the profile id contains the FULL name: every name token, in order
|
|
534
|
+
(or reversed), joined by nothing, '-', '_' or '.' (``beau-carnes``,
|
|
535
|
+
``beaucarnes``, ``carnes-beau-12ab``). A single-word name never matches: one word
|
|
536
|
+
identifies nobody."""
|
|
537
|
+
slug = fold(linkedin_slug(url))
|
|
538
|
+
tokens = [t for t in re.split(r"[^a-z0-9]+", fold(name)) if t]
|
|
539
|
+
if not slug or len(tokens) < 2:
|
|
540
|
+
return False
|
|
541
|
+
sep = r"[-_.]?"
|
|
542
|
+
forward = sep.join(map(re.escape, tokens))
|
|
543
|
+
backward = sep.join(map(re.escape, reversed(tokens)))
|
|
544
|
+
return bool(re.search(forward, slug) or re.search(backward, slug))
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Credential access - a facade over a pluggable backend (see ``creds_backends``).
|
|
2
|
+
|
|
3
|
+
Public API is unchanged: ``get`` / ``get_optional`` / ``has`` / ``set_secret`` /
|
|
4
|
+
``delete``. The backend is chosen once by the ``AAT_CREDS_BACKEND`` env var; the
|
|
5
|
+
default (unset) is ``windows`` = Windows Credential Manager, the historical
|
|
6
|
+
behaviour, so a local Claude Code session is never affected. Import-safe on any OS:
|
|
7
|
+
the Windows/keyring code loads only when the windows backend is selected, so this
|
|
8
|
+
module imports cleanly inside a Linux container.
|
|
9
|
+
|
|
10
|
+
Credentials are identified as ``agents-and-tools:<tool>:<secret>``.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
from tools._lib import creds_backends
|
|
17
|
+
|
|
18
|
+
NAMESPACE = "agents-and-tools"
|
|
19
|
+
|
|
20
|
+
_backend = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _service(tool: str) -> str:
|
|
24
|
+
return f"{NAMESPACE}:{tool}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _b():
|
|
28
|
+
global _backend
|
|
29
|
+
if _backend is None:
|
|
30
|
+
_backend = creds_backends.make(os.environ.get("AAT_CREDS_BACKEND"))
|
|
31
|
+
return _backend
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def reset_backend() -> None:
|
|
35
|
+
"""Drop the cached backend. For tests, or after changing AAT_CREDS_BACKEND."""
|
|
36
|
+
global _backend
|
|
37
|
+
_backend = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get(tool: str, secret_name: str) -> str:
|
|
41
|
+
"""Return the secret value. Raises KeyError if missing."""
|
|
42
|
+
value = _b().get_optional(tool, secret_name)
|
|
43
|
+
if value is None:
|
|
44
|
+
raise KeyError(f"missing credential: {_service(tool)} / {secret_name}")
|
|
45
|
+
return value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_optional(tool: str, secret_name: str) -> str | None:
|
|
49
|
+
"""Return the secret value or None if missing. Never raises on absence."""
|
|
50
|
+
return _b().get_optional(tool, secret_name)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def has(tool: str, secret_name: str) -> bool:
|
|
54
|
+
return _b().get_optional(tool, secret_name) is not None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def has_for_report(tool: str, secret_name: str) -> bool:
|
|
58
|
+
"""Presence, for a listing or a view. NEVER raises.
|
|
59
|
+
|
|
60
|
+
Use this and only this where the answer decorates output: a `has_password`
|
|
61
|
+
field, a registry's `ready` flag, a dashboard tile. On a machine with no
|
|
62
|
+
reachable credential store - a headless Linux box has no session keyring at
|
|
63
|
+
all - constructing the backend throws, and a reporting call that propagates
|
|
64
|
+
that takes down the whole listing with it. Unreachable reports False, which
|
|
65
|
+
is the honest answer to "can I show a credential here": no.
|
|
66
|
+
|
|
67
|
+
Do NOT use it where the value is then acted on. `get` and `get_optional`
|
|
68
|
+
still raise on an unreachable store on purpose, because a store that cannot
|
|
69
|
+
be read must never be mistaken for a store that is empty; that is how a
|
|
70
|
+
caller ends up storing a secret again under the wrong key.
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
return has(tool, secret_name)
|
|
74
|
+
except Exception: # noqa: BLE001 - see docstring
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def set_secret(tool: str, secret_name: str, value: str) -> None:
|
|
79
|
+
_b().set(tool, secret_name, value)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def delete(tool: str, secret_name: str) -> None:
|
|
83
|
+
_b().delete(tool, secret_name)
|