firedrill-run 2.2.5 → 3.0.1
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/README.md +176 -22
- package/bin/firedrill.mjs +29 -5
- package/dist/assembly.json +13 -0
- package/dist/cli/runtime/candidate_copy.py +395 -0
- package/dist/cli/runtime/source_snapshot.py +391 -0
- package/dist/cli/src/administration.js +295 -0
- package/dist/cli/src/agent-changes.js +496 -0
- package/dist/cli/src/assessment-requests.js +67 -0
- package/dist/cli/src/auth.js +506 -0
- package/dist/cli/src/billing-commerce-contracts.js +1 -0
- package/dist/cli/src/billing-usage.js +227 -0
- package/dist/cli/src/billing-wallet-contracts.js +1 -0
- package/dist/cli/src/billing.js +170 -0
- package/dist/cli/src/bootstrap-process-lock.js +136 -0
- package/dist/cli/src/browser-links.js +14 -0
- package/dist/cli/src/campaign-controls.js +122 -0
- package/dist/cli/src/campaign-diagnoses.js +118 -0
- package/dist/cli/src/campaign-reports.js +1215 -0
- package/dist/cli/src/campaign-world.js +156 -0
- package/dist/cli/src/candidate-copy.js +376 -0
- package/dist/cli/src/catalog-contracts.js +1 -0
- package/dist/cli/src/ci-access-contracts.js +1 -0
- package/dist/cli/src/ci-configuration-contracts.js +1 -0
- package/dist/cli/src/ci-coverage.js +64 -0
- package/dist/cli/src/ci-delivery-contracts.js +1 -0
- package/dist/cli/src/ci-result-contracts.js +1 -0
- package/dist/cli/src/ci-run-contracts.js +1 -0
- package/dist/cli/src/ci-setup-proposals.js +52 -0
- package/dist/cli/src/company-policies.js +229 -0
- package/dist/cli/src/completion.js +79 -0
- package/dist/cli/src/configuration-intents.js +277 -0
- package/dist/cli/src/configuration.js +369 -0
- package/dist/cli/src/continuous-simulation.js +277 -0
- package/dist/cli/src/coordinated-checkpoints.js +133 -0
- package/dist/cli/src/credential-mode.js +139 -0
- package/dist/cli/src/credentials.js +331 -0
- package/dist/cli/src/credit-format.js +8 -0
- package/dist/cli/src/dataset-contracts.js +1 -0
- package/dist/cli/src/dataset-export.js +154 -0
- package/dist/cli/src/dataset-reads.js +37 -0
- package/dist/cli/src/dependency-preparation.js +239 -0
- package/dist/cli/src/director-tool-setup.js +104 -0
- package/dist/cli/src/driver-review.js +225 -0
- package/dist/cli/src/evaluation-authoring.js +315 -0
- package/dist/cli/src/evidence-bundles.js +367 -0
- package/dist/cli/src/execution-proposals.js +36 -0
- package/dist/cli/src/http.js +302 -0
- package/dist/cli/src/local-container-boundary.js +38 -0
- package/dist/cli/src/local-container-policy.js +201 -0
- package/dist/cli/src/local-execution-setup.js +81 -0
- package/dist/cli/src/local-source-basis.js +73 -0
- package/dist/cli/src/local-source.js +66 -0
- package/dist/cli/src/local-value-store.js +91 -0
- package/dist/cli/src/local-values.js +163 -0
- package/dist/cli/src/login-organizations.js +70 -0
- package/dist/cli/src/main.js +1627 -0
- package/dist/cli/src/mcp-connection.js +109 -0
- package/dist/cli/src/native/bindings.js +137 -0
- package/dist/cli/src/native/canonical.js +370 -0
- package/dist/cli/src/native/cloud-resources.js +942 -0
- package/dist/cli/src/native/companion-channel.js +421 -0
- package/dist/cli/src/native/companion-ipc.js +473 -0
- package/dist/cli/src/native/contracts.js +218 -0
- package/dist/cli/src/native/control-plane.js +1354 -0
- package/dist/cli/src/native/credit-approval.js +15 -0
- package/dist/cli/src/native/dependency-proxy.js +1006 -0
- package/dist/cli/src/native/drill-cleanup.js +115 -0
- package/dist/cli/src/native/drill-runner.js +1562 -0
- package/dist/cli/src/native/drivers/action-trace.js +210 -0
- package/dist/cli/src/native/drivers/function-wrapper.js +152 -0
- package/dist/cli/src/native/drivers/http.js +442 -0
- package/dist/cli/src/native/drivers/index.js +47 -0
- package/dist/cli/src/native/drivers/jsonl-process.js +538 -0
- package/dist/cli/src/native/drivers/playwright.js +600 -0
- package/dist/cli/src/native/drivers/signed-event.js +244 -0
- package/dist/cli/src/native/drivers/types.js +578 -0
- package/dist/cli/src/native/evidence-filter.js +255 -0
- package/dist/cli/src/native/isolated-access.js +241 -0
- package/dist/cli/src/native/library-sharing.js +220 -0
- package/dist/cli/src/native/local-run.js +667 -0
- package/dist/cli/src/native/local-secrets.js +19 -0
- package/dist/cli/src/native/mcp-config.js +123 -0
- package/dist/cli/src/native/metadata-requests.js +120 -0
- package/dist/cli/src/native/observations.js +1159 -0
- package/dist/cli/src/native/owned-groups.js +46 -0
- package/dist/cli/src/native/plan.js +594 -0
- package/dist/cli/src/native/png.js +159 -0
- package/dist/cli/src/native/ports.js +43 -0
- package/dist/cli/src/native/readiness.js +139 -0
- package/dist/cli/src/native/reassessments.js +206 -0
- package/dist/cli/src/native/redaction.js +235 -0
- package/dist/cli/src/native/registration.js +155 -0
- package/dist/cli/src/native/roots.js +110 -0
- package/dist/cli/src/native/run-cleanup.js +199 -0
- package/dist/cli/src/native/scratch.js +117 -0
- package/dist/cli/src/native/screenshots.js +60 -0
- package/dist/cli/src/native/session.js +556 -0
- package/dist/cli/src/native/standalone-proxy.js +580 -0
- package/dist/cli/src/native/standalone-renewals.js +183 -0
- package/dist/cli/src/native/standalone-state.js +193 -0
- package/dist/cli/src/native/standalone-tools.js +187 -0
- package/dist/cli/src/native/supervisor.js +831 -0
- package/dist/cli/src/native/target-auth.js +161 -0
- package/dist/cli/src/native/target-state.js +330 -0
- package/dist/cli/src/native/tool-reuse.js +364 -0
- package/dist/cli/src/native/tool-setups.js +336 -0
- package/dist/cli/src/native/world-reuse.js +288 -0
- package/dist/cli/src/onboarding.js +23 -0
- package/dist/cli/src/priority-requests.js +70 -0
- package/dist/cli/src/priority.js +223 -0
- package/dist/cli/src/prompts.js +184 -0
- package/dist/cli/src/real-operations.js +221 -0
- package/dist/cli/src/recipe-intents.js +58 -0
- package/dist/cli/src/recorded-context-intents.js +59 -0
- package/dist/cli/src/recorded-context.js +248 -0
- package/dist/cli/src/recordings.js +69 -0
- package/dist/cli/src/registration-guide.js +39 -0
- package/dist/cli/src/report-exports.js +213 -0
- package/dist/cli/src/request-input.js +47 -0
- package/dist/cli/src/resource-lifecycle.js +141 -0
- package/dist/cli/src/revision-requests.js +55 -0
- package/dist/cli/src/runner-abandon.js +53 -0
- package/dist/cli/src/runner-campaign-controls.js +262 -0
- package/dist/cli/src/runner-claim-reads.js +36 -0
- package/dist/cli/src/runner-completion.js +65 -0
- package/dist/cli/src/runner-configuration.js +546 -0
- package/dist/cli/src/runner-container.js +794 -0
- package/dist/cli/src/runner-evidence-erasure.js +351 -0
- package/dist/cli/src/runner-evidence-privacy.js +37 -0
- package/dist/cli/src/runner-evidence-retention.js +161 -0
- package/dist/cli/src/runner-evidence.js +326 -0
- package/dist/cli/src/runner-execute.js +92 -0
- package/dist/cli/src/runner-execution-deadline.js +25 -0
- package/dist/cli/src/runner-grants.js +307 -0
- package/dist/cli/src/runner-identity.js +615 -0
- package/dist/cli/src/runner-inputs.js +308 -0
- package/dist/cli/src/runner-jobs.js +49 -0
- package/dist/cli/src/runner-model-transport.js +392 -0
- package/dist/cli/src/runner-ownership.js +332 -0
- package/dist/cli/src/runner-process-lock.js +175 -0
- package/dist/cli/src/runner-runtime.js +396 -0
- package/dist/cli/src/runner-session-channel.js +377 -0
- package/dist/cli/src/runner-trial-completion.js +34 -0
- package/dist/cli/src/runner-trial-journal.js +358 -0
- package/dist/cli/src/runner-trial-live.js +393 -0
- package/dist/cli/src/runner-trial-protocol.js +289 -0
- package/dist/cli/src/runner-trial-sync.js +110 -0
- package/dist/cli/src/runner-trial-transfer.js +107 -0
- package/dist/cli/src/runner-voice.js +40 -0
- package/dist/cli/src/runner-world-claim.js +21 -0
- package/dist/cli/src/runner-world-collector.js +159 -0
- package/dist/cli/src/runner-world-command-evidence.js +487 -0
- package/dist/cli/src/runner-world-commands.js +268 -0
- package/dist/cli/src/runner-world-completion.js +76 -0
- package/dist/cli/src/runner-world-erasure.js +95 -0
- package/dist/cli/src/runner-world-evidence-sync.js +91 -0
- package/dist/cli/src/runner-world-journal.js +510 -0
- package/dist/cli/src/runner-world-native-filters.js +134 -0
- package/dist/cli/src/runner-world-observations.js +50 -0
- package/dist/cli/src/runner-world-session-protocol.js +134 -0
- package/dist/cli/src/saved-records.js +40 -0
- package/dist/cli/src/scenario-authoring.js +212 -0
- package/dist/cli/src/scenario-discovery.js +216 -0
- package/dist/cli/src/scenario-paths.js +68 -0
- package/dist/cli/src/scenario-requests.js +4 -0
- package/dist/cli/src/service-receipts.js +105 -0
- package/dist/cli/src/simple/authoring.js +270 -0
- package/dist/cli/src/simple/credit-approval.js +16 -0
- package/dist/cli/src/simple/help.js +98 -0
- package/dist/cli/src/simple/init.js +96 -0
- package/dist/cli/src/simple/library-sharing.js +414 -0
- package/dist/cli/src/simple/metadata-requests.js +46 -0
- package/dist/cli/src/simple/native-report.js +841 -0
- package/dist/cli/src/simple/native-tools.js +780 -0
- package/dist/cli/src/simple/plan-apply.js +506 -0
- package/dist/cli/src/simple/reassessment.js +323 -0
- package/dist/cli/src/simple/register.js +123 -0
- package/dist/cli/src/simple/run.js +122 -0
- package/dist/cli/src/simple/status.js +49 -0
- package/dist/cli/src/simple/tool-renewal.js +308 -0
- package/dist/cli/src/simple/tool-reuse.js +526 -0
- package/dist/cli/src/simple/tool-start-catalog.js +100 -0
- package/dist/cli/src/simple/tool-state.js +426 -0
- package/dist/cli/src/simple/tools-author.js +222 -0
- package/dist/cli/src/simple/watch.js +28 -0
- package/dist/cli/src/simple/world-reuse.js +586 -0
- package/dist/cli/src/simulation-intents.js +406 -0
- package/dist/cli/src/simulation-run.js +1058 -0
- package/dist/cli/src/source-batch.js +322 -0
- package/dist/cli/src/source-connection.js +451 -0
- package/dist/cli/src/source-runtime.js +346 -0
- package/dist/cli/src/source-selection-intents.js +75 -0
- package/dist/cli/src/source-sets.js +214 -0
- package/dist/cli/src/target-models.js +109 -0
- package/dist/cli/src/tool-catalog.js +224 -0
- package/dist/cli/src/tool-command-contracts.js +1 -0
- package/dist/cli/src/tool-command-export.js +245 -0
- package/dist/cli/src/tool-connection-contracts.js +1 -0
- package/dist/cli/src/tool-connection-reads.js +23 -0
- package/dist/cli/src/tool-contributions.js +58 -0
- package/dist/cli/src/tool-data.js +116 -0
- package/dist/cli/src/tool-datasets.js +176 -0
- package/dist/cli/src/tool-group-contracts.js +1 -0
- package/dist/cli/src/tool-group-reads.js +26 -0
- package/dist/cli/src/tool-group-state.js +147 -0
- package/dist/cli/src/tool-groups.js +311 -0
- package/dist/cli/src/tool-instances.js +186 -0
- package/dist/cli/src/tool-lifecycle-contracts.js +1 -0
- package/dist/cli/src/tool-lifecycle-reads.js +30 -0
- package/dist/cli/src/tool-lifecycle.js +323 -0
- package/dist/cli/src/tool-package-contracts.js +1 -0
- package/dist/cli/src/tool-package-reads.js +41 -0
- package/dist/cli/src/tool-records.js +53 -0
- package/dist/cli/src/tool-runtime.js +96 -0
- package/dist/cli/src/tool-state-contracts.js +1 -0
- package/dist/cli/src/tool-state-reads.js +31 -0
- package/dist/cli/src/tools.js +306 -0
- package/dist/cli/src/trial-actions.js +93 -0
- package/dist/cli/src/trial-checkpoints.js +133 -0
- package/dist/cli/src/trial-checks.js +70 -0
- package/dist/cli/src/trial-state.js +197 -0
- package/dist/cli/src/vault.js +266 -0
- package/dist/cli/src/workspace-registration.js +383 -0
- package/dist/cli/src/workspace-team-contracts.js +1 -0
- package/dist/cli/src/world-access.js +231 -0
- package/dist/cli/src/world-agent.js +4 -0
- package/dist/cli/src/world-checkpoint-scopes.js +15 -0
- package/dist/cli/src/world-contracts.js +6 -0
- package/dist/cli/src/world-creation.js +201 -0
- package/dist/cli/src/world-datasets.js +207 -0
- package/dist/cli/src/world-inspection.js +257 -0
- package/dist/cli/src/world-recipes.js +103 -0
- package/dist/cli/src/world-requests.js +241 -0
- package/dist/cli/src/world-session-checkpoints.js +196 -0
- package/dist/cli/src/world-session-lifecycle.js +207 -0
- package/dist/cli/src/world-session-requests.js +215 -0
- package/dist/cli/src/world-session-state.js +145 -0
- package/dist/cli/src/world-session-voice.js +66 -0
- package/dist/cli/src/world-sessions.js +288 -0
- package/dist/cli/src/world-stream.js +99 -0
- package/dist/cli/src/world-time-report.js +58 -0
- package/dist/contracts/agent-change-contracts.js +75 -0
- package/dist/contracts/agent-execution-contracts.js +38 -0
- package/dist/contracts/application-state-contracts.js +26 -0
- package/dist/contracts/assessment-aggregate-contracts.js +15 -0
- package/dist/contracts/assessment-contracts.js +74 -0
- package/dist/contracts/billing-commerce-contracts.js +107 -0
- package/dist/contracts/billing-wallet-contracts.js +43 -0
- package/dist/contracts/campaign-lifecycle.js +1 -0
- package/dist/contracts/campaign-world-contracts.js +80 -0
- package/dist/contracts/catalog-contracts.js +73 -0
- package/dist/contracts/ci-access-contracts.js +68 -0
- package/dist/contracts/ci-configuration-contracts.js +88 -0
- package/dist/contracts/ci-coverage-contracts.js +65 -0
- package/dist/contracts/ci-coverage-labels.js +25 -0
- package/dist/contracts/ci-delivery-contracts.js +61 -0
- package/dist/contracts/ci-result-contracts.js +103 -0
- package/dist/contracts/ci-run-contracts.js +51 -0
- package/dist/contracts/ci-setup-contracts.js +88 -0
- package/dist/contracts/clip-contracts.js +32 -0
- package/dist/contracts/comparison-contracts.js +53 -0
- package/dist/contracts/composite-contracts.js +34 -0
- package/dist/contracts/context-contracts.js +76 -0
- package/dist/contracts/continuation-report-contracts.js +1 -0
- package/dist/contracts/continuity-requirements.js +8 -0
- package/dist/contracts/coordinated-checkpoint-contracts.js +34 -0
- package/dist/contracts/dataset-contracts.js +88 -0
- package/dist/contracts/dependency-decision-contracts.js +36 -0
- package/dist/contracts/dependency-protocol-contracts.js +43 -0
- package/dist/contracts/director-execution.js +51 -0
- package/dist/contracts/driver-review-contracts.js +99 -0
- package/dist/contracts/evidence-contracts.js +65 -0
- package/dist/contracts/evidence-coverage-contracts.js +10 -0
- package/dist/contracts/export-contracts.js +44 -0
- package/dist/contracts/github-directory-contracts.js +96 -0
- package/dist/contracts/insight-contracts.js +11 -0
- package/dist/contracts/inspection-contracts.js +1 -0
- package/dist/contracts/local-claim-contracts.js +39 -0
- package/dist/contracts/local-configuration-contracts.js +7 -0
- package/dist/contracts/local-evidence-contracts.js +32 -0
- package/dist/contracts/local-grant-contracts.js +83 -0
- package/dist/contracts/local-input-contracts.js +19 -0
- package/dist/contracts/local-runner-contracts.js +42 -0
- package/dist/contracts/local-trial-live-contracts.js +16 -0
- package/dist/contracts/local-value-source-contracts.js +92 -0
- package/dist/contracts/local-world-native-contracts.js +8 -0
- package/dist/contracts/mcp-contracts.js +42 -0
- package/dist/contracts/media-contracts.js +28 -0
- package/dist/contracts/native-check-contracts.js +31 -0
- package/dist/contracts/native-identity-contracts.js +52 -0
- package/dist/contracts/onboarding-contracts.js +32 -0
- package/dist/contracts/performance-contracts.js +85 -0
- package/dist/contracts/planning-contracts.js +81 -0
- package/dist/contracts/real-operation-contracts.js +30 -0
- package/dist/contracts/recommendation-contracts.js +54 -0
- package/dist/contracts/report-contracts.js +1 -0
- package/dist/contracts/resource-lifecycle-contracts.js +57 -0
- package/dist/contracts/revision-contracts.js +12 -0
- package/dist/contracts/revision-request-contracts.js +39 -0
- package/dist/contracts/saved-record-contracts.js +76 -0
- package/dist/contracts/scenario-draft-comparison.js +91 -0
- package/dist/contracts/scenario-world-contracts.js +14 -0
- package/dist/contracts/service-contracts.js +1 -0
- package/dist/contracts/session-command-contracts.js +56 -0
- package/dist/contracts/session-contracts.js +65 -0
- package/dist/contracts/session-state-contracts.js +67 -0
- package/dist/contracts/session-voice-contracts.js +45 -0
- package/dist/contracts/source-set-contracts.js +26 -0
- package/dist/contracts/source-upload-contracts.js +22 -0
- package/dist/contracts/tool-command-contracts.js +57 -0
- package/dist/contracts/tool-connection-contracts.js +87 -0
- package/dist/contracts/tool-contribution-contracts.js +35 -0
- package/dist/contracts/tool-group-contracts.js +53 -0
- package/dist/contracts/tool-group-state-contracts.js +63 -0
- package/dist/contracts/tool-lifecycle-contracts.js +94 -0
- package/dist/contracts/tool-package-contracts.js +91 -0
- package/dist/contracts/tool-protocol-contracts.js +71 -0
- package/dist/contracts/tool-runtime-billing-contracts.js +28 -0
- package/dist/contracts/tool-setup-proposal.js +59 -0
- package/dist/contracts/tool-state-contracts.js +79 -0
- package/dist/contracts/trial-state-contracts.js +30 -0
- package/dist/contracts/typescript/agent-change.js +1 -0
- package/dist/contracts/typescript/assessment-aggregate.js +1 -0
- package/dist/contracts/typescript/assessment.js +1 -0
- package/dist/contracts/typescript/billing-commerce.js +1 -0
- package/dist/contracts/typescript/campaigns.js +1 -0
- package/dist/contracts/typescript/catalog.js +1 -0
- package/dist/contracts/typescript/ci-access.js +1 -0
- package/dist/contracts/typescript/ci-configuration.js +1 -0
- package/dist/contracts/typescript/ci-delivery.js +1 -0
- package/dist/contracts/typescript/ci-result.js +1 -0
- package/dist/contracts/typescript/ci-runs.js +1 -0
- package/dist/contracts/typescript/clips.js +1 -0
- package/dist/contracts/typescript/comparison.js +1 -0
- package/dist/contracts/typescript/composite.js +1 -0
- package/dist/contracts/typescript/connections.js +1 -0
- package/dist/contracts/typescript/context.js +1 -0
- package/dist/contracts/typescript/datasets.js +1 -0
- package/dist/contracts/typescript/evidence-coverage.js +1 -0
- package/dist/contracts/typescript/export.js +1 -0
- package/dist/contracts/typescript/github-directory.js +1 -0
- package/dist/contracts/typescript/insight.js +1 -0
- package/dist/contracts/typescript/inspection.js +1 -0
- package/dist/contracts/typescript/intake.js +1 -0
- package/dist/contracts/typescript/local-claim.js +1 -0
- package/dist/contracts/typescript/local-configuration.js +1 -0
- package/dist/contracts/typescript/local-evidence.js +1 -0
- package/dist/contracts/typescript/local-grant.js +1 -0
- package/dist/contracts/typescript/local-input.js +1 -0
- package/dist/contracts/typescript/local-runner.js +1 -0
- package/dist/contracts/typescript/local-trial-live.js +1 -0
- package/dist/contracts/typescript/local-value-source.js +1 -0
- package/dist/contracts/typescript/local-world-native.js +1 -0
- package/dist/contracts/typescript/mcp.js +1 -0
- package/dist/contracts/typescript/media.js +1 -0
- package/dist/contracts/typescript/native-check.js +1 -0
- package/dist/contracts/typescript/native-identity.js +1 -0
- package/dist/contracts/typescript/onboarding.js +1 -0
- package/dist/contracts/typescript/performance.js +1 -0
- package/dist/contracts/typescript/planning.js +1 -0
- package/dist/contracts/typescript/preparation.js +1 -0
- package/dist/contracts/typescript/real-operation.js +1 -0
- package/dist/contracts/typescript/recommendation.js +1 -0
- package/dist/contracts/typescript/reports.js +1 -0
- package/dist/contracts/typescript/resource-lifecycle.js +1 -0
- package/dist/contracts/typescript/saved-record.js +1 -0
- package/dist/contracts/typescript/scenarios.js +1 -0
- package/dist/contracts/typescript/services.js +1 -0
- package/dist/contracts/typescript/session-command.js +1 -0
- package/dist/contracts/typescript/session-state.js +1 -0
- package/dist/contracts/typescript/session-voice.js +1 -0
- package/dist/contracts/typescript/session.js +1 -0
- package/dist/contracts/typescript/tool-command.js +1 -0
- package/dist/contracts/typescript/tool-connection.js +1 -0
- package/dist/contracts/typescript/tool-contribution.js +1 -0
- package/dist/contracts/typescript/tool-groups.js +1 -0
- package/dist/contracts/typescript/tool-lifecycle.js +1 -0
- package/dist/contracts/typescript/tool-package.js +1 -0
- package/dist/contracts/typescript/tool-state.js +1 -0
- package/dist/contracts/typescript/trial-state.js +1 -0
- package/dist/contracts/typescript/vault.js +1 -0
- package/dist/contracts/typescript/workspace-team.js +1 -0
- package/dist/contracts/typescript/world-stream.js +1 -0
- package/dist/contracts/workspace-team-contracts.js +55 -0
- package/dist/contracts/world-command.js +65 -0
- package/dist/contracts/world-lifetime-contracts.js +74 -0
- package/dist/contracts/world-recipe-contracts.js +27 -0
- package/dist/contracts/world-stream-contracts.js +27 -0
- package/dist/package.json +1 -0
- package/package.json +16 -16
- package/LICENSE.md +0 -23
- package/PACKAGING.json +0 -50
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { api } from './auth.js';
|
|
2
|
+
import { CliError } from './http.js';
|
|
3
|
+
import { selectOne } from './prompts.js';
|
|
4
|
+
import { identifier } from './world-contracts.js';
|
|
5
|
+
import { selectedWorld } from './world-access.js';
|
|
6
|
+
import { recommendationReceiptMatches, recommendationRequestsMatch } from '../../contracts/recommendation-contracts.js';
|
|
7
|
+
const keyPattern = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,127}$/;
|
|
8
|
+
const invalid = () => new CliError('recommendation_request_invalid', 'The original priority request receipt could not be confirmed. No evaluation was repeated.');
|
|
9
|
+
function checked(value, scope, turn, key) {
|
|
10
|
+
if (!recommendationReceiptMatches(value, { actor: scope.owner, workspace: scope.workspace, world: scope.world, turn }, key))
|
|
11
|
+
throw invalid();
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
export function priorityRequestText(value) {
|
|
15
|
+
return `Request: ${value.request_key}\n${value.state === 'accepted'
|
|
16
|
+
? value.resource_available ? `Priority assessment accepted\n${value.batch_id}\nAcceptance is not a completed score. No evaluation or agent execution was repeated.`
|
|
17
|
+
: 'The request was accepted, but its priority assessment is no longer available.'
|
|
18
|
+
: value.state === 'withdrawn' ? 'Canceled before acceptance. This request cannot start a priority assessment.'
|
|
19
|
+
: 'No acceptance is recorded yet. The original request may still arrive. Check it again or cancel only this unaccepted request.'}`;
|
|
20
|
+
}
|
|
21
|
+
export async function inspectPriorityRequest(scope, turn, key, signal) {
|
|
22
|
+
const params = new URLSearchParams({ turn_id: identifier(turn), request_key: key });
|
|
23
|
+
if (!keyPattern.test(key))
|
|
24
|
+
throw invalid();
|
|
25
|
+
return checked(await api(`${scope.prefix}/recommendation-request?${params}`, {
|
|
26
|
+
actor: scope.owner, workspace: scope.workspace, signal
|
|
27
|
+
}), scope, turn, key);
|
|
28
|
+
}
|
|
29
|
+
export async function priorityRequests(args, options, output) {
|
|
30
|
+
const action = args[0], history = action === 'requests';
|
|
31
|
+
if (!['requests', 'request', 'withdraw-request'].includes(action) || args.length !== (history ? 2 : 3)
|
|
32
|
+
|| !history && (options.page !== undefined || options.search !== undefined || options.status !== undefined)
|
|
33
|
+
|| action !== 'withdraw-request' && options.approve)
|
|
34
|
+
throw new CliError('arguments_invalid', 'Use priority requests <turn>, request <turn> <key>, or withdraw-request <turn> <key>. Choose the saved world with --world.', 2);
|
|
35
|
+
const turn = identifier(args[1]), key = args[2];
|
|
36
|
+
if (!history && !keyPattern.test(key))
|
|
37
|
+
throw invalid();
|
|
38
|
+
const page = options.page ?? '1', query = options.search ?? '', status = options.status ?? '';
|
|
39
|
+
if (!/^[1-9][0-9]{0,8}$/.test(page) || query.length > 200 || /[\u0000-\u001f\u007f]/u.test(query) || !['', 'accepted', 'withdrawn'].includes(status))
|
|
40
|
+
throw new CliError('arguments_invalid', 'Use a positive page, request-key search of up to 200 characters and accepted or withdrawn status.', 2);
|
|
41
|
+
const interactive = !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY;
|
|
42
|
+
const scope = await selectedWorld({ ...options, interactive });
|
|
43
|
+
const auth = { actor: scope.owner, workspace: scope.workspace, signal: options.signal };
|
|
44
|
+
if (history) {
|
|
45
|
+
const params = new URLSearchParams({ turn_id: turn, page, q: query, status });
|
|
46
|
+
const result = await api(`${scope.prefix}/recommendation-requests?${params}`, auth);
|
|
47
|
+
if (!recommendationRequestsMatch(result, { actor: scope.owner, workspace: scope.workspace, world: scope.world, turn, query, status }))
|
|
48
|
+
throw invalid();
|
|
49
|
+
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
50
|
+
output(result, (result.results.map(value => priorityRequestText(checked(value, scope, turn))).join('\n\n') || 'No matching priority requests from your account.')
|
|
51
|
+
+ `\nPage ${result.page}, ${result.count} requests.${result.next ? `\nContinue: firedrill priority requests ${turn} --world ${scope.world} --workspace ${scope.workspace} --page ${result.page + 1}${query ? ' --search ' + quote(query) : ''}${status ? ' --status ' + status : ''}${options.json ? ' --json' : ''}` : ''}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (action === 'request') {
|
|
55
|
+
const value = await inspectPriorityRequest(scope, turn, key, options.signal);
|
|
56
|
+
output(value, priorityRequestText(value));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!options.approve) {
|
|
60
|
+
if (!interactive)
|
|
61
|
+
throw new CliError('approval_required', 'Use --approve-assessment to cancel this exact unaccepted request. Accepted assessments are only inspected.', 2);
|
|
62
|
+
if (!await selectOne('Cancel this unaccepted priority request? An accepted assessment will only be inspected.', [
|
|
63
|
+
{ value: false, label: 'Keep request' }, { value: true, label: 'Cancel unaccepted request' }
|
|
64
|
+
], options.signal))
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const value = checked(await api(`${scope.prefix}/recommendation-request`, { ...auth, method: 'POST',
|
|
68
|
+
body: { turn_id: turn, request_key: key } }), scope, turn, key);
|
|
69
|
+
output(value, priorityRequestText(value));
|
|
70
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { workspaceLink } from './browser-links.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { api } from './auth.js';
|
|
4
|
+
import { remember, recovery } from './evaluation-authoring.js';
|
|
5
|
+
import { CliError } from './http.js';
|
|
6
|
+
import { priorityRequests } from './priority-requests.js';
|
|
7
|
+
import { selectMany, selectOne } from './prompts.js';
|
|
8
|
+
import { identifier } from './world-contracts.js';
|
|
9
|
+
import { selectedWorkspace, selectedWorld } from './world-access.js';
|
|
10
|
+
import { recommendationBatchMatches, recommendationBatchesMatch, recommendationTurnsMatch, recommendationDetailMatches, recommendationRecoveryMatches } from '../../contracts/recommendation-contracts.js';
|
|
11
|
+
const names = { queued: 'Queued', preparing: 'Preparing context', submitted: 'Assessing priority',
|
|
12
|
+
succeeded: 'Assessed', not_assessable: 'Not assessable', failed: 'Evaluation error', unknown: 'Reply unconfirmed', unavailable: 'Context unavailable' };
|
|
13
|
+
const invalid = () => new CliError('recommendation_response_invalid', 'The saved proposal priority response could not be confirmed. No evaluation or agent execution was repeated.');
|
|
14
|
+
const interactive = (options) => !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY;
|
|
15
|
+
const auth = (scope, options) => ({ actor: scope.owner, workspace: scope.workspace, signal: options.signal });
|
|
16
|
+
function pageOf(value, page, maximum = 100) {
|
|
17
|
+
if (!value || !Array.isArray(value.results) || value.results.length > maximum || value.page !== page
|
|
18
|
+
|| !Number.isSafeInteger(value.count) || value.count < 0 || !Number.isSafeInteger(value.page_size)
|
|
19
|
+
|| value.page_size < 1 || value.page_size > maximum || value.results.length > value.page_size
|
|
20
|
+
|| value.next !== null && typeof value.next !== 'string')
|
|
21
|
+
throw invalid();
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function checkedBatch(value, scope, id, turn) {
|
|
25
|
+
if (!recommendationBatchMatches(value, { workspace: scope.workspace, world: scope.world, turn }, id)
|
|
26
|
+
|| typeof value.created_at !== 'string' || typeof value.model !== 'string')
|
|
27
|
+
throw invalid();
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
async function chooseTurn(scope, options, requested) {
|
|
31
|
+
let number = 1;
|
|
32
|
+
while (true) {
|
|
33
|
+
const data = pageOf(await api(`${scope.prefix}/recommendation-turns?page=${number}${requested ? '&turn_id=' + identifier(requested) : ''}`, auth(scope, options)), number);
|
|
34
|
+
if (!recommendationTurnsMatch(data, { workspace: scope.workspace, world: scope.world }, requested))
|
|
35
|
+
throw invalid();
|
|
36
|
+
if (!data.can_assess)
|
|
37
|
+
throw new CliError('recommendation_unavailable', 'Priority assessment needs write access and a configured evaluator. Saved reports remain readable.');
|
|
38
|
+
for (const one of data.results) {
|
|
39
|
+
identifier(one.id);
|
|
40
|
+
identifier(one.response_id);
|
|
41
|
+
if (!Number.isSafeInteger(one.proposal_count) || one.proposal_count < 1 || typeof one.created_at !== 'string')
|
|
42
|
+
throw invalid();
|
|
43
|
+
}
|
|
44
|
+
if (requested) {
|
|
45
|
+
if (data.results.length !== 1 || data.results[0].id !== requested)
|
|
46
|
+
throw new CliError('recommendation_selection_changed', 'This response has no current pending proposals. Inspect the world before another request.');
|
|
47
|
+
return data.results[0];
|
|
48
|
+
}
|
|
49
|
+
const choices = data.results.map(one => ({ value: one.id, label: `${one.proposal_count} pending proposals · ${one.created_at}` }));
|
|
50
|
+
if (number > 1)
|
|
51
|
+
choices.unshift({ value: 'previous', label: 'Previous page' });
|
|
52
|
+
if (data.next)
|
|
53
|
+
choices.push({ value: 'next', label: 'Next page' });
|
|
54
|
+
choices.push({ value: 'cancel', label: 'Cancel' });
|
|
55
|
+
if (!data.results.length && number === 1)
|
|
56
|
+
throw new CliError('recommendation_selection_empty', 'No current proposals are ready. No priority assessment was requested.');
|
|
57
|
+
const selected = await selectOne('Proposal response', choices, options.signal);
|
|
58
|
+
if (selected === 'cancel')
|
|
59
|
+
throw new CliError('canceled', 'No priority assessment was requested.', 130);
|
|
60
|
+
if (selected === 'previous') {
|
|
61
|
+
number -= 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (selected === 'next') {
|
|
65
|
+
number += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const turn = data.results.find(one => one.id === selected);
|
|
69
|
+
if (!turn)
|
|
70
|
+
throw invalid();
|
|
71
|
+
return turn;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function chooseProposals(scope, options, requestedTurn) {
|
|
75
|
+
const turn = await chooseTurn(scope, options, requestedTurn), selected = new Set();
|
|
76
|
+
let number = 1;
|
|
77
|
+
while (true) {
|
|
78
|
+
const data = pageOf(await api(`${scope.prefix}/messages/${turn.response_id}/proposals?page=${number}&page_size=25`, auth(scope, options)), number, 25);
|
|
79
|
+
if (data.message_id !== turn.response_id)
|
|
80
|
+
throw invalid();
|
|
81
|
+
const eligible = data.results.filter(one => one.state === 'pending' && !one.stale && one.evidence_available);
|
|
82
|
+
for (const one of data.results)
|
|
83
|
+
if (one.turn_id !== turn.id)
|
|
84
|
+
throw invalid();
|
|
85
|
+
for (const one of data.results)
|
|
86
|
+
if (!eligible.some(item => item.id === one.id))
|
|
87
|
+
selected.delete(one.id);
|
|
88
|
+
if (eligible.length) {
|
|
89
|
+
const chosen = await selectMany(`Proposals · ${selected.size} selected`, eligible.map(one => ({ value: identifier(one.id),
|
|
90
|
+
label: one.title, detail: `${one.scenario_count} scenarios · ${one.purpose}` })), options.signal, [...selected]);
|
|
91
|
+
for (const one of eligible)
|
|
92
|
+
selected.delete(one.id);
|
|
93
|
+
for (const one of chosen)
|
|
94
|
+
selected.add(one);
|
|
95
|
+
if (selected.size > 64)
|
|
96
|
+
throw new CliError('recommendation_selection_capacity', 'Choose up to 64 proposals in one priority request. No request was submitted.', 2);
|
|
97
|
+
}
|
|
98
|
+
const choices = [];
|
|
99
|
+
if (selected.size)
|
|
100
|
+
choices.push({ value: 'create', label: `Review ${selected.size} selected proposals` });
|
|
101
|
+
if (data.next)
|
|
102
|
+
choices.push({ value: 'next', label: 'Next proposal page' });
|
|
103
|
+
if (number > 1)
|
|
104
|
+
choices.push({ value: 'previous', label: 'Previous proposal page' });
|
|
105
|
+
choices.push({ value: 'cancel', label: 'Cancel' });
|
|
106
|
+
const action = await selectOne(eligible.length ? 'Continue' : 'No eligible proposals on this page', choices, options.signal);
|
|
107
|
+
if (action === 'cancel')
|
|
108
|
+
throw new CliError('canceled', 'No priority assessment was requested.', 130);
|
|
109
|
+
if (action === 'next') {
|
|
110
|
+
number += 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (action === 'previous') {
|
|
114
|
+
number -= 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
return { turn_id: turn.id, proposal_ids: [...selected].sort(), share_context: true };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function detailText(value, workspace) {
|
|
121
|
+
if (!Array.isArray(value.results) || value.results.length > 64 || !value.rubric || !Array.isArray(value.rubric.levels)
|
|
122
|
+
|| value.rubric.levels.length < 2 || value.rubric.levels.length > 10 || typeof value.rubric.instructions !== 'string'
|
|
123
|
+
|| value.rubric.levels.some(one => typeof one !== 'string') || !value.context)
|
|
124
|
+
throw invalid();
|
|
125
|
+
const rows = value.results.map(one => {
|
|
126
|
+
if (!one || !Object.hasOwn(names, one.state) || typeof one.title !== 'string' || typeof one.requested_model !== 'string'
|
|
127
|
+
|| one.model !== null && typeof one.model !== 'string')
|
|
128
|
+
throw invalid();
|
|
129
|
+
let score = names[one.state];
|
|
130
|
+
if (one.result?.score != null) {
|
|
131
|
+
if (one.state !== 'succeeded' || !one.model || !Number.isFinite(one.result.score) || !Array.isArray(one.result.levels)
|
|
132
|
+
|| one.result.levels.length !== value.rubric.levels.length || one.result.score < 0 || one.result.score > value.rubric.levels.length - 1)
|
|
133
|
+
throw invalid();
|
|
134
|
+
score = `Priority ${one.result.score.toLocaleString(undefined, { maximumFractionDigits: 2 })} / ${value.rubric.levels.length - 1}`;
|
|
135
|
+
}
|
|
136
|
+
if (!Array.isArray(one.evidence_refs) || one.evidence_refs.length > 20)
|
|
137
|
+
throw invalid();
|
|
138
|
+
const citations = one.evidence_refs.map((ref, index) => {
|
|
139
|
+
if (!ref || typeof ref.record_id !== 'string' || typeof ref.kind !== 'string')
|
|
140
|
+
throw invalid();
|
|
141
|
+
const id = identifier(ref.record_id), pattern = /^\/campaigns\/([a-f0-9-]{36})\/trials\/([a-f0-9-]{36})\?view=(conversation|actions)&record=([a-f0-9-]{36})$/;
|
|
142
|
+
const match = typeof ref.path === 'string' ? ref.path.match(pattern) : null;
|
|
143
|
+
if (!match || !['message', 'action'].includes(ref.kind) || match[4] !== id
|
|
144
|
+
|| match[3] !== (ref.kind === 'message' ? 'conversation' : 'actions'))
|
|
145
|
+
throw invalid();
|
|
146
|
+
identifier(match[1]);
|
|
147
|
+
identifier(match[2]);
|
|
148
|
+
return `${ref.kind === 'message' ? 'Conversation evidence' : 'Tool action evidence'} ${index + 1}: ${workspaceLink(workspace, ref.path)}`;
|
|
149
|
+
});
|
|
150
|
+
return `${one.title}\n${score} · ${one.model ?? `Requested ${one.requested_model}`}${one.problem ? '\n' + one.problem : ''}${citations.length ? '\n' + citations.join('\n') : ''}`;
|
|
151
|
+
});
|
|
152
|
+
return [value.id, ...rows, 'Priority scale', ...value.rubric.levels.map((one, index) => `${index}: ${one}`),
|
|
153
|
+
value.rubric.instructions, `World revision ${value.context.world_revision}. Up to ${value.context.sample_per_kind} declarations of each kind and the proposal's cited observations were supplied.`,
|
|
154
|
+
'This is proposal priority, not an agent quality score, confirmed defect or probability of failure. No proposal was applied.'].join('\n\n');
|
|
155
|
+
}
|
|
156
|
+
export async function priorityCommand(args, options, output) {
|
|
157
|
+
const action = args[0] ?? 'create';
|
|
158
|
+
if (['requests', 'request', 'withdraw-request'].includes(action)) {
|
|
159
|
+
if (options.request)
|
|
160
|
+
throw new CliError('arguments_invalid', 'Use the original request key as an argument for priority request inspection.', 2);
|
|
161
|
+
return priorityRequests(args, options, (data, readable) => output('result', data, readable));
|
|
162
|
+
}
|
|
163
|
+
if (!['create', 'list', 'show', 'recover-replies', 'resume', 'forget'].includes(action) || options.search !== undefined || options.status !== undefined)
|
|
164
|
+
throw new CliError('arguments_invalid', 'Use priority, create <turn> <proposal...>, list <turn>, show <batch>, recover-replies <batch>, resume or forget.', 2);
|
|
165
|
+
if (['resume', 'forget'].includes(action))
|
|
166
|
+
return recovery('priority', args, await selectedWorkspace({ ...options, interactive: interactive(options) }), options, output);
|
|
167
|
+
if (action !== 'list' && options.page !== undefined || action !== 'create' && action !== 'recover-replies' && (options.request || options.approve))
|
|
168
|
+
throw new CliError('arguments_invalid', 'Use paging only with priority list, and request/consent options only with the intended change.', 2);
|
|
169
|
+
if (action === 'create' ? args.length > 66 : args.length !== 2)
|
|
170
|
+
throw new CliError('arguments_invalid', 'Use priority for guided selection, or create <turn> <proposal...>. List/show/recover-replies take one saved ID.', 2);
|
|
171
|
+
const scope = await selectedWorld({ ...options, interactive: interactive(options) });
|
|
172
|
+
if (action === 'create') {
|
|
173
|
+
let body;
|
|
174
|
+
if (args.length > 2) {
|
|
175
|
+
const proposals = args.slice(2).map(identifier);
|
|
176
|
+
if (new Set(proposals).size !== proposals.length)
|
|
177
|
+
throw new CliError('arguments_invalid', 'Choose distinct proposal IDs.', 2);
|
|
178
|
+
body = { turn_id: identifier(args[1]), proposal_ids: proposals.sort(), share_context: true };
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
if (!interactive(options))
|
|
182
|
+
throw new CliError('recommendation_selection_required', 'Use priority create <turn-id> <proposal-id...> --approve-assessment in automation, or run priority in a terminal for guided selection.', 2);
|
|
183
|
+
body = await chooseProposals(scope, options, args[1] ? identifier(args[1]) : undefined);
|
|
184
|
+
}
|
|
185
|
+
if (!options.approve && !(interactive(options) && await selectOne('Assess priority using the selected proposals and cited observations? Keep a private recovery record. This does not run the agent or apply a proposal.', [{ value: false, label: 'Cancel' }, { value: true, label: 'Assess proposal priority' }], options.signal)))
|
|
186
|
+
throw new CliError('recommendation_consent_required', 'No priority assessment was submitted. Automation requires --approve-assessment after reviewing the selected context.', 2);
|
|
187
|
+
await remember('priority', `${scope.prefix}/recommendation-batches`, body, scope, options, output);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const id = identifier(args[1]);
|
|
191
|
+
if (action === 'list') {
|
|
192
|
+
const number = options.page ?? '1';
|
|
193
|
+
if (!/^[1-9][0-9]{0,8}$/.test(number))
|
|
194
|
+
throw new CliError('arguments_invalid', 'Choose a positive priority history page.', 2);
|
|
195
|
+
const data = pageOf(await api(`${scope.prefix}/recommendation-batches?turn_id=${id}&page=${number}`, auth(scope, options)), Number(number));
|
|
196
|
+
if (!recommendationBatchesMatch(data, { workspace: scope.workspace, world: scope.world, turn: id }))
|
|
197
|
+
throw invalid();
|
|
198
|
+
output('result', data, (data.results.map(one => {
|
|
199
|
+
checkedBatch(one, scope, undefined, id);
|
|
200
|
+
return `${one.id}\n${one.created_at} · ${one.proposal_count} proposals\n${Object.entries(one.states).map(([state, count]) => `${names[state]}: ${count}`).join(', ')}`;
|
|
201
|
+
}).join('\n\n') || 'No priority assessments for this response.') + `\nPage ${data.page}, ${data.count} assessments.${data.next
|
|
202
|
+
? `\nContinue: firedrill priority list ${id} --workspace ${scope.workspace} --world ${scope.world} --page ${data.page + 1}${options.json ? ' --json' : ''}` : ''}`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (action === 'show') {
|
|
206
|
+
const result = await api(`${scope.prefix}/recommendation-batches/${id}`, auth(scope, options));
|
|
207
|
+
checkedBatch(result, scope, id);
|
|
208
|
+
if (!recommendationDetailMatches(result, { workspace: scope.workspace, world: scope.world }, id))
|
|
209
|
+
throw invalid();
|
|
210
|
+
output('result', result, detailText(result, scope.workspace));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (!options.approve && !(interactive(options) && await selectOne('Read only saved assessments? This will not request another assessment or run the agent again.', [{ value: false, label: 'Cancel' }, { value: true, label: 'Check saved replies' }], options.signal)))
|
|
214
|
+
throw new CliError('recommendation_consent_required', 'Use --approve-assessment to request an original saved-reply check.', 2);
|
|
215
|
+
const key = options.request ? identifier(options.request) : randomUUID();
|
|
216
|
+
output('recovery_request', { batch_id: id, request_key: key }, `Request: ${key}\nIf interrupted: firedrill priority recover-replies ${id} --world ${scope.world} --workspace ${scope.workspace} --request-key ${key} --approve-assessment`);
|
|
217
|
+
const result = await api(`${scope.prefix}/recommendation-batches/${id}/recover-replies`, {
|
|
218
|
+
...auth(scope, options), method: 'POST', body: {}, idempotencyKey: key
|
|
219
|
+
});
|
|
220
|
+
if (!recommendationRecoveryMatches(result, { workspace: scope.workspace, world: scope.world, actor: scope.owner }, id, key))
|
|
221
|
+
throw invalid();
|
|
222
|
+
output('result', result, result.requested ? 'Original saved-reply check requested. No new evaluation was submitted.' : 'No unconfirmed original replies to check.');
|
|
223
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
2
|
+
import { createInterface } from 'node:readline/promises';
|
|
3
|
+
import { CliError } from './http.js';
|
|
4
|
+
let accessible = false, interactive = true;
|
|
5
|
+
export function configurePrompts(options) {
|
|
6
|
+
accessible = options.accessible === true;
|
|
7
|
+
interactive = options.interactive !== false;
|
|
8
|
+
}
|
|
9
|
+
export const plainPromptMode = () => accessible || process.env.FIREDRILL_ACCESSIBLE === '1' || process.env.TERM === 'dumb' || process.env.NO_COLOR !== undefined;
|
|
10
|
+
async function lineChoices(title, choices, multiple, signal, initial = []) {
|
|
11
|
+
// Same current server page as the interactive widget, never tenant-wide data.
|
|
12
|
+
process.stderr.write(terminalText(title) + '\n');
|
|
13
|
+
choices.forEach((choice, index) => process.stderr.write(`${index + 1}. ${terminalText(choice.label)}${choice.detail ? ': ' + terminalText(choice.detail) : ''}${initial.includes(choice.value) ? ' (selected)' : ''}\n`));
|
|
14
|
+
const prompt = createInterface({ input: process.stdin, output: process.stderr, terminal: false });
|
|
15
|
+
const closed = new AbortController();
|
|
16
|
+
prompt.once('close', () => closed.abort());
|
|
17
|
+
const active = signal ? AbortSignal.any([signal, closed.signal]) : closed.signal;
|
|
18
|
+
try {
|
|
19
|
+
while (true) {
|
|
20
|
+
active.throwIfAborted();
|
|
21
|
+
const answer = (await prompt.question(multiple ? 'Numbers separated by commas; Enter keeps selection; none clears; q cancels: '
|
|
22
|
+
: 'Choose a number, or q to cancel: ', { signal: active })).trim();
|
|
23
|
+
if (answer.toLowerCase() === 'q')
|
|
24
|
+
throw new CliError('canceled', 'Selection canceled.', 130);
|
|
25
|
+
if (multiple && !answer)
|
|
26
|
+
return choices.filter(choice => initial.includes(choice.value)).map(choice => choice.value);
|
|
27
|
+
if (multiple && answer.toLowerCase() === 'none')
|
|
28
|
+
return [];
|
|
29
|
+
const parts = answer.split(',').map(value => value.trim());
|
|
30
|
+
if ((!multiple && parts.length !== 1) || parts.some(value => !/^[1-9][0-9]*$/.test(value)
|
|
31
|
+
|| !Number.isSafeInteger(Number(value)) || Number(value) > choices.length)
|
|
32
|
+
|| new Set(parts.map(Number)).size !== parts.length) {
|
|
33
|
+
process.stderr.write('Choose only the listed numbers.\n');
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
return parts.map(Number).sort((a, b) => a - b).map(number => choices[number - 1].value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (active.aborted)
|
|
41
|
+
throw new CliError('canceled', 'Selection canceled.', 130);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
prompt.close();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function terminalText(value) {
|
|
49
|
+
// Strip terminal control sequences, including OSC hyperlinks and bidi marks.
|
|
50
|
+
// Preserve the underlying string in --json output instead of rewriting data.
|
|
51
|
+
return String(value).replace(/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g, ' ');
|
|
52
|
+
}
|
|
53
|
+
async function choose(title, choices, multiple, signal, initial = []) {
|
|
54
|
+
if (!interactive || !process.stdin.isTTY || !process.stderr.isTTY) {
|
|
55
|
+
throw new CliError('selection_required', 'Choose an item using its command option. Interactive selection requires a terminal.', 2);
|
|
56
|
+
}
|
|
57
|
+
if (!choices.length)
|
|
58
|
+
throw new CliError('selection_empty', 'No available items match this selection.');
|
|
59
|
+
if (plainPromptMode())
|
|
60
|
+
return lineChoices(title, choices, multiple, signal, initial);
|
|
61
|
+
emitKeypressEvents(process.stdin);
|
|
62
|
+
let index = 0, rendered = 0;
|
|
63
|
+
const selected = new Set(choices.flatMap((choice, index) => initial.includes(choice.value) ? [index] : []));
|
|
64
|
+
function draw() {
|
|
65
|
+
if (rendered)
|
|
66
|
+
process.stderr.write(`\x1b[${rendered}A\x1b[J`);
|
|
67
|
+
const limit = Math.max(2, Math.min(8, (process.stderr.rows ?? 24) - 6));
|
|
68
|
+
const first = Math.max(0, Math.min(index - Math.floor(limit / 2), choices.length - limit));
|
|
69
|
+
const lines = [terminalText(title)];
|
|
70
|
+
choices.slice(first, first + limit).forEach((choice, offset) => {
|
|
71
|
+
const position = offset + first;
|
|
72
|
+
const marker = multiple ? selected.has(position) ? '[x]' : '[ ]' : index === position ? '(●)' : '( )';
|
|
73
|
+
lines.push(`${index === position ? '›' : ' '} ${marker} ${terminalText(choice.label).slice(0, Math.max(10, (process.stderr.columns ?? 80) - 12))}`);
|
|
74
|
+
if (index === position && choice.detail)
|
|
75
|
+
lines.push(` ${terminalText(choice.detail).slice(0, Math.max(10, (process.stderr.columns ?? 80) - 12))}`);
|
|
76
|
+
});
|
|
77
|
+
lines.push(multiple ? '↑↓ move · Space select · Enter continue · Esc cancel' : '↑↓ move · Enter select · Esc cancel');
|
|
78
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
79
|
+
rendered = lines.length;
|
|
80
|
+
}
|
|
81
|
+
const wasRaw = process.stdin.isRaw;
|
|
82
|
+
process.stdin.setRawMode(true);
|
|
83
|
+
process.stdin.resume();
|
|
84
|
+
process.stderr.write('\x1b[?25l');
|
|
85
|
+
try {
|
|
86
|
+
return await new Promise((resolve, reject) => {
|
|
87
|
+
const finish = (error) => {
|
|
88
|
+
process.stdin.off('keypress', onKey);
|
|
89
|
+
signal?.removeEventListener('abort', onAbort);
|
|
90
|
+
if (error)
|
|
91
|
+
reject(error);
|
|
92
|
+
else
|
|
93
|
+
resolve(multiple ? [...selected].sort((a, b) => a - b).map(position => choices[position].value) : [choices[index].value]);
|
|
94
|
+
};
|
|
95
|
+
const onAbort = () => finish(new CliError('canceled', 'Selection canceled.', 130));
|
|
96
|
+
const onKey = (_character, key) => {
|
|
97
|
+
if (key.name === 'escape' || key.ctrl && key.name === 'c') {
|
|
98
|
+
finish(new CliError('canceled', 'Selection canceled.', 130));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (key.name === 'return') {
|
|
102
|
+
finish();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (key.name === 'up')
|
|
106
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
107
|
+
else if (key.name === 'down')
|
|
108
|
+
index = (index + 1) % choices.length;
|
|
109
|
+
else if (multiple && key.name === 'space') {
|
|
110
|
+
if (selected.has(index))
|
|
111
|
+
selected.delete(index);
|
|
112
|
+
else
|
|
113
|
+
selected.add(index);
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
return;
|
|
117
|
+
draw();
|
|
118
|
+
};
|
|
119
|
+
process.stdin.on('keypress', onKey);
|
|
120
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
121
|
+
if (signal?.aborted)
|
|
122
|
+
onAbort();
|
|
123
|
+
else
|
|
124
|
+
draw();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
process.stdin.setRawMode(wasRaw);
|
|
129
|
+
process.stdin.pause();
|
|
130
|
+
process.stderr.write('\x1b[?25h');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export async function selectOne(title, choices, signal) { return (await choose(title, choices, false, signal))[0]; }
|
|
134
|
+
export async function selectMany(title, choices, signal, initial = []) { return choose(title, choices, true, signal, initial); }
|
|
135
|
+
/** A value is never echoed, retained on disk or placed in command arguments. */
|
|
136
|
+
export async function secretLine(title, signal) {
|
|
137
|
+
if (!interactive || !process.stdin.isTTY || !process.stderr.isTTY)
|
|
138
|
+
throw new CliError('value_input_required', 'Use an interactive terminal or pipe the exact value with --value-stdin. Do not pass secrets in command arguments.', 2);
|
|
139
|
+
emitKeypressEvents(process.stdin);
|
|
140
|
+
const wasRaw = process.stdin.isRaw;
|
|
141
|
+
process.stdin.setRawMode(true);
|
|
142
|
+
process.stdin.resume();
|
|
143
|
+
process.stderr.write(`${terminalText(title)} (hidden): `);
|
|
144
|
+
let value = '';
|
|
145
|
+
try {
|
|
146
|
+
return await new Promise((resolve, reject) => {
|
|
147
|
+
const finish = (error) => {
|
|
148
|
+
process.stdin.off('keypress', onKey);
|
|
149
|
+
signal.removeEventListener('abort', onAbort);
|
|
150
|
+
process.stderr.write('\n');
|
|
151
|
+
if (error)
|
|
152
|
+
reject(error);
|
|
153
|
+
else
|
|
154
|
+
resolve(value);
|
|
155
|
+
value = '';
|
|
156
|
+
};
|
|
157
|
+
const onAbort = () => finish(new CliError('canceled', 'Value entry canceled. No save was submitted.', 130));
|
|
158
|
+
const onKey = (character, key) => {
|
|
159
|
+
if (key.name === 'escape' || key.ctrl && key.name === 'c')
|
|
160
|
+
return onAbort();
|
|
161
|
+
if (key.name === 'return')
|
|
162
|
+
return finish();
|
|
163
|
+
if (key.name === 'backspace') {
|
|
164
|
+
value = Array.from(value).slice(0, -1).join('');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (key.ctrl || key.meta || !character || /[\u0000-\u001f\u007f-\u009f]/.test(character))
|
|
168
|
+
return;
|
|
169
|
+
if (Buffer.byteLength(value + character) > 65536)
|
|
170
|
+
return finish(new CliError('value_capacity', 'This value exceeds secure-entry capacity. No save was submitted.', 2));
|
|
171
|
+
value += character;
|
|
172
|
+
};
|
|
173
|
+
process.stdin.on('keypress', onKey);
|
|
174
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
175
|
+
if (signal.aborted)
|
|
176
|
+
onAbort();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
value = '';
|
|
181
|
+
process.stdin.setRawMode(wasRaw);
|
|
182
|
+
process.stdin.pause();
|
|
183
|
+
}
|
|
184
|
+
}
|