@ketecode/loom 0.0.0-stage → 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +516 -2
- package/agents/accessibility-reviewer/AGENT.md +70 -0
- package/agents/accessibility-reviewer/evals/triggers.json +15 -0
- package/agents/agent-evaluator/AGENT.md +74 -0
- package/agents/agent-evaluator/evals/triggers.json +15 -0
- package/agents/architect/AGENT.md +82 -0
- package/agents/architect/evals/triggers.json +17 -0
- package/agents/billing-reviewer/AGENT.md +68 -0
- package/agents/billing-reviewer/evals/triggers.json +16 -0
- package/agents/code-reviewer/AGENT.md +51 -0
- package/agents/code-reviewer/evals/triggers.json +14 -0
- package/agents/code-simplifier/AGENT.md +74 -0
- package/agents/code-simplifier/evals/triggers.json +15 -0
- package/agents/cpp-build-resolver/AGENT.md +73 -0
- package/agents/cpp-build-resolver/evals/triggers.json +16 -0
- package/agents/cpp-reviewer/AGENT.md +59 -0
- package/agents/cpp-reviewer/evals/triggers.json +16 -0
- package/agents/csharp-build-resolver/AGENT.md +84 -0
- package/agents/csharp-build-resolver/evals/triggers.json +17 -0
- package/agents/csharp-reviewer/AGENT.md +88 -0
- package/agents/csharp-reviewer/evals/triggers.json +17 -0
- package/agents/dart-build-resolver/AGENT.md +80 -0
- package/agents/dart-build-resolver/evals/triggers.json +17 -0
- package/agents/dart-reviewer/AGENT.md +66 -0
- package/agents/dart-reviewer/evals/triggers.json +17 -0
- package/agents/database-reviewer/AGENT.md +72 -0
- package/agents/database-reviewer/evals/triggers.json +16 -0
- package/agents/django-reviewer/AGENT.md +61 -0
- package/agents/django-reviewer/evals/triggers.json +15 -0
- package/agents/doc-updater/AGENT.md +92 -0
- package/agents/doc-updater/evals/triggers.json +14 -0
- package/agents/e2e-runner/AGENT.md +88 -0
- package/agents/e2e-runner/evals/triggers.json +16 -0
- package/agents/fastapi-reviewer/AGENT.md +63 -0
- package/agents/fastapi-reviewer/evals/triggers.json +15 -0
- package/agents/flutter-reviewer/AGENT.md +70 -0
- package/agents/flutter-reviewer/evals/triggers.json +15 -0
- package/agents/fsharp-build-resolver/AGENT.md +81 -0
- package/agents/fsharp-build-resolver/evals/triggers.json +15 -0
- package/agents/fsharp-reviewer/AGENT.md +77 -0
- package/agents/fsharp-reviewer/evals/triggers.json +15 -0
- package/agents/go-build-resolver/AGENT.md +85 -0
- package/agents/go-build-resolver/evals/triggers.json +16 -0
- package/agents/go-reviewer/AGENT.md +66 -0
- package/agents/go-reviewer/evals/triggers.json +16 -0
- package/agents/healthcare-reviewer/AGENT.md +65 -0
- package/agents/healthcare-reviewer/evals/triggers.json +16 -0
- package/agents/i18n-reviewer/AGENT.md +64 -0
- package/agents/i18n-reviewer/evals/triggers.json +16 -0
- package/agents/infra-reviewer/AGENT.md +93 -0
- package/agents/infra-reviewer/evals/triggers.json +15 -0
- package/agents/java-build-resolver/AGENT.md +82 -0
- package/agents/java-build-resolver/evals/triggers.json +16 -0
- package/agents/java-reviewer/AGENT.md +64 -0
- package/agents/java-reviewer/evals/triggers.json +16 -0
- package/agents/kotlin-build-resolver/AGENT.md +89 -0
- package/agents/kotlin-build-resolver/evals/triggers.json +16 -0
- package/agents/kotlin-reviewer/AGENT.md +75 -0
- package/agents/kotlin-reviewer/evals/triggers.json +16 -0
- package/agents/mle-reviewer/AGENT.md +68 -0
- package/agents/mle-reviewer/evals/triggers.json +16 -0
- package/agents/network-config-reviewer/AGENT.md +68 -0
- package/agents/network-config-reviewer/evals/triggers.json +17 -0
- package/agents/payments-reviewer/AGENT.md +64 -0
- package/agents/payments-reviewer/evals/triggers.json +16 -0
- package/agents/performance-optimizer/AGENT.md +86 -0
- package/agents/performance-optimizer/evals/triggers.json +16 -0
- package/agents/perl-build-resolver/AGENT.md +84 -0
- package/agents/perl-build-resolver/evals/triggers.json +15 -0
- package/agents/perl-reviewer/AGENT.md +77 -0
- package/agents/perl-reviewer/evals/triggers.json +15 -0
- package/agents/php-build-resolver/AGENT.md +88 -0
- package/agents/php-build-resolver/evals/triggers.json +16 -0
- package/agents/php-reviewer/AGENT.md +61 -0
- package/agents/php-reviewer/evals/triggers.json +15 -0
- package/agents/planner/AGENT.md +75 -0
- package/agents/planner/evals/triggers.json +17 -0
- package/agents/pr-test-analyzer/AGENT.md +82 -0
- package/agents/pr-test-analyzer/evals/triggers.json +16 -0
- package/agents/python-build-resolver/AGENT.md +87 -0
- package/agents/python-build-resolver/evals/triggers.json +16 -0
- package/agents/python-reviewer/AGENT.md +63 -0
- package/agents/python-reviewer/evals/triggers.json +15 -0
- package/agents/rag-reviewer/AGENT.md +71 -0
- package/agents/rag-reviewer/evals/triggers.json +16 -0
- package/agents/react-reviewer/AGENT.md +70 -0
- package/agents/react-reviewer/evals/triggers.json +16 -0
- package/agents/refactor-cleaner/AGENT.md +82 -0
- package/agents/refactor-cleaner/evals/triggers.json +16 -0
- package/agents/ruby-build-resolver/AGENT.md +93 -0
- package/agents/ruby-build-resolver/evals/triggers.json +16 -0
- package/agents/ruby-reviewer/AGENT.md +62 -0
- package/agents/ruby-reviewer/evals/triggers.json +16 -0
- package/agents/rust-build-resolver/AGENT.md +98 -0
- package/agents/rust-build-resolver/evals/triggers.json +16 -0
- package/agents/rust-reviewer/AGENT.md +78 -0
- package/agents/rust-reviewer/evals/triggers.json +15 -0
- package/agents/security-reviewer/AGENT.md +84 -0
- package/agents/security-reviewer/evals/triggers.json +16 -0
- package/agents/silent-failure-hunter/AGENT.md +66 -0
- package/agents/silent-failure-hunter/evals/triggers.json +16 -0
- package/agents/solidity-reviewer/AGENT.md +70 -0
- package/agents/solidity-reviewer/evals/triggers.json +16 -0
- package/agents/swift-build-resolver/AGENT.md +85 -0
- package/agents/swift-build-resolver/evals/triggers.json +15 -0
- package/agents/swift-reviewer/AGENT.md +75 -0
- package/agents/swift-reviewer/evals/triggers.json +15 -0
- package/agents/typescript-build-resolver/AGENT.md +76 -0
- package/agents/typescript-build-resolver/evals/triggers.json +15 -0
- package/agents/typescript-reviewer/AGENT.md +76 -0
- package/agents/typescript-reviewer/evals/triggers.json +15 -0
- package/agents/vue-reviewer/AGENT.md +75 -0
- package/agents/vue-reviewer/evals/triggers.json +15 -0
- package/bin/kete-loom.js +683 -0
- package/commands/aside.md +19 -0
- package/commands/build-fix.md +26 -0
- package/commands/campaign.md +19 -0
- package/commands/changelog.md +36 -0
- package/commands/checkpoint.md +26 -0
- package/commands/codemap.md +25 -0
- package/commands/commit.md +26 -0
- package/commands/deps-audit.md +32 -0
- package/commands/epic-decompose.md +21 -0
- package/commands/explain.md +37 -0
- package/commands/feature-dev.md +22 -0
- package/commands/hookify.md +23 -0
- package/commands/i18n-check.md +39 -0
- package/commands/issue-sync.md +22 -0
- package/commands/jira.md +19 -0
- package/commands/learn.md +23 -0
- package/commands/loom.md +21 -0
- package/commands/loop.md +20 -0
- package/commands/model-route.md +27 -0
- package/commands/onboard.md +35 -0
- package/commands/orchestrate.md +21 -0
- package/commands/plan-feature.md +36 -0
- package/commands/postmortem.md +42 -0
- package/commands/pr-describe.md +39 -0
- package/commands/pr.md +22 -0
- package/commands/prd.md +41 -0
- package/commands/project-init.md +21 -0
- package/commands/refactor-clean.md +24 -0
- package/commands/resume-session.md +22 -0
- package/commands/review.md +35 -0
- package/commands/save-session.md +37 -0
- package/commands/security-scan.md +32 -0
- package/commands/sessions.md +21 -0
- package/commands/skill-create.md +21 -0
- package/commands/tdd.md +36 -0
- package/commands/test-coverage.md +24 -0
- package/commands/threat-model.md +36 -0
- package/commands/update-docs.md +25 -0
- package/commands/verify.md +21 -0
- package/hooks/block-dangerous-commands/HOOK.md +30 -0
- package/hooks/block-dangerous-commands/cases.json +50 -0
- package/hooks/block-dangerous-commands/hook.js +75 -0
- package/hooks/block-no-verify/HOOK.md +25 -0
- package/hooks/block-no-verify/cases.json +30 -0
- package/hooks/block-no-verify/hook.js +43 -0
- package/hooks/format-on-edit/HOOK.md +24 -0
- package/hooks/format-on-edit/hook.js +89 -0
- package/hooks/guard-secrets/HOOK.md +19 -0
- package/hooks/guard-secrets/cases.json +110 -0
- package/hooks/guard-secrets/hook.js +44 -0
- package/hooks/load-session-context/HOOK.md +20 -0
- package/hooks/load-session-context/hook.js +56 -0
- package/hooks/protect-files/HOOK.md +24 -0
- package/hooks/protect-files/cases.json +27 -0
- package/hooks/protect-files/hook.js +39 -0
- package/hooks/run.js +132 -0
- package/mcp/atlassian.json +11 -0
- package/mcp/chrome-devtools.json +18 -0
- package/mcp/cloudflare.json +11 -0
- package/mcp/context7.json +11 -0
- package/mcp/fetch.json +17 -0
- package/mcp/figma.json +11 -0
- package/mcp/git.json +17 -0
- package/mcp/github.json +11 -0
- package/mcp/linear.json +11 -0
- package/mcp/memory.json +18 -0
- package/mcp/notion.json +11 -0
- package/mcp/playwright.json +18 -0
- package/mcp/sentry.json +11 -0
- package/mcp/stripe.json +11 -0
- package/mcp/supabase.json +11 -0
- package/mcp/time.json +17 -0
- package/package.json +59 -4
- package/profiles.json +165 -0
- package/rules/common.md +21 -0
- package/rules/cpp.md +29 -0
- package/rules/csharp.md +25 -0
- package/rules/dart.md +29 -0
- package/rules/fsharp.md +28 -0
- package/rules/git.md +26 -0
- package/rules/go.md +29 -0
- package/rules/java.md +29 -0
- package/rules/kotlin.md +27 -0
- package/rules/perl.md +26 -0
- package/rules/php.md +28 -0
- package/rules/python.md +30 -0
- package/rules/ruby.md +27 -0
- package/rules/rust.md +26 -0
- package/rules/security.md +29 -0
- package/rules/swift.md +28 -0
- package/rules/testing.md +25 -0
- package/rules/typescript.md +29 -0
- package/schemas/skill-frontmatter.schema.json +52 -0
- package/scripts/lib/audit.js +96 -0
- package/scripts/lib/frontmatter.js +65 -0
- package/scripts/lib/hooks-install.js +292 -0
- package/scripts/lib/i18n.js +51 -0
- package/scripts/lib/inventory.js +85 -0
- package/scripts/lib/lint.js +158 -0
- package/scripts/lib/mcp-install.js +284 -0
- package/scripts/lib/profiles.js +128 -0
- package/scripts/lib/render.js +86 -0
- package/scripts/lib/rules-install.js +118 -0
- package/scripts/lib/skills.js +86 -0
- package/scripts/lib/targets.js +113 -0
- package/skills/accounting-api-integration/SKILL.md +81 -0
- package/skills/accounting-api-integration/evals/triggers.json +17 -0
- package/skills/accounting-api-integration/references/quickbooks.md +48 -0
- package/skills/accounting-api-integration/references/xero.md +45 -0
- package/skills/agent-harness-design/SKILL.md +125 -0
- package/skills/agent-harness-design/evals/triggers.json +21 -0
- package/skills/ai-media-generation/SKILL.md +89 -0
- package/skills/ai-media-generation/evals/triggers.json +16 -0
- package/skills/ai-media-generation/references/job-pipeline.md +76 -0
- package/skills/angular-patterns/SKILL.md +122 -0
- package/skills/angular-patterns/evals/triggers.json +16 -0
- package/skills/api-design/SKILL.md +96 -0
- package/skills/api-design/evals/triggers.json +18 -0
- package/skills/api-design/references/conventions.md +133 -0
- package/skills/architecture-decision-records/SKILL.md +98 -0
- package/skills/architecture-decision-records/evals/triggers.json +17 -0
- package/skills/article-writing/SKILL.md +108 -0
- package/skills/article-writing/evals/triggers.json +16 -0
- package/skills/aspnet-core-patterns/SKILL.md +149 -0
- package/skills/aspnet-core-patterns/evals/triggers.json +16 -0
- package/skills/auth-patterns/SKILL.md +104 -0
- package/skills/auth-patterns/evals/triggers.json +17 -0
- package/skills/auth-patterns/references/oauth-oidc.md +64 -0
- package/skills/autonomous-loops/SKILL.md +99 -0
- package/skills/autonomous-loops/evals/triggers.json +14 -0
- package/skills/aws-patterns/SKILL.md +91 -0
- package/skills/aws-patterns/evals/triggers.json +16 -0
- package/skills/azure-patterns/SKILL.md +86 -0
- package/skills/azure-patterns/evals/triggers.json +16 -0
- package/skills/benchmark-methodology/SKILL.md +88 -0
- package/skills/benchmark-methodology/evals/triggers.json +16 -0
- package/skills/benchmark-methodology/references/language-harnesses.md +130 -0
- package/skills/bgp-diagnostics/SKILL.md +84 -0
- package/skills/bgp-diagnostics/evals/triggers.json +18 -0
- package/skills/bgp-diagnostics/references/bgp-reference.md +81 -0
- package/skills/bioinformatics-workflows/SKILL.md +108 -0
- package/skills/bioinformatics-workflows/evals/triggers.json +16 -0
- package/skills/bioinformatics-workflows/references/commands.md +71 -0
- package/skills/bioinformatics-workflows/references/formats.md +24 -0
- package/skills/bioinformatics-workflows/references/workflow-managers.md +107 -0
- package/skills/brand-voice/SKILL.md +112 -0
- package/skills/brand-voice/evals/triggers.json +16 -0
- package/skills/browser-qa/SKILL.md +99 -0
- package/skills/browser-qa/evals/triggers.json +16 -0
- package/skills/bun-runtime/SKILL.md +93 -0
- package/skills/bun-runtime/evals/triggers.json +16 -0
- package/skills/cisco-ios-config/SKILL.md +101 -0
- package/skills/cisco-ios-config/evals/triggers.json +18 -0
- package/skills/cisco-ios-config/references/verification.md +63 -0
- package/skills/clickhouse-patterns/SKILL.md +101 -0
- package/skills/clickhouse-patterns/evals/triggers.json +18 -0
- package/skills/clickhouse-patterns/references/engines-and-views.md +138 -0
- package/skills/clinical-decision-support/SKILL.md +90 -0
- package/skills/clinical-decision-support/evals/triggers.json +16 -0
- package/skills/clinical-decision-support/references/cds-hooks.md +76 -0
- package/skills/clinical-decision-support/references/regulatory-boundaries.md +37 -0
- package/skills/code-tour/SKILL.md +97 -0
- package/skills/code-tour/evals/triggers.json +14 -0
- package/skills/codebase-onboarding/SKILL.md +112 -0
- package/skills/codebase-onboarding/evals/triggers.json +14 -0
- package/skills/cold-outreach-sequences/SKILL.md +90 -0
- package/skills/cold-outreach-sequences/evals/triggers.json +16 -0
- package/skills/content-repurposing/SKILL.md +82 -0
- package/skills/content-repurposing/evals/triggers.json +16 -0
- package/skills/context-budget/SKILL.md +84 -0
- package/skills/context-budget/evals/triggers.json +14 -0
- package/skills/conventional-commits/SKILL.md +144 -0
- package/skills/conventional-commits/evals/triggers.json +16 -0
- package/skills/cpp-patterns/SKILL.md +129 -0
- package/skills/cpp-patterns/evals/triggers.json +16 -0
- package/skills/cpp-patterns/references/cmake-and-tooling.md +88 -0
- package/skills/cpp-patterns/references/undefined-behaviour.md +75 -0
- package/skills/cpp-testing/SKILL.md +131 -0
- package/skills/cpp-testing/evals/triggers.json +16 -0
- package/skills/csharp-patterns/SKILL.md +110 -0
- package/skills/csharp-patterns/evals/triggers.json +17 -0
- package/skills/csharp-patterns/references/analyzers.md +59 -0
- package/skills/csharp-testing/SKILL.md +85 -0
- package/skills/csharp-testing/evals/triggers.json +17 -0
- package/skills/csharp-testing/references/integration-tests.md +77 -0
- package/skills/css-architecture/SKILL.md +143 -0
- package/skills/css-architecture/evals/triggers.json +16 -0
- package/skills/customs-trade-compliance/SKILL.md +95 -0
- package/skills/customs-trade-compliance/evals/triggers.json +16 -0
- package/skills/customs-trade-compliance/references/incoterms-2020.md +42 -0
- package/skills/dart-patterns/SKILL.md +157 -0
- package/skills/dart-patterns/evals/triggers.json +18 -0
- package/skills/dart-testing/SKILL.md +164 -0
- package/skills/dart-testing/evals/triggers.json +18 -0
- package/skills/data-analysis-notebooks/SKILL.md +113 -0
- package/skills/data-analysis-notebooks/evals/triggers.json +16 -0
- package/skills/data-analysis-notebooks/references/dataframe-pitfalls.md +49 -0
- package/skills/data-analysis-notebooks/references/statistics-pitfalls.md +24 -0
- package/skills/data-pipelines/SKILL.md +90 -0
- package/skills/data-pipelines/evals/triggers.json +16 -0
- package/skills/data-pipelines/references/orchestrators.md +64 -0
- package/skills/database-migrations/SKILL.md +102 -0
- package/skills/database-migrations/evals/triggers.json +16 -0
- package/skills/database-migrations/references/recipes.md +98 -0
- package/skills/database-migrations/references/tools.md +21 -0
- package/skills/dbt-patterns/SKILL.md +96 -0
- package/skills/dbt-patterns/evals/triggers.json +16 -0
- package/skills/dbt-patterns/references/yaml-examples.md +114 -0
- package/skills/deep-research/SKILL.md +107 -0
- package/skills/deep-research/evals/triggers.json +14 -0
- package/skills/dependency-audit/SKILL.md +103 -0
- package/skills/dependency-audit/evals/triggers.json +16 -0
- package/skills/dependency-audit/references/tools.md +124 -0
- package/skills/deployment-strategies/SKILL.md +108 -0
- package/skills/deployment-strategies/evals/triggers.json +16 -0
- package/skills/django-patterns/SKILL.md +123 -0
- package/skills/django-patterns/evals/triggers.json +16 -0
- package/skills/docker-patterns/SKILL.md +132 -0
- package/skills/docker-patterns/evals/triggers.json +16 -0
- package/skills/docker-patterns/references/dockerfiles.md +105 -0
- package/skills/e2e-testing-playwright/SKILL.md +94 -0
- package/skills/e2e-testing-playwright/evals/triggers.json +16 -0
- package/skills/e2e-testing-playwright/references/config-and-ci.md +129 -0
- package/skills/ef-core-patterns/SKILL.md +117 -0
- package/skills/ef-core-patterns/evals/triggers.json +16 -0
- package/skills/email-deliverability/SKILL.md +99 -0
- package/skills/email-deliverability/evals/triggers.json +17 -0
- package/skills/email-deliverability/references/dns-records.md +85 -0
- package/skills/error-handling-patterns/SKILL.md +112 -0
- package/skills/error-handling-patterns/evals/triggers.json +18 -0
- package/skills/error-handling-patterns/references/language-idioms.md +49 -0
- package/skills/estimation-and-scoping/SKILL.md +97 -0
- package/skills/estimation-and-scoping/evals/triggers.json +14 -0
- package/skills/evm-dapp-integration/SKILL.md +73 -0
- package/skills/evm-dapp-integration/evals/triggers.json +16 -0
- package/skills/fastapi-patterns/SKILL.md +127 -0
- package/skills/fastapi-patterns/evals/triggers.json +16 -0
- package/skills/ffmpeg-media-processing/SKILL.md +94 -0
- package/skills/ffmpeg-media-processing/evals/triggers.json +16 -0
- package/skills/ffmpeg-media-processing/references/recipes.md +112 -0
- package/skills/fhir-integration/SKILL.md +81 -0
- package/skills/fhir-integration/evals/triggers.json +16 -0
- package/skills/fhir-integration/references/search-and-bundles.md +73 -0
- package/skills/fhir-integration/references/smart-on-fhir.md +50 -0
- package/skills/flask-patterns/SKILL.md +132 -0
- package/skills/flask-patterns/evals/triggers.json +16 -0
- package/skills/flutter-patterns/SKILL.md +99 -0
- package/skills/flutter-patterns/evals/triggers.json +16 -0
- package/skills/flutterwave-integration/SKILL.md +75 -0
- package/skills/flutterwave-integration/evals/triggers.json +16 -0
- package/skills/flutterwave-integration/references/api.md +73 -0
- package/skills/flutterwave-integration/references/webhooks.md +49 -0
- package/skills/fsharp-patterns/SKILL.md +133 -0
- package/skills/fsharp-patterns/evals/triggers.json +16 -0
- package/skills/fsharp-testing/SKILL.md +136 -0
- package/skills/fsharp-testing/evals/triggers.json +16 -0
- package/skills/gcp-patterns/SKILL.md +86 -0
- package/skills/gcp-patterns/evals/triggers.json +16 -0
- package/skills/git-workflow/SKILL.md +114 -0
- package/skills/git-workflow/evals/triggers.json +16 -0
- package/skills/github-actions-ci/SKILL.md +117 -0
- package/skills/github-actions-ci/evals/triggers.json +16 -0
- package/skills/github-api-automation/SKILL.md +101 -0
- package/skills/github-api-automation/evals/triggers.json +17 -0
- package/skills/go-patterns/SKILL.md +118 -0
- package/skills/go-patterns/evals/triggers.json +17 -0
- package/skills/go-patterns/references/concurrency.md +162 -0
- package/skills/go-testing/SKILL.md +154 -0
- package/skills/go-testing/evals/triggers.json +17 -0
- package/skills/hexagonal-architecture/SKILL.md +89 -0
- package/skills/hexagonal-architecture/evals/triggers.json +17 -0
- package/skills/hipaa-compliance/SKILL.md +91 -0
- package/skills/hipaa-compliance/evals/triggers.json +18 -0
- package/skills/hipaa-compliance/references/safeguards.md +68 -0
- package/skills/hl7v2-integration/SKILL.md +69 -0
- package/skills/hl7v2-integration/evals/triggers.json +16 -0
- package/skills/hl7v2-integration/references/messages-and-ack.md +78 -0
- package/skills/homelab-network-setup/SKILL.md +125 -0
- package/skills/homelab-network-setup/evals/triggers.json +18 -0
- package/skills/homelab-network-setup/references/firewall-and-dns.md +45 -0
- package/skills/homelab-network-setup/references/wireguard.md +72 -0
- package/skills/hubtel-integration/SKILL.md +77 -0
- package/skills/hubtel-integration/evals/triggers.json +16 -0
- package/skills/hubtel-integration/references/api.md +92 -0
- package/skills/i18n-localization/SKILL.md +114 -0
- package/skills/i18n-localization/evals/triggers.json +16 -0
- package/skills/i18n-localization/references/libraries.md +52 -0
- package/skills/incident-postmortem/SKILL.md +118 -0
- package/skills/incident-postmortem/evals/triggers.json +14 -0
- package/skills/inventory-demand-planning/SKILL.md +89 -0
- package/skills/inventory-demand-planning/evals/triggers.json +16 -0
- package/skills/inventory-demand-planning/references/formulas.md +62 -0
- package/skills/investor-materials/SKILL.md +127 -0
- package/skills/investor-materials/evals/triggers.json +16 -0
- package/skills/investor-materials/references/templates.md +74 -0
- package/skills/invoicing-and-tax/SKILL.md +84 -0
- package/skills/invoicing-and-tax/evals/triggers.json +17 -0
- package/skills/invoicing-and-tax/references/e-invoicing.md +24 -0
- package/skills/invoicing-and-tax/references/invoice-fields.md +38 -0
- package/skills/invoicing-and-tax/references/rounding.md +26 -0
- package/skills/java-patterns/SKILL.md +141 -0
- package/skills/java-patterns/evals/triggers.json +17 -0
- package/skills/java-testing/SKILL.md +142 -0
- package/skills/java-testing/evals/triggers.json +16 -0
- package/skills/jetpack-compose-patterns/SKILL.md +88 -0
- package/skills/jetpack-compose-patterns/evals/triggers.json +16 -0
- package/skills/jira-integration/SKILL.md +92 -0
- package/skills/jira-integration/evals/triggers.json +17 -0
- package/skills/jpa-hibernate/SKILL.md +92 -0
- package/skills/jpa-hibernate/evals/triggers.json +18 -0
- package/skills/jpa-hibernate/references/mapping.md +99 -0
- package/skills/kotlin-multiplatform/SKILL.md +97 -0
- package/skills/kotlin-multiplatform/evals/triggers.json +16 -0
- package/skills/kotlin-patterns/SKILL.md +128 -0
- package/skills/kotlin-patterns/evals/triggers.json +16 -0
- package/skills/kotlin-testing/SKILL.md +117 -0
- package/skills/kotlin-testing/evals/triggers.json +16 -0
- package/skills/ktor-patterns/SKILL.md +135 -0
- package/skills/ktor-patterns/evals/triggers.json +18 -0
- package/skills/kubernetes-patterns/SKILL.md +106 -0
- package/skills/kubernetes-patterns/evals/triggers.json +16 -0
- package/skills/kubernetes-patterns/references/manifests.md +115 -0
- package/skills/laravel-patterns/SKILL.md +136 -0
- package/skills/laravel-patterns/evals/triggers.json +16 -0
- package/skills/linear-integration/SKILL.md +98 -0
- package/skills/linear-integration/evals/triggers.json +17 -0
- package/skills/literature-review/SKILL.md +109 -0
- package/skills/literature-review/evals/triggers.json +16 -0
- package/skills/literature-review/references/appraisal.md +41 -0
- package/skills/literature-review/references/search-sources.md +39 -0
- package/skills/llm-app-patterns/SKILL.md +97 -0
- package/skills/llm-app-patterns/evals/triggers.json +17 -0
- package/skills/llm-evals/SKILL.md +98 -0
- package/skills/llm-evals/evals/triggers.json +16 -0
- package/skills/llm-evals/references/graders.md +66 -0
- package/skills/logistics-exception-management/SKILL.md +90 -0
- package/skills/logistics-exception-management/evals/triggers.json +16 -0
- package/skills/manim-animations/SKILL.md +106 -0
- package/skills/manim-animations/evals/triggers.json +16 -0
- package/skills/market-research/SKILL.md +117 -0
- package/skills/market-research/evals/triggers.json +16 -0
- package/skills/marketing-campaign/SKILL.md +111 -0
- package/skills/marketing-campaign/evals/triggers.json +16 -0
- package/skills/mcp-server-patterns/SKILL.md +127 -0
- package/skills/mcp-server-patterns/evals/triggers.json +18 -0
- package/skills/mcp-server-patterns/references/protocol-notes.md +68 -0
- package/skills/metrics-dashboards/SKILL.md +113 -0
- package/skills/metrics-dashboards/evals/triggers.json +16 -0
- package/skills/ml-experiment-workflow/SKILL.md +125 -0
- package/skills/ml-experiment-workflow/evals/triggers.json +16 -0
- package/skills/mongodb-patterns/SKILL.md +115 -0
- package/skills/mongodb-patterns/evals/triggers.json +18 -0
- package/skills/mongodb-patterns/references/indexes-and-aggregation.md +94 -0
- package/skills/mpesa-daraja-integration/SKILL.md +70 -0
- package/skills/mpesa-daraja-integration/evals/triggers.json +16 -0
- package/skills/mpesa-daraja-integration/references/api.md +103 -0
- package/skills/mpesa-daraja-integration/references/callbacks.md +74 -0
- package/skills/mtn-momo-integration/SKILL.md +66 -0
- package/skills/mtn-momo-integration/evals/triggers.json +16 -0
- package/skills/mtn-momo-integration/references/api.md +153 -0
- package/skills/mtn-momo-integration/references/callbacks.md +92 -0
- package/skills/mtn-momo-integration/references/sandbox-and-go-live.md +51 -0
- package/skills/mtn-momo-integration/scripts/sandbox-setup.js +79 -0
- package/skills/mysql-patterns/SKILL.md +96 -0
- package/skills/mysql-patterns/evals/triggers.json +16 -0
- package/skills/mysql-patterns/references/explain.md +34 -0
- package/skills/mysql-patterns/references/online-ddl.md +41 -0
- package/skills/nestjs-patterns/SKILL.md +114 -0
- package/skills/nestjs-patterns/evals/triggers.json +16 -0
- package/skills/nestjs-patterns/references/testing.md +68 -0
- package/skills/network-automation/SKILL.md +107 -0
- package/skills/network-automation/evals/triggers.json +18 -0
- package/skills/network-automation/references/tool-snippets.md +153 -0
- package/skills/network-troubleshooting/SKILL.md +152 -0
- package/skills/network-troubleshooting/evals/triggers.json +18 -0
- package/skills/network-troubleshooting/references/commands.md +33 -0
- package/skills/nextjs-patterns/SKILL.md +150 -0
- package/skills/nextjs-patterns/evals/triggers.json +18 -0
- package/skills/nextjs-patterns/references/caching.md +87 -0
- package/skills/node-backend-patterns/SKILL.md +103 -0
- package/skills/node-backend-patterns/evals/triggers.json +16 -0
- package/skills/node-backend-patterns/references/templates.md +122 -0
- package/skills/nuxt-patterns/SKILL.md +103 -0
- package/skills/nuxt-patterns/evals/triggers.json +16 -0
- package/skills/parallel-agent-orchestration/SKILL.md +106 -0
- package/skills/parallel-agent-orchestration/evals/triggers.json +14 -0
- package/skills/paystack-integration/SKILL.md +59 -0
- package/skills/paystack-integration/evals/triggers.json +14 -0
- package/skills/paystack-integration/references/api.md +73 -0
- package/skills/paystack-integration/references/mobile-money.md +50 -0
- package/skills/paystack-integration/references/webhooks.md +80 -0
- package/skills/pci-dss-scope/SKILL.md +103 -0
- package/skills/pci-dss-scope/evals/triggers.json +18 -0
- package/skills/pci-dss-scope/references/saq-and-data.md +36 -0
- package/skills/perl-patterns/SKILL.md +135 -0
- package/skills/perl-patterns/evals/triggers.json +18 -0
- package/skills/perl-testing/SKILL.md +126 -0
- package/skills/perl-testing/evals/triggers.json +17 -0
- package/skills/php-patterns/SKILL.md +116 -0
- package/skills/php-patterns/evals/triggers.json +16 -0
- package/skills/php-testing/SKILL.md +123 -0
- package/skills/php-testing/evals/triggers.json +16 -0
- package/skills/postgres-patterns/SKILL.md +103 -0
- package/skills/postgres-patterns/evals/triggers.json +17 -0
- package/skills/postgres-patterns/references/explain.md +59 -0
- package/skills/postgres-patterns/references/locks.md +53 -0
- package/skills/prd-writing/SKILL.md +124 -0
- package/skills/prd-writing/evals/triggers.json +15 -0
- package/skills/prisma-patterns/SKILL.md +83 -0
- package/skills/prisma-patterns/evals/triggers.json +18 -0
- package/skills/prisma-patterns/references/prisma-8.md +46 -0
- package/skills/prisma-patterns/references/workflow.md +50 -0
- package/skills/privacy-compliance/SKILL.md +121 -0
- package/skills/privacy-compliance/evals/triggers.json +18 -0
- package/skills/privacy-compliance/references/jurisdictions.md +51 -0
- package/skills/production-scheduling/SKILL.md +98 -0
- package/skills/production-scheduling/evals/triggers.json +16 -0
- package/skills/prompt-engineering/SKILL.md +92 -0
- package/skills/prompt-engineering/evals/triggers.json +16 -0
- package/skills/python-patterns/SKILL.md +107 -0
- package/skills/python-patterns/evals/triggers.json +16 -0
- package/skills/python-patterns/references/tooling.md +101 -0
- package/skills/python-testing/SKILL.md +126 -0
- package/skills/python-testing/evals/triggers.json +16 -0
- package/skills/python-testing/references/mocking.md +71 -0
- package/skills/pytorch-patterns/SKILL.md +139 -0
- package/skills/pytorch-patterns/evals/triggers.json +16 -0
- package/skills/pytorch-patterns/references/debugging.md +39 -0
- package/skills/quarkus-patterns/SKILL.md +93 -0
- package/skills/quarkus-patterns/evals/triggers.json +18 -0
- package/skills/rag-pipeline-patterns/SKILL.md +117 -0
- package/skills/rag-pipeline-patterns/evals/triggers.json +18 -0
- package/skills/rag-pipeline-patterns/references/evaluation.md +37 -0
- package/skills/rag-pipeline-patterns/references/retrieval-recipes.md +78 -0
- package/skills/rails-patterns/SKILL.md +132 -0
- package/skills/rails-patterns/evals/triggers.json +16 -0
- package/skills/react-native-patterns/SKILL.md +101 -0
- package/skills/react-native-patterns/evals/triggers.json +16 -0
- package/skills/react-patterns/SKILL.md +140 -0
- package/skills/react-patterns/evals/triggers.json +18 -0
- package/skills/react-performance/SKILL.md +107 -0
- package/skills/react-performance/evals/triggers.json +18 -0
- package/skills/redis-patterns/SKILL.md +109 -0
- package/skills/redis-patterns/evals/triggers.json +18 -0
- package/skills/redis-patterns/references/operations.md +61 -0
- package/skills/refactoring-safely/SKILL.md +107 -0
- package/skills/refactoring-safely/evals/triggers.json +17 -0
- package/skills/release-management/SKILL.md +141 -0
- package/skills/release-management/evals/triggers.json +16 -0
- package/skills/remotion-video/SKILL.md +91 -0
- package/skills/remotion-video/evals/triggers.json +16 -0
- package/skills/remotion-video/references/lambda.md +66 -0
- package/skills/returns-reverse-logistics/SKILL.md +97 -0
- package/skills/returns-reverse-logistics/evals/triggers.json +16 -0
- package/skills/ruby-patterns/SKILL.md +127 -0
- package/skills/ruby-patterns/evals/triggers.json +18 -0
- package/skills/ruby-testing/SKILL.md +113 -0
- package/skills/ruby-testing/evals/triggers.json +18 -0
- package/skills/runbook-writing/SKILL.md +101 -0
- package/skills/runbook-writing/evals/triggers.json +14 -0
- package/skills/rust-patterns/SKILL.md +118 -0
- package/skills/rust-patterns/evals/triggers.json +16 -0
- package/skills/rust-patterns/references/cargo-and-clippy.md +105 -0
- package/skills/rust-patterns/references/errors-and-traits.md +97 -0
- package/skills/rust-testing/SKILL.md +148 -0
- package/skills/rust-testing/evals/triggers.json +16 -0
- package/skills/rust-testing/references/fakes-proptest-async.md +113 -0
- package/skills/search-first/SKILL.md +73 -0
- package/skills/search-first/evals/triggers.json +14 -0
- package/skills/security-review/SKILL.md +58 -0
- package/skills/security-review/evals/triggers.json +14 -0
- package/skills/security-review/references/checklist.md +54 -0
- package/skills/security-review/scripts/scan-secrets.js +79 -0
- package/skills/seo-audit/SKILL.md +111 -0
- package/skills/seo-audit/evals/triggers.json +16 -0
- package/skills/seo-audit/references/checks.md +65 -0
- package/skills/session-handoff/SKILL.md +101 -0
- package/skills/session-handoff/evals/triggers.json +14 -0
- package/skills/skill-authoring/SKILL.md +87 -0
- package/skills/skill-authoring/evals/triggers.json +14 -0
- package/skills/skill-authoring/references/frontmatter.md +40 -0
- package/skills/slack-app-integration/SKILL.md +108 -0
- package/skills/slack-app-integration/evals/triggers.json +17 -0
- package/skills/smart-contract-security/SKILL.md +110 -0
- package/skills/smart-contract-security/evals/triggers.json +16 -0
- package/skills/smart-contract-security/references/vulnerability-checklist.md +79 -0
- package/skills/social-media-publishing/SKILL.md +84 -0
- package/skills/social-media-publishing/evals/triggers.json +17 -0
- package/skills/social-media-publishing/references/platform-apis.md +116 -0
- package/skills/solidity-patterns/SKILL.md +95 -0
- package/skills/solidity-patterns/evals/triggers.json +16 -0
- package/skills/spring-boot-patterns/SKILL.md +128 -0
- package/skills/spring-boot-patterns/evals/triggers.json +18 -0
- package/skills/spring-boot-patterns/references/testing.md +72 -0
- package/skills/stripe-integration/SKILL.md +69 -0
- package/skills/stripe-integration/evals/triggers.json +16 -0
- package/skills/stripe-integration/references/api.md +71 -0
- package/skills/stripe-integration/references/billing.md +58 -0
- package/skills/stripe-integration/references/webhooks.md +76 -0
- package/skills/subscription-billing-ops/SKILL.md +118 -0
- package/skills/subscription-billing-ops/evals/triggers.json +17 -0
- package/skills/subscription-billing-ops/references/proration.md +47 -0
- package/skills/subscription-billing-ops/references/revenue-metrics.md +38 -0
- package/skills/svelte-patterns/SKILL.md +122 -0
- package/skills/svelte-patterns/evals/triggers.json +16 -0
- package/skills/swift-patterns/SKILL.md +113 -0
- package/skills/swift-patterns/evals/triggers.json +16 -0
- package/skills/swift-patterns/references/concurrency-migration.md +40 -0
- package/skills/swift-testing/SKILL.md +121 -0
- package/skills/swift-testing/evals/triggers.json +16 -0
- package/skills/swift-testing/references/swift-testing-cheatsheet.md +81 -0
- package/skills/swiftui-patterns/SKILL.md +102 -0
- package/skills/swiftui-patterns/evals/triggers.json +16 -0
- package/skills/tdd-workflow/SKILL.md +67 -0
- package/skills/tdd-workflow/evals/triggers.json +14 -0
- package/skills/technical-writing/SKILL.md +95 -0
- package/skills/technical-writing/evals/triggers.json +14 -0
- package/skills/terraform-patterns/SKILL.md +122 -0
- package/skills/terraform-patterns/evals/triggers.json +16 -0
- package/skills/terraform-patterns/references/backends.md +67 -0
- package/skills/test-coverage-improvement/SKILL.md +107 -0
- package/skills/test-coverage-improvement/evals/triggers.json +16 -0
- package/skills/threat-modeling/SKILL.md +113 -0
- package/skills/threat-modeling/evals/triggers.json +16 -0
- package/skills/typescript-patterns/SKILL.md +122 -0
- package/skills/typescript-patterns/evals/triggers.json +16 -0
- package/skills/typescript-testing/SKILL.md +101 -0
- package/skills/typescript-testing/evals/triggers.json +16 -0
- package/skills/verification-loop/SKILL.md +70 -0
- package/skills/verification-loop/evals/triggers.json +14 -0
- package/skills/verification-loop/scripts/detect-checks.js +102 -0
- package/skills/vite-patterns/SKILL.md +117 -0
- package/skills/vite-patterns/evals/triggers.json +16 -0
- package/skills/vue-patterns/SKILL.md +104 -0
- package/skills/vue-patterns/evals/triggers.json +16 -0
- package/skills/web-accessibility/SKILL.md +119 -0
- package/skills/web-accessibility/evals/triggers.json +16 -0
- package/skills/web-performance/SKILL.md +107 -0
- package/skills/web-performance/evals/triggers.json +16 -0
- package/skills/web-scraping/SKILL.md +80 -0
- package/skills/web-scraping/evals/triggers.json +16 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hl7v2-integration
|
|
3
|
+
description: Parses, generates and routes pipe-delimited HL7 version 2.x messages such as ADT admit/discharge/transfer events, ORM and OML orders and ORU results, handling MSH encoding characters and escape sequences, repeating fields and components, MSH-10 control IDs, original and enhanced ACK/NACK modes, MLLP framing over TCP, interface engines like Mirth Connect (NextGen Connect), and mapping v2 segments to FHIR resources. Use when receiving or sending HL7 v2 feeds from a hospital, lab or EHR, debugging a rejected message or missing ACK, writing an MLLP listener, or converting PID, PV1, OBR and OBX data into FHIR.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Live feeds need network access to the interface engine or MLLP endpoint. Rules apply to any language or HL7 library (for example HAPI HL7v2, python-hl7, hl7apy).
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: integrations
|
|
9
|
+
kete-loom/risk: network
|
|
10
|
+
kete-loom/owner: "Kete-org"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# HL7 v2 integration
|
|
14
|
+
|
|
15
|
+
Build an HL7 v2 interface that never loses a message, never files a result against the wrong patient, and tells the sender exactly why it rejected something. For the FHIR REST API use `fhir-integration`; for HIPAA safeguards around the feed use `hipaa-compliance`.
|
|
16
|
+
|
|
17
|
+
This is engineering guidance, not clinical advice. Every live interface is validated with the sending site before go-live.
|
|
18
|
+
|
|
19
|
+
## Ground rules
|
|
20
|
+
|
|
21
|
+
1. **The interface specification wins.** Every site customises v2 (Z-segments, optional fields made required, local code tables). Get the sender's or receiver's interface spec and sample messages before writing a parser. If there is none, stop and ask for it.
|
|
22
|
+
2. **Read encoding characters from MSH, never hard-code them.** MSH-1 is the field separator (normally `|`), MSH-2 holds component `^`, repetition `~`, escape `\`, subcomponent `&` (and truncation `#` from v2.7). Segments end with carriage return `\r` (0x0D), not `\n`.
|
|
23
|
+
3. **Unescape and escape.** `\F\` `\S\` `\R\` `\E\` `\T\` stand for the separators inside data; `\X..\` is hex and `\.br\` a line break in formatted text. Escape on output; a name with `^` or `&` must not split fields.
|
|
24
|
+
4. **Fields repeat.** PID-3 (identifiers), PID-5 (names), PID-13 (phones) and OBX-5 can repeat with `~`. Pick the identifier by its assigning authority and type code (PID-3.4, PID-3.5 such as `MR`), never "the first one".
|
|
25
|
+
5. **Persist before you ACK.** Write the raw message to durable storage (queue or database) in the same step that decides the ACK. Send `AA`/`CA` only once it is stored. If storage fails, send `AR` with ERR-3 code `207` (application internal error) or send no ACK so the sender retries; `AE` means the content is wrong and senders usually don't resend it unchanged.
|
|
26
|
+
6. **MSH-10 is the idempotency key.** Senders resend when an ACK is lost. Deduplicate on sending facility (MSH-4) plus MSH-10 and return the same ACK again for a duplicate.
|
|
27
|
+
7. **Preserve order per patient.** ADT events (A01 admit, A02 transfer, A03 discharge, A08 update, A40 merge) must be processed in order for a patient. Process one connection serially, or partition by patient identifier; never parallelise blindly.
|
|
28
|
+
8. **Merges and corrections are first-class.** Handle A40 (merge patient), A11/A13 (cancel admit/discharge), A08 updates, and ORU results with OBX-11 result status `C` (corrected) or `D` (delete). Never treat them as new records.
|
|
29
|
+
9. **Never log full messages.** Messages contain PHI. Log message type, MSH-10, MSH-4, processing outcome and error location (segment, field) only. Keep raw messages in access-controlled storage with a retention policy.
|
|
30
|
+
|
|
31
|
+
## Steps
|
|
32
|
+
|
|
33
|
+
1. **Collect the spec and samples:** versions (MSH-12, for example `2.3`, `2.5.1`), message types and trigger events (MSH-9, for example `ADT^A01^ADT_A01`, `ORU^R01^ORU_R01`), required segments, Z-segments, code tables, ACK mode (MSH-15/MSH-16), and character set (MSH-18).
|
|
34
|
+
2. **Transport.** For MLLP use the framing in `references/messages-and-ack.md`. Run MLLP over a VPN or TLS tunnel; MLLP itself has no encryption or authentication. Restrict the listener port to the sender's IPs.
|
|
35
|
+
3. **Parse** with a maintained library, configured to read separators from MSH. Validate: required segments present, MSH-9 is a type you accept, MSH-11 processing ID (`P` production, `T` test, `D` debug) matches the environment. Reject `T` messages in production and `P` messages in test.
|
|
36
|
+
4. **Acknowledge** with the ACK format in `references/messages-and-ack.md`. On a rejected message set MSA-1 to `AE` (a problem in the message content, such as a missing required field; the sender must fix it before resending) or `AR` (rejected for reasons not about the content: unsupported message type, version or processing ID, or a receiver failure), and fill ERR with the segment and field location and a code.
|
|
37
|
+
5. **Map** fields to your model or to FHIR using the table in `references/messages-and-ack.md` and the HL7 Version 2 to FHIR IG (hl7.org/fhir/uv/v2mappings). Keep the original identifiers and the raw message ID on every mapped record for traceability.
|
|
38
|
+
6. **Produce** outbound messages with a fresh MSH-10 per message, MSH-7 timestamp with timezone, correct MSH-3/4/5/6 sending and receiving application and facility, and wait for the ACK before sending the next on that connection. Retry with the same MSH-10 on timeout.
|
|
39
|
+
7. **Test** with de-identified sample messages from the site, plus edge cases: escaped separators, repeating identifiers, missing optional segments, Z-segments, a duplicate MSH-10, a merge (A40), a corrected result, an empty OBX-5, a message with `\n` line endings.
|
|
40
|
+
|
|
41
|
+
## Interface engines
|
|
42
|
+
|
|
43
|
+
If the organisation runs an engine (Mirth Connect by NextGen Healthcare, commercially licensed from 4.6; Rhapsody; Cloverleaf; InterSystems HealthShare; or a cloud service such as Google Cloud Healthcare API HL7v2 stores with its MLLP adapter), put routing, filtering and transformation there rather than in each application, and keep transformations in source control. Your application then gets one well-defined feed.
|
|
44
|
+
|
|
45
|
+
## Stop and ask the user
|
|
46
|
+
|
|
47
|
+
- Before connecting to a production feed, or replaying messages into production.
|
|
48
|
+
- When identifiers in PID-3 don't match the agreed assigning authority, or a message would update a patient that can't be matched uniquely.
|
|
49
|
+
- When the sample messages contradict the interface spec.
|
|
50
|
+
- Before discarding or "fixing up" messages that fail validation.
|
|
51
|
+
|
|
52
|
+
## Never
|
|
53
|
+
|
|
54
|
+
- Never split on `|` and `^` with hard-coded indices without handling escapes and repetitions.
|
|
55
|
+
- Never ACK before the message is durably stored.
|
|
56
|
+
- Never drop OBX segments you don't understand from a result; store them and flag them.
|
|
57
|
+
- Never reuse an MSH-10 for a different message.
|
|
58
|
+
- Never mix test and production feeds on one listener.
|
|
59
|
+
|
|
60
|
+
## Report format
|
|
61
|
+
|
|
62
|
+
When asked to review or design an interface, finish with:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Feed: <sender → receiver, message types and versions, transport>
|
|
66
|
+
ACK mode: <original | enhanced>, persisted before ACK: <yes/no>
|
|
67
|
+
Findings: <ranked list, file:line, rule broken, fix>
|
|
68
|
+
Open questions for the interface analyst: <list>
|
|
69
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Write an MLLP listener in Node that receives ADT messages and sends back an ACK",
|
|
4
|
+
"The lab says our ORU^R01 parser files results under the wrong patient when PID-3 has two identifiers",
|
|
5
|
+
"Our interface engine keeps resending messages because it never gets an AA back, help me debug the ACK",
|
|
6
|
+
"Map OBX segments from incoming HL7 v2 lab results into FHIR Observation resources",
|
|
7
|
+
"Generate outbound ORM order messages with proper MSH-10 control IDs and escaping of the ^ character in names"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Query a FHIR server for all Encounters in the last week using _include for the patient",
|
|
11
|
+
"Design drug-interaction alerts so clinicians don't get alert fatigue",
|
|
12
|
+
"Set up a Kafka consumer with exactly-once processing for our order events",
|
|
13
|
+
"Check our hospital feed handler for patient data written to application logs",
|
|
14
|
+
"Write the HIPAA audit logging requirements for our EHR export feature"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# HL7 v2 messages, ACKs, MLLP and FHIR mapping
|
|
2
|
+
|
|
3
|
+
Sources: HL7 v2.5.1 and later chapters 2 (control) and 3 (ADT), the MLLP transport spec (HL7 v2 Appendix C / Transport Specification MLLP Release 2), and the HL7 Version 2 to FHIR IG 1.0.0. Field positions below are the common ones; the site's interface spec overrides them.
|
|
4
|
+
|
|
5
|
+
## Example ADT^A01
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
MSH|^~\&|ADT1|GOOD HEALTH HOSPITAL|RCV|RCVFAC|20240612083000+0100||ADT^A01^ADT_A01|MSG00001|P|2.5.1
|
|
9
|
+
EVN|A01|20240612083000+0100
|
|
10
|
+
PID|1||12345^^^GHH^MR~987654321^^^NHS^NH||DOE^JANE^A||19800101|F|||1 MAIN ST^^LEEDS^^LS1 1AA^GBR
|
|
11
|
+
PV1|1|I|WARD1^ROOM2^BED3||||1234^SMITH^JOHN|||MED
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Segments are separated by `\r`. The line breaks above are for reading only.
|
|
15
|
+
|
|
16
|
+
## Key fields
|
|
17
|
+
|
|
18
|
+
| Field | Meaning |
|
|
19
|
+
|---|---|
|
|
20
|
+
| MSH-3 / MSH-4 | sending application / facility |
|
|
21
|
+
| MSH-5 / MSH-6 | receiving application / facility |
|
|
22
|
+
| MSH-7 | message date/time |
|
|
23
|
+
| MSH-9 | message type ^ trigger event ^ structure |
|
|
24
|
+
| MSH-10 | message control ID (unique per message from a sender) |
|
|
25
|
+
| MSH-11 | processing ID: P, T, D |
|
|
26
|
+
| MSH-12 | version ID |
|
|
27
|
+
| MSH-15 / MSH-16 | accept / application acknowledgment type (AL, NE, ER, SU); blank means original mode |
|
|
28
|
+
| PID-3 | patient identifier list (ID ^^^ assigning authority ^ type code) |
|
|
29
|
+
| PID-5 / PID-7 / PID-8 | name / birth date / administrative sex |
|
|
30
|
+
| PV1-2 / PV1-3 | patient class / assigned location |
|
|
31
|
+
| ORC-1 / ORC-2 / ORC-3 | order control / placer order number / filler order number |
|
|
32
|
+
| OBR-4 | universal service identifier (ordered test) |
|
|
33
|
+
| OBX-2 / OBX-3 / OBX-5 / OBX-6 | value type / observation identifier / value / units |
|
|
34
|
+
| OBX-8 / OBX-11 | abnormal flags / result status (F final, P preliminary, C corrected, D delete, X cannot obtain) |
|
|
35
|
+
|
|
36
|
+
## ACK
|
|
37
|
+
|
|
38
|
+
Original mode: the receiving application replies once.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
MSH|^~\&|RCV|RCVFAC|ADT1|GOOD HEALTH HOSPITAL|20240612083001+0100||ACK^A01^ACK|ACK00001|P|2.5.1
|
|
42
|
+
MSA|AA|MSG00001
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- MSA-1: `AA` accepted, `AE` error, `AR` rejected.
|
|
46
|
+
- MSA-2: the MSH-10 of the message being acknowledged.
|
|
47
|
+
- On `AE`/`AR`, add `ERR` with location (ERR-2, for example `PID^1^3`), HL7 error code (ERR-3, from table 0357, for example `101` required field missing, `200` unsupported message type, `207` application internal error) and severity (ERR-4: `E`, `W`, `I`).
|
|
48
|
+
- Swap sending and receiving application/facility relative to the inbound MSH.
|
|
49
|
+
|
|
50
|
+
Enhanced mode (MSH-15/16 set): a commit ACK (`CA`, `CE`, `CR`) confirms safe receipt, and an optional application ACK (`AA`, `AE`, `AR`) follows later as a separate message.
|
|
51
|
+
|
|
52
|
+
## MLLP framing
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
<VT> message <FS><CR>
|
|
56
|
+
VT = 0x0B FS = 0x1C CR = 0x0D
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- Read bytes until `0x1C 0x0D`; strip the leading `0x0B`. A TCP read can contain part of a message or several messages: buffer and split on the trailer.
|
|
60
|
+
- One message at a time per connection: send, wait for the ACK, then the next.
|
|
61
|
+
- Set an ACK timeout (often 30 to 60 seconds, agree it with the partner) and resend with the same MSH-10 on timeout.
|
|
62
|
+
- Keep connections open; reconnect with backoff.
|
|
63
|
+
- MLLP has no security. Use a VPN, TLS wrapper or engine-to-engine secure channel.
|
|
64
|
+
|
|
65
|
+
## Mapping to FHIR (summary of the v2-to-FHIR IG)
|
|
66
|
+
|
|
67
|
+
| v2 | FHIR R4 |
|
|
68
|
+
|---|---|
|
|
69
|
+
| PID | Patient (PID-3 → identifier, PID-5 → name, PID-7 → birthDate, PID-8 → gender via a concept map) |
|
|
70
|
+
| PV1 | Encounter (PV1-2 → class, PV1-3 → location, PV1-19 visit number → identifier) |
|
|
71
|
+
| ORC + OBR | ServiceRequest (placer/filler numbers → identifier) and DiagnosticReport for results |
|
|
72
|
+
| OBX | Observation (OBX-3 → code, OBX-5 by OBX-2 type → value[x], OBX-6 → valueQuantity.unit, OBX-11 → status) |
|
|
73
|
+
| AL1 | AllergyIntolerance |
|
|
74
|
+
| DG1 | Condition |
|
|
75
|
+
| NK1 | RelatedPerson or Patient.contact |
|
|
76
|
+
| MSH, EVN | Provenance / MessageHeader (keep MSH-10 for traceability) |
|
|
77
|
+
|
|
78
|
+
Code tables map through ConceptMaps; local codes need a site-specific map. Map OBX-11 `C` to Observation `status = corrected` and update the existing Observation rather than creating a new one; `D` maps to `entered-in-error`.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: homelab-network-setup
|
|
3
|
+
description: Plans and builds a self-hosted home lab network from an address and VLAN plan through inter-VLAN firewall policy, local DNS filtering with Pi-hole or AdGuard Home, WireGuard remote access, configuration backups, and exposing a few services to the internet without opening the whole LAN. Use when someone is designing or rebuilding a home or small-office network, isolating IoT or guest devices, setting up a VPN to reach home from outside, or deciding how to publish a self-hosted app safely.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Vendor-neutral; applies to OPNsense, pfSense, OpenWrt, MikroTik, UniFi and similar routers. WireGuard commands need wireguard-tools.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: devops
|
|
9
|
+
kete-loom/risk: writes-files
|
|
10
|
+
kete-loom/owner: "Kete-org"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Home lab network setup
|
|
14
|
+
|
|
15
|
+
Produce a written plan first, then configs. A home lab is usually run by one person with no out-of-band access, so every step must leave a way back in.
|
|
16
|
+
|
|
17
|
+
WireGuard configs and key handling are in `references/wireguard.md`. A starting firewall policy matrix and DNS details are in `references/firewall-and-dns.md`. Read them when you reach those steps.
|
|
18
|
+
|
|
19
|
+
## Ground rules
|
|
20
|
+
|
|
21
|
+
1. **Plan before touching the router.** Write the address plan, VLAN list and firewall matrix and get the user to confirm it.
|
|
22
|
+
2. **Keep a way back in.** Before changing the router or firewall: export its configuration, keep a wired port on the management network, and know how to factory-reset and restore. Never change the VLAN or rules on the port or Wi-Fi you are using to manage it without a rollback.
|
|
23
|
+
3. **Default deny between segments.** Each VLAN reaches only what the matrix allows. The internet never reaches the LAN except through the VPN or an explicitly published service.
|
|
24
|
+
4. **Prefer VPN to port forwards.** A service reachable only over WireGuard has no internet attack surface beyond one UDP port that ignores unauthenticated packets.
|
|
25
|
+
5. **Secrets stay local.** Never print or commit private keys, PSKs, admin passwords or API tokens. Generate keys on the device that uses them.
|
|
26
|
+
|
|
27
|
+
## Step 1: Gather facts (ask if unknown)
|
|
28
|
+
|
|
29
|
+
- Router/firewall hardware and OS, and whether switches and access points support 802.1Q VLANs (managed or "smart" switches; unmanaged switches do not).
|
|
30
|
+
- ISP setup: does the router's WAN address match the public IP shown by an external "what is my IP" check? A WAN address in `100.64.0.0/10`, or one that does not match, means CGNAT: inbound port forwards and a plain WireGuard server will not work. Stop and offer a relay option (a VPS endpoint or a mesh VPN service) or asking the ISP for a public IPv4.
|
|
31
|
+
- Devices by trust level, and which services must be reachable from where.
|
|
32
|
+
- Whether IPv6 is delivered. If it is, every rule below must be written for IPv6 too, because IPv6 hosts are globally addressable.
|
|
33
|
+
|
|
34
|
+
## Step 2: Address and VLAN plan
|
|
35
|
+
|
|
36
|
+
Use one `/24` per VLAN from `10.0.0.0/8` or `172.16.0.0/12`, and avoid `192.168.0.0/24` and `192.168.1.0/24`: they collide with most remote networks you will VPN in from. Make the third octet equal the VLAN ID so addresses are self-describing.
|
|
37
|
+
|
|
38
|
+
| VLAN | Name | Example subnet | Holds |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| 10 | mgmt | 10.10.10.0/24 | Router, switch, AP and hypervisor admin interfaces |
|
|
41
|
+
| 20 | trusted | 10.10.20.0/24 | Your laptops and phones |
|
|
42
|
+
| 30 | servers | 10.10.30.0/24 | Self-hosted services, NAS |
|
|
43
|
+
| 40 | iot | 10.10.40.0/24 | TVs, smart plugs, speakers |
|
|
44
|
+
| 50 | guest | 10.10.50.0/24 | Visitors, internet only |
|
|
45
|
+
| 60 | cameras | 10.10.60.0/24 | IP cameras, no internet |
|
|
46
|
+
| — | wg | 10.10.99.0/24 | WireGuard clients (not a VLAN, a routed tunnel subnet) |
|
|
47
|
+
|
|
48
|
+
- Use `home.arpa` (reserved for home networks by RFC 8375) or a subdomain of a domain you own for local names. Do not use `.local` (mDNS) or an invented TLD.
|
|
49
|
+
- Reserve static DHCP leases for servers and infrastructure; keep a dynamic range for the rest.
|
|
50
|
+
- Switch ports: trunk (tagged) to the router, APs and hypervisors; access (untagged) ports for single devices. Map each Wi-Fi SSID to one VLAN.
|
|
51
|
+
|
|
52
|
+
## Step 3: Firewall policy
|
|
53
|
+
|
|
54
|
+
Write the policy as a matrix, source rows by destination columns, before writing any rule. Start from `references/firewall-and-dns.md` and adapt it. Then on each internal interface, in this order:
|
|
55
|
+
|
|
56
|
+
1. Allow established and related traffic.
|
|
57
|
+
2. Allow DNS (UDP and TCP 53) and DHCP to the router or the DNS server only.
|
|
58
|
+
3. Allow the specific source-to-destination ports from the matrix (for example trusted → servers TCP 443, 22).
|
|
59
|
+
4. Block the interface's traffic to all private ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, and the router's own addresses on other VLANs, including its admin UI).
|
|
60
|
+
5. Allow to the internet (any remaining destination), except for VLANs that must stay offline, such as cameras.
|
|
61
|
+
|
|
62
|
+
- Admin UIs (router, switch, hypervisor, NAS) answer only from mgmt and trusted, never from IoT or guest.
|
|
63
|
+
- Casting and discovery across VLANs need an mDNS reflector or repeater on the router plus narrow allow rules; enable them only for the pairs that need them.
|
|
64
|
+
- Turn UPnP/NAT-PMP off, or limit it to one gaming console's address. It lets any LAN device open inbound ports.
|
|
65
|
+
|
|
66
|
+
## Step 4: DNS
|
|
67
|
+
|
|
68
|
+
1. Run Pi-hole or AdGuard Home on a static address in the servers or mgmt VLAN, with upstream either a public DoT/DoH resolver or a local recursive resolver such as Unbound.
|
|
69
|
+
2. Hand it out by DHCP on every VLAN. Do not list a public resolver as a second DNS server: clients use both, and filtering silently stops.
|
|
70
|
+
3. If you want a fallback, run a second filtering instance, not a public resolver.
|
|
71
|
+
4. Optionally redirect or block outbound port 53 from clients so hard-coded resolvers go through yours. Devices using DNS-over-HTTPS will bypass this; that needs DoH blocklists, not a port rule.
|
|
72
|
+
5. Add local records for each service (`nas.home.arpa`) so you never browse by IP.
|
|
73
|
+
|
|
74
|
+
## Step 5: WireGuard remote access
|
|
75
|
+
|
|
76
|
+
Follow `references/wireguard.md`. The essentials:
|
|
77
|
+
|
|
78
|
+
- Run the server on the router or a server in the servers VLAN; forward one UDP port (51820 by convention) to it.
|
|
79
|
+
- One key pair per client device. `AllowedIPs` on the server side is that peer's single tunnel address (`/32`).
|
|
80
|
+
- Split tunnel (clients route only the home subnets) by default; full tunnel (`0.0.0.0/0, ::/0`) only when the user wants all traffic to exit at home.
|
|
81
|
+
- Treat the `wg` subnet as its own source in the firewall matrix: usually the same access as trusted, never more.
|
|
82
|
+
- For a dynamic home IP, point a DNS name at it with dynamic DNS and use that name as the client `Endpoint`.
|
|
83
|
+
|
|
84
|
+
## Step 6: Publishing a service safely
|
|
85
|
+
|
|
86
|
+
Ask first whether the service really needs to be public. If only the user and family use it, the VPN is the answer. When it must be public:
|
|
87
|
+
|
|
88
|
+
1. Put it in the servers VLAN (or a dedicated DMZ VLAN) that cannot initiate connections to trusted or mgmt.
|
|
89
|
+
2. Expose only a reverse proxy on TCP 443 with automatic certificates; forward nothing else. Never forward SSH, RDP, SMB, database ports, or any admin UI.
|
|
90
|
+
3. Add authentication in front of apps that lack strong login (a forward-auth or SSO layer), and rate limiting or ban-on-failure.
|
|
91
|
+
4. As an alternative to a port forward, an outbound tunnel service keeps the home IP private and works behind CGNAT. Note that the provider then sees the traffic.
|
|
92
|
+
5. Keep the published software patched; subscribe to its release notes.
|
|
93
|
+
|
|
94
|
+
## Step 7: Backups and change safety
|
|
95
|
+
|
|
96
|
+
- Export router, switch and AP configs after every change and store them off the device (encrypted if they contain keys). Note firmware versions with them.
|
|
97
|
+
- Back up the DNS server's config and the WireGuard configs the same way.
|
|
98
|
+
- Before applying firewall changes remotely, schedule an automatic rollback (use the router's own mechanism if it has one, such as MikroTik Safe Mode; on Linux, schedule a restore of the saved ruleset with `at` or a timer) and cancel it only once you have reconnected.
|
|
99
|
+
- Follow 3-2-1 for data on the NAS: three copies, two media, one off-site.
|
|
100
|
+
|
|
101
|
+
## Stop and ask the user
|
|
102
|
+
|
|
103
|
+
- Before applying any change to the router, firewall or switch you connect through.
|
|
104
|
+
- When CGNAT is detected, or the hardware cannot do VLANs.
|
|
105
|
+
- Before exposing anything to the internet; confirm which service, who uses it, and the auth in front of it.
|
|
106
|
+
|
|
107
|
+
## Never
|
|
108
|
+
|
|
109
|
+
- Never put admin interfaces on the WAN or in the guest/IoT VLANs.
|
|
110
|
+
- Never reuse one WireGuard key pair on several devices.
|
|
111
|
+
- Never write keys, passwords or real public IPs into files that will be committed or shared.
|
|
112
|
+
|
|
113
|
+
## Deliverable
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
1. Facts: hardware, ISP/CGNAT result, IPv6 yes/no
|
|
117
|
+
2. Address and VLAN plan (table)
|
|
118
|
+
3. Firewall matrix (table) and rule order per interface
|
|
119
|
+
4. DNS: server, upstream, records, how clients get it
|
|
120
|
+
5. Remote access: WireGuard server, port, peers, split or full tunnel
|
|
121
|
+
6. Published services: name, path (proxy/tunnel), auth
|
|
122
|
+
7. Backup and rollback plan
|
|
123
|
+
8. Order of work, with the step that could cut access marked
|
|
124
|
+
9. Open questions for the user
|
|
125
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"I just bought a managed switch and an OPNsense box, how should I lay out VLANs for my homelab?",
|
|
4
|
+
"Help me put all my smart plugs and the TV on their own network so they can't see my NAS",
|
|
5
|
+
"Set up WireGuard so I can reach Jellyfin and my NAS at home from my phone",
|
|
6
|
+
"Should I port forward Nextcloud or is there a safer way to make it reachable from outside?",
|
|
7
|
+
"Plan a Pi-hole setup for every VLAN at home without a public DNS fallback leaking queries",
|
|
8
|
+
"I'm rebuilding my home network from scratch, guest Wi-Fi, cameras with no internet, the works"
|
|
9
|
+
],
|
|
10
|
+
"should_not_trigger": [
|
|
11
|
+
"My laptop gets a 169.254 address and can't reach anything, how do I debug it?",
|
|
12
|
+
"Audit this pf.conf and OpenVPN server config from my firewall for weak settings",
|
|
13
|
+
"Write a docker-compose file for Nextcloud with Postgres and Redis",
|
|
14
|
+
"Create a Kubernetes Ingress with cert-manager for my cluster",
|
|
15
|
+
"Design an AWS VPC with private subnets and a NAT gateway for production",
|
|
16
|
+
"dig shows SERVFAIL for my domain but only on some resolvers, why?"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Firewall matrix and DNS details
|
|
2
|
+
|
|
3
|
+
## Starting matrix
|
|
4
|
+
|
|
5
|
+
Rows are sources, columns are destinations. "Ports" means only the listed ports; everything not listed is blocked. Adapt it with the user, then turn each cell into rules.
|
|
6
|
+
|
|
7
|
+
| From \ To | mgmt | trusted | servers | iot | guest | cameras | internet |
|
|
8
|
+
|---|---|---|---|---|---|---|---|
|
|
9
|
+
| mgmt | allow | allow | allow | allow | no | allow | allow |
|
|
10
|
+
| trusted | admin ports (443, 22) | allow | service ports | control ports (casting, app) | no | NVR ports | allow |
|
|
11
|
+
| wg (VPN) | admin ports, if wanted | same as trusted | service ports | no | no | NVR ports | per tunnel mode |
|
|
12
|
+
| servers | no | no | allow | no | no | NVR pulls streams | allow (updates) |
|
|
13
|
+
| iot | no | replies only | specific ports (Home Assistant, MQTT) | allow or no | no | no | allow |
|
|
14
|
+
| guest | no | no | no | no | no (client isolation) | no | allow |
|
|
15
|
+
| cameras | no | no | NVR only | no | no | no | **no** |
|
|
16
|
+
|
|
17
|
+
"Replies only" is covered by the established/related rule; it never needs its own allow.
|
|
18
|
+
|
|
19
|
+
Every VLAN also needs: DNS to the DNS server, DHCP to the router, and NTP to the router (or allow NTP out) so clocks stay correct for TLS.
|
|
20
|
+
|
|
21
|
+
## Rule example (nftables, one internal interface)
|
|
22
|
+
|
|
23
|
+
```nft
|
|
24
|
+
table inet filter {
|
|
25
|
+
chain forward {
|
|
26
|
+
type filter hook forward priority 0; policy drop;
|
|
27
|
+
ct state established,related accept
|
|
28
|
+
ct state invalid drop
|
|
29
|
+
iifname "vlan40" ip daddr 10.10.30.53 meta l4proto { tcp, udp } th dport 53 accept # iot -> Pi-hole/AdGuard
|
|
30
|
+
iifname "vlan40" ip daddr 10.10.30.10 tcp dport { 1883, 8123 } accept # iot -> Home Assistant, MQTT
|
|
31
|
+
iifname "vlan40" ip daddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } drop
|
|
32
|
+
iifname "vlan40" oifname "wan" accept
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Check a ruleset file without applying it: `sudo nft -c -f ruleset.nft`. Put every allow to a private address (DNS server included) above the private-range drop, or it never matches. Rules for traffic to the router itself (its DNS, admin UI) go in the `input` chain, not `forward`.
|
|
38
|
+
|
|
39
|
+
## DNS details
|
|
40
|
+
|
|
41
|
+
- **Recursive upstream.** Running Unbound locally as Pi-hole/AdGuard's upstream means no third-party resolver sees your queries; the trade-off is your ISP sees queries to authoritative servers in clear text. A DoT/DoH upstream hides queries from the ISP but trusts that provider.
|
|
42
|
+
- **Conditional forwarding** lets the DNS server ask the router for names of DHCP clients (`laptop.home.arpa`) and for reverse lookups of local addresses.
|
|
43
|
+
- **Router's own DNS.** If the router also runs a resolver, either point it at the filter too or disable it, so there is one source of truth.
|
|
44
|
+
- **IPv6.** Router advertisements can hand out their own DNS server (RDNSS). Make it the filter's IPv6 address, or clients will bypass the filter over IPv6.
|
|
45
|
+
- **Do not expose DNS to the WAN.** An open resolver on port 53 is abused for amplification attacks. Allow port 53 only from internal interfaces and the VPN.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# WireGuard remote access
|
|
2
|
+
|
|
3
|
+
## Keys
|
|
4
|
+
|
|
5
|
+
Generate each key pair on the device that will use it, so the private key never travels:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
umask 077
|
|
9
|
+
wg genkey | tee client.key | wg pubkey > client.pub
|
|
10
|
+
wg genpsk > client.psk # optional pre-shared key per peer, adds a symmetric layer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Only public keys (and the PSK, over a secure channel) are exchanged. Phone apps generate their own pair; copy only the public key off the phone. If you must generate a config for a phone elsewhere and show it as a QR code (`qrencode -t ansiutf8 < phone.conf`), delete the file afterwards.
|
|
14
|
+
|
|
15
|
+
## Server (`/etc/wireguard/wg0.conf`)
|
|
16
|
+
|
|
17
|
+
```ini
|
|
18
|
+
[Interface]
|
|
19
|
+
Address = 10.10.99.1/24
|
|
20
|
+
ListenPort = 51820
|
|
21
|
+
PrivateKey = <server private key>
|
|
22
|
+
|
|
23
|
+
[Peer]
|
|
24
|
+
# laptop
|
|
25
|
+
PublicKey = <laptop public key>
|
|
26
|
+
PresharedKey = <laptop psk>
|
|
27
|
+
AllowedIPs = 10.10.99.2/32
|
|
28
|
+
|
|
29
|
+
[Peer]
|
|
30
|
+
# phone
|
|
31
|
+
PublicKey = <phone public key>
|
|
32
|
+
AllowedIPs = 10.10.99.3/32
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- Server-side `AllowedIPs` is both a route and an access check: packets from that peer are accepted only from those source addresses. Give each client a single `/32` (and `/128` for IPv6). Add a subnet only for a site-to-site peer that routes a whole network.
|
|
36
|
+
- The server needs IP forwarding (`net.ipv4.ip_forward=1`, and `net.ipv6.conf.all.forwarding=1` for IPv6) and firewall rules allowing `wg0` to reach the permitted VLANs. If the home subnets do not know the route back to `10.10.99.0/24` (the server is not the router), add that route on the router or NAT the tunnel traffic.
|
|
37
|
+
- On router OSes (OPNsense, pfSense, OpenWrt, MikroTik) the same fields exist in the WireGuard UI; the firewall rules go on the WireGuard interface.
|
|
38
|
+
|
|
39
|
+
## Client
|
|
40
|
+
|
|
41
|
+
```ini
|
|
42
|
+
[Interface]
|
|
43
|
+
Address = 10.10.99.2/32
|
|
44
|
+
PrivateKey = <laptop private key>
|
|
45
|
+
DNS = 10.10.30.53 # your Pi-hole/AdGuard, so home names resolve
|
|
46
|
+
|
|
47
|
+
[Peer]
|
|
48
|
+
PublicKey = <server public key>
|
|
49
|
+
PresharedKey = <laptop psk>
|
|
50
|
+
Endpoint = vpn.example.com:51820
|
|
51
|
+
AllowedIPs = 10.10.0.0/16 # split tunnel: home subnets only
|
|
52
|
+
PersistentKeepalive = 25 # only needed if the client sits behind NAT and must receive traffic
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- Full tunnel: `AllowedIPs = 0.0.0.0/0, ::/0`. Then home DNS and the server's NAT to the internet must work, or the client loses all connectivity.
|
|
56
|
+
- If the client's own LAN uses the same subnet as home, routing breaks. That is why the plan avoids `192.168.0.0/24` and `192.168.1.0/24`.
|
|
57
|
+
|
|
58
|
+
## Bring up and check
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
sudo wg-quick up wg0
|
|
62
|
+
sudo wg show # after sending traffic (ping the server tunnel IP): a recent "latest handshake" and rising transfer counters = working
|
|
63
|
+
sudo systemctl enable wg-quick@wg0 # start at boot on systemd hosts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- No handshake at all: the UDP port forward, the ISP (CGNAT), the `Endpoint` name, or mismatched public keys.
|
|
67
|
+
- Handshake but no traffic: `AllowedIPs` on either side, IP forwarding, or firewall rules on `wg0`.
|
|
68
|
+
- Handshake and pings work but web pages or SSH stall: MTU. Set `MTU = 1280` in the client `[Interface]` to test, then raise it.
|
|
69
|
+
|
|
70
|
+
## Revoking a device
|
|
71
|
+
|
|
72
|
+
Delete its `[Peer]` block from the server's `wg0.conf`, then apply without dropping other peers: `sudo wg syncconf wg0 <(sudo wg-quick strip wg0)` (bash), or remove it live with `sudo wg set wg0 peer <pubkey> remove`. Do both, or the peer returns on the next restart. Because each device has its own key, nothing else changes.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hubtel-integration
|
|
3
|
+
description: Guides Ghana-focused Hubtel merchant integrations end to end, from Online Checkout (redirect or onsite iframe) and Direct Receive Money prompts on MTN, Telecel and AirtelTigo wallets to callback handling, the mandatory Transaction Status Check, Basic auth with API ID and key, the 4-IP whitelist, and sending SMS through the Hubtel SMS API. Use when accepting payments into a Hubtel Merchant Account, wiring a Hubtel callback or status check, fixing a Hubtel 403 or 4000 response, or sending transactional SMS through Hubtel.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Any Agent Skills host. Live calls need network access to Hubtel's API hosts from a whitelisted server IP. Examples are TypeScript (Node 18+); the rules apply to any backend language.
|
|
6
|
+
metadata:
|
|
7
|
+
kete-loom/version: "0.1.0"
|
|
8
|
+
kete-loom/category: integrations
|
|
9
|
+
kete-loom/risk: network
|
|
10
|
+
kete-loom/owner: "Kete-org"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Hubtel integration
|
|
14
|
+
|
|
15
|
+
Accept payments into a Hubtel Merchant Account so that a fake callback can't mark an order paid, a missing callback still settles, and a `clientReference` is never charged twice. Also covers sending SMS. If the project takes Ghana payments through Paystack or calls MTN MoMo directly, follow that skill instead.
|
|
16
|
+
|
|
17
|
+
## Ground rules
|
|
18
|
+
|
|
19
|
+
1. **Credentials stay on the server.** Sales (payments) use `Authorization: Basic base64(API ID:API Key)`; SMS uses `base64(ClientID:ClientSecret)`. Read both from the environment. They are different credential pairs.
|
|
20
|
+
2. **Calls only work from whitelisted IPs.** Share your server's public egress IP with your Hubtel Retail Systems Engineer. Hubtel allows at most 4 IPs per service; anything else gets `403 Forbidden` or a timeout. Serverless functions with changing IPs need a fixed-egress proxy or NAT.
|
|
21
|
+
3. **Your Collection Account Number is in the URL or body** of every payment and status call. It identifies your merchant account; keep it in config, not code.
|
|
22
|
+
4. **One unique `clientReference` per attempt, stored first.** Hubtel says it must never be duplicated. Max 32 characters for Online Checkout, 36 for Direct Receive Money; use alphanumerics.
|
|
23
|
+
5. **The server sets the amount.** `totalAmount` / `Amount` are decimals in cedis with at most 2 decimal places. Keep integer pesewas in your database and format at the boundary (`(pesewas / 100).toFixed(2)`), never compute with floats.
|
|
24
|
+
6. **Callbacks are not signed.** Use them as a trigger only. Confirm with the Transaction Status Check, and give value only when `status` is `Paid` and the amount equals what you stored. Hubtel publishes a callback source IP per product that you can allow-list (see `references/api.md`).
|
|
25
|
+
7. **Status check is mandatory when no callback arrives.** Hubtel's docs require it for any transaction with no final callback after 5 minutes. Run it from a background job.
|
|
26
|
+
8. **Prevent unsolicited prompts.** For Direct Receive Money, Hubtel requires that customers can't freely type any number: either lock the number to a verified account or send an OTP to the number first.
|
|
27
|
+
|
|
28
|
+
## Choose the flow
|
|
29
|
+
|
|
30
|
+
| Need | Use |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Hosted page with mobile money, card, Hubtel/G-Money wallet and GhQR | Online Checkout: `POST https://payproxyapi.hubtel.com/items/initiate`, redirect to `checkoutUrl` or embed `checkoutDirectUrl` in an iframe |
|
|
33
|
+
| Prompt a wallet from your own UI | Direct Receive Money: `POST https://rmp.hubtel.com/merchantaccount/merchants/{Collection_Account_Number}/receive/mobilemoney` with `Channel` `mtn-gh`, `vodafone-gh` (Telecel) or `tigo-gh` (AirtelTigo) |
|
|
34
|
+
| Notify a customer | SMS: `POST https://sms.hubtel.com/v1/messages/send` |
|
|
35
|
+
|
|
36
|
+
## Flow
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
client ──(order id)──▶ server: create pending payment with clientReference
|
|
40
|
+
server ──initiate checkout or receive money──▶ Hubtel
|
|
41
|
+
checkout: responseCode "0000" + checkoutUrl → redirect customer
|
|
42
|
+
receive money: ResponseCode "0001" = pending, customer approves on phone
|
|
43
|
+
Hubtel ──POST callbackUrl / PrimaryCallbackURL──▶ server: 200 fast, queue a status check
|
|
44
|
+
server ──GET api-txnstatus.hubtel.com/transactions/{acct}/status?clientReference=..──▶ Paid + amount match → fulfil once
|
|
45
|
+
background job: status-check anything still pending after 5 minutes
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Implementation steps
|
|
49
|
+
|
|
50
|
+
1. **Configure**: API ID and key, Collection Account Number, callback base URL, and (for SMS) client ID, secret and an approved sender ID (max 11 characters). Confirm the server's egress IP is whitelisted before debugging anything else.
|
|
51
|
+
2. **Initiate** with the request bodies in `references/api.md`. `description` must not contain special characters (`&*!%@`), which Hubtel rejects.
|
|
52
|
+
3. **Callback endpoint**: accept `POST`, respond `200` quickly, look up the pending payment by `ClientReference` from the body, and enqueue a status check. Unknown reference → log and ignore.
|
|
53
|
+
4. **Status check** `GET https://api-txnstatus.hubtel.com/transactions/{Collection_Account_Number}/status?clientReference=...`. Map `data.status`: `Paid` → fulfil (guarded update `WHERE status = 'pending'`), `Unpaid` → keep waiting until your timeout then mark failed, `Refunded` → reverse fulfilment.
|
|
54
|
+
5. **Map response codes** using `references/api.md`: `0000` success, `0001` pending, `2001` customer-side failure (insufficient funds, wrong PIN, timeout), `4000` validation error, `4070` fees not set or below minimum, `0005` unknown state (contact Hubtel; do not retry blindly).
|
|
55
|
+
6. **SMS**: send with Basic auth, then store `messageId` and check delivery with `GET https://sms.hubtel.com/v1/messages/{messageId}`. `Sent` is not `Delivered`.
|
|
56
|
+
7. **Persist** per payment: `clientReference`, order id, expected amount (pesewas), channel, phone, Hubtel `TransactionId` / `checkoutId`, `ExternalTransactionId`, charges, status, timestamps.
|
|
57
|
+
|
|
58
|
+
## Reviewing an existing integration
|
|
59
|
+
|
|
60
|
+
- Order fulfilled from the callback body, or from `0000` on the initiate response.
|
|
61
|
+
- No status-check job, so payments stuck in `0001` never settle.
|
|
62
|
+
- `clientReference` reused on retry, or longer than the limit.
|
|
63
|
+
- Float amounts with more than 2 decimals, or a price taken from the client.
|
|
64
|
+
- Sales and SMS credentials mixed up, or credentials in client code.
|
|
65
|
+
- Calls from an unwhitelisted host (works locally through a VPN, fails in production with 403).
|
|
66
|
+
- Direct Receive Money open to any typed phone number with no OTP or account lock.
|
|
67
|
+
- Charges ignored: `AmountAfterCharges` is what settles; reconcile on it.
|
|
68
|
+
|
|
69
|
+
## Stop and ask the user
|
|
70
|
+
|
|
71
|
+
- Whether they already have a Hubtel Merchant Account, the Collection Account Number, and a whitelisted IP. Without these nothing can be tested live.
|
|
72
|
+
- Which product they want (Online Checkout or Direct Receive Money) and whether Hubtel has enabled it on the account.
|
|
73
|
+
- Before sending bulk SMS: sender ID approval and consent for marketing messages.
|
|
74
|
+
|
|
75
|
+
## Before you finish
|
|
76
|
+
|
|
77
|
+
Hubtel's docs at developers.hubtel.com are the final word; this skill gives the rules and the shape. Then test: a paid checkout, a cancelled checkout, a wallet prompt the customer rejects, a prompt that times out, a callback that never arrives (status job settles it), the same callback twice (fulfils once), and a forged callback for a real `clientReference` while the status check says `Unpaid` (must not fulfil).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"should_trigger": [
|
|
3
|
+
"Integrate Hubtel online checkout for a Ghanaian school fees portal",
|
|
4
|
+
"Accept Telecel Cash payments through Hubtel in our Ghana app",
|
|
5
|
+
"Hubtel receive money returns 403 Forbidden from our server, why?",
|
|
6
|
+
"Send order confirmation SMS to customers with Hubtel",
|
|
7
|
+
"Hubtel never sent the callback for some payments, how do I check their status?"
|
|
8
|
+
],
|
|
9
|
+
"should_not_trigger": [
|
|
10
|
+
"Let users pay with MTN MoMo through Paystack in the mobile app",
|
|
11
|
+
"Call the MTN MoMo Open API requesttopay endpoint directly",
|
|
12
|
+
"Accept Ghana mobile money with Flutterwave direct charge instead of the hosted page",
|
|
13
|
+
"Audit our payment webhook handlers for missing signature checks",
|
|
14
|
+
"Send transactional email receipts with SendGrid after checkout"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Hubtel API shapes used by this skill
|
|
2
|
+
|
|
3
|
+
Payments (Sales) auth: `Authorization: Basic base64(API ID:API Key)`. SMS auth: `Authorization: Basic base64(ClientID:ClientSecret)`. Send `Content-Type: application/json`. All payment hosts only answer whitelisted IPs (max 4 per service).
|
|
4
|
+
|
|
5
|
+
Confirm fields against developers.hubtel.com before shipping; this is a working summary.
|
|
6
|
+
|
|
7
|
+
## Online Checkout
|
|
8
|
+
|
|
9
|
+
`POST https://payproxyapi.hubtel.com/items/initiate`
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"totalAmount": 25.50,
|
|
14
|
+
"description": "Order 8F2C1A",
|
|
15
|
+
"callbackUrl": "https://api.example.com/hooks/hubtel/checkout",
|
|
16
|
+
"returnUrl": "https://app.example.com/pay/return",
|
|
17
|
+
"cancellationUrl": "https://app.example.com/pay/cancelled",
|
|
18
|
+
"merchantAccountNumber": "<Collection Account Number>",
|
|
19
|
+
"clientReference": "ord8f2c1aA1",
|
|
20
|
+
"payeeName": "Ama Mensah",
|
|
21
|
+
"payeeMobileNumber": "233241234567",
|
|
22
|
+
"payeeEmail": "ama@example.com"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
All fields above are mandatory except the `payee*` ones. `clientReference` max 32 characters. Response: `responseCode` `"0000"` (accepted), `data.checkoutUrl` (redirect), `data.checkoutDirectUrl` (onsite iframe), `data.checkoutId`, `data.clientReference`. The return URL is display only; fulfilment waits for the callback plus status check.
|
|
27
|
+
|
|
28
|
+
Checkout callback source IP to allow-list (optional extra layer, confirm the current value in Hubtel's docs because it can change): `108.129.40.25`.
|
|
29
|
+
|
|
30
|
+
## Direct Receive Money
|
|
31
|
+
|
|
32
|
+
`POST https://rmp.hubtel.com/merchantaccount/merchants/{Collection_Account_Number}/receive/mobilemoney`
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"CustomerName": "Ama Mensah",
|
|
37
|
+
"CustomerMsisdn": "233241234567",
|
|
38
|
+
"CustomerEmail": "ama@example.com",
|
|
39
|
+
"Channel": "mtn-gh",
|
|
40
|
+
"Amount": 25.50,
|
|
41
|
+
"PrimaryCallbackURL": "https://api.example.com/hooks/hubtel/receive",
|
|
42
|
+
"Description": "Order 8F2C1A",
|
|
43
|
+
"ClientReference": "ord8f2c1aA1"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Channel | Network |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `mtn-gh` | MTN Ghana |
|
|
50
|
+
| `vodafone-gh` | Telecel Ghana |
|
|
51
|
+
| `tigo-gh` | AirtelTigo |
|
|
52
|
+
|
|
53
|
+
`CustomerMsisdn` in international format (`233...`) and on the network named by `Channel`. `ClientReference` max 36 characters. Response: `ResponseCode` `"0001"` (pending, callback follows), `Data.TransactionId`, `Data.Amount`, `Data.Charges`, `Data.AmountAfterCharges`, `Data.AmountCharged`. The final state usually arrives within about 30 seconds.
|
|
54
|
+
|
|
55
|
+
Receive Money callback source IP to allow-list (optional extra layer, confirm the current value in Hubtel's docs): `18.202.122.131`.
|
|
56
|
+
|
|
57
|
+
## Transaction Status Check
|
|
58
|
+
|
|
59
|
+
`GET https://api-txnstatus.hubtel.com/transactions/{Collection_Account_Number}/status?clientReference=ord8f2c1aA1`
|
|
60
|
+
|
|
61
|
+
Also accepts `hubtelTransactionId` or `networkTransactionId`; prefer `clientReference`. Response `data`: `status` (`Paid`, `Unpaid`, `Refunded`), `amount`, `charges`, `amountAfterCharges`, `transactionId`, `externalTransactionId`, `paymentMethod`, `clientReference`, `date`.
|
|
62
|
+
|
|
63
|
+
## Response codes
|
|
64
|
+
|
|
65
|
+
| Code | Meaning | Action |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `0000` | Success (in a callback: paid; on checkout initiate: accepted) | Confirm with status check, then fulfil |
|
|
68
|
+
| `0001` | Accepted, pending | Wait for callback; status-check after 5 minutes |
|
|
69
|
+
| `0005` | HTTP failure reaching the payment partner; state unknown | Don't retry with a new reference; contact Hubtel to confirm |
|
|
70
|
+
| `2001` | Customer or telco failure (insufficient funds, wrong PIN, USSD timeout, number not on channel, special characters in description) | Mark failed; let the customer retry with a new reference |
|
|
71
|
+
| `4000` | Validation error | Fix the request |
|
|
72
|
+
| `4070` | Fees not set or amount below minimum | Contact Hubtel account manager |
|
|
73
|
+
|
|
74
|
+
## SMS
|
|
75
|
+
|
|
76
|
+
`POST https://sms.hubtel.com/v1/messages/send` with `{ "From": "MyBrand", "To": "233241234567", "Content": "Your order 8F2C1A is paid." }`. `From` is the sender ID, max 11 characters. Batch: `POST /v1/messages/batch/simple/send` (`Recipients` array) or `/v1/messages/batch/personalized/send`. Status: `GET /v1/messages/{messageId}` or `/v1/messages/batch/{batchId}`; statuses include `Delivered`, `Sent`, `Pending`, `Blacklisted`, `Undeliverable`, `Rejected`. A `GET` send variant takes `clientid` and `clientsecret` in the query string; avoid it, because URLs end up in logs.
|
|
77
|
+
|
|
78
|
+
## Status check client (TypeScript)
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const auth = 'Basic ' + Buffer.from(`${process.env.HUBTEL_API_ID}:${process.env.HUBTEL_API_KEY}`).toString('base64');
|
|
82
|
+
|
|
83
|
+
export async function hubtelIsPaid(clientReference: string, expectedPesewas: number) {
|
|
84
|
+
const acct = process.env.HUBTEL_COLLECTION_ACCOUNT!;
|
|
85
|
+
const url = `https://api-txnstatus.hubtel.com/transactions/${acct}/status?clientReference=${encodeURIComponent(clientReference)}`;
|
|
86
|
+
const res = await fetch(url, { headers: { Authorization: auth } });
|
|
87
|
+
if (res.status === 403) throw new Error('Hubtel 403: server IP is not whitelisted');
|
|
88
|
+
if (!res.ok) throw new Error(`Hubtel status check failed: ${res.status}`);
|
|
89
|
+
const { data } = await res.json();
|
|
90
|
+
return data?.status === 'Paid' && Math.round(Number(data.amount) * 100) === expectedPesewas;
|
|
91
|
+
}
|
|
92
|
+
```
|