toolroll 0.5.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/LICENSE +21 -0
- package/README.md +1113 -0
- package/THIRD_PARTY_NOTICES.md +449 -0
- package/dist/accent-colors.d.ts +48 -0
- package/dist/accent-colors.js +122 -0
- package/dist/action-ledger.d.ts +26 -0
- package/dist/action-ledger.js +75 -0
- package/dist/agent-fence.d.ts +42 -0
- package/dist/agent-fence.js +183 -0
- package/dist/agentconfig.d.ts +216 -0
- package/dist/agentconfig.js +454 -0
- package/dist/api-tokens.d.ts +16 -0
- package/dist/api-tokens.js +27 -0
- package/dist/approval-policy.d.ts +69 -0
- package/dist/approval-policy.js +137 -0
- package/dist/approval-rules-ui.d.ts +25 -0
- package/dist/approval-rules-ui.js +39 -0
- package/dist/assignment-adapters.d.ts +180 -0
- package/dist/assignment-adapters.js +239 -0
- package/dist/assignment-brief.d.ts +71 -0
- package/dist/assignment-brief.js +129 -0
- package/dist/assignment-delivery.d.ts +56 -0
- package/dist/assignment-delivery.js +160 -0
- package/dist/assignment-presentation.d.ts +26 -0
- package/dist/assignment-presentation.js +80 -0
- package/dist/assignment-status.d.ts +62 -0
- package/dist/assignment-status.js +152 -0
- package/dist/assignment-ui.d.ts +71 -0
- package/dist/assignment-ui.js +103 -0
- package/dist/assignment.d.ts +222 -0
- package/dist/assignment.js +399 -0
- package/dist/attest.d.ts +56 -0
- package/dist/attest.js +153 -0
- package/dist/backend.d.ts +97 -0
- package/dist/backend.js +166 -0
- package/dist/backup-ui.d.ts +22 -0
- package/dist/backup-ui.js +59 -0
- package/dist/backup.d.ts +84 -0
- package/dist/backup.js +421 -0
- package/dist/beads.d.ts +34 -0
- package/dist/beads.js +135 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +22 -0
- package/dist/board.d.ts +150 -0
- package/dist/board.js +210 -0
- package/dist/boot-identity.d.ts +63 -0
- package/dist/boot-identity.js +99 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
- package/dist/browser/workspace.css +4 -0
- package/dist/browser/workspace.js +225 -0
- package/dist/browser-crew.d.ts +11 -0
- package/dist/browser-crew.js +50 -0
- package/dist/browser-shell.d.ts +8 -0
- package/dist/browser-shell.js +60 -0
- package/dist/browser-workspace.d.ts +780 -0
- package/dist/browser-workspace.js +47 -0
- package/dist/budget-alerts.d.ts +19 -0
- package/dist/budget-alerts.js +46 -0
- package/dist/builder.d.ts +370 -0
- package/dist/builder.js +3262 -0
- package/dist/capscan.d.ts +35 -0
- package/dist/capscan.js +113 -0
- package/dist/chat-acceptance.d.ts +71 -0
- package/dist/chat-acceptance.js +120 -0
- package/dist/chat-actions.d.ts +265 -0
- package/dist/chat-actions.js +1273 -0
- package/dist/chat-channel.d.ts +109 -0
- package/dist/chat-channel.js +652 -0
- package/dist/chat-continuity.d.ts +32 -0
- package/dist/chat-continuity.js +317 -0
- package/dist/chat-controls.d.ts +113 -0
- package/dist/chat-controls.js +53 -0
- package/dist/chat-delivery-state.d.ts +159 -0
- package/dist/chat-delivery-state.js +309 -0
- package/dist/chat-delivery.d.ts +38 -0
- package/dist/chat-delivery.js +693 -0
- package/dist/chat-display.d.ts +1 -0
- package/dist/chat-display.js +18 -0
- package/dist/chat-evidence.d.ts +123 -0
- package/dist/chat-evidence.js +207 -0
- package/dist/chat-flow.d.ts +44 -0
- package/dist/chat-flow.js +215 -0
- package/dist/chat-inbox.d.ts +57 -0
- package/dist/chat-inbox.js +123 -0
- package/dist/chat-polish.d.ts +16 -0
- package/dist/chat-polish.js +135 -0
- package/dist/chat-review.d.ts +46 -0
- package/dist/chat-review.js +98 -0
- package/dist/chat-rooms.d.ts +123 -0
- package/dist/chat-rooms.js +216 -0
- package/dist/chat-task-actions.d.ts +44 -0
- package/dist/chat-task-actions.js +79 -0
- package/dist/check-progress.d.ts +51 -0
- package/dist/check-progress.js +290 -0
- package/dist/child-database.d.ts +12 -0
- package/dist/child-database.js +30 -0
- package/dist/claim.d.ts +688 -0
- package/dist/claim.js +1740 -0
- package/dist/cli.d.ts +137 -0
- package/dist/cli.js +1461 -0
- package/dist/codex-limits.d.ts +15 -0
- package/dist/codex-limits.js +145 -0
- package/dist/coding-context.d.ts +55 -0
- package/dist/coding-context.js +92 -0
- package/dist/coding-handoff.d.ts +57 -0
- package/dist/coding-handoff.js +277 -0
- package/dist/coding-provider.d.ts +72 -0
- package/dist/coding-provider.js +424 -0
- package/dist/coding-shipping-ui.d.ts +4 -0
- package/dist/coding-shipping-ui.js +12 -0
- package/dist/coding-types.d.ts +62 -0
- package/dist/coding-types.js +1 -0
- package/dist/coding-ui.d.ts +17 -0
- package/dist/coding-ui.js +364 -0
- package/dist/coding-update.d.ts +15 -0
- package/dist/coding-update.js +149 -0
- package/dist/coding-workspace.d.ts +124 -0
- package/dist/coding-workspace.js +768 -0
- package/dist/container-state.d.ts +5 -0
- package/dist/container-state.js +62 -0
- package/dist/containment.d.ts +197 -0
- package/dist/containment.js +559 -0
- package/dist/contest.d.ts +318 -0
- package/dist/contest.js +753 -0
- package/dist/control-setup.d.ts +35 -0
- package/dist/control-setup.js +40 -0
- package/dist/control-ui.d.ts +29 -0
- package/dist/control-ui.js +53 -0
- package/dist/controller-service.d.ts +1 -0
- package/dist/controller-service.js +19 -0
- package/dist/controller-supervisor.d.ts +30 -0
- package/dist/controller-supervisor.js +104 -0
- package/dist/converse.d.ts +304 -0
- package/dist/converse.js +849 -0
- package/dist/coordinator-proposals.d.ts +29 -0
- package/dist/coordinator-proposals.js +137 -0
- package/dist/coordinator.d.ts +186 -0
- package/dist/coordinator.js +447 -0
- package/dist/credentials-ui.d.ts +25 -0
- package/dist/credentials-ui.js +48 -0
- package/dist/daemon.d.ts +209 -0
- package/dist/daemon.js +604 -0
- package/dist/decision.d.ts +120 -0
- package/dist/decision.js +388 -0
- package/dist/demo.d.ts +55 -0
- package/dist/demo.js +992 -0
- package/dist/desktop-access.d.ts +28 -0
- package/dist/desktop-access.js +88 -0
- package/dist/desktop-bundle.d.ts +16 -0
- package/dist/desktop-bundle.js +74 -0
- package/dist/desktop-host.d.ts +54 -0
- package/dist/desktop-host.js +508 -0
- package/dist/desktop-update-gate.d.ts +12 -0
- package/dist/desktop-update-gate.js +91 -0
- package/dist/desktop-update-recovery.d.ts +14 -0
- package/dist/desktop-update-recovery.js +243 -0
- package/dist/desktop-update.d.ts +110 -0
- package/dist/desktop-update.js +740 -0
- package/dist/discord-api.d.ts +20 -0
- package/dist/discord-api.js +138 -0
- package/dist/discord-chat.d.ts +16 -0
- package/dist/discord-chat.js +381 -0
- package/dist/discord-settings.d.ts +7 -0
- package/dist/discord-settings.js +75 -0
- package/dist/discord.d.ts +10 -0
- package/dist/discord.js +169 -0
- package/dist/discover.d.ts +75 -0
- package/dist/discover.js +150 -0
- package/dist/dispatch.d.ts +100 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispose.d.ts +161 -0
- package/dist/dispose.js +803 -0
- package/dist/email-settings.d.ts +34 -0
- package/dist/email-settings.js +50 -0
- package/dist/envelope.d.ts +47 -0
- package/dist/envelope.js +105 -0
- package/dist/evidence-pack.d.ts +185 -0
- package/dist/evidence-pack.js +258 -0
- package/dist/evidence.d.ts +345 -0
- package/dist/evidence.js +782 -0
- package/dist/exec.d.ts +285 -0
- package/dist/exec.js +1841 -0
- package/dist/exhaustion.d.ts +85 -0
- package/dist/exhaustion.js +141 -0
- package/dist/export-ui.d.ts +4 -0
- package/dist/export-ui.js +17 -0
- package/dist/export.d.ts +48 -0
- package/dist/export.js +305 -0
- package/dist/flow-actions.d.ts +77 -0
- package/dist/flow-actions.js +203 -0
- package/dist/flow-code.d.ts +50 -0
- package/dist/flow-code.js +159 -0
- package/dist/flow-draft.d.ts +38 -0
- package/dist/flow-draft.js +80 -0
- package/dist/flow-engine.d.ts +66 -0
- package/dist/flow-engine.js +416 -0
- package/dist/flow-insights.d.ts +95 -0
- package/dist/flow-insights.js +149 -0
- package/dist/flow-live.d.ts +24 -0
- package/dist/flow-live.js +150 -0
- package/dist/flow-people.d.ts +24 -0
- package/dist/flow-people.js +91 -0
- package/dist/flow-replies.d.ts +48 -0
- package/dist/flow-replies.js +113 -0
- package/dist/flow-scripts.d.ts +38 -0
- package/dist/flow-scripts.js +73 -0
- package/dist/flow-secrets.d.ts +13 -0
- package/dist/flow-secrets.js +45 -0
- package/dist/flow-sort.d.ts +82 -0
- package/dist/flow-sort.js +153 -0
- package/dist/flow-steps.d.ts +40 -0
- package/dist/flow-steps.js +408 -0
- package/dist/flow-triggers.d.ts +244 -0
- package/dist/flow-triggers.js +959 -0
- package/dist/flows-ui.d.ts +20 -0
- package/dist/flows-ui.js +175 -0
- package/dist/flows.d.ts +229 -0
- package/dist/flows.js +968 -0
- package/dist/fonts.d.ts +19 -0
- package/dist/fonts.js +19 -0
- package/dist/gaps.d.ts +35 -0
- package/dist/gaps.js +101 -0
- package/dist/gate-failure.d.ts +40 -0
- package/dist/gate-failure.js +66 -0
- package/dist/git.d.ts +54 -0
- package/dist/git.js +94 -0
- package/dist/google-mail.d.ts +58 -0
- package/dist/google-mail.js +151 -0
- package/dist/grant.d.ts +136 -0
- package/dist/grant.js +238 -0
- package/dist/graph.d.ts +164 -0
- package/dist/graph.js +383 -0
- package/dist/guides.d.ts +22 -0
- package/dist/guides.js +363 -0
- package/dist/held.d.ts +150 -0
- package/dist/held.js +799 -0
- package/dist/invoke.d.ts +112 -0
- package/dist/invoke.js +780 -0
- package/dist/issues.d.ts +41 -0
- package/dist/issues.js +131 -0
- package/dist/job-object-helper.ps1 +252 -0
- package/dist/jsonl-discriminants.d.ts +11 -0
- package/dist/jsonl-discriminants.js +93 -0
- package/dist/keys.d.ts +104 -0
- package/dist/keys.js +229 -0
- package/dist/kits-ui.d.ts +17 -0
- package/dist/kits-ui.js +46 -0
- package/dist/kits.d.ts +86 -0
- package/dist/kits.js +215 -0
- package/dist/knowledge-cli.d.ts +101 -0
- package/dist/knowledge-cli.js +92 -0
- package/dist/knowledge-ui.d.ts +24 -0
- package/dist/knowledge-ui.js +40 -0
- package/dist/lead-context.d.ts +8 -0
- package/dist/lead-context.js +56 -0
- package/dist/lead-follow.d.ts +22 -0
- package/dist/lead-follow.js +167 -0
- package/dist/lead-status.d.ts +75 -0
- package/dist/lead-status.js +285 -0
- package/dist/ledger-chain.d.ts +46 -0
- package/dist/ledger-chain.js +187 -0
- package/dist/ledger-csv.d.ts +4 -0
- package/dist/ledger-csv.js +11 -0
- package/dist/ledger-view.d.ts +24 -0
- package/dist/ledger-view.js +58 -0
- package/dist/limits-ui.d.ts +26 -0
- package/dist/limits-ui.js +67 -0
- package/dist/link.d.ts +72 -0
- package/dist/link.js +217 -0
- package/dist/live.d.ts +96 -0
- package/dist/live.js +366 -0
- package/dist/liveness.d.ts +30 -0
- package/dist/liveness.js +42 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +25 -0
- package/dist/mailbox.d.ts +57 -0
- package/dist/mailbox.js +150 -0
- package/dist/maintenance.d.ts +11 -0
- package/dist/maintenance.js +35 -0
- package/dist/mate-cli.d.ts +52 -0
- package/dist/mate-cli.js +345 -0
- package/dist/mate-contract.d.ts +10 -0
- package/dist/mate-contract.js +30 -0
- package/dist/mate-doors.d.ts +69 -0
- package/dist/mate-doors.js +548 -0
- package/dist/mate-progress.d.ts +29 -0
- package/dist/mate-progress.js +121 -0
- package/dist/mate-tools.d.ts +94 -0
- package/dist/mate-tools.js +1878 -0
- package/dist/mate.d.ts +92 -0
- package/dist/mate.js +435 -0
- package/dist/mcp-connect.d.ts +103 -0
- package/dist/mcp-connect.js +252 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.js +651 -0
- package/dist/memory-cli.d.ts +466 -0
- package/dist/memory-cli.js +111 -0
- package/dist/memory-pass.d.ts +160 -0
- package/dist/memory-pass.js +409 -0
- package/dist/metrics.d.ts +3 -0
- package/dist/metrics.js +56 -0
- package/dist/mobile-viewport.d.ts +3 -0
- package/dist/mobile-viewport.js +37 -0
- package/dist/model-catalog.d.ts +118 -0
- package/dist/model-catalog.js +376 -0
- package/dist/models-cli.d.ts +102 -0
- package/dist/models-cli.js +66 -0
- package/dist/models-ui.d.ts +34 -0
- package/dist/models-ui.js +68 -0
- package/dist/modes.d.ts +80 -0
- package/dist/modes.js +182 -0
- package/dist/monitoring-settings.d.ts +44 -0
- package/dist/monitoring-settings.js +173 -0
- package/dist/monitoring-ui.d.ts +20 -0
- package/dist/monitoring-ui.js +51 -0
- package/dist/monitoring.d.ts +34 -0
- package/dist/monitoring.js +221 -0
- package/dist/names.d.ts +67 -0
- package/dist/names.js +102 -0
- package/dist/observations.d.ts +40 -0
- package/dist/observations.js +211 -0
- package/dist/oidc.d.ts +71 -0
- package/dist/oidc.js +182 -0
- package/dist/onboard.d.ts +108 -0
- package/dist/onboard.js +325 -0
- package/dist/openrouter-models.d.ts +30 -0
- package/dist/openrouter-models.js +120 -0
- package/dist/operate.d.ts +118 -0
- package/dist/operate.js +11321 -0
- package/dist/peek-cli.d.ts +97 -0
- package/dist/peek-cli.js +215 -0
- package/dist/peek.d.ts +134 -0
- package/dist/peek.js +578 -0
- package/dist/phase-routing.d.ts +307 -0
- package/dist/phase-routing.js +658 -0
- package/dist/plan-auto.d.ts +14 -0
- package/dist/plan-auto.js +92 -0
- package/dist/plan.d.ts +186 -0
- package/dist/plan.js +401 -0
- package/dist/planner-source.d.ts +251 -0
- package/dist/planner-source.js +460 -0
- package/dist/planner.d.ts +75 -0
- package/dist/planner.js +992 -0
- package/dist/policy-ui.d.ts +20 -0
- package/dist/policy-ui.js +41 -0
- package/dist/policy.d.ts +111 -0
- package/dist/policy.js +231 -0
- package/dist/prepared-evidence.d.ts +16 -0
- package/dist/prepared-evidence.js +96 -0
- package/dist/pricing.d.ts +45 -0
- package/dist/pricing.js +77 -0
- package/dist/principal.d.ts +42 -0
- package/dist/principal.js +82 -0
- package/dist/probe.d.ts +46 -0
- package/dist/probe.js +79 -0
- package/dist/process-custody.d.ts +17 -0
- package/dist/process-custody.js +91 -0
- package/dist/process-liveness.d.ts +10 -0
- package/dist/process-liveness.js +81 -0
- package/dist/process-recovery-anchor.d.ts +75 -0
- package/dist/process-recovery-anchor.js +188 -0
- package/dist/process-recovery-coalition.d.ts +25 -0
- package/dist/process-recovery-coalition.js +105 -0
- package/dist/process-recovery-eligibility.d.ts +178 -0
- package/dist/process-recovery-eligibility.js +170 -0
- package/dist/process-recovery-native.d.ts +172 -0
- package/dist/process-recovery-native.js +592 -0
- package/dist/process-recovery-provenance.d.ts +163 -0
- package/dist/process-recovery-provenance.js +292 -0
- package/dist/process-recovery-services.d.ts +56 -0
- package/dist/process-recovery-services.js +191 -0
- package/dist/process-recovery-settlement.d.ts +32 -0
- package/dist/process-recovery-settlement.js +132 -0
- package/dist/process-recovery.d.ts +41 -0
- package/dist/process-recovery.js +91 -0
- package/dist/process-tree.d.ts +41 -0
- package/dist/process-tree.js +264 -0
- package/dist/project-access.d.ts +14 -0
- package/dist/project-access.js +34 -0
- package/dist/project-cli.d.ts +18 -0
- package/dist/project-cli.js +104 -0
- package/dist/project-delete-ui.d.ts +22 -0
- package/dist/project-delete-ui.js +48 -0
- package/dist/project-delete.d.ts +55 -0
- package/dist/project-delete.js +413 -0
- package/dist/project-knowledge.d.ts +111 -0
- package/dist/project-knowledge.js +241 -0
- package/dist/project-learning.d.ts +100 -0
- package/dist/project-learning.js +438 -0
- package/dist/project-memory.d.ts +81 -0
- package/dist/project-memory.js +264 -0
- package/dist/project-skills.d.ts +152 -0
- package/dist/project-skills.js +660 -0
- package/dist/project-tools.d.ts +219 -0
- package/dist/project-tools.js +796 -0
- package/dist/project.d.ts +61 -0
- package/dist/project.js +125 -0
- package/dist/prompt.d.ts +22 -0
- package/dist/prompt.js +72 -0
- package/dist/proof.d.ts +513 -0
- package/dist/proof.js +1140 -0
- package/dist/proposal.d.ts +82 -0
- package/dist/proposal.js +210 -0
- package/dist/provider-connection.d.ts +22 -0
- package/dist/provider-connection.js +98 -0
- package/dist/provider-limits.d.ts +35 -0
- package/dist/provider-limits.js +95 -0
- package/dist/provider.d.ts +279 -0
- package/dist/provider.js +935 -0
- package/dist/publish.d.ts +107 -0
- package/dist/publish.js +650 -0
- package/dist/pulls.d.ts +118 -0
- package/dist/pulls.js +240 -0
- package/dist/push.d.ts +95 -0
- package/dist/push.js +353 -0
- package/dist/quality.d.ts +8 -0
- package/dist/quality.js +6 -0
- package/dist/recipe-ui.d.ts +18 -0
- package/dist/recipe-ui.js +152 -0
- package/dist/recipes.d.ts +81 -0
- package/dist/recipes.js +332 -0
- package/dist/remote.d.ts +67 -0
- package/dist/remote.js +122 -0
- package/dist/render.d.ts +64 -0
- package/dist/render.js +587 -0
- package/dist/report-summary.d.ts +13 -0
- package/dist/report-summary.js +15 -0
- package/dist/repos.d.ts +65 -0
- package/dist/repos.js +277 -0
- package/dist/repository-context-ui.d.ts +2 -0
- package/dist/repository-context-ui.js +10 -0
- package/dist/repository-context.d.ts +67 -0
- package/dist/repository-context.js +376 -0
- package/dist/restart-certification.d.ts +134 -0
- package/dist/restart-certification.js +226 -0
- package/dist/result-actions.d.ts +28 -0
- package/dist/result-actions.js +270 -0
- package/dist/result-completion.d.ts +18 -0
- package/dist/result-completion.js +66 -0
- package/dist/result-review.d.ts +213 -0
- package/dist/result-review.js +430 -0
- package/dist/retention-ui.d.ts +12 -0
- package/dist/retention-ui.js +39 -0
- package/dist/retention.d.ts +72 -0
- package/dist/retention.js +288 -0
- package/dist/review-context.d.ts +243 -0
- package/dist/review-context.js +1043 -0
- package/dist/review-evidence.d.ts +33 -0
- package/dist/review-evidence.js +56 -0
- package/dist/reviewer.d.ts +97 -0
- package/dist/reviewer.js +202 -0
- package/dist/routine.d.ts +228 -0
- package/dist/routine.js +764 -0
- package/dist/runner.d.ts +266 -0
- package/dist/runner.js +399 -0
- package/dist/scan.d.ts +32 -0
- package/dist/scan.js +92 -0
- package/dist/scope.d.ts +695 -0
- package/dist/scope.js +1176 -0
- package/dist/scout-report.d.ts +42 -0
- package/dist/scout-report.js +108 -0
- package/dist/scout.d.ts +69 -0
- package/dist/scout.js +351 -0
- package/dist/serve.d.ts +313 -0
- package/dist/serve.js +21776 -0
- package/dist/session-brief.d.ts +8 -0
- package/dist/session-brief.js +24 -0
- package/dist/session-cli.d.ts +13 -0
- package/dist/session-cli.js +284 -0
- package/dist/session-contract.d.ts +183 -0
- package/dist/session-contract.js +122 -0
- package/dist/session-http.d.ts +19 -0
- package/dist/session-http.js +154 -0
- package/dist/session-server.d.ts +12 -0
- package/dist/session-server.js +37 -0
- package/dist/session-service.d.ts +22 -0
- package/dist/session-service.js +157 -0
- package/dist/setup-guide.d.ts +39 -0
- package/dist/setup-guide.js +93 -0
- package/dist/sign-in-guard.d.ts +45 -0
- package/dist/sign-in-guard.js +74 -0
- package/dist/skills-ui.d.ts +11 -0
- package/dist/skills-ui.js +58 -0
- package/dist/skills.d.ts +97 -0
- package/dist/skills.js +276 -0
- package/dist/slack-api.d.ts +55 -0
- package/dist/slack-api.js +229 -0
- package/dist/slack-chat.d.ts +28 -0
- package/dist/slack-chat.js +413 -0
- package/dist/slack-settings.d.ts +7 -0
- package/dist/slack-settings.js +75 -0
- package/dist/slack-state.d.ts +13 -0
- package/dist/slack-state.js +9 -0
- package/dist/slack.d.ts +13 -0
- package/dist/slack.js +167 -0
- package/dist/spend-ui.d.ts +28 -0
- package/dist/spend-ui.js +95 -0
- package/dist/spend.d.ts +142 -0
- package/dist/spend.js +313 -0
- package/dist/sqlite-runtime.d.ts +3 -0
- package/dist/sqlite-runtime.js +6 -0
- package/dist/sso-settings.d.ts +33 -0
- package/dist/sso-settings.js +103 -0
- package/dist/sso-ui.d.ts +22 -0
- package/dist/sso-ui.js +39 -0
- package/dist/storage.d.ts +15 -0
- package/dist/storage.js +84 -0
- package/dist/store.d.ts +7754 -0
- package/dist/store.js +23279 -0
- package/dist/structured-output.d.ts +58 -0
- package/dist/structured-output.js +103 -0
- package/dist/style-asset.d.ts +7 -0
- package/dist/style-asset.js +41 -0
- package/dist/subscription-chat.d.ts +46 -0
- package/dist/subscription-chat.js +275 -0
- package/dist/summary.d.ts +33 -0
- package/dist/summary.js +71 -0
- package/dist/supervisor.mjs +297 -0
- package/dist/surface.d.ts +63 -0
- package/dist/surface.js +352 -0
- package/dist/sync.d.ts +94 -0
- package/dist/sync.js +279 -0
- package/dist/task-composer.d.ts +13 -0
- package/dist/task-composer.js +52 -0
- package/dist/task-control.d.ts +165 -0
- package/dist/task-control.js +214 -0
- package/dist/task-outcome-cli.d.ts +10 -0
- package/dist/task-outcome-cli.js +92 -0
- package/dist/task-text.d.ts +30 -0
- package/dist/task-text.js +41 -0
- package/dist/team-cli.d.ts +15 -0
- package/dist/team-cli.js +618 -0
- package/dist/team-contract.d.ts +92 -0
- package/dist/team-contract.js +1 -0
- package/dist/team-http.d.ts +17 -0
- package/dist/team-http.js +144 -0
- package/dist/team-leads.d.ts +81 -0
- package/dist/team-leads.js +565 -0
- package/dist/team-runtime.d.ts +27 -0
- package/dist/team-runtime.js +264 -0
- package/dist/team-ui.d.ts +3 -0
- package/dist/team-ui.js +9 -0
- package/dist/team-updates.d.ts +8 -0
- package/dist/team-updates.js +57 -0
- package/dist/teammate-admin.d.ts +53 -0
- package/dist/teammate-admin.js +111 -0
- package/dist/teammate-desk.d.ts +60 -0
- package/dist/teammate-desk.js +154 -0
- package/dist/teammate-memory.d.ts +64 -0
- package/dist/teammate-memory.js +196 -0
- package/dist/teammate-question.d.ts +59 -0
- package/dist/teammate-question.js +175 -0
- package/dist/teammate-tools.d.ts +108 -0
- package/dist/teammate-tools.js +271 -0
- package/dist/teammate-week.d.ts +68 -0
- package/dist/teammate-week.js +132 -0
- package/dist/teammate-work.d.ts +76 -0
- package/dist/teammate-work.js +280 -0
- package/dist/teammates-ui.d.ts +20 -0
- package/dist/teammates-ui.js +166 -0
- package/dist/teammates.d.ts +180 -0
- package/dist/teammates.js +253 -0
- package/dist/teams-api.d.ts +33 -0
- package/dist/teams-api.js +204 -0
- package/dist/teams-chat.d.ts +26 -0
- package/dist/teams-chat.js +215 -0
- package/dist/teams-settings.d.ts +12 -0
- package/dist/teams-settings.js +57 -0
- package/dist/teams.d.ts +21 -0
- package/dist/teams.js +124 -0
- package/dist/telegram-flow.d.ts +49 -0
- package/dist/telegram-flow.js +110 -0
- package/dist/telegram-mate.d.ts +137 -0
- package/dist/telegram-mate.js +707 -0
- package/dist/telegram-progress.d.ts +14 -0
- package/dist/telegram-progress.js +129 -0
- package/dist/telegram-settings.d.ts +9 -0
- package/dist/telegram-settings.js +36 -0
- package/dist/telegram-status.d.ts +75 -0
- package/dist/telegram-status.js +265 -0
- package/dist/telegram-team.d.ts +85 -0
- package/dist/telegram-team.js +359 -0
- package/dist/telegram.d.ts +230 -0
- package/dist/telegram.js +1931 -0
- package/dist/templates.d.ts +65 -0
- package/dist/templates.js +118 -0
- package/dist/tool-launcher.d.ts +1 -0
- package/dist/tool-launcher.js +101 -0
- package/dist/tools-ui.d.ts +30 -0
- package/dist/tools-ui.js +40 -0
- package/dist/transitions-recipes.d.ts +1 -0
- package/dist/transitions-recipes.js +203 -0
- package/dist/tree-proof.d.ts +34 -0
- package/dist/tree-proof.js +58 -0
- package/dist/verification-evidence.d.ts +44 -0
- package/dist/verification-evidence.js +238 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/dist/webhooks.d.ts +91 -0
- package/dist/webhooks.js +283 -0
- package/dist/work-index.d.ts +68 -0
- package/dist/work-index.js +384 -0
- package/dist/work-summary.d.ts +57 -0
- package/dist/work-summary.js +116 -0
- package/dist/workspace-motion.d.ts +4 -0
- package/dist/workspace-motion.js +133 -0
- package/dist/workspace-revision.d.ts +19 -0
- package/dist/workspace-revision.js +92 -0
- package/dist/workspace-ui.d.ts +204 -0
- package/dist/workspace-ui.js +411 -0
- package/dist/worktree-notices.d.ts +13 -0
- package/dist/worktree-notices.js +17 -0
- package/dist/worktree.d.ts +249 -0
- package/dist/worktree.js +720 -0
- package/package.json +122 -0
- package/scripts/canary-assertions.mjs +42 -0
- package/scripts/crash-canary.mjs +253 -0
- package/scripts/fixtures/crash-process.mjs +74 -0
- package/scripts/fixtures/pilot-scenarios.mjs +85 -0
- package/scripts/fixtures/restart-service.mjs +27 -0
- package/scripts/launchd-certification.mjs +143 -0
- package/scripts/pilot.mjs +121 -0
- package/scripts/proof-preflight.mjs +140 -0
- package/scripts/provider-canary.mjs +379 -0
- package/scripts/recovery-canary.mjs +135 -0
- package/scripts/restart-certification.mjs +98 -0
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The daemon manager: the loop as a service, with no crontab in sight.
|
|
3
|
+
*
|
|
4
|
+
* `toolroll daemon install` writes the platform's own supervision unit —
|
|
5
|
+
* a launchd LaunchAgent on macOS, a systemd user unit on Linux, a Task
|
|
6
|
+
* Scheduler task on Windows, chosen by process.platform — pointed at
|
|
7
|
+
* `toolroll watch`, and loads it. The OS keeps it alive across crashes
|
|
8
|
+
* and reboots; watch's incarnation recovery is what makes those restarts
|
|
9
|
+
* safe, so the two halves were built for each other.
|
|
10
|
+
*
|
|
11
|
+
* The runner token never enters the unit file. It is written 0600 to a
|
|
12
|
+
* credential file beside the database (the same discipline as the Telegram
|
|
13
|
+
* bot token), and the unit runs `watch --token-file <path>`. A unit file is
|
|
14
|
+
* the kind of thing that ends up in a dotfiles repo; a credential file
|
|
15
|
+
* beside the database is the kind of thing backup tooling already treats
|
|
16
|
+
* as sensitive.
|
|
17
|
+
*
|
|
18
|
+
* The lifecycle (OS containment and login recovery plan) is ONE contract
|
|
19
|
+
* for the CLI daemon and the desktop service, which share the launchd road
|
|
20
|
+
* below:
|
|
21
|
+
* - an installed always-on controller restarts after an unexpected
|
|
22
|
+
* clean exit as well as a crash (launchd KeepAlive=true, systemd
|
|
23
|
+
* Restart=always, a cmd restart loop under Task Scheduler);
|
|
24
|
+
* - start is idempotent — a healthy running service is never killed
|
|
25
|
+
* because the installer or the app window ran again;
|
|
26
|
+
* - a CHANGED definition (runtime, entry, flags) really reloads: the old
|
|
27
|
+
* job is booted out, its disappearance is awaited (the pending-bootout
|
|
28
|
+
* race), and the new unit is bootstrapped;
|
|
29
|
+
* - explicit stop unloads AND disables, so nothing relaunches it until a
|
|
30
|
+
* person installs again — which re-enables;
|
|
31
|
+
* - status tells a loaded definition from a fresh working controller,
|
|
32
|
+
* and names a missing runtime, a missing entry, or a disabled service.
|
|
33
|
+
*
|
|
34
|
+
* Everything here is generation plus one supervisor invocation, both
|
|
35
|
+
* injectable — the tests read the generated unit and script `launchctl`,
|
|
36
|
+
* and never touch the machine's real supervision.
|
|
37
|
+
*/
|
|
38
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
39
|
+
import { createHash } from "node:crypto";
|
|
40
|
+
import { homedir } from "node:os";
|
|
41
|
+
import { fileURLToPath } from "node:url";
|
|
42
|
+
import { dirname, join } from "node:path";
|
|
43
|
+
import { isAlive } from "./runner.js";
|
|
44
|
+
import { envTwins } from "./names.js";
|
|
45
|
+
/** A stable fingerprint of a unit's text: the changed-definition test. */
|
|
46
|
+
export function definitionDigest(unitContent) {
|
|
47
|
+
return createHash("sha256").update(unitContent).digest("hex").slice(0, 16);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `launchctl bootout` can return before the old label has disappeared from
|
|
51
|
+
* the user domain. Bootstrapping the replacement during that short window
|
|
52
|
+
* appears to succeed, then the pending bootout removes the new job too. Wait
|
|
53
|
+
* for the observable supervisor boundary before loading the replacement.
|
|
54
|
+
*/
|
|
55
|
+
async function waitForLaunchdBootout(run, service, attempts = 50) {
|
|
56
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
57
|
+
const status = await run("launchctl", ["print", service]);
|
|
58
|
+
if (status.code !== 0)
|
|
59
|
+
return true;
|
|
60
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Pin a service to the Node runtime that is executing Toolroll.
|
|
66
|
+
*
|
|
67
|
+
* launchd does not inherit an interactive shell's PATH, so invoking a JS
|
|
68
|
+
* package bin through `#!/usr/bin/env node` can install successfully and then
|
|
69
|
+
* fail before the CLI starts. The service instead runs the absolute Node
|
|
70
|
+
* binary with the package entry as its first argument. An explicitly supplied
|
|
71
|
+
* non-JavaScript executable remains an escape hatch for wrappers and packaged
|
|
72
|
+
* binaries.
|
|
73
|
+
*/
|
|
74
|
+
export function daemonLaunchCommand(args) {
|
|
75
|
+
if (args.explicitBin !== undefined) {
|
|
76
|
+
return /\.(?:c|m)?js$/i.test(args.explicitBin)
|
|
77
|
+
? { bin: args.execPath, binArgs: [args.explicitBin] }
|
|
78
|
+
: { bin: args.explicitBin, binArgs: [] };
|
|
79
|
+
}
|
|
80
|
+
if (args.entry === undefined || /\.(?:c|m)?tsx?$/i.test(args.entry))
|
|
81
|
+
return null;
|
|
82
|
+
return { bin: args.execPath, binArgs: [args.entry] };
|
|
83
|
+
}
|
|
84
|
+
/** One service per repo, named so two repos' watches never collide. */
|
|
85
|
+
export function labelFor(repo) {
|
|
86
|
+
return `com.toolroll.watch.${watchSlug(repo)}`;
|
|
87
|
+
}
|
|
88
|
+
/** The label a watch installed before the rename to Toolroll runs under: still found, stopped and uninstalled. */
|
|
89
|
+
export function legacyLabelFor(repo) {
|
|
90
|
+
return `com.standing-orders.watch.${watchSlug(repo)}`;
|
|
91
|
+
}
|
|
92
|
+
function watchSlug(repo) {
|
|
93
|
+
const slug = repo
|
|
94
|
+
.replace(/[^A-Za-z0-9]+/g, "-")
|
|
95
|
+
.replace(/^-+|-+$/g, "")
|
|
96
|
+
.toLowerCase()
|
|
97
|
+
.slice(-40);
|
|
98
|
+
return slug === "" ? "root" : slug;
|
|
99
|
+
}
|
|
100
|
+
/** The uid launchd's gui domain is keyed by. */
|
|
101
|
+
function launchdUid() {
|
|
102
|
+
return typeof process.getuid === "function" ? process.getuid() : 501;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The launchd unit shared by the CLI daemon and the desktop service.
|
|
106
|
+
* KeepAlive requests relaunch after either clean exit or crash. macOS may
|
|
107
|
+
* defer nondemand launches; the desktop's live controller supervisor covers
|
|
108
|
+
* controller exits independently. Login/OS-service recovery requires the
|
|
109
|
+
* physical certification, not just a valid plist.
|
|
110
|
+
*/
|
|
111
|
+
export function launchdPlist(args) {
|
|
112
|
+
if (args.startInterval !== undefined && (!Number.isSafeInteger(args.startInterval) || args.startInterval < 1))
|
|
113
|
+
throw Error("Invalid launchd start interval.");
|
|
114
|
+
if (args.associatedBundleId !== undefined && !/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(args.associatedBundleId))
|
|
115
|
+
throw new Error("Invalid associated app bundle identifier.");
|
|
116
|
+
const escaped = args.command.map(part => ` <string>${xml(part)}</string>`).join("\n");
|
|
117
|
+
const digest = createHash("sha256").update(JSON.stringify(args));
|
|
118
|
+
// Include executable and package code identity so an update at the same
|
|
119
|
+
// path reloads the service. Files are only read; credentials are excluded.
|
|
120
|
+
for (const file of args.command.slice(0, 2)) {
|
|
121
|
+
try {
|
|
122
|
+
if (file && existsSync(file))
|
|
123
|
+
digest.update(readFileSync(file));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
digest.update("unreadable-runtime");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const entry = args.command[1];
|
|
130
|
+
if (entry?.endsWith(".js")) {
|
|
131
|
+
try {
|
|
132
|
+
for (const file of readdirSync(dirname(entry)).filter(name => /\.(js|ps1)$/.test(name)).sort())
|
|
133
|
+
digest.update(file).update(readFileSync(join(dirname(entry), file)));
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
digest.update("unreadable-package");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const environment = Object.entries({ PATH: args.pathEnv, ...(args.environment ?? {}), ...envTwins("SERVICE_DIGEST", digest.digest("hex")) })
|
|
140
|
+
.map(([key, value]) => ` <key>${xml(key)}</key>\n <string>${xml(value)}</string>`)
|
|
141
|
+
.join("\n");
|
|
142
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
143
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
144
|
+
<plist version="1.0">
|
|
145
|
+
<dict>
|
|
146
|
+
<key>Label</key>
|
|
147
|
+
<string>${xml(args.label)}</string>
|
|
148
|
+
${args.associatedBundleId ? ` <key>AssociatedBundleIdentifiers</key>\n <array><string>${xml(args.associatedBundleId)}</string></array>` : ""}
|
|
149
|
+
<key>ProgramArguments</key>
|
|
150
|
+
<array>
|
|
151
|
+
${escaped}
|
|
152
|
+
</array>
|
|
153
|
+
<key>WorkingDirectory</key>
|
|
154
|
+
<string>${xml(args.workingDirectory)}</string>
|
|
155
|
+
<key>EnvironmentVariables</key>
|
|
156
|
+
<dict>
|
|
157
|
+
${environment}
|
|
158
|
+
</dict>
|
|
159
|
+
<key>RunAtLoad</key>
|
|
160
|
+
<true/>
|
|
161
|
+
<key>KeepAlive</key>
|
|
162
|
+
<${args.keepAlive === false ? "false" : "true"}/>${args.startInterval === undefined ? "" : `\n <key>StartInterval</key>\n <integer>${args.startInterval}</integer>`}
|
|
163
|
+
<key>ThrottleInterval</key>
|
|
164
|
+
<integer>15</integer>
|
|
165
|
+
<key>ExitTimeOut</key>
|
|
166
|
+
<integer>60</integer>
|
|
167
|
+
<key>StandardOutPath</key>
|
|
168
|
+
<string>${xml(args.logPath)}</string>
|
|
169
|
+
<key>StandardErrorPath</key>
|
|
170
|
+
<string>${xml(args.logPath)}</string>
|
|
171
|
+
</dict>
|
|
172
|
+
</plist>
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Everything `install` would do, computed without doing it — the unit text,
|
|
177
|
+
* where it goes, where logs land. `--dry-run` prints exactly this.
|
|
178
|
+
*/
|
|
179
|
+
export function planDaemon(args) {
|
|
180
|
+
const { platform, bin, binArgs, runner, repo, configDir, watchFlags } = args;
|
|
181
|
+
const home = args.home ?? homedir();
|
|
182
|
+
const pathEnv = args.pathEnv ?? process.env["PATH"] ?? "";
|
|
183
|
+
if (platform !== "darwin" && platform !== "linux" && platform !== "win32") {
|
|
184
|
+
return {
|
|
185
|
+
error: `no supervisor template for ${platform} — run \`toolroll watch\` under your own service manager`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const label = args.label ?? labelFor(repo);
|
|
189
|
+
const logDir = join(configDir, "logs");
|
|
190
|
+
const logPath = join(logDir, `${label}.log`);
|
|
191
|
+
const tokenFile = join(configDir, "runner-token");
|
|
192
|
+
const entry = binArgs[0] !== undefined && /\.(?:c|m)?js$/i.test(binArgs[0]) ? binArgs[0] : null;
|
|
193
|
+
const command = [
|
|
194
|
+
bin,
|
|
195
|
+
...binArgs,
|
|
196
|
+
"watch",
|
|
197
|
+
"--runner",
|
|
198
|
+
runner,
|
|
199
|
+
"--token-file",
|
|
200
|
+
tokenFile,
|
|
201
|
+
"--repo",
|
|
202
|
+
repo,
|
|
203
|
+
...watchFlags,
|
|
204
|
+
];
|
|
205
|
+
if (platform === "darwin") {
|
|
206
|
+
return {
|
|
207
|
+
platform,
|
|
208
|
+
label,
|
|
209
|
+
unitPath: join(home, "Library", "LaunchAgents", `${label}.plist`),
|
|
210
|
+
unitContent: launchdPlist({ label, command: [process.execPath, fileURLToPath(new URL("./controller-service.js", import.meta.url)), ...command], workingDirectory: repo, pathEnv, logPath }),
|
|
211
|
+
logPath,
|
|
212
|
+
tokenFile,
|
|
213
|
+
bin,
|
|
214
|
+
entry,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (platform === "win32") {
|
|
218
|
+
// Task Scheduler is Windows' launchd: a logon-triggered task, restarted
|
|
219
|
+
// on failure, created from an XML definition — no admin, no Service
|
|
220
|
+
// wrapper. schtasks does not redirect output, so the action runs
|
|
221
|
+
// through cmd with an append redirection into the same log file the
|
|
222
|
+
// other platforms use. RestartOnFailure covers a crash only; a clean
|
|
223
|
+
// exit would end the task, so the action itself is a bounded restart
|
|
224
|
+
// loop (15 s between runs, like ThrottleInterval) — `schtasks /End`
|
|
225
|
+
// (explicit stop) ends the loop's whole tree.
|
|
226
|
+
const inner = [quoteWin(bin), ...binArgs.map(quoteWin), "watch",
|
|
227
|
+
"--runner", quoteWin(runner), "--token-file", quoteWin(tokenFile),
|
|
228
|
+
"--repo", quoteWin(repo), ...watchFlags.map(quoteWin)].join(" ");
|
|
229
|
+
const pathPrefix = pathEnv === "" ? "" : `set "PATH=${pathEnv};%PATH%" && `;
|
|
230
|
+
const cmdArguments = `/d /s /c "${pathPrefix}for /L %i in (1,0,2) do (${inner} >> ${quoteWin(logPath)} 2>&1 & timeout /t 15 /nobreak >nul)"`;
|
|
231
|
+
const unitContent = `<?xml version="1.0" encoding="UTF-16"?>
|
|
232
|
+
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
233
|
+
<RegistrationInfo>
|
|
234
|
+
<Description>toolroll watch — ${xml(repo)}</Description>
|
|
235
|
+
</RegistrationInfo>
|
|
236
|
+
<Triggers>
|
|
237
|
+
<LogonTrigger>
|
|
238
|
+
<Enabled>true</Enabled>
|
|
239
|
+
</LogonTrigger>
|
|
240
|
+
</Triggers>
|
|
241
|
+
<Settings>
|
|
242
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
243
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
244
|
+
<StartWhenAvailable>true</StartWhenAvailable>
|
|
245
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
246
|
+
<RestartOnFailure>
|
|
247
|
+
<Interval>PT1M</Interval>
|
|
248
|
+
<Count>999</Count>
|
|
249
|
+
</RestartOnFailure>
|
|
250
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
251
|
+
</Settings>
|
|
252
|
+
<Actions Context="Author">
|
|
253
|
+
<Exec>
|
|
254
|
+
<Command>cmd.exe</Command>
|
|
255
|
+
<Arguments>${xml(cmdArguments)}</Arguments>
|
|
256
|
+
</Exec>
|
|
257
|
+
</Actions>
|
|
258
|
+
</Task>
|
|
259
|
+
`;
|
|
260
|
+
return {
|
|
261
|
+
platform,
|
|
262
|
+
label,
|
|
263
|
+
unitPath: join(configDir, "daemon", `${label}.xml`),
|
|
264
|
+
unitContent,
|
|
265
|
+
logPath,
|
|
266
|
+
tokenFile,
|
|
267
|
+
bin,
|
|
268
|
+
entry,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
// Restart=always: a clean exit the operator did not ask for comes back
|
|
272
|
+
// too; `systemctl --user stop` / `disable --now` (explicit stop) does
|
|
273
|
+
// not trigger a restart. Whether the user manager itself survives logout
|
|
274
|
+
// is linger (`loginctl enable-linger`) — documented, not assumed.
|
|
275
|
+
const unitContent = `[Unit]
|
|
276
|
+
Description=toolroll watch — ${repo}
|
|
277
|
+
|
|
278
|
+
[Service]
|
|
279
|
+
Environment=${systemdEscape(`PATH=${pathEnv}`)}
|
|
280
|
+
ExecStart=${command.map(systemdEscape).join(" ")}
|
|
281
|
+
WorkingDirectory=${systemdEscape(repo)}
|
|
282
|
+
Restart=always
|
|
283
|
+
RestartSec=15
|
|
284
|
+
|
|
285
|
+
StandardOutput=append:${logPath}
|
|
286
|
+
StandardError=append:${logPath}
|
|
287
|
+
|
|
288
|
+
[Install]
|
|
289
|
+
WantedBy=default.target
|
|
290
|
+
`;
|
|
291
|
+
return {
|
|
292
|
+
platform,
|
|
293
|
+
label,
|
|
294
|
+
unitPath: join(home, ".config", "systemd", "user", `${label}.service`),
|
|
295
|
+
unitContent,
|
|
296
|
+
logPath,
|
|
297
|
+
tokenFile,
|
|
298
|
+
bin,
|
|
299
|
+
entry,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/** The desktop shell's always-on controller: the same launchd contract, pointed at the desktop host. */
|
|
303
|
+
export function planDesktopService(args) {
|
|
304
|
+
const home = args.home ?? homedir();
|
|
305
|
+
const logPath = join(args.stateDir, "service.log");
|
|
306
|
+
return {
|
|
307
|
+
platform: "darwin",
|
|
308
|
+
label: args.label,
|
|
309
|
+
unitPath: join(home, "Library", "LaunchAgents", `${args.label}.plist`),
|
|
310
|
+
unitContent: launchdPlist({
|
|
311
|
+
label: args.label,
|
|
312
|
+
command: [args.node, args.helper, "serve", "--state", args.stateDir],
|
|
313
|
+
workingDirectory: args.stateDir,
|
|
314
|
+
pathEnv: args.pathEnv ?? process.env["PATH"] ?? "",
|
|
315
|
+
logPath,
|
|
316
|
+
associatedBundleId: args.associatedBundleId ?? "com.standing-orders.desktop",
|
|
317
|
+
...(args.environment === undefined ? {} : { environment: args.environment }),
|
|
318
|
+
}),
|
|
319
|
+
logPath,
|
|
320
|
+
bin: args.node,
|
|
321
|
+
entry: args.helper,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/** The file on disk does not identify the definition launchd has loaded. */
|
|
325
|
+
function loadedLaunchdMatches(unit, status) {
|
|
326
|
+
// Either name: a unit written before the rename carries only the older one.
|
|
327
|
+
const wanted = /<key>(?:TOOLROLL|STANDING_ORDERS)_SERVICE_DIGEST<\/key>\s*<string>([a-f0-9]{64})<\/string>/.exec(unit)?.[1];
|
|
328
|
+
const actual = /(?:TOOLROLL|STANDING_ORDERS)_SERVICE_DIGEST\s*=>\s*([a-f0-9]{64})/.exec(status)?.[1];
|
|
329
|
+
return wanted !== undefined && actual === wanted;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* The shared launchd road. Idempotent start: a loaded job whose definition
|
|
333
|
+
* is unchanged is kickstarted WITHOUT -k (a running one is left alone; a
|
|
334
|
+
* stopped one starts); a loaded job whose definition changed is booted out,
|
|
335
|
+
* awaited, and bootstrapped afresh; an unloaded label is enabled (an
|
|
336
|
+
* explicit stop had disabled it) and bootstrapped. Nothing here kills a
|
|
337
|
+
* healthy service to prove it can.
|
|
338
|
+
*/
|
|
339
|
+
export async function installLaunchdService(definition, run) {
|
|
340
|
+
const uid = launchdUid();
|
|
341
|
+
const service = `gui/${uid}/${definition.label}`;
|
|
342
|
+
const before = existsSync(definition.unitPath) ? readFileSync(definition.unitPath, "utf8") : null;
|
|
343
|
+
let changed = before !== definition.unitContent;
|
|
344
|
+
mkdirSync(join(definition.logPath, ".."), { recursive: true });
|
|
345
|
+
mkdirSync(join(definition.unitPath, ".."), { recursive: true });
|
|
346
|
+
writeFileSync(definition.unitPath, definition.unitContent, { mode: 0o644 });
|
|
347
|
+
const status = await run("launchctl", ["print", service]);
|
|
348
|
+
const loaded = status.code === 0;
|
|
349
|
+
if (loaded)
|
|
350
|
+
changed ||= !loadedLaunchdMatches(definition.unitContent, status.stdout);
|
|
351
|
+
if (loaded && !changed) {
|
|
352
|
+
const started = await run("launchctl", ["kickstart", service]);
|
|
353
|
+
if (started.code !== 0)
|
|
354
|
+
return { ok: false, message: `loaded, but launchctl could not start the worker: ${firstLine(started.stderr) || `exit ${started.code}`}` };
|
|
355
|
+
return { ok: true, changed: false, action: "started" };
|
|
356
|
+
}
|
|
357
|
+
if (loaded) {
|
|
358
|
+
const replaced = await run("launchctl", ["bootout", service]);
|
|
359
|
+
if (replaced.code !== 0)
|
|
360
|
+
return { ok: false, message: `launchctl could not boot out the previous definition: ${firstLine(replaced.stderr) || `exit ${replaced.code}`}` };
|
|
361
|
+
if (!(await waitForLaunchdBootout(run, service))) {
|
|
362
|
+
return { ok: false, message: "launchd did not finish stopping the previous worker; try the install again" };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// An explicit stop disables the label; installing again is the only road back.
|
|
366
|
+
const enabled = await run("launchctl", ["enable", service]);
|
|
367
|
+
if (enabled.code !== 0)
|
|
368
|
+
return { ok: false, message: `launchctl could not enable the service: ${firstLine(enabled.stderr) || enabled.code}` };
|
|
369
|
+
const modern = await run("launchctl", ["bootstrap", `gui/${uid}`, definition.unitPath]);
|
|
370
|
+
if (modern.code === 0) {
|
|
371
|
+
// RunAtLoad starts the job; the kickstart (no -k: never a restart) gives
|
|
372
|
+
// the installer a synchronous boundary instead of "the plist parsed".
|
|
373
|
+
const started = await run("launchctl", ["kickstart", service]);
|
|
374
|
+
if (started.code === 0)
|
|
375
|
+
return { ok: true, changed, action: loaded ? "reloaded" : "bootstrapped" };
|
|
376
|
+
return { ok: false, message: `loaded, but launchctl could not start the worker: ${firstLine(started.stderr) || `exit ${started.code}`}` };
|
|
377
|
+
}
|
|
378
|
+
// Modern first, legacy fallback: `bootstrap` replaced `load` but older
|
|
379
|
+
// macOS answers only to the old verb.
|
|
380
|
+
const legacy = await run("launchctl", ["load", "-w", definition.unitPath]);
|
|
381
|
+
if (legacy.code === 0)
|
|
382
|
+
return { ok: true, changed, action: loaded ? "reloaded" : "bootstrapped" };
|
|
383
|
+
return { ok: false, message: `launchctl refused the unit: ${firstLine(modern.stderr) || firstLine(legacy.stderr) || `exit ${legacy.code}`}` };
|
|
384
|
+
}
|
|
385
|
+
/** Explicit stop: unload AND disable, so nothing relaunches it until a person installs again. */
|
|
386
|
+
export async function stopLaunchdService(definition, run) {
|
|
387
|
+
const service = `gui/${launchdUid()}/${definition.label}`;
|
|
388
|
+
const disabled = await run("launchctl", ["disable", service]);
|
|
389
|
+
if (disabled.code !== 0)
|
|
390
|
+
return { ok: false, message: `launchctl could not disable ${definition.label}: ${firstLine(disabled.stderr) || disabled.code}` };
|
|
391
|
+
const modern = await run("launchctl", ["bootout", service]);
|
|
392
|
+
if (modern.code !== 0)
|
|
393
|
+
await run("launchctl", ["unload", definition.unitPath]);
|
|
394
|
+
if (!(await waitForLaunchdBootout(run, service)))
|
|
395
|
+
return { ok: false, message: `launchctl did not stop ${definition.label}; the service is still loaded` };
|
|
396
|
+
return { ok: true, wasLoaded: modern.code === 0 };
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* The same repo's watch as installed under the label it had before the
|
|
400
|
+
* rename to Toolroll, when its unit is still on disk; null otherwise.
|
|
401
|
+
* Installing the new one stops and removes it, so two watches never run.
|
|
402
|
+
*/
|
|
403
|
+
export function installedLegacyDaemon(args) {
|
|
404
|
+
const legacy = planDaemon({ ...args, label: legacyLabelFor(args.repo) });
|
|
405
|
+
return "error" in legacy || !existsSync(legacy.unitPath) ? null : legacy;
|
|
406
|
+
}
|
|
407
|
+
/** Write the token 0600 and the unit, then hand the unit to the supervisor. */
|
|
408
|
+
export async function installDaemon(plan, token, run) {
|
|
409
|
+
writeFileSync(plan.tokenFile, `${token}\n`, { mode: 0o600 });
|
|
410
|
+
chmodSync(plan.tokenFile, 0o600);
|
|
411
|
+
if (plan.platform === "darwin")
|
|
412
|
+
return installLaunchdService(plan, run);
|
|
413
|
+
const before = existsSync(plan.unitPath) ? readFileSync(plan.unitPath, "utf8") : null;
|
|
414
|
+
const changed = before !== plan.unitContent;
|
|
415
|
+
mkdirSync(join(plan.logPath, ".."), { recursive: true });
|
|
416
|
+
mkdirSync(join(plan.unitPath, ".."), { recursive: true });
|
|
417
|
+
writeFileSync(plan.unitPath, plan.unitContent, { mode: 0o644 });
|
|
418
|
+
if (plan.platform === "win32") {
|
|
419
|
+
// /F replaces an existing definition, so re-install is idempotent. A
|
|
420
|
+
// task already running under an UNCHANGED definition is left alone; a
|
|
421
|
+
// changed one is ended and run again so the new action takes effect.
|
|
422
|
+
const created = await run("schtasks", ["/Create", "/TN", plan.label, "/XML", plan.unitPath, "/F"]);
|
|
423
|
+
if (created.code !== 0) {
|
|
424
|
+
return { ok: false, message: `schtasks /Create failed: ${firstLine(created.stderr) || `exit ${created.code}`}` };
|
|
425
|
+
}
|
|
426
|
+
const query = await run("schtasks", ["/Query", "/TN", plan.label, "/FO", "LIST", "/V"]);
|
|
427
|
+
const running = query.code === 0 && /Status:\s*Running/i.test(query.stdout);
|
|
428
|
+
if (running && !changed)
|
|
429
|
+
return { ok: true, changed: false, action: "running" };
|
|
430
|
+
if (running)
|
|
431
|
+
await run("schtasks", ["/End", "/TN", plan.label]);
|
|
432
|
+
const started = await run("schtasks", ["/Run", "/TN", plan.label]);
|
|
433
|
+
if (started.code !== 0) {
|
|
434
|
+
return { ok: false, message: `created, but schtasks /Run failed: ${firstLine(started.stderr)}` };
|
|
435
|
+
}
|
|
436
|
+
return { ok: true, changed, action: running ? "reloaded" : "started" };
|
|
437
|
+
}
|
|
438
|
+
const reload = await run("systemctl", ["--user", "daemon-reload"]);
|
|
439
|
+
if (reload.code !== 0) {
|
|
440
|
+
return { ok: false, message: `systemctl daemon-reload failed: ${firstLine(reload.stderr)}` };
|
|
441
|
+
}
|
|
442
|
+
const active = (await run("systemctl", ["--user", "is-active", plan.label])).stdout.trim() === "active";
|
|
443
|
+
if (active && changed) {
|
|
444
|
+
// `enable --now` leaves a running service on its OLD definition; only a
|
|
445
|
+
// restart loads the new one.
|
|
446
|
+
const restarted = await run("systemctl", ["--user", "restart", plan.label]);
|
|
447
|
+
if (restarted.code !== 0)
|
|
448
|
+
return { ok: false, message: `systemctl restart failed: ${firstLine(restarted.stderr)}` };
|
|
449
|
+
}
|
|
450
|
+
const enable = await run("systemctl", ["--user", "enable", "--now", plan.label]);
|
|
451
|
+
if (enable.code !== 0) {
|
|
452
|
+
return { ok: false, message: `systemctl enable failed: ${firstLine(enable.stderr)}` };
|
|
453
|
+
}
|
|
454
|
+
return { ok: true, changed, action: active ? (changed ? "reloaded" : "running") : "started" };
|
|
455
|
+
}
|
|
456
|
+
export async function uninstallDaemon(plan, run) {
|
|
457
|
+
if (plan.platform === "darwin") {
|
|
458
|
+
const stopped = await stopLaunchdService(plan, run);
|
|
459
|
+
if (!stopped.ok)
|
|
460
|
+
throw new Error(stopped.message);
|
|
461
|
+
}
|
|
462
|
+
else if (plan.platform === "win32") {
|
|
463
|
+
await run("schtasks", ["/End", "/TN", plan.label]);
|
|
464
|
+
await run("schtasks", ["/Delete", "/TN", plan.label, "/F"]);
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
await run("systemctl", ["--user", "disable", "--now", plan.label]);
|
|
468
|
+
}
|
|
469
|
+
let existed = true;
|
|
470
|
+
try {
|
|
471
|
+
rmSync(plan.unitPath);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
existed = false;
|
|
475
|
+
}
|
|
476
|
+
return { ok: true, existed };
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* The definition a label is INSTALLED under, read back from the unit file
|
|
480
|
+
* (the certificate's view: whatever is on disk is the baseline, so `stale`
|
|
481
|
+
* is false by construction and the runtime/entry are the installed ones).
|
|
482
|
+
* Null when no unit file exists for the label.
|
|
483
|
+
*/
|
|
484
|
+
export function installedServiceDefinition(args) {
|
|
485
|
+
const platform = args.platform ?? process.platform;
|
|
486
|
+
if (platform !== "darwin" && platform !== "linux" && platform !== "win32")
|
|
487
|
+
return null;
|
|
488
|
+
const home = args.home ?? homedir();
|
|
489
|
+
const unitPath = platform === "darwin" ? join(home, "Library", "LaunchAgents", `${args.label}.plist`)
|
|
490
|
+
: platform === "linux" ? join(home, ".config", "systemd", "user", `${args.label}.service`)
|
|
491
|
+
: join(args.configDir ?? home, "daemon", `${args.label}.xml`);
|
|
492
|
+
if (!existsSync(unitPath))
|
|
493
|
+
return null;
|
|
494
|
+
const unitContent = readFileSync(unitPath, "utf8");
|
|
495
|
+
let bin = "";
|
|
496
|
+
let entry = null;
|
|
497
|
+
if (platform === "darwin") {
|
|
498
|
+
const strings = [...(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(unitContent)?.[1] ?? "").matchAll(/<string>([^<]*)<\/string>/g)].map(match => unxml(match[1] ?? ""));
|
|
499
|
+
bin = strings[0] ?? "";
|
|
500
|
+
entry = strings[1] !== undefined && /\.(?:c|m)?js$/i.test(strings[1]) ? strings[1] : null;
|
|
501
|
+
}
|
|
502
|
+
else if (platform === "linux") {
|
|
503
|
+
const exec = /^ExecStart=(.*)$/m.exec(unitContent)?.[1] ?? "";
|
|
504
|
+
const parts = exec.match(/"(?:[^"\\]|\\.)*"|\S+/g)?.map(part => part.startsWith('"') ? part.slice(1, -1).replace(/\\(["\\])/g, "$1") : part) ?? [];
|
|
505
|
+
bin = parts[0] ?? "";
|
|
506
|
+
entry = parts[1] !== undefined && /\.(?:c|m)?js$/i.test(parts[1]) ? parts[1] : null;
|
|
507
|
+
}
|
|
508
|
+
const logPath = args.logPath ?? (/<key>StandardOutPath<\/key>\s*<string>([^<]*)<\/string>/.exec(unitContent)?.[1] ?? /^StandardOutput=append:(.*)$/m.exec(unitContent)?.[1] ?? "");
|
|
509
|
+
return { platform, label: args.label, unitPath, unitContent, logPath: unxml(logPath), bin, entry };
|
|
510
|
+
}
|
|
511
|
+
function unxml(text) {
|
|
512
|
+
return text.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
513
|
+
}
|
|
514
|
+
/** Runtime facts that make a loaded definition unable to become a working controller. */
|
|
515
|
+
export function runtimeProblems(definition) {
|
|
516
|
+
const problems = [];
|
|
517
|
+
if (!existsSync(definition.bin))
|
|
518
|
+
problems.push(`the service's runtime ${definition.bin} does not exist — reinstall to pin the current Node binary`);
|
|
519
|
+
if (definition.entry !== null && !existsSync(definition.entry))
|
|
520
|
+
problems.push(`the service's entry ${definition.entry} does not exist — reinstall from the current package`);
|
|
521
|
+
return problems;
|
|
522
|
+
}
|
|
523
|
+
export async function daemonStatus(plan, run) {
|
|
524
|
+
const problems = runtimeProblems(plan);
|
|
525
|
+
const installed = existsSync(plan.unitPath) ? readFileSync(plan.unitPath, "utf8") : null;
|
|
526
|
+
const facts = { problems, installedDigest: installed === null ? null : definitionDigest(installed), stale: installed !== null && installed !== plan.unitContent };
|
|
527
|
+
if (plan.platform === "darwin") {
|
|
528
|
+
const service = `gui/${launchdUid()}/${plan.label}`;
|
|
529
|
+
const answer = await run("launchctl", ["print", service]);
|
|
530
|
+
if (answer.code !== 0) {
|
|
531
|
+
const disabled = await run("launchctl", ["print-disabled", `gui/${launchdUid()}`]);
|
|
532
|
+
const pattern = new RegExp(`"${plan.label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"\\s*=>\\s*(disabled|true)`);
|
|
533
|
+
if (disabled.code === 0 && pattern.test(disabled.stdout)) {
|
|
534
|
+
return { state: "disabled", pid: null, detail: "disabled by an explicit stop; install again to re-enable", ...facts };
|
|
535
|
+
}
|
|
536
|
+
return { state: "not-installed", pid: null, detail: "launchd does not know the label", ...facts };
|
|
537
|
+
}
|
|
538
|
+
facts.stale ||= !loadedLaunchdMatches(plan.unitContent, answer.stdout);
|
|
539
|
+
const pid = /pid = (\d+)/.exec(answer.stdout)?.[1];
|
|
540
|
+
return pid === undefined
|
|
541
|
+
? { state: "loaded", pid: null, detail: "loaded, not currently running", ...facts }
|
|
542
|
+
: { state: "running", pid: Number(pid), detail: `running as pid ${pid}`, ...facts };
|
|
543
|
+
}
|
|
544
|
+
if (plan.platform === "win32") {
|
|
545
|
+
const answer = await run("schtasks", ["/Query", "/TN", plan.label, "/FO", "LIST", "/V"]);
|
|
546
|
+
if (answer.code !== 0) {
|
|
547
|
+
return { state: "not-installed", pid: null, detail: "the scheduler does not know the task", ...facts };
|
|
548
|
+
}
|
|
549
|
+
if (/Status:\s*Running/i.test(answer.stdout))
|
|
550
|
+
return { state: "running", pid: null, detail: "running (Task Scheduler does not expose the pid)", ...facts };
|
|
551
|
+
if (/Status:\s*Disabled/i.test(answer.stdout) || /Scheduled Task State:\s*Disabled/i.test(answer.stdout)) {
|
|
552
|
+
return { state: "disabled", pid: null, detail: "the task is disabled", ...facts };
|
|
553
|
+
}
|
|
554
|
+
return { state: "loaded", pid: null, detail: "installed, not currently running", ...facts };
|
|
555
|
+
}
|
|
556
|
+
const answer = await run("systemctl", ["--user", "is-active", plan.label]);
|
|
557
|
+
const active = answer.stdout.trim() === "active";
|
|
558
|
+
if (answer.code !== 0 && !active) {
|
|
559
|
+
const enabled = await run("systemctl", ["--user", "is-enabled", plan.label]);
|
|
560
|
+
const word = enabled.stdout.trim();
|
|
561
|
+
if (word === "disabled")
|
|
562
|
+
return { state: "disabled", pid: null, detail: "disabled by an explicit stop; install again to re-enable", ...facts };
|
|
563
|
+
if (word === "enabled" || word === "linked")
|
|
564
|
+
return { state: "loaded", pid: null, detail: answer.stdout.trim() || "inactive", ...facts };
|
|
565
|
+
return { state: "not-installed", pid: null, detail: answer.stdout.trim() || "inactive", ...facts };
|
|
566
|
+
}
|
|
567
|
+
const pidAnswer = await run("systemctl", ["--user", "show", "--property=MainPID", plan.label]);
|
|
568
|
+
const pid = Number(/MainPID=(\d+)/.exec(pidAnswer.stdout)?.[1] ?? 0);
|
|
569
|
+
return active
|
|
570
|
+
? { state: "running", pid: pid > 0 ? pid : null, detail: "active", ...facts }
|
|
571
|
+
: { state: "loaded", pid: null, detail: "installed, inactive", ...facts };
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* A supervisor PID is necessary but not sufficient: macOS can leave a
|
|
575
|
+
* process stuck behind a protected-folder access check before it ever
|
|
576
|
+
* opens the queue. The credentialed runner heartbeat is the end-to-end
|
|
577
|
+
* readiness receipt that proves a service reached the work loop — and a
|
|
578
|
+
* FRESH one, newer than what stood before the start, so a stale row from
|
|
579
|
+
* the previous incarnation cannot pass for the new controller.
|
|
580
|
+
*/
|
|
581
|
+
export async function awaitFreshHeartbeat(store, runnerName, heartbeatBefore, deadlineMs = 5_000, sleep = ms => new Promise(resolve => setTimeout(resolve, ms))) {
|
|
582
|
+
const deadline = Date.now() + deadlineMs;
|
|
583
|
+
for (;;) {
|
|
584
|
+
const live = store.getRunner(runnerName)?.runner ?? null;
|
|
585
|
+
if (live !== null && live.heartbeatAt !== heartbeatBefore && isAlive(live, new Date()))
|
|
586
|
+
return { ok: true, heartbeatAt: live.heartbeatAt };
|
|
587
|
+
if (Date.now() >= deadline)
|
|
588
|
+
return { ok: false };
|
|
589
|
+
await sleep(100);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function xml(text) {
|
|
593
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
594
|
+
}
|
|
595
|
+
/** cmd.exe quoting: wrap anything with spaces; embedded quotes are not survivable in cmd — refuse them upstream by construction (paths and flags here never carry them). */
|
|
596
|
+
function quoteWin(part) {
|
|
597
|
+
return /[\s&|<>^]/.test(part) ? `"${part}"` : part;
|
|
598
|
+
}
|
|
599
|
+
function systemdEscape(part) {
|
|
600
|
+
return /[\s"'\\]/.test(part) ? `"${part.replace(/(["\\])/g, "\\$1")}"` : part;
|
|
601
|
+
}
|
|
602
|
+
function firstLine(text) {
|
|
603
|
+
return text.trim().split("\n")[0] ?? "";
|
|
604
|
+
}
|