@mmerterden/multi-agent-pipeline 20.34.0 → 20.35.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.
Files changed (321) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README.md +1 -0
  3. package/docs/facts.json +2 -2
  4. package/install/catalog-history.json +1 -1
  5. package/install/claude.mjs +16 -1
  6. package/install/templates/claude-hooks.json +23 -1
  7. package/manifest.json +324 -293
  8. package/package.json +1 -1
  9. package/pipeline/agents/bulk-reader.md +3 -0
  10. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
  11. package/pipeline/commands/multi-agent/office-on/SKILL.md +7 -3
  12. package/pipeline/commands/multi-agent/office-status/SKILL.md +3 -2
  13. package/pipeline/contract/CHANGELOG.md +9 -0
  14. package/pipeline/contract/README.md +1 -0
  15. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  16. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  17. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  18. package/pipeline/contract/manifest.json +14 -1
  19. package/pipeline/contract/types/index.d.ts +24 -2
  20. package/pipeline/lib/json-file-lock.mjs +61 -15
  21. package/pipeline/lib/office-remote.mjs +228 -0
  22. package/pipeline/lib/qr-code.mjs +360 -0
  23. package/pipeline/lib/repo-hygiene.sh +1 -0
  24. package/pipeline/lib/run-completion.mjs +17 -0
  25. package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
  26. package/pipeline/multi-agent-refs/features/office-remote-protocol.md +166 -0
  27. package/pipeline/multi-agent-refs/features/office-threat-model.md +1 -1
  28. package/pipeline/multi-agent-refs/features/scope-check.md +16 -1
  29. package/pipeline/multi-agent-refs/features/verify-by-test.md +10 -0
  30. package/pipeline/multi-agent-refs/keychain.md +7 -0
  31. package/pipeline/multi-agent-refs/phases/phase-0-init.md +6 -2
  32. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -6
  33. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +9 -12
  34. package/pipeline/multi-agent-refs/phases/phase-3-review.md +9 -8
  35. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +1 -4
  36. package/pipeline/multi-agent-refs/rules.md +3 -2
  37. package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
  38. package/pipeline/office/NOTICE.md +4 -1
  39. package/pipeline/office/assets/char_12.png +0 -0
  40. package/pipeline/office/assets/char_13.png +0 -0
  41. package/pipeline/office/assets/char_14.png +0 -0
  42. package/pipeline/office/assets/char_15.png +0 -0
  43. package/pipeline/office/assets/char_16.png +0 -0
  44. package/pipeline/office/assets/char_17.png +0 -0
  45. package/pipeline/office/assets/char_18.png +0 -0
  46. package/pipeline/office/assets/char_19.png +0 -0
  47. package/pipeline/office/assets/char_20.png +0 -0
  48. package/pipeline/office/assets/char_21.png +0 -0
  49. package/pipeline/office/assets/char_22.png +0 -0
  50. package/pipeline/office/assets/char_23.png +0 -0
  51. package/pipeline/office/assets/char_24.png +0 -0
  52. package/pipeline/office/assets/char_25.png +0 -0
  53. package/pipeline/office/assets/char_26.png +0 -0
  54. package/pipeline/office/assets/char_27.png +0 -0
  55. package/pipeline/office/assets/char_28.png +0 -0
  56. package/pipeline/office/assets/char_29.png +0 -0
  57. package/pipeline/office/assets/char_30.png +0 -0
  58. package/pipeline/office/index.html +293 -49
  59. package/pipeline/schemas/agent-state.schema.json +18 -0
  60. package/pipeline/schemas/planning-output.schema.json +18 -0
  61. package/pipeline/schemas/pr-reach.schema.json +55 -0
  62. package/pipeline/schemas/prefs.schema.json +13 -7
  63. package/pipeline/schemas/triage-output.schema.json +12 -1
  64. package/pipeline/schemas/unattended-policy.json +2 -1
  65. package/pipeline/scripts/_guard_rules.py +449 -1
  66. package/pipeline/scripts/_test-gaming.mjs +36 -0
  67. package/pipeline/scripts/agent-guard.py +79 -19
  68. package/pipeline/scripts/anonymize-findings.mjs +99 -0
  69. package/pipeline/scripts/answer-check.mjs +113 -0
  70. package/pipeline/scripts/answer-check.sh +20 -0
  71. package/pipeline/scripts/autopilot-publish.mjs +9 -2
  72. package/pipeline/scripts/contract-server.mjs +35 -1
  73. package/pipeline/scripts/gate-ledger.mjs +144 -21
  74. package/pipeline/scripts/office-build.mjs +276 -5
  75. package/pipeline/scripts/office-control.mjs +70 -10
  76. package/pipeline/scripts/office-pair.mjs +407 -0
  77. package/pipeline/scripts/office-remote-client.mjs +463 -0
  78. package/pipeline/scripts/phase-tracker.sh +8 -0
  79. package/pipeline/scripts/phase0-exit-gate.mjs +40 -37
  80. package/pipeline/scripts/phone-devices.mjs +9 -1
  81. package/pipeline/scripts/pre-commit-check.sh +28 -2
  82. package/pipeline/scripts/pre-push-check.sh +6 -12
  83. package/pipeline/scripts/rework-base.mjs +87 -0
  84. package/pipeline/scripts/run-commit-check.mjs +104 -0
  85. package/pipeline/scripts/run-scope-guard.mjs +58 -3
  86. package/pipeline/scripts/run-scope-guard.sh +13 -1
  87. package/pipeline/scripts/scope-check-gate.mjs +70 -6
  88. package/pipeline/scripts/task-mirror.mjs +106 -15
  89. package/pipeline/scripts/task-mirror.sh +3 -2
  90. package/pipeline/scripts/test-stability-gate.mjs +112 -0
  91. package/pipeline/scripts/update-check.sh +7 -1
  92. package/pipeline/scripts/update-issue-progress.sh +73 -8
  93. package/pipeline/scripts/validate-planning.mjs +105 -1
  94. package/pipeline/scripts/validate-triage.mjs +42 -2
  95. package/pipeline/scripts/work-items.mjs +119 -1
  96. package/pipeline/scripts/worktree-prepare-all.mjs +200 -0
  97. package/pipeline/scripts/worktree-prepare.sh +40 -4
  98. package/pipeline/scripts/write-state.mjs +67 -24
  99. package/pipeline/skills/.skill-manifest.json +5 -5
  100. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +5 -5
  101. package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +1 -1
  102. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
  103. package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +2 -2
  104. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +14 -0
  105. package/pipeline/skills/shared/external/android-architecture/references/patterns.md +6 -0
  106. package/pipeline/skills/shared/external/android-build-quality-gates/references/patterns.md +10 -0
  107. package/pipeline/skills/shared/external/android-datastore/references/patterns.md +11 -0
  108. package/pipeline/skills/shared/external/android-design-tokens-codegen/references/patterns.md +8 -0
  109. package/pipeline/skills/shared/external/android-jetpack-compose-expert/references/patterns.md +6 -0
  110. package/pipeline/skills/shared/external/android-mvi-viewmodel/references/patterns.md +10 -0
  111. package/pipeline/skills/shared/external/android-performance/references/patterns.md +13 -0
  112. package/pipeline/skills/shared/external/android-security/references/patterns.md +16 -0
  113. package/pipeline/skills/shared/external/api-patterns/references/contract-details.md +9 -0
  114. package/pipeline/skills/shared/external/api-security-best-practices/references/abuse-controls.md +12 -0
  115. package/pipeline/skills/shared/external/api-security-best-practices/references/identity-and-access.md +13 -0
  116. package/pipeline/skills/shared/external/api-security-best-practices/references/request-handling.md +12 -0
  117. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +9 -0
  118. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +8 -0
  119. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +10 -0
  120. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +21 -0
  121. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +9 -0
  122. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +7 -0
  123. package/pipeline/skills/shared/external/app-store-review/SKILL.md +2 -2
  124. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +10 -0
  125. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +10 -1
  126. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +22 -0
  127. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +17 -0
  128. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +22 -0
  129. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +17 -0
  130. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +9 -0
  131. package/pipeline/skills/shared/external/authentication/references/passkeys.md +11 -0
  132. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +13 -0
  133. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +9 -0
  134. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +10 -0
  135. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +15 -0
  136. package/pipeline/skills/shared/external/compose-components/references/patterns.md +7 -0
  137. package/pipeline/skills/shared/external/compose-navigation/references/patterns.md +7 -0
  138. package/pipeline/skills/shared/external/compose-testing/references/patterns.md +15 -0
  139. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +9 -0
  140. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +11 -0
  141. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +10 -0
  142. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +13 -0
  143. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +20 -0
  144. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +16 -0
  145. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +16 -0
  146. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +14 -0
  147. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +13 -0
  148. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +10 -0
  149. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +10 -0
  150. package/pipeline/skills/shared/external/fastapi-pro/references/app-structure.md +10 -0
  151. package/pipeline/skills/shared/external/gradle-kotlin-dsl/references/patterns.md +17 -0
  152. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +12 -0
  153. package/pipeline/skills/shared/external/hig-app-icons/references/icon-workflow.md +10 -0
  154. package/pipeline/skills/shared/external/hig-components-content/references/content-views.md +10 -0
  155. package/pipeline/skills/shared/external/hig-components-layout/references/bars-and-tabs.md +11 -0
  156. package/pipeline/skills/shared/external/hig-foundations/references/foundations-detail.md +13 -0
  157. package/pipeline/skills/shared/external/hig-foundations/references/type-color-spacing.md +13 -0
  158. package/pipeline/skills/shared/external/hig-patterns/references/onboarding-and-states.md +11 -0
  159. package/pipeline/skills/shared/external/hig-platforms/references/ipad-layout.md +12 -0
  160. package/pipeline/skills/shared/external/hig-technologies/references/technologies.md +16 -0
  161. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +9 -0
  162. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +18 -0
  163. package/pipeline/skills/shared/external/ios-accessibility/references/large-content-and-appearance.md +9 -0
  164. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +9 -0
  165. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +7 -0
  166. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +13 -0
  167. package/pipeline/skills/shared/external/ios-design-review/references/appearance-matrix.md +9 -0
  168. package/pipeline/skills/shared/external/ios-design-review/references/checklist.md +15 -0
  169. package/pipeline/skills/shared/external/ios-haptics/references/core-haptics.md +11 -0
  170. package/pipeline/skills/shared/external/ios-iphone-duo/references/api-map.md +11 -0
  171. package/pipeline/skills/shared/external/ios-iphone-duo/references/fixed-assumptions.md +15 -0
  172. package/pipeline/skills/shared/external/ios-iphone-duo/references/poses-and-panes.md +11 -0
  173. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +18 -0
  174. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +19 -0
  175. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +15 -0
  176. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +7 -0
  177. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +8 -0
  178. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +12 -0
  179. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +17 -0
  180. package/pipeline/skills/shared/external/ios-security/references/app-review-guidelines.md +8 -0
  181. package/pipeline/skills/shared/external/ios-security/references/cryptokit-advanced.md +14 -0
  182. package/pipeline/skills/shared/external/ios-security/references/file-storage-patterns.md +8 -0
  183. package/pipeline/skills/shared/external/ios-security/references/privacy-manifest.md +9 -0
  184. package/pipeline/skills/shared/external/ios-sf-symbols/references/effects-and-custom-symbols.md +9 -0
  185. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +13 -0
  186. package/pipeline/skills/shared/external/kotlin-coroutines-expert/references/patterns.md +8 -0
  187. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +21 -0
  188. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +9 -0
  189. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +12 -0
  190. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +16 -0
  191. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +21 -0
  192. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +10 -0
  193. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +9 -0
  194. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +12 -0
  195. package/pipeline/skills/shared/external/nodejs-backend-patterns/references/runtime-patterns.md +11 -0
  196. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +11 -0
  197. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +18 -0
  198. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +11 -0
  199. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +11 -0
  200. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +15 -0
  201. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +14 -0
  202. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +14 -0
  203. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +14 -0
  204. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +14 -0
  205. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +10 -0
  206. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +12 -0
  207. package/pipeline/skills/shared/external/retrofit-networking/references/patterns.md +14 -0
  208. package/pipeline/skills/shared/external/room-database/references/patterns.md +16 -0
  209. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +10 -0
  210. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +10 -0
  211. package/pipeline/skills/shared/external/spm-build-analysis/references/build-optimization-sources.md +14 -0
  212. package/pipeline/skills/shared/external/spm-build-analysis/references/spm-analysis-checks.md +19 -0
  213. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +13 -0
  214. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +31 -0
  215. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +8 -0
  216. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +9 -0
  217. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +10 -0
  218. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +8 -0
  219. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +24 -0
  220. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +8 -0
  221. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +14 -0
  222. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +12 -0
  223. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +10 -0
  224. package/pipeline/skills/shared/external/swift-concurrency-expert/references/swift-6-2-concurrency.md +8 -0
  225. package/pipeline/skills/shared/external/swift-concurrency-pro/references/actors.md +10 -0
  226. package/pipeline/skills/shared/external/swift-concurrency-pro/references/cancellation.md +7 -0
  227. package/pipeline/skills/shared/external/swift-concurrency-pro/references/interop.md +10 -0
  228. package/pipeline/skills/shared/external/swift-concurrency-pro/references/new-features.md +11 -0
  229. package/pipeline/skills/shared/external/swift-concurrency-pro/references/structured.md +9 -0
  230. package/pipeline/skills/shared/external/swift-concurrency-pro/references/testing.md +13 -0
  231. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +7 -0
  232. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +14 -0
  233. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +16 -0
  234. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +17 -0
  235. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +19 -0
  236. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +16 -0
  237. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +19 -0
  238. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +18 -0
  239. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +16 -0
  240. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +17 -0
  241. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +17 -0
  242. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +17 -0
  243. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +18 -0
  244. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +21 -0
  245. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +16 -0
  246. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +16 -0
  247. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +9 -0
  248. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +24 -0
  249. package/pipeline/skills/shared/external/swift-testing-pro/references/async-tests.md +9 -0
  250. package/pipeline/skills/shared/external/swift-testing-pro/references/new-features.md +11 -0
  251. package/pipeline/skills/shared/external/swift-testing-pro/references/writing-better-tests.md +13 -0
  252. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +8 -0
  253. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +17 -0
  254. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +12 -0
  255. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +11 -0
  256. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +7 -0
  257. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +13 -0
  258. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +24 -0
  259. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +12 -0
  260. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +15 -0
  261. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +15 -0
  262. package/pipeline/skills/shared/external/swiftui-animation/references/scroll-effects.md +12 -0
  263. package/pipeline/skills/shared/external/swiftui-expert-skill/references/accessibility-patterns.md +11 -0
  264. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-advanced.md +7 -0
  265. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-basics.md +13 -0
  266. package/pipeline/skills/shared/external/swiftui-expert-skill/references/animation-transitions.md +9 -0
  267. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts-accessibility.md +9 -0
  268. package/pipeline/skills/shared/external/swiftui-expert-skill/references/charts.md +17 -0
  269. package/pipeline/skills/shared/external/swiftui-expert-skill/references/image-optimization.md +9 -0
  270. package/pipeline/skills/shared/external/swiftui-expert-skill/references/latest-apis.md +10 -0
  271. package/pipeline/skills/shared/external/swiftui-expert-skill/references/layout-best-practices.md +12 -0
  272. package/pipeline/skills/shared/external/swiftui-expert-skill/references/list-patterns.md +12 -0
  273. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-scenes.md +13 -0
  274. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-views.md +12 -0
  275. package/pipeline/skills/shared/external/swiftui-expert-skill/references/macos-window-styling.md +12 -0
  276. package/pipeline/skills/shared/external/swiftui-expert-skill/references/performance-patterns.md +7 -0
  277. package/pipeline/skills/shared/external/swiftui-expert-skill/references/scroll-patterns.md +9 -0
  278. package/pipeline/skills/shared/external/swiftui-expert-skill/references/sheet-navigation-patterns.md +10 -0
  279. package/pipeline/skills/shared/external/swiftui-expert-skill/references/state-management.md +18 -0
  280. package/pipeline/skills/shared/external/swiftui-expert-skill/references/view-structure.md +16 -0
  281. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +11 -0
  282. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +8 -0
  283. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +14 -0
  284. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +9 -0
  285. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +22 -0
  286. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/uikit-glass.md +14 -0
  287. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +10 -0
  288. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +10 -0
  289. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +13 -0
  290. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +10 -0
  291. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +14 -0
  292. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +30 -0
  293. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +16 -0
  294. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +11 -0
  295. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +11 -0
  296. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +10 -0
  297. package/pipeline/skills/shared/external/swiftui-performance-audit/references/code-smells.md +8 -0
  298. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/app-wiring.md +12 -0
  299. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/menu-bar.md +10 -0
  300. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/navigationstack.md +11 -0
  301. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/scroll-reveal.md +12 -0
  302. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/sheets.md +14 -0
  303. package/pipeline/skills/shared/external/swiftui-ui-patterns/references/tabview.md +10 -0
  304. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +10 -0
  305. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +13 -0
  306. package/pipeline/skills/shared/external/swiftui-view-refactor/references/mv-patterns.md +12 -0
  307. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +8 -0
  308. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +8 -0
  309. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +14 -0
  310. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +17 -0
  311. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +11 -0
  312. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +11 -0
  313. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +27 -0
  314. package/pipeline/skills/shared/external/xcode-build-fixer/references/build-settings-best-practices.md +9 -0
  315. package/pipeline/skills/shared/external/xcode-build-fixer/references/fix-patterns.md +7 -0
  316. package/pipeline/skills/shared/external/xcode-build-orchestrator/references/build-settings-best-practices.md +9 -0
  317. package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/build-optimization-sources.md +14 -0
  318. package/pipeline/skills/shared/external/xcode-compilation-analyzer/references/code-compilation-checks.md +8 -0
  319. package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-optimization-sources.md +14 -0
  320. package/pipeline/skills/shared/external/xcode-project-analyzer/references/build-settings-best-practices.md +9 -0
  321. package/pipeline/skills/shared/external/xcode-project-analyzer/references/project-audit-checks.md +13 -0
@@ -9,9 +9,11 @@
9
9
  * pipeline/office/index.html: the list of sprite names in pipeline/office/assets
10
10
  * (served by contract-server.mjs at /office/assets/), the strings as a JSON
11
11
  * literal, the two code files in order inside one IIFE. It also renders the
12
- * pixel grids in pipeline/office-art into PNG sprites in pipeline/office/assets
13
- * and writes pipeline/office/toolkit.json, the toolkit version shipped with
14
- * the page; --check holds all three to their sources.
12
+ * pixel grids in pipeline/office-art into PNG sprites in pipeline/office/assets,
13
+ * recolors the character sheets into the company's employees
14
+ * (office-art/employees.json) and writes pipeline/office/toolkit.json, the
15
+ * toolkit version shipped with the page; --check holds all of them to their
16
+ * sources.
15
17
  *
16
18
  * node pipeline/scripts/office-build.mjs write the page
17
19
  * node pipeline/scripts/office-build.mjs --check exit 1 when the page on
@@ -21,7 +23,7 @@
21
23
 
22
24
  import { createHash } from "node:crypto";
23
25
  import { readFileSync, readdirSync, writeFileSync } from "node:fs";
24
- import { deflateSync } from "node:zlib";
26
+ import { deflateSync, inflateSync } from "node:zlib";
25
27
  import { basename, join } from "node:path";
26
28
  import { pathToFileURL } from "node:url";
27
29
 
@@ -78,6 +80,272 @@ export function encodePng(width, height, rgba) {
78
80
  ]);
79
81
  }
80
82
 
83
+ /**
84
+ * An 8-bit RGBA PNG decoded to {width, height, rgba}. Only that colour type is
85
+ * read (every sheet in office/assets is one); anything else throws.
86
+ */
87
+ export function decodePng(buf) {
88
+ if (buf.subarray(0, 8).toString("hex") !== PNG_SIGNATURE) throw new Error("not a PNG file");
89
+ let o = 8;
90
+ let width = 0;
91
+ let height = 0;
92
+ const idat = [];
93
+ while (o + 8 <= buf.length) {
94
+ const len = buf.readUInt32BE(o);
95
+ const type = buf.toString("ascii", o + 4, o + 8);
96
+ const data = buf.subarray(o + 8, o + 8 + len);
97
+ if (type === "IHDR") {
98
+ width = data.readUInt32BE(0);
99
+ height = data.readUInt32BE(4);
100
+ if (data[8] !== 8 || data[9] !== 6 || data[12] !== 0)
101
+ throw new Error("only 8-bit RGBA, non-interlaced PNGs are read");
102
+ } else if (type === "IDAT") idat.push(data);
103
+ else if (type === "IEND") break;
104
+ o += 12 + len;
105
+ }
106
+ if (!width || !height) throw new Error("PNG has no IHDR");
107
+ const raw = inflateSync(Buffer.concat(idat));
108
+ const stride = width * 4;
109
+ if (raw.length !== (stride + 1) * height) throw new Error("PNG data has the wrong length");
110
+ const rgba = Buffer.alloc(stride * height);
111
+ for (let y = 0; y < height; y++) {
112
+ const filter = raw[y * (stride + 1)];
113
+ const line = y * (stride + 1) + 1;
114
+ for (let x = 0; x < stride; x++) {
115
+ const a = x >= 4 ? rgba[y * stride + x - 4] : 0;
116
+ const b = y > 0 ? rgba[(y - 1) * stride + x] : 0;
117
+ const c = x >= 4 && y > 0 ? rgba[(y - 1) * stride + x - 4] : 0;
118
+ let v = raw[line + x];
119
+ if (filter === 1) v += a;
120
+ else if (filter === 2) v += b;
121
+ else if (filter === 3) v += (a + b) >> 1;
122
+ else if (filter === 4) {
123
+ const p = a + b - c;
124
+ const pa = Math.abs(p - a);
125
+ const pb = Math.abs(p - b);
126
+ const pc = Math.abs(p - c);
127
+ v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
128
+ } else if (filter !== 0) throw new Error(`unknown PNG filter ${filter}`);
129
+ rgba[y * stride + x] = v & 255;
130
+ }
131
+ }
132
+ return { width, height, rgba };
133
+ }
134
+
135
+ // --- the employees -----------------------------------------------------------
136
+ // office-art/employees.json gives each person of the company a base character
137
+ // sheet and four colours. A sheet's colours are sorted into parts by where
138
+ // they sit in the 16x32 frame (hair above the face, the shirt below it, then
139
+ // trousers and shoes) and skin by the face's tone; the near-black outline,
140
+ // white, the eyes and the shoes keep their colours. Each part moves to the
141
+ // person's colour with its shading kept.
142
+
143
+ const FRAME_H = 32;
144
+ const BANDS = [
145
+ ["hair", 0, 13],
146
+ ["face", 13, 18],
147
+ ["top", 18, 25],
148
+ ["bottom", 25, 28],
149
+ ["shoes", 28, 32],
150
+ ];
151
+ const PARTS = ["hair", "skin", "top", "bottom"];
152
+ const SKIN_NEAR = 48;
153
+
154
+ const channels = (hex) => [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16));
155
+
156
+ function hsl(r, g, b) {
157
+ const [R, G, B] = [r / 255, g / 255, b / 255];
158
+ const max = Math.max(R, G, B);
159
+ const min = Math.min(R, G, B);
160
+ const l = (max + min) / 2;
161
+ const d = max - min;
162
+ if (!d) return [0, 0, l];
163
+ const s = d / (1 - Math.abs(2 * l - 1));
164
+ const h = max === R ? ((G - B) / d + 6) % 6 : max === G ? (B - R) / d + 2 : (R - G) / d + 4;
165
+ return [h * 60, s, l];
166
+ }
167
+
168
+ function rgbOf(h, s, l) {
169
+ const c = (1 - Math.abs(2 * l - 1)) * s;
170
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
171
+ const m = l - c / 2;
172
+ const sector = [
173
+ [c, x, 0],
174
+ [x, c, 0],
175
+ [0, c, x],
176
+ [0, x, c],
177
+ [x, 0, c],
178
+ [c, 0, x],
179
+ ][Math.min(5, Math.floor(h / 60))];
180
+ return sector.map((v) => Math.round(Math.min(1, Math.max(0, v + m)) * 255));
181
+ }
182
+
183
+ /** The band of a sheet row: hair, face, top, bottom or shoes, by its place in the 32-row frame. */
184
+ export function bandOf(y) {
185
+ return BANDS.find(([, a, b]) => y % FRAME_H >= a && y % FRAME_H < b)[0];
186
+ }
187
+
188
+ /**
189
+ * The middle of the face where it faces the viewer: the first row of frames
190
+ * (the front view; the second shows the back of the head), the face rows,
191
+ * away from the sides long hair covers and the collar rises into.
192
+ */
193
+ export function inFaceCentre(x, y) {
194
+ return y >= 13 && y < 18 && x % 16 >= 5 && x % 16 <= 10;
195
+ }
196
+
197
+ /**
198
+ * The part of every pixel of a sheet: hair, skin, top, bottom, keep, or null
199
+ * for a transparent one. A pixel is judged by its colour and its row:
200
+ * - near-black outline and white keep their colour everywhere;
201
+ * - the face's tone is skin wherever it is (neck, arms, hands, legs);
202
+ * - the hair rows are hair, the dark strand lines inside it included;
203
+ * - the face rows are skin, hair (a colour of the hair rows: long hair at the
204
+ * sides) or kept (eyes, mouth, the face's outline), never clothing;
205
+ * - the body rows are the top, except a hair colour (a braid over the
206
+ * shoulder), the leg rows are the bottom, and the shoes keep theirs.
207
+ */
208
+ export function pixelParts(sheet) {
209
+ const { width, height, rgba } = sheet;
210
+ const seen = new Map();
211
+ for (let y = 0; y < height; y++) {
212
+ const band = bandOf(y);
213
+ for (let x = 0; x < width; x++) {
214
+ const o = (y * width + x) * 4;
215
+ if (!rgba[o + 3]) continue;
216
+ const hex = rgba.subarray(o, o + 3).toString("hex");
217
+ const counts = seen.get(hex) || {
218
+ hair: 0,
219
+ face: 0,
220
+ top: 0,
221
+ bottom: 0,
222
+ shoes: 0,
223
+ centre: 0,
224
+ };
225
+ counts[band]++;
226
+ if (inFaceCentre(x, y)) counts.centre++;
227
+ seen.set(hex, counts);
228
+ }
229
+ }
230
+ const info = new Map();
231
+ for (const [hex, counts] of seen) {
232
+ const rgb = channels(hex);
233
+ const [h, s, l] = hsl(...rgb);
234
+ const n = counts.hair + counts.face + counts.top + counts.bottom + counts.shoes;
235
+ const [band] = BANDS.map(([name]) => [name, counts[name]]).sort((p, q) => q[1] - p[1])[0];
236
+ info.set(hex, {
237
+ rgb,
238
+ counts,
239
+ n,
240
+ band,
241
+ fixed: Math.max(...rgb) < 0x18 || Math.min(...rgb) > 0xf8,
242
+ skinLike: h >= 8 && h <= 45 && s >= 0.25 && l >= 0.15,
243
+ h,
244
+ s,
245
+ });
246
+ }
247
+ // The face is the skin-like colours of its middle, where no hair hangs, that
248
+ // the hair rows use less (a forehead under the fringe counts). Skin elsewhere
249
+ // is a colour the hair rows never use that sits next to a face colour (the
250
+ // shading of arms and hands); a hair highlight of a skin-like hue lives in the
251
+ // hair rows, and a clothing colour of a skin-like hue is far from the face or
252
+ // lives below the waist. Hair never reaches the middle of the face.
253
+ const faceTones = [...info.values()].filter(
254
+ (c) => c.skinLike && c.counts.centre > 0 && c.counts.centre * 2 >= c.counts.hair,
255
+ );
256
+ const near = (c, f) =>
257
+ Math.abs(c.s - f.s) <= 0.3 && c.rgb.every((v, i) => Math.abs(v - f.rgb[i]) <= SKIN_NEAR);
258
+ const skin = (c) =>
259
+ faceTones.includes(c) ||
260
+ (c.skinLike && c.counts.hair === 0 && c.band !== "bottom" && faceTones.some((f) => near(c, f)));
261
+ const hairColour = (c) => c.counts.hair > 0 && c.counts.hair * 2 >= c.n - c.counts.hair;
262
+ const out = new Array(width * height).fill(null);
263
+ for (let y = 0; y < height; y++) {
264
+ const band = bandOf(y);
265
+ for (let x = 0; x < width; x++) {
266
+ const o = (y * width + x) * 4;
267
+ if (!rgba[o + 3]) continue;
268
+ const c = info.get(rgba.subarray(o, o + 3).toString("hex"));
269
+ let part;
270
+ if (c.fixed) part = "keep";
271
+ else if (skin(c)) part = "skin";
272
+ else if (band === "hair") part = "hair";
273
+ else if (band === "face") part = hairColour(c) && !inFaceCentre(x, y) ? "hair" : "keep";
274
+ else if (band === "top") part = c.band === "hair" ? "hair" : "top";
275
+ else if (band === "bottom") part = "bottom";
276
+ else part = "keep";
277
+ out[y * width + x] = part;
278
+ }
279
+ }
280
+ return out;
281
+ }
282
+
283
+ /** A part's target lightness for a source lightness: a monotone ramp around the part's mean. */
284
+ function rampOf(l, mean, target) {
285
+ if (l <= mean) return target * (l / mean);
286
+ const top = Math.min(0.92, target + 0.35);
287
+ return target + (top - target) * ((l - mean) / (1 - mean));
288
+ }
289
+
290
+ /**
291
+ * A character sheet in one person's colours: {hair, skin, top, bottom} as
292
+ * "rrggbb". Returns {width, height, rgba, parts}, parts being pixelParts.
293
+ */
294
+ export function recolorSheet(sheet, colours) {
295
+ const parts = pixelParts(sheet);
296
+ const { rgba } = sheet;
297
+ const sum = {};
298
+ const n = {};
299
+ for (let i = 0; i < parts.length; i++) {
300
+ const p = parts[i];
301
+ if (!PARTS.includes(p)) continue;
302
+ const [, , l] = hsl(rgba[i * 4], rgba[i * 4 + 1], rgba[i * 4 + 2]);
303
+ sum[p] = (sum[p] || 0) + l;
304
+ n[p] = (n[p] || 0) + 1;
305
+ }
306
+ const targets = {};
307
+ for (const p of PARTS) {
308
+ if (!n[p]) continue;
309
+ if (!/^[0-9a-f]{6}$/i.test(colours[p] || "")) throw new Error(`no ${p} colour`);
310
+ targets[p] = hsl(...channels(colours[p]));
311
+ }
312
+ const out = Buffer.from(rgba);
313
+ const memo = new Map();
314
+ for (let i = 0; i < parts.length; i++) {
315
+ const p = parts[i];
316
+ if (!PARTS.includes(p)) continue;
317
+ const o = i * 4;
318
+ const key = p + rgba.subarray(o, o + 3).toString("hex");
319
+ let to = memo.get(key);
320
+ if (!to) {
321
+ const [th, ts, tl] = targets[p];
322
+ const [, , l] = hsl(rgba[o], rgba[o + 1], rgba[o + 2]);
323
+ to = rgbOf(th, ts, Math.min(0.94, Math.max(0.03, rampOf(l, sum[p] / n[p], tl))));
324
+ memo.set(key, to);
325
+ }
326
+ [out[o], out[o + 1], out[o + 2]] = to;
327
+ }
328
+ return { width: sheet.width, height: sheet.height, rgba: out, parts };
329
+ }
330
+
331
+ /** The employees as the page reads them, and their sheets: "char_<slot>" -> PNG bytes. */
332
+ export function employees() {
333
+ const { employees: list } = JSON.parse(readFileSync(join(ART, "employees.json"), "utf8"));
334
+ const sprites = new Map();
335
+ const slots = new Set();
336
+ const people = list.map((e) => {
337
+ if (!Number.isInteger(e.slot) || e.slot < 12 || e.slot > 99 || slots.has(e.slot))
338
+ throw new Error(`${e.role}: slot must be a unique integer from 12 to 99`);
339
+ if (!Number.isInteger(e.base) || e.base < 0 || e.base > 5)
340
+ throw new Error(`${e.role}: base is a character sheet from 0 to 5`);
341
+ slots.add(e.slot);
342
+ const sheet = recolorSheet(decodePng(readFileSync(join(ASSETS, `char_${e.base}.png`))), e);
343
+ sprites.set(`char_${e.slot}`, encodePng(sheet.width, sheet.height, sheet.rgba));
344
+ return { role: e.role, name: e.name, agent: e.agent, tier: e.tier, slot: e.slot };
345
+ });
346
+ return { people, sprites };
347
+ }
348
+
81
349
  /** One art file as a PNG; throws on a size line, row length or letter that does not fit. */
82
350
  export function renderArt(text, colors, name = "sprite") {
83
351
  const lines = text.replace(/\r/g, "").trimEnd().split("\n");
@@ -150,11 +418,13 @@ export function build() {
150
418
  const read = (f) => readFileSync(join(SRC, f), "utf8");
151
419
  const strings = JSON.parse(read("strings.json"));
152
420
  const sprites = atlas();
421
+ const staff = employees();
153
422
  const script = [
154
423
  '"use strict";',
155
424
  "(() => {",
156
425
  `const ATLAS = ${JSON.stringify(sprites)};`,
157
426
  `const STRINGS = ${JSON.stringify(strings)};`,
427
+ `const EMPLOYEES = ${JSON.stringify(staff.people)};`,
158
428
  'const spriteUrl = (name) => "/office/assets/" + name + ".png";',
159
429
  read("world.js").trim(),
160
430
  read("app.js").trim(),
@@ -166,6 +436,7 @@ export function build() {
166
436
  const hash = createHash("sha256").update(script).update(style).update(read("shell.html"));
167
437
  // Redrawn sprites change the page too, though no source text did.
168
438
  for (const [name, png] of artSprites()) hash.update(name).update(png);
439
+ for (const [name, png] of staff.sprites) hash.update(name).update(png);
169
440
  const build = hash.digest("hex").slice(0, 12);
170
441
  // Text that would close the inline element early, or open an HTML comment
171
442
  // inside it, turns source into markup.
@@ -181,7 +452,7 @@ export function build() {
181
452
 
182
453
  const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
183
454
  if (isMain) {
184
- const art = artSprites();
455
+ const art = new Map([...artSprites(), ...employees().sprites]);
185
456
  const check = process.argv.includes("--check");
186
457
  if (!check) for (const [name, png] of art) writeFileSync(join(ASSETS, `${name}.png`), png);
187
458
  const html = build();
@@ -23,11 +23,11 @@
23
23
  * status running or not, the URL without the token, the idle
24
24
  * limit, remote access, the paired devices the phone
25
25
  * registry lists, and the last panel actions from
26
- * office-actions.jsonl. Remote access is "not available in
27
- * this installation" unless a remote layer is present: a
28
- * prefs.global.office.remote block, or a separately
29
- * installed module at ~/.claude/office-remote/. This
30
- * script only reports it; it carries no remote code.
26
+ * office-actions.jsonl. Remote access is "off" until
27
+ * office-pair pairs a phone, "configured" when
28
+ * prefs.global.office.remote is set but nothing is paired,
29
+ * and "paired" once a device is; while paired, office-on
30
+ * starts office-remote-client.mjs beside the server.
31
31
  *
32
32
  * State: one 0600 file, ~/.claude/logs/multi-agent/office/office.json, with
33
33
  * the pid, port and token file of the server this script started. The server
@@ -58,6 +58,7 @@ import { homedir } from "node:os";
58
58
  import { dirname, join } from "node:path";
59
59
 
60
60
  import { codeDigest } from "../lib/code-digest.mjs";
61
+ import { phoneDir } from "./_phone-auth.mjs";
61
62
  import { fileURLToPath } from "node:url";
62
63
 
63
64
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -72,6 +73,7 @@ const START_SKEW_MS = 120 * 1000;
72
73
  const home = (env) => env.HOME || homedir();
73
74
  const stateDir = (env) => join(home(env), ".claude", "logs", "multi-agent", "office");
74
75
  const stateFile = (env) => join(stateDir(env), "office.json");
76
+ const clientFile = (env) => join(stateDir(env), "remote-client.json");
75
77
  const actionsLog = (env) =>
76
78
  join(home(env), ".claude", "logs", "multi-agent", "office-actions.jsonl");
77
79
 
@@ -86,16 +88,22 @@ const readJson = (p) => {
86
88
  /** The panel page: next to this scripts directory, in a checkout and in the install. */
87
89
  export const pagePath = () => join(HERE, "..", "office", "index.html");
88
90
 
89
- /** Whether a remote layer is present; none ships with the pipeline. */
91
+ /** Whether a device is paired for remote use (office-pair wrote the remote config). */
92
+ export const remotePaired = (env = process.env) => existsSync(join(phoneDir(env), "remote.json"));
93
+
94
+ /** Whether the remote layer is configured: a paired device, or a prefs block. */
90
95
  export function remoteLayer(env = process.env) {
96
+ if (remotePaired(env)) return true;
91
97
  const prefs = readJson(join(home(env), ".claude", "multi-agent-preferences.json"));
92
98
  const block = prefs?.global?.office?.remote;
93
- if (block && typeof block === "object" && !Array.isArray(block)) return true;
94
- return existsSync(join(home(env), ".claude", "office-remote"));
99
+ return Boolean(block && typeof block === "object" && !Array.isArray(block));
95
100
  }
96
101
 
97
- const remoteText = (env) =>
98
- remoteLayer(env) ? "provided by a separate remote layer" : "not available in this installation";
102
+ const remoteText = (env) => {
103
+ if (remotePaired(env)) return "paired: the remote client runs while the office is on";
104
+ if (remoteLayer(env)) return "configured, no phone paired (office-pair start)";
105
+ return "off: no phone paired";
106
+ };
99
107
 
100
108
  /** The contract server: beside this script, else the install. */
101
109
  export function serverScript(env = process.env) {
@@ -127,6 +135,55 @@ function psField(pid, field) {
127
135
  return r.status === 0 ? r.stdout.trim() : "";
128
136
  }
129
137
 
138
+ /** The remote client this script started, if it is still running. */
139
+ function remoteClient(env) {
140
+ const c = readJson(clientFile(env));
141
+ if (!c || !Number.isInteger(c.pid) || !alive(c.pid)) return null;
142
+ return psField(c.pid, "command").includes("office-remote-client.mjs") ? c : null;
143
+ }
144
+
145
+ /**
146
+ * Start office-remote-client.mjs beside the server when a device is paired,
147
+ * or keep the one already running. Without a pairing nothing starts, so
148
+ * remote stays off by default; the client also stops by itself once the
149
+ * server is gone.
150
+ */
151
+ export function startRemoteClient(env = process.env) {
152
+ if (!remotePaired(env)) return false;
153
+ if (remoteClient(env)) return true;
154
+ const script = join(HERE, "office-remote-client.mjs");
155
+ if (!existsSync(script)) return false;
156
+ const child = spawn(process.execPath, [script, "loop"], {
157
+ env,
158
+ detached: true,
159
+ stdio: "ignore",
160
+ });
161
+ child.unref();
162
+ mkdirSync(stateDir(env), { recursive: true, mode: 0o700 });
163
+ writeFileSync(
164
+ clientFile(env),
165
+ JSON.stringify({ pid: child.pid, startedAt: new Date().toISOString() }) + "\n",
166
+ { mode: 0o600 },
167
+ );
168
+ return true;
169
+ }
170
+
171
+ /** Stop the remote client this script started. */
172
+ export function stopRemoteClient(env = process.env) {
173
+ const c = remoteClient(env);
174
+ let stopped = false;
175
+ if (c) {
176
+ try {
177
+ process.kill(c.pid, "SIGTERM");
178
+ stopped = true;
179
+ } catch {
180
+ stopped = false;
181
+ }
182
+ }
183
+ rmSync(clientFile(env), { force: true });
184
+ return stopped;
185
+ }
186
+
130
187
  /**
131
188
  * Whether `pid` is still the server a state record names: its token file
132
189
  * names the same pid, the process runs contract-server.mjs, and it started
@@ -313,6 +370,7 @@ export async function officeOn({ env = process.env, open = true } = {}) {
313
370
  if (!s) throw new Error("the server stopped right after it started");
314
371
  }
315
372
  const page = `${s.url}/office`;
373
+ const remote = startRemoteClient(env);
316
374
  const opened = open ? openBrowser(`${page}#code=${await panelCode(s)}`, env) : false;
317
375
  return {
318
376
  running: true,
@@ -321,12 +379,14 @@ export async function officeOn({ env = process.env, open = true } = {}) {
321
379
  url: page,
322
380
  pid: s.pid,
323
381
  idleMinutes: s.idleMinutes,
382
+ remote,
324
383
  opened,
325
384
  };
326
385
  }
327
386
 
328
387
  /** Stop the server this script started. */
329
388
  export function officeOff({ env = process.env } = {}) {
389
+ stopRemoteClient(env);
330
390
  const s = readJson(stateFile(env));
331
391
  let stopped = false;
332
392
  if (isOurServer(s, s ? readJson(s.tokenFile ?? "") : null)) {