@mmerterden/multi-agent-pipeline 20.34.0 → 20.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -1
- package/README.md +7 -5
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +4 -4
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +16 -1
- package/install/templates/claude-hooks.json +23 -1
- package/manifest.json +338 -305
- package/package.json +5 -1
- package/pipeline/agents/bulk-reader.md +3 -0
- package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/office-on/SKILL.md +7 -3
- package/pipeline/commands/multi-agent/office-pair/SKILL.md +66 -0
- package/pipeline/commands/multi-agent/office-status/SKILL.md +3 -2
- package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/README.md +1 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/manifest.json +14 -1
- package/pipeline/contract/types/index.d.ts +24 -2
- package/pipeline/lib/json-file-lock.mjs +61 -15
- package/pipeline/lib/office-remote.mjs +228 -0
- package/pipeline/lib/qr-code.mjs +386 -0
- package/pipeline/lib/repo-hygiene.sh +1 -0
- package/pipeline/lib/run-completion.mjs +17 -0
- package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/features/office-remote-protocol.md +166 -0
- package/pipeline/multi-agent-refs/features/office-threat-model.md +1 -1
- package/pipeline/multi-agent-refs/features/scope-check.md +16 -1
- package/pipeline/multi-agent-refs/features/verify-by-test.md +10 -0
- package/pipeline/multi-agent-refs/help/en.md +1 -0
- package/pipeline/multi-agent-refs/help/tr.md +1 -0
- package/pipeline/multi-agent-refs/keychain.md +7 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -2
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -6
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -12
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +9 -8
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -4
- package/pipeline/multi-agent-refs/rules.md +3 -2
- package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
- package/pipeline/office/NOTICE.md +4 -1
- package/pipeline/office/assets/char_12.png +0 -0
- package/pipeline/office/assets/char_13.png +0 -0
- package/pipeline/office/assets/char_14.png +0 -0
- package/pipeline/office/assets/char_15.png +0 -0
- package/pipeline/office/assets/char_16.png +0 -0
- package/pipeline/office/assets/char_17.png +0 -0
- package/pipeline/office/assets/char_18.png +0 -0
- package/pipeline/office/assets/char_19.png +0 -0
- package/pipeline/office/assets/char_20.png +0 -0
- package/pipeline/office/assets/char_21.png +0 -0
- package/pipeline/office/assets/char_22.png +0 -0
- package/pipeline/office/assets/char_23.png +0 -0
- package/pipeline/office/assets/char_24.png +0 -0
- package/pipeline/office/assets/char_25.png +0 -0
- package/pipeline/office/assets/char_26.png +0 -0
- package/pipeline/office/assets/char_27.png +0 -0
- package/pipeline/office/assets/char_28.png +0 -0
- package/pipeline/office/assets/char_29.png +0 -0
- package/pipeline/office/assets/char_30.png +0 -0
- package/pipeline/office/index.html +293 -49
- package/pipeline/schemas/agent-state.schema.json +18 -0
- package/pipeline/schemas/planning-output.schema.json +18 -0
- package/pipeline/schemas/pr-reach.schema.json +55 -0
- package/pipeline/schemas/prefs.schema.json +13 -7
- package/pipeline/schemas/triage-output.schema.json +12 -1
- package/pipeline/schemas/unattended-policy.json +2 -1
- package/pipeline/scripts/_guard_rules.py +449 -1
- package/pipeline/scripts/_test-gaming.mjs +36 -0
- package/pipeline/scripts/agent-guard.py +79 -19
- package/pipeline/scripts/anonymize-findings.mjs +99 -0
- package/pipeline/scripts/answer-check.mjs +113 -0
- package/pipeline/scripts/answer-check.sh +20 -0
- package/pipeline/scripts/autopilot-publish.mjs +9 -2
- package/pipeline/scripts/contract-server.mjs +35 -1
- package/pipeline/scripts/gate-ledger.mjs +144 -21
- package/pipeline/scripts/office-build.mjs +276 -5
- package/pipeline/scripts/office-control.mjs +70 -10
- package/pipeline/scripts/office-pair.mjs +438 -0
- package/pipeline/scripts/office-remote-client.mjs +463 -0
- package/pipeline/scripts/phase-tracker.sh +8 -0
- package/pipeline/scripts/phase0-exit-gate.mjs +40 -37
- package/pipeline/scripts/phone-devices.mjs +9 -1
- package/pipeline/scripts/pre-commit-check.sh +28 -2
- package/pipeline/scripts/pre-push-check.sh +6 -12
- package/pipeline/scripts/rework-base.mjs +87 -0
- package/pipeline/scripts/run-commit-check.mjs +104 -0
- package/pipeline/scripts/run-scope-guard.mjs +58 -3
- package/pipeline/scripts/run-scope-guard.sh +13 -1
- package/pipeline/scripts/scope-check-gate.mjs +70 -6
- package/pipeline/scripts/task-mirror.mjs +106 -15
- package/pipeline/scripts/task-mirror.sh +3 -2
- package/pipeline/scripts/test-stability-gate.mjs +112 -0
- package/pipeline/scripts/update-check.sh +7 -1
- package/pipeline/scripts/update-issue-progress.sh +73 -8
- package/pipeline/scripts/validate-planning.mjs +105 -1
- package/pipeline/scripts/validate-triage.mjs +42 -2
- package/pipeline/scripts/work-items.mjs +119 -1
- package/pipeline/scripts/worktree-prepare-all.mjs +200 -0
- package/pipeline/scripts/worktree-prepare.sh +40 -4
- package/pipeline/scripts/write-state.mjs +67 -24
- package/pipeline/skills/.skill-manifest.json +12 -8
- package/pipeline/skills/.skills-index.json +15 -4
- package/pipeline/skills/shared/README.md +4 -3
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +1 -1
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +6 -6
- package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +1 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-office-pair/SKILL.md +19 -0
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +14 -0
- package/pipeline/skills/shared/external/android-architecture/references/patterns.md +6 -0
- package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +10 -0
- package/pipeline/skills/shared/external/android-datastore/references/patterns.md +11 -0
- package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +8 -0
- package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +6 -0
- package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +10 -0
- package/pipeline/skills/shared/external/android-performance/references/patterns.md +13 -0
- package/pipeline/skills/shared/external/android-security/references/patterns.md +16 -0
- package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +9 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +12 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +13 -0
- package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +12 -0
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +9 -0
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +8 -0
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +10 -0
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +21 -0
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +9 -0
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +7 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -2
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +10 -0
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +10 -1
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +22 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +17 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +22 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +17 -0
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +9 -0
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +11 -0
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +13 -0
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +9 -0
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +10 -0
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +15 -0
- package/pipeline/skills/shared/external/compose-components/references/patterns.md +7 -0
- package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +7 -0
- package/pipeline/skills/shared/external/compose-testing/references/patterns.md +15 -0
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +9 -0
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +11 -0
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +10 -0
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +13 -0
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +20 -0
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +16 -0
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +16 -0
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +14 -0
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +13 -0
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +10 -0
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +10 -0
- package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +10 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +17 -0
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +12 -0
- package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +10 -0
- package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +10 -0
- package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +11 -0
- package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +13 -0
- package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +13 -0
- package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +11 -0
- package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +12 -0
- package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +16 -0
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +9 -0
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +18 -0
- package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +9 -0
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +9 -0
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +7 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +13 -0
- package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +9 -0
- package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +15 -0
- package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +11 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +11 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +15 -0
- package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +11 -0
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +18 -0
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +19 -0
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +15 -0
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +7 -0
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +8 -0
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +12 -0
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +17 -0
- package/pipeline/skills/shared/external/ios-security/references/app-review-guidelines.md +8 -0
- package/pipeline/skills/shared/external/ios-security/references/cryptokit-advanced.md +14 -0
- package/pipeline/skills/shared/external/ios-security/references/file-storage-patterns.md +8 -0
- package/pipeline/skills/shared/external/ios-security/references/privacy-manifest.md +9 -0
- package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +9 -0
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +13 -0
- package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +8 -0
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +21 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +9 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +12 -0
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +16 -0
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +21 -0
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +10 -0
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +9 -0
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +12 -0
- package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +11 -0
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +11 -0
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +18 -0
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +11 -0
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +11 -0
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +15 -0
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +14 -0
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +14 -0
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +14 -0
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +14 -0
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +10 -0
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +12 -0
- package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +14 -0
- package/pipeline/skills/shared/external/room-database/references/patterns.md +16 -0
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +10 -0
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +10 -0
- package/pipeline/skills/shared/external/spm-build-analysis/references/build-optimization-sources.md +14 -0
- package/pipeline/skills/shared/external/spm-build-analysis/references/spm-analysis-checks.md +19 -0
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +13 -0
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +31 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +8 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +9 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +10 -0
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +8 -0
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +24 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +8 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +14 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +12 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +10 -0
- package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +8 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/actors.md +10 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/cancellation.md +7 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/interop.md +10 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/new-features.md +11 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/structured.md +9 -0
- package/pipeline/skills/shared/external/swift-concurrency-pro/references/testing.md +13 -0
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +7 -0
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +14 -0
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +16 -0
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +17 -0
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +19 -0
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +16 -0
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +19 -0
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +18 -0
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +16 -0
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +17 -0
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +17 -0
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +17 -0
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +18 -0
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +21 -0
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +16 -0
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +16 -0
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +9 -0
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +24 -0
- package/pipeline/skills/shared/external/swift-testing-pro/references/async-tests.md +9 -0
- package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +11 -0
- package/pipeline/skills/shared/external/swift-testing-pro/references/writing-better-tests.md +13 -0
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +8 -0
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +17 -0
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +12 -0
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +11 -0
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +7 -0
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +13 -0
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +24 -0
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +12 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +15 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +15 -0
- package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/accessibility-patterns.md +11 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +7 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +13 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +9 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts-accessibility.md +9 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts.md +17 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/image-optimization.md +9 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/latest-apis.md +10 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/layout-best-practices.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/list-patterns.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-scenes.md +13 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-views.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-window-styling.md +12 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/performance-patterns.md +7 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/scroll-patterns.md +9 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/sheet-navigation-patterns.md +10 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/state-management.md +18 -0
- package/pipeline/skills/shared/external/swiftui-expert-skill/references/view-structure.md +16 -0
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +11 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +8 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +14 -0
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +9 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +22 -0
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +14 -0
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +10 -0
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +10 -0
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +13 -0
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +10 -0
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +14 -0
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +30 -0
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +16 -0
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +11 -0
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +11 -0
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +10 -0
- package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +8 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +12 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +10 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +11 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +12 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +14 -0
- package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +10 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +10 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +13 -0
- package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +12 -0
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +8 -0
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +8 -0
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +14 -0
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +17 -0
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +11 -0
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +11 -0
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +27 -0
- package/pipeline/skills/shared/external/xcode-build-fixer/references/build-settings-best-practices.md +9 -0
- package/pipeline/skills/shared/external/xcode-build-fixer/references/fix-patterns.md +7 -0
- package/pipeline/skills/shared/external/xcode-build-orchestrator/references/build-settings-best-practices.md +9 -0
- package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/build-optimization-sources.md +14 -0
- package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/code-compilation-checks.md +8 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-optimization-sources.md +14 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-settings-best-practices.md +9 -0
- package/pipeline/skills/shared/external/xcode-project-analyzer/references/project-audit-checks.md +13 -0
- package/pipeline/skills/skills-index.md +2 -1
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file office-pair.mjs - pair a phone with this machine for the office
|
|
4
|
+
* remote layer (features/office-remote-protocol.md, Pairing).
|
|
5
|
+
*
|
|
6
|
+
* start [--relay <url>] [--terminal] register a pairing with the relay and
|
|
7
|
+
* show its QR (on this terminal when it is one)
|
|
8
|
+
* code on this machine's screen
|
|
9
|
+
* wait wait for the phone's pairing message, open it and
|
|
10
|
+
* print the comparison code
|
|
11
|
+
* confirm <code> enrol the device once the code the phone shows is
|
|
12
|
+
* typed back here, and send the phone its device id
|
|
13
|
+
* in the first sealed machine-to-phone frame
|
|
14
|
+
* cancel drop the pending pairing
|
|
15
|
+
*
|
|
16
|
+
* What stays on this machine: the machine private key and the relay token
|
|
17
|
+
* live in <phone dir>/remote.json (0600); the relay only gets the SHA-256 of
|
|
18
|
+
* the token. The one-time secret and the machine public key travel in the QR
|
|
19
|
+
* link's fragment, which a browser never sends to a server, so the relay can
|
|
20
|
+
* neither open the phone's pairing message nor swap the machine key. The QR
|
|
21
|
+
* page is a 0600 file in a 0700 directory opened in the local browser; the
|
|
22
|
+
* secret is never printed. Enrolment goes through `phone-devices.mjs add`
|
|
23
|
+
* with scopes read,answer, so `launch` stays a separate local switch.
|
|
24
|
+
*
|
|
25
|
+
* Every subcommand refuses an unattended run (unattended-policy.json).
|
|
26
|
+
* Exit: 0 done, 1 refused or failed, 2 usage, 3 unattended.
|
|
27
|
+
*
|
|
28
|
+
* @module pipeline/scripts/office-pair
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { spawnSync } from "node:child_process";
|
|
32
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
33
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
34
|
+
import { homedir } from "node:os";
|
|
35
|
+
import { dirname, join } from "node:path";
|
|
36
|
+
import { fileURLToPath } from "node:url";
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
comparisonCode,
|
|
40
|
+
createMachineKeys,
|
|
41
|
+
directionKeys,
|
|
42
|
+
openPairing,
|
|
43
|
+
sealFrame,
|
|
44
|
+
} from "../lib/office-remote.mjs";
|
|
45
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
46
|
+
import { encodeQr, qrSvg, qrText } from "../lib/qr-code.mjs";
|
|
47
|
+
import { isUnattended } from "../lib/unattended.mjs";
|
|
48
|
+
import { phoneDir } from "./_phone-auth.mjs";
|
|
49
|
+
import { readConfig, writeConfig } from "./office-remote-client.mjs";
|
|
50
|
+
|
|
51
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
52
|
+
export const PAIRING_TTL_MS = 10 * 60_000;
|
|
53
|
+
const NAME = /^[\p{L}\p{N} ._'-]{1,64}$/u;
|
|
54
|
+
const B64U = /^[A-Za-z0-9_-]+$/;
|
|
55
|
+
|
|
56
|
+
const home = (env) => env.HOME || homedir();
|
|
57
|
+
export const pendingPath = (env = process.env) => join(phoneDir(env), "pair-pending.json");
|
|
58
|
+
const pageDir = (env) => join(home(env), ".claude", "logs", "multi-agent", "office");
|
|
59
|
+
const pagePath = (env) => join(pageDir(env), "pair.html");
|
|
60
|
+
const b64u = (b) => Buffer.from(b).toString("base64url");
|
|
61
|
+
const sha256hex = (s) => createHash("sha256").update(s).digest("hex");
|
|
62
|
+
|
|
63
|
+
/** First 16 hex digits of SHA-256 over a public key, in groups of four (the phone shows the same). */
|
|
64
|
+
export const keyFingerprint = (publicKey) =>
|
|
65
|
+
createHash("sha256")
|
|
66
|
+
.update(Buffer.from(publicKey, "base64url"))
|
|
67
|
+
.digest("hex")
|
|
68
|
+
.slice(0, 16)
|
|
69
|
+
.replace(/(.{4})(?=.)/g, "$1 ");
|
|
70
|
+
|
|
71
|
+
class Refusal extends Error {}
|
|
72
|
+
|
|
73
|
+
function readJson(p) {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function writePrivate(file, text) {
|
|
82
|
+
mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
|
|
83
|
+
chmodSync(dirname(file), 0o700);
|
|
84
|
+
writeFileSync(file, text, { mode: 0o600 });
|
|
85
|
+
chmodSync(file, 0o600);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A relay URL: https, or http on the loopback; no credentials, query or fragment. */
|
|
89
|
+
export function relayOrigin(raw) {
|
|
90
|
+
let u;
|
|
91
|
+
try {
|
|
92
|
+
u = new URL(String(raw));
|
|
93
|
+
} catch {
|
|
94
|
+
throw new Refusal("the relay URL is not a URL");
|
|
95
|
+
}
|
|
96
|
+
const loopback = ["127.0.0.1", "localhost", "[::1]"].includes(u.hostname);
|
|
97
|
+
if (u.protocol !== "https:" && !(u.protocol === "http:" && loopback)) {
|
|
98
|
+
throw new Refusal("the relay URL must be https");
|
|
99
|
+
}
|
|
100
|
+
if (u.username || u.password || u.search || u.hash || (u.pathname && u.pathname !== "/")) {
|
|
101
|
+
throw new Refusal("the relay URL is an origin only (scheme, host, port)");
|
|
102
|
+
}
|
|
103
|
+
return u.origin;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function relayFromPrefs(env) {
|
|
107
|
+
const prefs = readJson(join(home(env), ".claude", "multi-agent-preferences.json"));
|
|
108
|
+
return prefs?.global?.office?.remote?.relayUrl ?? null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The link the QR code carries; everything after `#` stays on the phone. */
|
|
112
|
+
export function pairingLink({ relayUrl, pairingId, secret, machinePublic }) {
|
|
113
|
+
return `${relayUrl}/office/pair#p=${pairingId}&s=${secret}&k=${machinePublic}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function pageHtml(svg) {
|
|
117
|
+
return (
|
|
118
|
+
"<!doctype html><html><head><meta charset=utf-8>" +
|
|
119
|
+
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'">` +
|
|
120
|
+
"<title>Office pairing</title></head><body>" +
|
|
121
|
+
"<p>Scan with the phone, signed in to your site. The code expires in 10 minutes.</p>" +
|
|
122
|
+
`${svg}</body></html>`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function openPage(file, env) {
|
|
127
|
+
const bin = env.MA_OFFICE_OPENER || "open";
|
|
128
|
+
const r = spawnSync(bin, [`file://${file}`], { stdio: "ignore", timeout: 15000 });
|
|
129
|
+
return r.status === 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function relayCall(fetchImpl, url, init) {
|
|
133
|
+
const res = await fetchImpl(url, { ...init, signal: AbortSignal.timeout(30_000) });
|
|
134
|
+
const text = await res.text();
|
|
135
|
+
let body;
|
|
136
|
+
try {
|
|
137
|
+
body = text ? JSON.parse(text) : null;
|
|
138
|
+
} catch {
|
|
139
|
+
body = null;
|
|
140
|
+
}
|
|
141
|
+
return { status: res.status, ok: res.ok, body };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Register a pairing with the relay and show its QR code.
|
|
146
|
+
*
|
|
147
|
+
* With `terminal`, the QR is also drawn on that stream, but only when it is a
|
|
148
|
+
* TTY: captured output (an agent's tool call, a pipe, a log) would keep the
|
|
149
|
+
* one-time secret the code carries.
|
|
150
|
+
*
|
|
151
|
+
* @returns {Promise<{pairingId: string, expiresAt: string, page: string, opened: boolean,
|
|
152
|
+
* terminal?: boolean, terminalRefused?: string}>}
|
|
153
|
+
*/
|
|
154
|
+
export async function start({
|
|
155
|
+
relay,
|
|
156
|
+
env = process.env,
|
|
157
|
+
fetchImpl = fetch,
|
|
158
|
+
now = Date.now(),
|
|
159
|
+
terminal = null,
|
|
160
|
+
}) {
|
|
161
|
+
const configured = relay ?? relayFromPrefs(env);
|
|
162
|
+
if (!configured) {
|
|
163
|
+
throw new Refusal(
|
|
164
|
+
"no relay is set; add prefs global.office.remote.relayUrl or pass --relay <url>",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const relayUrl = relayOrigin(configured);
|
|
168
|
+
const existing = readConfig(env);
|
|
169
|
+
const machine =
|
|
170
|
+
existing?.machinePrivatePem && existing?.machinePublic
|
|
171
|
+
? { privatePem: existing.machinePrivatePem, publicKey: existing.machinePublic }
|
|
172
|
+
: createMachineKeys();
|
|
173
|
+
const relayToken =
|
|
174
|
+
existing?.relayToken && existing.relayUrl === relayUrl
|
|
175
|
+
? existing.relayToken
|
|
176
|
+
: b64u(randomBytes(32));
|
|
177
|
+
const pairingId = b64u(randomBytes(16));
|
|
178
|
+
const secret = b64u(randomBytes(16));
|
|
179
|
+
const expiresAt = new Date(now + PAIRING_TTL_MS).toISOString();
|
|
180
|
+
const r = await relayCall(fetchImpl, `${relayUrl}/api/office/relay/pairings`, {
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: { "content-type": "application/json" },
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
pairingId,
|
|
185
|
+
machinePublic: machine.publicKey,
|
|
186
|
+
tokenHash: sha256hex(relayToken),
|
|
187
|
+
expiresAt,
|
|
188
|
+
}),
|
|
189
|
+
});
|
|
190
|
+
if (!r.ok) throw new Refusal(`the relay refused the pairing (${r.status})`);
|
|
191
|
+
writeConfig(
|
|
192
|
+
{
|
|
193
|
+
...(existing ?? {}),
|
|
194
|
+
relayUrl,
|
|
195
|
+
relayToken,
|
|
196
|
+
pairingId: existing?.pairingId ?? null,
|
|
197
|
+
machinePrivatePem: machine.privatePem,
|
|
198
|
+
machinePublic: machine.publicKey,
|
|
199
|
+
devices: existing?.devices ?? {},
|
|
200
|
+
},
|
|
201
|
+
env,
|
|
202
|
+
);
|
|
203
|
+
writePrivate(
|
|
204
|
+
pendingPath(env),
|
|
205
|
+
JSON.stringify({ relayUrl, pairingId, secret, expiresAt, relayToken }) + "\n",
|
|
206
|
+
);
|
|
207
|
+
const link = pairingLink({ relayUrl, pairingId, secret, machinePublic: machine.publicKey });
|
|
208
|
+
const qr = encodeQr(link, { ecc: "M" });
|
|
209
|
+
writePrivate(pagePath(env), pageHtml(qrSvg(qr)));
|
|
210
|
+
const out = { pairingId, expiresAt, page: pagePath(env) };
|
|
211
|
+
if (terminal) {
|
|
212
|
+
if (terminal.isTTY) {
|
|
213
|
+
terminal.write(`${qrText(qr)}\n\nScan with the phone; the code expires in 10 minutes.\n`);
|
|
214
|
+
return { ...out, opened: false, terminal: true };
|
|
215
|
+
}
|
|
216
|
+
out.terminal = false;
|
|
217
|
+
out.terminalRefused = "stdout is not a terminal, so the QR stays in the page";
|
|
218
|
+
}
|
|
219
|
+
return { ...out, opened: openPage(pagePath(env), env) };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function pending(env, now) {
|
|
223
|
+
const p = readJson(pendingPath(env));
|
|
224
|
+
if (!p?.pairingId) throw new Refusal("no pairing is pending; run office-pair start");
|
|
225
|
+
if (Date.parse(p.expiresAt) <= now) {
|
|
226
|
+
dropPending(env);
|
|
227
|
+
throw new Refusal("the pairing expired; run office-pair start again");
|
|
228
|
+
}
|
|
229
|
+
return p;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function dropPending(env) {
|
|
233
|
+
rmSync(pendingPath(env), { force: true });
|
|
234
|
+
rmSync(pagePath(env), { force: true });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Check what the phone sent before anything of it is kept. */
|
|
238
|
+
export function checkPairingMessage(value, relayUrl) {
|
|
239
|
+
const v = value ?? {};
|
|
240
|
+
if (typeof v.deviceName !== "string" || !NAME.test(v.deviceName))
|
|
241
|
+
throw new Refusal("the device name is not 1 to 64 letters, digits, spaces or ._'-");
|
|
242
|
+
if (typeof v.devicePublicKey !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(v.devicePublicKey))
|
|
243
|
+
throw new Refusal("the device key is not a base64url Ed25519 public key");
|
|
244
|
+
const pk = v.passkey ?? {};
|
|
245
|
+
if (typeof pk.credential !== "string" || !B64U.test(pk.credential) || pk.credential.length > 400)
|
|
246
|
+
throw new Refusal("the passkey public key is missing");
|
|
247
|
+
if (typeof pk.credentialId !== "string" || !B64U.test(pk.credentialId))
|
|
248
|
+
throw new Refusal("the passkey credential id is missing");
|
|
249
|
+
if (pk.origin !== relayUrl) throw new Refusal("the passkey origin is not the relay's");
|
|
250
|
+
if (pk.rpId !== new URL(relayUrl).hostname)
|
|
251
|
+
throw new Refusal("the passkey rp id is not the relay's host");
|
|
252
|
+
return {
|
|
253
|
+
deviceName: v.deviceName,
|
|
254
|
+
devicePublicKey: v.devicePublicKey,
|
|
255
|
+
passkey: {
|
|
256
|
+
credential: pk.credential,
|
|
257
|
+
credentialId: pk.credentialId,
|
|
258
|
+
rpId: pk.rpId,
|
|
259
|
+
origin: pk.origin,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Wait for the phone's pairing message, open it and keep it with the pending
|
|
266
|
+
* pairing. Prints the comparison code, never a key.
|
|
267
|
+
*/
|
|
268
|
+
export async function wait({
|
|
269
|
+
env = process.env,
|
|
270
|
+
fetchImpl = fetch,
|
|
271
|
+
now = Date.now,
|
|
272
|
+
pollMs = 2000,
|
|
273
|
+
}) {
|
|
274
|
+
const p = pending(env, now());
|
|
275
|
+
const config = readConfig(env);
|
|
276
|
+
while (now() < Date.parse(p.expiresAt)) {
|
|
277
|
+
const r = await relayCall(
|
|
278
|
+
fetchImpl,
|
|
279
|
+
`${p.relayUrl}/api/office/relay/pairings/${encodeURIComponent(p.pairingId)}`,
|
|
280
|
+
{ headers: { Authorization: `Bearer ${p.relayToken}` } },
|
|
281
|
+
);
|
|
282
|
+
if (r.ok && r.body?.envelope) {
|
|
283
|
+
const { sessionPublic, sealed } = r.body.envelope;
|
|
284
|
+
if (typeof sessionPublic !== "string" || !B64U.test(sessionPublic))
|
|
285
|
+
throw new Refusal("the phone sent no session key");
|
|
286
|
+
const opened = openPairing({
|
|
287
|
+
privatePem: config.machinePrivatePem,
|
|
288
|
+
peerPublic: sessionPublic,
|
|
289
|
+
secret: p.secret,
|
|
290
|
+
pairingId: p.pairingId,
|
|
291
|
+
sealed,
|
|
292
|
+
});
|
|
293
|
+
if (!opened.ok) throw new Refusal("the pairing message did not open with this pairing");
|
|
294
|
+
const message = checkPairingMessage(opened.value, p.relayUrl);
|
|
295
|
+
writePrivate(pendingPath(env), JSON.stringify({ ...p, sessionPublic, message }) + "\n");
|
|
296
|
+
return {
|
|
297
|
+
deviceName: message.deviceName,
|
|
298
|
+
code: comparisonCode(config.machinePublic, sessionPublic),
|
|
299
|
+
machineKey: keyFingerprint(config.machinePublic),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (!r.ok && r.status !== 404) throw new Refusal(`the relay answered ${r.status}`);
|
|
303
|
+
await new Promise((res) => setTimeout(res, pollMs));
|
|
304
|
+
}
|
|
305
|
+
dropPending(env);
|
|
306
|
+
throw new Refusal("the pairing expired before the phone answered");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function enrol(message, env) {
|
|
310
|
+
const r = spawnSync(
|
|
311
|
+
process.execPath,
|
|
312
|
+
[
|
|
313
|
+
join(HERE, "phone-devices.mjs"),
|
|
314
|
+
"add",
|
|
315
|
+
"--name",
|
|
316
|
+
message.deviceName,
|
|
317
|
+
"--public-key",
|
|
318
|
+
message.devicePublicKey,
|
|
319
|
+
"--scopes",
|
|
320
|
+
"read,answer",
|
|
321
|
+
],
|
|
322
|
+
{ env, encoding: "utf8", timeout: 30_000 },
|
|
323
|
+
);
|
|
324
|
+
if (r.status !== 0) {
|
|
325
|
+
throw new Refusal(`phone-devices refused the device: ${(r.stderr || "").trim()}`);
|
|
326
|
+
}
|
|
327
|
+
return JSON.parse(r.stdout);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Enrol the device once the user typed the code the phone shows.
|
|
332
|
+
*
|
|
333
|
+
* @returns {Promise<{id: string, name: string, scopes: string[]}>}
|
|
334
|
+
*/
|
|
335
|
+
export async function confirm({ code, env = process.env, fetchImpl = fetch, now = Date.now() }) {
|
|
336
|
+
const p = pending(env, now);
|
|
337
|
+
if (!p.message || !p.sessionPublic) throw new Refusal("run office-pair wait first");
|
|
338
|
+
const config = readConfig(env);
|
|
339
|
+
const want = Buffer.from(comparisonCode(config.machinePublic, p.sessionPublic));
|
|
340
|
+
const got = Buffer.from(String(code ?? "").trim());
|
|
341
|
+
if (got.length !== want.length || !timingSafeEqual(got, want)) {
|
|
342
|
+
dropPending(env);
|
|
343
|
+
throw new Refusal("the code does not match; the pairing was dropped, start again");
|
|
344
|
+
}
|
|
345
|
+
const device = enrol(p.message, env);
|
|
346
|
+
const keys = directionKeys({
|
|
347
|
+
privatePem: config.machinePrivatePem,
|
|
348
|
+
peerPublic: p.sessionPublic,
|
|
349
|
+
pairingId: p.pairingId,
|
|
350
|
+
role: "machine",
|
|
351
|
+
});
|
|
352
|
+
const welcome = sealFrame(keys.send, 1, "machine-to-phone", {
|
|
353
|
+
deviceId: device.id,
|
|
354
|
+
scopes: device.scopes,
|
|
355
|
+
});
|
|
356
|
+
const r = await relayCall(
|
|
357
|
+
fetchImpl,
|
|
358
|
+
`${p.relayUrl}/api/office/relay/pairings/${encodeURIComponent(p.pairingId)}/confirm`,
|
|
359
|
+
{
|
|
360
|
+
method: "POST",
|
|
361
|
+
headers: { Authorization: `Bearer ${p.relayToken}`, "content-type": "application/json" },
|
|
362
|
+
body: JSON.stringify({ welcome }),
|
|
363
|
+
},
|
|
364
|
+
);
|
|
365
|
+
if (!r.ok) {
|
|
366
|
+
spawnSync(process.execPath, [join(HERE, "phone-devices.mjs"), "revoke", device.id], {
|
|
367
|
+
env,
|
|
368
|
+
stdio: "ignore",
|
|
369
|
+
timeout: 30_000,
|
|
370
|
+
});
|
|
371
|
+
dropPending(env);
|
|
372
|
+
throw new Refusal(
|
|
373
|
+
`the relay refused the confirmation (${r.status}); the device was revoked again, start over`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
writeConfig(
|
|
377
|
+
{
|
|
378
|
+
...config,
|
|
379
|
+
pairingId: p.pairingId,
|
|
380
|
+
devices: {
|
|
381
|
+
[device.id]: {
|
|
382
|
+
id: device.id,
|
|
383
|
+
sessionPublic: p.sessionPublic,
|
|
384
|
+
passkey: { ...p.message.passkey, signCount: 0 },
|
|
385
|
+
recvCounter: 0,
|
|
386
|
+
sendCounter: 1,
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
env,
|
|
391
|
+
);
|
|
392
|
+
dropPending(env);
|
|
393
|
+
return { id: device.id, name: device.name, scopes: device.scopes };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Drop the pending pairing and its QR page. */
|
|
397
|
+
export function cancel({ env = process.env } = {}) {
|
|
398
|
+
const had = existsSync(pendingPath(env));
|
|
399
|
+
dropPending(env);
|
|
400
|
+
return { cancelled: had };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function main(argv) {
|
|
404
|
+
const [cmd, ...rest] = argv;
|
|
405
|
+
if (!["start", "wait", "confirm", "cancel"].includes(cmd)) {
|
|
406
|
+
process.stderr.write(
|
|
407
|
+
"usage: office-pair.mjs start [--relay <url>] [--terminal] | wait | confirm <code> | cancel\n",
|
|
408
|
+
);
|
|
409
|
+
return 2;
|
|
410
|
+
}
|
|
411
|
+
if (isUnattended()) {
|
|
412
|
+
process.stderr.write("office-pair: an unattended run may not pair a device\n");
|
|
413
|
+
return 3;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
let out;
|
|
417
|
+
if (cmd === "start") {
|
|
418
|
+
const i = rest.indexOf("--relay");
|
|
419
|
+
out = await start({
|
|
420
|
+
relay: i === -1 ? undefined : rest[i + 1],
|
|
421
|
+
terminal: rest.includes("--terminal") ? process.stdout : null,
|
|
422
|
+
});
|
|
423
|
+
} else if (cmd === "wait") out = await wait({});
|
|
424
|
+
else if (cmd === "confirm") out = await confirm({ code: rest[0] });
|
|
425
|
+
else out = cancel();
|
|
426
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
427
|
+
return 0;
|
|
428
|
+
} catch (err) {
|
|
429
|
+
process.stderr.write(`office-pair: ${err.message}\n`);
|
|
430
|
+
return 1;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (invokedDirectly(import.meta.url)) {
|
|
435
|
+
main(process.argv.slice(2)).then((code) => {
|
|
436
|
+
process.exitCode = code;
|
|
437
|
+
});
|
|
438
|
+
}
|