propr-cli 0.8.3 → 0.8.5

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 (44) hide show
  1. package/README.md +4 -4
  2. package/dist/api/relay.js +10 -0
  3. package/dist/assets/env.example.txt +182 -59
  4. package/dist/auth/githubLogin.js +66 -0
  5. package/dist/commands/agentCommands.js +74 -0
  6. package/dist/commands/agentValidation.js +548 -0
  7. package/dist/commands/checkCommands.js +981 -76
  8. package/dist/commands/imageCommands.js +60 -0
  9. package/dist/commands/index.js +3 -0
  10. package/dist/commands/initStack.js +50 -1
  11. package/dist/commands/relayCommands.js +45 -12
  12. package/dist/commands/setup/agents.js +185 -0
  13. package/dist/commands/setup/engine.js +956 -0
  14. package/dist/commands/setup/github.js +181 -0
  15. package/dist/commands/setup/sequential.js +501 -0
  16. package/dist/commands/setup/state.js +242 -0
  17. package/dist/commands/setup/types.js +85 -0
  18. package/dist/commands/setupCommand.js +85 -0
  19. package/dist/commands/stackCommands.js +14 -2
  20. package/dist/commands/systemCommands.js +49 -2
  21. package/dist/commands/tunnelCommand.js +562 -0
  22. package/dist/config/ConfigManager.js +22 -0
  23. package/dist/config/types.js +1 -0
  24. package/dist/index.js +14 -45
  25. package/dist/orchestrator/format.js +46 -0
  26. package/dist/orchestrator/index.js +7 -2
  27. package/dist/orchestrator/manifest.json +12 -11
  28. package/dist/orchestrator/orchestrator.mjs +872 -73
  29. package/dist/tui/AgentTableApp.js +86 -0
  30. package/dist/tui/CheckApp.js +202 -0
  31. package/dist/tui/SetupApp.js +586 -0
  32. package/dist/tui/SetupApp.test.js +172 -0
  33. package/dist/tui/app.js +84 -0
  34. package/dist/tui/render.js +28 -2
  35. package/dist/utils/envFile.js +45 -0
  36. package/dist/vendor/shared/githubEventIntakeMode.js +41 -0
  37. package/dist/vendor/shared/index.js +17 -0
  38. package/dist/vendor/shared/intakeModePrerequisites.js +76 -0
  39. package/dist/vendor/shared/modelDefinitions.js +4 -4
  40. package/dist/vendor/shared/proprCompatibility.js +70 -0
  41. package/dist/vendor/shared/proprServiceUrls.js +124 -0
  42. package/dist/vendor/shared/statusKeys.js +14 -0
  43. package/dist/vendor/shared/validateRoutingUrl.js +46 -0
  44. package/package.json +3 -3
package/README.md CHANGED
@@ -5,7 +5,7 @@ Command-line interface for interacting with the ProPR backend. ProPR enables AI-
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install -g @propr/cli
8
+ npm install -g propr-cli
9
9
  ```
10
10
 
11
11
  The host CLI requires Node.js 22 or newer. The Docker launcher image is separate
@@ -223,12 +223,12 @@ For OpenCode agents, install and authenticate OpenCode on the host before adding
223
223
 
224
224
  ```bash
225
225
  curl -fsSL https://opencode.ai/install | bash
226
- mkdir -p ~/.config/opencode ~/.opencode
226
+ mkdir -p ~/.config/opencode
227
227
  opencode auth login
228
228
  mkdir -p ~/.config/opencode/xdg-data/opencode && cp ~/.local/share/opencode/auth.json ~/.config/opencode/xdg-data/opencode/auth.json
229
229
  ```
230
230
 
231
- OpenCode stores `auth.json` under `~/.local/share/opencode`, but ProPR mounts the configured OpenCode config directory into the agent container. When using copied file-based auth, set `XDG_DATA_HOME=/home/node/.config/opencode/xdg-data` on the OpenCode agent. Legacy agents can keep `~/.opencode` as their `configPath`; new agents should use `~/.config/opencode`.
231
+ OpenCode stores `auth.json` under `~/.local/share/opencode`, but ProPR mounts the configured OpenCode config directory into the agent container. When using copied file-based auth, set `XDG_DATA_HOME=/home/node/.config/opencode/xdg-data` on the OpenCode agent. Use `~/.config/opencode` as the agent `configPath`.
232
232
 
233
233
  The example model `opencode-minimax-m3-free` is a built-in free OpenCode model. OpenCode's model list changes with auth providers; run `opencode models` after logging in and register any desired provider/model IDs with ProPR's `opencode-` prefix, such as `opencode-openai/gpt-5.5`. ProPR converts these IDs back to OpenCode's native `provider/model` syntax at execution time and does not add authenticated provider models by default.
234
234
  Dynamic OpenCode GitHub labels use the format `llm-<agent-alias>~<propr-opencode-model-id>`, for example `llm-opencode~opencode-openai/gpt-5.5`. The `~` separator is an intentional public contract — these labels are persisted on GitHub issues and resolved later for execution routing.
@@ -348,7 +348,7 @@ import {
348
348
  createConfigManager,
349
349
  createApiClient,
350
350
  resolveProject,
351
- } from '@propr/cli';
351
+ } from 'propr-cli';
352
352
 
353
353
  const config = await createConfigManager();
354
354
  const client = await createApiClient();
package/dist/api/relay.js CHANGED
@@ -58,6 +58,16 @@ async function relayRequest(options, path, method, body, init) {
58
58
  throw new Error("The relay returned a malformed JSON response.");
59
59
  }
60
60
  }
61
+ /**
62
+ * Identity probe: returns the GitHub user and the installations they may access
63
+ * (the same `/v1/auth/me` route the dashboard boots from). Used by `propr relay`
64
+ * to discover an installation id when none was passed explicitly. Note these are
65
+ * access-scoped (ownership or team membership); minting a relay token is stricter
66
+ * (owner-only), so a discovered installation may still 403 on enroll.
67
+ */
68
+ export function fetchAuthenticatedUser(options) {
69
+ return relayRequest(options, "/auth/me", "GET");
70
+ }
61
71
  export function enrollRelayToken(options, params) {
62
72
  return relayRequest(options, "/relay-tokens", "POST", {
63
73
  installation_id: params.installationId,
@@ -1,34 +1,128 @@
1
- # GitHub App Configuration
2
- GH_APP_ID=your_app_id
3
- GH_PRIVATE_KEY_PATH=./path/to/your-app-private-key.pem
4
- GH_INSTALLATION_ID=your_installation_id
1
+ # =============================================================================
2
+ # GitHub event intake mode
3
+ # =============================================================================
4
+ # How ProPR receives GitHub events. New installs use the hosted ProPR GitHub App
5
+ # with WebSocket routing — no public URL and no GitHub App of your own required.
6
+ # This is the default and the normal path; polling and direct webhook below are
7
+ # advanced opt-ins.
8
+ # routing_websocket — events stream over the ProPR routing WebSocket (default)
9
+ # polling — ProPR pulls events from the GitHub API (advanced opt-in)
10
+ # direct_webhook — GitHub delivers events to your own public /webhook URL
11
+ # (advanced; requires your own GitHub App)
12
+ #
13
+ # Migrating an existing install: the default is now routing_websocket. If you run
14
+ # your OWN GitHub App (the old polling/webhook setup), routing_websocket will not
15
+ # work for you — it needs the hosted ProPR App via the relay (relay auth mode).
16
+ # Keep your current behavior by setting this explicitly to `polling` or
17
+ # `direct_webhook`, or migrate to the hosted path with `propr relay enroll`.
18
+ GITHUB_EVENT_INTAKE_MODE=routing_websocket
5
19
 
6
- # Host path to the GitHub App private key (.pem). Recommended when running via
7
- # the `propr` CLI or launcher: the key is bind-mounted (read-only) into the app
8
- # containers and GH_PRIVATE_KEY_PATH above is overridden to point at it, so you
9
- # can keep the key anywhere on the host instead of staging it under data/.
10
- # Must be an absolute host path (no '~').
11
- # HOST_GH_PRIVATE_KEY=/home/your-user/propr/app-private-key.pem
12
-
13
- # GitHub auth: token relay (shared-app path) — ALTERNATIVE to the App private key
14
- # above. When PROPR_GH_RELAY_URL is set, the backend fetches short-lived
15
- # installation tokens from a vendor-run relay instead of minting them from a
16
- # private key, so you don't need to hold the shared App's key. You still set
17
- # GH_INSTALLATION_ID (which installation), but GH_PRIVATE_KEY_PATH /
18
- # HOST_GH_PRIVATE_KEY are not required in relay mode.
19
- # PROPR_GH_RELAY_URLhttps URL of the relay (http only allowed for localhost)
20
- # PROPR_GH_RELAY_TOKEN the durable relay credential issued for your install
21
- # PROPR_GH_RELAY_URL=https://relay.propr.dev/v1
20
+ # --- Routing WebSocket (default intake) --------------------------------------
21
+ # The hosted path: ProPR authenticates through the shared ProPR GitHub App via
22
+ # the token relay and streams events over the routing WebSocket. Run
23
+ # `propr relay enroll` to populate PROPR_GH_RELAY_URL / PROPR_GH_RELAY_TOKEN /
24
+ # GH_INSTALLATION_ID for you, or set them by hand below.
25
+ # PROPR_ROUTING_URL — routing WebSocket origin (wss://; ws:// only for localhost)
26
+ # PROPR_GH_RELAY_URL — token relay URL incl. version prefix
27
+ # (https://; http only for localhost)
28
+ # PROPR_GH_RELAY_TOKEN durable relay credential issued for your install
29
+ # GH_INSTALLATION_ID — which GitHub App installation ProPR acts on
30
+ # Both URLs default to the hosted relay (webhook.propr.dev) when unset, so you
31
+ # only need them below to point at a self-hosted relay.
32
+ # PROPR_ROUTING_URL=wss://webhook.propr.dev
33
+ # PROPR_GH_RELAY_URL=https://webhook.propr.dev/v1
34
+ # Optional transport keepalive override. The default is 300000 ms (5 minutes);
35
+ # lower it only if a network path closes otherwise-healthy WebSockets too quickly.
36
+ # PROPR_ROUTING_WS_PING_INTERVAL_MS=300000
37
+ # Per-install secrets — left commented so an unedited copy is not mistaken for a
38
+ # configured relay. Run `propr relay enroll` to fill these in, or uncomment and set
39
+ # them by hand. (Active placeholder values here could make intake/auth mode look
40
+ # configured when it is not.)
22
41
  # PROPR_GH_RELAY_TOKEN=your_relay_token
23
- # Optional: force a mode explicitly instead of inferring (app | relay | demo).
42
+ # GH_INSTALLATION_ID=your_installation_id
43
+ # Optional: force the auth mode explicitly instead of inferring (app | relay | demo).
44
+ # Relay mode is inferred automatically when PROPR_GH_RELAY_URL + PROPR_GH_RELAY_TOKEN
45
+ # are set, so you normally do not need this.
24
46
  # GH_AUTH_MODE=relay
25
47
 
48
+ # --- Hosted UI tunnel (v1, optional) -----------------------------------------
49
+ # Expose this local stack's API to the hosted control plane at
50
+ # https://app.propr.dev through a Cloudflare Tunnel, so you can drive a
51
+ # locally-running stack from the hosted UI. The tunnel publishes the API
52
+ # container (only /api/* and /socket.io/* are routed; the root URL returns 404);
53
+ # the UI bundle is served by app.propr.dev, not through the tunnel. Cloudflare
54
+ # forwards the tunnel to the Docker-internal http://api:4000, NOT host port 4000,
55
+ # so the published host port is irrelevant to the tunnel and cannot conflict with
56
+ # it. Off by default; local development is unaffected (API_PUBLIC_URL /
57
+ # FRONTEND_URL keep their localhost defaults below).
58
+ #
59
+ # Normal setup is CLI-first: ProPR Connect shows a one-time command such as
60
+ # `propr tunnel setup --token ... --url https://<id>.proxy.propr.dev --start`.
61
+ # Run that from this stack directory so the CLI writes these values and restarts
62
+ # the stack for you. The variables below are documented as a fallback for older
63
+ # CLI versions, manual recovery, or auditing what the setup command persists.
64
+ #
65
+ # Enable the tunnel:
66
+ # PROPR_UI_TUNNEL_TOKEN — Cloudflare Tunnel token; required to start. Setting it
67
+ # enables the tunnel by default, so the next `propr start`
68
+ # brings up the sidecar (unless you ran `propr tunnel off`).
69
+ # WARNING: this is a LIVE Cloudflare credential — anyone
70
+ # with it can route traffic through your tunnel. Do not
71
+ # commit, log, or share it; keep it in this .env only.
72
+ # PROPR_UI_TUNNEL_ENABLED — explicitly enable the tunnel (`true`/`1`). A token is
73
+ # STILL required — `propr check` fails if this is set
74
+ # without PROPR_UI_TUNNEL_TOKEN. Redundant when a token
75
+ # is set, since a token alone already enables the tunnel
76
+ # PROPR_INSTANCE_ID — this stack's instance id; must be a valid DNS label
77
+ # (letters, digits, hyphens; 1-63 chars). Derives the
78
+ # public URL https://<id>.proxy.propr.dev when no
79
+ # explicit URL is set
80
+ # PROPR_UI_PUBLIC_API_URL — explicit public API URL the hosted UI talks to (overrides the derived one)
81
+ # PROPR_CLOUDFLARED_IMAGE — cloudflared image (default: cloudflare/cloudflared:2024.12.2,
82
+ # a pinned tag; override only if you need a different build)
83
+ # PROPR_UI_TUNNEL_TOKEN=your_cloudflare_tunnel_token
84
+ # PROPR_INSTANCE_ID=abc123
85
+ # PROPR_UI_PUBLIC_API_URL=https://abc123.proxy.propr.dev
86
+ # PROPR_CLOUDFLARED_IMAGE=cloudflare/cloudflared:2024.12.2
87
+ #
88
+ # Why `.proxy.propr.dev`: each enabled stack is published under a per-instance
89
+ # hostname `<PROPR_INSTANCE_ID>.proxy.propr.dev`. The hosted UI at
90
+ # https://app.propr.dev discovers and reaches your stack's API through that
91
+ # single shared suffix, without you owning or registering a domain.
92
+ #
93
+ # Browser origin vs API host — they are DIFFERENT and must be set accordingly.
94
+ # The browser loads the UI from app.propr.dev; the API, Socket.IO, OAuth
95
+ # callback, and session cookie all live on the proxy host. In tunnel mode
96
+ # FRONTEND_URL, API_PUBLIC_URL, and GH_OAUTH_CALLBACK_URL are derived
97
+ # automatically (FRONTEND_URL -> https://app.propr.dev, API_PUBLIC_URL -> the
98
+ # proxy host, GH_OAUTH_CALLBACK_URL -> <proxy host>/api/auth/github/callback);
99
+ # set them only to override. You must still register the callback URL — derived
100
+ # or explicit — in your GitHub OAuth App.
101
+ # FRONTEND_URL=https://app.propr.dev
102
+ # API_PUBLIC_URL=https://abc123.proxy.propr.dev
103
+ # GH_OAUTH_CALLBACK_URL=https://abc123.proxy.propr.dev/api/auth/github/callback
104
+ #
105
+ # COOKIE_DOMAIN: leave UNSET for v1. The session cookie is host-only on the
106
+ # single `<id>.proxy.propr.dev` host — correct because that host and
107
+ # app.propr.dev share the propr.dev registrable domain (same-site). Setting
108
+ # COOKIE_DOMAIN (e.g. `.proxy.propr.dev`) would scope the cookie across every
109
+ # instance's subdomain and is not supported for v1 proxy sessions.
110
+ # COOKIE_DOMAIN=
111
+
26
112
  # Logging Configuration
27
113
  LOG_LEVEL=info
28
114
  NODE_ENV=development
29
115
 
30
116
  # Daemon Configuration
31
117
  GITHUB_REPOS_TO_MONITOR=owner/repo1,owner/repo2
118
+
119
+ # --- Polling intake (advanced opt-in) ----------------------------------------
120
+ # Polling is an explicit opt-in alternative to the default routing WebSocket.
121
+ # It is only used when GITHUB_EVENT_INTAKE_MODE=polling (set above); leave the
122
+ # intake mode at routing_websocket to keep the hosted path. In polling mode
123
+ # ProPR pulls events from the GitHub API on this interval using any usable
124
+ # GitHub auth (relay or your own App). POLLING_INTERVAL_MS is the poll period in
125
+ # milliseconds (default: 60000).
32
126
  POLLING_INTERVAL_MS=60000
33
127
 
34
128
  # Config Repository (for dynamic repository management)
@@ -55,10 +149,34 @@ COMMENT_BATCH_DELAY_MS=3000
55
149
  # and fallback paths fail. Defaults to 3600000 (1 hour).
56
150
  # SUMMARIZATION_QUOTA_COOLDOWN_MS=3600000
57
151
 
58
- # Webhook Configuration
59
- # Webhooks are received by the dashboard API service (port 4000)
60
- ENABLE_GITHUB_WEBHOOKS=false
61
- GH_WEBHOOK_SECRET=your-webhook-secret
152
+ # --- Direct webhook intake (advanced) ----------------------------------------
153
+ # Direct webhook is an advanced alternative to the default routing WebSocket.
154
+ # It is only used when GITHUB_EVENT_INTAKE_MODE=direct_webhook (set near the top
155
+ # of this file). This path requires your OWN GitHub App (not the hosted ProPR
156
+ # App) and a publicly reachable POST /webhook URL that GitHub can deliver events
157
+ # to — the endpoint is served by the dashboard API service (port 4000). Because
158
+ # of those requirements, most installs should stay on routing_websocket.
159
+ #
160
+ # Own GitHub App credentials (app auth mode — required for direct webhook):
161
+ # GH_APP_ID — your GitHub App's numeric id
162
+ # GH_PRIVATE_KEY_PATH — path to your App's private key (.pem)
163
+ # GH_INSTALLATION_ID — which installation to act on (set in the routing
164
+ # section above; reused here)
165
+ # GH_APP_ID=your_app_id
166
+ # GH_PRIVATE_KEY_PATH=./path/to/your-app-private-key.pem
167
+ # Host path to the GitHub App private key (.pem). Recommended when running via
168
+ # the `propr` CLI or launcher: the key is bind-mounted (read-only) into the app
169
+ # containers and GH_PRIVATE_KEY_PATH above is overridden to point at it, so you
170
+ # can keep the key anywhere on the host instead of staging it under data/.
171
+ # Must be an absolute host path (no '~').
172
+ # HOST_GH_PRIVATE_KEY=/home/your-user/propr/app-private-key.pem
173
+ # Shared secret GitHub signs webhook deliveries with; required for this mode.
174
+ # GH_WEBHOOK_SECRET=your-webhook-secret
175
+
176
+ # Deprecated: ENABLE_GITHUB_WEBHOOKS no longer selects the intake mode. Use
177
+ # GITHUB_EVENT_INTAKE_MODE instead (routing_websocket | polling | direct_webhook).
178
+ # Documented here only so existing .env files are recognized; do not rely on it.
179
+ # ENABLE_GITHUB_WEBHOOKS=false
62
180
 
63
181
  # System Task Authorization
64
182
  # Secret used to sign system task requests (e.g., revert operations)
@@ -104,19 +222,14 @@ ANTIGRAVITY_TIMEOUT_MS=300000
104
222
  OPENCODE_TIMEOUT_MS=3600000
105
223
  # Launcher credential mount paths (required when using docker/launcher).
106
224
  # HOST_OPENCODE_XDG_DIR points to the XDG config directory
107
- # (/home/your-user/.config/opencode)
108
- # and takes precedence over HOST_OPENCODE_DIR.
109
- # HOST_OPENCODE_DIR is accepted as a fallback alias for HOST_OPENCODE_XDG_DIR.
110
- # HOST_OPENCODE_LEGACY_DIR points to the legacy /home/your-user/.opencode directory.
111
- # The saved agent configPath still controls the runtime mount; the launcher
112
- # sets OPENCODE_CONFIG_PATH only as the default path for worker/API processes.
225
+ # (/home/your-user/.config/opencode). The saved agent configPath still controls
226
+ # the runtime mount; the launcher sets OPENCODE_CONFIG_PATH only as the default
227
+ # path for worker/API processes.
113
228
  # HOST_OPENCODE_DATA_DIR points to normal opencode auth data
114
229
  # (/home/your-user/.local/share/opencode). Set it for launcher deployments
115
230
  # so normal `opencode auth login` credentials are visible to spawned OpenCode
116
231
  # agent containers and can refresh auth metadata when required.
117
232
  # HOST_OPENCODE_XDG_DIR=/home/your-user/.config/opencode
118
- # HOST_OPENCODE_DIR=/home/your-user/.config/opencode
119
- # HOST_OPENCODE_LEGACY_DIR=/home/your-user/.opencode
120
233
  # HOST_OPENCODE_DATA_DIR=/home/your-user/.local/share/opencode
121
234
 
122
235
  # --- Mistral Vibe Configuration (only required when using a Vibe agent) ---
@@ -135,19 +248,31 @@ VIBE_TIMEOUT_MS=3600000
135
248
  # directory name intentionally differs from the variable name.
136
249
  # HOST_ANTIGRAVITY_DIR=/home/your-user/.gemini
137
250
  # HOST_VIBE_DIR=/home/your-user/.vibe
138
- # Required for Vibe Docker-outside-Docker: prompt files must be written to a
139
- # host-visible directory so spawned agent containers can bind-mount them.
140
- # Both should reference the same host path.
141
- # NOTE: These are required whenever Vibe agents are used with the launcher
142
- # (i.e. when MISTRAL_API_KEY is set), not only when HOST_VIBE_DIR is set.
143
- # Create the directory before starting: mkdir -p /tmp/propr-vibe-prompts
251
+ # Vibe Docker-outside-Docker writes prompt files to a host-visible directory so
252
+ # spawned agent containers can bind-mount them. When Vibe is enabled, the
253
+ # launcher defaults the host path to /tmp/propr-vibe-prompts-<uid>; the container
254
+ # path defaults to /tmp/propr-vibe-prompts. Set both only to override those
255
+ # locations.
144
256
  # VIBE_PROMPT_CACHE_DIR=/tmp/propr-vibe-prompts
145
- # HOST_VIBE_PROMPT_CACHE_DIR=/tmp/propr-vibe-prompts
257
+ # HOST_VIBE_PROMPT_CACHE_DIR=/tmp/propr-vibe-prompts-1000
146
258
 
147
259
  # Dashboard API Configuration
148
260
  DASHBOARD_API_PORT=4000
149
- # Required in normal and demo mode for auth redirects.
150
- FRONTEND_URL=http://localhost:5173
261
+ # Browser origin: required in normal and demo mode for CORS and auth redirects.
262
+ # Defaults to http://localhost:5173 when unset. In hosted UI tunnel mode this is
263
+ # derived automatically as the hosted UI origin (https://app.propr.dev) — NOT the
264
+ # proxy host — so leave it COMMENTED to let derivation win. An uncommented value
265
+ # here always overrides the derived origin (set one only to force a custom
266
+ # origin). See the "Hosted UI tunnel" section above.
267
+ # FRONTEND_URL=http://localhost:5173
268
+ # Public URL the API is reached at (auth redirects, attachment links, cookie
269
+ # security). Defaults to http://localhost:4000 when unset; set it to the
270
+ # https://<id>.proxy.propr.dev host when the hosted UI tunnel is enabled.
271
+ # API_PUBLIC_URL=http://localhost:4000
272
+ # Session cookie domain. Leave UNSET for v1 — including hosted UI tunnel proxy
273
+ # sessions, which run on a single <id>.proxy.propr.dev host (see the tunnel
274
+ # section above). Only set it for a custom multi-subdomain deployment.
275
+ # COOKIE_DOMAIN=
151
276
  # Optional comma-separated redirect hosts for auth preview flows.
152
277
  # Entries are exact host matches by default. Prefix with "." or "*." only for
153
278
  # parent domains where every subdomain is trusted, for example .preview.example.com.
@@ -155,9 +280,16 @@ FRONTEND_URL=http://localhost:5173
155
280
  # AUTH_REDIRECT_ALLOWED_HOSTS=preview.example.com
156
281
 
157
282
  # GitHub OAuth Configuration
283
+ # The OAuth callback is served by the API and is derived automatically when left
284
+ # unset: <proxy host>/api/auth/github/callback in hosted UI tunnel mode, or
285
+ # http://localhost:4000/api/auth/github/callback for local development. Leave it
286
+ # commented so enabling the tunnel (token/instance id) cannot leave a stale
287
+ # localhost callback that breaks hosted OAuth — an active localhost value is used
288
+ # as-is even in tunnel mode (the launcher only warns). Uncomment to override; then
289
+ # register that exact URL in your GitHub OAuth App.
158
290
  GH_OAUTH_CLIENT_ID=your_github_oauth_client_id
159
291
  GH_OAUTH_CLIENT_SECRET=your_github_oauth_client_secret
160
- GH_OAUTH_CALLBACK_URL=http://localhost:4000/api/auth/github/callback
292
+ # GH_OAUTH_CALLBACK_URL=http://localhost:4000/api/auth/github/callback
161
293
  SESSION_SECRET=your-session-secret-here
162
294
 
163
295
  # Bearer Token Authentication (CLI)
@@ -174,25 +306,16 @@ PROPR_DEMO_MODE=false
174
306
  DB_FILENAME=./data/propr.sqlite
175
307
 
176
308
  # --- PR Preview Environment ---
309
+ # docker-compose.yml and scripts/deploy-pr.sh consume these local names.
310
+ # The PR Preview workflow maps repository variable PR_PREVIEW_STAGING_ENV_FILE
311
+ # to STAGING_ENV_FILE for deploys.
177
312
  # Path to staging .env file (provides base configuration for PR previews)
178
313
  # PR-specific values (ports, API URLs) are passed as inline overrides
179
314
  STAGING_ENV_FILE=/path/to/staging/.env
180
315
 
181
- # Path to staging database file for seeding PR preview environments
316
+ # Optional path to a staging database file for seeding PR preview environments.
317
+ # The workflow maps repository variable PR_PREVIEW_STAGING_DB_PATH to this env.
182
318
  STAGING_DB_PATH=/path/to/staging/data/propr.sqlite
183
319
 
184
- # Enable preview environment routing (routes webhooks to PR preview instances)
185
- ENABLE_PREVIEW_ROUTING=false
186
-
187
- # The ProPR repository in 'owner/repo' format. Label events from this repo
188
- # trigger processor assignment changes. Defaults to 'integry/propr'.
189
- PROPR_REPO=integry/propr
190
-
191
- # The label that designates a ProPR PR as the active processor for webhook routing.
192
- # When this label is added to a ProPR repo PR, that PR's preview instance becomes
193
- # the active processor. Defaults to 'preview-env'.
194
- PROCESSOR_LABEL=preview-env
195
-
196
- # Host address for forwarding webhooks to PR preview instances.
197
- # Defaults to 'http://host.docker.internal' for Docker environments.
198
- # HOST_GATEWAY_ADDRESS=http://host.docker.internal
320
+ # PR preview deploys are owned by the GitHub Actions workflow and run only after
321
+ # a maintainer adds the preview-env label to a pull request.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Shared GitHub authentication via the `gh` CLI.
3
+ *
4
+ * Both `propr login` (commands wired in index.ts) and `propr setup`'s relay
5
+ * enrollment need a stored GitHub token. This centralises the `gh`-CLI flow —
6
+ * reuse an existing `gh` session, or run the interactive `gh auth login` — so
7
+ * the two callers stay in sync. It returns a result object instead of writing to
8
+ * the console or calling process.exit, leaving presentation to the caller.
9
+ */
10
+ /** Scopes requested when launching the interactive `gh auth login`. */
11
+ const GH_LOGIN_SCOPES = "repo,read:org";
12
+ /**
13
+ * Authenticate with GitHub through the `gh` CLI and persist the token.
14
+ *
15
+ * Order: confirm `gh` is installed → reuse an existing `gh auth token` →
16
+ * (interactive only) run `gh auth login` and read the token back.
17
+ */
18
+ export async function loginWithGithubCli(configManager, options = {}) {
19
+ const { interactive = false, onLog } = options;
20
+ const { execSync, spawnSync } = await import("child_process");
21
+ // Require the gh CLI up front — every path below shells out to it.
22
+ try {
23
+ execSync("gh --version", { stdio: "ignore" });
24
+ }
25
+ catch {
26
+ return {
27
+ ok: false,
28
+ message: "GitHub CLI (gh) is not installed. Install it from https://cli.github.com, or run `propr login <token>` with a personal access token.",
29
+ };
30
+ }
31
+ // Reuse an existing gh session when one is already authenticated.
32
+ const existing = readGhToken(execSync);
33
+ if (existing) {
34
+ await configManager.setGithubToken(existing);
35
+ return { ok: true, token: existing, message: "Authenticated using your existing gh CLI session." };
36
+ }
37
+ if (!interactive) {
38
+ return {
39
+ ok: false,
40
+ message: "No gh CLI session found. Run `propr login` (or `gh auth login`) to authenticate first.",
41
+ };
42
+ }
43
+ // Launch the interactive browser/device login. Inherits stdio so the user can
44
+ // complete the gh prompts directly.
45
+ onLog?.("No existing gh session found. Starting interactive login…");
46
+ const result = spawnSync("gh", ["auth", "login", "-s", GH_LOGIN_SCOPES], { stdio: "inherit" });
47
+ if (result.status !== 0) {
48
+ return { ok: false, message: "GitHub login failed or was cancelled." };
49
+ }
50
+ const token = readGhToken(execSync);
51
+ if (!token) {
52
+ return { ok: false, message: "Could not retrieve a token after login." };
53
+ }
54
+ await configManager.setGithubToken(token);
55
+ return { ok: true, token, message: "Authentication successful." };
56
+ }
57
+ /** Read the current `gh` token, or null when no session is authenticated. */
58
+ function readGhToken(execSync) {
59
+ try {
60
+ const token = execSync("gh auth token", { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }).trim();
61
+ return token || null;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
@@ -5,7 +5,14 @@
5
5
  * Provides the `agent` command group with `list`, `add`, and `delete` subcommands.
6
6
  */
7
7
  import { Command } from "commander";
8
+ import { spawnSync } from "node:child_process";
9
+ import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
8
12
  import { listAgents, addAgent, deleteAgent, setAgentEnabled, AGENT_TYPES, } from "../api/agents.js";
13
+ import { createConfigManager } from "../config/index.js";
14
+ import { getHostConfig } from "../orchestrator/index.js";
15
+ import { planAgentLogin, loginableAgents } from "./agentValidation.js";
9
16
  import { ApiError, NetworkError, NotFoundError, UnauthorizedError } from "../api/errors.js";
10
17
  import { printOutput, readJsonInput, validateJsonFields, JsonInputError, } from "../utils/index.js";
11
18
  const AGENT_TYPE_LIST = AGENT_TYPES.join(", ");
@@ -401,5 +408,72 @@ Examples:
401
408
  process.exit(1);
402
409
  }
403
410
  });
411
+ // agent login
412
+ agent
413
+ .command("login [type]")
414
+ .description("Authenticate an agent by logging in through its Docker image (writes host credentials)")
415
+ .option("--root <dir>", "Stack root directory (where .env/data/logs/repos live)")
416
+ .addHelpText("after", `
417
+ Logs in using the agent's own pinned CLI inside its image, with the credential
418
+ directory mounted — so the credentials match exactly what runs jobs (no host
419
+ install or host/image version drift). Useful after a failed image check.
420
+
421
+ Examples:
422
+ $ propr agent login antigravity
423
+ $ propr agent login opencode
424
+ `)
425
+ .action(async (type, options) => {
426
+ try {
427
+ const available = loginableAgents();
428
+ if (!type) {
429
+ console.log("Usage: propr agent login <type>");
430
+ console.log(`Agents with interactive login: ${available.join(", ")}`);
431
+ return;
432
+ }
433
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
434
+ console.error("Error: propr agent login requires an interactive terminal because Docker login runs with -it.");
435
+ process.exit(1);
436
+ }
437
+ const configManager = await createConfigManager();
438
+ const { orch, cfg } = await getHostConfig({ configManager, root: options.root });
439
+ const tmp = mkdtempSync(join(tmpdir(), "propr-login-"));
440
+ const workspaceDir = join(tmp, "workspace");
441
+ mkdirSync(workspaceDir, { recursive: true });
442
+ try {
443
+ const loginType = type.toLowerCase();
444
+ const { plan, error } = planAgentLogin(loginType, cfg, workspaceDir, orch.validateDockerBindPath);
445
+ if (error || !plan) {
446
+ console.error(`Error: ${error ?? "could not plan login"}`);
447
+ if (available.length > 0)
448
+ console.error(`Agents with interactive login: ${available.join(", ")}`);
449
+ process.exit(1);
450
+ }
451
+ if (orch.docker(["images", "-q", plan.image], { capture: true }).stdout.trim().length === 0) {
452
+ console.error(`Image ${plan.image} is not present locally. Pull it first: propr images pull`);
453
+ process.exit(1);
454
+ }
455
+ mkdirSync(plan.hostDir, { recursive: true, mode: 0o700 });
456
+ console.log(`Logging in to ${loginType} via ${plan.image}`);
457
+ console.log(`Credentials will be written to ${plan.hostDir}`);
458
+ console.log("");
459
+ const res = spawnSync("docker", plan.dockerArgs, { stdio: "inherit" });
460
+ if (res.status === 0) {
461
+ console.log("");
462
+ console.log(`${loginType} login finished. Verify with: propr check agents --agents ${loginType}`);
463
+ }
464
+ else {
465
+ console.error(`\n${loginType} login exited with code ${res.status ?? "?"}.`);
466
+ process.exit(1);
467
+ }
468
+ }
469
+ finally {
470
+ rmSync(tmp, { recursive: true, force: true });
471
+ }
472
+ }
473
+ catch (error) {
474
+ console.error(`Error during agent login: ${error.message}`);
475
+ process.exit(1);
476
+ }
477
+ });
404
478
  return agent;
405
479
  }