@tiny-fish/cli 0.48.2-next.386 → 0.48.2
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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], {
|