@4xeoz/re-entry 0.2.11 → 0.2.13

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,5 +1,10 @@
1
1
  # Re-entry Local Connector
2
2
 
3
+ > **Cloud Receiver dependency notice — 2026-09-02:** The former `re-entry-weld.vercel.app` Cloud
4
+ > Receiver is deprecated and must not be used for new pairing, credentials, or production traffic.
5
+ > This Connector package remains a reusable/local preview surface, but a new Receiver origin must
6
+ > be supplied explicitly by an accepted replacement service.
7
+
3
8
  Install it once on the Mac where Codex should open; after one dashboard pairing code, a macOS
4
9
  LaunchAgent keeps the outbound Connector running at login.
5
10
 
@@ -26,11 +31,9 @@ npm install --global @4xeoz/re-entry
26
31
  The package installs both `re-entry` and the older `reentry` spelling; `re-entry` is the documented
27
32
  command.
28
33
 
29
- For the retained hosted preview, pass the deployment alias explicitly:
30
- `https://re-entry-weld.vercel.app`.
31
-
32
- The currently published CLI source still has its previous hosted default. Until a new package
33
- release repoints that default, use `--receiver https://re-entry-weld.vercel.app` for hosted tests.
34
+ The CLI no longer has a default hosted Receiver. For any new or historical test, pass an explicit
35
+ accepted Receiver origin with `--receiver` or `REENTRY_RECEIVER_ORIGIN`; the former
36
+ `https://re-entry-weld.vercel.app` alias is retired.
34
37
 
35
38
  The interactive CLI first offers Desktop, the current folder, or a small folder browser. Choose
36
39
  with ↑/↓ and Enter. If you want to skip the picker, pass the workspace directly:
@@ -56,8 +59,7 @@ npm install --global @4xeoz/re-entry
56
59
  re-entry install --codex-cd /absolute/path/to/your/project
57
60
  ```
58
61
 
59
- Use `--receiver` to select the retained hosted preview or a different Receiver, such as the local
60
- preview:
62
+ Use `--receiver` to select an accepted replacement Receiver, such as a local historical preview:
61
63
 
62
64
  ```sh
63
65
  npx --yes --package=@4xeoz/re-entry re-entry install \
@@ -72,7 +74,7 @@ choose Desktop, the current folder, or another Codex workspace
72
74
  -> check Node + find Codex + validate the selected directory
73
75
  -> open the dedicated Re-entry user account page in the default browser
74
76
  -> sign in or create a user account
75
- -> click Pair this Mac in the dashboard
77
+ -> land on the user dashboard and click Pair this Mac
76
78
  -> enter the short pairing code in the CLI
77
79
  -> save the device credential with mode 0600
78
80
  -> install and start a per-user macOS LaunchAgent
@@ -200,9 +202,15 @@ the npm account that owns the `4xeoz` scope, publish from this directory:
200
202
 
201
203
  ```sh
202
204
  npm login
205
+ npm whoami
206
+ npm version patch --no-git-tag-version
207
+ npm run verify
203
208
  npm publish --access public
204
209
  ```
205
210
 
211
+ NPM versions are immutable: if the current version has already been published, bump it before
212
+ publishing (the current published `0.2.11` therefore requires `0.2.12` or another new version).
213
+
206
214
  After publishing, users run `npx --yes --package=@4xeoz/re-entry re-entry install`. The explicit
207
215
  `--package` form works across npm versions when the package is scoped; the executable itself is
208
216
  still named `re-entry`.
@@ -254,9 +262,9 @@ npm start -- \
254
262
  ```
255
263
 
256
264
  On the first run, the Connector checks the Mac, opens the dedicated Re-entry user account page,
257
- waits for you to create or sign in to an account and click Pair this Mac, then asks for the
258
- dashboard code and stores the credential. On later runs it reuses the saved credential and skips
259
- pairing:
265
+ waits for you to create or sign in to an account, lands on the user dashboard, and asks for the
266
+ pairing code. It then stores the credential. On later runs it reuses the saved credential and
267
+ skips pairing:
260
268
 
261
269
  ```sh
262
270
  npm start -- --codex-cd "$HOME/Code/my-host-project"
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@4xeoz/re-entry",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
- "description": "Outbound Local Connector process for the Re-entry Core local preview.",
5
+ "description": "Outbound Local Connector process for Re-entry Core; new Receiver origins must be explicit.",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -11,8 +11,8 @@
11
11
  "src"
12
12
  ],
13
13
  "bin": {
14
- "re-entry": "./src/main.mjs",
15
- "reentry": "./src/main.mjs"
14
+ "re-entry": "src/main.mjs",
15
+ "reentry": "src/main.mjs"
16
16
  },
17
17
  "exports": {
18
18
  ".": "./src/index.mjs",
package/src/main.mjs CHANGED
@@ -34,7 +34,9 @@ import {
34
34
  } from "./macos-service.mjs";
35
35
  import { createTerminalUi } from "./terminal-ui.mjs";
36
36
 
37
- const DEFAULT_RECEIVER_ORIGIN = process.env.REENTRY_RECEIVER_ORIGIN ?? "https://re-entry-weld.vercel.app";
37
+ // The former hosted Cloud Receiver is retired. New pairing must name an accepted replacement
38
+ // explicitly instead of silently sending credentials to the old deployment alias.
39
+ const DEFAULT_RECEIVER_ORIGIN = process.env.REENTRY_RECEIVER_ORIGIN;
38
40
  const DEFAULT_POLL_INTERVAL_MS = 5_000;
39
41
  const DEFAULT_MAX_CONSECUTIVE_ERRORS = 5;
40
42
  const DEFAULT_PAIRING_REQUEST_TIMEOUT_MS = 20_000;
@@ -141,7 +143,7 @@ Commands:
141
143
  pair Legacy Host-code pairing preview
142
144
 
143
145
  Common options:
144
- --receiver <url> Re-entry Receiver origin
146
+ --receiver <url> Accepted Receiver origin (required when pairing a new Mac)
145
147
  --codex-cd <path> Workspace for Codex; interactive mode offers a folder picker if omitted
146
148
  --codex-binary <path> Explicit Codex executable
147
149
  --json Machine-readable output
@@ -274,6 +276,9 @@ async function connect(flags, ui, options = {}) {
274
276
  ui.begin("Connect this Mac", "Create a Re-entry account, pair this Mac, then leave it running");
275
277
  }
276
278
  const receiver = flags.receiver ?? DEFAULT_RECEIVER_ORIGIN;
279
+ if (typeof receiver !== "string" || receiver.trim().length === 0) {
280
+ throw cliFailure("connector_receiver_missing");
281
+ }
277
282
  const credentialFile = flags["credential-file"] ?? defaultCredentialFile();
278
283
  const store = new LocalConnectorCredentialStore({ filename: credentialFile });
279
284
  const current = await store.load();
@@ -307,7 +312,7 @@ async function connect(flags, ui, options = {}) {
307
312
  baseUrl: receiver,
308
313
  requestTimeoutMs: DEFAULT_PAIRING_REQUEST_TIMEOUT_MS,
309
314
  });
310
- const portalUrl = `${receiver}/user-register?next=${encodeURIComponent("/dashboard")}`;
315
+ const portalUrl = `${receiver}/user-register?next=${encodeURIComponent("/user-dashboard")}`;
311
316
  if (ui.interactive) {
312
317
  ui.stopWait("Secure link", "ready");
313
318
  ui.info("Re-entry", portalUrl);
@@ -1035,8 +1040,10 @@ function errorHint(error) {
1035
1040
  connector_codex_binary_not_found: "check --codex-binary or remove it to use automatic discovery",
1036
1041
  connector_codex_unusable: "open Codex, complete login if needed, then run `npm run doctor` again",
1037
1042
  connector_node_unsupported: "use Node.js 24 or newer, then run the command again",
1043
+ connector_receiver_missing: "pass --receiver <replacement-receiver-origin> or set REENTRY_RECEIVER_ORIGIN",
1038
1044
  connector_credentials_missing: "connect this Mac once with `re-entry connect`",
1039
1045
  connector_credentials_expired: "run `re-entry connect` to authorize this Mac again",
1046
+ connector_credentials_already_exists: "use the existing Connector credential or ask for a new pairing code",
1040
1047
  connector_identity_invalid: "run `re-entry connect` and approve this Mac again",
1041
1048
  connector_reauthorization_required: "run `re-entry connect` and approve this Mac again",
1042
1049
  connector_pairing_code_missing: "ask the Host backend for a new pairing code, then run pair in a terminal",
@@ -69,6 +69,14 @@ const CREDENTIAL_FIELDS = Object.freeze([
69
69
  "connector_expires_at",
70
70
  "duplicate",
71
71
  ]);
72
+ const TOKENLESS_CREDENTIAL_FIELDS = Object.freeze([
73
+ "type",
74
+ "protocol_version",
75
+ "pairing_id",
76
+ "connector_id",
77
+ "connector_expires_at",
78
+ "duplicate",
79
+ ]);
72
80
  const ERROR_FIELDS = Object.freeze(["error"]);
73
81
  const ERROR_BODY_FIELDS = Object.freeze(["code"]);
74
82
  const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/;
@@ -218,8 +226,16 @@ export class LocalConnectorPairingClient {
218
226
  device_name: deviceName,
219
227
  });
220
228
  if (response.status !== 200) throw await parseHttpFailure(response);
229
+ const credentials = normalizeAccountCredentials(await parseJsonResponse(response));
230
+ if (credentials.duplicate && !Object.hasOwn(credentials, "connector_token")) {
231
+ throw pairingFailure(
232
+ "connector_credentials_already_exists",
233
+ "Pairing is already complete; use the existing Connector credential or create a new pairing",
234
+ { statusCode: response.status },
235
+ );
236
+ }
221
237
  return Object.freeze({
222
- ...normalizeCredentials(await parseJsonResponse(response)),
238
+ ...credentials,
223
239
  browserOpened: false,
224
240
  });
225
241
  }
@@ -358,6 +374,33 @@ function normalizeCredentials(value) {
358
374
  };
359
375
  }
360
376
 
377
+ function normalizeAccountCredentials(value) {
378
+ const fields = value?.duplicate === true ? TOKENLESS_CREDENTIAL_FIELDS : CREDENTIAL_FIELDS;
379
+ requireExactRecord(
380
+ value,
381
+ fields,
382
+ fields,
383
+ "Account Connector credential response",
384
+ "pairing_response_invalid",
385
+ );
386
+ if (value.type !== "webmcp.connector_credentials" || value.protocol_version !== "0.1") {
387
+ throw pairingFailure("pairing_response_invalid", "Account Connector credential response is unsupported");
388
+ }
389
+ if (typeof value.duplicate !== "boolean") {
390
+ throw pairingFailure("pairing_response_invalid", "Account Connector credential duplicate flag is invalid");
391
+ }
392
+ const normalized = {
393
+ pairing_id: requireIdentifier(value.pairing_id, "pairing_id"),
394
+ connector_id: requireIdentifier(value.connector_id, "connector_id"),
395
+ connector_expires_at: requireTimestamp(value.connector_expires_at, "connector_expires_at"),
396
+ duplicate: value.duplicate,
397
+ };
398
+ if (!value.duplicate) {
399
+ normalized.connector_token = requireToken(value.connector_token, "connector_token");
400
+ }
401
+ return normalized;
402
+ }
403
+
361
404
  function normalizeAccountPairingCode(value) {
362
405
  if (typeof value !== "string") {
363
406
  throw pairingFailure("account_pairing_code_invalid", "Pairing code is invalid");
@@ -547,17 +590,17 @@ function pairingFailure(code, message, options = {}) {
547
590
  return new PairingClientError(code, message, options);
548
591
  }
549
592
 
550
- function requireExactRecord(value, allowedFields, requiredFields, label) {
593
+ function requireExactRecord(value, allowedFields, requiredFields, label, failureCode = "pairing_input_invalid") {
551
594
  if (!value || typeof value !== "object" || Array.isArray(value)) {
552
- throw pairingFailure("pairing_input_invalid", `${label} must be an object`);
595
+ throw pairingFailure(failureCode, `${label} must be an object`);
553
596
  }
554
597
  const prototype = Object.getPrototypeOf(value);
555
598
  if (prototype !== Object.prototype && prototype !== null) {
556
- throw pairingFailure("pairing_input_invalid", `${label} must be a plain object`);
599
+ throw pairingFailure(failureCode, `${label} must be a plain object`);
557
600
  }
558
601
  const fields = Object.keys(value);
559
602
  if (fields.some((field) => !allowedFields.includes(field)) || requiredFields.some((field) => !fields.includes(field))) {
560
- throw pairingFailure("pairing_input_invalid", `${label} fields are invalid`);
603
+ throw pairingFailure(failureCode, `${label} fields are invalid`);
561
604
  }
562
605
  }
563
606