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.
- package/README.md +4 -4
- package/dist/api/relay.js +10 -0
- package/dist/assets/env.example.txt +93 -57
- package/dist/auth/githubLogin.js +66 -0
- package/dist/commands/agentCommands.js +74 -0
- package/dist/commands/agentValidation.js +548 -0
- package/dist/commands/checkCommands.js +981 -76
- package/dist/commands/imageCommands.js +60 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/initStack.js +50 -1
- package/dist/commands/relayCommands.js +45 -12
- package/dist/commands/setup/agents.js +185 -0
- package/dist/commands/setup/engine.js +956 -0
- package/dist/commands/setup/github.js +181 -0
- package/dist/commands/setup/sequential.js +501 -0
- package/dist/commands/setup/state.js +242 -0
- package/dist/commands/setup/types.js +85 -0
- package/dist/commands/setupCommand.js +85 -0
- package/dist/commands/systemCommands.js +49 -2
- package/dist/index.js +13 -45
- package/dist/orchestrator/manifest.json +10 -10
- package/dist/orchestrator/orchestrator.mjs +513 -61
- package/dist/tui/AgentTableApp.js +86 -0
- package/dist/tui/CheckApp.js +202 -0
- package/dist/tui/SetupApp.js +586 -0
- package/dist/tui/SetupApp.test.js +172 -0
- package/dist/tui/app.js +84 -0
- package/dist/tui/render.js +11 -0
- package/dist/utils/envFile.js +45 -0
- package/dist/vendor/shared/githubEventIntakeMode.js +41 -0
- package/dist/vendor/shared/index.js +16 -0
- package/dist/vendor/shared/intakeModePrerequisites.js +76 -0
- package/dist/vendor/shared/modelDefinitions.js +4 -4
- package/dist/vendor/shared/proprServiceUrls.js +27 -0
- package/dist/vendor/shared/statusKeys.js +14 -0
- package/dist/vendor/shared/validateRoutingUrl.js +46 -0
- package/package.json +2 -2
- 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
|
|
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
|
|
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.
|
|
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 '
|
|
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
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
#
|
|
109
|
-
#
|
|
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
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
185
|
-
|
|
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
|
}
|