@tiny-fish/cli 0.48.2-next.386 → 0.48.2-next.388

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.
@@ -22,7 +22,7 @@ export function planText(harness, mcpUrl, apiKey) {
22
22
  return planCommandCodeWrite(mcpUrl, apiKey);
23
23
  if (harness === 'hermes' && apiKey) {
24
24
  return (`would run \`tinyfish connect hermes\` (harness-owned MCP write; the CLI writes ` +
25
- `${HERMES_KEY_VAR} to Hermes' .env, installs the tinyfish plugin from npm @latest, ` +
25
+ `${HERMES_KEY_VAR} to Hermes' .env, installs the tinyfish plugin from the Hermes catalog, ` +
26
26
  `and points its web backends at tinyfish)`);
27
27
  }
28
28
  if (harness === 'hermes') {
@@ -67,7 +67,7 @@ interface ConnectStageDetail {
67
67
  phase?: ConnectCheckpoint;
68
68
  failureReason?: ConnectFailureReason;
69
69
  harnessVersion?: string;
70
- /** The plugin the client pins, not a probe of what landed. */
70
+ /** Probed after install; absent when the probe cannot answer. */
71
71
  pluginVersion?: string;
72
72
  authMode?: ConnectAuthMode;
73
73
  /** Orthogonal to `authMode`: a stored key wins over `mcp login` on a healthy client too. */
@@ -1,7 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { type InstallOptions } from './cli-install.js';
3
- export declare const HERMES_PLUGIN_PACKAGE = "@tiny-fish/hermes";
4
- export declare const HERMES_PLUGIN_MANUAL_INSTALL = "hermes plugins install tinyfish-io/tinyfish-web-agent-integrations/hermes --enable";
3
+ export declare const HERMES_PLUGIN_MANUAL_INSTALL = "hermes plugins install tinyfish --enable";
5
4
  export declare function installHermesPlugin(home: string, apiKey: string, { verbose }: Pick<InstallOptions, 'verbose'>): string | undefined;
6
5
  /** Absent `enabled` reads as enabled, so never write this field-by-field. */
7
6
  export declare function writeHermesMcpEntry(home: string, mcpUrl: string, mode?: 'api-key' | 'keyless'): void;
@@ -1,7 +1,3 @@
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
1
  import spawn from 'cross-spawn';
6
2
  import { z } from 'zod';
7
3
  import { capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from './cli-install.js';
@@ -9,131 +5,35 @@ import { commandNotFound, ConnectStepError, spawnStepError, throwIfInterrupted,
9
5
  import { HARNESS_PROBE_TIMEOUT_MS, TINYFISH_ACCESS_MODE_HEADER, TINYFISH_KEYLESS_ACCESS_MODE, } from './constants.js';
10
6
  import { HERMES_HEADER_TEMPLATE, HERMES_MCP_SERVER_KEY } from './config/hermes-config.js';
11
7
  import { errLine, parseJson } from './output.js';
12
- // Resolved at connect time, so plugin releases need no CLI release.
13
- export const HERMES_PLUGIN_PACKAGE = '@tiny-fish/hermes';
14
- // Anchored to --ref: an older Hermes rejects the flag and needs the plain URL.
15
- const REF_UNSUPPORTED_PATTERN = /unrecognized arguments:[^\n]*--ref/;
16
- // Tracks the repo head; npm @latest can trail a merge by one publish run.
17
- export const HERMES_PLUGIN_MANUAL_INSTALL = 'hermes plugins install tinyfish-io/tinyfish-web-agent-integrations/hermes --enable';
18
- function installSpawnEnv(home, apiKey) {
19
- // Hermes' install-time requires_env check reads os.environ, not our .env seed.
20
- return { ...process.env, HERMES_HOME: home, TINYFISH_API_KEY: apiKey };
21
- }
22
- function runPluginInstall(source, env) {
23
- // Captured even under --verbose: the fallback matches on stderr.
24
- return spawn.sync('hermes', ['plugins', 'install', ...source, '--enable', '--force'], {
25
- encoding: 'utf8',
26
- maxBuffer: STEP_MAX_BUFFER,
27
- timeout: SKILL_INSTALL_TIMEOUT_MS,
28
- env,
29
- });
30
- }
31
- function refUnsupported(result) {
32
- return result.status !== 0 && REF_UNSUPPORTED_PATTERN.test(String(result.stderr ?? ''));
33
- }
34
- function runGit(args) {
35
- const result = spawn.sync('git', args, {
8
+ export const HERMES_PLUGIN_MANUAL_INSTALL = 'hermes plugins install tinyfish --enable';
9
+ // Bare catalog names install only from this Hermes release onward.
10
+ const HERMES_CATALOG_MIN_VERSION = 'v2026.9.11';
11
+ export function installHermesPlugin(home, apiKey, { verbose }) {
12
+ errLine('Installing the TinyFish web plugin in Hermes...');
13
+ const result = spawn.sync('hermes', ['plugins', 'install', 'tinyfish', '--enable', '--force'], {
36
14
  encoding: 'utf8',
37
15
  maxBuffer: STEP_MAX_BUFFER,
38
16
  timeout: SKILL_INSTALL_TIMEOUT_MS,
17
+ // Hermes' install-time requires_env check reads os.environ, not our .env seed.
18
+ env: { ...process.env, HERMES_HOME: home, TINYFISH_API_KEY: apiKey },
39
19
  });
40
- if (!result.error && result.status === 0)
41
- return result;
42
- if (commandNotFound(result.error)) {
43
- throw new ConnectStepError('git is required to stage the plugin for Hermes; install git and run connect again', 'command_not_found');
44
- }
45
- // Built first: its interrupt check must run before any replay.
46
- const error = spawnStepError('Could not stage the TinyFish Hermes plugin', result);
47
- replay(capturedOutput(result));
48
- throw error;
49
- }
50
- function runNpmDownload(tmp) {
51
- // --prefix both downloads and extracts, so no tar dependency anywhere.
52
- const result = spawn.sync('npm', [
53
- 'install',
54
- `${HERMES_PLUGIN_PACKAGE}@latest`,
55
- '--prefix',
56
- tmp,
57
- // Lifecycle scripts would run arbitrary code with the key in env.
58
- '--ignore-scripts',
59
- '--no-save',
60
- '--no-audit',
61
- '--no-fund',
62
- '--loglevel=error',
63
- ], { encoding: 'utf8', maxBuffer: STEP_MAX_BUFFER, timeout: SKILL_INSTALL_TIMEOUT_MS });
64
- if (!result.error && result.status === 0)
65
- return;
66
- if (commandNotFound(result.error)) {
67
- throw new ConnectStepError(`npm is required to download the TinyFish Hermes plugin; to install it yourself run: ${HERMES_PLUGIN_MANUAL_INSTALL}`, 'command_not_found');
68
- }
69
- // Built first: its interrupt check must run before any replay.
70
- const error = spawnStepError(`Could not download ${HERMES_PLUGIN_PACKAGE} from npm; to install it yourself run: ${HERMES_PLUGIN_MANUAL_INSTALL}`, result);
71
- replay(capturedOutput(result));
72
- throw error;
73
- }
74
- function stagedPluginVersion(dir) {
75
- try {
76
- const parsed = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
77
- const version = parsed.version;
78
- return typeof version === 'string' && version ? version : undefined;
79
- }
80
- catch {
81
- return undefined;
20
+ if (result.error || result.status !== 0) {
21
+ // A declined security scan exits 1 here, and reconnecting repeats it.
22
+ const error = spawnStepError(`Could not install the TinyFish web plugin in Hermes; to install it yourself run: ` +
23
+ `${HERMES_PLUGIN_MANUAL_INSTALL} (Hermes older than ${HERMES_CATALOG_MIN_VERSION} needs \`hermes update\` first)`, result);
24
+ replay(capturedOutput(result));
25
+ throw error;
82
26
  }
83
- }
84
- // Hermes installs plugins only by git clone, so the payload gets a throwaway repo.
85
- function stagePluginFromNpm(tmp) {
86
- runNpmDownload(tmp);
87
- const dir = path.join(tmp, 'node_modules', ...HERMES_PLUGIN_PACKAGE.split('/'));
88
- // Empty hooksPath: a global hooks dir or init template must not run here.
89
- const hooksDir = path.join(tmp, 'git-hooks');
90
- fs.mkdirSync(hooksDir);
91
- runGit(['-C', dir, 'init', '--quiet']);
92
- runGit(['-C', dir, 'add', '-A']);
93
- // Inline identity: fresh machines have no git config and commit fails without it.
94
- runGit([
95
- '-C',
96
- dir,
97
- '-c',
98
- `core.hooksPath=${hooksDir}`,
99
- '-c',
100
- 'user.name=tinyfish-cli',
101
- '-c',
102
- 'user.email=support@tinyfish.io',
103
- 'commit',
104
- '--quiet',
105
- // A global commit.gpgSign would summon a signer this commit must not need.
106
- '--no-gpg-sign',
107
- '-m',
108
- 'stage @tiny-fish/hermes for install',
109
- ]);
110
- const sha = String(runGit(['-C', dir, 'rev-parse', 'HEAD']).stdout ?? '').trim();
111
- return { dir, sha, version: stagedPluginVersion(dir) };
112
- }
113
- export function installHermesPlugin(home, apiKey, { verbose }) {
114
- errLine('Installing the TinyFish web plugin in Hermes...');
115
- const env = installSpawnEnv(home, apiKey);
116
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'tinyfish-hermes-plugin-'));
117
- try {
118
- const staged = stagePluginFromNpm(tmp);
119
- const url = pathToFileURL(staged.dir).href;
120
- // --ref: a prior pinned install rejects an unpinned reinstall of the same name.
121
- const primary = runPluginInstall([url, '--ref', staged.sha], env);
122
- // A pre---ref Hermes never pins, so the plain URL is safe there.
123
- const result = refUnsupported(primary) ? runPluginInstall([url], env) : primary;
124
- if (result.error || result.status !== 0) {
125
- // A declined security scan exits 1 here, and reconnecting repeats it.
126
- const error = spawnStepError(`Could not install the TinyFish web plugin in Hermes; to install it yourself run: ${HERMES_PLUGIN_MANUAL_INSTALL}`, result);
127
- replay(capturedOutput(result));
128
- throw error;
129
- }
130
- if (verbose)
27
+ if (verbose)
28
+ replay(capturedOutput(result));
29
+ const probe = readHermesPluginStatus(home);
30
+ // A refused enable exits 0; backends need a present provider.
31
+ if (probe.state === 'not_installed') {
32
+ if (!verbose)
131
33
  replay(capturedOutput(result));
132
- return staged.version;
133
- }
134
- finally {
135
- fs.rmSync(tmp, { recursive: true, force: true });
34
+ throw new ConnectStepError('Hermes installed the TinyFish web plugin but did not enable it; run: hermes plugins enable tinyfish', 'invalid_config');
136
35
  }
36
+ return probe.state === 'ok' ? (probe.status.plugin_version ?? undefined) : undefined;
137
37
  }
138
38
  function hermesConfig(args, home) {
139
39
  return spawn.sync('hermes', ['config', ...args], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.48.2-next.386",
3
+ "version": "0.48.2-next.388",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {