@4xeoz/re-entry 0.2.9 → 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,8 +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
- This uses the deployed Re-entry Cloud Receiver by default:
30
- `https://reentry-cloud.vercel.app`.
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.
31
37
 
32
38
  The interactive CLI first offers Desktop, the current folder, or a small folder browser. Choose
33
39
  with ↑/↓ and Enter. If you want to skip the picker, pass the workspace directly:
@@ -53,8 +59,7 @@ npm install --global @4xeoz/re-entry
53
59
  re-entry install --codex-cd /absolute/path/to/your/project
54
60
  ```
55
61
 
56
- The published package uses the hosted Receiver by default. Use `--receiver` only when testing a
57
- different Receiver, such as the local preview:
62
+ Use `--receiver` to select an accepted replacement Receiver, such as a local historical preview:
58
63
 
59
64
  ```sh
60
65
  npx --yes --package=@4xeoz/re-entry re-entry install \
@@ -69,7 +74,7 @@ choose Desktop, the current folder, or another Codex workspace
69
74
  -> check Node + find Codex + validate the selected directory
70
75
  -> open the dedicated Re-entry user account page in the default browser
71
76
  -> sign in or create a user account
72
- -> click Pair this Mac in the dashboard
77
+ -> land on the user dashboard and click Pair this Mac
73
78
  -> enter the short pairing code in the CLI
74
79
  -> save the device credential with mode 0600
75
80
  -> install and start a per-user macOS LaunchAgent
@@ -197,9 +202,15 @@ the npm account that owns the `4xeoz` scope, publish from this directory:
197
202
 
198
203
  ```sh
199
204
  npm login
205
+ npm whoami
206
+ npm version patch --no-git-tag-version
207
+ npm run verify
200
208
  npm publish --access public
201
209
  ```
202
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
+
203
214
  After publishing, users run `npx --yes --package=@4xeoz/re-entry re-entry install`. The explicit
204
215
  `--package` form works across npm versions when the package is scoped; the executable itself is
205
216
  still named `re-entry`.
@@ -251,9 +262,9 @@ npm start -- \
251
262
  ```
252
263
 
253
264
  On the first run, the Connector checks the Mac, opens the dedicated Re-entry user account page,
254
- waits for you to create or sign in to an account and click Pair this Mac, then asks for the
255
- dashboard code and stores the credential. On later runs it reuses the saved credential and skips
256
- 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:
257
268
 
258
269
  ```sh
259
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.9",
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://reentry-cloud.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