@tiny-fish/cli 0.18.1-next.173 → 0.19.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/dist/commands/connect.js
CHANGED
|
@@ -8,6 +8,7 @@ import { runConnectAll } from "../lib/connect-all.js";
|
|
|
8
8
|
import { detectHumanInitiated } from "../lib/harness.js";
|
|
9
9
|
import { emitNotice } from "../lib/notice.js";
|
|
10
10
|
import { errLine, sanitizeLine } from "../lib/output.js";
|
|
11
|
+
import { codexOauthCompleted } from "../lib/registration-detect.js";
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
import { installSignalGuard } from "../lib/signals.js";
|
|
13
14
|
import { postConnectEvent, telemetryDisabled } from "../lib/setup-telemetry.js";
|
|
@@ -192,6 +193,9 @@ const CODEX = {
|
|
|
192
193
|
unavailableMessage: CODEX_OAUTH_RESOURCE_UNAVAILABLE_MESSAGE,
|
|
193
194
|
},
|
|
194
195
|
launchWalkthrough: launchCodexWalkthrough,
|
|
196
|
+
probeAuthenticated: codexOauthCompleted,
|
|
197
|
+
// Retry only: running it after a finished inline OAuth is a second browser hop.
|
|
198
|
+
loginArgs: ["mcp", "login", "tinyfish"],
|
|
195
199
|
removals: [{ args: ["mcp", "remove", "tinyfish"], label: "TinyFish registration from Codex" }],
|
|
196
200
|
addArgs: (mcpUrl, oauthResource) => [
|
|
197
201
|
"mcp",
|
|
@@ -627,7 +631,9 @@ async function connectNativeMcpClient(client, options) {
|
|
|
627
631
|
// Undefining `loginArgs` reuses the deferred-auth path codex/hermes already take.
|
|
628
632
|
const loginDegraded = !!client.loginArgs && !optionalSupported;
|
|
629
633
|
const loginArgs = loginDegraded ? undefined : client.loginArgs;
|
|
630
|
-
|
|
634
|
+
// A probed client authenticates inside `mcp add` too, so a failure there is either.
|
|
635
|
+
state.stage =
|
|
636
|
+
loginArgs && !client.probeAuthenticated ? "registration" : "registration_or_authentication";
|
|
631
637
|
errLine(`Adding TinyFish to ${client.displayName}...`);
|
|
632
638
|
const addResult = spawn.sync(client.command, [
|
|
633
639
|
...client.addArgs(mcpUrl.toString(), options.mcpUrl),
|
|
@@ -644,6 +650,11 @@ async function connectNativeMcpClient(client, options) {
|
|
|
644
650
|
});
|
|
645
651
|
}
|
|
646
652
|
telemetry.track("checkpoint", { phase: "registered" });
|
|
653
|
+
// Codex runs OAuth inside `mcp add` and exits 0 either way, so the recorded state decides
|
|
654
|
+
// whether the login step below still runs. Unknown keeps the pre-probe behaviour.
|
|
655
|
+
const addSignedIn = client.probeAuthenticated?.();
|
|
656
|
+
if (addSignedIn)
|
|
657
|
+
telemetry.track("checkpoint", { phase: "oauth_done" });
|
|
647
658
|
const degradedNotes = loginDegraded ? client.degradedAuthNotes : undefined;
|
|
648
659
|
if (useKeyAuth) {
|
|
649
660
|
errLine(degradedNotes?.keyed ?? "Using your stored TinyFish API key — no browser sign-in needed.");
|
|
@@ -652,10 +663,13 @@ async function connectNativeMcpClient(client, options) {
|
|
|
652
663
|
// Printed here, not with the closing line, so a `--launch` run sees it before the handover.
|
|
653
664
|
errLine(degradedNotes.deferred);
|
|
654
665
|
}
|
|
655
|
-
|
|
666
|
+
// A probed client signs in again only when its inline OAuth is known to have been abandoned.
|
|
667
|
+
const retryLogin = client.probeAuthenticated ? addSignedIn === false : true;
|
|
668
|
+
const pendingLogin = !useKeyAuth && retryLogin ? loginArgs : undefined;
|
|
669
|
+
if (pendingLogin) {
|
|
656
670
|
state.stage = "client_oauth";
|
|
657
671
|
errLine("Signing in to TinyFish...");
|
|
658
|
-
const loginResult = spawn.sync(client.command,
|
|
672
|
+
const loginResult = spawn.sync(client.command, pendingLogin, {
|
|
659
673
|
stdio: "inherit",
|
|
660
674
|
});
|
|
661
675
|
if (loginResult.error || loginResult.status !== 0) {
|
|
@@ -668,7 +682,7 @@ async function connectNativeMcpClient(client, options) {
|
|
|
668
682
|
}
|
|
669
683
|
// Registration/OAuth make MCP work; later steps are cosmetic and must not fail the attempt.
|
|
670
684
|
settle(state, telemetry, "completed");
|
|
671
|
-
runPostInstallSteps(client, options, state, telemetry, !
|
|
685
|
+
runPostInstallSteps(client, options, state, telemetry, !useKeyAuth && !addSignedIn && !pendingLogin);
|
|
672
686
|
});
|
|
673
687
|
}
|
|
674
688
|
function connectedLine(client, signInDeferred) {
|
package/dist/lib/connect-all.js
CHANGED
|
@@ -18,6 +18,7 @@ const DISPLAY_NAMES = {
|
|
|
18
18
|
opencode: "OpenCode",
|
|
19
19
|
};
|
|
20
20
|
// `codex mcp add` succeeds unauthenticated; OAuth lands at first tool use.
|
|
21
|
+
// TODO(PF-3580): connect now probes Codex sign-in, so this over-warns a signed-in install.
|
|
21
22
|
const AUTH_DEFERRED_AT_INSTALL = new Set(["codex"]);
|
|
22
23
|
// Harnesses that drive their own OAuth at install — a stored TinyFish key says nothing
|
|
23
24
|
// about their session, so it must never unlock a headless run for them.
|
|
@@ -43,8 +43,8 @@ declare const doctorHarnessSchema: z.ZodObject<{
|
|
|
43
43
|
}>;
|
|
44
44
|
auth_mode: z.ZodEnum<{
|
|
45
45
|
unknown: "unknown";
|
|
46
|
-
oauth: "oauth";
|
|
47
46
|
"api-key": "api-key";
|
|
47
|
+
oauth: "oauth";
|
|
48
48
|
}>;
|
|
49
49
|
proves_harness_reach: z.ZodBoolean;
|
|
50
50
|
}, z.core.$strip>;
|
|
@@ -105,8 +105,8 @@ export declare const doctorReportSchema: z.ZodObject<{
|
|
|
105
105
|
}>;
|
|
106
106
|
auth_mode: z.ZodEnum<{
|
|
107
107
|
unknown: "unknown";
|
|
108
|
-
oauth: "oauth";
|
|
109
108
|
"api-key": "api-key";
|
|
109
|
+
oauth: "oauth";
|
|
110
110
|
}>;
|
|
111
111
|
proves_harness_reach: z.ZodBoolean;
|
|
112
112
|
}, z.core.$strip>>;
|
|
@@ -11,5 +11,6 @@ export interface RegistrationStatus {
|
|
|
11
11
|
registeredUrl?: string;
|
|
12
12
|
reason?: string;
|
|
13
13
|
}
|
|
14
|
+
export declare function codexOauthCompleted(): boolean | undefined;
|
|
14
15
|
/** Never throws; a failed probe is `unknown` with a reason, so no caller can read it as healthy. */
|
|
15
16
|
export declare function detectRegistrations(harnesses?: Harness[]): RegistrationStatus[];
|
|
@@ -129,19 +129,14 @@ function fromMcpGet(command, keyAuthPattern) {
|
|
|
129
129
|
}
|
|
130
130
|
// `codex mcp get <missing>` prints an error and exits 0, so presence must never be read from
|
|
131
131
|
// its exit code; `mcp list --json` is the only trustworthy read-back.
|
|
132
|
-
function
|
|
132
|
+
function readCodexEntry() {
|
|
133
133
|
const probe = runProbe("codex", ["mcp", "list", "--json"]);
|
|
134
|
-
if (probe.outcome === "unavailable")
|
|
135
|
-
return {
|
|
136
|
-
}
|
|
134
|
+
if (probe.outcome === "unavailable")
|
|
135
|
+
return { reason: probe.reason };
|
|
137
136
|
// Presence is never inferred from exit code 0, but a nonzero exit means the output cannot
|
|
138
137
|
// be trusted either — that is `unknown`, not "no server".
|
|
139
138
|
if (probe.exitCode !== 0) {
|
|
140
|
-
return {
|
|
141
|
-
registered: "unknown",
|
|
142
|
-
authMode: "unknown",
|
|
143
|
-
reason: `\`codex mcp list --json\` exited ${probe.exitCode}`,
|
|
144
|
-
};
|
|
139
|
+
return { reason: `\`codex mcp list --json\` exited ${probe.exitCode}` };
|
|
145
140
|
}
|
|
146
141
|
let servers;
|
|
147
142
|
try {
|
|
@@ -150,26 +145,35 @@ function probeCodex() {
|
|
|
150
145
|
servers = JSON.parse(probe.stdout);
|
|
151
146
|
}
|
|
152
147
|
catch {
|
|
153
|
-
return {
|
|
154
|
-
registered: "unknown",
|
|
155
|
-
authMode: "unknown",
|
|
156
|
-
reason: "`codex mcp list --json` returned unparseable output",
|
|
157
|
-
};
|
|
148
|
+
return { reason: "`codex mcp list --json` returned unparseable output" };
|
|
158
149
|
}
|
|
159
150
|
// A scalar or a wrapper object decodes fine and then silently reports "no server", which
|
|
160
151
|
// would suggest a spurious repair. Anything but a list is unknown.
|
|
161
152
|
const shape = codexListSchema.safeParse(servers);
|
|
162
|
-
if (!shape.success)
|
|
163
|
-
return {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
153
|
+
if (!shape.success)
|
|
154
|
+
return { reason: "`codex mcp list --json` returned an unexpected shape" };
|
|
155
|
+
return { entry: shape.data.find((s) => s.name === "tinyfish") };
|
|
156
|
+
}
|
|
157
|
+
// `mcp add` exits 0 whether or not its inline OAuth finished, so `not_logged_in` is the only
|
|
158
|
+
// evidence of an abandoned sign-in; every other state, older Codex included, is unknown.
|
|
159
|
+
export function codexOauthCompleted() {
|
|
160
|
+
const read = readCodexEntry();
|
|
161
|
+
if ("reason" in read)
|
|
162
|
+
return undefined;
|
|
163
|
+
const status = read.entry?.auth_status;
|
|
164
|
+
if (status === "o_auth")
|
|
165
|
+
return true;
|
|
166
|
+
if (status === "not_logged_in")
|
|
167
|
+
return false;
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
function probeCodex() {
|
|
171
|
+
const read = readCodexEntry();
|
|
172
|
+
if ("reason" in read)
|
|
173
|
+
return { registered: "unknown", authMode: "unknown", reason: read.reason };
|
|
174
|
+
const entry = read.entry;
|
|
170
175
|
if (!entry)
|
|
171
176
|
return { registered: "no", authMode: "unknown" };
|
|
172
|
-
// auth_status is the configured auth type, not whether a login succeeded — exactly auth_mode.
|
|
173
177
|
const codexUrl = entry.transport?.url;
|
|
174
178
|
return {
|
|
175
179
|
registered: "yes",
|