@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 +19 -11
- package/package.json +4 -4
- package/src/main.mjs +10 -3
- package/src/pairing-client.mjs +48 -5
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
|
-
|
|
30
|
-
`
|
|
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
|
|
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
|
|
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
|
|
258
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Outbound Local Connector process for
|
|
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": "
|
|
15
|
-
"reentry": "
|
|
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
|
-
|
|
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>
|
|
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",
|
package/src/pairing-client.mjs
CHANGED
|
@@ -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
|
-
...
|
|
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(
|
|
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(
|
|
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(
|
|
603
|
+
throw pairingFailure(failureCode, `${label} fields are invalid`);
|
|
561
604
|
}
|
|
562
605
|
}
|
|
563
606
|
|