@4pm/cli 1.4.5 → 1.4.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.
Files changed (2) hide show
  1. package/dist/index.js +45 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28420,6 +28420,31 @@ function collectFingerprint() {
28420
28420
  return { fingerprint, hostname: host };
28421
28421
  }
28422
28422
 
28423
+ // src/utils/secure-url.ts
28424
+ function isLocalHost(hostname2) {
28425
+ return hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1" || hostname2.endsWith(".localhost");
28426
+ }
28427
+ function isInsecureRemoteUrl(url) {
28428
+ let parsed;
28429
+ try {
28430
+ parsed = new URL(url);
28431
+ } catch {
28432
+ return false;
28433
+ }
28434
+ if (parsed.protocol === "https:" || parsed.protocol === "wss:") return false;
28435
+ return !isLocalHost(parsed.hostname);
28436
+ }
28437
+ function insecureTransportAllowed() {
28438
+ const flag = (process.env.FOURPM_ALLOW_INSECURE_TRANSPORT ?? "").trim().toLowerCase();
28439
+ return flag === "1" || flag === "true" || flag === "yes";
28440
+ }
28441
+ var INSECURE_URL_BLOCKED = "\u2717 Refusing to connect over plaintext (http/ws) to a non-local host: credentials (pairing codes / ws_token) would travel in the clear to an unauthenticated server. Use an https://server URL. To override on a trusted private network only, set FOURPM_ALLOW_INSECURE_TRANSPORT=1.";
28442
+ function assertSecureRemoteUrl(url) {
28443
+ if (isInsecureRemoteUrl(url) && !insecureTransportAllowed()) {
28444
+ throw new Error(INSECURE_URL_BLOCKED);
28445
+ }
28446
+ }
28447
+
28423
28448
  // src/services/api.ts
28424
28449
  var CliApiError = class extends Error {
28425
28450
  constructor(errorCode, message, status) {
@@ -28443,6 +28468,7 @@ async function unwrap(res) {
28443
28468
  return envelope.data;
28444
28469
  }
28445
28470
  async function post(serverUrl, path, body) {
28471
+ assertSecureRemoteUrl(serverUrl);
28446
28472
  const res = await fetch(`${serverUrl}${path}`, {
28447
28473
  method: "POST",
28448
28474
  headers: { "Content-Type": "application/json" },
@@ -28478,30 +28504,12 @@ async function fetchCliVersion(serverUrl) {
28478
28504
  return unwrap(res);
28479
28505
  }
28480
28506
 
28481
- // src/utils/secure-url.ts
28482
- function isLocalHost(hostname2) {
28483
- return hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1" || hostname2.endsWith(".localhost");
28484
- }
28485
- function isInsecureRemoteUrl(url) {
28486
- let parsed;
28487
- try {
28488
- parsed = new URL(url);
28489
- } catch {
28490
- return false;
28491
- }
28492
- if (parsed.protocol === "https:" || parsed.protocol === "wss:") return false;
28493
- return !isLocalHost(parsed.hostname);
28494
- }
28495
- var INSECURE_URL_WARNING = "\u26A0 Insecure transport: connecting over plaintext (http/ws) to a non-local host. Pairing codes and the ws_token are sent in cleartext and the server is not authenticated (the app-layer encryption does not protect against a man-in-the-middle). Use an https:// server URL in production.";
28496
-
28497
28507
  // src/commands/link.ts
28498
28508
  function sanitizeProfileName(name) {
28499
28509
  return name.replace(/[^a-zA-Z0-9._-]/g, "_") || "profile";
28500
28510
  }
28501
28511
  async function runLink(serverUrl, explicitProfile, token = null) {
28502
- if (isInsecureRemoteUrl(serverUrl)) console.warn(`
28503
- ${INSECURE_URL_WARNING}
28504
- `);
28512
+ assertSecureRemoteUrl(serverUrl);
28505
28513
  let result;
28506
28514
  if (token) {
28507
28515
  console.log(`\u2500\u2500 4PM cli headless pairing (profile: ${explicitProfile ?? "default"}) \u2500\u2500`);
@@ -28782,17 +28790,17 @@ function isValidRoleSet(roles) {
28782
28790
 
28783
28791
  // vendor/@4pm/constants/worker-tools.ts
28784
28792
  var WORKER_TOOL_CATALOG = [
28785
- { id: "git", label: "Git", category: "vcs", versionArg: "--version", installPackage: null },
28786
- { id: "node", label: "Node.js", category: "runtime", versionArg: "--version", installPackage: null },
28787
- { id: "npm", label: "npm", category: "runtime", versionArg: "--version", installPackage: null },
28788
- { id: "pnpm", label: "pnpm", category: "runtime", versionArg: "--version", installPackage: "pnpm" },
28789
- { id: "claude", label: "Claude Code", category: "ai-cli", versionArg: "--version", installPackage: "@anthropic-ai/claude-code" },
28790
- { id: "codex", label: "Codex", category: "ai-cli", versionArg: "--version", installPackage: "@openai/codex" },
28791
- { id: "gh", label: "GitHub CLI", category: "vcs", versionArg: "--version", installPackage: null },
28792
- { id: "glab", label: "GitLab CLI", category: "vcs", versionArg: "--version", installPackage: null }
28793
+ { id: "git", label: "Git", category: "vcs", versionArg: "--version", installPackage: null, installable: false },
28794
+ { id: "node", label: "Node.js", category: "runtime", versionArg: "--version", installPackage: null, installable: false },
28795
+ { id: "npm", label: "npm", category: "runtime", versionArg: "--version", installPackage: null, installable: false },
28796
+ { id: "pnpm", label: "pnpm", category: "runtime", versionArg: "--version", installPackage: "pnpm", installable: false },
28797
+ { id: "claude", label: "Claude Code", category: "ai-cli", versionArg: "--version", installPackage: "@anthropic-ai/claude-code", installable: false },
28798
+ { id: "codex", label: "Codex", category: "ai-cli", versionArg: "--version", installPackage: "@openai/codex", installable: false },
28799
+ { id: "gh", label: "GitHub CLI", category: "vcs", versionArg: "--version", installPackage: null, installable: false },
28800
+ { id: "glab", label: "GitLab CLI", category: "vcs", versionArg: "--version", installPackage: null, installable: false }
28793
28801
  ];
28794
28802
  var WORKER_TOOL_PREREQUISITE_IDS = WORKER_TOOL_CATALOG.filter(
28795
- (t) => t.installPackage === null
28803
+ (t) => !t.installable
28796
28804
  ).map((t) => t.id);
28797
28805
  var WORKER_TOOL_BUNDLED_GLOBALS = ["npm", "corepack"];
28798
28806
 
@@ -36660,7 +36668,7 @@ async function detectWorkerTools() {
36660
36668
  category: t.category,
36661
36669
  installed: version !== null,
36662
36670
  version,
36663
- installable: t.installPackage !== null
36671
+ installable: t.installable
36664
36672
  };
36665
36673
  })
36666
36674
  );
@@ -36670,10 +36678,10 @@ async function detectWorkerTools() {
36670
36678
  function resolvePackage(name, op) {
36671
36679
  const entry = WORKER_TOOL_CATALOG.find((t) => t.id === name);
36672
36680
  if (entry) {
36673
- if (entry.installPackage === null) {
36681
+ if (!entry.installable) {
36674
36682
  return { error: `"${name}" is a prerequisite and cannot be ${op}ed from here.` };
36675
36683
  }
36676
- return { pkg: entry.installPackage };
36684
+ return { pkg: entry.installPackage ?? name };
36677
36685
  }
36678
36686
  if (WORKER_TOOL_PREREQUISITE_IDS.includes(name)) {
36679
36687
  return { error: `"${name}" is a prerequisite and cannot be ${op}ed from here.` };
@@ -36749,7 +36757,6 @@ var WsClient = class {
36749
36757
  ecdh = null;
36750
36758
  wsToken = "";
36751
36759
  /** Warn about a plaintext-remote server URL only once per process (ADR-0194 finding #1). */
36752
- insecureWarned = false;
36753
36760
  heartbeat = null;
36754
36761
  usageTimer = null;
36755
36762
  logUploadTimer = null;
@@ -36993,10 +37000,12 @@ var WsClient = class {
36993
37000
  }
36994
37001
  const wsBase = this.credential.wsUrl ?? this.credential.serverUrl;
36995
37002
  const wsUrl = wsBase.replace(/^http/, "ws") + "/ws";
36996
- if (!this.insecureWarned && isInsecureRemoteUrl(wsUrl)) {
36997
- this.insecureWarned = true;
36998
- this.bus.log(INSECURE_URL_WARNING, "warn");
36999
- logger.warn("ws.insecure-transport", { url: wsBase });
37003
+ if (isInsecureRemoteUrl(wsUrl) && !insecureTransportAllowed()) {
37004
+ this.stopped = true;
37005
+ this.bus.log(INSECURE_URL_BLOCKED, "error");
37006
+ this.bus.setStatus("stopped");
37007
+ logger.error("ws.insecure-transport-blocked", { url: wsBase });
37008
+ throw new Error(INSECURE_URL_BLOCKED);
37000
37009
  }
37001
37010
  const socket = new wrapper_default(wsUrl);
37002
37011
  this.socket = socket;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "license": "LicenseRef-4PM-Source-Available",