@galaxyproject/orbit 0.0.0 → 0.9.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/CHANGELOG.md +123 -0
- package/LICENSE +21 -0
- package/README.md +760 -5
- package/bin/legacy-mcp-config.js +363 -0
- package/bin/orbit.js +810 -0
- package/bin/pi-extension-path.d.ts +4 -0
- package/bin/pi-extension-path.js +44 -0
- package/bin/provider-auth.d.ts +22 -0
- package/bin/provider-auth.js +77 -0
- package/bin/thinking-pref.js +38 -0
- package/bin/update-check.d.ts +15 -0
- package/bin/update-check.js +137 -0
- package/extensions/cli-update/index.ts +32 -0
- package/extensions/orbit/activity-hooks.ts +141 -0
- package/extensions/orbit/activity.ts +116 -0
- package/extensions/orbit/agent-dir.ts +11 -0
- package/extensions/orbit/auto-resume.ts +352 -0
- package/extensions/orbit/config.ts +3 -0
- package/extensions/orbit/confusables.ts +1 -0
- package/extensions/orbit/context.ts +957 -0
- package/extensions/orbit/dashboard-commands.ts +191 -0
- package/extensions/orbit/dashboard-store.ts +473 -0
- package/extensions/orbit/dashboard-tools.ts +1185 -0
- package/extensions/orbit/empty-completion.ts +99 -0
- package/extensions/orbit/evidence-gate.ts +1276 -0
- package/extensions/orbit/evidence-override-command.ts +294 -0
- package/extensions/orbit/exec-guard/bash-risk.ts +1408 -0
- package/extensions/orbit/exec-guard/gate.ts +181 -0
- package/extensions/orbit/exec-guard/guardian-config.ts +54 -0
- package/extensions/orbit/exec-guard/index.ts +1 -0
- package/extensions/orbit/exec-guard/model-tier.ts +36 -0
- package/extensions/orbit/exec-guard/path-jail.ts +54 -0
- package/extensions/orbit/exec-guard/policy.ts +342 -0
- package/extensions/orbit/exec-guard/runtime-state.ts +15 -0
- package/extensions/orbit/exec-guard/sensitive-read.ts +525 -0
- package/extensions/orbit/exec-guard/types.ts +42 -0
- package/extensions/orbit/execution-commands.ts +134 -0
- package/extensions/orbit/feedback-command.ts +81 -0
- package/extensions/orbit/feedback.ts +96 -0
- package/extensions/orbit/galaxy-api.test.ts +122 -0
- package/extensions/orbit/galaxy-api.ts +606 -0
- package/extensions/orbit/galaxy-artifact-link-guidance.ts +5 -0
- package/extensions/orbit/galaxy-destructive-gate.ts +113 -0
- package/extensions/orbit/galaxy-enrich.ts +1108 -0
- package/extensions/orbit/galaxy-failure.ts +101 -0
- package/extensions/orbit/galaxy-fixture.ts +126 -0
- package/extensions/orbit/galaxy-history-guard.ts +233 -0
- package/extensions/orbit/galaxy-job-block.ts +347 -0
- package/extensions/orbit/galaxy-job-ops.ts +288 -0
- package/extensions/orbit/galaxy-live-source.ts +929 -0
- package/extensions/orbit/galaxy-markdown-adapter.ts +248 -0
- package/extensions/orbit/galaxy-op-policy.ts +332 -0
- package/extensions/orbit/galaxy-ops-tools.ts +326 -0
- package/extensions/orbit/galaxy-output-store.ts +80 -0
- package/extensions/orbit/galaxy-output.ts +346 -0
- package/extensions/orbit/galaxy-page-binding.ts +222 -0
- package/extensions/orbit/galaxy-page-markdown-guidance.ts +15 -0
- package/extensions/orbit/galaxy-page-ops.ts +159 -0
- package/extensions/orbit/galaxy-page-sync.test.ts +417 -0
- package/extensions/orbit/galaxy-page-sync.ts +318 -0
- package/extensions/orbit/galaxy-pages-api.ts +203 -0
- package/extensions/orbit/galaxy-pages-sync.ts +409 -0
- package/extensions/orbit/galaxy-poll-guard.ts +189 -0
- package/extensions/orbit/galaxy-poller.ts +706 -0
- package/extensions/orbit/galaxy-provenance.ts +623 -0
- package/extensions/orbit/galaxy-reconcile.ts +956 -0
- package/extensions/orbit/galaxy-recovery.ts +176 -0
- package/extensions/orbit/galaxy-repeat-guard.ts +55 -0
- package/extensions/orbit/galaxy-submission-capture.ts +707 -0
- package/extensions/orbit/galaxy-submission.ts +502 -0
- package/extensions/orbit/galaxy-tool-source.ts +78 -0
- package/extensions/orbit/galaxy-tool-wrap.ts +234 -0
- package/extensions/orbit/galaxy-udt-block.ts +127 -0
- package/extensions/orbit/galaxy-upload-tus.ts +334 -0
- package/extensions/orbit/galaxy-upload.ts +300 -0
- package/extensions/orbit/galaxy-version-capture.ts +65 -0
- package/extensions/orbit/git.ts +173 -0
- package/extensions/orbit/harness-block-fields.ts +597 -0
- package/extensions/orbit/index.ts +593 -0
- package/extensions/orbit/init-gate.ts +284 -0
- package/extensions/orbit/instructions-command.ts +136 -0
- package/extensions/orbit/invocation-failure-hint.ts +57 -0
- package/extensions/orbit/iwc-plan-check.ts +94 -0
- package/extensions/orbit/lesson-command.ts +303 -0
- package/extensions/orbit/lesson-hint.ts +245 -0
- package/extensions/orbit/lesson-nudge.ts +106 -0
- package/extensions/orbit/lessons/compose.ts +177 -0
- package/extensions/orbit/lessons/counters.ts +120 -0
- package/extensions/orbit/lessons/enabled.ts +116 -0
- package/extensions/orbit/lessons/matcher.ts +346 -0
- package/extensions/orbit/lessons/paths.ts +286 -0
- package/extensions/orbit/lessons/pi-event-contract.ts +105 -0
- package/extensions/orbit/lessons/propose-replay.ts +103 -0
- package/extensions/orbit/lessons/propose-tool.ts +222 -0
- package/extensions/orbit/lessons/propose.ts +502 -0
- package/extensions/orbit/lessons/replay.ts +89 -0
- package/extensions/orbit/lessons/reproduction-index.ts +83 -0
- package/extensions/orbit/lessons/search-tool.ts +99 -0
- package/extensions/orbit/lessons/search.ts +136 -0
- package/extensions/orbit/lessons/store.ts +415 -0
- package/extensions/orbit/lessons/text.ts +36 -0
- package/extensions/orbit/lessons/types.ts +69 -0
- package/extensions/orbit/lessons/user-dir.ts +24 -0
- package/extensions/orbit/lessons/user-lesson.ts +88 -0
- package/extensions/orbit/lessons/wrapper.ts +61 -0
- package/extensions/orbit/lessons-command.ts +147 -0
- package/extensions/orbit/local-exec.ts +39 -0
- package/extensions/orbit/mcp-output.ts +179 -0
- package/extensions/orbit/mcp-servers.test.ts +24 -0
- package/extensions/orbit/mcp-servers.ts +25 -0
- package/extensions/orbit/model-script.ts +351 -0
- package/extensions/orbit/notebook-anchors.ts +342 -0
- package/extensions/orbit/notebook-writer.ts +863 -0
- package/extensions/orbit/observation-allowlists.ts +1109 -0
- package/extensions/orbit/observation-replay.ts +148 -0
- package/extensions/orbit/observation-triggers.ts +563 -0
- package/extensions/orbit/observation-ui.ts +175 -0
- package/extensions/orbit/observations-command.ts +421 -0
- package/extensions/orbit/observations-config.ts +200 -0
- package/extensions/orbit/observations.ts +1381 -0
- package/extensions/orbit/profiles.ts +270 -0
- package/extensions/orbit/progress-updates.ts +95 -0
- package/extensions/orbit/proposal-commands.ts +882 -0
- package/extensions/orbit/provenance-fs-file.ts +59 -0
- package/extensions/orbit/provenance-fs.ts +32 -0
- package/extensions/orbit/provenance-memory-fs.ts +59 -0
- package/extensions/orbit/record-store-file.ts +177 -0
- package/extensions/orbit/record-store-page.ts +77 -0
- package/extensions/orbit/record-store-seam.ts +47 -0
- package/extensions/orbit/record-store.ts +149 -0
- package/extensions/orbit/registry-carrier.ts +107 -0
- package/extensions/orbit/registry-eligibility.ts +95 -0
- package/extensions/orbit/registry-evaluation.ts +403 -0
- package/extensions/orbit/registry-evaluator.ts +519 -0
- package/extensions/orbit/registry-import.ts +91 -0
- package/extensions/orbit/registry-lock.ts +221 -0
- package/extensions/orbit/registry-memory-fs.ts +42 -0
- package/extensions/orbit/registry-page-carrier.ts +103 -0
- package/extensions/orbit/registry-proposal.ts +1348 -0
- package/extensions/orbit/registry-runtime.ts +234 -0
- package/extensions/orbit/registry-schema.ts +735 -0
- package/extensions/orbit/registry-submitter.ts +59 -0
- package/extensions/orbit/registry-templates.ts +346 -0
- package/extensions/orbit/registry.ts +476 -0
- package/extensions/orbit/sandbox/index.ts +120 -0
- package/extensions/orbit/sandbox/sandbox-bash.ts +93 -0
- package/extensions/orbit/sandbox/sandbox-config.ts +87 -0
- package/extensions/orbit/sandbox/sandbox-init-message.ts +28 -0
- package/extensions/orbit/secret-redaction.ts +247 -0
- package/extensions/orbit/session-index/cwd.ts +18 -0
- package/extensions/orbit/session-index/db.ts +56 -0
- package/extensions/orbit/session-index/indexer.ts +154 -0
- package/extensions/orbit/session-index/is-enabled.ts +17 -0
- package/extensions/orbit/session-index/parse.ts +254 -0
- package/extensions/orbit/session-index/query.ts +209 -0
- package/extensions/orbit/session-index/schema.ts +72 -0
- package/extensions/orbit/session-index/tools.ts +152 -0
- package/extensions/orbit/session-lifecycle.ts +321 -0
- package/extensions/orbit/skill-triggers.ts +159 -0
- package/extensions/orbit/skills-command.ts +48 -0
- package/extensions/orbit/skills-discovery.ts +435 -0
- package/extensions/orbit/skills.ts +84 -0
- package/extensions/orbit/sra-import-gate.ts +77 -0
- package/extensions/orbit/state.ts +265 -0
- package/extensions/orbit/submission-replay.ts +147 -0
- package/extensions/orbit/sync-command.ts +157 -0
- package/extensions/orbit/teams/critic-parser.ts +93 -0
- package/extensions/orbit/teams/dispatcher.ts +210 -0
- package/extensions/orbit/teams/is-enabled.ts +22 -0
- package/extensions/orbit/teams/tool.ts +187 -0
- package/extensions/orbit/teams/types.ts +57 -0
- package/extensions/orbit/teams/validate.ts +42 -0
- package/extensions/orbit/tester-id-command.ts +133 -0
- package/extensions/orbit/tools-sync.ts +279 -0
- package/extensions/orbit/tools.ts +1300 -0
- package/extensions/orbit/types.ts +41 -0
- package/extensions/orbit/ui-bridge.ts +55 -0
- package/extensions/orbit/ulid.ts +60 -0
- package/extensions/orbit/user-instructions.ts +388 -0
- package/extensions/orbit/vendor/skills/_catalog.json +68 -0
- package/extensions/orbit/vendor/skills/_manifest.json +1106 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/SKILL.md +112 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_provenance.json +218 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_required_tools.json +33 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/add.json +50 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-extract.json +51 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-next-step.json +42 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/draft-validate.json +140 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/cli/list.json +32 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/notes/galaxy-tool-job-failure-reference.md +137 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/notes/open-requirements-ledger.md +173 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/schemas/galaxy-tool-summary.schema.json +175 -0
- package/extensions/orbit/vendor/skills/advance-galaxy-draft-step/references/schemas/galaxy-workflow-draft.schema.json +2727 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/SKILL.md +82 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_provenance.json +133 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_required_tools.json +16 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/cli/draft-validate.json +140 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/notes/galaxy-workflow-draft-format.md +135 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/notes/open-requirements-ledger.md +173 -0
- package/extensions/orbit/vendor/skills/apply-galaxy-workflow-changeset/references/schemas/galaxy-workflow-draft.schema.json +2727 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/SKILL.md +84 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/_provenance.json +166 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/_verify.json +29 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/galaxy-workflow-testability-design.md +136 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/iwc-test-data-conventions.md +308 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/planemo-asserts-idioms.md +246 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/notes/planemo-workflow-test-architecture.md +151 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/galaxy-workflow-test-plan.schema.json +552 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/summary-galaxy-workflow.schema.json +687 -0
- package/extensions/orbit/vendor/skills/changeset-to-galaxy-test-plan/references/schemas/tests-format.schema.json +7328 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/SKILL.md +127 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_provenance.json +198 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_required_tools.json +16 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/cli/convert.json +123 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/galaxy-data-flow-draft-contract.md +129 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/iwc-test-data-conventions.md +308 -0
- package/extensions/orbit/vendor/skills/compare-against-iwc-exemplar/references/notes/open-requirements-ledger.md +173 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/SKILL.md +73 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/_provenance.json +160 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-collection-semantics.md +43 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-collection-semantics.yml +1250 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-tool-job-failure-reference.md +137 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/nextflow-operators-to-galaxy-collection-recipes.md +98 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/planemo-asserts-idioms.md +246 -0
- package/extensions/orbit/vendor/skills/debug-galaxy-workflow-output/references/notes/planemo-workflow-test-architecture.md +151 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/SKILL.md +174 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/_provenance.json +130 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/_required_tools.json +18 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/_verify.json +17 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-revisions.json +48 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-search.json +95 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/cli/tool-versions.json +39 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/notes/component-tool-shed-search.md +230 -0
- package/extensions/orbit/vendor/skills/discover-shed-tool/references/schemas/galaxy-tool-discovery.schema.json +231 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/SKILL.md +93 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_provenance.json +201 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_required_tools.json +28 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/_verify.json +17 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/cli/planemo.md +44 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/cli/validate-tests.json +40 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/galaxy-workflow-testability-design.md +136 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/iwc-shortcuts-anti-patterns.md +357 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/iwc-test-data-conventions.md +308 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/planemo-asserts-idioms.md +246 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/notes/planemo-workflow-test-architecture.md +151 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/schemas/galaxy-workflow-test-plan.schema.json +552 -0
- package/extensions/orbit/vendor/skills/implement-galaxy-workflow-test/references/schemas/tests-format.schema.json +7328 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/SKILL.md +81 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/_provenance.json +93 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/_verify.json +17 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/references/notes/open-requirements-ledger.md +173 -0
- package/extensions/orbit/vendor/skills/interview-to-galaxy-workflow-changeset/references/schemas/summary-galaxy-workflow.schema.json +687 -0
- package/extensions/orbit/vendor/skills/pipeline-update-interview-to-galaxy/SKILL.md +81 -0
- package/extensions/orbit/vendor/skills/pipeline-update-interview-to-galaxy/_assembly.json +23 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/SKILL.md +79 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/_provenance.json +125 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/_required_tools.json +28 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/cli/planemo.md +44 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/cli/validate-tests.json +40 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/planemo-asserts-idioms.md +246 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/notes/planemo-workflow-test-architecture.md +151 -0
- package/extensions/orbit/vendor/skills/run-workflow-test/references/schemas/tests-format.schema.json +7328 -0
- package/extensions/orbit/vendor/skills/skills/LICENSE +21 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/README.md +39 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/SKILL.md +892 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/api-patterns.md +927 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/apply-rules.md +1482 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/test-patterns.md +996 -0
- package/extensions/orbit/vendor/skills/skills/collection-manipulation/references/tools.md +609 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-data-libraries/SKILL.md +170 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/README.md +162 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/SKILL.md +99 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/README.md +119 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/tool-checking.md +419 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/examples/workflow-testing.md +617 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/galaxy-integration.md +614 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/SKILL.md +144 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/average_col3.ipynb +68 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/extract_sample_metadata.ipynb +190 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/variant_annotation.ipynb +816 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/jupyterlite/examples/vcp_variant_map.ipynb +443 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/scripts/README.md +165 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-integration/scripts/galaxy_tool_checker.py +521 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/SKILL.md +128 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/gotchas.md +135 -0
- package/extensions/orbit/vendor/skills/skills/galaxy-mcp-reference/history-access.md +132 -0
- package/extensions/orbit/vendor/skills/skills/reproduciblify/SKILL.md +146 -0
- package/extensions/orbit/vendor/skills/skills/reproduciblify/references/directives.yml +496 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/SKILL.md +245 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/01-minimal-echo.yml +27 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/02-single-data-in-out.yml +27 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/03-multiple-data-js.yml +28 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/04-select-integer-boolean.yml +41 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/05-resource-requirements.yml +25 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/06-biocontainer-tool.yml +38 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/07-inline-python-script.yml +56 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/examples/08-configfile-script.yml +74 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/references/common-mistakes.md +76 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/references/schema-reference.md +155 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/references/templating.md +196 -0
- package/extensions/orbit/vendor/skills/skills/udt-authoring/scripts/validate.py +109 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/README.md +71 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/SKILL.md +190 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/examples/histology-staining.md +145 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/examples/tissue-microarray-analysis.md +176 -0
- package/extensions/orbit/vendor/skills/skills/workflow-reports/references/directives.md +415 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/SKILL.md +98 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_provenance.json +93 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_required_tools.json +28 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/_verify.json +17 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/convert.json +123 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/foundry.md +25 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/cli/validate.json +142 -0
- package/extensions/orbit/vendor/skills/summarize-galaxy-workflow/references/schemas/summary-galaxy-workflow.schema.json +687 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/SKILL.md +74 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_provenance.json +71 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_required_tools.json +16 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/_verify.json +4 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/cli/validate.json +142 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/notes/galaxy-workflow-invocation-failure-reference.md +138 -0
- package/extensions/orbit/vendor/skills/validate-galaxy-workflow/references/notes/galaxy-workflow-testability-design.md +136 -0
- package/extensions/orbit/vendor-skills.ts +283 -0
- package/extensions/orbit/workspace-state-dir.ts +46 -0
- package/extensions/orbit-handoff/index.ts +73 -0
- package/extensions/orbit-handoff/orbit-launcher.ts +142 -0
- package/extensions/whats-new/index.ts +98 -0
- package/lessons/LICENSE +17 -0
- package/lessons/README.md +117 -0
- package/lessons/SCHEMA.md +187 -0
- package/lessons/build-snapshot.mjs +311 -0
- package/lessons/data/downloaded-file-is-not-what-its-extension-says.md +61 -0
- package/lessons/galaxy-api/403-history-is-a-hard-stop.md +57 -0
- package/lessons/galaxy-api/collection-into-single-dataset-input.md +61 -0
- package/lessons/galaxy-api/connectedvalue-in-command-line.md +62 -0
- package/lessons/galaxy-api/hid-is-not-an-id.md +59 -0
- package/lessons/galaxy-api/invoke-workflow-inputs-not-params.md +60 -0
- package/lessons/galaxy-api/repeat-param-pipe-keys.md +63 -0
- package/lessons/galaxy-api/run-tool-returns-on-submit.md +61 -0
- package/lessons/galaxy-tools/reference-index-not-on-server.md +62 -0
- package/lessons/log.md +42 -0
- package/lessons/reproduction/input-population-mismatch.md +59 -0
- package/lessons/reproduction/methods-text-vs-executed-parameters.md +62 -0
- package/lessons/snapshot.json +1004 -0
- package/lessons/stats/de-contrast-direction-and-sample-labels.md +63 -0
- package/lessons/stats/na-coerced-to-zero-in-filters.md +59 -0
- package/lessons/validate.mjs +1219 -0
- package/package.json +103 -5
- package/shared/agent-prompt.d.ts +8 -0
- package/shared/agent-prompt.js +15 -0
- package/shared/approval-prompt.d.ts +4 -0
- package/shared/approval-prompt.js +61 -0
- package/shared/brain-env.d.ts +14 -0
- package/shared/brain-env.js +108 -0
- package/shared/brain-exit.d.ts +20 -0
- package/shared/brain-exit.js +149 -0
- package/shared/capability-profile.d.ts +16 -0
- package/shared/capability-profile.js +75 -0
- package/shared/config-source.d.ts +13 -0
- package/shared/config-source.js +17 -0
- package/shared/confusables.d.ts +3 -0
- package/shared/confusables.js +93 -0
- package/shared/custom-provider.d.ts +45 -0
- package/shared/custom-provider.js +169 -0
- package/shared/dashboard-contract.d.ts +123 -0
- package/shared/dashboard-contract.js +560 -0
- package/shared/dashboard-layout-store.d.ts +44 -0
- package/shared/dashboard-layout-store.js +281 -0
- package/shared/feedback-contract.d.ts +61 -0
- package/shared/feedback-contract.js +146 -0
- package/shared/galaxy-artifact-links.d.ts +30 -0
- package/shared/galaxy-artifact-links.js +166 -0
- package/shared/galaxy-credentials.d.ts +26 -0
- package/shared/galaxy-credentials.js +71 -0
- package/shared/galaxy-destructive.d.ts +21 -0
- package/shared/galaxy-destructive.js +269 -0
- package/shared/galaxy-guidance.d.ts +2 -0
- package/shared/galaxy-guidance.js +53 -0
- package/shared/galaxy-live-contract.d.ts +92 -0
- package/shared/galaxy-live-contract.js +180 -0
- package/shared/galaxy-page-cells.d.ts +2 -0
- package/shared/galaxy-page-cells.js +22 -0
- package/shared/galaxy-tool-family.d.ts +5 -0
- package/shared/galaxy-tool-family.js +27 -0
- package/shared/galaxy-url.d.ts +1 -0
- package/shared/galaxy-url.js +48 -0
- package/shared/gtn.d.ts +31 -0
- package/shared/gtn.js +253 -0
- package/shared/handled-prompt.d.ts +3 -0
- package/shared/handled-prompt.js +26 -0
- package/shared/known-provider.d.ts +11 -0
- package/shared/known-provider.js +89 -0
- package/shared/lesson-hint-marker.d.ts +7 -0
- package/shared/lesson-hint-marker.js +32 -0
- package/shared/lesson-rules.d.ts +150 -0
- package/shared/lesson-rules.js +203 -0
- package/shared/malformed-object-ids.d.ts +1 -0
- package/shared/malformed-object-ids.js +37 -0
- package/shared/markdown-sections.d.ts +10 -0
- package/shared/markdown-sections.js +118 -0
- package/shared/notebook-fences.d.ts +12 -0
- package/shared/notebook-fences.js +53 -0
- package/shared/observation-contract.d.ts +91 -0
- package/shared/observation-contract.js +544 -0
- package/shared/orbit-config.d.ts +226 -0
- package/shared/orbit-config.js +189 -0
- package/shared/orbit-env.d.ts +13 -0
- package/shared/orbit-env.js +124 -0
- package/shared/orbit-shell-contract.d.ts +74 -0
- package/shared/orbit-shell-contract.js +30 -0
- package/shared/page-content-hash.d.ts +1 -0
- package/shared/page-content-hash.js +13 -0
- package/shared/pi-default-model.d.ts +7 -0
- package/shared/pi-default-model.js +41 -0
- package/shared/prompt-blocks.d.ts +87 -0
- package/shared/prompt-blocks.js +898 -0
- package/shared/provider-auth-caps.d.ts +22 -0
- package/shared/provider-auth-caps.js +57 -0
- package/shared/random.d.ts +2 -0
- package/shared/random.js +15 -0
- package/shared/record-excerpt.d.ts +4 -0
- package/shared/record-excerpt.js +16 -0
- package/shared/redirect-guard.d.ts +36 -0
- package/shared/redirect-guard.js +235 -0
- package/shared/skills-pin.d.ts +8 -0
- package/shared/skills-pin.js +9 -0
- package/shared/skills-router.d.ts +17 -0
- package/shared/skills-router.js +63 -0
- package/shared/sra-import.d.ts +34 -0
- package/shared/sra-import.js +206 -0
- package/shared/state-dir.d.ts +21 -0
- package/shared/state-dir.js +240 -0
- package/shared/team-dispatch-contract.d.ts +28 -0
- package/shared/team-dispatch-contract.js +1 -0
- package/shared/truncate-middle.d.ts +11 -0
- package/shared/truncate-middle.js +68 -0
- package/shared/version-compare.d.ts +10 -0
- package/shared/version-compare.js +74 -0
- package/shared/whats-new.d.ts +31 -0
- package/shared/whats-new.js +83 -0
- package/shared/wsl.d.ts +10 -0
- package/shared/wsl.js +24 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lesson trigger matching. Pure -- no fs, no config, no pi, no clock. Every
|
|
3
|
+
* input is passed in, which is what lets the table be tested exhaustively and
|
|
4
|
+
* what lets one implementation answer for a live tool result, a plan step and
|
|
5
|
+
* an eval fixture.
|
|
6
|
+
*
|
|
7
|
+
* Three deliberate properties:
|
|
8
|
+
*
|
|
9
|
+
* - **`isError` is not an input.** A lesson fires on what a result SAYS, not on
|
|
10
|
+
* whether pi called it an error. Galaxy reports a failed invocation inside a
|
|
11
|
+
* perfectly successful tool result (`invocation-failure-hint.ts` relies on
|
|
12
|
+
* the same thing), so gating on `isError` would miss the cases that matter.
|
|
13
|
+
* - **A lesson with a non-empty `graduated_to` never matches.** Its advice
|
|
14
|
+
* already ships somewhere else, and surfacing both is noise. The lesson stays
|
|
15
|
+
* in the corpus as the record of what happened.
|
|
16
|
+
* - **Signatures are matched after normalizing BOTH sides with C1's own
|
|
17
|
+
* function.** A stored signature already carries C1's placeholders, so a raw
|
|
18
|
+
* substring test could never hit, and a second implementation of those rules
|
|
19
|
+
* would drift from the one that normalized the corpus. Two matching-only
|
|
20
|
+
* additions on top: it is applied PER LINE (its "first line only" clause
|
|
21
|
+
* turns one error into one signature; applied to a whole tool result it would
|
|
22
|
+
* see only line 1), and both sides are lowercased, because a lesson author
|
|
23
|
+
* will not match the tool's capitalization.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { normalizeSignature, UNKNOWN_SIGNATURE } from "../../../shared/observation-contract.js";
|
|
27
|
+
import type { Lesson, Match, MatchTrigger } from "./types";
|
|
28
|
+
|
|
29
|
+
/** Lower is more specific, so a signature hit beats an ambient step-text hit. */
|
|
30
|
+
const TRIGGER_RANK: Record<MatchTrigger, number> = {
|
|
31
|
+
signature: 0,
|
|
32
|
+
tool: 1,
|
|
33
|
+
host: 2,
|
|
34
|
+
extension: 3,
|
|
35
|
+
step_keyword: 4,
|
|
36
|
+
search: 5,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Caps so a hostile result or a pathological lesson cannot burn the turn. */
|
|
40
|
+
const MAX_RESULT_CHARS = 200_000;
|
|
41
|
+
const MAX_ARG_CHARS = 20_000;
|
|
42
|
+
const MAX_VALUE_DEPTH = 6;
|
|
43
|
+
const MAX_VALUES = 500;
|
|
44
|
+
const MAX_STEP_CHARS = 4_000;
|
|
45
|
+
|
|
46
|
+
/** A short "signature" would match nearly every result (C3 sets the same floor). */
|
|
47
|
+
const MIN_SIGNATURE_CHARS = 8;
|
|
48
|
+
|
|
49
|
+
/** Shorter than this a tool id or keyword cannot discriminate. */
|
|
50
|
+
const MIN_TOOL_CHARS = 3;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `normalizeSignature` keeps one line and truncates at 200 (C1's cap), so a
|
|
54
|
+
* long single line -- a serialized JSON blob, a stack frame -- is scanned in
|
|
55
|
+
* overlapping windows instead. Each window's normalized text is itself cut at
|
|
56
|
+
* 200, so a window starting at most STRIDE characters before a signature
|
|
57
|
+
* holds it whole when the signature normalizes to at most 200 - STRIDE = 150
|
|
58
|
+
* characters. Longer signatures (C1 allows 200) can be missed on lines over
|
|
59
|
+
* WINDOW characters; the shipped corpus tops out well under 100.
|
|
60
|
+
*/
|
|
61
|
+
const WINDOW = 256;
|
|
62
|
+
const STRIDE = 50;
|
|
63
|
+
|
|
64
|
+
/** Every line of the result, run through `normalizeSignature` and lowercased. */
|
|
65
|
+
export function matchHaystack(resultText: string): string[] {
|
|
66
|
+
const out: string[] = [];
|
|
67
|
+
const push = (raw: string): void => {
|
|
68
|
+
const normalized = normalizeSignature(raw);
|
|
69
|
+
if (normalized !== UNKNOWN_SIGNATURE) out.push(normalized.toLowerCase());
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
for (const line of resultText.slice(0, MAX_RESULT_CHARS).split("\n")) {
|
|
73
|
+
if (!line.trim()) continue;
|
|
74
|
+
if (line.length <= WINDOW) {
|
|
75
|
+
push(line);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Run to the very end: the window that first touches it is also cut at
|
|
79
|
+
// 200 once normalized, so the line's tail needs the shorter ones too.
|
|
80
|
+
for (let i = 0; i < line.length; i += STRIDE) push(line.slice(i, i + WINDOW));
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Every string VALUE in a call's arguments, recursively. Values only: matching
|
|
87
|
+
* key names would let a lesson fire on a schema field rather than on data.
|
|
88
|
+
*/
|
|
89
|
+
export function collectStringValues(value: unknown, depth = 0, out: string[] = []): string[] {
|
|
90
|
+
if (depth > MAX_VALUE_DEPTH || out.length >= MAX_VALUES) return out;
|
|
91
|
+
if (typeof value === "string") out.push(value);
|
|
92
|
+
else if (Array.isArray(value)) {
|
|
93
|
+
for (const v of value) collectStringValues(v, depth + 1, out);
|
|
94
|
+
} else if (value && typeof value === "object") {
|
|
95
|
+
for (const v of Object.values(value)) collectStringValues(v, depth + 1, out);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const URL_HOST = /\b[a-z][a-z0-9+.-]*:\/\/([^/?#\s"'`\\]+)/gi;
|
|
101
|
+
|
|
102
|
+
export function extractHosts(text: string): Set<string> {
|
|
103
|
+
const out = new Set<string>();
|
|
104
|
+
for (const m of text.matchAll(URL_HOST)) {
|
|
105
|
+
let host = m[1];
|
|
106
|
+
const at = host.lastIndexOf("@");
|
|
107
|
+
if (at >= 0) host = host.slice(at + 1);
|
|
108
|
+
host = host.replace(/:\d*$/, "").replace(/\.$/, "").toLowerCase();
|
|
109
|
+
if (host) out.add(host);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A lesson host matches the exact host or any subdomain of it, on a dot
|
|
116
|
+
* boundary. Without the boundary `ncbi.nlm.nih.gov` would also match
|
|
117
|
+
* `evilncbi.nlm.nih.gov`.
|
|
118
|
+
*/
|
|
119
|
+
export function hostMatches(seen: Set<string>, want: string): boolean {
|
|
120
|
+
const w = want.trim().toLowerCase().replace(/^\.+/, "");
|
|
121
|
+
if (!w) return false;
|
|
122
|
+
for (const host of seen) {
|
|
123
|
+
if (host === w || host.endsWith(`.${w}`)) return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Letter-led, so a version segment (`tool/2.0.1`) never reads as an extension.
|
|
130
|
+
* A following `.` counts as a boundary because double suffixes are everywhere
|
|
131
|
+
* here -- `.fastq.gz` has to yield both halves.
|
|
132
|
+
*/
|
|
133
|
+
const EXT_TOKEN = /\.([A-Za-z][A-Za-z0-9]{0,7})(?=[."'\s,;:)\]}\\]|$)/g;
|
|
134
|
+
|
|
135
|
+
export function extractExtensions(text: string): Set<string> {
|
|
136
|
+
const out = new Set<string>();
|
|
137
|
+
for (const m of text.matchAll(EXT_TOKEN)) out.add(m[1].toLowerCase());
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function escapeRegExp(value: string): string {
|
|
142
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A lesson extension (".gtf", ".tsv.gz") on the end of a file name somewhere
|
|
147
|
+
* in the arguments. Matched as one dotted unit so a two-part extension means
|
|
148
|
+
* what its author wrote, and anchored on a name character before the dot so a
|
|
149
|
+
* bare ".gtf" in prose is not a file.
|
|
150
|
+
*/
|
|
151
|
+
export function extensionMatches(haystack: string, want: string): boolean {
|
|
152
|
+
const w = want.trim().toLowerCase().replace(/^\.+/, "");
|
|
153
|
+
if (!w) return false;
|
|
154
|
+
const re = new RegExp(`[A-Za-z0-9_-]\\.${escapeRegExp(w)}(?![A-Za-z0-9])`, "i");
|
|
155
|
+
return re.test(haystack);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* `<host>/repos/<owner>/<repo>/<name>[/<version>]` -> `<name>`. Lesson tool
|
|
160
|
+
* triggers are short ids or families (C3 forbids `/` in them), while a Galaxy
|
|
161
|
+
* call carries the full ToolShed GUID, so the GUID's name segment is the part
|
|
162
|
+
* a lesson can name.
|
|
163
|
+
*/
|
|
164
|
+
const TOOLSHED_GUID =
|
|
165
|
+
/^(?:https?:\/\/)?[^/\s]+\/repos\/[^/\s]+\/[^/\s]+\/([^/\s]+)(?:\/[^/\s]+)?$/i;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Every string argument that could be a tool id, as lowercased candidates: the
|
|
169
|
+
* value itself (a bare id like `Filter1`) and, for a ToolShed GUID, its name
|
|
170
|
+
* segment. A lesson's `trigger.tools` entry has to equal one of them.
|
|
171
|
+
*/
|
|
172
|
+
export function toolCandidates(values: readonly string[]): Set<string> {
|
|
173
|
+
const out = new Set<string>();
|
|
174
|
+
for (const raw of values) {
|
|
175
|
+
const v = raw.trim();
|
|
176
|
+
if (!v || v.length > 300) continue;
|
|
177
|
+
out.add(v.toLowerCase());
|
|
178
|
+
const guid = TOOLSHED_GUID.exec(v);
|
|
179
|
+
if (guid) out.add(guid[1].toLowerCase());
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function toolMatches(candidates: Set<string>, want: string): boolean {
|
|
185
|
+
const w = want.trim().toLowerCase();
|
|
186
|
+
return w.length >= MIN_TOOL_CHARS && candidates.has(w);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Lessons written before the in-process tools named Galaxy tools this way; a
|
|
191
|
+
* user's lessons dir can still hold them, so they keep firing on the bare call.
|
|
192
|
+
*/
|
|
193
|
+
const LEGACY_LESSON_PREFIX = "mcp__galaxy__";
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The Galaxy tool behind a name, or undefined when it is not one. The remainder
|
|
197
|
+
* is held to the Galaxy tools' own name shape -- no leading "_", no "__" -- on
|
|
198
|
+
* both sides, so a lesson naming `galaxy__x` cannot line up with another
|
|
199
|
+
* server's `mcp__<server>__x` spelling.
|
|
200
|
+
*/
|
|
201
|
+
function galaxyToolOf(name: string): string | undefined {
|
|
202
|
+
const raw = name.trim();
|
|
203
|
+
const tool = raw.toLowerCase().startsWith("galaxy_") ? raw.slice("galaxy_".length) : raw;
|
|
204
|
+
if (!tool || tool.startsWith("_") || tool.includes("__")) return undefined;
|
|
205
|
+
return tool.toLowerCase();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Compared after stripping the `galaxy_` prefix from both sides, so a lesson
|
|
210
|
+
* written against `galaxy_run_tool` still fires when the call arrived as
|
|
211
|
+
* `run_tool`. The legacy prefix is stripped from the lesson only: a call named
|
|
212
|
+
* `mcp__galaxy__x` comes from a server the user added, not the Galaxy tools.
|
|
213
|
+
*/
|
|
214
|
+
export function mcpToolMatches(toolName: string, want: string): boolean {
|
|
215
|
+
const lessonTool = want.trim().toLowerCase().startsWith(LEGACY_LESSON_PREFIX)
|
|
216
|
+
? want.trim().slice(LEGACY_LESSON_PREFIX.length)
|
|
217
|
+
: want;
|
|
218
|
+
const a = galaxyToolOf(toolName);
|
|
219
|
+
const b = galaxyToolOf(lessonTool);
|
|
220
|
+
return a !== undefined && b !== undefined && a === b;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function isMatchable(lesson: Lesson): boolean {
|
|
224
|
+
return !(lesson.graduated_to ?? []).some((g) => typeof g === "string" && g.trim().length > 0);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function firstHit(
|
|
228
|
+
trigger: MatchTrigger,
|
|
229
|
+
values: readonly string[] | undefined,
|
|
230
|
+
test: (value: string) => boolean,
|
|
231
|
+
): { trigger: MatchTrigger; matched: string } | null {
|
|
232
|
+
for (const value of values ?? []) {
|
|
233
|
+
if (typeof value !== "string") continue;
|
|
234
|
+
if (test(value)) return { trigger, matched: value };
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function originRank(lesson: Lesson): number {
|
|
240
|
+
// A lesson you recorded yourself is about your machine and your data, so on
|
|
241
|
+
// an equal trigger it goes ahead of the shipped corpus.
|
|
242
|
+
return lesson.origin === "user" ? 0 : 1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function rank(matches: Match[]): Match[] {
|
|
246
|
+
return matches.sort((a, b) => {
|
|
247
|
+
const byTrigger = TRIGGER_RANK[a.trigger] - TRIGGER_RANK[b.trigger];
|
|
248
|
+
if (byTrigger !== 0) return byTrigger;
|
|
249
|
+
const byOrigin = originRank(a.lesson) - originRank(b.lesson);
|
|
250
|
+
if (byOrigin !== 0) return byOrigin;
|
|
251
|
+
return a.lesson.id < b.lesson.id ? -1 : a.lesson.id > b.lesson.id ? 1 : 0;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface ToolEventInput {
|
|
256
|
+
/**
|
|
257
|
+
* pi's tool name: the Galaxy tools by their bare names, Orbit's own as
|
|
258
|
+
* `galaxy_<name>`. mcpToolMatches strips the `galaxy_` prefix, so a lesson can
|
|
259
|
+
* name the tool either way.
|
|
260
|
+
*/
|
|
261
|
+
toolName: string;
|
|
262
|
+
/** The call's arguments. */
|
|
263
|
+
input: Record<string, unknown>;
|
|
264
|
+
/** Every text block of the result, joined with newlines. */
|
|
265
|
+
resultText: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** One match per lesson, most specific trigger first. */
|
|
269
|
+
export function matchToolEvent(ev: ToolEventInput, lessons: readonly Lesson[]): Match[] {
|
|
270
|
+
const lines = matchHaystack(ev.resultText);
|
|
271
|
+
const values = collectStringValues(ev.input);
|
|
272
|
+
const args = values.join("\n").slice(0, MAX_ARG_CHARS);
|
|
273
|
+
const hosts = extractHosts(args);
|
|
274
|
+
const exts = extractExtensions(args);
|
|
275
|
+
const tools = toolCandidates(values);
|
|
276
|
+
const argValues = new Set(values.map((v) => v.trim().toLowerCase()));
|
|
277
|
+
|
|
278
|
+
const out: Match[] = [];
|
|
279
|
+
for (const lesson of lessons) {
|
|
280
|
+
if (!isMatchable(lesson)) continue;
|
|
281
|
+
const t = lesson.trigger ?? {};
|
|
282
|
+
const hit =
|
|
283
|
+
firstHit("signature", t.signatures, (s) => {
|
|
284
|
+
// Normalized on this side too: a corpus signature already is, and the
|
|
285
|
+
// function is idempotent.
|
|
286
|
+
const needle = normalizeSignature(s).toLowerCase();
|
|
287
|
+
return (
|
|
288
|
+
needle !== UNKNOWN_SIGNATURE &&
|
|
289
|
+
needle.length >= MIN_SIGNATURE_CHARS &&
|
|
290
|
+
lines.some((l) => l.includes(needle))
|
|
291
|
+
);
|
|
292
|
+
}) ??
|
|
293
|
+
firstHit("tool", t.mcp_tools, (s) => mcpToolMatches(ev.toolName, s)) ??
|
|
294
|
+
firstHit("tool", t.tools, (s) => toolMatches(tools, s)) ??
|
|
295
|
+
firstHit("host", t.hosts, (s) => hostMatches(hosts, s)) ??
|
|
296
|
+
firstHit("extension", t.extensions, (s) => extensionMatches(args, s)) ??
|
|
297
|
+
// C5 has no `format` trigger value, so a datatype hit reports as the
|
|
298
|
+
// nearest one. Matched two ways because C3 uses `formats` for both Galaxy
|
|
299
|
+
// datatype names and extensions: a `file_type: "gtf"` argument, and a
|
|
300
|
+
// `.gtf` on a file name.
|
|
301
|
+
firstHit("extension", t.formats, (s) => {
|
|
302
|
+
const f = s.trim().toLowerCase().replace(/^\.+/, "");
|
|
303
|
+
return f.length > 0 && (exts.has(f) || argValues.has(f));
|
|
304
|
+
});
|
|
305
|
+
if (hit) out.push({ lesson, trigger: hit.trigger, matched: hit.matched });
|
|
306
|
+
}
|
|
307
|
+
return rank(out);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Fold to lowercase words separated by single spaces, padded with spaces, so
|
|
312
|
+
* a keyword test is a whole-word (or whole-phrase) test -- `gtf` must not
|
|
313
|
+
* match `gtfoobar`, and `sample to condition` has to match
|
|
314
|
+
* "sample-to-condition".
|
|
315
|
+
*/
|
|
316
|
+
export function wordHaystack(text: string): string {
|
|
317
|
+
const folded = text
|
|
318
|
+
.slice(0, MAX_STEP_CHARS)
|
|
319
|
+
.toLowerCase()
|
|
320
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
321
|
+
.trim();
|
|
322
|
+
return ` ${folded} `;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Plan-step matching. `/execute` already parses the next step's text before
|
|
327
|
+
* building its prompt, so matched lessons can ride along at no extra cost. It
|
|
328
|
+
* only reaches what the step says -- there is no stage state to ask instead,
|
|
329
|
+
* and no regex answers "is this a normalizing step?". That gap is why the
|
|
330
|
+
* reproduction index exists.
|
|
331
|
+
*/
|
|
332
|
+
export function matchStepText(text: string, lessons: readonly Lesson[]): Match[] {
|
|
333
|
+
const hay = wordHaystack(text);
|
|
334
|
+
if (hay.trim().length === 0) return [];
|
|
335
|
+
|
|
336
|
+
const out: Match[] = [];
|
|
337
|
+
for (const lesson of lessons) {
|
|
338
|
+
if (!isMatchable(lesson)) continue;
|
|
339
|
+
const hit = firstHit("step_keyword", lesson.trigger?.step_keywords, (kw) => {
|
|
340
|
+
const needle = wordHaystack(kw);
|
|
341
|
+
return needle.trim().length >= MIN_TOOL_CHARS && hay.includes(needle);
|
|
342
|
+
});
|
|
343
|
+
if (hit) out.push({ lesson, trigger: hit.trigger, matched: hit.matched });
|
|
344
|
+
}
|
|
345
|
+
return rank(out);
|
|
346
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where local lessons and staged drafts live on disk.
|
|
3
|
+
*
|
|
4
|
+
* Two placements carry weight:
|
|
5
|
+
*
|
|
6
|
+
* - Lessons go under getConfigDir(), which is ~/.loom today and ~/.orbit after
|
|
7
|
+
* the rename (shared/state-dir.js). Nothing here joins homedir() with
|
|
8
|
+
* ".loom" itself, so the move stays one switch. LOOM_LESSONS_DIR replaces
|
|
9
|
+
* that directory for the writer and the store alike (./user-dir), so a
|
|
10
|
+
* lesson saved here is a lesson the store loads.
|
|
11
|
+
* - Staged drafts go in a SIBLING directory, not inside lessons/. A draft the
|
|
12
|
+
* user has not approved must not be loadable as a lesson, and a separate
|
|
13
|
+
* directory guarantees that without the lesson store having to know the
|
|
14
|
+
* difference. The flat `<namespace>__<slug>.md` name also cannot satisfy
|
|
15
|
+
* lessonIdFromPath, which requires exactly `<namespace>/<slug>.md`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as crypto from "crypto";
|
|
19
|
+
import * as fs from "fs";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
import { getConfigDir } from "../../../shared/orbit-config.js";
|
|
22
|
+
import { userLessonsDir } from "./user-dir";
|
|
23
|
+
import {
|
|
24
|
+
LESSON_NAMESPACES,
|
|
25
|
+
MAX_FILE_BYTES,
|
|
26
|
+
isValidLessonNamespace,
|
|
27
|
+
isValidLessonSlug,
|
|
28
|
+
lessonIdFromPath,
|
|
29
|
+
} from "../../../shared/lesson-rules.js";
|
|
30
|
+
|
|
31
|
+
export interface LocalLesson {
|
|
32
|
+
id: string;
|
|
33
|
+
namespace: string;
|
|
34
|
+
slug: string;
|
|
35
|
+
path: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function lessonsDir(): string {
|
|
39
|
+
return userLessonsDir();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function draftsDir(): string {
|
|
43
|
+
return path.join(getConfigDir(), "lesson-drafts");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Throws rather than returning null: every caller has already validated, so a
|
|
48
|
+
* bad value here means a check was skipped, and building a path anyway would
|
|
49
|
+
* be a traversal write.
|
|
50
|
+
*/
|
|
51
|
+
function assertId(namespace: string, slug: string): void {
|
|
52
|
+
if (!isValidLessonNamespace(namespace)) {
|
|
53
|
+
throw new Error(`not a lesson namespace: ${JSON.stringify(namespace)}`);
|
|
54
|
+
}
|
|
55
|
+
if (!isValidLessonSlug(slug)) {
|
|
56
|
+
throw new Error(`not a lesson slug: ${JSON.stringify(slug)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function lessonFilePath(namespace: string, slug: string): string {
|
|
61
|
+
assertId(namespace, slug);
|
|
62
|
+
return path.join(lessonsDir(), namespace, `${slug}.md`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function draftFilePath(namespace: string, slug: string): string {
|
|
66
|
+
assertId(namespace, slug);
|
|
67
|
+
return path.join(draftsDir(), `${namespace}__${slug}.md`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Split `<namespace>/<slug>` into its parts, or null if it is not a lesson id. */
|
|
71
|
+
export function parseLessonId(raw: unknown): { namespace: string; slug: string } | null {
|
|
72
|
+
if (typeof raw !== "string") return null;
|
|
73
|
+
const parts = raw.replace(/^[ \t]+|[ \t]+$/g, "").split("/");
|
|
74
|
+
if (parts.length !== 2) return null;
|
|
75
|
+
const [namespace, slug] = parts;
|
|
76
|
+
if (!isValidLessonNamespace(namespace) || !isValidLessonSlug(slug)) return null;
|
|
77
|
+
return { namespace, slug };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function listLocalLessons(): LocalLesson[] {
|
|
81
|
+
const root = lessonsDir();
|
|
82
|
+
const out: LocalLesson[] = [];
|
|
83
|
+
for (const namespace of LESSON_NAMESPACES) {
|
|
84
|
+
let entries: fs.Dirent[];
|
|
85
|
+
try {
|
|
86
|
+
entries = fs.readdirSync(path.join(root, namespace), { withFileTypes: true });
|
|
87
|
+
} catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
// isFile() is false for a symlink file; a symlinked directory is caught
|
|
92
|
+
// when the file is read (parentIsReal).
|
|
93
|
+
if (!entry.isFile()) continue;
|
|
94
|
+
const id = lessonIdFromPath(`${namespace}/${entry.name}`);
|
|
95
|
+
if (!id) continue;
|
|
96
|
+
out.push({
|
|
97
|
+
id,
|
|
98
|
+
namespace,
|
|
99
|
+
slug: entry.name.slice(0, -3),
|
|
100
|
+
path: path.join(root, namespace, entry.name),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function listDrafts(): LocalLesson[] {
|
|
108
|
+
const root = draftsDir();
|
|
109
|
+
let entries: fs.Dirent[];
|
|
110
|
+
try {
|
|
111
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
112
|
+
} catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const out: LocalLesson[] = [];
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
118
|
+
const parts = entry.name.slice(0, -3).split("__");
|
|
119
|
+
if (parts.length !== 2) continue;
|
|
120
|
+
const [namespace, slug] = parts;
|
|
121
|
+
if (!isValidLessonNamespace(namespace) || !isValidLessonSlug(slug)) continue;
|
|
122
|
+
out.push({ id: `${namespace}/${slug}`, namespace, slug, path: path.join(root, entry.name) });
|
|
123
|
+
}
|
|
124
|
+
return out.sort((a, b) => a.id.localeCompare(b.id));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type ReadResult = { ok: true; text: string } | { ok: false; detail: string };
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Read a lesson or draft, refusing anything over the schema's byte cap or that
|
|
131
|
+
* is not a regular file. lstat rather than stat so a symlink is refused rather
|
|
132
|
+
* than followed, and the cap is checked before the read so a planted 2 GB file
|
|
133
|
+
* costs a syscall, not the heap.
|
|
134
|
+
*/
|
|
135
|
+
export function readLessonFile(filePath: string): ReadResult {
|
|
136
|
+
try {
|
|
137
|
+
if (!parentIsReal(filePath)) return { ok: false, detail: "inside a symlinked directory" };
|
|
138
|
+
const stat = fs.lstatSync(filePath);
|
|
139
|
+
if (!stat.isFile()) return { ok: false, detail: "not a regular file" };
|
|
140
|
+
if (stat.size > MAX_FILE_BYTES) {
|
|
141
|
+
return { ok: false, detail: `file is too large (cap is ${MAX_FILE_BYTES} bytes)` };
|
|
142
|
+
}
|
|
143
|
+
return { ok: true, text: fs.readFileSync(filePath, "utf-8") };
|
|
144
|
+
} catch (err) {
|
|
145
|
+
const code = (err as NodeJS.ErrnoException)?.code;
|
|
146
|
+
return { ok: false, detail: code === "ENOENT" ? "no such file" : (code ?? "unreadable") };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type WriteResult = { ok: true } | { ok: false; reason: "exists" | "error"; detail?: string };
|
|
151
|
+
|
|
152
|
+
function errorDetail(err: unknown): string {
|
|
153
|
+
return (err as NodeJS.ErrnoException)?.code ?? (err instanceof Error ? err.message : "error");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The directory a path under the lessons tree is measured from. With
|
|
158
|
+
* LOOM_LESSONS_DIR set, the directory the user named is the root for anything
|
|
159
|
+
* under it -- itself possibly a link, wherever it sits, the same as the store
|
|
160
|
+
* reads it. Otherwise the state dir, so a linked lessons/ inside it is refused.
|
|
161
|
+
* The state dir itself may be a link (dotfile setups do that).
|
|
162
|
+
*/
|
|
163
|
+
function rootOf(p: string): string | undefined {
|
|
164
|
+
const stateDir = getConfigDir();
|
|
165
|
+
const overridden = lessonsDir() !== path.join(stateDir, "lessons");
|
|
166
|
+
const roots = overridden ? [lessonsDir(), stateDir] : [stateDir];
|
|
167
|
+
return roots.find((r) => {
|
|
168
|
+
const rel = path.relative(r, p);
|
|
169
|
+
return !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** True when `p` exists and nothing between its root and it is a link. */
|
|
174
|
+
function resolvesInPlace(root: string, p: string): boolean {
|
|
175
|
+
return fs.realpathSync(p) === path.join(fs.realpathSync(root), path.relative(root, p));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* True when no directory between the root and `filePath` is a symlink.
|
|
180
|
+
* lstat on the file only covers its last component; a linked lessons/ or
|
|
181
|
+
* namespace directory would otherwise send reads and writes somewhere else.
|
|
182
|
+
* A directory that does not exist yet is judged by its nearest existing
|
|
183
|
+
* ancestor, so the check can run before mkdir creates anything through a link.
|
|
184
|
+
*/
|
|
185
|
+
function parentIsReal(filePath: string): boolean {
|
|
186
|
+
const dir = path.dirname(filePath);
|
|
187
|
+
const root = rootOf(dir);
|
|
188
|
+
if (!root) return false;
|
|
189
|
+
for (let probe = dir; ; probe = path.dirname(probe)) {
|
|
190
|
+
try {
|
|
191
|
+
return resolvesInPlace(root, probe);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if ((err as NodeJS.ErrnoException)?.code !== "ENOENT") return false;
|
|
194
|
+
// A dangling link is something there, not nothing.
|
|
195
|
+
if (fs.lstatSync(probe, { throwIfNoEntry: false })) return false;
|
|
196
|
+
// Nothing there yet means nothing to follow; the caller's own fs call
|
|
197
|
+
// reports the missing file.
|
|
198
|
+
if (probe === root) return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Write the bytes beside `filePath` under a name nothing else will pick. */
|
|
204
|
+
function writeTemp(filePath: string, text: string): string {
|
|
205
|
+
const tmp = path.join(
|
|
206
|
+
path.dirname(filePath),
|
|
207
|
+
`.${path.basename(filePath)}.${crypto.randomBytes(6).toString("hex")}.tmp`,
|
|
208
|
+
);
|
|
209
|
+
const fd = fs.openSync(tmp, "wx", 0o644);
|
|
210
|
+
try {
|
|
211
|
+
fs.writeSync(fd, text);
|
|
212
|
+
fs.fsyncSync(fd);
|
|
213
|
+
} finally {
|
|
214
|
+
fs.closeSync(fd);
|
|
215
|
+
}
|
|
216
|
+
return tmp;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Create the lesson or fail -- never replace one. The bytes go to a temp file
|
|
221
|
+
* first and are hard-linked into place, so the lesson appears whole or not at
|
|
222
|
+
* all, and link() refuses an existing target in the same syscall that would
|
|
223
|
+
* otherwise race an exists-check. Nothing on this path rewrites a lesson: an
|
|
224
|
+
* id that is taken is a new slug's problem, not an overwrite.
|
|
225
|
+
*/
|
|
226
|
+
export function writeNoClobber(filePath: string, text: string): WriteResult {
|
|
227
|
+
const refused: WriteResult = { ok: false, reason: "error", detail: "symlinked directory" };
|
|
228
|
+
let tmp: string;
|
|
229
|
+
try {
|
|
230
|
+
if (!parentIsReal(filePath)) return refused;
|
|
231
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
232
|
+
if (!parentIsReal(filePath)) return refused;
|
|
233
|
+
tmp = writeTemp(filePath, text);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
return { ok: false, reason: "error", detail: errorDetail(err) };
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
fs.linkSync(tmp, filePath);
|
|
239
|
+
// The checks above run before the write, so a directory swapped for a
|
|
240
|
+
// link in between would carry the lesson out of the tree. Look at where
|
|
241
|
+
// it actually landed and take it back if that is somewhere else.
|
|
242
|
+
const root = rootOf(filePath);
|
|
243
|
+
let inPlace = false;
|
|
244
|
+
try {
|
|
245
|
+
inPlace = root !== undefined && resolvesInPlace(root, filePath);
|
|
246
|
+
} catch {
|
|
247
|
+
/* unresolvable counts as not in place */
|
|
248
|
+
}
|
|
249
|
+
if (!inPlace) {
|
|
250
|
+
fs.unlinkSync(filePath);
|
|
251
|
+
return refused;
|
|
252
|
+
}
|
|
253
|
+
return { ok: true };
|
|
254
|
+
} catch (err) {
|
|
255
|
+
if ((err as NodeJS.ErrnoException)?.code === "EEXIST") return { ok: false, reason: "exists" };
|
|
256
|
+
return { ok: false, reason: "error", detail: errorDetail(err) };
|
|
257
|
+
} finally {
|
|
258
|
+
try {
|
|
259
|
+
fs.unlinkSync(tmp);
|
|
260
|
+
} catch {
|
|
261
|
+
/* a stray dot-file is harmless; the lesson write already reported */
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Drafts are scratch space, so a re-proposal replaces one. Still atomic. */
|
|
267
|
+
export function writeOverwrite(
|
|
268
|
+
filePath: string,
|
|
269
|
+
text: string,
|
|
270
|
+
): { ok: true } | { ok: false; detail: string } {
|
|
271
|
+
try {
|
|
272
|
+
if (!parentIsReal(filePath)) return { ok: false, detail: "symlinked directory" };
|
|
273
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
274
|
+
if (!parentIsReal(filePath)) return { ok: false, detail: "symlinked directory" };
|
|
275
|
+
const tmp = writeTemp(filePath, text);
|
|
276
|
+
try {
|
|
277
|
+
fs.renameSync(tmp, filePath);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
fs.rmSync(tmp, { force: true });
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
return { ok: true };
|
|
283
|
+
} catch (err) {
|
|
284
|
+
return { ok: false, detail: errorDetail(err) };
|
|
285
|
+
}
|
|
286
|
+
}
|