@tokenoftrust/cli 1.3.4-rc.5 → 1.4.0-rc.0
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 +17 -3
- package/bin/tot.mjs +7 -0
- package/package.json +1 -1
- package/src/auth.mjs +55 -77
- package/src/commands/checkout.mjs +24 -17
- package/src/commands/dev.mjs +2 -2
- package/src/commands/doctor.mjs +18 -12
- package/src/commands/feedback.mjs +4 -4
- package/src/commands/grants.mjs +264 -0
- package/src/commands/login.mjs +69 -13
- package/src/commands/start.mjs +30 -32
- package/src/commands/submit.mjs +195 -17
- package/src/commands/whoami.mjs +9 -12
- package/src/mcp.mjs +2 -2
- package/src/prompt.mjs +32 -0
- package/src/token-store.mjs +22 -2
- package/src/validate.mjs +8 -1
package/README.md
CHANGED
|
@@ -40,10 +40,24 @@ The same `tot` does the right thing wherever you run it (walks up like `git`):
|
|
|
40
40
|
|
|
41
41
|
## Auth
|
|
42
42
|
|
|
43
|
-
`tot` talks to the Token of Trust MCP (default `https://mcp.tokenoftrust.com`, override with `--mcp` or `MCP_BASE_URL`). It
|
|
43
|
+
`tot` talks to the Token of Trust MCP (default `https://mcp.tokenoftrust.com`, override with `--mcp` or `MCP_BASE_URL`). It is **single-plane**: the only identity is **you**, signed in against the MCP over OAuth.
|
|
44
44
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
45
|
+
- Run `tot login` once — it opens your browser (or falls back to a device code on a headless box), you sign in as yourself, and the session is cached at `~/.tot/credentials.json` and refreshed silently. Every later command (`tot checkout`, `tot start`, `tot submit`, …) runs as you, with no re-auth. Entitlement is derived server-side from your ToT memberships.
|
|
46
|
+
- Not signed in? On a terminal, `tot start` / `tot checkout` **offer to sign you in right there** and continue in-flow — no "run `tot login`, then re-run".
|
|
47
|
+
- The old operator env-triple (`TOT_API_KEY` / `TOT_SECRET_KEY` / `TOT_APP_DOMAIN`) **no longer signs the CLI in** — tot-mcp went OAuth-first on 2026-07-23. If those vars are set, `tot` prints a one-line advisory and uses your `tot login` session anyway; it never reads them for auth.
|
|
48
|
+
|
|
49
|
+
### Multiple identities at once (`TOT_PROFILE`)
|
|
50
|
+
|
|
51
|
+
One credential file = one active identity, so a plain `tot login` replaces the previous session. To keep **different identities live in different terminals** — e.g. a staff `@tokenoftrust.com` sign-in in one shell and a plain developer identity in another — set a profile per shell:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
# terminal A
|
|
55
|
+
export TOT_PROFILE=staff && tot login
|
|
56
|
+
# terminal B
|
|
57
|
+
export TOT_PROFILE=dev && tot login
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each profile gets its own `~/.tot/credentials.<profile>.json` (the renderer cache and everything else stay shared). Unset → the default session, unchanged. `tot whoami` shows the active profile.
|
|
47
61
|
|
|
48
62
|
## Design notes
|
|
49
63
|
|
package/bin/tot.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* tot login sign in to Token of Trust (OAuth) ← built (MCP OAuth PKCE loopback; caches ~/.tot/credentials.json)
|
|
9
9
|
* tot logout sign out (clear the cached session) ← built (deletes ~/.tot/credentials.json; local-only, no server revoke)
|
|
10
10
|
* tot whoami who you're signed in as ← built
|
|
11
|
+
* tot grants capability/tier/expiry per store you can act on ← built (introspection diagnostics)
|
|
11
12
|
* tot checkout [<tenant>] clone a store you can build on ← built
|
|
12
13
|
* tot validate lint your store before you submit ← built
|
|
13
14
|
* tot dev run your store locally with save→reload ← built (monorepo: host astro; standalone: runs the published runner image)
|
|
@@ -52,6 +53,7 @@ tot — Token of Trust developer CLI
|
|
|
52
53
|
tot login sign in to Token of Trust
|
|
53
54
|
tot logout sign out (clear the cached session)
|
|
54
55
|
tot whoami show who you're signed in as
|
|
56
|
+
tot grants capability/tier/expiry per store you can act on
|
|
55
57
|
tot checkout [<tenant>] clone a store you can build on
|
|
56
58
|
tot validate lint your store before you submit
|
|
57
59
|
tot dev run your store locally with save→reload
|
|
@@ -112,6 +114,11 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
112
114
|
return run(rest, ctx);
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
if (cmd === "grants") {
|
|
118
|
+
const { run } = await import("../src/commands/grants.mjs");
|
|
119
|
+
return run(rest, ctx);
|
|
120
|
+
}
|
|
121
|
+
|
|
115
122
|
if (cmd === "ideas") {
|
|
116
123
|
const { run } = await import("../src/commands/ideas.mjs");
|
|
117
124
|
return run(rest, ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-rc.0",
|
|
4
4
|
"description": "Token of Trust developer CLI — check out 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/auth.mjs
CHANGED
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth
|
|
3
|
-
*
|
|
2
|
+
* Auth for `tot` — how we establish a VALIDATED MCP session before a privileged
|
|
3
|
+
* tool call (tenant_checkout, client_switch, …).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* SINGLE PLANE: there is exactly one identity that drives `tot` — the human
|
|
6
|
+
* developer, signed in as THEMSELVES against the MCP via OAuth (the same identity
|
|
7
|
+
* `claude mcp add … tot` + `/mcp` establishes). `tot login` runs the MCP OAuth
|
|
8
|
+
* PKCE loopback and caches the token (~/.tot/credentials.json via token-store.mjs);
|
|
9
|
+
* this provider reads it back, silently refreshing when expired, and attaches it as
|
|
10
|
+
* the client's Bearer. Entitlement is derived server-side from the developer's ToT
|
|
11
|
+
* memberships — no local check. Every command calls `establishSession(client)`.
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* establishes). `tot login` runs the MCP OAuth PKCE loopback and
|
|
18
|
-
* caches the token (~/.tot/credentials.json via token-store.mjs);
|
|
19
|
-
* this provider reads it back, silently refreshing when expired,
|
|
20
|
-
* and attaches it as the client's Bearer. Entitlement is derived
|
|
21
|
-
* server-side from the developer's ToT memberships — no local check.
|
|
22
|
-
*
|
|
23
|
-
* Selection: operator creds win when present (explicit, deterministic, what CI
|
|
24
|
-
* sets); otherwise we fall to the developer provider.
|
|
13
|
+
* The old `operator` provider (a TOT_API_KEY / TOT_SECRET_KEY / TOT_APP_DOMAIN
|
|
14
|
+
* env-triple → `credential_validate`) is GONE. tot-mcp went OAuth-first on
|
|
15
|
+
* 2026-07-23: `credential_validate` was retired to a stateless key-liveness probe
|
|
16
|
+
* that establishes NO human identity and sets NO tenant scope, so the operator
|
|
17
|
+
* path can no longer sign the CLI in to anything. The env-triple is now treated as
|
|
18
|
+
* DETECTION ONLY — if it's set we print a one-line advisory and proceed on the
|
|
19
|
+
* OAuth session (see hasLegacyOperatorEnv / legacyOperatorEnvAdvisory). We never
|
|
20
|
+
* read or transmit the secret.
|
|
25
21
|
*/
|
|
26
22
|
import {
|
|
27
23
|
defaultCredentialsPath, readCredentials, writeCredentials, isExpired,
|
|
28
24
|
} from "./token-store.mjs";
|
|
29
25
|
import { refreshAccessToken, credentialsFromToken } from "./oauth.mjs";
|
|
30
|
-
import { recordServerPolicy } from "./update-check.mjs";
|
|
31
26
|
|
|
32
27
|
/** Thrown when no provider can authenticate — carries actionable guidance. */
|
|
33
28
|
export class AuthUnavailableError extends Error {
|
|
@@ -99,42 +94,29 @@ export function developerCredentialRecoveryHint(creds) {
|
|
|
99
94
|
return "run `tot login` to sign in again.";
|
|
100
95
|
}
|
|
101
96
|
|
|
102
|
-
/**
|
|
103
|
-
|
|
97
|
+
/**
|
|
98
|
+
* True when the RETIRED operator env-triple (TOT_API_KEY / TOT_SECRET_KEY /
|
|
99
|
+
* TOT_APP_DOMAIN) is fully set. Detection only — these keys no longer sign the
|
|
100
|
+
* CLI in to the MCP (OAuth-first since 2026-07-23); we use it purely to emit an
|
|
101
|
+
* advisory so a stale env setup self-explains instead of silently doing nothing.
|
|
102
|
+
* We never read the secret values.
|
|
103
|
+
*/
|
|
104
|
+
export function hasLegacyOperatorEnv(env = process.env) {
|
|
104
105
|
return Boolean(env.TOT_API_KEY && env.TOT_SECRET_KEY && env.TOT_APP_DOMAIN);
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/** One quiet line shown when the retired operator env-triple is present. */
|
|
109
|
+
export const legacyOperatorEnvAdvisory =
|
|
110
|
+
"note: ToT API keys in this shell no longer sign the CLI in — using your `tot login` session instead.";
|
|
111
|
+
|
|
107
112
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* @param {
|
|
111
|
-
* @param {
|
|
112
|
-
* @returns {Promise<{ identity: "operator"|"developer", appDomain: string|null }>}
|
|
113
|
+
* Emit the legacy-env advisory to stderr when the retired operator triple is set.
|
|
114
|
+
* A no-op otherwise. `warn` is injectable for tests; never touches the secret.
|
|
115
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
116
|
+
* @param {(msg: string) => void} [warn]
|
|
113
117
|
*/
|
|
114
|
-
export
|
|
115
|
-
|
|
116
|
-
const prefer = opts.prefer || (hasOperatorCreds(env) ? "operator" : "developer");
|
|
117
|
-
|
|
118
|
-
if (prefer === "operator") {
|
|
119
|
-
if (!hasOperatorCreds(env)) {
|
|
120
|
-
throw new AuthUnavailableError(
|
|
121
|
-
"operator auth requested but TOT_API_KEY / TOT_SECRET_KEY / TOT_APP_DOMAIN are not all set.",
|
|
122
|
-
{ hint: "source apps/storefront/lib/resolve-tot-credentials.sh (from a storefront checkout), or unset --identity to use developer sign-in." },
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
const validated = await client.callTool("credential_validate", {
|
|
126
|
-
totApiKey: env.TOT_API_KEY,
|
|
127
|
-
totSecretKey: env.TOT_SECRET_KEY,
|
|
128
|
-
appDomain: env.TOT_APP_DOMAIN,
|
|
129
|
-
});
|
|
130
|
-
// Update-awareness Layer 2: the MCP may attach a version-support policy to the
|
|
131
|
-
// authed response (wire contract: `cliPolicy`). Safe no-op when absent.
|
|
132
|
-
recordServerPolicy(validated?.cliPolicy, env);
|
|
133
|
-
return { identity: "operator", appDomain: env.TOT_APP_DOMAIN };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// developer provider — the real invited-dev path.
|
|
137
|
-
return resolveDeveloperSession(client, env);
|
|
118
|
+
export function warnLegacyOperatorEnv(env = process.env, warn = (m) => console.error(m)) {
|
|
119
|
+
if (hasLegacyOperatorEnv(env)) warn(legacyOperatorEnvAdvisory);
|
|
138
120
|
}
|
|
139
121
|
|
|
140
122
|
/**
|
|
@@ -221,39 +203,35 @@ export async function resolveDeveloperSession(client, env, deps = {}) {
|
|
|
221
203
|
* Establish a validated session on `client`, attaching auth in the CORRECT order
|
|
222
204
|
* relative to the MCP handshake — the one thing every command must get right:
|
|
223
205
|
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* operator → client.initialize() FIRST, then credential_validate, which is an
|
|
230
|
-
* in-session tool call that requires a completed handshake.
|
|
206
|
+
* attach the cached developer bearer via setToken() BEFORE client.initialize(),
|
|
207
|
+
* so the server binds the session to this identity AT initialize time. An
|
|
208
|
+
* init-then-attach order leaves the session anonymous for its whole life on a
|
|
209
|
+
* server that only binds identity at initialize — this is the invited-developer
|
|
210
|
+
* "no stores you can build on" dead-end. This ordering is load-bearing.
|
|
231
211
|
*
|
|
232
|
-
* Every command calls THIS instead of hand-ordering initialize() +
|
|
233
|
-
* so the ordering rule lives in exactly one place. `opts.initialize`
|
|
234
|
-
* inject its own handshake (to wrap the unreachable-MCP error, or
|
|
235
|
-
* clientInfo); it defaults to `() => client.initialize()`.
|
|
212
|
+
* Every command calls THIS instead of hand-ordering initialize() + the developer
|
|
213
|
+
* resolver, so the ordering rule lives in exactly one place. `opts.initialize`
|
|
214
|
+
* lets a caller inject its own handshake (to wrap the unreachable-MCP error, or
|
|
215
|
+
* pass custom clientInfo); it defaults to `() => client.initialize()`.
|
|
216
|
+
*
|
|
217
|
+
* If the retired operator env-triple is set it emits a one-line advisory (see
|
|
218
|
+
* warnLegacyOperatorEnv) and proceeds on the OAuth session regardless — the keys
|
|
219
|
+
* are never read for auth.
|
|
236
220
|
*
|
|
237
221
|
* @param {ReturnType<import("./mcp.mjs").createMcpClient>} client
|
|
238
|
-
* @param {{ env?: NodeJS.ProcessEnv,
|
|
239
|
-
*
|
|
240
|
-
* @returns {Promise<{ identity: "operator"|"developer", appDomain: string|null, email?: string|null }>}
|
|
222
|
+
* @param {{ env?: NodeJS.ProcessEnv, initialize?: () => Promise<any> }} [opts]
|
|
223
|
+
* @returns {Promise<{ identity: "developer", appDomain: null, token: string, email?: string|null }>}
|
|
241
224
|
*/
|
|
242
225
|
export async function establishSession(client, opts = {}) {
|
|
243
226
|
const env = opts.env || process.env;
|
|
244
|
-
const prefer = opts.prefer || (hasOperatorCreds(env) ? "operator" : "developer");
|
|
245
227
|
const initialize = opts.initialize || (() => client.initialize());
|
|
246
228
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
await initialize();
|
|
252
|
-
return session;
|
|
253
|
-
}
|
|
254
|
-
// Operator: handshake FIRST, then credential_validate (an in-session tool call).
|
|
229
|
+
warnLegacyOperatorEnv(env);
|
|
230
|
+
// Attach the bearer, THEN handshake — the handshake carries the Authorization
|
|
231
|
+
// header so the server binds this identity at initialize time.
|
|
232
|
+
const session = await resolveDeveloperSession(client, env);
|
|
255
233
|
await initialize();
|
|
256
|
-
return
|
|
234
|
+
return session;
|
|
257
235
|
}
|
|
258
236
|
|
|
259
237
|
/**
|
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
* tot checkout <tenant> show the checkout for <tenant>
|
|
7
7
|
* tot checkout <tenant> --clone DIR clone it to DIR with an authed remote
|
|
8
8
|
*
|
|
9
|
-
* This is the SAME MCP code path a developer
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* This is the SAME MCP code path a developer gets when they switch to a tenant:
|
|
10
|
+
* sign-in → client_switch(tenant) → tenant_checkout, where the MCP derives your
|
|
11
|
+
* per-tenant Git user and mints a FRESH, single-active, repo-scoped push
|
|
12
|
+
* credential (a later checkout for the same tenant rotates it). `tot` performs NO
|
|
13
|
+
* privileged forge work itself — the MCP owns that.
|
|
14
14
|
*
|
|
15
|
-
* Auth is
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Auth is the developer's own ToT identity (the cached `tot login` session,
|
|
16
|
+
* resolved via src/auth.mjs). When there's no session yet and we're on a TTY, we
|
|
17
|
+
* offer to sign in right here and retry — no "run tot login, then re-run".
|
|
18
18
|
*
|
|
19
19
|
* Dependency-free (global fetch + `git` via child_process).
|
|
20
20
|
*/
|
|
@@ -22,6 +22,7 @@ import { execFile } from "node:child_process";
|
|
|
22
22
|
import { promisify } from "node:util";
|
|
23
23
|
import { createMcpClient } from "../mcp.mjs";
|
|
24
24
|
import { establishSession, AuthUnavailableError } from "../auth.mjs";
|
|
25
|
+
import { offerSignIn } from "./login.mjs";
|
|
25
26
|
import { CliError, fail, formatError } from "../errors.mjs";
|
|
26
27
|
import { writeNvmrc } from "../sample.mjs";
|
|
27
28
|
import { emitObstacle } from "../obstacle.mjs";
|
|
@@ -36,7 +37,6 @@ function parseArgs(argv) {
|
|
|
36
37
|
tag: "main",
|
|
37
38
|
clone: null,
|
|
38
39
|
mcp: null,
|
|
39
|
-
identity: null, // "operator" | "developer" — override auto-selection
|
|
40
40
|
printRemote: false,
|
|
41
41
|
help: false,
|
|
42
42
|
};
|
|
@@ -45,7 +45,6 @@ function parseArgs(argv) {
|
|
|
45
45
|
if (t === "--tag") a.tag = argv[++i];
|
|
46
46
|
else if (t === "--clone") a.clone = argv[++i];
|
|
47
47
|
else if (t === "--mcp") a.mcp = argv[++i];
|
|
48
|
-
else if (t === "--identity") a.identity = argv[++i];
|
|
49
48
|
else if (t === "--print-remote") a.printRemote = true;
|
|
50
49
|
else if (t === "--help" || t === "-h") a.help = true;
|
|
51
50
|
else if (!t.startsWith("--") && !a.tenant) a.tenant = t;
|
|
@@ -64,7 +63,6 @@ Options:
|
|
|
64
63
|
--clone <dir> git clone the authenticated remote into <dir>.
|
|
65
64
|
--mcp <url> MCP base URL. Default: env MCP_BASE_URL / TOT_MCP_URL, else
|
|
66
65
|
${DEFAULT_MCP_URL}.
|
|
67
|
-
--identity <who> force "operator" or "developer" auth (default: auto).
|
|
68
66
|
--print-remote also print the authenticated remote (contains a live token!).`;
|
|
69
67
|
|
|
70
68
|
/** Redact known secrets from any string before it hits the terminal. */
|
|
@@ -100,12 +98,21 @@ export async function run(argv, ctx) {
|
|
|
100
98
|
|
|
101
99
|
try {
|
|
102
100
|
// Attach auth in the right order relative to the handshake (developer bearer
|
|
103
|
-
// BEFORE initialize
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
// BEFORE initialize) — see establishSession. When there's no session yet and
|
|
102
|
+
// we're on a TTY, offer to sign in inline and retry once, so a not-signed-in
|
|
103
|
+
// developer isn't dead-ended at "run tot login, then re-run".
|
|
104
|
+
try {
|
|
105
|
+
await establishSession(client, { env });
|
|
106
|
+
} catch (e) {
|
|
107
|
+
if (e instanceof AuthUnavailableError && e.reason === "missing") {
|
|
108
|
+
const signedIn = await offerSignIn(client.mcpUrl, env, {});
|
|
109
|
+
if (!signedIn) throw e; // declined/non-TTY → the crisp error below
|
|
110
|
+
await establishSession(client, { env }); // retry once, in-flow
|
|
111
|
+
} else {
|
|
112
|
+
throw e;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
console.error(`~ signed in → ${client.mcpUrl}`);
|
|
109
116
|
|
|
110
117
|
// No tenant → list the stores this identity can build on and stop.
|
|
111
118
|
if (!args.tenant) {
|
package/src/commands/dev.mjs
CHANGED
|
@@ -651,7 +651,7 @@ export async function resolveEntitledRendererSource(args, { client: providedClie
|
|
|
651
651
|
*
|
|
652
652
|
* Exported + accepts an already-authenticated `client` (C1/F3 integration:
|
|
653
653
|
* `tot start` reuses its own session and overlaps this with the checkout clone
|
|
654
|
-
* instead of paying for a second client.initialize()+
|
|
654
|
+
* instead of paying for a second client.initialize()+establishSession() — same
|
|
655
655
|
* pattern as ensureRegistryLogin's `providedClient`). `tot dev` standalone
|
|
656
656
|
* omits it and this establishes its own, as before.
|
|
657
657
|
* @returns {Promise<string>} the cached, installed runner tree's root directory.
|
|
@@ -1355,7 +1355,7 @@ export function isPrivateRegistryImage(image) {
|
|
|
1355
1355
|
*
|
|
1356
1356
|
* Exported + accepts an already-authenticated `client` (C1: `tot start` reuses
|
|
1357
1357
|
* its own session and overlaps this with the checkout clone instead of paying
|
|
1358
|
-
* for a second client.initialize()+
|
|
1358
|
+
* for a second client.initialize()+establishSession() serially afterward).
|
|
1359
1359
|
* `tot dev` standalone omits it and this establishes its own, as before.
|
|
1360
1360
|
*/
|
|
1361
1361
|
export async function ensureRegistryLogin(image, args, { client: providedClient } = {}) {
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { spawnSync } from "node:child_process";
|
|
|
22
22
|
import { existsSync, mkdirSync } from "node:fs";
|
|
23
23
|
import { homedir } from "node:os";
|
|
24
24
|
import { join } from "node:path";
|
|
25
|
-
import {
|
|
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
28
|
import { defaultCredentialsPath, readCredentials, isExpired } from "../token-store.mjs";
|
|
@@ -110,28 +110,34 @@ export function collectChecks(_ctx, env = process.env) {
|
|
|
110
110
|
blocking: false,
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
const
|
|
114
|
-
const devCreds = operator ? null : readCredentials(defaultCredentialsPath(env));
|
|
113
|
+
const devCreds = readCredentials(defaultCredentialsPath(env));
|
|
115
114
|
const devUsable = !!devCreds?.accessToken && (!isExpired(devCreds) || !!devCreds.refreshToken);
|
|
116
115
|
checks.push({
|
|
117
116
|
name: "auth identity",
|
|
118
|
-
pass:
|
|
119
|
-
detail:
|
|
120
|
-
? "operator creds present (TOT_API_KEY/TOT_SECRET_KEY/TOT_APP_DOMAIN)"
|
|
121
|
-
: devUsable
|
|
122
|
-
? "signed in as a developer"
|
|
123
|
-
: "not signed in — run `tot login`",
|
|
117
|
+
pass: devUsable,
|
|
118
|
+
detail: devUsable ? "signed in as a developer" : "not signed in — run `tot login`",
|
|
124
119
|
blocking: false,
|
|
125
120
|
});
|
|
126
121
|
|
|
122
|
+
// A stale operator env-triple no longer signs the CLI in (OAuth-first since
|
|
123
|
+
// 2026-07-23) — surface it as an informational note, never a failure.
|
|
124
|
+
if (hasLegacyOperatorEnv(env)) {
|
|
125
|
+
checks.push({
|
|
126
|
+
name: "legacy keys",
|
|
127
|
+
pass: true,
|
|
128
|
+
detail: legacyOperatorEnvAdvisory,
|
|
129
|
+
blocking: false,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
return checks;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
/**
|
|
131
137
|
* Auto-remediate the checks that are safely fixable without human judgment
|
|
132
138
|
* (F2) — announcing each one as it runs. A stale-but-refreshable sign-in isn't
|
|
133
|
-
* handled here because it isn't a failing check:
|
|
134
|
-
* silently the next time it's actually used (B4).
|
|
139
|
+
* handled here because it isn't a failing check: the developer session refreshes
|
|
140
|
+
* it silently the next time it's actually used (B4).
|
|
135
141
|
*/
|
|
136
142
|
async function applyFixes(checks, env) {
|
|
137
143
|
const totDir = join(env.TOT_HOME || homedir(), ".tot");
|
|
@@ -144,7 +150,7 @@ async function applyFixes(checks, env) {
|
|
|
144
150
|
if (docker && !docker.pass) await tryStartDocker();
|
|
145
151
|
|
|
146
152
|
const auth = checks.find((c) => c.name === "auth identity");
|
|
147
|
-
if (auth && !auth.pass
|
|
153
|
+
if (auth && !auth.pass) {
|
|
148
154
|
const mcpUrl = env.MCP_BASE_URL || env.TOT_MCP_URL || DEFAULT_MCP_URL;
|
|
149
155
|
console.log(" ~ not signed in — opening the browser to sign in…");
|
|
150
156
|
try {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* the developer copy-pasting anything.
|
|
10
10
|
*
|
|
11
11
|
* Auth: the MCP feedback tool is authenticated, so this reuses the signed-in session
|
|
12
|
-
* (`
|
|
13
|
-
*
|
|
12
|
+
* (the `tot login` developer token, via `establishSession`). Not signed in → a
|
|
13
|
+
* clear "run `tot login`" instead of a stack trace.
|
|
14
14
|
*
|
|
15
15
|
* Sending publishes to Token of Trust, so we PREVIEW the report and ask before
|
|
16
16
|
* sending (skippable with --yes; required in a non-interactive shell).
|
|
@@ -179,8 +179,8 @@ export async function run(argv) {
|
|
|
179
179
|
const client = createMcpClient(mcpUrl);
|
|
180
180
|
try {
|
|
181
181
|
// Attach auth in the right order relative to the handshake (developer bearer
|
|
182
|
-
// pre-initialize
|
|
183
|
-
//
|
|
182
|
+
// pre-initialize). We keep the feedback-specific clientInfo by injecting our
|
|
183
|
+
// own initialize.
|
|
184
184
|
await establishSession(client, {
|
|
185
185
|
env,
|
|
186
186
|
initialize: () => client.initialize({ name: "tot-cli", version: "feedback" }),
|