@piagent/platform 1.3.1 → 1.4.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/CHANGELOG.md +132 -0
- package/README.md +10 -4
- package/README.vi.md +27 -0
- package/architecture/layers.json +96 -1
- package/docs/README.md +1 -0
- package/docs/architecture.md +1 -0
- package/docs/authority-manifest.md +1 -0
- package/docs/auto-delegation-policy.md +1 -0
- package/docs/capability-packs.md +1 -0
- package/docs/command-reference-vietnamese.md +5 -4
- package/docs/context-engine.md +1 -0
- package/docs/context-window-policy.md +1 -0
- package/docs/distribution-standard.md +8 -7
- package/docs/en/architecture.md +13 -0
- package/docs/herdr-workflow.md +1 -0
- package/docs/languages.json +20 -4
- package/docs/mcp-and-tools.md +125 -9
- package/docs/memory-policy.md +1 -0
- package/docs/model-options.md +1 -0
- package/docs/oauth-providers.md +1 -0
- package/docs/operator-manual-vietnamese.md +15 -12
- package/docs/package-architecture-notes.md +1 -0
- package/docs/project-adapters.md +1 -0
- package/docs/project-onboarding.md +1 -0
- package/docs/publishing-for-teams.md +37 -0
- package/docs/quality-benchmark.md +1 -0
- package/docs/quickstart-vietnamese.md +3 -2
- package/docs/release-install-policy.md +22 -8
- package/docs/runtime-harness-standard.md +2 -1
- package/docs/runtime-policy-design.md +1 -0
- package/docs/runtime-quality-baseline.md +1 -0
- package/docs/security-redaction-benchmark.md +1 -0
- package/docs/security-threat-model.md +2 -1
- package/docs/subagent-orchestration-capabilities.md +1 -0
- package/docs/subagents-and-multiagent.md +1 -0
- package/docs/task-implementation-contract.md +12 -4
- package/docs/task-lifecycle-vietnamese.md +1 -0
- package/docs/team-onboarding.md +3 -2
- package/docs/third-party-neutrality.md +26 -0
- package/docs/usage-observability.md +71 -0
- package/docs/vercel-docs-site.md +1 -0
- package/docs/vi/architecture.md +13 -0
- package/docs/workflow-recipes.md +1 -0
- package/docs-site/architecture.html +5 -1
- package/docs-site/benchmark.html +5 -1
- package/docs-site/commands.html +5 -1
- package/docs-site/content/en/index.html +49 -0
- package/docs-site/content/en/runtime.html +13 -0
- package/docs-site/content/index.html +49 -0
- package/docs-site/content/runtime.html +28 -0
- package/docs-site/context-engine.html +5 -1
- package/docs-site/en/architecture.html +5 -1
- package/docs-site/en/benchmark.html +5 -1
- package/docs-site/en/commands.html +5 -1
- package/docs-site/en/context-engine.html +5 -1
- package/docs-site/en/index.html +54 -1
- package/docs-site/en/mcp-approval.html +5 -1
- package/docs-site/en/mcp-auth.html +5 -1
- package/docs-site/en/mcp-servers.html +5 -1
- package/docs-site/en/mcp.html +5 -1
- package/docs-site/en/permissions.html +5 -1
- package/docs-site/en/profiles.html +5 -1
- package/docs-site/en/quickstart.html +5 -1
- package/docs-site/en/runtime.html +18 -1
- package/docs-site/en/scope.html +5 -1
- package/docs-site/en/security.html +5 -1
- package/docs-site/en/team.html +5 -1
- package/docs-site/en/whats-new.html +5 -1
- package/docs-site/en/workflows.html +5 -1
- package/docs-site/index.html +54 -1
- package/docs-site/mcp-approval.html +5 -1
- package/docs-site/mcp-auth.html +5 -1
- package/docs-site/mcp-servers.html +5 -1
- package/docs-site/mcp.html +5 -1
- package/docs-site/permissions.html +5 -1
- package/docs-site/profiles.html +5 -1
- package/docs-site/quickstart.html +5 -1
- package/docs-site/runtime.html +33 -1
- package/docs-site/scope.html +5 -1
- package/docs-site/security.html +5 -1
- package/docs-site/team.html +5 -1
- package/docs-site/whats-new.html +5 -1
- package/docs-site/workflows.html +5 -1
- package/evals/fixtures/mutation-provenance-record.invalid.json +17 -0
- package/evals/fixtures/mutation-provenance-record.valid.json +27 -0
- package/evals/fixtures/piagent-webui/approval-v1.invalid.json +54 -0
- package/evals/fixtures/piagent-webui/approval-v1.valid.json +58 -0
- package/evals/fixtures/piagent-webui/attachment-v1.invalid.json +25 -0
- package/evals/fixtures/piagent-webui/attachment-v1.valid.json +25 -0
- package/evals/fixtures/piagent-webui/capabilities-v1.invalid.json +51 -0
- package/evals/fixtures/piagent-webui/capabilities-v1.valid.json +122 -0
- package/evals/fixtures/piagent-webui/commit-summary-v1.invalid.json +10 -0
- package/evals/fixtures/piagent-webui/commit-summary-v1.valid.json +25 -0
- package/evals/fixtures/piagent-webui/control-command-v1.invalid.json +37 -0
- package/evals/fixtures/piagent-webui/control-command-v1.valid.json +35 -0
- package/evals/fixtures/piagent-webui/diff-v1.invalid.json +13 -0
- package/evals/fixtures/piagent-webui/diff-v1.valid.json +141 -0
- package/evals/fixtures/piagent-webui/gateway-capabilities-v1.invalid.json +35 -0
- package/evals/fixtures/piagent-webui/gateway-capabilities-v1.valid.json +35 -0
- package/evals/fixtures/piagent-webui/gateway-protocol-v1.invalid.json +14 -0
- package/evals/fixtures/piagent-webui/gateway-protocol-v1.valid.json +14 -0
- package/evals/fixtures/piagent-webui/handoff-history-v1.invalid.json +16 -0
- package/evals/fixtures/piagent-webui/handoff-history-v1.valid.json +16 -0
- package/evals/fixtures/piagent-webui/model-catalog-v1.invalid.json +31 -0
- package/evals/fixtures/piagent-webui/model-catalog-v1.valid.json +43 -0
- package/evals/fixtures/piagent-webui/queue-v1.invalid.json +29 -0
- package/evals/fixtures/piagent-webui/queue-v1.valid.json +44 -0
- package/evals/fixtures/piagent-webui/recovery-history-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/recovery-history-v1.valid.json +20 -0
- package/evals/fixtures/piagent-webui/release-monitor-v1.invalid.json +12 -0
- package/evals/fixtures/piagent-webui/release-monitor-v1.valid.json +29 -0
- package/evals/fixtures/piagent-webui/review-state-v1.invalid.json +25 -0
- package/evals/fixtures/piagent-webui/review-state-v1.valid.json +38 -0
- package/evals/fixtures/piagent-webui/runtime-event-v2.invalid.json +49 -0
- package/evals/fixtures/piagent-webui/runtime-event-v2.valid.json +49 -0
- package/evals/fixtures/piagent-webui/session-catalog-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/session-catalog-v1.valid.json +58 -0
- package/evals/fixtures/piagent-webui/session-command-v1.invalid.json +19 -0
- package/evals/fixtures/piagent-webui/session-command-v1.valid.json +19 -0
- package/evals/fixtures/piagent-webui/snapshot-v1.invalid.json +5 -0
- package/evals/fixtures/piagent-webui/snapshot-v1.valid.json +191 -0
- package/evals/fixtures/piagent-webui/source-change-v1.invalid.json +57 -0
- package/evals/fixtures/piagent-webui/source-change-v1.valid.json +98 -0
- package/evals/fixtures/piagent-webui/source-mutation-v1.invalid.json +19 -0
- package/evals/fixtures/piagent-webui/source-mutation-v1.valid.json +38 -0
- package/evals/fixtures/piagent-webui/source-revert-v1.invalid.json +20 -0
- package/evals/fixtures/piagent-webui/source-revert-v1.valid.json +62 -0
- package/evals/fixtures/piagent-webui/subagent-tree-v1.invalid.json +26 -0
- package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +39 -0
- package/evals/fixtures/piagent-webui/task-index-v1.invalid.json +15 -0
- package/evals/fixtures/piagent-webui/task-index-v1.valid.json +15 -0
- package/evals/fixtures/piagent-webui/task-timeline-v1.invalid.json +14 -0
- package/evals/fixtures/piagent-webui/task-timeline-v1.valid.json +16 -0
- package/evals/fixtures/piagent-webui/transcript-v1.invalid.json +35 -0
- package/evals/fixtures/piagent-webui/transcript-v1.valid.json +55 -0
- package/evals/fixtures/task-baseline-manifest.invalid.json +16 -0
- package/evals/fixtures/task-baseline-manifest.valid.json +41 -0
- package/evals/fixtures/verifier-file-snapshot.invalid.json +21 -0
- package/evals/fixtures/verifier-file-snapshot.valid.json +21 -0
- package/package.json +43 -4
- package/packages/piagent-core/README.md +3 -2
- package/packages/piagent-core/benchmark/benchmark-candidate.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-forensics.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-ledger.d.ts +4 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-runtime.d.ts +1 -0
- package/packages/piagent-core/capabilities/capability-core.js +2 -2
- package/packages/piagent-core/capabilities/capability-verification-cache.js +17 -7
- package/packages/piagent-core/capabilities/runtime-integrity.js +190 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +80 -272
- package/packages/piagent-core/extensions/redaction-core.d.ts +2 -0
- package/packages/piagent-core/extensions/runtime-evidence.js +12 -1
- package/packages/piagent-core/extensions/shell-reach.ts +250 -0
- package/packages/piagent-core/extensions/task-journal.js +30 -1
- package/packages/piagent-core/extensions/task-state.js +33 -36
- package/packages/piagent-core/mcp/mcp-command-actions.js +1 -1
- package/packages/piagent-core/mcp/mcp-server-catalog.js +139 -9
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +2 -2
- package/packages/piagent-core/runtime/hooks/tool-call-hook.ts +12 -2
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +20 -19
- package/packages/piagent-core/runtime/hooks/tool-result-metadata.ts +13 -0
- package/packages/piagent-core/runtime/input/chat-images.ts +2 -2
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +94 -0
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +318 -0
- package/packages/piagent-core/runtime/inspection/commit-summary-projection.ts +59 -0
- package/packages/piagent-core/runtime/inspection/context-telemetry-inspection.ts +37 -0
- package/packages/piagent-core/runtime/inspection/criteria-links.ts +295 -0
- package/packages/piagent-core/runtime/inspection/diff-projection.ts +499 -0
- package/packages/piagent-core/runtime/inspection/git-filter-safety.ts +38 -0
- package/packages/piagent-core/runtime/inspection/git-numstat-adapter.ts +71 -0
- package/packages/piagent-core/runtime/inspection/git-status-adapter.ts +493 -0
- package/packages/piagent-core/runtime/inspection/git-tree-adapter.ts +108 -0
- package/packages/piagent-core/runtime/inspection/mcp-control.ts +10 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-contract.ts +131 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-recorder.ts +48 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-store.ts +161 -0
- package/packages/piagent-core/runtime/inspection/project-runtime-inspection.ts +36 -0
- package/packages/piagent-core/runtime/inspection/review-state-contract.ts +108 -0
- package/packages/piagent-core/runtime/inspection/review-state-projection.ts +93 -0
- package/packages/piagent-core/runtime/inspection/review-state-store.ts +112 -0
- package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +262 -0
- package/packages/piagent-core/runtime/inspection/same-process-bridge-proof.ts +103 -0
- package/packages/piagent-core/runtime/inspection/source-change-projection.ts +471 -0
- package/packages/piagent-core/runtime/inspection/source-evidence-contract.ts +139 -0
- package/packages/piagent-core/runtime/inspection/source-evidence-store.ts +315 -0
- package/packages/piagent-core/runtime/inspection/source-handoff-store.ts +70 -0
- package/packages/piagent-core/runtime/inspection/source-inspection-plan.ts +34 -0
- package/packages/piagent-core/runtime/inspection/source-mutation-projection.ts +164 -0
- package/packages/piagent-core/runtime/inspection/source-mutation-store.ts +168 -0
- package/packages/piagent-core/runtime/inspection/source-open-target.ts +38 -0
- package/packages/piagent-core/runtime/inspection/source-revert-projection.ts +147 -0
- package/packages/piagent-core/runtime/inspection/task-baseline-start-capture.ts +22 -0
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +160 -0
- package/packages/piagent-core/runtime/inspection/task-control-journal.ts +131 -0
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +95 -0
- package/packages/piagent-core/runtime/inspection/task-provenance-projection.ts +60 -0
- package/packages/piagent-core/runtime/inspection/task-recovery-timeline.ts +88 -0
- package/packages/piagent-core/runtime/inspection/task-run-index.ts +87 -0
- package/packages/piagent-core/runtime/inspection/task-source-projection.ts +401 -0
- package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +101 -0
- package/packages/piagent-core/runtime/inspection/text-diff.ts +60 -0
- package/packages/piagent-core/runtime/inspection/verifier-snapshot-contract.ts +114 -0
- package/packages/piagent-core/runtime/inspection/verifier-snapshot-store.ts +189 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +320 -0
- package/packages/piagent-core/runtime/inspection/workspace-file-reader.ts +132 -0
- package/packages/piagent-core/runtime/inspection/workspace-source-projection.ts +96 -0
- package/packages/piagent-core/runtime/inspection/zero-turn-conformance.ts +175 -0
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +93 -6
- package/packages/piagent-core/runtime/policy/block-remedy.ts +139 -0
- package/packages/piagent-core/runtime/policy/source-index-transaction.ts +173 -0
- package/packages/piagent-core/runtime/policy/source-mutation-guard-binding.ts +42 -0
- package/packages/piagent-core/runtime/policy/source-mutation-guard.ts +184 -0
- package/packages/piagent-core/runtime/policy/source-worktree-transaction.ts +105 -0
- package/packages/piagent-core/runtime/product/activity-inspector-footer.ts +5 -3
- package/packages/piagent-core/runtime/product/activity-inspector.ts +85 -343
- package/packages/piagent-core/runtime/recovery/resume-state.ts +38 -1
- package/packages/piagent-core/runtime/registration/activity-inspector-command.ts +68 -14
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +22 -27
- package/packages/piagent-core/runtime/session/model-authorship-state.ts +31 -3
- package/packages/piagent-core/runtime/session/runtime-state.ts +11 -1
- package/packages/piagent-core/runtime/session/token-economics.js +34 -0
- package/packages/piagent-core/runtime/tools/phase-tool-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +5 -0
- package/packages/piagent-core/security/sensitive-data.js +7 -0
- package/packages/piagent-webui/dist/client/assets/index-DwKCWbwd.css +1 -0
- package/packages/piagent-webui/dist/client/assets/index-bu7XpD9Q.js +172 -0
- package/packages/piagent-webui/dist/client/index.html +16 -0
- package/packages/piagent-webui/extension/attachment-store.ts +323 -0
- package/packages/piagent-webui/extension/held-message-queue.ts +311 -0
- package/packages/piagent-webui/extension/lifecycle-controller.ts +423 -0
- package/packages/piagent-webui/extension/lifecycle-event-adapter.ts +23 -0
- package/packages/piagent-webui/extension/piagent-webui.ts +470 -0
- package/packages/piagent-webui/extension/review-controller.ts +160 -0
- package/packages/piagent-webui/extension/same-session-bridge.ts +500 -0
- package/packages/piagent-webui/extension/session-options-controller.ts +281 -0
- package/packages/piagent-webui/extension/session-stream-adapter.ts +249 -0
- package/packages/piagent-webui/extension/source-mutation-controller.ts +215 -0
- package/packages/piagent-webui/extension/source-open-controller.ts +136 -0
- package/packages/piagent-webui/extension/source-revert-controller.ts +189 -0
- package/packages/piagent-webui/extension/terminal-session-adapter.ts +73 -0
- package/packages/piagent-webui/extension/vscode-handoff.ts +51 -0
- package/packages/piagent-webui/gateway/control-socket.ts +92 -0
- package/packages/piagent-webui/gateway/extension-authority.ts +47 -0
- package/packages/piagent-webui/gateway/gateway-events.ts +60 -0
- package/packages/piagent-webui/gateway/gateway-protocol-service.ts +64 -0
- package/packages/piagent-webui/gateway/gateway-service.ts +215 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +110 -0
- package/packages/piagent-webui/gateway/mcp-auth-broker.ts +158 -0
- package/packages/piagent-webui/gateway/native-project-picker.ts +29 -0
- package/packages/piagent-webui/gateway/pi-host.ts +41 -0
- package/packages/piagent-webui/gateway/profile-state.ts +1 -0
- package/packages/piagent-webui/gateway/project-registry.ts +108 -0
- package/packages/piagent-webui/gateway/provider-auth-broker.ts +172 -0
- package/packages/piagent-webui/gateway/session-catalog.ts +128 -0
- package/packages/piagent-webui/gateway/session-command-controller.ts +237 -0
- package/packages/piagent-webui/gateway/session-command-store.ts +224 -0
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +239 -0
- package/packages/piagent-webui/gateway/session-lease-store.ts +1 -0
- package/packages/piagent-webui/gateway/session-metadata-store.ts +152 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +489 -0
- package/packages/piagent-webui/ownership/gateway-runtime-context.ts +8 -0
- package/packages/piagent-webui/ownership/profile-state.ts +117 -0
- package/packages/piagent-webui/ownership/session-lease-store.ts +329 -0
- package/packages/piagent-webui/ownership/session-refs.ts +8 -0
- package/packages/piagent-webui/package.json +33 -0
- package/packages/piagent-webui/server/README.md +4 -0
- package/packages/piagent-webui/server/benchmark-release-monitor.ts +230 -0
- package/packages/piagent-webui/server/core-inspection-provider.ts +403 -0
- package/packages/piagent-webui/server/gateway-websocket.ts +143 -0
- package/packages/piagent-webui/server/http-security.ts +27 -0
- package/packages/piagent-webui/server/ipc-read-model-client.ts +89 -0
- package/packages/piagent-webui/server/loopback-server.ts +368 -0
- package/packages/piagent-webui/server/read-model-provider.ts +37 -0
- package/packages/piagent-webui/server/read-only-router.ts +105 -0
- package/packages/piagent-webui/server/session-auth.ts +88 -0
- package/packages/piagent-webui/server/sidecar-main.ts +86 -0
- package/packages/piagent-webui/server/sse-hub.ts +76 -0
- package/packages/piagent-webui/server/static-bundle.ts +40 -0
- package/packages/piagent-webui/server/transcript-projection.ts +125 -0
- package/schemas/mutation-provenance-record.schema.json +100 -0
- package/schemas/piagent-webui/approval-v1.schema.json +654 -0
- package/schemas/piagent-webui/attachment-v1.schema.json +267 -0
- package/schemas/piagent-webui/capabilities-v1.schema.json +729 -0
- package/schemas/piagent-webui/catalog-v1.json +240 -0
- package/schemas/piagent-webui/commit-summary-v1.schema.json +63 -0
- package/schemas/piagent-webui/common-v1.schema.json +479 -0
- package/schemas/piagent-webui/control-command-v1.schema.json +1780 -0
- package/schemas/piagent-webui/diff-v1.schema.json +826 -0
- package/schemas/piagent-webui/gateway-capabilities-v1.schema.json +136 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +456 -0
- package/schemas/piagent-webui/handoff-history-v1.schema.json +92 -0
- package/schemas/piagent-webui/model-catalog-v1.schema.json +95 -0
- package/schemas/piagent-webui/queue-v1.schema.json +99 -0
- package/schemas/piagent-webui/recovery-history-v1.schema.json +144 -0
- package/schemas/piagent-webui/release-monitor-v1.schema.json +173 -0
- package/schemas/piagent-webui/review-state-v1.schema.json +206 -0
- package/schemas/piagent-webui/runtime-event-v2.schema.json +5206 -0
- package/schemas/piagent-webui/session-catalog-v1.schema.json +222 -0
- package/schemas/piagent-webui/session-command-v1.schema.json +390 -0
- package/schemas/piagent-webui/snapshot-v1.schema.json +2622 -0
- package/schemas/piagent-webui/source-change-v1.schema.json +763 -0
- package/schemas/piagent-webui/source-mutation-v1.schema.json +96 -0
- package/schemas/piagent-webui/source-revert-v1.schema.json +130 -0
- package/schemas/piagent-webui/subagent-tree-v1.schema.json +88 -0
- package/schemas/piagent-webui/task-index-v1.schema.json +74 -0
- package/schemas/piagent-webui/task-timeline-v1.schema.json +49 -0
- package/schemas/piagent-webui/transcript-v1.schema.json +243 -0
- package/schemas/task-baseline-manifest.schema.json +122 -0
- package/schemas/verifier-file-snapshot.schema.json +81 -0
- package/scripts/build-docs-site.mjs +21 -1
- package/scripts/check-architecture.mjs +1 -0
- package/scripts/check-doc-languages.mjs +19 -2
- package/scripts/check-third-party-neutrality.mjs +43 -0
- package/scripts/explain-command.mjs +240 -0
- package/scripts/install-global.sh +105 -4
- package/scripts/mcp-manage.mjs +17 -4
- package/scripts/pi-session-stats.sh +10 -2
- package/scripts/piagent-cli.mjs +30 -3
- package/scripts/piagent-dashboard.mjs +261 -0
- package/scripts/piagent-webui-launcher.mjs +121 -0
- package/scripts/setup.sh +69 -33
- package/scripts/traffic-snapshot.mjs +214 -0
- package/scripts/typescript-loader.mjs +42 -1
- package/scripts/uninstall-global.sh +53 -2
- package/scripts/update-global.mjs +33 -1
- package/scripts/verify-local.sh +257 -0
- package/scripts/verify-release-identity.mjs +4 -1
- package/scripts/verify-translation-pairing.mjs +121 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Archives GitHub repository traffic before GitHub discards it.
|
|
3
|
+
//
|
|
4
|
+
// The traffic API only serves a rolling 14-day window. Anything older is gone
|
|
5
|
+
// for good -- there is no endpoint that returns it and no way to reconstruct
|
|
6
|
+
// it. So the counts are merged into CSV files that keep growing, and a run that
|
|
7
|
+
// happens at least every 14 days loses nothing.
|
|
8
|
+
//
|
|
9
|
+
// Merge rule: a row is keyed by its day and the newest fetch wins. Today's
|
|
10
|
+
// bucket is still filling while the run happens, so it is always partial; the
|
|
11
|
+
// next run overwrites it with the settled value. Once a day leaves the window
|
|
12
|
+
// it stops being returned at all, and the stored row is final.
|
|
13
|
+
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
|
|
17
|
+
const EXIT_USAGE = 2;
|
|
18
|
+
const EXIT_FORBIDDEN = 3;
|
|
19
|
+
|
|
20
|
+
const SERIES = [
|
|
21
|
+
{ endpoint: "clones", file: "clones.csv", columns: ["date", "count", "uniques"] },
|
|
22
|
+
{ endpoint: "views", file: "views.csv", columns: ["date", "count", "uniques"] }
|
|
23
|
+
];
|
|
24
|
+
// Referrers and paths are a 14-day rollup, not a daily series: the API gives no
|
|
25
|
+
// per-day breakdown, so a row is only meaningful next to the day it was read.
|
|
26
|
+
const ROLLUPS = [
|
|
27
|
+
{ endpoint: "popular/referrers", file: "referrers.csv", key: "referrer", columns: ["snapshot_date", "referrer", "count", "uniques"] },
|
|
28
|
+
{ endpoint: "popular/paths", file: "paths.csv", key: "path", columns: ["snapshot_date", "path", "title", "count", "uniques"] }
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function fail(message, code = EXIT_USAGE) {
|
|
32
|
+
console.error(`FAIL: ${message}`);
|
|
33
|
+
process.exit(code);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseArguments(argv) {
|
|
37
|
+
const options = { repository: "", out: "traffic", dryRun: false, today: "" };
|
|
38
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
39
|
+
const argument = argv[index];
|
|
40
|
+
switch (argument) {
|
|
41
|
+
case "--repository": case "--repo": {
|
|
42
|
+
options.repository = argv[index += 1] ?? "";
|
|
43
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(options.repository)) fail("--repository needs owner/name");
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case "--out": {
|
|
47
|
+
options.out = argv[index += 1] ?? "";
|
|
48
|
+
if (!options.out) fail("--out needs a directory");
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
// The archive is written from a snapshot date, and a test cannot wait a
|
|
52
|
+
// day to prove the merge. Injecting it keeps the assertions exact.
|
|
53
|
+
case "--today": {
|
|
54
|
+
options.today = argv[index += 1] ?? "";
|
|
55
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(options.today)) fail("--today needs YYYY-MM-DD");
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "--dry-run": options.dryRun = true; break;
|
|
59
|
+
case "-h": case "--help": options.help = true; break;
|
|
60
|
+
default: fail(`unknown argument: ${argument}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return options;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function csvEscape(value) {
|
|
67
|
+
const text = String(value ?? "");
|
|
68
|
+
return /[",\n\r]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function parseCsv(text) {
|
|
72
|
+
const rows = [];
|
|
73
|
+
let row = [], field = "", quoted = false;
|
|
74
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
75
|
+
const character = text[index];
|
|
76
|
+
if (quoted) {
|
|
77
|
+
if (character === '"' && text[index + 1] === '"') { field += '"'; index += 1; }
|
|
78
|
+
else if (character === '"') quoted = false;
|
|
79
|
+
else field += character;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (character === '"') quoted = true;
|
|
83
|
+
else if (character === ",") { row.push(field); field = ""; }
|
|
84
|
+
else if (character === "\n") { row.push(field); rows.push(row); row = []; field = ""; }
|
|
85
|
+
else if (character !== "\r") field += character;
|
|
86
|
+
}
|
|
87
|
+
if (field.length || row.length) { row.push(field); rows.push(row); }
|
|
88
|
+
return rows.filter((entry) => entry.some((cell) => cell !== ""));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readTable(file, columns) {
|
|
92
|
+
if (!fs.existsSync(file)) return [];
|
|
93
|
+
const rows = parseCsv(fs.readFileSync(file, "utf8"));
|
|
94
|
+
const header = rows.shift();
|
|
95
|
+
if (!header || header.join(",") !== columns.join(",")) {
|
|
96
|
+
fail(`${file} has header [${header?.join(", ") ?? "none"}], expected [${columns.join(", ")}]`);
|
|
97
|
+
}
|
|
98
|
+
return rows.map((row) => Object.fromEntries(columns.map((column, index) => [column, row[index] ?? ""])));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeTable(file, columns, rows) {
|
|
102
|
+
const lines = [columns.join(",")];
|
|
103
|
+
for (const row of rows) lines.push(columns.map((column) => csvEscape(row[column])).join(","));
|
|
104
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
105
|
+
fs.writeFileSync(file, `${lines.join("\n")}\n`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Existing rows plus fetched rows, keyed by `key`. The fetched value wins,
|
|
110
|
+
* because it is the newer reading of the same day. Rows outside the API window
|
|
111
|
+
* are untouched -- that is the whole point of the archive.
|
|
112
|
+
*/
|
|
113
|
+
export function mergeByKey(existing, fetched, key) {
|
|
114
|
+
const merged = new Map(existing.map((row) => [row[key], row]));
|
|
115
|
+
for (const row of fetched) merged.set(row[key], row);
|
|
116
|
+
return [...merged.values()].sort((a, b) => String(a[key]).localeCompare(String(b[key])));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Rollup rows for one snapshot date, replacing any earlier run on that date. */
|
|
120
|
+
export function mergeRollup(existing, fetched, snapshotDate) {
|
|
121
|
+
const kept = existing.filter((row) => row.snapshot_date !== snapshotDate);
|
|
122
|
+
return [...kept, ...fetched].sort((a, b) =>
|
|
123
|
+
String(a.snapshot_date).localeCompare(String(b.snapshot_date))
|
|
124
|
+
|| Number(b.count) - Number(a.count));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function fetchTraffic(repository, endpoint, token) {
|
|
128
|
+
const url = `https://api.github.com/repos/${repository}/traffic/${endpoint}`;
|
|
129
|
+
const response = await fetch(url, {
|
|
130
|
+
headers: {
|
|
131
|
+
accept: "application/vnd.github+json",
|
|
132
|
+
authorization: `Bearer ${token}`,
|
|
133
|
+
"user-agent": "piagent-traffic-snapshot",
|
|
134
|
+
"x-github-api-version": "2022-11-28"
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
if (response.status === 403 || response.status === 404) {
|
|
138
|
+
// The distinction matters: this is the one failure an operator cannot debug
|
|
139
|
+
// from the status code, because the traffic API answers 403 for a token
|
|
140
|
+
// that is valid but lacks push access -- which the default workflow token
|
|
141
|
+
// may or may not have, depending on repository settings.
|
|
142
|
+
fail(`the token cannot read ${endpoint} for ${repository} (HTTP ${response.status}).\n`
|
|
143
|
+
+ " Repository traffic needs push access. If this ran in GitHub Actions with the\n"
|
|
144
|
+
+ " default GITHUB_TOKEN, create a fine-grained personal access token with the\n"
|
|
145
|
+
+ " 'Administration: read' permission on this repository, store it as the\n"
|
|
146
|
+
+ " TRAFFIC_TOKEN secret, and re-run. Nothing was written.", EXIT_FORBIDDEN);
|
|
147
|
+
}
|
|
148
|
+
if (!response.ok) fail(`GET ${url} returned HTTP ${response.status}`);
|
|
149
|
+
return response.json();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function snapshot(options, token, fetcher = fetchTraffic) {
|
|
153
|
+
const today = options.today || new Date().toISOString().slice(0, 10);
|
|
154
|
+
const summary = { repository: options.repository, snapshotDate: today, files: {} };
|
|
155
|
+
|
|
156
|
+
// Read every existing file before fetching anything. A corrupt archive is a
|
|
157
|
+
// reason to stop, and stopping before the network call keeps the failure
|
|
158
|
+
// about the file rather than about whatever the API happened to answer.
|
|
159
|
+
const existing = new Map();
|
|
160
|
+
for (const target of [...SERIES, ...ROLLUPS]) {
|
|
161
|
+
existing.set(target.file, readTable(path.join(options.out, target.file), target.columns));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const series of SERIES) {
|
|
165
|
+
const payload = await fetcher(options.repository, series.endpoint, token);
|
|
166
|
+
const fetched = (payload[series.endpoint] ?? []).map((entry) => ({
|
|
167
|
+
date: String(entry.timestamp ?? "").slice(0, 10),
|
|
168
|
+
count: String(entry.count ?? 0),
|
|
169
|
+
uniques: String(entry.uniques ?? 0)
|
|
170
|
+
})).filter((row) => row.date);
|
|
171
|
+
const file = path.join(options.out, series.file);
|
|
172
|
+
const merged = mergeByKey(existing.get(series.file), fetched, "date");
|
|
173
|
+
if (!options.dryRun) writeTable(file, series.columns, merged);
|
|
174
|
+
summary.files[series.file] = { fetchedDays: fetched.length, totalDays: merged.length };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const rollup of ROLLUPS) {
|
|
178
|
+
const payload = await fetcher(options.repository, rollup.endpoint, token);
|
|
179
|
+
const fetched = (Array.isArray(payload) ? payload : []).map((entry) => ({
|
|
180
|
+
snapshot_date: today,
|
|
181
|
+
[rollup.key]: String(entry[rollup.key] ?? entry.referrer ?? entry.path ?? ""),
|
|
182
|
+
title: String(entry.title ?? ""),
|
|
183
|
+
count: String(entry.count ?? 0),
|
|
184
|
+
uniques: String(entry.uniques ?? 0)
|
|
185
|
+
}));
|
|
186
|
+
const file = path.join(options.out, rollup.file);
|
|
187
|
+
const merged = mergeRollup(existing.get(rollup.file), fetched, today);
|
|
188
|
+
if (!options.dryRun) writeTable(file, rollup.columns, merged);
|
|
189
|
+
summary.files[rollup.file] = { fetchedRows: fetched.length, totalRows: merged.length };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return summary;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const invokedDirectly = process.argv[1]
|
|
196
|
+
&& path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname);
|
|
197
|
+
|
|
198
|
+
if (invokedDirectly) {
|
|
199
|
+
const options = parseArguments(process.argv.slice(2));
|
|
200
|
+
if (options.help) {
|
|
201
|
+
console.log(`Usage:
|
|
202
|
+
node scripts/traffic-snapshot.mjs --repository <owner/name> [--out <dir>] [--dry-run]
|
|
203
|
+
|
|
204
|
+
Archives GitHub traffic (clones, views, referrers, paths) into growing CSV files.
|
|
205
|
+
The API only keeps 14 days, so run this at least fortnightly or the gap is
|
|
206
|
+
permanent. Reads the token from TRAFFIC_TOKEN, else GITHUB_TOKEN.`);
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
if (!options.repository) fail("--repository is required");
|
|
210
|
+
const token = process.env.TRAFFIC_TOKEN || process.env.GITHUB_TOKEN;
|
|
211
|
+
if (!token) fail("set TRAFFIC_TOKEN or GITHUB_TOKEN");
|
|
212
|
+
const summary = await snapshot(options, token);
|
|
213
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
214
|
+
}
|
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { stripTypeScriptTypes } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
// Repository TypeScript must remain erasable: the integrity lock hashes the
|
|
7
|
+
// source bytes and `strip` fails closed if a future change needs codegen.
|
|
8
|
+
// pi-mcp-adapter@2.15.0 is the sole exception because its published OAuth
|
|
9
|
+
// implementation contains parameter properties. Keep that transform isolated
|
|
10
|
+
// to the exact installed adapter root and Node release reviewed below. A Node
|
|
11
|
+
// or adapter upgrade must deliberately update these pins before OAuth loads.
|
|
12
|
+
export const PINNED_EXTERNAL_TRANSFORM = Object.freeze({
|
|
13
|
+
nodeVersion: "24.11.1",
|
|
14
|
+
packageName: "pi-mcp-adapter",
|
|
15
|
+
packageVersion: "2.15.0"
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function pinnedTransformRoot(url) {
|
|
19
|
+
const configured = process.env.PIAGENT_PINNED_TS_TRANSFORM_ROOT;
|
|
20
|
+
if (!configured || process.versions.node !== PINNED_EXTERNAL_TRANSFORM.nodeVersion) return false;
|
|
21
|
+
// This asks one question: is the file being loaded inside the pinned adapter?
|
|
22
|
+
// A configured root that is absent, unreadable or not that adapter answers
|
|
23
|
+
// "no". It used to throw instead, and the throw escaped `load` on the first
|
|
24
|
+
// `.ts` file of any kind -- so a machine without pi-mcp-adapter installed
|
|
25
|
+
// could not start the gateway at all, for a reason that had nothing to do
|
|
26
|
+
// with the file it died on. Answering "no" falls back to `strip`, which is
|
|
27
|
+
// the stricter of the two modes: this can only refuse a transform, never
|
|
28
|
+
// grant one.
|
|
29
|
+
let root;
|
|
30
|
+
let metadata;
|
|
31
|
+
try {
|
|
32
|
+
root = await fs.realpath(configured);
|
|
33
|
+
metadata = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf8"));
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (metadata?.name !== PINNED_EXTERNAL_TRANSFORM.packageName
|
|
38
|
+
|| metadata?.version !== PINNED_EXTERNAL_TRANSFORM.packageVersion) return false;
|
|
39
|
+
const target = await fs.realpath(fileURLToPath(url));
|
|
40
|
+
const relative = path.relative(root, target);
|
|
41
|
+
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
42
|
+
}
|
|
3
43
|
|
|
4
44
|
export async function load(url, context, nextLoad) {
|
|
5
45
|
if (!url.endsWith(".ts")) return nextLoad(url, context);
|
|
6
46
|
const source = await fs.readFile(new URL(url), "utf8");
|
|
47
|
+
const mode = await pinnedTransformRoot(url) ? "transform" : "strip";
|
|
7
48
|
return {
|
|
8
49
|
format: "module",
|
|
9
50
|
shortCircuit: true,
|
|
10
|
-
source: stripTypeScriptTypes(source, { mode
|
|
51
|
+
source: stripTypeScriptTypes(source, { mode, sourceUrl: url })
|
|
11
52
|
};
|
|
12
53
|
}
|
|
@@ -80,6 +80,46 @@ run_cmd() {
|
|
|
80
80
|
fi
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
# The helper can live under a non-default npm prefix: install falls back to a
|
|
84
|
+
# user-writable root when the configured one is not writable, and --npm-prefix
|
|
85
|
+
# picks one deliberately. A bare `npm uninstall -g` resolves against the
|
|
86
|
+
# configured prefix instead, so it removes nothing and still reports success.
|
|
87
|
+
# Derive the prefix from where this script actually runs, the same way
|
|
88
|
+
# update-global.mjs does, and only add --prefix when it differs.
|
|
89
|
+
installed_helper_prefix() {
|
|
90
|
+
local root="$ROOT"
|
|
91
|
+
[[ "$(basename "$root")" == "platform" ]] || return 0
|
|
92
|
+
local scope_root node_modules_root parent
|
|
93
|
+
scope_root="$(dirname "$root")"
|
|
94
|
+
[[ "$(basename "$scope_root")" == "@piagent" ]] || return 0
|
|
95
|
+
node_modules_root="$(dirname "$scope_root")"
|
|
96
|
+
[[ "$(basename "$node_modules_root")" == "node_modules" ]] || return 0
|
|
97
|
+
parent="$(dirname "$node_modules_root")"
|
|
98
|
+
# An npx run lives in a prunable cache that owns no bin on anyone's PATH;
|
|
99
|
+
# uninstalling from there would claim to remove an install nobody has.
|
|
100
|
+
case "$parent" in
|
|
101
|
+
*/_npx/*|*/_npx) return 0 ;;
|
|
102
|
+
esac
|
|
103
|
+
if [[ "$(basename "$parent")" == "lib" ]]; then
|
|
104
|
+
dirname "$parent"
|
|
105
|
+
else
|
|
106
|
+
printf '%s' "$parent"
|
|
107
|
+
fi
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# The prefix npm has to be pointed at, or empty when the helper already lives
|
|
111
|
+
# under the configured one and the plain command is correct. Returns a single
|
|
112
|
+
# value rather than an argument list: macOS still ships bash 3.2, where
|
|
113
|
+
# expanding an empty array under `set -u` aborts the script.
|
|
114
|
+
npm_prefix_override() {
|
|
115
|
+
local helper_prefix configured
|
|
116
|
+
helper_prefix="$(installed_helper_prefix)"
|
|
117
|
+
[[ -n "$helper_prefix" ]] || return 0
|
|
118
|
+
configured="$(npm prefix -g 2>/dev/null || true)"
|
|
119
|
+
[[ "$helper_prefix" != "$configured" ]] || return 0
|
|
120
|
+
printf '%s' "$helper_prefix"
|
|
121
|
+
}
|
|
122
|
+
|
|
83
123
|
# Reads the package sources Pi has registered, so removal targets what is
|
|
84
124
|
# actually installed rather than what this version happens to install today. An
|
|
85
125
|
# older release registered a different source shape, and it still has to come
|
|
@@ -174,7 +214,13 @@ if [[ "$WITH_HOST" == true ]]; then
|
|
|
174
214
|
echo
|
|
175
215
|
if command -v npm >/dev/null 2>&1; then
|
|
176
216
|
echo "Uninstalling Pi host:"
|
|
177
|
-
|
|
217
|
+
HOST_PREFIX="$(npm_prefix_override)"
|
|
218
|
+
if [[ -n "$HOST_PREFIX" ]]; then
|
|
219
|
+
echo " (prefix $HOST_PREFIX, where this helper is installed)"
|
|
220
|
+
run_cmd npm uninstall -g --prefix "$HOST_PREFIX" "$PI_HOST_PACKAGE"
|
|
221
|
+
else
|
|
222
|
+
run_cmd npm uninstall -g "$PI_HOST_PACKAGE"
|
|
223
|
+
fi
|
|
178
224
|
else
|
|
179
225
|
echo "WARN: npm is not on PATH; cannot uninstall $PI_HOST_PACKAGE." >&2
|
|
180
226
|
fi
|
|
@@ -231,7 +277,12 @@ echo
|
|
|
231
277
|
if [[ "$APPLY" == true ]]; then
|
|
232
278
|
echo "Uninstall complete."
|
|
233
279
|
echo "The npm-global helper is separate and stays until you remove it:"
|
|
234
|
-
|
|
280
|
+
HELPER_PREFIX="$(npm_prefix_override)"
|
|
281
|
+
if [[ -n "$HELPER_PREFIX" ]]; then
|
|
282
|
+
echo " npm uninstall -g --prefix $HELPER_PREFIX $PLATFORM_PACKAGE"
|
|
283
|
+
else
|
|
284
|
+
echo " npm uninstall -g $PLATFORM_PACKAGE"
|
|
285
|
+
fi
|
|
235
286
|
else
|
|
236
287
|
echo "Dry run only. Nothing was removed. Re-run with --apply to perform it."
|
|
237
288
|
fi
|
|
@@ -213,8 +213,21 @@ function binForPrefix(prefix) {
|
|
|
213
213
|
return process.platform === "win32" ? prefix : path.join(prefix, "bin");
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// The npx cache is a `node_modules` tree like any other, so the documented
|
|
217
|
+
// bootstrap -- `npm exec -y --package @piagent/platform@X.Y.Z -- piagent-update`
|
|
218
|
+
// -- ran the helper from `~/.npm/_npx/<hash>/node_modules/@piagent/platform` and
|
|
219
|
+
// this function happily reported that hash directory as the prefix to install
|
|
220
|
+
// into. Host and helper then landed in a cache npm is free to prune, with a `bin`
|
|
221
|
+
// that is on nobody's PATH: the command reported success and the machine had
|
|
222
|
+
// nothing installed. A cache is never an install target, so it is refused here
|
|
223
|
+
// and the caller falls back to a real global prefix.
|
|
224
|
+
function isNpxCacheRoot(root) {
|
|
225
|
+
return root.split(path.sep).some((segment) => segment === "_npx" || segment === "_cacache");
|
|
226
|
+
}
|
|
227
|
+
|
|
216
228
|
function npmPrefixForInstalledHelper(root) {
|
|
217
229
|
if (path.basename(root) !== "platform") return undefined;
|
|
230
|
+
if (isNpxCacheRoot(root)) return undefined;
|
|
218
231
|
const scopeRoot = path.dirname(root);
|
|
219
232
|
if (path.basename(scopeRoot) !== "@piagent") return undefined;
|
|
220
233
|
const nodeModulesRoot = path.dirname(scopeRoot);
|
|
@@ -444,10 +457,29 @@ function main() {
|
|
|
444
457
|
console.log(` npm: using explicit global prefix ${npmTarget.prefix}`);
|
|
445
458
|
}
|
|
446
459
|
|
|
460
|
+
// The host version above was read before this prepend, so it described whatever
|
|
461
|
+
// `pi` the caller's PATH happened to resolve. Once the prefix moves -- a default
|
|
462
|
+
// global root that is not writable, an explicit `--npm-prefix`, or a helper
|
|
463
|
+
// already installed elsewhere -- every step below uses a different `pi`, and
|
|
464
|
+
// "(already current)" could be describing a binary this run never touches. The
|
|
465
|
+
// decision belongs to the `pi` that will actually be used, so it is taken again
|
|
466
|
+
// here rather than trusted from before the move.
|
|
467
|
+
let effectiveHost = currentHost;
|
|
468
|
+
let effectiveHostNeedsChange = hostNeedsChange;
|
|
469
|
+
if (npmTarget.bin) {
|
|
470
|
+
const activeHost = installedHostVersion();
|
|
471
|
+
if (activeHost !== currentHost) {
|
|
472
|
+
effectiveHost = activeHost;
|
|
473
|
+
effectiveHostNeedsChange = options.host && (options.force || activeHost !== targetHost);
|
|
474
|
+
console.log(` host: the active prefix resolves pi ${activeHost ?? "not installed"}, not ${currentHost ?? "not installed"};`
|
|
475
|
+
+ ` using ${effectiveHost ?? "not installed"} -> ${targetHost}${effectiveHostNeedsChange ? "" : " (already current)"}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
447
479
|
// Host first: piagent-install refuses to run against a host that does not match
|
|
448
480
|
// the version its package.json pins, so a helper installed ahead of the host
|
|
449
481
|
// would fail on its very next step.
|
|
450
|
-
if (
|
|
482
|
+
if (effectiveHostNeedsChange) {
|
|
451
483
|
const args = npmInstallArgs(`${HOST_PACKAGE}@${targetHost}`, npmTarget);
|
|
452
484
|
describeStep(options.dryRun, "npm", args);
|
|
453
485
|
if (!options.dryRun && !run("npm", args).ok) fail(`could not install ${HOST_PACKAGE}@${targetHost}`);
|