@qpfai/pf-gate-cli 1.0.64 → 1.0.65-darwin-x64

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 CHANGED
@@ -1,81 +1,15 @@
1
- # @qpfai/pf-gate-cli
1
+ # @qpfai/pf-gate-cli (darwin-x64)
2
2
 
3
- Global CLI launcher for PF Gate with Codex-style platform runtime packages.
3
+ Platform runtime payload for PF Gate CLI.
4
4
 
5
- ## Install
5
+ The bundled launcher at `vendor/<target-triple>/pf-gate/` runs PF Gate through Python.
6
6
 
7
- ```bash
8
- npm i -g @qpfai/pf-gate-cli
9
- ```
7
+ Default install source:
8
+ - bundled wheel: `vendor/<target-triple>/python/persons_field-1.0.0-py3-none-any.whl`
10
9
 
11
- ## Use
12
-
13
- Open the PF Gate terminal UX:
14
-
15
- ```bash
16
- pf gate
17
- ```
18
-
19
- `pf gate` checks npm for a newer `@qpfai/pf-gate-cli` release on startup and
20
- auto-installs updates before launching UX.
21
- To disable this behavior, set `PF_GATE_DISABLE_AUTO_UPDATE=1`.
22
-
23
- Run direct CLI commands:
24
-
25
- ```bash
26
- pf --version
27
- pf gate --selftest
28
- ```
29
-
30
- The launcher resolves a platform package at install time and executes a bundled runtime binary at run time.
31
- On first run, it bootstraps a per-user runtime environment under `~/.pf-gate/runtime/.venv`.
32
-
33
- ## Maintainer release flow
34
-
35
- Set the release version for all npm package manifests:
36
-
37
- ```bash
38
- node npm/tools/set-version.mjs 1.0.0
39
- ```
40
-
41
- Validate release topology without publishing:
42
-
43
- ```bash
44
- node npm/tools/publish-all.mjs --dry-run
45
- ```
46
-
47
- `publish-all.mjs` rebuilds `persons_field-1.0.0-py3-none-any.whl` from current source
48
- and syncs it into every platform package before validation/publish.
49
-
50
- The dry run warns on placeholder runtime payloads by default.
51
- To fail dry run on placeholders, use:
52
-
53
- ```bash
54
- node npm/tools/publish-all.mjs --dry-run --strict-placeholder-check
55
- ```
56
-
57
- Publish all platform variants, then the meta package:
58
-
59
- ```bash
60
- node npm/tools/publish-all.mjs --registry=https://registry.npmjs.org/
61
- ```
62
-
63
- Release tags used by default:
64
- - platform payload packages: `platform`
65
- - meta package: `latest`
66
-
67
- With npm passkey/WebAuthn 2FA:
68
-
69
- ```bash
70
- cd "/Users/nicholashuunguyen/Documents/PF Gate/PF Gate V7"
71
- node npm/tools/publish-all.mjs --registry=https://registry.npmjs.org/
72
- npm view @qpfai/pf-gate-cli version --registry=https://registry.npmjs.org/
73
- ```
74
-
75
- If npm prompts with `Authenticate your account at ... Press ENTER to open in the browser...`,
76
- press Enter and approve with your passkey in the browser.
77
-
78
- Each platform package must contain:
79
-
80
- - `vendor/<target-triple>/pf-gate/<binary>`
81
- - optional helper tools in `vendor/<target-triple>/path/`
10
+ Runtime behavior:
11
+ - Uses `PF_GATE_PYTHON` when set.
12
+ - Otherwise creates/uses a per-user runtime venv at `~/.pf-gate/runtime/.venv` (or `PF_GATE_RUNTIME_HOME/.venv`).
13
+ - Installs from bundled wheel by default.
14
+ - Optional override: set `PF_GATE_PIP_SPEC` to force a different install source.
15
+ - Starts `persons_field.terminal.launcher` with forwarded arguments.
package/package.json CHANGED
@@ -1,33 +1,22 @@
1
1
  {
2
2
  "name": "@qpfai/pf-gate-cli",
3
- "version": "1.0.64",
4
- "description": "PF Gate platform launcher with optional native runtime packages.",
3
+ "version": "1.0.65-darwin-x64",
4
+ "description": "PF Gate runtime payload for darwin x64.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
- "bin": {
8
- "pf": "bin/pf.mjs",
9
- "pf-gate": "bin/pf.mjs"
10
- },
7
+ "os": [
8
+ "darwin"
9
+ ],
10
+ "cpu": [
11
+ "x64"
12
+ ],
11
13
  "files": [
12
- "bin",
13
- "lib",
14
14
  "vendor",
15
15
  "README.md"
16
16
  ],
17
- "scripts": {
18
- "test": "node --test tests/*.test.mjs"
19
- },
20
17
  "engines": {
21
18
  "node": ">=18"
22
19
  },
23
- "optionalDependencies": {
24
- "@qpfai/pf-gate-cli-linux-x64": "npm:@qpfai/pf-gate-cli@1.0.64-linux-x64",
25
- "@qpfai/pf-gate-cli-linux-arm64": "npm:@qpfai/pf-gate-cli@1.0.64-linux-arm64",
26
- "@qpfai/pf-gate-cli-darwin-x64": "npm:@qpfai/pf-gate-cli@1.0.64-darwin-x64",
27
- "@qpfai/pf-gate-cli-darwin-arm64": "npm:@qpfai/pf-gate-cli@1.0.64-darwin-arm64",
28
- "@qpfai/pf-gate-cli-win32-x64": "npm:@qpfai/pf-gate-cli@1.0.64-win32-x64",
29
- "@qpfai/pf-gate-cli-win32-arm64": "npm:@qpfai/pf-gate-cli@1.0.64-win32-arm64"
30
- },
31
20
  "publishConfig": {
32
21
  "access": "public"
33
22
  }
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env sh
2
+ set -eu
3
+
4
+ SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
5
+ BUNDLED_WHEEL="$SCRIPT_DIR/../python/persons_field-1.0.0-py3-none-any.whl"
6
+ USER_HOME="${HOME:-}"
7
+ RUNTIME_HOME="${PF_GATE_RUNTIME_HOME:-}"
8
+ if [ -z "$RUNTIME_HOME" ] && [ -n "$USER_HOME" ]; then
9
+ RUNTIME_HOME="$USER_HOME/.pf-gate/runtime"
10
+ fi
11
+ WHEEL_STAMP_FILE=""
12
+ if [ -n "$RUNTIME_HOME" ]; then
13
+ WHEEL_STAMP_FILE="$RUNTIME_HOME/.bundled-wheel.sha256"
14
+ fi
15
+ SYSTEM_PYTHON=""
16
+ VENV_DIR=""
17
+ IS_MANAGED_VENV=0
18
+
19
+ resolve_system_python() {
20
+ if command -v python3 >/dev/null 2>&1; then
21
+ printf '%s' "python3"
22
+ return 0
23
+ fi
24
+ if command -v python >/dev/null 2>&1; then
25
+ printf '%s' "python"
26
+ return 0
27
+ fi
28
+ return 1
29
+ }
30
+
31
+ sha256_file() {
32
+ file_path="$1"
33
+ if command -v shasum >/dev/null 2>&1; then
34
+ shasum -a 256 "$file_path" | awk '{print $1}'
35
+ return 0
36
+ fi
37
+ if command -v sha256sum >/dev/null 2>&1; then
38
+ sha256sum "$file_path" | awk '{print $1}'
39
+ return 0
40
+ fi
41
+ if command -v openssl >/dev/null 2>&1; then
42
+ openssl dgst -sha256 "$file_path" | awk '{print $NF}'
43
+ return 0
44
+ fi
45
+ return 1
46
+ }
47
+
48
+ read_wheel_stamp() {
49
+ if [ -z "$WHEEL_STAMP_FILE" ] || [ ! -f "$WHEEL_STAMP_FILE" ]; then
50
+ return 0
51
+ fi
52
+ head -n 1 "$WHEEL_STAMP_FILE" 2>/dev/null || true
53
+ }
54
+
55
+ write_wheel_stamp() {
56
+ wheel_hash="$1"
57
+ if [ -z "$WHEEL_STAMP_FILE" ] || [ -z "$wheel_hash" ]; then
58
+ return 0
59
+ fi
60
+ mkdir -p "$(dirname "$WHEEL_STAMP_FILE")"
61
+ printf '%s\n' "$wheel_hash" >"$WHEEL_STAMP_FILE" || true
62
+ }
63
+
64
+ PYTHON_BIN="${PF_GATE_PYTHON:-}"
65
+ if [ -z "$PYTHON_BIN" ]; then
66
+ SYSTEM_PYTHON="$(resolve_system_python || true)"
67
+ if [ -z "$SYSTEM_PYTHON" ]; then
68
+ echo "PF Gate runtime error: Python 3.13+ not found. Install Python or set PF_GATE_PYTHON." >&2
69
+ exit 1
70
+ fi
71
+
72
+ if [ -z "$USER_HOME" ] && [ -z "${PF_GATE_RUNTIME_HOME:-}" ]; then
73
+ echo "PF Gate runtime error: HOME is not set. Set PF_GATE_RUNTIME_HOME or PF_GATE_PYTHON." >&2
74
+ exit 1
75
+ fi
76
+
77
+ if [ -z "$RUNTIME_HOME" ]; then
78
+ RUNTIME_HOME="$USER_HOME/.pf-gate/runtime"
79
+ fi
80
+ VENV_DIR="$RUNTIME_HOME/.venv"
81
+ IS_MANAGED_VENV=1
82
+ PYTHON_BIN="$VENV_DIR/bin/python"
83
+
84
+ if [ ! -x "$PYTHON_BIN" ]; then
85
+ echo "PF Gate runtime: creating runtime environment at $VENV_DIR" >&2
86
+ mkdir -p "$RUNTIME_HOME"
87
+ if ! "$SYSTEM_PYTHON" -m venv "$VENV_DIR"; then
88
+ echo "PF Gate runtime error: failed to create runtime environment at $VENV_DIR" >&2
89
+ exit 1
90
+ fi
91
+ fi
92
+ fi
93
+
94
+ PIP_TARGET="${PF_GATE_PIP_SPEC:-$BUNDLED_WHEEL}"
95
+ if [ -z "${PF_GATE_PIP_SPEC:-}" ] && [ ! -f "$BUNDLED_WHEEL" ]; then
96
+ echo "PF Gate runtime error: missing bundled wheel at $BUNDLED_WHEEL" >&2
97
+ exit 1
98
+ fi
99
+
100
+ BUNDLED_HASH=""
101
+ if [ -z "${PF_GATE_PIP_SPEC:-}" ] && [ -f "$BUNDLED_WHEEL" ]; then
102
+ BUNDLED_HASH="$(sha256_file "$BUNDLED_WHEEL" 2>/dev/null || true)"
103
+ fi
104
+
105
+ NEEDS_INSTALL=0
106
+ FORCE_REINSTALL=0
107
+ INSTALL_REASON="persons_field is missing"
108
+ if ! "$PYTHON_BIN" -c "import persons_field.terminal.launcher" >/dev/null 2>&1; then
109
+ NEEDS_INSTALL=1
110
+ elif [ -z "${PF_GATE_PIP_SPEC:-}" ] && [ -n "$BUNDLED_HASH" ]; then
111
+ STORED_HASH="$(read_wheel_stamp)"
112
+ if [ "$STORED_HASH" != "$BUNDLED_HASH" ]; then
113
+ NEEDS_INSTALL=1
114
+ FORCE_REINSTALL=1
115
+ INSTALL_REASON="bundled runtime payload changed"
116
+ fi
117
+ fi
118
+
119
+ if [ "$NEEDS_INSTALL" -eq 1 ]; then
120
+ echo "PF Gate runtime: ${INSTALL_REASON}; installing ${PIP_TARGET}..." >&2
121
+ FORCE_PIP_REINSTALL="$FORCE_REINSTALL"
122
+ if [ "$FORCE_REINSTALL" -eq 1 ] && [ "$IS_MANAGED_VENV" -eq 1 ] && [ -n "$SYSTEM_PYTHON" ] && [ -n "$VENV_DIR" ]; then
123
+ echo "PF Gate runtime: refreshing runtime environment at $VENV_DIR" >&2
124
+ rm -rf "$VENV_DIR"
125
+ mkdir -p "$RUNTIME_HOME"
126
+ if ! "$SYSTEM_PYTHON" -m venv "$VENV_DIR"; then
127
+ echo "PF Gate runtime error: failed to refresh runtime environment at $VENV_DIR" >&2
128
+ exit 1
129
+ fi
130
+ PYTHON_BIN="$VENV_DIR/bin/python"
131
+ FORCE_PIP_REINSTALL=0
132
+ fi
133
+ if ! "$PYTHON_BIN" -m pip --version >/dev/null 2>&1; then
134
+ "$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 || true
135
+ fi
136
+ if [ "$FORCE_PIP_REINSTALL" -eq 1 ]; then
137
+ if ! "$PYTHON_BIN" -m pip install --upgrade --force-reinstall --disable-pip-version-check "$PIP_TARGET"; then
138
+ if [ "$IS_MANAGED_VENV" -eq 1 ]; then
139
+ echo "PF Gate runtime error: failed to install ${PIP_TARGET}. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry." >&2
140
+ exit 1
141
+ fi
142
+ if ! "$PYTHON_BIN" -m pip install --user --upgrade --force-reinstall --disable-pip-version-check "$PIP_TARGET"; then
143
+ echo "PF Gate runtime error: failed to install ${PIP_TARGET}. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry." >&2
144
+ exit 1
145
+ fi
146
+ fi
147
+ else
148
+ if ! "$PYTHON_BIN" -m pip install --upgrade --disable-pip-version-check "$PIP_TARGET"; then
149
+ if [ "$IS_MANAGED_VENV" -eq 1 ]; then
150
+ echo "PF Gate runtime error: failed to install ${PIP_TARGET}. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry." >&2
151
+ exit 1
152
+ fi
153
+ if ! "$PYTHON_BIN" -m pip install --user --upgrade --disable-pip-version-check "$PIP_TARGET"; then
154
+ echo "PF Gate runtime error: failed to install ${PIP_TARGET}. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry." >&2
155
+ exit 1
156
+ fi
157
+ fi
158
+ fi
159
+ fi
160
+
161
+ if [ -z "${PF_GATE_PIP_SPEC:-}" ] && [ -n "$BUNDLED_HASH" ]; then
162
+ write_wheel_stamp "$BUNDLED_HASH"
163
+ fi
164
+
165
+ exec "$PYTHON_BIN" -m persons_field.terminal.launcher "$@"
package/bin/pf.mjs DELETED
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { runCli } from "../lib/main.mjs";
4
-
5
- runCli(process.argv.slice(2)).catch((error) => {
6
- const message = error instanceof Error ? error.message : String(error);
7
- console.error(`PF Gate launcher error: ${message}`);
8
- process.exit(1);
9
- });
10
-
package/lib/main.mjs DELETED
@@ -1,648 +0,0 @@
1
- import { spawn, spawnSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import path from "node:path";
4
- import process from "node:process";
5
- import { createInterface } from "node:readline";
6
- import { createRequire } from "node:module";
7
- import { fileURLToPath } from "node:url";
8
-
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
- const PACKAGE_ROOT = path.resolve(__dirname, "..");
12
- const require = createRequire(import.meta.url);
13
- const NPM_REGISTRY = "https://registry.npmjs.org/";
14
- const CLI_PACKAGE_NAME = "@qpfai/pf-gate-cli";
15
- const CLI_PACKAGE_VERSION = (() => {
16
- try {
17
- const packageJson = require(path.join(PACKAGE_ROOT, "package.json"));
18
- return String(packageJson?.version || "").trim();
19
- } catch {
20
- return "";
21
- }
22
- })();
23
-
24
- export function resolveCliPackageVersion() {
25
- return CLI_PACKAGE_VERSION;
26
- }
27
-
28
- function envFlagEnabled(value) {
29
- return /^(1|true|yes|on)$/i.test(String(value || "").trim());
30
- }
31
-
32
- function parseVersion(rawVersion) {
33
- const normalized = String(rawVersion || "")
34
- .trim()
35
- .replace(/^v/i, "");
36
- const [coreRaw, prereleaseRaw = ""] = normalized.split("-", 2);
37
- const core = coreRaw
38
- .split(".")
39
- .map((segment) => Number.parseInt(segment, 10))
40
- .map((value) => (Number.isFinite(value) ? value : 0))
41
- .slice(0, 3);
42
- while (core.length < 3) {
43
- core.push(0);
44
- }
45
- return {
46
- core,
47
- prerelease: prereleaseRaw.trim(),
48
- };
49
- }
50
-
51
- export function compareCliVersions(left, right) {
52
- const a = parseVersion(left);
53
- const b = parseVersion(right);
54
- for (let index = 0; index < 3; index += 1) {
55
- if (a.core[index] > b.core[index]) {
56
- return 1;
57
- }
58
- if (a.core[index] < b.core[index]) {
59
- return -1;
60
- }
61
- }
62
- if (!a.prerelease && !b.prerelease) {
63
- return 0;
64
- }
65
- if (!a.prerelease) {
66
- return 1;
67
- }
68
- if (!b.prerelease) {
69
- return -1;
70
- }
71
- return a.prerelease.localeCompare(b.prerelease, undefined, {
72
- numeric: true,
73
- sensitivity: "base",
74
- });
75
- }
76
-
77
- function normalizeVersionValue(raw) {
78
- if (typeof raw === "string") {
79
- return raw.trim();
80
- }
81
- if (Array.isArray(raw) && raw.length > 0 && typeof raw[0] === "string") {
82
- return raw[0].trim();
83
- }
84
- return "";
85
- }
86
-
87
- function normalizeDistTagsValue(raw) {
88
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
89
- return {};
90
- }
91
- const parsed = {};
92
- for (const [key, value] of Object.entries(raw)) {
93
- const tag = String(key || "").trim();
94
- const version = String(value || "").trim();
95
- if (!tag || !version) {
96
- continue;
97
- }
98
- parsed[tag] = version;
99
- }
100
- return parsed;
101
- }
102
-
103
- export function resolveLatestPublishedVersion(options = {}) {
104
- const packageManager = options.packageManager || "npm";
105
- if (packageManager !== "npm") {
106
- return "";
107
- }
108
- const env = options.env || process.env;
109
- const runSync = options.runSync || spawnSync;
110
- const timeoutMs = Number(options.timeoutMs || 5000);
111
- const requestedTag = String(options.tag || "latest").trim() || "latest";
112
- const packageSpecifier =
113
- requestedTag === "latest" ? CLI_PACKAGE_NAME : `${CLI_PACKAGE_NAME}@${requestedTag}`;
114
- const result = runSync(
115
- "npm",
116
- ["view", packageSpecifier, "version", "--json", "--registry", NPM_REGISTRY],
117
- {
118
- env,
119
- encoding: "utf-8",
120
- timeout: timeoutMs,
121
- },
122
- );
123
- if (result.error || (result.status ?? 1) !== 0) {
124
- return "";
125
- }
126
- const stdout = String(result.stdout || "").trim();
127
- if (!stdout) {
128
- return "";
129
- }
130
- try {
131
- return normalizeVersionValue(JSON.parse(stdout));
132
- } catch {
133
- return normalizeVersionValue(stdout.replace(/^"+|"+$/g, ""));
134
- }
135
- }
136
-
137
- export function resolvePublishedDistTags(options = {}) {
138
- const packageManager = options.packageManager || "npm";
139
- if (packageManager !== "npm") {
140
- return {};
141
- }
142
- const env = options.env || process.env;
143
- const runSync = options.runSync || spawnSync;
144
- const timeoutMs = Number(options.timeoutMs || 5000);
145
- const result = runSync(
146
- "npm",
147
- ["view", CLI_PACKAGE_NAME, "dist-tags", "--json", "--registry", NPM_REGISTRY],
148
- {
149
- env,
150
- encoding: "utf-8",
151
- timeout: timeoutMs,
152
- },
153
- );
154
- if (result.error || (result.status ?? 1) !== 0) {
155
- return {};
156
- }
157
- const stdout = String(result.stdout || "").trim();
158
- if (!stdout) {
159
- return {};
160
- }
161
- try {
162
- return normalizeDistTagsValue(JSON.parse(stdout));
163
- } catch {
164
- return {};
165
- }
166
- }
167
-
168
- export function resolveAutoUpdateTag(options = {}) {
169
- const env = options.env || process.env;
170
- const forcedTag = String(options.updateTag || env.PF_GATE_UPDATE_TAG || "").trim();
171
- if (forcedTag) {
172
- return forcedTag;
173
- }
174
-
175
- const distTags = normalizeDistTagsValue(options.distTags || {});
176
- const currentVersion = String(options.currentVersion || "").trim();
177
- if (!currentVersion || !distTags.latest) {
178
- return "latest";
179
- }
180
-
181
- for (const [tag, version] of Object.entries(distTags)) {
182
- if (version === currentVersion) {
183
- return tag;
184
- }
185
- }
186
-
187
- if (compareCliVersions(currentVersion, distTags.latest) <= 0) {
188
- return "latest";
189
- }
190
-
191
- let selectedTag = "latest";
192
- let selectedVersion = "";
193
- for (const [tag, version] of Object.entries(distTags)) {
194
- if (tag === "latest") {
195
- continue;
196
- }
197
- if (compareCliVersions(version, currentVersion) <= 0) {
198
- continue;
199
- }
200
- if (!selectedVersion || compareCliVersions(version, selectedVersion) < 0) {
201
- selectedTag = tag;
202
- selectedVersion = version;
203
- }
204
- }
205
- return selectedTag;
206
- }
207
-
208
- export function maybeAutoUpdateCli(options = {}) {
209
- const env = options.env || process.env;
210
- const log =
211
- typeof options.log === "function"
212
- ? options.log
213
- : (message) => process.stderr.write(String(message));
214
- if (envFlagEnabled(env.PF_GATE_DISABLE_AUTO_UPDATE)) {
215
- return { updated: false, latestVersion: "" };
216
- }
217
- if (envFlagEnabled(env.PF_GATE_AUTO_UPDATE_APPLIED)) {
218
- return { updated: false, latestVersion: "" };
219
- }
220
-
221
- const packageManager = options.packageManager || detectPackageManager(env) || "npm";
222
- if (packageManager !== "npm") {
223
- return { updated: false, latestVersion: "" };
224
- }
225
-
226
- const currentVersion = String(options.currentVersion || CLI_PACKAGE_VERSION).trim();
227
- if (!currentVersion) {
228
- return { updated: false, latestVersion: "" };
229
- }
230
-
231
- const runSync = options.runSync || spawnSync;
232
- const latestVersionOverride = String(options.latestVersionOverride || "").trim();
233
- const latestVersion =
234
- latestVersionOverride ||
235
- resolveLatestPublishedVersion({
236
- packageManager,
237
- env,
238
- runSync,
239
- timeoutMs: options.timeoutMs,
240
- tag: options.updateTag,
241
- });
242
- if (!latestVersion) {
243
- return { updated: false, latestVersion: "" };
244
- }
245
- if (compareCliVersions(latestVersion, currentVersion) <= 0) {
246
- return { updated: false, latestVersion };
247
- }
248
-
249
- if (typeof options.shouldInstall === "function") {
250
- const shouldInstall = Boolean(
251
- options.shouldInstall({
252
- currentVersion,
253
- latestVersion,
254
- packageName: CLI_PACKAGE_NAME,
255
- }),
256
- );
257
- if (!shouldInstall) {
258
- return { updated: false, latestVersion };
259
- }
260
- }
261
-
262
- const installTarget =
263
- String(options.installTarget || "").trim() || CLI_PACKAGE_NAME;
264
- log(`PF Gate launcher: updating ${CLI_PACKAGE_NAME} ${currentVersion} -> ${latestVersion}...\n`);
265
- const installResult = runSync(
266
- "npm",
267
- ["install", "-g", installTarget, "--registry", NPM_REGISTRY],
268
- {
269
- env,
270
- stdio: "inherit",
271
- },
272
- );
273
- if (installResult.error || (installResult.status ?? 1) !== 0) {
274
- log("PF Gate launcher warning: auto-update failed; continuing with installed version.\n");
275
- return { updated: false, latestVersion };
276
- }
277
- return { updated: true, latestVersion };
278
- }
279
-
280
- export function resolveEffectiveCliVersion(options = {}) {
281
- const env = options.env || process.env;
282
- const currentVersion = String(options.currentVersion || CLI_PACKAGE_VERSION).trim();
283
- const existingVersion = String(env.PF_GATE_CLI_VERSION || "").trim();
284
- if (existingVersion) {
285
- return existingVersion;
286
- }
287
-
288
- const updateResult = options.updateResult || {};
289
- const updated = Boolean(updateResult.updated);
290
- const latestVersion = String(updateResult.latestVersion || "").trim();
291
- if (updated && latestVersion) {
292
- return latestVersion;
293
- }
294
- return currentVersion;
295
- }
296
-
297
- export function promptOutputStream(primaryOut = process.stderr, secondaryOut = process.stdout) {
298
- if (primaryOut?.isTTY) {
299
- return primaryOut;
300
- }
301
- if (secondaryOut?.isTTY) {
302
- return secondaryOut;
303
- }
304
- return primaryOut || secondaryOut;
305
- }
306
-
307
- export function isInteractivePrompt(
308
- streamIn = process.stdin,
309
- primaryOut = process.stderr,
310
- secondaryOut = process.stdout,
311
- ) {
312
- const output = promptOutputStream(primaryOut, secondaryOut);
313
- return Boolean(streamIn?.isTTY && output?.isTTY);
314
- }
315
-
316
- async function promptYesNo(
317
- message,
318
- { streamIn = process.stdin, streamOut = process.stderr, fallbackOut = process.stdout } = {},
319
- ) {
320
- const output = promptOutputStream(streamOut, fallbackOut);
321
- if (!isInteractivePrompt(streamIn, output)) {
322
- return false;
323
- }
324
- return await new Promise((resolve) => {
325
- const rl = createInterface({
326
- input: streamIn,
327
- output,
328
- });
329
-
330
- const ask = () => {
331
- rl.question(`${message} [Y/N]: `, (rawAnswer) => {
332
- const answer = String(rawAnswer || "").trim().toLowerCase();
333
- if (answer === "y" || answer === "yes") {
334
- rl.close();
335
- resolve(true);
336
- return;
337
- }
338
- if (answer === "n" || answer === "no") {
339
- rl.close();
340
- resolve(false);
341
- return;
342
- }
343
- output.write("Please enter Y or N.\n");
344
- ask();
345
- });
346
- };
347
-
348
- ask();
349
- });
350
- }
351
-
352
- function shouldAutoUpdateForArgs(args) {
353
- if (!Array.isArray(args) || args.length === 0) {
354
- return true;
355
- }
356
- const command = String(args[0] || "")
357
- .trim()
358
- .toLowerCase();
359
- return command === "gate" || command === "ux" || command === "shell";
360
- }
361
-
362
- const PLATFORM_PACKAGE_BY_TARGET = Object.freeze({
363
- "x86_64-unknown-linux-musl": "@qpfai/pf-gate-cli-linux-x64",
364
- "aarch64-unknown-linux-musl": "@qpfai/pf-gate-cli-linux-arm64",
365
- "x86_64-apple-darwin": "@qpfai/pf-gate-cli-darwin-x64",
366
- "aarch64-apple-darwin": "@qpfai/pf-gate-cli-darwin-arm64",
367
- "x86_64-pc-windows-msvc": "@qpfai/pf-gate-cli-win32-x64",
368
- "aarch64-pc-windows-msvc": "@qpfai/pf-gate-cli-win32-arm64",
369
- });
370
-
371
- function commandForPackageManager(packageManager) {
372
- if (packageManager === "bun") {
373
- return "bun install -g @qpfai/pf-gate-cli";
374
- }
375
- return "npm install -g @qpfai/pf-gate-cli";
376
- }
377
-
378
- function firstExisting(paths, existsSyncFn) {
379
- for (const candidate of paths) {
380
- if (existsSyncFn(candidate)) {
381
- return candidate;
382
- }
383
- }
384
- return null;
385
- }
386
-
387
- export function resolveTargetTriple(platform = process.platform, arch = process.arch) {
388
- switch (platform) {
389
- case "linux":
390
- case "android":
391
- if (arch === "x64") {
392
- return "x86_64-unknown-linux-musl";
393
- }
394
- if (arch === "arm64") {
395
- return "aarch64-unknown-linux-musl";
396
- }
397
- return null;
398
- case "darwin":
399
- if (arch === "x64") {
400
- return "x86_64-apple-darwin";
401
- }
402
- if (arch === "arm64") {
403
- return "aarch64-apple-darwin";
404
- }
405
- return null;
406
- case "win32":
407
- if (arch === "x64") {
408
- return "x86_64-pc-windows-msvc";
409
- }
410
- if (arch === "arm64") {
411
- return "aarch64-pc-windows-msvc";
412
- }
413
- return null;
414
- default:
415
- return null;
416
- }
417
- }
418
-
419
- export function packageAliasForTarget(targetTriple) {
420
- return PLATFORM_PACKAGE_BY_TARGET[targetTriple] || null;
421
- }
422
-
423
- export function binaryNameCandidates(platform = process.platform) {
424
- if (platform === "win32") {
425
- return ["pf-gate.exe", "pf-gate.cmd", "pf-gate.bat"];
426
- }
427
- return ["pf-gate"];
428
- }
429
-
430
- export function prependPath(existingPath, newDirs, platform = process.platform) {
431
- const separator = platform === "win32" ? ";" : ":";
432
- const current = String(existingPath || "")
433
- .split(separator)
434
- .filter(Boolean);
435
- return [...newDirs, ...current].join(separator);
436
- }
437
-
438
- export function detectPackageManager(env = process.env, dirnameHint = __dirname) {
439
- const userAgent = String(env.npm_config_user_agent || "");
440
- if (/\bbun\//.test(userAgent)) {
441
- return "bun";
442
- }
443
- const execPath = String(env.npm_execpath || "");
444
- if (execPath.includes("bun")) {
445
- return "bun";
446
- }
447
- if (
448
- dirnameHint.includes(".bun/install/global") ||
449
- dirnameHint.includes(".bun\\install\\global")
450
- ) {
451
- return "bun";
452
- }
453
- if (userAgent) {
454
- return "npm";
455
- }
456
- return null;
457
- }
458
-
459
- export function shouldUseShellForBinary(binaryPath, platform = process.platform) {
460
- if (platform !== "win32") {
461
- return false;
462
- }
463
- return /\.(cmd|bat)$/i.test(binaryPath);
464
- }
465
-
466
- export function resolveRuntimeBinary(options = {}) {
467
- const platform = options.platform || process.platform;
468
- const arch = options.arch || process.arch;
469
- const packageRoot = options.packageRoot || PACKAGE_ROOT;
470
- const existsSyncFn = options.existsSync || existsSync;
471
- const requireResolve = options.requireResolve || ((specifier) => require.resolve(specifier));
472
-
473
- const targetTriple = resolveTargetTriple(platform, arch);
474
- if (!targetTriple) {
475
- throw new Error(`Unsupported platform: ${platform} (${arch})`);
476
- }
477
-
478
- const packageAlias = packageAliasForTarget(targetTriple);
479
- if (!packageAlias) {
480
- throw new Error(`Unsupported target triple: ${targetTriple}`);
481
- }
482
-
483
- const names = binaryNameCandidates(platform);
484
- const localVendorRoot = path.join(packageRoot, "vendor");
485
- const localBinaryCandidates = names.map((name) =>
486
- path.join(localVendorRoot, targetTriple, "pf-gate", name),
487
- );
488
-
489
- let vendorRoot = null;
490
- try {
491
- const packageJsonPath = requireResolve(`${packageAlias}/package.json`);
492
- vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
493
- } catch {
494
- const localBinary = firstExisting(localBinaryCandidates, existsSyncFn);
495
- if (localBinary) {
496
- vendorRoot = localVendorRoot;
497
- }
498
- }
499
-
500
- if (!vendorRoot) {
501
- const packageManager = detectPackageManager();
502
- const reinstallCommand = commandForPackageManager(packageManager);
503
- throw new Error(
504
- `Missing optional dependency ${packageAlias}. Reinstall PF Gate CLI: ${reinstallCommand}`,
505
- );
506
- }
507
-
508
- const binaryCandidates = names.map((name) => path.join(vendorRoot, targetTriple, "pf-gate", name));
509
- const binaryPath = firstExisting(binaryCandidates, existsSyncFn);
510
- if (!binaryPath) {
511
- throw new Error(
512
- `PF Gate binary missing for ${targetTriple}. Expected one of: ${binaryCandidates.join(", ")}`,
513
- );
514
- }
515
-
516
- const pathDir = path.join(vendorRoot, targetTriple, "path");
517
- const additionalPathDirs = existsSyncFn(pathDir) ? [pathDir] : [];
518
- return {
519
- targetTriple,
520
- packageAlias,
521
- vendorRoot,
522
- binaryPath,
523
- additionalPathDirs,
524
- };
525
- }
526
-
527
- function forwardSignal(child, signal) {
528
- if (child.killed) {
529
- return;
530
- }
531
- try {
532
- child.kill(signal);
533
- } catch {
534
- // Ignore child process signaling failures.
535
- }
536
- }
537
-
538
- export async function runCli(args) {
539
- const env = { ...process.env };
540
- const packageManager = detectPackageManager(env);
541
- const effectivePackageManager = packageManager || "npm";
542
- const shouldAutoUpdate = shouldAutoUpdateForArgs(args);
543
- const currentVersion = String(CLI_PACKAGE_VERSION || "").trim();
544
- let updateTag = "latest";
545
-
546
- let approvedUpdate = true;
547
- let latestVersionHint = "";
548
- if (
549
- shouldAutoUpdate &&
550
- effectivePackageManager === "npm" &&
551
- currentVersion &&
552
- !envFlagEnabled(env.PF_GATE_DISABLE_AUTO_UPDATE) &&
553
- !envFlagEnabled(env.PF_GATE_AUTO_UPDATE_APPLIED)
554
- ) {
555
- const distTags = resolvePublishedDistTags({
556
- packageManager: effectivePackageManager,
557
- env,
558
- runSync: spawnSync,
559
- });
560
- updateTag = resolveAutoUpdateTag({
561
- currentVersion,
562
- distTags,
563
- env,
564
- });
565
- latestVersionHint = resolveLatestPublishedVersion({
566
- packageManager: effectivePackageManager,
567
- env,
568
- runSync: spawnSync,
569
- tag: updateTag,
570
- });
571
- if (!latestVersionHint && updateTag !== "latest") {
572
- updateTag = "latest";
573
- latestVersionHint = resolveLatestPublishedVersion({
574
- packageManager: effectivePackageManager,
575
- env,
576
- runSync: spawnSync,
577
- tag: updateTag,
578
- });
579
- }
580
- if (latestVersionHint && compareCliVersions(latestVersionHint, currentVersion) > 0) {
581
- const tagHint = updateTag === "latest" ? "" : ` [channel: ${updateTag}]`;
582
- approvedUpdate = await promptYesNo(
583
- `PF Gate launcher: update available ${CLI_PACKAGE_NAME} ${currentVersion} -> ${latestVersionHint}${tagHint}. Update now?`,
584
- );
585
- }
586
- }
587
-
588
- const installTarget =
589
- updateTag === "latest" ? CLI_PACKAGE_NAME : `${CLI_PACKAGE_NAME}@${updateTag}`;
590
- const updateResult = shouldAutoUpdate
591
- ? maybeAutoUpdateCli({
592
- currentVersion: CLI_PACKAGE_VERSION,
593
- packageManager: effectivePackageManager,
594
- env,
595
- latestVersionOverride: latestVersionHint,
596
- updateTag,
597
- installTarget,
598
- shouldInstall: () => approvedUpdate,
599
- })
600
- : { updated: false, latestVersion: "" };
601
-
602
- if (updateResult.updated) {
603
- env.PF_GATE_AUTO_UPDATE_APPLIED = "1";
604
- }
605
- env.PF_GATE_CLI_VERSION = resolveEffectiveCliVersion({
606
- env,
607
- currentVersion: CLI_PACKAGE_VERSION,
608
- updateResult,
609
- });
610
-
611
- const runtime = resolveRuntimeBinary();
612
- if (runtime.additionalPathDirs.length > 0) {
613
- env.PATH = prependPath(env.PATH, runtime.additionalPathDirs);
614
- }
615
- const managedByVar = packageManager === "bun" ? "PF_GATE_MANAGED_BY_BUN" : "PF_GATE_MANAGED_BY_NPM";
616
- env[managedByVar] = "1";
617
-
618
- const child = spawn(runtime.binaryPath, args, {
619
- stdio: "inherit",
620
- env,
621
- shell: shouldUseShellForBinary(runtime.binaryPath),
622
- });
623
-
624
- child.on("error", (error) => {
625
- console.error(error);
626
- process.exit(1);
627
- });
628
-
629
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
630
- process.on(signal, () => forwardSignal(child, signal));
631
- }
632
-
633
- const childResult = await new Promise((resolve) => {
634
- child.on("exit", (code, signal) => {
635
- if (signal) {
636
- resolve({ type: "signal", signal });
637
- return;
638
- }
639
- resolve({ type: "code", exitCode: code ?? 1 });
640
- });
641
- });
642
-
643
- if (childResult.type === "signal") {
644
- process.kill(process.pid, childResult.signal);
645
- return;
646
- }
647
- process.exit(childResult.exitCode);
648
- }