ctrlrelay 0.1.12__tar.gz → 0.2.1__tar.gz
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.
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/CHANGELOG.md +126 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/PKG-INFO +1 -1
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/operations.md +13 -8
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/pyproject.toml +1 -1
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/cli.py +572 -80
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/config.py +74 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/github.py +35 -6
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/state.py +150 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/worktree.py +176 -12
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/pipelines/dev.py +457 -51
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/pipelines/post_merge.py +150 -18
- ctrlrelay-0.2.1/tests/test_cli_repos.py +268 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_config.py +73 -1
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_dev_integration.py +148 -7
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_dev_pipeline.py +574 -11
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_github.py +39 -0
- ctrlrelay-0.2.1/tests/test_post_merge.py +1312 -0
- ctrlrelay-0.2.1/tests/test_state.py +594 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_worktree.py +485 -12
- ctrlrelay-0.1.12/tests/test_post_merge.py +0 -666
- ctrlrelay-0.1.12/tests/test_state.py +0 -271
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/dependabot.yml +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/workflows/build.yml +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/workflows/cla.yml +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/workflows/pages.yml +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/workflows/publish.yml +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.github/workflows/test.yml +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/.gitignore +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/CODE_OF_CONDUCT.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/CONTRIBUTING.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/LICENSE +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/README.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/SECURITY.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/config/orchestrator.yaml.example +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/Gemfile +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/_config.yml +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/architecture.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/bridge.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/cli.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/configuration.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/development.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/feedback-loop.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/getting-started.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/docs/index.md +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/__init__.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/bridge/__init__.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/bridge/__main__.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/bridge/protocol.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/bridge/server.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/bridge/telegram_handler.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/__init__.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/audit.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/checkpoint.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/dispatcher.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/obs.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/poller.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/pr_verifier.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/pr_watcher.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/core/scheduler.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/dashboard/__init__.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/dashboard/client.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/pipelines/__init__.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/pipelines/base.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/pipelines/secops.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/pipelines/task.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/transports/__init__.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/transports/base.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/transports/file_mock.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/src/ctrlrelay/transports/socket_client.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/__init__.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/conftest.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_audit.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_bridge_protocol.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_bridge_server.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_checkpoint.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_cli_ci_wait.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_cli_dev.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_cli_secops.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_cli_start.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_cli_version.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_dashboard_client.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_dispatcher.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_docs_site.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_obs.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_pipeline_base.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_poller.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_pr_verifier.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_pr_watcher.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_scheduler.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_secops_integration.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_secops_pipeline.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_task_pipeline.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_telegram_handler.py +0 -0
- {ctrlrelay-0.1.12 → ctrlrelay-0.2.1}/tests/test_transport.py +0 -0
|
@@ -7,6 +7,132 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.1] - 2026-04-28
|
|
11
|
+
|
|
12
|
+
Patch release. Fixes a long-standing UX bug where `ctrlrelay` could
|
|
13
|
+
only be invoked from a directory containing `config/orchestrator.yaml`.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`--config` now auto-discovers `orchestrator.yaml`.** Every CLI
|
|
18
|
+
command previously hardcoded a relative `config/orchestrator.yaml`
|
|
19
|
+
default, so running `ctrlrelay status` (or any other subcommand)
|
|
20
|
+
from `/tmp`, `$HOME`, or anywhere outside the project root failed
|
|
21
|
+
with `Config file not found: config/orchestrator.yaml`. The CLI
|
|
22
|
+
now resolves the config in this order:
|
|
23
|
+
|
|
24
|
+
1. The path passed to `--config` / `-c`, if any.
|
|
25
|
+
2. `$CTRLRELAY_CONFIG` (a new environment variable).
|
|
26
|
+
3. `./config/orchestrator.yaml`, walking up from the current
|
|
27
|
+
working directory to the filesystem root — matches how `git`
|
|
28
|
+
and `uv` find their config.
|
|
29
|
+
4. `$XDG_CONFIG_HOME/ctrlrelay/orchestrator.yaml` (defaults to
|
|
30
|
+
`~/.config/ctrlrelay/orchestrator.yaml`).
|
|
31
|
+
|
|
32
|
+
When nothing matches, the error now lists every location searched
|
|
33
|
+
so it's clear where to drop the file or which env var to set.
|
|
34
|
+
Daemon spawn paths (`ctrlrelay poller start` re-exec under
|
|
35
|
+
`--foreground`) pass the *resolved* absolute path to the child so
|
|
36
|
+
the daemon doesn't break when launchd starts it from `/`.
|
|
37
|
+
|
|
38
|
+
## [0.2.0] - 2026-04-27
|
|
39
|
+
|
|
40
|
+
Minor release. Adds bulk repo operations driven by
|
|
41
|
+
`config/orchestrator.yaml`, plus a batch of dev-pipeline correctness
|
|
42
|
+
fixes (worktree ownership, PR-CI lock contention, watcher
|
|
43
|
+
persistence) and a docs cleanup.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **`ctrlrelay repos clone-all/pull-all/status`** (closes #117).
|
|
48
|
+
Stand up an isolated workspace from the orchestrator manifest in
|
|
49
|
+
one command:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
ctrlrelay repos clone-all ~/code/myproject [--filter ORG] [--dry-run]
|
|
53
|
+
ctrlrelay repos pull-all ~/code/myproject [--filter ORG] [--dry-run]
|
|
54
|
+
ctrlrelay repos status ~/code/myproject [--filter ORG]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Each repo lands at `DEST/<org>/<repo>` derived from the `name:`
|
|
58
|
+
field; remote is `git@github.com:{name}.git`. The configured
|
|
59
|
+
`local_path` is ignored when `DEST` is passed, so existing
|
|
60
|
+
`~/Projects/...` checkouts stay untouched. Replaces the legacy
|
|
61
|
+
`bkp/sync` shell scripts that broke when the manifest was
|
|
62
|
+
archived during the rewrite.
|
|
63
|
+
- **`RepoConfig.name` validator.** Repo names are now validated
|
|
64
|
+
against `^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$` at config load.
|
|
65
|
+
Rejects `..`, extra slashes, and shell metacharacters before they
|
|
66
|
+
reach a clone target — defense in depth for the new bulk
|
|
67
|
+
commands.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- **Watchers persist across poller restarts** (closes #57). Adds
|
|
72
|
+
a `pr_watches` state-db table so in-flight merge watchers
|
|
73
|
+
survive launchd kickstart, crashes, and reboots. Before this,
|
|
74
|
+
any PR sitting in review across a poller restart silently lost
|
|
75
|
+
its post-merge automation (issue auto-close + Telegram
|
|
76
|
+
notification) for the rest of its 7-day window. The poller now
|
|
77
|
+
rehydrates surviving rows on startup and spawns one watcher
|
|
78
|
+
task per row.
|
|
79
|
+
- **Repo lock released during PR CI verification** (closes #29).
|
|
80
|
+
`run_dev_issue` used to hold the per-repo lock through the
|
|
81
|
+
`PRVerifier.wait_for_checks` polling window — a pure `gh` poll
|
|
82
|
+
that can run for up to 30 minutes — which made every peer
|
|
83
|
+
session targeting the same repo report "Repository locked by
|
|
84
|
+
another session" while no git work was in flight. The lock is
|
|
85
|
+
now released before `verify`, reacquired only if a `request_fix`
|
|
86
|
+
follow-up needs to spawn an agent against the worktree, and
|
|
87
|
+
released again on cleanup. `CancelledError` during the unlocked
|
|
88
|
+
window propagates without leaking a lock row.
|
|
89
|
+
- **Branch ownership signal survives delete+recreate** (closes #51).
|
|
90
|
+
`create_worktree_with_new_branch` now returns
|
|
91
|
+
`(path, created_fresh)` so the caller knows whether THIS session
|
|
92
|
+
created the branch (fresh from default, or via the stale-merged
|
|
93
|
+
delete+recreate path). Before #51, `run_dev_issue` snapshotted
|
|
94
|
+
`branch_preexisted` BEFORE the call; the snapshot went stale the
|
|
95
|
+
moment the helper detected a fully-merged local branch and
|
|
96
|
+
deleted+recreated it. A FAILED cleanup would then skip
|
|
97
|
+
`delete_branch` and leak partial commits into the next retry.
|
|
98
|
+
- **Refuse reuse when branch still backs an open PR** (closes #52).
|
|
99
|
+
`create_worktree_with_new_branch` now probes GitHub (via
|
|
100
|
+
`GitHubCLI.list_prs(head=...)`) before reusing an existing local
|
|
101
|
+
branch. If an open PR still backs it (prior DONE session whose
|
|
102
|
+
PR is unmerged, or any external source), raises `WorktreeError`
|
|
103
|
+
with the PR number and a concrete operator action instead of
|
|
104
|
+
silently hijacking the reviewer's already-reviewed branch or
|
|
105
|
+
tripping "A pull request already exists" at `gh pr create`.
|
|
106
|
+
- **`pull-all` checks subprocess return codes.** `git status`
|
|
107
|
+
failure no longer treats an empty stdout as "clean" and proceeds
|
|
108
|
+
to pull a corrupt repo. `git fetch` failure on a dirty tree is
|
|
109
|
+
now reported as `failed` instead of silently being counted as
|
|
110
|
+
`dirty — fetched only`.
|
|
111
|
+
- **`status` no longer crashes on edge cases.** `git rev-list`
|
|
112
|
+
parsing wrapped in a helper that returns 0 on any non-zero
|
|
113
|
+
return code or non-numeric output, instead of raising on
|
|
114
|
+
`int(ahead)`.
|
|
115
|
+
|
|
116
|
+
### Changed
|
|
117
|
+
|
|
118
|
+
- **Docs use `com.example.*` placeholder for launchd labels**
|
|
119
|
+
(closes #23). The launchd plist examples and `launchctl`
|
|
120
|
+
commands no longer hard-code `com.ainvirion.ctrlrelay-*` as the
|
|
121
|
+
job label. Anyone copying the docs verbatim picked up that label
|
|
122
|
+
too, which is fine until two forks of the project share a
|
|
123
|
+
machine. Swapped to `com.example.ctrlrelay-*` with a one-line
|
|
124
|
+
note directing readers to use a reverse-DNS prefix they own.
|
|
125
|
+
|
|
126
|
+
### Operator notes
|
|
127
|
+
|
|
128
|
+
- Upgrade via `uv tool install ctrlrelay@latest --force` and
|
|
129
|
+
restart poller + bridge.
|
|
130
|
+
No schema or config changes — the new `pr_watches` state-db
|
|
131
|
+
table is created idempotently on first start.
|
|
132
|
+
- New workflow: `ctrlrelay repos clone-all ~/code/myproject` to
|
|
133
|
+
stand up a fresh workspace, `repos pull-all` to refresh it.
|
|
134
|
+
Existing `~/Projects/...` checkouts are not touched.
|
|
135
|
+
|
|
10
136
|
## [0.1.12] - 2026-04-22
|
|
11
137
|
|
|
12
138
|
Patch release. Closes #90 — three related polish items on the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctrlrelay
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Local-first orchestrator for headless coding agents across multiple GitHub repos
|
|
5
5
|
Project-URL: Homepage, https://github.com/AInvirion/ctrlrelay
|
|
6
6
|
Project-URL: Documentation, https://ainvirion.github.io/ctrlrelay/
|
|
@@ -33,7 +33,12 @@ and restart on failure — the unit examples below already do this.
|
|
|
33
33
|
Save plist files under `~/Library/LaunchAgents/`. Use the **absolute** path to
|
|
34
34
|
`ctrlrelay` (run `which ctrlrelay` to find it) — launchd's PATH is minimal.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
The job labels below use `com.example.ctrlrelay-*` as a placeholder. Pick a
|
|
37
|
+
reverse-DNS prefix you own (e.g. `com.yourname.ctrlrelay-*`) and use it
|
|
38
|
+
consistently across the filename, the `<Label>` value, and the `launchctl`
|
|
39
|
+
commands so `launchctl list` output is unambiguous.
|
|
40
|
+
|
|
41
|
+
`~/Library/LaunchAgents/com.example.ctrlrelay-bridge.plist`:
|
|
37
42
|
|
|
38
43
|
{% raw %}
|
|
39
44
|
```xml
|
|
@@ -42,7 +47,7 @@ Save plist files under `~/Library/LaunchAgents/`. Use the **absolute** path to
|
|
|
42
47
|
<plist version="1.0">
|
|
43
48
|
<dict>
|
|
44
49
|
<key>Label</key>
|
|
45
|
-
<string>com.
|
|
50
|
+
<string>com.example.ctrlrelay-bridge</string>
|
|
46
51
|
<key>ProgramArguments</key>
|
|
47
52
|
<array>
|
|
48
53
|
<string>/opt/homebrew/bin/ctrlrelay</string>
|
|
@@ -72,7 +77,7 @@ Save plist files under `~/Library/LaunchAgents/`. Use the **absolute** path to
|
|
|
72
77
|
```
|
|
73
78
|
{% endraw %}
|
|
74
79
|
|
|
75
|
-
`~/Library/LaunchAgents/com.
|
|
80
|
+
`~/Library/LaunchAgents/com.example.ctrlrelay-poller.plist`:
|
|
76
81
|
|
|
77
82
|
{% raw %}
|
|
78
83
|
```xml
|
|
@@ -81,7 +86,7 @@ Save plist files under `~/Library/LaunchAgents/`. Use the **absolute** path to
|
|
|
81
86
|
<plist version="1.0">
|
|
82
87
|
<dict>
|
|
83
88
|
<key>Label</key>
|
|
84
|
-
<string>com.
|
|
89
|
+
<string>com.example.ctrlrelay-poller</string>
|
|
85
90
|
<key>ProgramArguments</key>
|
|
86
91
|
<array>
|
|
87
92
|
<string>/opt/homebrew/bin/ctrlrelay</string>
|
|
@@ -122,16 +127,16 @@ Create the log directory and load the agents:
|
|
|
122
127
|
|
|
123
128
|
```bash
|
|
124
129
|
mkdir -p ~/.ctrlrelay/logs
|
|
125
|
-
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.
|
|
126
|
-
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.
|
|
130
|
+
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.ctrlrelay-bridge.plist
|
|
131
|
+
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.ctrlrelay-poller.plist
|
|
127
132
|
```
|
|
128
133
|
|
|
129
134
|
Manage them:
|
|
130
135
|
|
|
131
136
|
```bash
|
|
132
137
|
launchctl list | grep ctrlrelay # check loaded
|
|
133
|
-
launchctl bootout gui/$(id -u)/com.
|
|
134
|
-
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.
|
|
138
|
+
launchctl bootout gui/$(id -u)/com.example.ctrlrelay-poller # stop
|
|
139
|
+
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.ctrlrelay-poller.plist # start
|
|
135
140
|
```
|
|
136
141
|
|
|
137
142
|
### Linux — systemd
|