propr-cli 0.8.3 → 0.8.4

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 (38) hide show
  1. package/README.md +4 -4
  2. package/dist/api/relay.js +10 -0
  3. package/dist/assets/env.example.txt +93 -57
  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 +2 -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/systemCommands.js +49 -2
  20. package/dist/index.js +13 -45
  21. package/dist/orchestrator/manifest.json +10 -10
  22. package/dist/orchestrator/orchestrator.mjs +513 -61
  23. package/dist/tui/AgentTableApp.js +86 -0
  24. package/dist/tui/CheckApp.js +202 -0
  25. package/dist/tui/SetupApp.js +586 -0
  26. package/dist/tui/SetupApp.test.js +172 -0
  27. package/dist/tui/app.js +84 -0
  28. package/dist/tui/render.js +11 -0
  29. package/dist/utils/envFile.js +45 -0
  30. package/dist/vendor/shared/githubEventIntakeMode.js +41 -0
  31. package/dist/vendor/shared/index.js +16 -0
  32. package/dist/vendor/shared/intakeModePrerequisites.js +76 -0
  33. package/dist/vendor/shared/modelDefinitions.js +4 -4
  34. package/dist/vendor/shared/proprServiceUrls.js +27 -0
  35. package/dist/vendor/shared/statusKeys.js +14 -0
  36. package/dist/vendor/shared/validateRoutingUrl.js +46 -0
  37. package/package.json +2 -2
  38. package/dist/assets/.env.example +0 -183
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,26 +1,45 @@
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
5
-
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_URL — https 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
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
19
+
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
+ # Per-install secrets — left commented so an unedited copy is not mistaken for a
35
+ # configured relay. Run `propr relay enroll` to fill these in, or uncomment and set
36
+ # them by hand. (Active placeholder values here could make intake/auth mode look
37
+ # configured when it is not.)
22
38
  # PROPR_GH_RELAY_TOKEN=your_relay_token
23
- # Optional: force a mode explicitly instead of inferring (app | relay | demo).
39
+ # GH_INSTALLATION_ID=your_installation_id
40
+ # Optional: force the auth mode explicitly instead of inferring (app | relay | demo).
41
+ # Relay mode is inferred automatically when PROPR_GH_RELAY_URL + PROPR_GH_RELAY_TOKEN
42
+ # are set, so you normally do not need this.
24
43
  # GH_AUTH_MODE=relay
25
44
 
26
45
  # Logging Configuration
@@ -29,6 +48,14 @@ NODE_ENV=development
29
48
 
30
49
  # Daemon Configuration
31
50
  GITHUB_REPOS_TO_MONITOR=owner/repo1,owner/repo2
51
+
52
+ # --- Polling intake (advanced opt-in) ----------------------------------------
53
+ # Polling is an explicit opt-in alternative to the default routing WebSocket.
54
+ # It is only used when GITHUB_EVENT_INTAKE_MODE=polling (set above); leave the
55
+ # intake mode at routing_websocket to keep the hosted path. In polling mode
56
+ # ProPR pulls events from the GitHub API on this interval using any usable
57
+ # GitHub auth (relay or your own App). POLLING_INTERVAL_MS is the poll period in
58
+ # milliseconds (default: 60000).
32
59
  POLLING_INTERVAL_MS=60000
33
60
 
34
61
  # Config Repository (for dynamic repository management)
@@ -55,10 +82,34 @@ COMMENT_BATCH_DELAY_MS=3000
55
82
  # and fallback paths fail. Defaults to 3600000 (1 hour).
56
83
  # SUMMARIZATION_QUOTA_COOLDOWN_MS=3600000
57
84
 
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
85
+ # --- Direct webhook intake (advanced) ----------------------------------------
86
+ # Direct webhook is an advanced alternative to the default routing WebSocket.
87
+ # It is only used when GITHUB_EVENT_INTAKE_MODE=direct_webhook (set near the top
88
+ # of this file). This path requires your OWN GitHub App (not the hosted ProPR
89
+ # App) and a publicly reachable POST /webhook URL that GitHub can deliver events
90
+ # to — the endpoint is served by the dashboard API service (port 4000). Because
91
+ # of those requirements, most installs should stay on routing_websocket.
92
+ #
93
+ # Own GitHub App credentials (app auth mode — required for direct webhook):
94
+ # GH_APP_ID — your GitHub App's numeric id
95
+ # GH_PRIVATE_KEY_PATH — path to your App's private key (.pem)
96
+ # GH_INSTALLATION_ID — which installation to act on (set in the routing
97
+ # section above; reused here)
98
+ # GH_APP_ID=your_app_id
99
+ # GH_PRIVATE_KEY_PATH=./path/to/your-app-private-key.pem
100
+ # Host path to the GitHub App private key (.pem). Recommended when running via
101
+ # the `propr` CLI or launcher: the key is bind-mounted (read-only) into the app
102
+ # containers and GH_PRIVATE_KEY_PATH above is overridden to point at it, so you
103
+ # can keep the key anywhere on the host instead of staging it under data/.
104
+ # Must be an absolute host path (no '~').
105
+ # HOST_GH_PRIVATE_KEY=/home/your-user/propr/app-private-key.pem
106
+ # Shared secret GitHub signs webhook deliveries with; required for this mode.
107
+ # GH_WEBHOOK_SECRET=your-webhook-secret
108
+
109
+ # Deprecated: ENABLE_GITHUB_WEBHOOKS no longer selects the intake mode. Use
110
+ # GITHUB_EVENT_INTAKE_MODE instead (routing_websocket | polling | direct_webhook).
111
+ # Documented here only so existing .env files are recognized; do not rely on it.
112
+ # ENABLE_GITHUB_WEBHOOKS=false
62
113
 
63
114
  # System Task Authorization
64
115
  # Secret used to sign system task requests (e.g., revert operations)
@@ -104,19 +155,14 @@ ANTIGRAVITY_TIMEOUT_MS=300000
104
155
  OPENCODE_TIMEOUT_MS=3600000
105
156
  # Launcher credential mount paths (required when using docker/launcher).
106
157
  # 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.
158
+ # (/home/your-user/.config/opencode). The saved agent configPath still controls
159
+ # the runtime mount; the launcher sets OPENCODE_CONFIG_PATH only as the default
160
+ # path for worker/API processes.
113
161
  # HOST_OPENCODE_DATA_DIR points to normal opencode auth data
114
162
  # (/home/your-user/.local/share/opencode). Set it for launcher deployments
115
163
  # so normal `opencode auth login` credentials are visible to spawned OpenCode
116
164
  # agent containers and can refresh auth metadata when required.
117
165
  # 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
166
  # HOST_OPENCODE_DATA_DIR=/home/your-user/.local/share/opencode
121
167
 
122
168
  # --- Mistral Vibe Configuration (only required when using a Vibe agent) ---
@@ -135,14 +181,13 @@ VIBE_TIMEOUT_MS=3600000
135
181
  # directory name intentionally differs from the variable name.
136
182
  # HOST_ANTIGRAVITY_DIR=/home/your-user/.gemini
137
183
  # 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
184
+ # Vibe Docker-outside-Docker writes prompt files to a host-visible directory so
185
+ # spawned agent containers can bind-mount them. When Vibe is enabled, the
186
+ # launcher defaults the host path to /tmp/propr-vibe-prompts-<uid>; the container
187
+ # path defaults to /tmp/propr-vibe-prompts. Set both only to override those
188
+ # locations.
144
189
  # VIBE_PROMPT_CACHE_DIR=/tmp/propr-vibe-prompts
145
- # HOST_VIBE_PROMPT_CACHE_DIR=/tmp/propr-vibe-prompts
190
+ # HOST_VIBE_PROMPT_CACHE_DIR=/tmp/propr-vibe-prompts-1000
146
191
 
147
192
  # Dashboard API Configuration
148
193
  DASHBOARD_API_PORT=4000
@@ -174,25 +219,16 @@ PROPR_DEMO_MODE=false
174
219
  DB_FILENAME=./data/propr.sqlite
175
220
 
176
221
  # --- PR Preview Environment ---
222
+ # docker-compose.yml and scripts/deploy-pr.sh consume these local names.
223
+ # The PR Preview workflow maps repository variable PR_PREVIEW_STAGING_ENV_FILE
224
+ # to STAGING_ENV_FILE for deploys.
177
225
  # Path to staging .env file (provides base configuration for PR previews)
178
226
  # PR-specific values (ports, API URLs) are passed as inline overrides
179
227
  STAGING_ENV_FILE=/path/to/staging/.env
180
228
 
181
- # Path to staging database file for seeding PR preview environments
229
+ # Optional path to a staging database file for seeding PR preview environments.
230
+ # The workflow maps repository variable PR_PREVIEW_STAGING_DB_PATH to this env.
182
231
  STAGING_DB_PATH=/path/to/staging/data/propr.sqlite
183
232
 
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
233
+ # PR preview deploys are owned by the GitHub Actions workflow and run only after
234
+ # 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
  }