From 163badaa1de1c83397eaca6720c69e69279d37a7 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sat, 7 Nov 2026 20:18:51 +0000 Subject: [PATCH 1/5] fix(cli): t3 environment link no longer shows the pairing code `t3 environment link` took the one-time pairing code only as a plain `--pairing-code` argument, so it stayed visible in the process list and shell history, and `--wizard` echoed it and printed it back in the command it built. The flag is now redacted, so the wizard masks it, and it falls back to `T3CODE_PAIRING_CODE` like the other `t3` flags with an environment equivalent. `--pairing-code` still works and wins over the variable. Co-Authored-By: Claude Opus 5.5 --- apps/server/src/cli/environment.test.ts | 55 +++++++++++++++++++++++++ apps/server/src/cli/environment.ts | 11 +++-- docs/user/remote-access.md | 3 +- 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 apps/server/src/cli/environment.test.ts diff --git a/apps/server/src/cli/environment.test.ts b/apps/server/src/cli/environment.test.ts new file mode 100644 index 0000000000000..2b94b4d860310 --- /dev/null +++ b/apps/server/src/cli/environment.test.ts @@ -0,0 +1,55 @@ +import { NodeHttpServer } from "@effect/platform-node"; +import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; +import * as NodeServices from "@effect/platform-node/NodeServices"; +import * as NetService from "@t3tools/shared/Net"; +import { expect, it } from "@effect/vitest"; +import * as ConfigProvider from "effect/ConfigProvider"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import * as TestConsole from "effect/testing/TestConsole"; +import { Command } from "effect/cli"; + +import { cli } from "../binCli.ts"; +import { descriptorOf, servePeer } from "../peer/PeerLinks.testkit.ts"; + +const runCli = (args: ReadonlyArray, env: Record) => + Command.runWith(cli, { version: "0.0.0" })(args).pipe( + Effect.andThen(TestConsole.logLines), + Effect.provide( + Layer.mergeAll( + NodeServices.layer, + NodeCrypto.layer, + NetService.layer, + TestConsole.layer, + ConfigProvider.layer(ConfigProvider.fromEnv({ env })), + ), + ), + ); + +it.effect("links with the pairing code from T3CODE_PAIRING_CODE, or --pairing-code over it", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const peer = yield* servePeer(descriptorOf("cli-link-peer", "Box"), Layer.empty); + const baseDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-cli-environment-link-" }); + yield* fs.makeDirectory(path.join(baseDir, "userdata")); + yield* fs.writeFileString( + path.join(baseDir, "userdata", "settings.json"), + JSON.stringify({ linkedEnvironmentsEnabled: true }), + ); + const link = ["environment", "link", peer.url, "--base-dir", baseDir]; + + const spent = (yield* peer.auth.issuePairingCredential()).credential; + expect(yield* runCli(link, { T3CODE_PAIRING_CODE: spent })).toContainEqual( + expect.stringContaining("Linked Box (cli-link-peer)"), + ); + + // The env code is spent now, so this links only if the flag wins. + const fresh = (yield* peer.auth.issuePairingCredential()).credential; + expect( + yield* runCli([...link, "--pairing-code", fresh], { T3CODE_PAIRING_CODE: spent }), + ).toContainEqual(expect.stringContaining("Linked Box (cli-link-peer)")); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), +); diff --git a/apps/server/src/cli/environment.ts b/apps/server/src/cli/environment.ts index 869c085cbad2b..f519cdb7ac59e 100644 --- a/apps/server/src/cli/environment.ts +++ b/apps/server/src/cli/environment.ts @@ -1,8 +1,10 @@ import { AuthMcpClientAccess, EnvironmentId, type PeerLinkSummary } from "@t3tools/contracts"; +import * as Config from "effect/Config"; import * as Console from "effect/Console"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Redacted from "effect/Redacted"; import * as References from "effect/References"; import * as Schema from "effect/Schema"; import * as Argument from "effect/cli/Argument"; @@ -93,8 +95,11 @@ const environmentLinkCommand = Command.make("link", { Flag.withDescription("Another address for the same environment, tried after the first."), Flag.atLeast(0), ), - pairingCode: Flag.String("pairing-code").pipe( - Flag.withDescription("A pairing code from the other environment (`t3 pair` there)."), + pairingCode: Flag.Redacted("pairing-code").pipe( + Flag.withDescription( + "A pairing code from `t3 pair` in the other environment (equivalent to T3CODE_PAIRING_CODE).", + ), + Flag.withFallbackConfig(Config.Redacted("T3CODE_PAIRING_CODE")), ), access: Flag.Literals("access", AuthMcpClientAccess.literals).pipe( Flag.withDescription( @@ -112,7 +117,7 @@ const environmentLinkCommand = Command.make("link", { const linked = yield* links.link({ url: flags.url, alternateUrls: flags.alternateUrl, - pairingCode: flags.pairingCode, + pairingCode: Redacted.value(flags.pairingCode), access: flags.access, }); yield* Console.log( diff --git a/docs/user/remote-access.md b/docs/user/remote-access.md index 649dfbca63780..945913d74cd3a 100644 --- a/docs/user/remote-access.md +++ b/docs/user/remote-access.md @@ -337,7 +337,8 @@ holds the machines this app knows and those on your T3 Connect account. If this app is connected to that machine with a session that manages access there, it makes the pairing code itself; otherwise paste one from that machine's **Settings → Sharing & access**. From a terminal, use -`t3 environment link
--pairing-code --access `. +`t3 environment link
--pairing-code --access `, or set +`T3CODE_PAIRING_CODE` instead of passing `--pairing-code`. You can also ask an agent to link a machine by name, such as "delegate this to my vps". It shows a card in the thread where you pick the machine the same way From 46c4f0f9724479300d39f4d5fa0e49c173358733 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sat, 7 Nov 2026 23:24:41 +0000 Subject: [PATCH 2/5] docs: clarify pairing code environment variable --- docs/user/remote-access.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/user/remote-access.md b/docs/user/remote-access.md index 945913d74cd3a..c9d556d5f4642 100644 --- a/docs/user/remote-access.md +++ b/docs/user/remote-access.md @@ -337,8 +337,15 @@ holds the machines this app knows and those on your T3 Connect account. If this app is connected to that machine with a session that manages access there, it makes the pairing code itself; otherwise paste one from that machine's **Settings → Sharing & access**. From a terminal, use -`t3 environment link
--pairing-code --access `, or set -`T3CODE_PAIRING_CODE` instead of passing `--pairing-code`. +the environment variable before running the link: + +```sh +export T3CODE_PAIRING_CODE= +t3 environment link
--access +``` + +You can pass the code with `--pairing-code ` instead, but entering it on +the command line exposes it in process arguments and shell history. You can also ask an agent to link a machine by name, such as "delegate this to my vps". It shows a card in the thread where you pick the machine the same way From 3c5cd7b199cb5afd7e5bf637303674ac8eab7424 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sun, 8 Nov 2026 00:58:14 +0000 Subject: [PATCH 3/5] docs: read the pairing code without echoing it or saving it in shell history Co-Authored-By: Claude Opus 5.5 --- docs/user/remote-access.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/user/remote-access.md b/docs/user/remote-access.md index c9d556d5f4642..c53e3042afebc 100644 --- a/docs/user/remote-access.md +++ b/docs/user/remote-access.md @@ -336,12 +336,14 @@ choose **Link**, and pick the other machine or enter its address. The list holds the machines this app knows and those on your T3 Connect account. If this app is connected to that machine with a session that manages access there, it makes the pairing code itself; otherwise paste one from that machine's -**Settings → Sharing & access**. From a terminal, use -the environment variable before running the link: +**Settings → Sharing & access**. From a terminal, read the code into the +environment variable without echoing it, then run the link: ```sh -export T3CODE_PAIRING_CODE= +IFS= read -rs T3CODE_PAIRING_CODE # paste the code, then press Enter +export T3CODE_PAIRING_CODE t3 environment link
--access +unset T3CODE_PAIRING_CODE ``` You can pass the code with `--pairing-code ` instead, but entering it on From 545a5a38186f76197574ee7f57ac55e56f4bce31 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sun, 8 Nov 2026 01:08:07 +0000 Subject: [PATCH 4/5] docs: show pairing code input for PowerShell and fish too Co-Authored-By: Claude Opus 5.5 --- docs/user/remote-access.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/user/remote-access.md b/docs/user/remote-access.md index c53e3042afebc..269d15c0ab827 100644 --- a/docs/user/remote-access.md +++ b/docs/user/remote-access.md @@ -337,7 +337,7 @@ holds the machines this app knows and those on your T3 Connect account. If this app is connected to that machine with a session that manages access there, it makes the pairing code itself; otherwise paste one from that machine's **Settings → Sharing & access**. From a terminal, read the code into the -environment variable without echoing it, then run the link: +environment variable without echoing it, then run the link. In bash or zsh: ```sh IFS= read -rs T3CODE_PAIRING_CODE # paste the code, then press Enter @@ -346,6 +346,17 @@ t3 environment link
--access unset T3CODE_PAIRING_CODE ``` +In PowerShell 7.1 or later: + +```powershell +$env:T3CODE_PAIRING_CODE = Read-Host -MaskInput "Pairing code" +t3 environment link
--access +Remove-Item Env:T3CODE_PAIRING_CODE +``` + +In fish, read it with `read --silent --export T3CODE_PAIRING_CODE` and clear it +afterwards with `set --erase T3CODE_PAIRING_CODE`. + You can pass the code with `--pairing-code ` instead, but entering it on the command line exposes it in process arguments and shell history. From 57a74e5d88eecfadcb57f2c2908d982a49602f97 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sun, 8 Nov 2026 01:40:31 +0000 Subject: [PATCH 5/5] docs: keep the fish pairing code variable out of universal scope Co-Authored-By: Claude Opus 5.5 --- docs/user/remote-access.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/remote-access.md b/docs/user/remote-access.md index 269d15c0ab827..d95cd19bdcbdf 100644 --- a/docs/user/remote-access.md +++ b/docs/user/remote-access.md @@ -354,8 +354,8 @@ t3 environment link
--access Remove-Item Env:T3CODE_PAIRING_CODE ``` -In fish, read it with `read --silent --export T3CODE_PAIRING_CODE` and clear it -afterwards with `set --erase T3CODE_PAIRING_CODE`. +In fish, read it with `read --global --silent --export T3CODE_PAIRING_CODE` and +clear it afterwards with `set --erase --global T3CODE_PAIRING_CODE`. You can pass the code with `--pairing-code ` instead, but entering it on the command line exposes it in process arguments and shell history.