ctrlrelay 0.3.0__tar.gz → 0.4.0__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.
Files changed (112) hide show
  1. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.gitignore +11 -0
  2. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/CHANGELOG.md +53 -0
  3. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/PKG-INFO +1 -1
  4. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/cli.md +45 -0
  5. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/configuration.md +3 -6
  6. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/getting-started.md +44 -59
  7. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/pyproject.toml +1 -1
  8. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/cli.py +337 -18
  9. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/config.py +35 -15
  10. ctrlrelay-0.4.0/src/ctrlrelay/setup.py +427 -0
  11. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_cli_repos.py +13 -7
  12. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_config.py +27 -11
  13. ctrlrelay-0.4.0/tests/test_setup.py +531 -0
  14. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  15. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  16. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/dependabot.yml +0 -0
  18. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/workflows/build.yml +0 -0
  19. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/workflows/cla.yml +0 -0
  20. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/workflows/pages.yml +0 -0
  21. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/workflows/publish.yml +0 -0
  22. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/.github/workflows/test.yml +0 -0
  23. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  24. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/CONTRIBUTING.md +0 -0
  25. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/LICENSE +0 -0
  26. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/README.md +0 -0
  27. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/SECURITY.md +0 -0
  28. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/config/orchestrator.yaml.example +0 -0
  29. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/Gemfile +0 -0
  30. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/_config.yml +0 -0
  31. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/architecture.md +0 -0
  32. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/bridge.md +0 -0
  33. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/development.md +0 -0
  34. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/feedback-loop.md +0 -0
  35. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/index.md +0 -0
  36. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/operations.md +0 -0
  37. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/docs/personalization.md +0 -0
  38. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/__init__.py +0 -0
  39. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/bridge/__init__.py +0 -0
  40. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/bridge/__main__.py +0 -0
  41. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/bridge/protocol.py +0 -0
  42. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/bridge/server.py +0 -0
  43. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/bridge/telegram_handler.py +0 -0
  44. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/__init__.py +0 -0
  45. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/audit.py +0 -0
  46. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/checkpoint.py +0 -0
  47. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/dispatcher.py +0 -0
  48. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/github.py +0 -0
  49. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/obs.py +0 -0
  50. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/poller.py +0 -0
  51. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/pr_verifier.py +0 -0
  52. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/pr_watcher.py +0 -0
  53. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/scheduler.py +0 -0
  54. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/state.py +0 -0
  55. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/core/worktree.py +0 -0
  56. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/dashboard/__init__.py +0 -0
  57. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/dashboard/client.py +0 -0
  58. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/install.py +0 -0
  59. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/personalization/__init__.py +0 -0
  60. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/personalization/manager.py +0 -0
  61. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/personalization/paths.py +0 -0
  62. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/pipelines/__init__.py +0 -0
  63. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/pipelines/base.py +0 -0
  64. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/pipelines/dev.py +0 -0
  65. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/pipelines/post_merge.py +0 -0
  66. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/pipelines/secops.py +0 -0
  67. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/pipelines/task.py +0 -0
  68. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/templates/__init__.py +0 -0
  69. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/templates/global-CLAUDE.md.snippet +0 -0
  70. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/templates/launchd/__init__.py +0 -0
  71. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/templates/launchd/bridge.plist.template +0 -0
  72. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/templates/launchd/poller.plist.template +0 -0
  73. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/templates/systemd/__init__.py +0 -0
  74. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/templates/systemd/bridge.service.template +0 -0
  75. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/templates/systemd/poller.service.template +0 -0
  76. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/transports/__init__.py +0 -0
  77. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/transports/base.py +0 -0
  78. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/transports/file_mock.py +0 -0
  79. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/src/ctrlrelay/transports/socket_client.py +0 -0
  80. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/__init__.py +0 -0
  81. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/conftest.py +0 -0
  82. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_audit.py +0 -0
  83. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_bridge_protocol.py +0 -0
  84. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_bridge_server.py +0 -0
  85. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_checkpoint.py +0 -0
  86. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_cli_ci_wait.py +0 -0
  87. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_cli_dev.py +0 -0
  88. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_cli_secops.py +0 -0
  89. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_cli_start.py +0 -0
  90. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_cli_version.py +0 -0
  91. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_dashboard_client.py +0 -0
  92. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_dev_integration.py +0 -0
  93. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_dev_pipeline.py +0 -0
  94. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_dispatcher.py +0 -0
  95. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_docs_site.py +0 -0
  96. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_github.py +0 -0
  97. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_install.py +0 -0
  98. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_obs.py +0 -0
  99. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_personalization.py +0 -0
  100. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_pipeline_base.py +0 -0
  101. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_poller.py +0 -0
  102. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_post_merge.py +0 -0
  103. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_pr_verifier.py +0 -0
  104. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_pr_watcher.py +0 -0
  105. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_scheduler.py +0 -0
  106. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_secops_integration.py +0 -0
  107. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_secops_pipeline.py +0 -0
  108. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_state.py +0 -0
  109. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_task_pipeline.py +0 -0
  110. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_telegram_handler.py +0 -0
  111. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_transport.py +0 -0
  112. {ctrlrelay-0.3.0 → ctrlrelay-0.4.0}/tests/test_worktree.py +0 -0
@@ -40,3 +40,14 @@ uv.lock
40
40
 
41
41
  # Operator-private notes (runbooks, personal cheatsheets).
42
42
  notes/
43
+
44
+ # Rendered service unit files. `ctrlrelay install launchd|systemd`
45
+ # substitutes CTRLRELAY_TELEGRAM_TOKEN into a copy of the in-package
46
+ # template and writes it to the system path (~/Library/LaunchAgents on
47
+ # macOS, ~/.config/systemd/user on Linux). The default install command
48
+ # never lands a rendered file inside the repo, but a future flag — or
49
+ # an operator running it with --target-dir pointed at the working tree —
50
+ # would. Defense-in-depth so a `git add .` after such a slip can't
51
+ # scoop the literal token into a commit.
52
+ *.plist
53
+ *.service
@@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-05-08
11
+
12
+ Minor release. One new feature plus one schema simplification:
13
+
14
+ - **`ctrlrelay setup`** — first-run onboarding command. Detects every
15
+ GitHub org you belong to, enumerates non-fork non-archived repos in
16
+ each, writes a fresh `~/.config/ctrlrelay/orchestrator.yaml`, clones
17
+ every repo to `~/Projects/<owner.lower()>/<repo>`, optionally
18
+ configures the personalization sync block, and optionally renders
19
+ launchd/systemd unit files. Replaces the multi-step manual playbook
20
+ operators previously had to follow on a new machine. Interactive by
21
+ default; `--yes` and `--owner` flags make it scriptable.
22
+ - **`paths.owner_aliases` deprecated; lowercase-org-folder convention.**
23
+ The path resolver now always derives `local_path` as
24
+ `${repo_root}/${owner.lower()}/${repo}` (closes #128). The previous
25
+ `owner_aliases` indirection caused `clone-all`/`pull-all`/`status`
26
+ to disagree with the dev pipeline on where a given repo lived.
27
+ Parsing of `owner_aliases` is retained so 0.3.x configs still load;
28
+ a `DeprecationWarning` fires when the block is non-empty.
29
+
30
+ ### Added
31
+
32
+ - **`ctrlrelay setup`** (closes the onboarding gap reported during the
33
+ v0.3.0 reinstall flow). Composes existing primitives (gh discovery,
34
+ config generation, `git clone`, `personalization init`, `install
35
+ launchd|systemd`) into a single command. Reads
36
+ `$CTRLRELAY_TELEGRAM_TOKEN` so the rendered plist isn't a
37
+ placeholder. Refuses to overwrite an existing `orchestrator.yaml`
38
+ without `--force`.
39
+ - **`repos clone-all` / `pull-all` / `status` accept DEST as
40
+ optional**. When omitted, each command operates on the
41
+ config-resolved `local_path` of every repo, so the same path
42
+ resolution serves the bulk commands and the dev pipeline. Pass DEST
43
+ to override (lands at `DEST/<owner.lower()>/<repo>`).
44
+
45
+ ### Changed
46
+
47
+ - **Path resolver: `owner.lower()` is the folder, always.** Closes #128.
48
+ Affects every command that touches `repo.local_path`. Operators on
49
+ v0.3.0 with mixed-case folders (e.g. `~/Projects/AInvirion/...`)
50
+ must either rename the folder, set per-repo `local_path` overrides,
51
+ or run `ctrlrelay setup --force` to land everything at the new
52
+ lowercase paths.
53
+
54
+ ### Migration from 0.3.0
55
+
56
+ - Drop `paths.owner_aliases` from `orchestrator.yaml` (or ignore the
57
+ deprecation warning until you next regenerate the config).
58
+ - Rename existing on-disk folders to lowercase (e.g.
59
+ `mv ~/Projects/AInvirion ~/Projects/ainvirion`) — or, easier, run
60
+ `ctrlrelay setup --force` to clone everything fresh under the new
61
+ convention.
62
+
10
63
  ## [0.3.0] - 2026-05-08
11
64
 
12
65
  Minor release. Three additive features: cross-machine **personalization
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctrlrelay
3
- Version: 0.3.0
3
+ Version: 0.4.0
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/
@@ -41,6 +41,51 @@ ctrlrelay [--version] <command> ...
41
41
  Every command that reads config accepts `--config` / `-c` to point at a
42
42
  non-default `orchestrator.yaml` (default: `config/orchestrator.yaml`).
43
43
 
44
+ ## `ctrlrelay setup`
45
+
46
+ ```bash
47
+ ctrlrelay setup [OPTIONS]
48
+ ```
49
+
50
+ First-run onboarding: detect GitHub orgs, enumerate their non-fork
51
+ non-archived repos, write a fresh `~/.config/ctrlrelay/orchestrator.yaml`,
52
+ clone every repo to `~/Projects/<owner.lower()>/<repo>`, and (optionally)
53
+ configure the personalization sync block plus install launchd/systemd unit
54
+ files. Composes the building blocks operators previously had to wire by hand.
55
+
56
+ The interactive flow asks one question at a time. `--yes` skips prompts and
57
+ accepts every default. Repeat `--owner` to lock the owner list non-interactively.
58
+
59
+ Key flags:
60
+
61
+ | Flag | Default | Notes |
62
+ |---|---|---|
63
+ | `--owner OWNER` | (interactive prompt) | Repeatable. When omitted, prompts to pick from accessible owners. |
64
+ | `--repo-root PATH` | `~/Projects` | Where repos land — `~/Projects/<owner.lower()>/<repo>`. |
65
+ | `--config-out PATH` | `~/.config/ctrlrelay/orchestrator.yaml` | Where to write the generated config. |
66
+ | `--timezone TZ` | `UTC` | IANA zone for cron schedules. |
67
+ | `--transport TRANSPORT` | `file_mock` | `telegram` or `file_mock`. |
68
+ | `--telegram-chat-id ID` | none | Required for `--transport=telegram`. |
69
+ | `--personalization-repo OWNER/REPO` | none | Adds a personalization block. |
70
+ | `--no-personalization` | off | Skip the personalization prompt entirely. |
71
+ | `--install-daemons` | (prompted) | Render and write launchd/systemd unit files. Reads `$CTRLRELAY_TELEGRAM_TOKEN` so the rendered plist isn't a placeholder. |
72
+ | `--skip-archived/--include-archived` | skip | gh repo list filter. |
73
+ | `--skip-forks/--include-forks` | skip | gh repo list filter. |
74
+ | `--yes` / `-y` | off | Accept every default; never prompt. |
75
+ | `--force` | off | Overwrite an existing `orchestrator.yaml` (and existing daemon plists when `--install-daemons`). |
76
+
77
+ Refuses to overwrite an existing config or daemon plist without `--force`.
78
+ Refuses to proceed if `gh auth status` fails — run `gh auth login` first.
79
+
80
+ After setup completes, the next manual steps are:
81
+
82
+ ```bash
83
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.ctrlrelay.ctrlrelay-poller.plist
84
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.ctrlrelay.ctrlrelay-bridge.plist
85
+ ```
86
+
87
+ (Or the systemd equivalents on Linux.)
88
+
44
89
  ## `ctrlrelay config`
45
90
 
46
91
  ### `config validate`
@@ -55,9 +55,6 @@ paths:
55
55
  skills: "~/.claude/skills"
56
56
  # Optional convention for repos[].local_path:
57
57
  repo_root: "~/Projects"
58
- owner_aliases:
59
- AInvirion: AINVIRION # GitHub owner -> on-disk folder name
60
- SemClone: SEMCL.ONE
61
58
  ```
62
59
 
63
60
  | Key | Type | Required | Description |
@@ -67,8 +64,8 @@ paths:
67
64
  | `bare_repos` | path | **yes** | Where ctrlrelay clones bare mirrors of each configured repo. |
68
65
  | `contexts` | path | **yes** | Per-repo context directory (looked up as `<contexts>/<owner-repo>/CLAUDE.md`). If a `CLAUDE.md` exists, it is symlinked into the worktree at session start. |
69
66
  | `skills` | path | **yes** | Claude Code skills directory used by `ctrlrelay skills audit` and `ctrlrelay skills list`. |
70
- | `repo_root` | path | no | Convention root for repo clones. When set, `repos[].local_path` may be omitted and is derived as `${repo_root}/${owner_aliases.get(owner, owner)}/${repo}`. Without `repo_root`, every repo entry must declare its own `local_path` (legacy behaviour). |
71
- | `owner_aliases` | object | no | Map of GitHub owner -> on-disk folder name. Lets the convention work when local folders use a vanity name (`SemClone` repos under `~/Projects/SEMCL.ONE/`). Lookup falls through to the literal owner if not present. |
67
+ | `repo_root` | path | no | Convention root for repo clones. When set, `repos[].local_path` may be omitted and is derived as `${repo_root}/${owner.lower()}/${repo}` (since v0.4.0). Without `repo_root`, every repo entry must declare its own `local_path` (legacy behaviour). |
68
+ | `owner_aliases` | object | no | **Deprecated since v0.4.0.** Pre-0.4.0 this remapped the on-disk folder name (e.g. `SemClone` -> `SEMCL.ONE`). The path resolver now always uses `owner.lower()`, so aliases are no longer consulted. Parsing is retained so 0.3.x configs still load; a `DeprecationWarning` is emitted when a non-empty mapping is supplied. To silence: drop the `owner_aliases` block, rename your on-disk folder to match `owner.lower()`, or set a per-repo `local_path` override for any that genuinely deviate. |
72
69
 
73
70
  ## claude
74
71
 
@@ -181,7 +178,7 @@ repos:
181
178
  | Key | Type | Required | Default | Description |
182
179
  |---|---|---|---|---|
183
180
  | `name` | string | **yes** | — | GitHub `owner/repo` slug. Used for `gh` calls and bare-repo / worktree naming. |
184
- | `local_path` | path | conditional | derived | Where the repo is checked out on disk for human use. Optional when `paths.repo_root` is set (then derived as `${repo_root}/${owner_aliases.get(owner, owner)}/${repo}`); required otherwise. An explicit value always wins as override. ctrlrelay itself uses bare mirrors under `paths.bare_repos`. |
181
+ | `local_path` | path | conditional | derived | Where the repo is checked out on disk for human use. Optional when `paths.repo_root` is set (then derived as `${repo_root}/${owner.lower()}/${repo}`, since v0.4.0); required otherwise. An explicit value always wins as override. ctrlrelay itself uses bare mirrors under `paths.bare_repos`. |
185
182
  | `dev_branch_template` | string | no | `"fix/issue-{n}"` | Branch-name template for dev-pipeline runs. `{n}` is replaced by the issue number. |
186
183
  | `automation` | object | no | (defaults) | See [automation](#repos-automation). |
187
184
  | `code_review` | object | no | (defaults) | Reserved for code-review policy. Currently unused by the bundled pipelines. |
@@ -28,75 +28,52 @@ Optional:
28
28
 
29
29
  ## Install
30
30
 
31
- Clone the repo and install in editable mode:
31
+ The recommended install is via `pipx`:
32
32
 
33
33
  ```bash
34
- git clone https://github.com/AInvirion/ctrlrelay.git
35
- cd ctrlrelay
34
+ pipx install ctrlrelay
35
+ ctrlrelay --version
36
+ ```
36
37
 
37
- # With uv (recommended):
38
- uv pip install -e .
38
+ Use `uv tool install ctrlrelay` if you prefer uv. For local development, see
39
+ [Development]({{ '/development/' | relative_url }}).
39
40
 
40
- # Or with pip:
41
- pip install -e .
42
- ```
41
+ ## First-run setup
42
+
43
+ Since v0.4.0, `ctrlrelay setup` does the boring parts of onboarding for you:
44
+ detect every GitHub org you belong to, list the non-fork non-archived repos
45
+ in each, write a fresh `orchestrator.yaml` to `~/.config/ctrlrelay/`, and
46
+ clone every repo to `~/Projects/<owner.lower()>/<repo>`. It can also
47
+ configure the personalization sync block and render daemon unit files for
48
+ launchd (macOS) or systemd (Linux).
43
49
 
44
- This installs the `ctrlrelay` console script. Verify:
50
+ Interactive (recommended for the first machine you set up):
45
51
 
46
52
  ```bash
47
- ctrlrelay --version
53
+ ctrlrelay setup
48
54
  ```
49
55
 
50
- ## Write your first config
56
+ Pick which owners to monitor when prompted. Setup will then enumerate repos,
57
+ write the config, clone everything, and ask whether you want personalization
58
+ sync and background services.
51
59
 
52
- ctrlrelay reads `config/orchestrator.yaml` by default. Start from the example:
60
+ Non-interactive (good for second/third machines once you know the answers):
53
61
 
54
62
  ```bash
55
- cp config/orchestrator.yaml.example config/orchestrator.yaml
63
+ ctrlrelay setup --yes \
64
+ --owner alice \
65
+ --owner AInvirion \
66
+ --transport telegram \
67
+ --telegram-chat-id 12345 \
68
+ --personalization-repo alice/dotclaude \
69
+ --install-daemons
56
70
  ```
57
71
 
58
- Open `config/orchestrator.yaml` and edit at least:
59
-
60
- - `timezone` your local IANA timezone.
61
- - `repos[].name` — the `owner/repo` slug of a repository you can push to.
62
- - `repos[].local_path` — where the local clone lives (or will live) on disk.
63
- *Or* set `paths.repo_root` to a workspace root and let the path be
64
- derived as `${repo_root}/${owner}/${repo}` (recommended for >1 repo).
65
-
66
- `node_id` is optional — when omitted it defaults to your machine's
67
- hostname (`socket.gethostname()`). Set it explicitly only if the
68
- hostname is meaningless (CI runners, containers).
69
-
70
- A minimal working config:
71
-
72
- ```yaml
73
- version: "1"
74
- timezone: "America/New_York"
75
-
76
- paths:
77
- state_db: "~/.ctrlrelay/state.db"
78
- worktrees: "~/.ctrlrelay/worktrees"
79
- bare_repos: "~/.ctrlrelay/repos"
80
- contexts: "~/.ctrlrelay/contexts"
81
- skills: "~/.claude/skills"
82
-
83
- claude:
84
- binary: "claude"
85
- default_timeout_seconds: 1800
86
- output_format: "json"
87
-
88
- transport:
89
- type: "file_mock"
90
- file_mock:
91
- inbox: "~/.ctrlrelay/inbox.txt"
92
- outbox: "~/.ctrlrelay/outbox.txt"
93
-
94
- repos:
95
- - name: "your-org/your-repo"
96
- local_path: "~/Projects/your-repo"
97
- ```
72
+ `--yes` accepts every default and never prompts. The Telegram bot token is
73
+ read from `$CTRLRELAY_TELEGRAM_TOKEN`; export it before running setup if you
74
+ want it baked into the rendered launchd plist.
98
75
 
99
- Validate it:
76
+ After setup completes, validate:
100
77
 
101
78
  ```bash
102
79
  ctrlrelay config validate
@@ -105,14 +82,22 @@ ctrlrelay config validate
105
82
  You should see something like:
106
83
 
107
84
  ```
108
- ✓ Config valid: config/orchestrator.yaml
85
+ ✓ Config valid: ~/.config/ctrlrelay/orchestrator.yaml
109
86
  Node ID: your-hostname.local
110
- Timezone: America/New_York
111
- Transport: file_mock
112
- Repos: 1
87
+ Timezone: UTC
88
+ Transport: telegram
89
+ Repos: 86
113
90
  ```
114
91
 
115
- For the full schema (every key, every default), see [Configuration]({{ '/configuration/' | relative_url }}).
92
+ For the full schema (every key, every default), see
93
+ [Configuration]({{ '/configuration/' | relative_url }}). To edit the file
94
+ later, open `~/.config/ctrlrelay/orchestrator.yaml`; ctrlrelay re-reads it on
95
+ every command. Re-running `ctrlrelay setup` refuses to clobber an existing
96
+ config without `--force`.
97
+
98
+ `node_id` is optional in the generated config — when omitted it defaults to
99
+ your machine's hostname (`socket.gethostname()`). Set it explicitly only if
100
+ the hostname is meaningless (CI runners, containers).
116
101
 
117
102
  ## Your first dev-pipeline run
118
103
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ctrlrelay"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Local-first orchestrator for headless coding agents across multiple GitHub repos"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"