@tiny-fish/cli 0.34.1-next.282 → 0.34.1-next.285
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 +2 -2
- package/dist/commands/connect.js +13 -4
- package/dist/commands/doctor.js +3 -1
- package/dist/lib/connect-clients.d.ts +6 -1
- package/dist/lib/connect-clients.js +37 -10
- package/dist/lib/connect-runtime.d.ts +2 -2
- package/dist/lib/hermes-plugin.d.ts +6 -0
- package/dist/lib/hermes-plugin.js +99 -0
- package/dist/lib/registration-detect.js +16 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,8 +57,8 @@ interactive walkthrough with one command:
|
|
|
57
57
|
npx -y @tiny-fish/cli@latest connect codex --launch --api-key sk-tinyfish-...
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
One-command OAuth requires a Codex release whose `codex mcp add` command supports
|
|
61
|
-
|
|
60
|
+
One-command OAuth requires a Codex release whose `codex mcp add` command supports `--url`, plus
|
|
61
|
+
`codex mcp login`. Update Codex if support is unavailable, or add TinyFish manually with
|
|
62
62
|
`codex mcp add`.
|
|
63
63
|
|
|
64
64
|
With `--api-key`, Codex reads the key from `TINYFISH_API_KEY` instead of signing in — the command
|
package/dist/commands/connect.js
CHANGED
|
@@ -75,11 +75,10 @@ function refuseKeylessInstall(client, keyAuthSupported) {
|
|
|
75
75
|
`Run: tinyfish auth login, then ${rerun}`, 'invalid_config', { failureDetail: 'keyless_install_refused' });
|
|
76
76
|
}
|
|
77
77
|
/** Resolved before the removals, so a refusal strands no registration. */
|
|
78
|
-
function keylessAddArgs(client, keyAuthSupported
|
|
78
|
+
function keylessAddArgs(client, keyAuthSupported) {
|
|
79
79
|
if (!client.addArgs)
|
|
80
80
|
refuseKeylessInstall(client, keyAuthSupported);
|
|
81
|
-
|
|
82
|
-
return (mcpUrl) => addArgs(mcpUrl, oauthResource);
|
|
81
|
+
return client.addArgs;
|
|
83
82
|
}
|
|
84
83
|
/** Runs before any write: a refused key strands nothing. */
|
|
85
84
|
async function verifyKeyBeforeSeeding(apiKey, displayName, mcpUrl) {
|
|
@@ -157,7 +156,7 @@ async function prepareInstallAuth(client, options, keyAuthSupported) {
|
|
|
157
156
|
// Refuses here when key-required; others keep their OAuth argv.
|
|
158
157
|
const buildAddArgs = keyedAdd
|
|
159
158
|
? (mcpUrl) => keyedAdd.spec.addArgs(mcpUrl, keyedAdd.key)
|
|
160
|
-
: keylessAddArgs(client, keyAuthSupported
|
|
159
|
+
: keylessAddArgs(client, keyAuthSupported);
|
|
161
160
|
if (options.keyAuthOnly && !keyedAdd) {
|
|
162
161
|
// All native clients have keyAuth; missing flags mean an old install.
|
|
163
162
|
if (client.keyAuth) {
|
|
@@ -346,6 +345,16 @@ async function runPostInstallSteps(client, options, state, telemetry, signInDefe
|
|
|
346
345
|
telemetry.track('checkpoint', { phase: 'skill_installed' });
|
|
347
346
|
});
|
|
348
347
|
}
|
|
348
|
+
const extraPostInstall = client.extraPostInstall;
|
|
349
|
+
if (extraPostInstall) {
|
|
350
|
+
await attempt('plugin_install', () => {
|
|
351
|
+
extraPostInstall({
|
|
352
|
+
apiKey: validatedApiKey(options.apiKey),
|
|
353
|
+
verbose: options.verbose ?? false,
|
|
354
|
+
});
|
|
355
|
+
telemetry.track('checkpoint', { phase: 'plugin_installed' });
|
|
356
|
+
});
|
|
357
|
+
}
|
|
349
358
|
// Before auth: a failed sign-in must not lose the harness record.
|
|
350
359
|
saveConnectContext(client.connectClient, telemetry.attemptId, state.authMode);
|
|
351
360
|
await attempt('authentication', () => {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -217,7 +217,9 @@ function repairsFor(checks, statuses) {
|
|
|
217
217
|
for (const status of statuses) {
|
|
218
218
|
// `unknown` earns nothing: connect would contradict the check's detail.
|
|
219
219
|
// A registered entry that still fails earns one too.
|
|
220
|
-
|
|
220
|
+
// `connected: false` is the harness's own no; it only warns, so it earns a repair here.
|
|
221
|
+
const brokenRegistration = status.connected === false ||
|
|
222
|
+
checks.some((c) => c.harness === status.harness && c.status === 'fail');
|
|
221
223
|
if (!status.detected || (status.registered !== Registered.No && !brokenRegistration))
|
|
222
224
|
continue;
|
|
223
225
|
repairs.push({
|
|
@@ -69,11 +69,16 @@ interface BaseMcpClient extends SupportedCommand {
|
|
|
69
69
|
detachedLaunch?: () => WalkthroughOutcome;
|
|
70
70
|
/** Printed after a successful connect, on both the launch and non-launch paths. */
|
|
71
71
|
postConnectNote?: string;
|
|
72
|
+
/** Post-install like the skill step: failures warn, never fail the attempt. */
|
|
73
|
+
extraPostInstall?: (options: {
|
|
74
|
+
apiKey?: string;
|
|
75
|
+
verbose: boolean;
|
|
76
|
+
}) => void;
|
|
72
77
|
}
|
|
73
78
|
/** Keyless installs use this argv; the harness signs itself in. */
|
|
74
79
|
export interface OauthCapableMcpClient extends BaseMcpClient {
|
|
75
80
|
keyRequired?: false;
|
|
76
|
-
addArgs: (mcpUrl: string
|
|
81
|
+
addArgs: (mcpUrl: string) => string[];
|
|
77
82
|
}
|
|
78
83
|
/** No sign-in exists here, so connect refuses a keyless install (Hermes). */
|
|
79
84
|
export interface KeyRequiredMcpClient extends BaseMcpClient {
|
|
@@ -7,6 +7,7 @@ import { ConnectStepError, spawnStepError, } from './connect-runtime.js';
|
|
|
7
7
|
import { TINYFISH_API_KEY_VAR } from './constants.js';
|
|
8
8
|
import { HERMES_KEY_VAR, captureHermesKeyRestore, hermesEnvPath, resolveHermesHome, writeHermesKey, } from './hermes-env.js';
|
|
9
9
|
import { hermesConfigPath, readHermesEntry } from './hermes-config.js';
|
|
10
|
+
import { installHermesPlugin, setHermesWebBackends } from './hermes-plugin.js';
|
|
10
11
|
const HERMES_SEED_TIMEOUT_MS = 120_000;
|
|
11
12
|
// An old install is only one reason a flag can go unseen, so no message asserts the cause.
|
|
12
13
|
const SUPPORT_CHECK_DEBUG_HINT = ' Set TINYFISH_DEBUG=1 and retry to print the help output TinyFish read.';
|
|
@@ -20,10 +21,15 @@ const CLAUDE_CODE_DEFERRED_AUTH_NOTE = 'TinyFish is registered in Claude Code bu
|
|
|
20
21
|
'one-command sign-in (`claude mcp login`). Open Claude Code and run `/mcp` to sign in to ' +
|
|
21
22
|
'TinyFish. `claude update` gets you one-command sign-in. Already have a TinyFish API key? ' +
|
|
22
23
|
'Run `tinyfish connect claude-code --api-key <key>` instead.';
|
|
23
|
-
const
|
|
24
|
-
'
|
|
25
|
-
'
|
|
24
|
+
const CODEX_MCP_URL_UNAVAILABLE_MESSAGE = 'Could not confirm this Codex installation supports remote MCP servers: `codex mcp add ' +
|
|
25
|
+
'--help` did not list `--url`. Update Codex and retry, or add TinyFish manually with ' +
|
|
26
|
+
'`codex mcp add`.' +
|
|
26
27
|
SUPPORT_CHECK_DEBUG_HINT;
|
|
28
|
+
const CODEX_KEYED_FALLBACK_NOTE = 'Using your stored TinyFish API key: this Codex cannot report whether an OAuth sign-in ' +
|
|
29
|
+
'finished, so no browser sign-in is attempted.';
|
|
30
|
+
const CODEX_DEFERRED_AUTH_NOTE = 'TinyFish is registered in Codex but not signed in: this Codex predates reliable sign-in ' +
|
|
31
|
+
'reporting. Run `codex mcp login tinyfish` to sign in, or update Codex with ' +
|
|
32
|
+
'`npm install -g @openai/codex@latest` for one-command sign-in.';
|
|
27
33
|
const HERMES_OAUTH_UNAVAILABLE_MESSAGE = 'Could not confirm this Hermes installation supports one-command MCP authentication: `hermes ' +
|
|
28
34
|
'mcp add --help` did not list `--auth oauth`. Update Hermes and retry, or add TinyFish ' +
|
|
29
35
|
'manually with `hermes mcp add`.' +
|
|
@@ -173,21 +179,28 @@ export const CODEX = {
|
|
|
173
179
|
displayName: 'Codex',
|
|
174
180
|
supportCheck: {
|
|
175
181
|
args: ['mcp', 'add', '--help'],
|
|
176
|
-
patterns: [/--
|
|
182
|
+
patterns: [/--url(?:[\s<=]|$)/m],
|
|
183
|
+
// Codex before ~0.142 reports `o_auth` for a server it never signed in to, so the
|
|
184
|
+
// sign-in probe lies; `--oauth-resource` is the only marker separating the two.
|
|
185
|
+
optionalPatterns: [/--oauth-resource(?:[\s<=]|$)/m],
|
|
177
186
|
keyAuthPattern: /(?:^|\s)--bearer-token-env-var(?:[\s<=]|$)/m,
|
|
178
|
-
unavailableMessage:
|
|
187
|
+
unavailableMessage: CODEX_MCP_URL_UNAVAILABLE_MESSAGE,
|
|
188
|
+
},
|
|
189
|
+
degradedAuthNotes: {
|
|
190
|
+
keyed: CODEX_KEYED_FALLBACK_NOTE,
|
|
191
|
+
deferred: CODEX_DEFERRED_AUTH_NOTE,
|
|
179
192
|
},
|
|
180
193
|
// Codex has no `--header`; the MCP server takes a key as `Bearer sk-…`.
|
|
181
|
-
// Omitting `--oauth-resource` is what keeps `mcp add` from running OAuth.
|
|
182
194
|
keyAuth: {
|
|
183
195
|
envVar: TINYFISH_API_KEY_VAR,
|
|
184
196
|
addArgs: (mcpUrl) => [...urlAddArgs(mcpUrl), '--bearer-token-env-var', TINYFISH_API_KEY_VAR],
|
|
185
197
|
},
|
|
186
198
|
detachedLaunch: launchCodexWalkthrough,
|
|
187
|
-
// Retry only: running it after a finished inline OAuth is a second browser hop.
|
|
188
199
|
loginArgs: ['mcp', 'login', 'tinyfish'],
|
|
189
200
|
removals: [{ args: ['mcp', 'remove', 'tinyfish'], label: 'TinyFish registration from Codex' }],
|
|
190
|
-
|
|
201
|
+
// `--oauth-resource` duplicates the resource Codex discovers; Clerk rejects both.
|
|
202
|
+
// `mcp add` still runs OAuth inline, so `loginArgs` stays a retry.
|
|
203
|
+
addArgs: urlAddArgs,
|
|
191
204
|
};
|
|
192
205
|
const grokAddArgs = (mcpUrl) => [
|
|
193
206
|
'mcp',
|
|
@@ -259,12 +272,16 @@ export function hermesRegistrationEnabled(home) {
|
|
|
259
272
|
// The tag keeps the path-bearing prose out of telemetry.
|
|
260
273
|
return { ok: false, detail, tag: `hermes_entry_${entry.state}` };
|
|
261
274
|
}
|
|
262
|
-
|
|
275
|
+
// A plain Error settles unexpected_error; cli_connect_stage is load-bearing.
|
|
276
|
+
function requireHermesHome() {
|
|
263
277
|
const home = resolveHermesHome();
|
|
264
|
-
// A plain Error settles unexpected_error; cli_connect_stage is load-bearing.
|
|
265
278
|
if (!home) {
|
|
266
279
|
throw new ConnectStepError("Could not determine Hermes' home directory from `hermes dump`", 'invalid_config', { failureDetail: 'hermes_home_undetermined' });
|
|
267
280
|
}
|
|
281
|
+
return home;
|
|
282
|
+
}
|
|
283
|
+
function seedHermesKey(apiKey) {
|
|
284
|
+
const home = requireHermesHome();
|
|
268
285
|
const restore = captureHermesKeyRestore(home);
|
|
269
286
|
writeHermesKey(home, apiKey);
|
|
270
287
|
return {
|
|
@@ -287,6 +304,15 @@ function rollbackHermesKey(home, restore) {
|
|
|
287
304
|
warnLine(`Could not restore ${HERMES_KEY_VAR} in ${hermesEnvPath(home)}; check it by hand.`);
|
|
288
305
|
}
|
|
289
306
|
}
|
|
307
|
+
// Hermes is keyRequired; a missing key here is a resolution bug.
|
|
308
|
+
function installHermesWebPlugin({ apiKey, verbose }) {
|
|
309
|
+
if (!apiKey) {
|
|
310
|
+
throw new ConnectStepError('No TinyFish API key was available to install the Hermes web plugin', 'invalid_config');
|
|
311
|
+
}
|
|
312
|
+
const home = requireHermesHome();
|
|
313
|
+
installHermesPlugin(home, apiKey, { verbose });
|
|
314
|
+
setHermesWebBackends(home);
|
|
315
|
+
}
|
|
290
316
|
export const HERMES = {
|
|
291
317
|
command: 'hermes',
|
|
292
318
|
connectClient: 'hermes',
|
|
@@ -305,6 +331,7 @@ export const HERMES = {
|
|
|
305
331
|
seedKey: seedHermesKey,
|
|
306
332
|
},
|
|
307
333
|
removals: [{ args: ['mcp', 'remove', 'tinyfish'], label: 'TinyFish registration from Hermes' }],
|
|
334
|
+
extraPostInstall: installHermesWebPlugin,
|
|
308
335
|
launchWalkthrough: launchHermesWalkthrough,
|
|
309
336
|
postConnectNote: 'Restart your Hermes session to pick up TinyFish — Hermes discovers MCP servers at startup.',
|
|
310
337
|
};
|
|
@@ -2,11 +2,11 @@ import { AuthMode } from './harness-detect.js';
|
|
|
2
2
|
export declare const NON_INTERACTIVE_TIMEOUT_MS = 10000;
|
|
3
3
|
export type AgentClient = 'claude-code' | 'codex' | 'cursor' | 'grok' | 'hermes' | 'openclaw' | 'opencode';
|
|
4
4
|
type ConnectStage = 'started' | 'checkpoint' | 'completed' | 'failed' | 'aborted' | 'post_install_failed';
|
|
5
|
-
export type ConnectFailureStage = 'prerequisite_check' | 'registration_cleanup' | 'registration' | 'registration_or_authentication' | 'client_oauth' | 'cli_install' | 'skill_install' | 'authentication' | 'walkthrough_launch' | 'cli_verification';
|
|
5
|
+
export type ConnectFailureStage = 'prerequisite_check' | 'registration_cleanup' | 'registration' | 'registration_or_authentication' | 'client_oauth' | 'cli_install' | 'skill_install' | 'plugin_install' | 'authentication' | 'walkthrough_launch' | 'cli_verification';
|
|
6
6
|
export declare const CONNECT_FAILURE_REASONS: readonly ["harness_not_installed", "harness_command_unsupported", "harness_too_old", "key_auth_unsupported", "command_not_found", "timeout", "spawn_error", "nonzero_exit", "invalid_config", "user_abandoned", "process_terminated", "unexpected_error"];
|
|
7
7
|
export type ConnectFailureReason = (typeof CONNECT_FAILURE_REASONS)[number];
|
|
8
8
|
export type ConnectAuthMode = Exclude<AuthMode, AuthMode.Unknown> | 'deferred';
|
|
9
|
-
export type ConnectCheckpoint = 'prerequisite_ok' | 'registered' | 'oauth_done' | 'cli_installed' | 'skill_installed' | 'authenticated' | 'cli_verified' | 'walkthrough_launched' | 'walkthrough_prompt_printed';
|
|
9
|
+
export type ConnectCheckpoint = 'prerequisite_ok' | 'registered' | 'oauth_done' | 'cli_installed' | 'skill_installed' | 'plugin_installed' | 'authenticated' | 'cli_verified' | 'walkthrough_launched' | 'walkthrough_prompt_printed';
|
|
10
10
|
export type CliFallbackOutcome = 'cli_verified' | 'cli_install_failed' | 'no_credentials' | 'invalid_credentials' | 'authentication_failed' | 'verification_failed' | 'abandoned';
|
|
11
11
|
/** Ctrl+C killed a setup child — abandonment, not error. */
|
|
12
12
|
export declare class ConnectInterruptedError extends Error {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type InstallOptions } from './cli-install.js';
|
|
2
|
+
export declare const HERMES_PLUGIN_SHA = "496cd63fefd982bbaa8a85ce78ef2270d700984f";
|
|
3
|
+
export declare const HERMES_PLUGIN_MANUAL_INSTALL = "hermes plugins install tinyfish-io/tinyfish-web-agent-integrations/hermes --ref 496cd63fefd982bbaa8a85ce78ef2270d700984f --enable";
|
|
4
|
+
export declare function installHermesPlugin(home: string, apiKey: string, { verbose }: Pick<InstallOptions, 'verbose'>): void;
|
|
5
|
+
/** After install only: backends must never name an absent provider. */
|
|
6
|
+
export declare function setHermesWebBackends(home: string): void;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import spawn from 'cross-spawn';
|
|
6
|
+
import { capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from './cli-install.js';
|
|
7
|
+
import { commandNotFound, ConnectStepError, spawnStepError } from './connect-runtime.js';
|
|
8
|
+
import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
|
|
9
|
+
import { errLine } from './output.js';
|
|
10
|
+
// Bump per CLI release.
|
|
11
|
+
export const HERMES_PLUGIN_SHA = '496cd63fefd982bbaa8a85ce78ef2270d700984f';
|
|
12
|
+
const PLUGIN_SOURCE = 'tinyfish-io/tinyfish-web-agent-integrations/hermes';
|
|
13
|
+
const PLUGIN_REPO_URL = 'https://github.com/tinyfish-io/tinyfish-web-agent-integrations.git';
|
|
14
|
+
export const HERMES_PLUGIN_MANUAL_INSTALL = `hermes plugins install ${PLUGIN_SOURCE} --ref ${HERMES_PLUGIN_SHA} --enable`;
|
|
15
|
+
// Anchored to --ref: another rejected flag would fail the clone path too.
|
|
16
|
+
const REF_UNSUPPORTED_PATTERN = /unrecognized arguments:[^\n]*--ref/;
|
|
17
|
+
function installSpawnEnv(home, apiKey) {
|
|
18
|
+
// Hermes' install-time requires_env check reads os.environ, not our .env seed.
|
|
19
|
+
return { ...process.env, HERMES_HOME: home, TINYFISH_API_KEY: apiKey };
|
|
20
|
+
}
|
|
21
|
+
function runPluginInstall(source, env) {
|
|
22
|
+
// Captured even under --verbose: the fallback matches on stderr.
|
|
23
|
+
return spawn.sync('hermes', ['plugins', 'install', ...source, '--enable', '--force'], {
|
|
24
|
+
encoding: 'utf8',
|
|
25
|
+
maxBuffer: STEP_MAX_BUFFER,
|
|
26
|
+
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
27
|
+
env,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function refUnsupported(result) {
|
|
31
|
+
return result.status !== 0 && REF_UNSUPPORTED_PATTERN.test(String(result.stderr ?? ''));
|
|
32
|
+
}
|
|
33
|
+
function runGit(args) {
|
|
34
|
+
const result = spawn.sync('git', args, {
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
maxBuffer: STEP_MAX_BUFFER,
|
|
37
|
+
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
38
|
+
});
|
|
39
|
+
if (!result.error && result.status === 0)
|
|
40
|
+
return;
|
|
41
|
+
if (commandNotFound(result.error)) {
|
|
42
|
+
throw new ConnectStepError('git is required to stage the pinned plugin; install git and run connect again', 'command_not_found');
|
|
43
|
+
}
|
|
44
|
+
// Built first: its interrupt check must run before any replay.
|
|
45
|
+
const error = spawnStepError('Could not stage the TinyFish Hermes plugin', result);
|
|
46
|
+
replay(capturedOutput(result));
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
// Pre-`--ref` Hermes: stage the pin ourselves, mirroring `--ref` internals.
|
|
50
|
+
function installFromClone(env) {
|
|
51
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tinyfish-hermes-plugin-'));
|
|
52
|
+
try {
|
|
53
|
+
runGit(['clone', '--depth', '1', PLUGIN_REPO_URL, tmp]);
|
|
54
|
+
runGit(['-C', tmp, 'fetch', '--depth', '1', 'origin', HERMES_PLUGIN_SHA]);
|
|
55
|
+
runGit(['-C', tmp, 'checkout', '--detach', HERMES_PLUGIN_SHA]);
|
|
56
|
+
return runPluginInstall([`${pathToFileURL(tmp).href}#hermes`], env);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function installHermesPlugin(home, apiKey, { verbose }) {
|
|
63
|
+
errLine('Installing the TinyFish web plugin in Hermes...');
|
|
64
|
+
const env = installSpawnEnv(home, apiKey);
|
|
65
|
+
// --ref always: a same-source reinstall without it keeps the previous pin.
|
|
66
|
+
const primary = runPluginInstall([PLUGIN_SOURCE, '--ref', HERMES_PLUGIN_SHA], env);
|
|
67
|
+
const result = refUnsupported(primary) ? installFromClone(env) : primary;
|
|
68
|
+
if (result.error || result.status !== 0) {
|
|
69
|
+
// A declined security scan exits 1 here, and reconnecting repeats it.
|
|
70
|
+
const error = spawnStepError(`Could not install the TinyFish web plugin in Hermes; to install it yourself run: ${HERMES_PLUGIN_MANUAL_INSTALL}`, result);
|
|
71
|
+
replay(capturedOutput(result));
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if (verbose)
|
|
75
|
+
replay(capturedOutput(result));
|
|
76
|
+
}
|
|
77
|
+
function hermesConfig(args, home) {
|
|
78
|
+
return spawn.sync('hermes', ['config', ...args], {
|
|
79
|
+
encoding: 'utf8',
|
|
80
|
+
maxBuffer: STEP_MAX_BUFFER,
|
|
81
|
+
timeout: HARNESS_PROBE_TIMEOUT_MS,
|
|
82
|
+
env: { ...process.env, HERMES_HOME: home },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function configWrite(home, args) {
|
|
86
|
+
const result = hermesConfig(args, home);
|
|
87
|
+
if (!result.error && result.status === 0)
|
|
88
|
+
return;
|
|
89
|
+
// Built first: its interrupt check must run before any replay.
|
|
90
|
+
const error = spawnStepError(`Could not run \`hermes config ${args.join(' ')}\``, result);
|
|
91
|
+
replay(capturedOutput(result));
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
const HERMES_BACKEND_KEYS = ['web.search_backend', 'web.extract_backend'];
|
|
95
|
+
/** After install only: backends must never name an absent provider. */
|
|
96
|
+
export function setHermesWebBackends(home) {
|
|
97
|
+
for (const key of HERMES_BACKEND_KEYS)
|
|
98
|
+
configWrite(home, ['set', key, 'tinyfish']);
|
|
99
|
+
}
|
|
@@ -13,6 +13,12 @@ import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMo
|
|
|
13
13
|
// A list, verified against codex 0.146. A name-keyed map was accepted here too, but every
|
|
14
14
|
// field is optional, so that arm parsed *any* object-of-objects: a wrapper like
|
|
15
15
|
// `{"servers": {...}}` decoded as one entry named `servers` and reported TinyFish absent.
|
|
16
|
+
// Codex's wire values, verified against 0.146; anything else stays unknown.
|
|
17
|
+
var CodexAuthStatus;
|
|
18
|
+
(function (CodexAuthStatus) {
|
|
19
|
+
CodexAuthStatus["OAuth"] = "o_auth";
|
|
20
|
+
CodexAuthStatus["NotLoggedIn"] = "not_logged_in";
|
|
21
|
+
})(CodexAuthStatus || (CodexAuthStatus = {}));
|
|
16
22
|
const codexEntrySchema = z.looseObject({
|
|
17
23
|
name: z.string().optional(),
|
|
18
24
|
auth_status: z.string().optional(),
|
|
@@ -216,9 +222,9 @@ export function codexOauthCompleted() {
|
|
|
216
222
|
if ('reason' in read)
|
|
217
223
|
return undefined;
|
|
218
224
|
const status = read.entry?.auth_status;
|
|
219
|
-
if (status ===
|
|
225
|
+
if (status === CodexAuthStatus.OAuth)
|
|
220
226
|
return true;
|
|
221
|
-
if (status ===
|
|
227
|
+
if (status === CodexAuthStatus.NotLoggedIn)
|
|
222
228
|
return false;
|
|
223
229
|
return undefined;
|
|
224
230
|
}
|
|
@@ -255,10 +261,16 @@ function probeCodex() {
|
|
|
255
261
|
const codexUrl = entry.transport?.url;
|
|
256
262
|
// Read from this entry: codex emits `bearer_token_env_var` on every HTTP server, so testing
|
|
257
263
|
// the whole payload reports api-key for TinyFish because some other server carries a key.
|
|
258
|
-
const envVar = entry.auth_status ===
|
|
264
|
+
const envVar = entry.auth_status === CodexAuthStatus.OAuth ? undefined : entry.transport?.bearer_token_env_var;
|
|
259
265
|
return {
|
|
260
266
|
registered: Registered.Yes,
|
|
261
|
-
authMode: entry.auth_status ===
|
|
267
|
+
authMode: entry.auth_status === CodexAuthStatus.OAuth
|
|
268
|
+
? AuthMode.OAuth
|
|
269
|
+
: envVar
|
|
270
|
+
? AuthMode.ApiKey
|
|
271
|
+
: AuthMode.Unknown,
|
|
272
|
+
// Codex's own verdict: a stale `oauth_resource` strands installs here.
|
|
273
|
+
...(entry.auth_status === CodexAuthStatus.NotLoggedIn && !envVar ? { connected: false } : {}),
|
|
262
274
|
...(codexUrl ? { registeredUrl: codexUrl } : {}),
|
|
263
275
|
...(envVar ? envKeyVerdict(envVar) : {}),
|
|
264
276
|
};
|