propr-cli 0.8.4 → 0.8.6
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 +36 -0
- package/dist/assets/.env.example +183 -0
- package/dist/assets/env.example.txt +90 -3
- package/dist/commands/index.js +1 -0
- package/dist/commands/stackCommands.js +14 -2
- package/dist/commands/tunnelCommand.js +574 -0
- package/dist/config/ConfigManager.js +22 -0
- package/dist/config/types.js +1 -0
- package/dist/index.js +3 -2
- package/dist/orchestrator/format.js +46 -0
- package/dist/orchestrator/index.js +7 -2
- package/dist/orchestrator/manifest.json +12 -11
- package/dist/orchestrator/orchestrator.mjs +376 -15
- package/dist/tui/render.js +17 -2
- package/dist/vendor/shared/index.js +2 -1
- package/dist/vendor/shared/proprCompatibility.js +70 -0
- package/dist/vendor/shared/proprServiceUrls.js +109 -0
- package/package.json +2 -2
package/dist/tui/render.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* In all cases the containers run detached, so the stack outlives this process.
|
|
7
7
|
*/
|
|
8
8
|
import { getHostConfig } from "../orchestrator/index.js";
|
|
9
|
-
import { renderStatusTable } from "../orchestrator/format.js";
|
|
9
|
+
import { renderStatusTable, renderTunnelEndpointSummary } from "../orchestrator/format.js";
|
|
10
10
|
import { ensureVibePromptCacheDir } from "../commands/initStack.js";
|
|
11
11
|
import { createInterface } from "node:readline/promises";
|
|
12
12
|
async function confirmRestart() {
|
|
@@ -71,8 +71,23 @@ export async function runStart(configManager, options) {
|
|
|
71
71
|
}
|
|
72
72
|
const ui = configManager.getUiEnabled();
|
|
73
73
|
const docs = cfg.docsEnabled;
|
|
74
|
+
// cfg.uiTunnelEnabled already reflects a persisted `propr tunnel on|off`
|
|
75
|
+
// toggle (forwarded as an override in getHostConfig), falling back to the
|
|
76
|
+
// env-derived default when the toggle has never been set.
|
|
77
|
+
const tunnel = cfg.uiTunnelEnabled;
|
|
74
78
|
orch.ensureNetwork(cfg, (l) => console.log(l));
|
|
75
|
-
const status = orch.startStack(cfg, { ui, docs, onLog: (l) => console.log(l) });
|
|
79
|
+
const status = orch.startStack(cfg, { ui, docs, tunnel, onLog: (l) => console.log(l) });
|
|
80
|
+
// When the tunnel is on, surface the concrete routed endpoints (not the base
|
|
81
|
+
// URL as a health target) so the operator can see where the hosted UI reaches
|
|
82
|
+
// this stack.
|
|
83
|
+
if (tunnel) {
|
|
84
|
+
const summary = renderTunnelEndpointSummary(cfg.uiPublicApiUrl);
|
|
85
|
+
if (summary.length > 0) {
|
|
86
|
+
console.log("");
|
|
87
|
+
for (const line of summary)
|
|
88
|
+
console.log(line);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
76
91
|
const interactive = options.tui !== false && Boolean(process.stdout.isTTY);
|
|
77
92
|
if (!interactive) {
|
|
78
93
|
console.log("");
|
|
@@ -10,7 +10,7 @@ export { validateRelayUrl } from './validateRelayUrl.js';
|
|
|
10
10
|
// Export the hosted propr-routing service default URLs (one source of truth for
|
|
11
11
|
// the webhook.propr.dev host shared by the CLI, the daemon dialer, and the
|
|
12
12
|
// boot/check prerequisite validators)
|
|
13
|
-
export { DEFAULT_PROPR_ROUTING_URL, DEFAULT_PROPR_GH_RELAY_URL, } from './proprServiceUrls.js';
|
|
13
|
+
export { DEFAULT_PROPR_ROUTING_URL, DEFAULT_PROPR_GH_RELAY_URL, DEFAULT_PROPR_UI_ORIGIN, PROPR_UI_PROXY_SUFFIX, PROPR_UI_PROXY_LABEL_PREFIX, DEFAULT_CLOUDFLARED_IMAGE, proprInstanceProxyUrl, isValidProprInstanceId, isProprProxyUrl, proprTunnelEndpoints, } from './proprServiceUrls.js';
|
|
14
14
|
// Export routing URL validation (shared by intake prerequisites and the daemon
|
|
15
15
|
// routing service so the boot/CLI checks and the dialer agree on one policy)
|
|
16
16
|
export { validateRoutingUrl } from './validateRoutingUrl.js';
|
|
@@ -25,6 +25,7 @@ export { validateIntakeModePrerequisites, } from './intakeModePrerequisites.js';
|
|
|
25
25
|
// Export shared Redis status keys (one source of truth for cross-process status
|
|
26
26
|
// keys so the daemon publisher, API status route, and CLI cannot drift)
|
|
27
27
|
export { ROUTING_STATUS_REDIS_KEY } from './statusKeys.js';
|
|
28
|
+
export { PROPR_VERSION, PROPR_API_COMPATIBILITY, PROPR_UI_COMPATIBILITY, PROPR_UI_SUPPORTED_API_COMPATIBILITY, getProprCompatibilityMetadata, evaluateProprApiCompatibility, } from './proprCompatibility.js';
|
|
28
29
|
export { shortHash, buildDynamicLlmLabel, MAX_GITHUB_LABEL_LENGTH } from './labelUtils.js';
|
|
29
30
|
// Export the default review guidance (the overridable part of the /review prompt)
|
|
30
31
|
export { DEFAULT_REVIEW_GUIDANCE } from './reviewPrompt.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Public ProPR version surfaced to the hosted UI via `/api/compatibility`. This
|
|
2
|
+
// must track the release version. The shared package is bundled for the browser
|
|
3
|
+
// (no fs/JSON-import of package.json available within rootDir), so it is kept as
|
|
4
|
+
// a constant rather than read from package.json at runtime. A release bump that
|
|
5
|
+
// updates packages/shared/package.json or docker/launcher/manifest.json but
|
|
6
|
+
// forgets this constant is caught by the drift test in
|
|
7
|
+
// test/orchestratorProprUrlsDrift.test.ts, which asserts all three agree.
|
|
8
|
+
export const PROPR_VERSION = '0.8.6';
|
|
9
|
+
// Bump this only when the API/UI contract changes in a way the hosted UI must
|
|
10
|
+
// account for. Patch releases that do not change the browser-facing contract can
|
|
11
|
+
// keep the same compatibility version.
|
|
12
|
+
export const PROPR_API_COMPATIBILITY = '2026-06-27';
|
|
13
|
+
export const PROPR_UI_COMPATIBILITY = PROPR_API_COMPATIBILITY;
|
|
14
|
+
export const PROPR_UI_SUPPORTED_API_COMPATIBILITY = [PROPR_API_COMPATIBILITY];
|
|
15
|
+
export function getProprCompatibilityMetadata() {
|
|
16
|
+
return {
|
|
17
|
+
version: PROPR_VERSION,
|
|
18
|
+
apiCompatibility: PROPR_API_COMPATIBILITY,
|
|
19
|
+
uiCompatibility: PROPR_UI_COMPATIBILITY,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function evaluateProprApiCompatibility(input) {
|
|
23
|
+
const apiCompatibility = input.apiCompatibility?.trim() || null;
|
|
24
|
+
const apiVersion = input.version?.trim() || null;
|
|
25
|
+
if (!apiCompatibility) {
|
|
26
|
+
return {
|
|
27
|
+
compatible: false,
|
|
28
|
+
apiCompatibility,
|
|
29
|
+
apiVersion,
|
|
30
|
+
reason: 'missing',
|
|
31
|
+
message: 'This ProPR instance does not publish API compatibility metadata. Update the local ProPR stack before using the hosted UI.',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (PROPR_UI_SUPPORTED_API_COMPATIBILITY.includes(apiCompatibility)) {
|
|
35
|
+
return { compatible: true, apiCompatibility, apiVersion };
|
|
36
|
+
}
|
|
37
|
+
// PROPR_UI_SUPPORTED_API_COMPATIBILITY currently holds a single value, so
|
|
38
|
+
// oldest === newest and only the too_old / too_new branches below can fire (the
|
|
39
|
+
// final `unsupported` branch is unreachable today). These are forward-looking:
|
|
40
|
+
// once the UI supports a range with gaps, an in-range-but-unsupported value can
|
|
41
|
+
// occur and the `unsupported` branch covers it.
|
|
42
|
+
const oldestSupported = PROPR_UI_SUPPORTED_API_COMPATIBILITY[0];
|
|
43
|
+
const newestSupported = PROPR_UI_SUPPORTED_API_COMPATIBILITY[PROPR_UI_SUPPORTED_API_COMPATIBILITY.length - 1];
|
|
44
|
+
if (apiCompatibility < oldestSupported) {
|
|
45
|
+
return {
|
|
46
|
+
compatible: false,
|
|
47
|
+
apiCompatibility,
|
|
48
|
+
apiVersion,
|
|
49
|
+
reason: 'too_old',
|
|
50
|
+
message: `This ProPR instance is too old for the hosted UI. Update the local ProPR stack to API compatibility ${oldestSupported} or newer.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (apiCompatibility > newestSupported) {
|
|
54
|
+
return {
|
|
55
|
+
compatible: false,
|
|
56
|
+
apiCompatibility,
|
|
57
|
+
apiVersion,
|
|
58
|
+
reason: 'too_new',
|
|
59
|
+
message: `This ProPR instance is newer than the hosted UI supports. Update the hosted UI or use the matching local UI for API compatibility ${apiCompatibility}.`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
compatible: false,
|
|
64
|
+
apiCompatibility,
|
|
65
|
+
apiVersion,
|
|
66
|
+
reason: 'unsupported',
|
|
67
|
+
message: `This hosted UI supports API compatibility ${PROPR_UI_SUPPORTED_API_COMPATIBILITY.join(', ')}, but the local ProPR instance reports ${apiCompatibility}.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=proprCompatibility.js.map
|
|
@@ -24,4 +24,113 @@ export const DEFAULT_PROPR_ROUTING_URL = 'wss://webhook.propr.dev';
|
|
|
24
24
|
* directly to this value.
|
|
25
25
|
*/
|
|
26
26
|
export const DEFAULT_PROPR_GH_RELAY_URL = 'https://webhook.propr.dev/v1';
|
|
27
|
+
/**
|
|
28
|
+
* Origin of the hosted Propr UI (https://app.propr.dev). This is where the
|
|
29
|
+
* managed control plane is served from; a local stack exposes its own UI on a
|
|
30
|
+
* tunnel under a {@link PROPR_UI_PROXY_LABEL_PREFIX} host on
|
|
31
|
+
* {@link PROPR_UI_PROXY_SUFFIX} so the hosted UI can reach it.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_PROPR_UI_ORIGIN = 'https://app.propr.dev';
|
|
34
|
+
/**
|
|
35
|
+
* DNS suffix and label prefix for per-instance UI/API tunnel hostnames. Each
|
|
36
|
+
* local stack with an instance id is reachable at
|
|
37
|
+
* `https://t-<instanceId>.propr.dev`, so the hosted UI at
|
|
38
|
+
* {@link DEFAULT_PROPR_UI_ORIGIN} can discover and address it.
|
|
39
|
+
*/
|
|
40
|
+
export const PROPR_UI_PROXY_SUFFIX = 'propr.dev';
|
|
41
|
+
export const PROPR_UI_PROXY_LABEL_PREFIX = 't-';
|
|
42
|
+
/**
|
|
43
|
+
* Default Cloudflare Tunnel image used to expose the local stack's UI/API to
|
|
44
|
+
* the hosted control plane when a UI tunnel is enabled. This is only a fallback:
|
|
45
|
+
* the launcher prefers the `cloudflared` entry pinned in the stack manifest
|
|
46
|
+
* (docker/launcher/manifest.json). Keep this tag in sync with that manifest pin
|
|
47
|
+
* so the effective default is the same regardless of which source supplies it.
|
|
48
|
+
*/
|
|
49
|
+
export const DEFAULT_CLOUDFLARED_IMAGE = 'cloudflare/cloudflared:2024.12.2';
|
|
50
|
+
/**
|
|
51
|
+
* Whether an instance id is usable as a single DNS label in the per-instance
|
|
52
|
+
* proxy hostname (`t-<id>.propr.dev`). Enforces the standard label rules:
|
|
53
|
+
* 1–63 characters, ASCII letters/digits/hyphens only, and no leading or
|
|
54
|
+
* trailing hyphen. This rejects spaces, slashes, dots, underscores, and other
|
|
55
|
+
* characters that would produce an invalid or ambiguous hostname.
|
|
56
|
+
*/
|
|
57
|
+
export function isValidProprInstanceId(instanceId) {
|
|
58
|
+
const id = (instanceId ?? '').trim();
|
|
59
|
+
return /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(id);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Derive the public API/UI URL for a local stack from its instance id, using
|
|
63
|
+
* the shared public tunnel host pattern. Returns `https://t-abc123.propr.dev`
|
|
64
|
+
* for instance id `abc123`. A caller may pass either the bare instance id or the
|
|
65
|
+
* public DNS label (`t-abc123`); the returned URL is canonicalized. Returns
|
|
66
|
+
* `undefined` for a missing/blank id — or an id that is not a valid DNS label
|
|
67
|
+
* (see {@link isValidProprInstanceId}) — so callers can fall back to an explicit
|
|
68
|
+
* URL or a local-development default rather than emitting a malformed hostname.
|
|
69
|
+
* The id is lowercased so a mixed-case instance id yields a canonical hostname
|
|
70
|
+
* (DNS is case-insensitive).
|
|
71
|
+
*/
|
|
72
|
+
export function proprInstanceProxyUrl(instanceId) {
|
|
73
|
+
const id = normalizeProprInstanceId(instanceId);
|
|
74
|
+
if (!isValidProprInstanceId(id))
|
|
75
|
+
return undefined;
|
|
76
|
+
return `https://${PROPR_UI_PROXY_LABEL_PREFIX}${id.toLowerCase()}.${PROPR_UI_PROXY_SUFFIX}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether a URL is a hosted per-instance proxy URL (`https://t-<id>.propr.dev`).
|
|
80
|
+
* propr-routing only forwards `/api/*` and `/socket.io/*` on these hosts, so the
|
|
81
|
+
* tunnel base URL must be one of them. Requires https and *exactly one* valid
|
|
82
|
+
* `t-<instance-id>` label in front of the shared {@link PROPR_UI_PROXY_SUFFIX}.
|
|
83
|
+
* Other propr.dev hosts like `app.propr.dev` and nested hosts are rejected. It
|
|
84
|
+
* must also be a bare origin: a non-root path, query, or fragment (e.g.
|
|
85
|
+
* `https://t-abc.propr.dev/api`) is rejected because
|
|
86
|
+
* {@link proprTunnelEndpoints} appends `/api/...` itself and a base path would
|
|
87
|
+
* double it up (`.../api/api/status`). Returns false for a malformed URL.
|
|
88
|
+
*/
|
|
89
|
+
export function isProprProxyUrl(url) {
|
|
90
|
+
if (!url)
|
|
91
|
+
return false;
|
|
92
|
+
try {
|
|
93
|
+
const { protocol, hostname, pathname, search, hash } = new URL(url);
|
|
94
|
+
if (protocol !== 'https:')
|
|
95
|
+
return false;
|
|
96
|
+
// Must be a bare origin — the tunnel endpoint helpers own the path suffix.
|
|
97
|
+
// Trailing slashes (`/`, `//`) are tolerated (callers trim them); any real
|
|
98
|
+
// path segment, query, or fragment is rejected so a base path can't double
|
|
99
|
+
// up the appended `/api/...`.
|
|
100
|
+
if (/[^/]/.test(pathname) || search || hash)
|
|
101
|
+
return false;
|
|
102
|
+
const suffix = `.${PROPR_UI_PROXY_SUFFIX}`;
|
|
103
|
+
if (!hostname.endsWith(suffix))
|
|
104
|
+
return false;
|
|
105
|
+
const label = hostname.slice(0, -suffix.length);
|
|
106
|
+
if (label.includes('.') || !label.startsWith(PROPR_UI_PROXY_LABEL_PREFIX)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return isValidProprInstanceId(label.slice(PROPR_UI_PROXY_LABEL_PREFIX.length));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function normalizeProprInstanceId(instanceId) {
|
|
116
|
+
const id = (instanceId ?? '').trim();
|
|
117
|
+
return id.startsWith(PROPR_UI_PROXY_LABEL_PREFIX)
|
|
118
|
+
? id.slice(PROPR_UI_PROXY_LABEL_PREFIX.length)
|
|
119
|
+
: id;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The concrete endpoints the hosted UI reaches through the tunnel base URL.
|
|
123
|
+
* propr-routing only allows `/api/*` and `/socket.io/*`, so the base (root) URL
|
|
124
|
+
* itself intentionally returns 404 — it is NOT a health target. Use `apiStatus`
|
|
125
|
+
* to probe liveness. The base is normalized (trailing slashes trimmed) so the
|
|
126
|
+
* derived paths never double up a slash.
|
|
127
|
+
*/
|
|
128
|
+
export function proprTunnelEndpoints(baseUrl) {
|
|
129
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
130
|
+
return {
|
|
131
|
+
apiStatus: `${base}/api/status`,
|
|
132
|
+
socketIo: `${base}/socket.io/`,
|
|
133
|
+
root: `${base}/`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
27
136
|
//# sourceMappingURL=proprServiceUrls.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "propr-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "CLI for interacting with the ProPR backend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,5 +27,5 @@
|
|
|
27
27
|
"code-review",
|
|
28
28
|
"automation"
|
|
29
29
|
],
|
|
30
|
-
"license": "
|
|
30
|
+
"license": "Apache-2.0"
|
|
31
31
|
}
|