@4xeoz/re-entry 0.2.11 → 0.2.14
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 +59 -31
- package/package.json +4 -4
- package/src/main.mjs +48 -31
- 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
|
|
|
@@ -13,7 +18,8 @@ Requirements: macOS, Node.js 24+, Codex installed and signed in, and an absolute
|
|
|
13
18
|
Codex may read and write.
|
|
14
19
|
|
|
15
20
|
```sh
|
|
16
|
-
npx
|
|
21
|
+
npx --yes --package=@4xeoz/re-entry re-entry install \
|
|
22
|
+
--receiver https://your-accepted-receiver.example
|
|
17
23
|
```
|
|
18
24
|
|
|
19
25
|
`npx` runs a temporary copy, so it does not leave a permanent shell command behind. Install the
|
|
@@ -24,25 +30,25 @@ npm install --global @4xeoz/re-entry
|
|
|
24
30
|
```
|
|
25
31
|
|
|
26
32
|
The package installs both `re-entry` and the older `reentry` spelling; `re-entry` is the documented
|
|
27
|
-
command.
|
|
33
|
+
command. If you keep using the temporary `npx` form, run later commands with the same executable
|
|
34
|
+
prefix, for example `npx --yes --package=@4xeoz/re-entry re-entry status`.
|
|
28
35
|
|
|
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.
|
|
36
|
+
The CLI no longer has a default hosted Receiver. For any new or historical test, pass an explicit
|
|
37
|
+
accepted Receiver origin with `--receiver` or `REENTRY_RECEIVER_ORIGIN`; the former
|
|
38
|
+
`https://re-entry-weld.vercel.app` alias is retired.
|
|
34
39
|
|
|
35
40
|
The interactive CLI first offers Desktop, the current folder, or a small folder browser. Choose
|
|
36
41
|
with ↑/↓ and Enter. If you want to skip the picker, pass the workspace directly:
|
|
37
42
|
|
|
38
43
|
```sh
|
|
39
|
-
npx
|
|
44
|
+
npx --yes --package=@4xeoz/re-entry re-entry install \
|
|
45
|
+
--receiver https://your-accepted-receiver.example \
|
|
40
46
|
--codex-cd /absolute/path/to/your/project
|
|
41
47
|
```
|
|
42
48
|
|
|
43
49
|
The guided screen stays intentionally small: **Workspace → System check → Connect Re-entry**. It
|
|
44
|
-
shows one
|
|
45
|
-
paths stay out of the normal success screen.
|
|
50
|
+
shows one executable next command when setup finishes; internal Connector IDs, credential paths, and
|
|
51
|
+
log paths stay out of the normal success screen.
|
|
46
52
|
|
|
47
53
|
Run this from the Host project directory, your home directory, or another normal working
|
|
48
54
|
directory—not from a checked-out `runtime/local-connector` package directory. npm can treat that
|
|
@@ -53,11 +59,12 @@ installation instead:
|
|
|
53
59
|
|
|
54
60
|
```sh
|
|
55
61
|
npm install --global @4xeoz/re-entry
|
|
56
|
-
re-entry install
|
|
62
|
+
re-entry install \
|
|
63
|
+
--receiver https://your-accepted-receiver.example \
|
|
64
|
+
--codex-cd /absolute/path/to/your/project
|
|
57
65
|
```
|
|
58
66
|
|
|
59
|
-
Use `--receiver` to select
|
|
60
|
-
preview:
|
|
67
|
+
Use `--receiver` to select an accepted replacement Receiver, such as a local historical preview:
|
|
61
68
|
|
|
62
69
|
```sh
|
|
63
70
|
npx --yes --package=@4xeoz/re-entry re-entry install \
|
|
@@ -72,7 +79,7 @@ choose Desktop, the current folder, or another Codex workspace
|
|
|
72
79
|
-> check Node + find Codex + validate the selected directory
|
|
73
80
|
-> open the dedicated Re-entry user account page in the default browser
|
|
74
81
|
-> sign in or create a user account
|
|
75
|
-
-> click Pair this Mac
|
|
82
|
+
-> land on the user dashboard and click Pair this Mac
|
|
76
83
|
-> enter the short pairing code in the CLI
|
|
77
84
|
-> save the device credential with mode 0600
|
|
78
85
|
-> install and start a per-user macOS LaunchAgent
|
|
@@ -84,29 +91,43 @@ preview.
|
|
|
84
91
|
|
|
85
92
|
## Check it
|
|
86
93
|
|
|
94
|
+
If you installed the package globally:
|
|
95
|
+
|
|
87
96
|
```sh
|
|
88
97
|
re-entry status
|
|
89
98
|
re-entry listen
|
|
90
99
|
re-entry --help
|
|
100
|
+
re-entry stop
|
|
101
|
+
re-entry uninstall
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
If you used the temporary `npx` invocation:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
npx --yes --package=@4xeoz/re-entry re-entry status
|
|
108
|
+
npx --yes --package=@4xeoz/re-entry re-entry listen
|
|
109
|
+
npx --yes --package=@4xeoz/re-entry re-entry --help
|
|
110
|
+
npx --yes --package=@4xeoz/re-entry re-entry stop
|
|
111
|
+
npx --yes --package=@4xeoz/re-entry re-entry uninstall
|
|
91
112
|
```
|
|
92
113
|
|
|
93
114
|
The status view checks the local authorization, background job, Receiver reachability, Node, and
|
|
94
115
|
Codex. If the Receiver rejects a previously saved device credential, the Connector pauses instead
|
|
95
|
-
of retrying forever, and `status`/`listen`
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
of retrying forever, and `status`/`listen` print an executable `connect` command. `listen` watches the
|
|
117
|
+
already-running background Connector and displays new activity until you press Ctrl+C; it does not
|
|
118
|
+
start a competing second poller. Useful development commands are:
|
|
98
119
|
|
|
99
120
|
```sh
|
|
100
|
-
re-entry doctor --codex-cd /absolute/path/to/project
|
|
101
|
-
re-entry connect --receiver http://127.0.0.1:43224
|
|
102
|
-
re-entry claim-once --codex-cd /absolute/path/to/project
|
|
103
|
-
re-entry start --codex-cd /absolute/path/to/project
|
|
121
|
+
npx --yes --package=@4xeoz/re-entry re-entry doctor --codex-cd /absolute/path/to/project
|
|
122
|
+
npx --yes --package=@4xeoz/re-entry re-entry connect --receiver http://127.0.0.1:43224
|
|
123
|
+
npx --yes --package=@4xeoz/re-entry re-entry claim-once --codex-cd /absolute/path/to/project
|
|
124
|
+
npx --yes --package=@4xeoz/re-entry re-entry start --codex-cd /absolute/path/to/project
|
|
104
125
|
```
|
|
105
126
|
|
|
106
127
|
Test the local Codex handoff without waiting for Cloud work:
|
|
107
128
|
|
|
108
129
|
```sh
|
|
109
|
-
re-entry test "Reply with: Re-entry is working."
|
|
130
|
+
npx --yes --package=@4xeoz/re-entry re-entry test "Reply with: Re-entry is working."
|
|
110
131
|
```
|
|
111
132
|
|
|
112
133
|
This starts one fresh local Codex process through the same adapter seam used by real deliveries. It
|
|
@@ -115,8 +136,8 @@ does not create a Grant, claim Receiver work, or prove the browser/WebMCP return
|
|
|
115
136
|
To pause or remove the local Connector:
|
|
116
137
|
|
|
117
138
|
```sh
|
|
118
|
-
re-entry stop
|
|
119
|
-
re-entry uninstall
|
|
139
|
+
npx --yes --package=@4xeoz/re-entry re-entry stop
|
|
140
|
+
npx --yes --package=@4xeoz/re-entry re-entry uninstall
|
|
120
141
|
```
|
|
121
142
|
|
|
122
143
|
`stop` pauses the macOS background service and keeps the account connection. `uninstall` requires
|
|
@@ -137,8 +158,8 @@ Install the Re-entry Local Connector on this Mac. First read the package README.
|
|
|
137
158
|
or newer and locate the Connector executable. Run `npx --yes --package=@4xeoz/re-entry re-entry install` with an absolute
|
|
138
159
|
project directory. Let the human create or sign in to a Re-entry account, click Pair this Mac, and
|
|
139
160
|
enter the code in the CLI; never copy browser cookies, organization keys, Connector tokens, or private keys
|
|
140
|
-
into chat, logs, source files, or git. Finish by running `re-entry status`
|
|
141
|
-
results without claiming Browser/WebMCP or production deployment.
|
|
161
|
+
into chat, logs, source files, or git. Finish by running `npx --yes --package=@4xeoz/re-entry re-entry status`
|
|
162
|
+
and report the bounded results without claiming Browser/WebMCP or production deployment.
|
|
142
163
|
```
|
|
143
164
|
|
|
144
165
|
## Package map
|
|
@@ -200,15 +221,22 @@ the npm account that owns the `4xeoz` scope, publish from this directory:
|
|
|
200
221
|
|
|
201
222
|
```sh
|
|
202
223
|
npm login
|
|
224
|
+
npm whoami
|
|
225
|
+
npm version patch --no-git-tag-version
|
|
226
|
+
npm run verify
|
|
203
227
|
npm publish --access public
|
|
204
228
|
```
|
|
205
229
|
|
|
230
|
+
NPM versions are immutable: if the target version has already been published, bump it before
|
|
231
|
+
publishing with `npm version patch --no-git-tag-version`.
|
|
232
|
+
|
|
206
233
|
After publishing, users run `npx --yes --package=@4xeoz/re-entry re-entry install`. The explicit
|
|
207
234
|
`--package` form works across npm versions when the package is scoped; the executable itself is
|
|
208
235
|
still named `re-entry`.
|
|
209
236
|
|
|
210
|
-
|
|
211
|
-
@4xeoz/re-entry` once and then
|
|
237
|
+
A temporary `npx` invocation does not add `re-entry` to your shell `PATH`. If you prefer the shorter
|
|
238
|
+
bare commands, run `npm install --global @4xeoz/re-entry` once and then use `re-entry install`
|
|
239
|
+
directly.
|
|
212
240
|
|
|
213
241
|
## Check a Mac before pairing
|
|
214
242
|
|
|
@@ -254,9 +282,9 @@ npm start -- \
|
|
|
254
282
|
```
|
|
255
283
|
|
|
256
284
|
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:
|
|
285
|
+
waits for you to create or sign in to an account, lands on the user dashboard, and asks for the
|
|
286
|
+
pairing code. It then stores the credential. On later runs it reuses the saved credential and
|
|
287
|
+
skips pairing:
|
|
260
288
|
|
|
261
289
|
```sh
|
|
262
290
|
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.14",
|
|
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,11 +34,15 @@ 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;
|
|
41
43
|
const CONNECTOR_VERSION = readConnectorVersion();
|
|
44
|
+
// The npx form is executable even when this CLI was launched from a temporary npx cache.
|
|
45
|
+
const TEMPORARY_CLI_COMMAND = "npx --yes --package=@4xeoz/re-entry re-entry";
|
|
42
46
|
|
|
43
47
|
async function main() {
|
|
44
48
|
let ui;
|
|
@@ -127,6 +131,10 @@ Usage:
|
|
|
127
131
|
re-entry listen Watch live activity
|
|
128
132
|
re-entry test "Reply with hello" Test Codex locally
|
|
129
133
|
|
|
134
|
+
Invocation:
|
|
135
|
+
Temporary npx: npx --yes --package=@4xeoz/re-entry re-entry <command>
|
|
136
|
+
Global install: npm install --global @4xeoz/re-entry, then re-entry <command>
|
|
137
|
+
|
|
130
138
|
Commands:
|
|
131
139
|
install Check this Mac, connect the Re-entry account, and start at login
|
|
132
140
|
status Show account, Receiver, service, Node.js, and Codex readiness
|
|
@@ -141,7 +149,7 @@ Commands:
|
|
|
141
149
|
pair Legacy Host-code pairing preview
|
|
142
150
|
|
|
143
151
|
Common options:
|
|
144
|
-
--receiver <url>
|
|
152
|
+
--receiver <url> Accepted Receiver origin (required when pairing a new Mac)
|
|
145
153
|
--codex-cd <path> Workspace for Codex; interactive mode offers a folder picker if omitted
|
|
146
154
|
--codex-binary <path> Explicit Codex executable
|
|
147
155
|
--json Machine-readable output
|
|
@@ -154,6 +162,10 @@ The Connector opens no inbound port. Host keys never belong on this Mac.
|
|
|
154
162
|
`);
|
|
155
163
|
}
|
|
156
164
|
|
|
165
|
+
function cliCommand(command) {
|
|
166
|
+
return `${TEMPORARY_CLI_COMMAND} ${command}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
157
169
|
function readConnectorVersion() {
|
|
158
170
|
const packageJson = JSON.parse(
|
|
159
171
|
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
@@ -263,7 +275,7 @@ async function pair(flags, ui, options = {}) {
|
|
|
263
275
|
});
|
|
264
276
|
if (ui.interactive) {
|
|
265
277
|
ui.complete("Pairing complete", "Re-entry can now deliver approved work to this Mac.");
|
|
266
|
-
ui.next("
|
|
278
|
+
ui.next(cliCommand("start"), "Wait for approved work in this terminal.");
|
|
267
279
|
} else {
|
|
268
280
|
process.stdout.write(`${JSON.stringify({ event: "connector_paired", connector_id: credentials.connector_id })}\n`);
|
|
269
281
|
}
|
|
@@ -274,6 +286,9 @@ async function connect(flags, ui, options = {}) {
|
|
|
274
286
|
ui.begin("Connect this Mac", "Create a Re-entry account, pair this Mac, then leave it running");
|
|
275
287
|
}
|
|
276
288
|
const receiver = flags.receiver ?? DEFAULT_RECEIVER_ORIGIN;
|
|
289
|
+
if (typeof receiver !== "string" || receiver.trim().length === 0) {
|
|
290
|
+
throw cliFailure("connector_receiver_missing");
|
|
291
|
+
}
|
|
277
292
|
const credentialFile = flags["credential-file"] ?? defaultCredentialFile();
|
|
278
293
|
const store = new LocalConnectorCredentialStore({ filename: credentialFile });
|
|
279
294
|
const current = await store.load();
|
|
@@ -283,7 +298,7 @@ async function connect(flags, ui, options = {}) {
|
|
|
283
298
|
if (ui.interactive) {
|
|
284
299
|
ui.success("Account", "already connected");
|
|
285
300
|
if (!options.guidedInstall) {
|
|
286
|
-
ui.next("
|
|
301
|
+
ui.next(cliCommand("start"), "Wait for approved work in this terminal.");
|
|
287
302
|
}
|
|
288
303
|
} else {
|
|
289
304
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -307,7 +322,7 @@ async function connect(flags, ui, options = {}) {
|
|
|
307
322
|
baseUrl: receiver,
|
|
308
323
|
requestTimeoutMs: DEFAULT_PAIRING_REQUEST_TIMEOUT_MS,
|
|
309
324
|
});
|
|
310
|
-
const portalUrl = `${receiver}/user-register?next=${encodeURIComponent("/dashboard")}`;
|
|
325
|
+
const portalUrl = `${receiver}/user-register?next=${encodeURIComponent("/user-dashboard")}`;
|
|
311
326
|
if (ui.interactive) {
|
|
312
327
|
ui.stopWait("Secure link", "ready");
|
|
313
328
|
ui.info("Re-entry", portalUrl);
|
|
@@ -342,7 +357,7 @@ async function connect(flags, ui, options = {}) {
|
|
|
342
357
|
if (ui.interactive) {
|
|
343
358
|
if (!options.guidedInstall) {
|
|
344
359
|
ui.complete("This Mac is connected", "Re-entry can now route approved work here.");
|
|
345
|
-
ui.next("
|
|
360
|
+
ui.next(cliCommand("start"), "Wait for approved work in this terminal.");
|
|
346
361
|
}
|
|
347
362
|
} else {
|
|
348
363
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -381,14 +396,14 @@ async function status(flags, ui) {
|
|
|
381
396
|
else if (credentials) ui.warning("Cloud", "unavailable");
|
|
382
397
|
|
|
383
398
|
if (reauthorizationRequired) {
|
|
384
|
-
ui.next("
|
|
399
|
+
ui.next(cliCommand("connect"), "Approve this Mac again in your browser; the old credential was rejected.");
|
|
385
400
|
} else if (!connected || !service.running) {
|
|
386
|
-
ui.next("
|
|
401
|
+
ui.next(cliCommand("install"), "Finish setup and start Re-entry in the background.");
|
|
387
402
|
} else if (!receiverReady) {
|
|
388
|
-
ui.next("
|
|
403
|
+
ui.next(cliCommand("status"), "Check again when the Re-entry Cloud service is available.");
|
|
389
404
|
} else {
|
|
390
405
|
ui.complete("Everything looks good", "Re-entry is ready for approved work.");
|
|
391
|
-
ui.next("
|
|
406
|
+
ui.next(cliCommand("listen"), "Watch live activity. Press Ctrl+C when you are done.");
|
|
392
407
|
}
|
|
393
408
|
} else {
|
|
394
409
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -420,11 +435,11 @@ async function listen(flags, ui) {
|
|
|
420
435
|
if (ui.interactive) {
|
|
421
436
|
if (reauthorizationRequired) {
|
|
422
437
|
ui.warning("Account", "reconnect required; the Cloud Receiver rejected this Mac");
|
|
423
|
-
ui.next("
|
|
438
|
+
ui.next(cliCommand("connect"), "Approve this Mac again in your browser.");
|
|
424
439
|
}
|
|
425
440
|
if (!credentials) ui.warning("Account", "not connected");
|
|
426
441
|
if (!service.running) ui.warning("Background", "not running");
|
|
427
|
-
ui.next("
|
|
442
|
+
ui.next(cliCommand("install"), "Finish setup and start the background Connector.");
|
|
428
443
|
} else {
|
|
429
444
|
process.stdout.write(`${JSON.stringify({
|
|
430
445
|
event: "connector_listener_unavailable",
|
|
@@ -489,7 +504,7 @@ async function testCodex(flags, positionals, ui) {
|
|
|
489
504
|
if (ui.interactive) {
|
|
490
505
|
ui.stopWait("Codex", "completed the local test");
|
|
491
506
|
ui.complete("Test passed", "The same fresh-session process seam is ready for Re-entry work.");
|
|
492
|
-
ui.next("
|
|
507
|
+
ui.next(cliCommand("listen"), "Watch the background Connector for approved work.");
|
|
493
508
|
} else {
|
|
494
509
|
process.stdout.write('{"event":"connector_codex_test_passed"}\n');
|
|
495
510
|
}
|
|
@@ -506,7 +521,7 @@ function reportLiveActivity(event, ui) {
|
|
|
506
521
|
);
|
|
507
522
|
} else if (event.event === "connector_reauthorization_required" || event.code === "connector_identity_invalid") {
|
|
508
523
|
ui.stopWait("Reconnect required", "the Cloud Receiver rejected this Mac's saved connection", "warning");
|
|
509
|
-
ui.next("
|
|
524
|
+
ui.next(cliCommand("connect"), "Approve this Mac again in your browser.");
|
|
510
525
|
return;
|
|
511
526
|
} else if (event.event === "connector_poll_failed" || event.event === "local_connector_failed") {
|
|
512
527
|
ui.stopWait("Connection interrupted", "Re-entry is retrying", "warning");
|
|
@@ -529,7 +544,7 @@ async function stop(flags, ui) {
|
|
|
529
544
|
if (!result.supported) ui.warning("Platform", "background service control currently supports macOS only");
|
|
530
545
|
else if (result.stopped) ui.complete("Re-entry is paused", "Your account connection is still saved.");
|
|
531
546
|
else ui.info("Already stopped", "no running background Connector was found");
|
|
532
|
-
if (result.supported) ui.next("
|
|
547
|
+
if (result.supported) ui.next(cliCommand("install"), "Start Re-entry in the background again.");
|
|
533
548
|
} else {
|
|
534
549
|
process.stdout.write(`${JSON.stringify({
|
|
535
550
|
event: "connector_stopped",
|
|
@@ -552,7 +567,7 @@ async function uninstall(flags, ui) {
|
|
|
552
567
|
});
|
|
553
568
|
if (ui.interactive) {
|
|
554
569
|
ui.complete("Removed from this Mac", "The local service, connection, and logs are gone.");
|
|
555
|
-
ui.next("
|
|
570
|
+
ui.next(cliCommand("install"), "Connect this Mac again whenever you are ready.");
|
|
556
571
|
} else {
|
|
557
572
|
process.stdout.write(`${JSON.stringify({
|
|
558
573
|
event: "connector_uninstalled",
|
|
@@ -584,7 +599,7 @@ async function install(flags, ui) {
|
|
|
584
599
|
if (ui.interactive) {
|
|
585
600
|
ui.stopWait("Background", "running at login");
|
|
586
601
|
ui.complete("You're all set", "Re-entry is connected and waiting for approved work.");
|
|
587
|
-
ui.next("
|
|
602
|
+
ui.next(cliCommand("listen"), "Watch live activity. Press Ctrl+C when you are done.");
|
|
588
603
|
} else {
|
|
589
604
|
process.stdout.write(`${JSON.stringify({
|
|
590
605
|
event: "connector_service_installed",
|
|
@@ -623,7 +638,7 @@ async function start(flags, ui) {
|
|
|
623
638
|
} else if (await hasConnectorReauthorizationRequired(credentialFile)) {
|
|
624
639
|
if (ui.interactive) {
|
|
625
640
|
ui.warning("Account", "this Mac needs to be connected again");
|
|
626
|
-
ui.next("
|
|
641
|
+
ui.next(cliCommand("connect"), "Create a new dashboard pairing code and connect this Mac again.");
|
|
627
642
|
} else {
|
|
628
643
|
process.stdout.write('{"event":"connector_reauthorization_required"}\n');
|
|
629
644
|
}
|
|
@@ -669,7 +684,7 @@ async function start(flags, ui) {
|
|
|
669
684
|
});
|
|
670
685
|
if (ui.interactive) {
|
|
671
686
|
ui.stopWait("Reconnect required", "the Cloud Receiver rejected this Mac's saved connection", "warning");
|
|
672
|
-
ui.next("
|
|
687
|
+
ui.next(cliCommand("connect"), "Approve this Mac again in your browser.");
|
|
673
688
|
} else {
|
|
674
689
|
process.stdout.write('{"event":"connector_reauthorization_required"}\n');
|
|
675
690
|
}
|
|
@@ -1033,13 +1048,15 @@ function errorHint(error) {
|
|
|
1033
1048
|
connector_codex_cd_missing: "pass --codex-cd /absolute/path/to/your/Host-project",
|
|
1034
1049
|
connector_codex_not_found: "install Codex, add it to PATH, or pass --codex-binary /path/to/codex",
|
|
1035
1050
|
connector_codex_binary_not_found: "check --codex-binary or remove it to use automatic discovery",
|
|
1036
|
-
connector_codex_unusable:
|
|
1051
|
+
connector_codex_unusable: `open Codex, complete login if needed, then run \`${cliCommand("doctor")}\` again`,
|
|
1037
1052
|
connector_node_unsupported: "use Node.js 24 or newer, then run the command again",
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1053
|
+
connector_receiver_missing: "pass --receiver <replacement-receiver-origin> or set REENTRY_RECEIVER_ORIGIN",
|
|
1054
|
+
connector_credentials_missing: `connect this Mac once with \`${cliCommand("connect")}\``,
|
|
1055
|
+
connector_credentials_expired: `run \`${cliCommand("connect")}\` to authorize this Mac again`,
|
|
1056
|
+
connector_credentials_already_exists: "use the existing Connector credential or ask for a new pairing code",
|
|
1057
|
+
connector_identity_invalid: `run \`${cliCommand("connect")}\` and approve this Mac again`,
|
|
1058
|
+
connector_reauthorization_required: `run \`${cliCommand("connect")}\` and approve this Mac again`,
|
|
1059
|
+
connector_pairing_code_missing: `ask the Host backend for a new pairing code, then run \`${cliCommand("pair")}\` in a terminal`,
|
|
1043
1060
|
pairing_code_invalid: "use the 16-character code returned by the Host, for example ABCD-EFGH-IJKL-MNOP",
|
|
1044
1061
|
host_subject_already_paired: "use the existing Connector credential or revoke/reset the preview pairing",
|
|
1045
1062
|
pairing_expired: "ask the Host backend for a new pairing code",
|
|
@@ -1047,14 +1064,14 @@ function errorHint(error) {
|
|
|
1047
1064
|
pairing_network_error: "check your internet connection and the Receiver address, then try again",
|
|
1048
1065
|
workspace_directory_unavailable: "choose a readable folder or pass --codex-cd /absolute/path",
|
|
1049
1066
|
workspace_selection_cancelled: "run the command again when you are ready to choose a workspace",
|
|
1050
|
-
device_authorization_expired:
|
|
1051
|
-
device_authorization_denied:
|
|
1067
|
+
device_authorization_expired: `run \`${cliCommand("connect")}\` again and approve within ten minutes`,
|
|
1068
|
+
device_authorization_denied: `run \`${cliCommand("connect")}\` again when you are ready to approve this Mac`,
|
|
1052
1069
|
connector_poll_interval_invalid: "use a polling interval between 1000 and 60000 milliseconds",
|
|
1053
1070
|
connector_max_errors_invalid: "use a maximum error count between 1 and 20",
|
|
1054
|
-
connector_service_load_failed:
|
|
1055
|
-
connector_uninstall_confirmation_required:
|
|
1056
|
-
connector_service_stop_failed:
|
|
1057
|
-
connector_test_prompt_missing:
|
|
1071
|
+
connector_service_load_failed: `run \`${cliCommand("install")}\` again; if it still fails, inspect the Connector error log`,
|
|
1072
|
+
connector_uninstall_confirmation_required: `run \`${cliCommand("uninstall")}\` interactively and type DELETE, or pass \`--yes\` in a deliberate script`,
|
|
1073
|
+
connector_service_stop_failed: `check the Connector status and try \`${cliCommand("stop")}\` again`,
|
|
1074
|
+
connector_test_prompt_missing: `use \`${cliCommand('test "Reply with: Re-entry is working."')}\``,
|
|
1058
1075
|
connector_test_prompt_invalid: "use one short, single-line prompt inside quotes",
|
|
1059
1076
|
connector_activation_timeout_invalid: "use an activation timeout between 100 and 60000 milliseconds",
|
|
1060
1077
|
};
|
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
|
|