@tokenoftrust/cli 3.10.1 → 3.10.3
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/bin/tot.mjs +0 -12
- package/package.json +1 -1
- package/src/commands/dev.mjs +13 -2
- package/src/commands/doctor.mjs +14 -22
- package/src/commands/hotfix.mjs +3 -1
- package/src/commands/ship.mjs +2 -2
- package/src/git-credential.mjs +8 -1
- package/src/open.mjs +13 -14
- package/src/plan.mjs +18 -15
- package/src/platform.mjs +8 -68
package/bin/tot.mjs
CHANGED
|
@@ -29,10 +29,6 @@ import {
|
|
|
29
29
|
recordDiagnostic,
|
|
30
30
|
resetInvocationDiagnostics,
|
|
31
31
|
} from "../src/diagnostics.mjs";
|
|
32
|
-
import {
|
|
33
|
-
nativeWindowsRefuseMessage,
|
|
34
|
-
shouldRefuseNativeWindows,
|
|
35
|
-
} from "../src/platform.mjs";
|
|
36
32
|
|
|
37
33
|
const BUILD_ORDER = ["clone", "validate", "dev", "preview"];
|
|
38
34
|
|
|
@@ -120,14 +116,6 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
120
116
|
return cmd ? 0 : 2;
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
// Native Windows: refuse before login/--device/start burn cycles on a host the
|
|
124
|
-
// local loop cannot run on. doctor/feedback/ideas stay available so the gate
|
|
125
|
-
// itself is discoverable. WSL reports as linux and passes through.
|
|
126
|
-
if (shouldRefuseNativeWindows(cmd)) {
|
|
127
|
-
process.stderr.write(nativeWindowsRefuseMessage(cmd));
|
|
128
|
-
return 2;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
119
|
// A mounted publication space's checkout (spaces/<id>/, recorded by `tot clone`) is its own
|
|
132
120
|
// repository: the store-root verbs below would push it with the ROOT's credential or merge the
|
|
133
121
|
// root's preview branch into it. Refuse them there, naming the space's own path.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.3",
|
|
4
4
|
"description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Token of Trust",
|
package/src/commands/dev.mjs
CHANGED
|
@@ -1567,7 +1567,7 @@ export function ensureCorepackPnpm(runnerDir, { logPath, spawnFn = spawnSync } =
|
|
|
1567
1567
|
// WITHOUT the global shim (see runPnpmInstall's fallback). Capture both to the
|
|
1568
1568
|
// log — a silent corepack failure was why the install error carried no cause.
|
|
1569
1569
|
for (const args of [["enable"], ["prepare", pm, "--activate"]]) {
|
|
1570
|
-
const r = spawnFn("corepack", args, { stdio: ["ignore", fd ?? "ignore", fd ?? "ignore"] });
|
|
1570
|
+
const r = spawnFn("corepack", args, { stdio: ["ignore", fd ?? "ignore", fd ?? "ignore"], shell: launcherNeedsShell() });
|
|
1571
1571
|
if (fd !== null && (r.error || r.status !== 0)) {
|
|
1572
1572
|
const rerr = /** @type {any} */ (r.error);
|
|
1573
1573
|
writeSync(fd, `[tot] corepack ${args.join(" ")} → ${rerr?.code || rerr?.message || `exit ${r.status}`}\n`);
|
|
@@ -1598,6 +1598,15 @@ export function ensureCorepackPnpm(runnerDir, { logPath, spawnFn = spawnSync } =
|
|
|
1598
1598
|
* @param {string} cmd @param {string[]} args @param {object} opts
|
|
1599
1599
|
* @returns {Promise<{ status: number|null, error: Error|null, signal?: string|null }>}
|
|
1600
1600
|
*/
|
|
1601
|
+
/**
|
|
1602
|
+
* On Windows `pnpm`, `corepack` and `npm` are `.cmd` shims, which Node refuses to
|
|
1603
|
+
* spawn without a shell (and a bare name ENOENTs). Their args here are fixed
|
|
1604
|
+
* literals, so the shell sees no user input.
|
|
1605
|
+
*/
|
|
1606
|
+
export function launcherNeedsShell(platform = process.platform) {
|
|
1607
|
+
return platform === "win32";
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1601
1610
|
function spawnAsyncResult(cmd, args, opts = {}) {
|
|
1602
1611
|
return new Promise((resolvePromise) => {
|
|
1603
1612
|
let settled = false;
|
|
@@ -1654,9 +1663,11 @@ export async function runPnpmInstall(runnerDir, { logPath, spawnFn = spawnAsyncR
|
|
|
1654
1663
|
cwd: runnerDir,
|
|
1655
1664
|
// Send both streams to the log fd (or swallow them) — never inherit.
|
|
1656
1665
|
stdio: ["ignore", fd ?? "ignore", fd ?? "ignore"],
|
|
1666
|
+
shell: launcherNeedsShell(),
|
|
1657
1667
|
});
|
|
1658
1668
|
if (r.status === 0) return cmd; // installed — the launcher that did it
|
|
1659
|
-
|
|
1669
|
+
// Through cmd.exe a missing launcher is exit 9009 ("not recognized"), not ENOENT.
|
|
1670
|
+
if (/** @type {any} */ (r.error)?.code === "ENOENT" || (launcherNeedsShell() && r.status === 9009)) {
|
|
1660
1671
|
// This launcher isn't on the machine — record it and try the next one.
|
|
1661
1672
|
if (fd !== null) writeSync(fd, `[tot] ${cmd} not found (ENOENT) — trying the next launcher\n`);
|
|
1662
1673
|
continue;
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -25,11 +25,7 @@ import { join } from "node:path";
|
|
|
25
25
|
import { hasLegacyOperatorEnv, legacyOperatorEnvAdvisory } from "../auth.mjs";
|
|
26
26
|
import { MIN_NODE, nodeMeetsFloor } from "../ensure-node.mjs";
|
|
27
27
|
import { clientPackages, osLabel } from "../mcp.mjs";
|
|
28
|
-
import {
|
|
29
|
-
isNativeWindows,
|
|
30
|
-
nativeWindowsDetail,
|
|
31
|
-
shouldAttemptDoctorAutoLogin,
|
|
32
|
-
} from "../platform.mjs";
|
|
28
|
+
import { isNativeWindows, nativeWindowsDetail } from "../platform.mjs";
|
|
33
29
|
import { readSessionCredentials, isExpired } from "../token-store.mjs";
|
|
34
30
|
import { dockerAvailable, tryStartDocker, RENDERER_CACHE_ROOT, rendererArtifactComplete } from "./dev.mjs";
|
|
35
31
|
import { loginAndCache } from "./login.mjs";
|
|
@@ -71,15 +67,15 @@ export function collectChecks(_ctx, env = process.env) {
|
|
|
71
67
|
blocking: true,
|
|
72
68
|
});
|
|
73
69
|
|
|
74
|
-
// Native Windows
|
|
75
|
-
// + tar extraction assume a POSIX toolchain) — WSL is
|
|
76
|
-
//
|
|
70
|
+
// Native Windows runs everything except the local renderer (`dev` / `start`: the
|
|
71
|
+
// runner install + tar extraction assume a POSIX toolchain) — WSL is the host for
|
|
72
|
+
// those, and reports as linux here. Informational, so the gap is visible up front.
|
|
77
73
|
const nativeWindows = isNativeWindows();
|
|
78
74
|
checks.push({
|
|
79
|
-
name: "
|
|
80
|
-
pass:
|
|
75
|
+
name: "platform",
|
|
76
|
+
pass: true,
|
|
81
77
|
detail: nativeWindows ? nativeWindowsDetail() : "macOS / Linux / WSL",
|
|
82
|
-
blocking:
|
|
78
|
+
blocking: false,
|
|
83
79
|
});
|
|
84
80
|
|
|
85
81
|
// Informational: the versions + OS this invocation is running on — the same
|
|
@@ -181,17 +177,13 @@ async function applyFixes(checks, env) {
|
|
|
181
177
|
|
|
182
178
|
const auth = checks.find((c) => c.name === "auth identity");
|
|
183
179
|
if (auth && !auth.pass) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
console.log("
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.log(" ✓ signed in");
|
|
192
|
-
} catch (e) {
|
|
193
|
-
console.log(` ~ couldn't sign in automatically (${e?.message || e}) — run \`tot login\``);
|
|
194
|
-
}
|
|
180
|
+
const mcpUrl = env.MCP_BASE_URL || env.TOT_MCP_URL || DEFAULT_MCP_URL;
|
|
181
|
+
console.log(" ~ not signed in — opening the browser to sign in…");
|
|
182
|
+
try {
|
|
183
|
+
await loginAndCache(mcpUrl, env, { log: (m) => console.log(` ${m}`) });
|
|
184
|
+
console.log(" ✓ signed in");
|
|
185
|
+
} catch (e) {
|
|
186
|
+
console.log(` ~ couldn't sign in automatically (${e?.message || e}) — run \`tot login\``);
|
|
195
187
|
}
|
|
196
188
|
}
|
|
197
189
|
}
|
package/src/commands/hotfix.mjs
CHANGED
|
@@ -39,7 +39,7 @@ import { openBrowser } from "../open.mjs";
|
|
|
39
39
|
import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
|
|
40
40
|
// Reuse `tot ship`'s operator-secret precedence + live-url derivation verbatim so
|
|
41
41
|
// ship + hotfix speak ONE operator-auth contract.
|
|
42
|
-
import { resolveOperatorSecret, liveUrlFor } from "./ship.mjs";
|
|
42
|
+
import { resolveOperatorSecret, liveUrlFor, normalizeLiveDrift } from "./ship.mjs";
|
|
43
43
|
|
|
44
44
|
const DEFAULT_STOREFRONT_URL = "https://storefront.tokenoftrust.store";
|
|
45
45
|
|
|
@@ -126,6 +126,7 @@ export function normalizeHotfixPlan(data) {
|
|
|
126
126
|
bypassedPreviewSha: typeof o.bypassedPreviewSha === "string" ? o.bypassedPreviewSha : null,
|
|
127
127
|
bypassedPrs: Array.isArray(o.bypassedPrs) ? o.bypassedPrs : [],
|
|
128
128
|
rollbackTarget: o.rollbackTarget && typeof o.rollbackTarget === "object" ? o.rollbackTarget : null,
|
|
129
|
+
warning: normalizeLiveDrift(o.warning),
|
|
129
130
|
paywall:
|
|
130
131
|
o.paywall && typeof o.paywall === "object"
|
|
131
132
|
? { allowed: o.paywall.allowed === true, message: typeof o.paywall.message === "string" ? o.paywall.message : null }
|
|
@@ -285,6 +286,7 @@ export async function runHotfix(
|
|
|
285
286
|
bypassedPrs: plan.bypassedPrs,
|
|
286
287
|
bypassedPreviewSha: plan.bypassedPreviewSha,
|
|
287
288
|
rollbackTarget: plan.rollbackTarget,
|
|
289
|
+
liveDrift: plan.warning,
|
|
288
290
|
paywall: plan.paywall,
|
|
289
291
|
targets: { live: liveUrl },
|
|
290
292
|
});
|
package/src/commands/ship.mjs
CHANGED
|
@@ -245,8 +245,8 @@ export function liveUrlFor(tenant) {
|
|
|
245
245
|
* paywall:{allowed:boolean,message:string|null}, alreadyShipped:boolean} |
|
|
246
246
|
* {ok:false, reason:string, message:string}}
|
|
247
247
|
*/
|
|
248
|
-
/** The
|
|
249
|
-
function normalizeLiveDrift(warning) {
|
|
248
|
+
/** The plan CDN drift warning, or null when the ledger and the CDN name the same release. */
|
|
249
|
+
export function normalizeLiveDrift(warning) {
|
|
250
250
|
if (!warning || typeof warning !== "object" || typeof warning.reason !== "string") return null;
|
|
251
251
|
const target = warning.correctedRollbackTarget;
|
|
252
252
|
if (!target || typeof target !== "object" || typeof target.aggregateSha !== "string") return null;
|
package/src/git-credential.mjs
CHANGED
|
@@ -252,6 +252,13 @@ export function forgeShimPath(env = process.env) {
|
|
|
252
252
|
return join(home, ".tot", "bin", "tot-git-credential");
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
/** The shim path as git's `!` helper runs it (through sh). Git for Windows' sh reads
|
|
256
|
+
* forward slashes, not backslashes, and a home directory may contain spaces, so a
|
|
257
|
+
* Windows path is slash-normalized and single-quoted. POSIX paths pass through. Pure. */
|
|
258
|
+
export function helperCommandPath(shim, platform = process.platform) {
|
|
259
|
+
return platform === "win32" ? shq(shim.replace(/\\/g, "/")) : shim;
|
|
260
|
+
}
|
|
261
|
+
|
|
255
262
|
/** The shim's contents: pin the ABSOLUTE node + CLI entry active at write time, with a
|
|
256
263
|
* PATH-search fallback for node, and FAIL LOUD to stderr (never silently) when no node
|
|
257
264
|
* is found — so a broken helper says how to fix itself instead of yielding an opaque
|
|
@@ -324,7 +331,7 @@ export function installForgeCredentialHelper(git, {
|
|
|
324
331
|
} = {}) {
|
|
325
332
|
if (!host) return false;
|
|
326
333
|
const shim = writeShim(env, nodePath, entryPath);
|
|
327
|
-
const helperValue = shim ? `!${shim}` : CREDENTIAL_HELPER;
|
|
334
|
+
const helperValue = shim ? `!${helperCommandPath(shim)}` : CREDENTIAL_HELPER;
|
|
328
335
|
const key = `credential.https://${host}.helper`;
|
|
329
336
|
let raw = null;
|
|
330
337
|
try { raw = git(["config", "--local", "--get-all", key]); } catch { raw = null; }
|
package/src/open.mjs
CHANGED
|
@@ -58,6 +58,17 @@ export async function firstFreePort(preferred, maxTries = 64) {
|
|
|
58
58
|
return preferred;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* The OS opener as [command, args]. Windows goes through `rundll32` with no shell:
|
|
63
|
+
* `cmd /c start` would treat the `&` in an OAuth URL as a command separator.
|
|
64
|
+
* @param {string} url @param {string} platform @returns {[string, string[]]}
|
|
65
|
+
*/
|
|
66
|
+
export function openerCommand(url, platform) {
|
|
67
|
+
if (platform === "darwin") return ["open", [url]];
|
|
68
|
+
if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
69
|
+
return ["xdg-open", [url]];
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
/**
|
|
62
73
|
* Open `url` in the user's default browser. Best-effort and non-blocking: the
|
|
63
74
|
* child is detached + unref'd so it never holds `tot` open, and any failure
|
|
@@ -65,21 +76,9 @@ export async function firstFreePort(preferred, maxTries = 64) {
|
|
|
65
76
|
* @returns {boolean} whether we managed to launch an opener.
|
|
66
77
|
*/
|
|
67
78
|
export function openBrowser(url, platform = process.platform) {
|
|
68
|
-
|
|
69
|
-
if (platform === "darwin") {
|
|
70
|
-
cmd = "open";
|
|
71
|
-
args = [url];
|
|
72
|
-
} else if (platform === "win32") {
|
|
73
|
-
// `start` is a shell builtin; the empty first arg is the window title.
|
|
74
|
-
cmd = "start";
|
|
75
|
-
args = ["", url];
|
|
76
|
-
shell = true;
|
|
77
|
-
} else {
|
|
78
|
-
cmd = "xdg-open";
|
|
79
|
-
args = [url];
|
|
80
|
-
}
|
|
79
|
+
const [cmd, args] = openerCommand(url, platform);
|
|
81
80
|
try {
|
|
82
|
-
const child = spawn(cmd, args, { stdio: "ignore", detached: true
|
|
81
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
83
82
|
child.on("error", () => {}); // swallow "opener not found" async errors
|
|
84
83
|
child.unref();
|
|
85
84
|
return true;
|
package/src/plan.mjs
CHANGED
|
@@ -40,6 +40,20 @@ function targetLabel({ pr, changeId }) {
|
|
|
40
40
|
return "this change";
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/** Empty when the CDN and the ledger name the same release. */
|
|
44
|
+
function cdnDriftLines(liveDrift) {
|
|
45
|
+
if (!liveDrift?.reason) return [];
|
|
46
|
+
return [
|
|
47
|
+
` ⚠ ${liveDrift.reason}: the CDN serves ${liveDrift.served ?? "an unknown revision"}, not the ledger's live ${liveDrift.live ?? "nothing"}.`,
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function rollbackToLine(rollbackTarget, liveDrift) {
|
|
52
|
+
return rollbackTarget
|
|
53
|
+
? ` rollback to: ${rollbackTarget.aggregateSha} (receipt ${rollbackTarget.receiptId})${liveDrift?.reason ? " — corrected target" : ""}`
|
|
54
|
+
: " rollback to: (none — first-ever ship)";
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
/**
|
|
44
58
|
* Build the EXACT plan for a mutating operator verb — structured lines ready
|
|
45
59
|
* to print verbatim (CLI) or join for a confirm() dialog (admin). Pure: no
|
|
@@ -137,16 +151,8 @@ export function planForAction({
|
|
|
137
151
|
lines.push(` - ${prLabel}${shortSha}`);
|
|
138
152
|
}
|
|
139
153
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
` ⚠ ${liveDrift.reason}: the CDN serves ${liveDrift.served ?? "an unknown revision"}, not the ledger's live ${liveDrift.live ?? "nothing"}.`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
lines.push(
|
|
146
|
-
rollbackTarget
|
|
147
|
-
? ` rollback to: ${rollbackTarget.aggregateSha} (receipt ${rollbackTarget.receiptId})${liveDrift?.reason ? " — corrected target" : ""}`
|
|
148
|
-
: " rollback to: (none — first-ever ship)",
|
|
149
|
-
);
|
|
154
|
+
lines.push(...cdnDriftLines(liveDrift));
|
|
155
|
+
lines.push(rollbackToLine(rollbackTarget, liveDrift));
|
|
150
156
|
if (paywall && paywall.allowed === false) {
|
|
151
157
|
lines.push(` ⚠ paywall: ${paywall.message || "go-live is blocked by the storefront subscription gate"}`);
|
|
152
158
|
}
|
|
@@ -206,11 +212,8 @@ export function planForAction({
|
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
214
|
if (bypassedPreviewSha) lines.push(` preview head (bypassed): ${bypassedPreviewSha}`);
|
|
209
|
-
lines.push(
|
|
210
|
-
|
|
211
|
-
? ` rollback to: ${rollbackTarget.aggregateSha} (receipt ${rollbackTarget.receiptId})`
|
|
212
|
-
: " rollback to: (none — first-ever ship)",
|
|
213
|
-
);
|
|
215
|
+
lines.push(...cdnDriftLines(liveDrift));
|
|
216
|
+
lines.push(rollbackToLine(rollbackTarget, liveDrift));
|
|
214
217
|
if (paywall && paywall.allowed === false) {
|
|
215
218
|
lines.push(` ⚠ paywall: ${paywall.message || "go-live is blocked by the storefront subscription gate"}`);
|
|
216
219
|
}
|
package/src/platform.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Host-platform
|
|
2
|
+
* Host-platform copy shared by `tot doctor` and login recovery hints.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Keep the check + copy in ONE place so doctor, the CLI gate, and login recovery
|
|
7
|
-
* hints cannot drift.
|
|
4
|
+
* Every command runs on native Windows. WSL reports as `linux` and remains the
|
|
5
|
+
* better-trodden path for the local renderer (`tot dev` / `tot start`).
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
8
|
export const WSL_INSTALL_URL = "https://learn.microsoft.com/windows/wsl/install";
|
|
@@ -17,75 +15,17 @@ export function isNativeWindows(platform = process.platform) {
|
|
|
17
15
|
return platform === "win32";
|
|
18
16
|
}
|
|
19
17
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
24
|
-
export function nativeWindowsDetail(opts = {}) {
|
|
25
|
-
const url = opts.url ?? WSL_INSTALL_URL;
|
|
26
|
-
return `native Windows isn't supported yet — run tot inside WSL (${url})`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Multi-line refuse message printed before exit. Names the attempted command so
|
|
31
|
-
* a pasted invite (`tot login --code … && tot start`) still points at WSL.
|
|
32
|
-
* @param {string} [command] e.g. "login", "start"
|
|
33
|
-
*/
|
|
34
|
-
export function nativeWindowsRefuseMessage(command) {
|
|
35
|
-
const verb = command ? `tot ${command}` : "tot";
|
|
36
|
-
return (
|
|
37
|
-
`✗ ${nativeWindowsDetail()}\n` +
|
|
38
|
-
` → next: install WSL, open an Ubuntu (or other Linux) terminal, install Node 24 LTS + git there,\n` +
|
|
39
|
-
` then re-run \`${verb}\` inside WSL — don't keep retrying login/--device on native Windows.\n`
|
|
40
|
-
);
|
|
18
|
+
/** One-line detail used by `tot doctor`. */
|
|
19
|
+
export function nativeWindowsDetail() {
|
|
20
|
+
return `native Windows — if \`tot dev\` or \`tot start\` misbehaves, WSL is the better-trodden path (${WSL_INSTALL_URL})`;
|
|
41
21
|
}
|
|
42
22
|
|
|
43
23
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*/
|
|
47
|
-
export const NATIVE_WINDOWS_ALLOWED_COMMANDS = new Set([
|
|
48
|
-
"help",
|
|
49
|
-
"--help",
|
|
50
|
-
"-h",
|
|
51
|
-
"--version",
|
|
52
|
-
"-v",
|
|
53
|
-
"version",
|
|
54
|
-
"doctor",
|
|
55
|
-
"feedback",
|
|
56
|
-
"ideas",
|
|
57
|
-
]);
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @param {string | null | undefined} command
|
|
61
|
-
* @param {string} [platform] Node `process.platform` value (defaults to current).
|
|
62
|
-
* @returns {boolean} true when this command must refuse on native Windows
|
|
63
|
-
*/
|
|
64
|
-
export function shouldRefuseNativeWindows(command, platform = process.platform) {
|
|
65
|
-
if (!isNativeWindows(/** @type {string} */ (platform))) return false;
|
|
66
|
-
const cmd = String(command ?? "").trim();
|
|
67
|
-
if (!cmd) return false; // bare `tot` → help
|
|
68
|
-
return !NATIVE_WINDOWS_ALLOWED_COMMANDS.has(cmd);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Recovery hint after a failed `tot login`. Never steers native Windows at
|
|
73
|
-
* `--device` — that path still can't run the local loop on win32.
|
|
74
|
-
* @param {{ device?: boolean, platform?: string }} [opts]
|
|
24
|
+
* Recovery hint after a failed `tot login`.
|
|
25
|
+
* @param {{ device?: boolean }} [opts]
|
|
75
26
|
*/
|
|
76
27
|
export function loginFailureNext(opts = {}) {
|
|
77
|
-
const platform = /** @type {string} */ (opts.platform ?? process.platform);
|
|
78
|
-
if (isNativeWindows(platform)) return nativeWindowsDetail();
|
|
79
28
|
return opts.device
|
|
80
29
|
? "re-run `tot login --device` and approve the code."
|
|
81
30
|
: "re-run `tot login` and finish the browser sign-in (approve the request) — or try `tot login --device`.";
|
|
82
31
|
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* `tot doctor --fix` must not open OAuth on native Windows — the session
|
|
86
|
-
* would be unusable for the local loop, and the browser hop burns a cycle.
|
|
87
|
-
* @param {string} [platform] Node `process.platform` value (defaults to current).
|
|
88
|
-
*/
|
|
89
|
-
export function shouldAttemptDoctorAutoLogin(platform = process.platform) {
|
|
90
|
-
return !isNativeWindows(/** @type {string} */ (platform));
|
|
91
|
-
}
|