@tiny-fish/cli 0.34.1-next.285 → 0.34.1-next.286

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.
@@ -3,6 +3,7 @@ import { NATIVE_BY_HARNESS, openclawSkillUninstall } from './connect-clients.js'
3
3
  import { ConnectInterruptedError, spawnRemoval } from './connect-runtime.js';
4
4
  import { removeCursorMcpServer, planCursorWrite } from './cursor-config.js';
5
5
  import { HERMES_KEY_VAR, hermesEnvPath, removeHermesKey, resolveHermesHome } from './hermes-env.js';
6
+ import { clearHermesWebBackends, HERMES_PLUGIN_SHA } from './hermes-plugin.js';
6
7
  import { HARNESS_DISPLAY_NAMES as DISPLAY_NAMES } from './harness-detect.js';
7
8
  import { errLine } from './output.js';
8
9
  // OpenCode ships no `mcp remove`, so the only removal is editing the config it wrote.
@@ -12,7 +13,9 @@ export function planText(harness, mcpUrl, apiKey) {
12
13
  return planCursorWrite(mcpUrl, apiKey);
13
14
  // Only the keyed path seeds the .env; a keyless Hermes install writes nothing.
14
15
  if (harness === 'hermes' && apiKey) {
15
- return `would run \`tinyfish connect hermes\` (harness-owned MCP write; the CLI writes ${HERMES_KEY_VAR} to Hermes' .env)`;
16
+ return (`would run \`tinyfish connect hermes\` (harness-owned MCP write; the CLI writes ` +
17
+ `${HERMES_KEY_VAR} to Hermes' .env, installs the tinyfish plugin at ` +
18
+ `${HERMES_PLUGIN_SHA.slice(0, 12)}, and points its web backends at tinyfish)`);
16
19
  }
17
20
  return `would run \`tinyfish connect ${harness}\` (harness-owned MCP write; no local file touched by the CLI)`;
18
21
  }
@@ -24,7 +27,9 @@ export function uninstallPlanText(harness) {
24
27
  return `would print \`${uninstallPointer(harness)}\` (no removal command exists)`;
25
28
  }
26
29
  // Hermes alone leaves a seeded key behind; `mcp remove` never clears it.
27
- const envNote = harness === 'hermes' ? ` and clear ${HERMES_KEY_VAR} from Hermes' .env` : '';
30
+ const envNote = harness === 'hermes'
31
+ ? ` and clear ${HERMES_KEY_VAR} from Hermes' .env plus its tinyfish web backends`
32
+ : '';
28
33
  return `would run \`${uninstallPointer(harness)}\`${envNote}`;
29
34
  }
30
35
  /** Undefined: no harness-CLI removal exists (opencode). */
@@ -36,7 +41,8 @@ function uninstallRuns(harness) {
36
41
  const native = NATIVE_BY_HARNESS.get(harness);
37
42
  if (!native || native.removals.length === 0)
38
43
  return undefined;
39
- return { command: native.command, runs: native.removals.map((r) => r.args) };
44
+ const extra = native.uninstallExtra ? [native.uninstallExtra.args] : [];
45
+ return { command: native.command, runs: [...native.removals.map((r) => r.args), ...extra] };
40
46
  }
41
47
  /** Derived from the same args the spawn runs, so the advice cannot drift. */
42
48
  function uninstallPointer(harness) {
@@ -48,9 +54,29 @@ function uninstallPointer(harness) {
48
54
  const joiner = process.platform === 'win32' ? ' && ' : ' ; ';
49
55
  return removal.runs.map((args) => `${removal.command} ${args.join(' ')}`).join(joiner);
50
56
  }
51
- /** `mcp remove` leaves this behind; a stale value gets silently reused. */
52
- function clearHermesEnvKey() {
57
+ /** `mcp remove` clears neither the key nor the backends we wrote. */
58
+ function clearHermesLocalState() {
53
59
  const home = resolveHermesHome();
60
+ const advisories = [clearHermesEnvKey(home), clearHermesBackends(home)].filter((advisory) => advisory !== undefined);
61
+ return advisories.length > 0 ? advisories.join(' ') : undefined;
62
+ }
63
+ /** Backends left naming an absent provider would break Hermes' web tools. */
64
+ function clearHermesBackends(home) {
65
+ // The key clearer already reported the unresolved home.
66
+ if (!home)
67
+ return undefined;
68
+ try {
69
+ clearHermesWebBackends(home);
70
+ return undefined;
71
+ }
72
+ catch (e) {
73
+ const detail = e instanceof Error ? e.message : String(e);
74
+ return (`could not unset Hermes' tinyfish web backends (${detail}). Run \`hermes config unset ` +
75
+ 'web.search_backend`, `web.extract_backend` and `web.backend` by hand.');
76
+ }
77
+ }
78
+ /** `mcp remove` leaves this behind; a stale value gets silently reused. */
79
+ function clearHermesEnvKey(home) {
54
80
  const retry = 're-run `tinyfish connect --all --uninstall`';
55
81
  if (!home) {
56
82
  return (`could not locate Hermes' home, so ${HERMES_KEY_VAR} may still be set in its .env. ` +
@@ -177,7 +203,7 @@ export function uninstallHarness(harness, configPath, verbose, binaryOnPath) {
177
203
  // Clearing a key the entry still references would break a working install.
178
204
  const removed = !result.removalFailed &&
179
205
  (result.outcome === 'uninstalled' || result.outcome === 'uninstall_noop');
180
- const advisory = removed ? clearHermesEnvKey() : hermesKeyKeptAdvisory();
206
+ const advisory = removed ? clearHermesLocalState() : hermesKeyKeptAdvisory();
181
207
  if (!advisory)
182
208
  return result;
183
209
  // Appended, never replacing: an advisory must not hide the removal's message.
@@ -74,6 +74,11 @@ interface BaseMcpClient extends SupportedCommand {
74
74
  apiKey?: string;
75
75
  verbose: boolean;
76
76
  }) => void;
77
+ /** Extra --uninstall row beyond `removals`; never runs pre-add. */
78
+ uninstallExtra?: {
79
+ args: string[];
80
+ label: string;
81
+ };
77
82
  }
78
83
  /** Keyless installs use this argv; the harness signs itself in. */
79
84
  export interface OauthCapableMcpClient extends BaseMcpClient {
@@ -7,7 +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
+ import { HERMES_PLUGIN_NAME, installHermesPlugin, setHermesWebBackends } from './hermes-plugin.js';
11
11
  const HERMES_SEED_TIMEOUT_MS = 120_000;
12
12
  // An old install is only one reason a flag can go unseen, so no message asserts the cause.
13
13
  const SUPPORT_CHECK_DEBUG_HINT = ' Set TINYFISH_DEBUG=1 and retry to print the help output TinyFish read.';
@@ -332,6 +332,10 @@ export const HERMES = {
332
332
  },
333
333
  removals: [{ args: ['mcp', 'remove', 'tinyfish'], label: 'TinyFish registration from Hermes' }],
334
334
  extraPostInstall: installHermesWebPlugin,
335
+ uninstallExtra: {
336
+ args: ['plugins', 'uninstall', HERMES_PLUGIN_NAME],
337
+ label: 'TinyFish web plugin from Hermes',
338
+ },
335
339
  launchWalkthrough: launchHermesWalkthrough,
336
340
  postConnectNote: 'Restart your Hermes session to pick up TinyFish — Hermes discovers MCP servers at startup.',
337
341
  };
@@ -1,6 +1,9 @@
1
1
  import { type InstallOptions } from './cli-install.js';
2
2
  export declare const HERMES_PLUGIN_SHA = "496cd63fefd982bbaa8a85ce78ef2270d700984f";
3
+ export declare const HERMES_PLUGIN_NAME = "tinyfish";
3
4
  export declare const HERMES_PLUGIN_MANUAL_INSTALL = "hermes plugins install tinyfish-io/tinyfish-web-agent-integrations/hermes --ref 496cd63fefd982bbaa8a85ce78ef2270d700984f --enable";
4
5
  export declare function installHermesPlugin(home: string, apiKey: string, { verbose }: Pick<InstallOptions, 'verbose'>): void;
5
6
  /** After install only: backends must never name an absent provider. */
6
7
  export declare function setHermesWebBackends(home: string): void;
8
+ /** Ours-only unsets: a user-chosen backend value survives the uninstall. */
9
+ export declare function clearHermesWebBackends(home: string): void;
@@ -4,11 +4,13 @@ import * as path from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
5
  import spawn from 'cross-spawn';
6
6
  import { capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from './cli-install.js';
7
- import { commandNotFound, ConnectStepError, spawnStepError } from './connect-runtime.js';
7
+ import { commandNotFound, ConnectStepError, spawnStepError, throwIfInterrupted, } from './connect-runtime.js';
8
8
  import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
9
9
  import { errLine } from './output.js';
10
10
  // Bump per CLI release.
11
11
  export const HERMES_PLUGIN_SHA = '496cd63fefd982bbaa8a85ce78ef2270d700984f';
12
+ // Manifest `name:`, not the repo subdir; `plugins uninstall` keys on it.
13
+ export const HERMES_PLUGIN_NAME = 'tinyfish';
12
14
  const PLUGIN_SOURCE = 'tinyfish-io/tinyfish-web-agent-integrations/hermes';
13
15
  const PLUGIN_REPO_URL = 'https://github.com/tinyfish-io/tinyfish-web-agent-integrations.git';
14
16
  export const HERMES_PLUGIN_MANUAL_INSTALL = `hermes plugins install ${PLUGIN_SOURCE} --ref ${HERMES_PLUGIN_SHA} --enable`;
@@ -92,8 +94,26 @@ function configWrite(home, args) {
92
94
  throw error;
93
95
  }
94
96
  const HERMES_BACKEND_KEYS = ['web.search_backend', 'web.extract_backend'];
97
+ // Dispatch falls back to web.backend, and the plugin's own setup can set it.
98
+ const HERMES_OWNED_BACKEND_KEYS = [...HERMES_BACKEND_KEYS, 'web.backend'];
95
99
  /** After install only: backends must never name an absent provider. */
96
100
  export function setHermesWebBackends(home) {
97
101
  for (const key of HERMES_BACKEND_KEYS)
98
102
  configWrite(home, ['set', key, 'tinyfish']);
99
103
  }
104
+ /** Unset prints empty and exits 0, so a failed read must not read as unset. */
105
+ function configGet(home, key) {
106
+ const result = hermesConfig(['get', key], home);
107
+ throwIfInterrupted(result);
108
+ if (result.error || result.status !== 0) {
109
+ throw spawnStepError(`Could not read \`hermes config get ${key}\``, result);
110
+ }
111
+ return String(result.stdout ?? '').trim() || undefined;
112
+ }
113
+ /** Ours-only unsets: a user-chosen backend value survives the uninstall. */
114
+ export function clearHermesWebBackends(home) {
115
+ for (const key of HERMES_OWNED_BACKEND_KEYS) {
116
+ if (configGet(home, key) === 'tinyfish')
117
+ configWrite(home, ['unset', key]);
118
+ }
119
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.34.1-next.285",
3
+ "version": "0.34.1-next.286",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {