@tiny-fish/cli 0.47.3-next.378 → 0.47.3-next.380
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/lib/config/connect-config-file.d.ts +3 -2
- package/dist/lib/config/connect-config-file.js +45 -26
- package/dist/lib/connect-all.js +1 -8
- package/dist/lib/connect-runtime.d.ts +3 -0
- package/dist/lib/connect-runtime.js +1 -0
- package/dist/lib/doctor-checks.d.ts +1 -1
- package/dist/lib/doctor-checks.js +4 -3
- package/dist/lib/doctor-report.d.ts +1 -0
- package/dist/lib/doctor-report.js +10 -1
- package/dist/lib/harness/harness-detect.d.ts +2 -0
- package/dist/lib/harness/harness-detect.js +7 -0
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ConnectAuthMode } from '../connect-runtime.js';
|
|
2
2
|
import { cursorMcpPath, writeCursorMcpConfig } from './cursor-config.js';
|
|
3
3
|
import { ompMcpPath, writeOmpMcpConfig } from './omp-config.js';
|
|
4
|
-
import { piMcpPath, writePiMcpConfig } from './pi-config.js';
|
|
5
|
-
declare function piPostConnectNotes(): string | undefined;
|
|
4
|
+
import { piMcpAdapterState, type PiAdapterState, piMcpPath, writePiMcpConfig } from './pi-config.js';
|
|
5
|
+
declare function piPostConnectNotes(adapterState?: PiAdapterState): string | undefined;
|
|
6
6
|
declare const CONFIG_FILE_HARNESSES: {
|
|
7
7
|
cursor: {
|
|
8
8
|
skillTarget: {
|
|
@@ -44,6 +44,7 @@ declare const CONFIG_FILE_HARNESSES: {
|
|
|
44
44
|
write: typeof writePiMcpConfig;
|
|
45
45
|
configPath: typeof piMcpPath;
|
|
46
46
|
tryLaunch: () => false;
|
|
47
|
+
adapterState: typeof piMcpAdapterState;
|
|
47
48
|
postConnectNote: typeof piPostConnectNotes;
|
|
48
49
|
copy: {
|
|
49
50
|
verified: string;
|
|
@@ -7,7 +7,7 @@ import { ConnectStepError, createConnectTelemetry, runGuarded, settle, } from '.
|
|
|
7
7
|
import { cursorInstallDeeplink, cursorMcpPath, readCursorTinyfishEntry, writeCursorMcpConfig, } from './cursor-config.js';
|
|
8
8
|
import { ompMcpPath, readOmpTinyfishEntry, writeOmpMcpConfig } from './omp-config.js';
|
|
9
9
|
import { PI_ADAPTER_INSTALL_COMMAND, piMcpAdapterState, piSkillDirMismatch, piMcpPath, readPiTinyfishEntry, writePiMcpConfig, } from './pi-config.js';
|
|
10
|
-
import { AuthMode, HARNESS_DISPLAY_NAMES } from '../harness/harness-detect.js';
|
|
10
|
+
import { AuthMode, detectInstalledHarnesses, HARNESS_COMMANDS, HARNESS_DISPLAY_NAMES, isStaleConfig, } from '../harness/harness-detect.js';
|
|
11
11
|
import { detectHumanInitiated } from '../harness.js';
|
|
12
12
|
import { errLine } from '../output.js';
|
|
13
13
|
import { verifyMcpAuth } from '../verify.js';
|
|
@@ -21,15 +21,14 @@ const PI_ADAPTER_DORMANT_NOTE = 'Note: pi has no built-in MCP support, so the en
|
|
|
21
21
|
// Only the dormant case was spoken for, leaving a working MCP install silent.
|
|
22
22
|
const PI_ADAPTER_LIVE_NOTE = 'pi-mcp-adapter is installed, so pi loads the TinyFish MCP tools as well as the skill once ' +
|
|
23
23
|
'you restart it.';
|
|
24
|
-
function piAdapterNote() {
|
|
25
|
-
const state = piMcpAdapterState();
|
|
24
|
+
function piAdapterNote(state) {
|
|
26
25
|
if (state === 'installed')
|
|
27
26
|
return PI_ADAPTER_LIVE_NOTE;
|
|
28
27
|
return state === 'absent' ? PI_ADAPTER_DORMANT_NOTE : undefined;
|
|
29
28
|
}
|
|
30
29
|
// The `skills` CLI writes to the default dir even when pi reads another one.
|
|
31
|
-
function piPostConnectNotes() {
|
|
32
|
-
const notes = [piAdapterNote()];
|
|
30
|
+
function piPostConnectNotes(adapterState) {
|
|
31
|
+
const notes = [piAdapterNote(adapterState)];
|
|
33
32
|
const skills = piSkillDirMismatch();
|
|
34
33
|
if (skills) {
|
|
35
34
|
notes.push(`Note: PI_CODING_AGENT_DIR points pi at ${skills.readFrom}, but the TinyFish skill ` +
|
|
@@ -79,6 +78,7 @@ const CONFIG_FILE_HARNESSES = {
|
|
|
79
78
|
configPath: piMcpPath,
|
|
80
79
|
// No install deeplink exists; the copy carries the restart instead.
|
|
81
80
|
tryLaunch: () => false,
|
|
81
|
+
adapterState: piMcpAdapterState,
|
|
82
82
|
postConnectNote: piPostConnectNotes,
|
|
83
83
|
copy: {
|
|
84
84
|
verified: "TinyFish is connected and verified (health+auth). Restart pi and it's live.",
|
|
@@ -111,6 +111,36 @@ function reportPostInstallFailure(harness, failed, onPostInstallFailed) {
|
|
|
111
111
|
errLine(finishSetupHint(harness));
|
|
112
112
|
onPostInstallFailed?.();
|
|
113
113
|
}
|
|
114
|
+
// A dormant config written here reads as `completed` to every dashboard.
|
|
115
|
+
function requireHarnessInstalled(harness) {
|
|
116
|
+
const detection = detectInstalledHarnesses().find((d) => d.harness === harness);
|
|
117
|
+
if (detection?.detected && !isStaleConfig(detection))
|
|
118
|
+
return;
|
|
119
|
+
throw new ConnectStepError(`${HARNESS_DISPLAY_NAMES[harness]} is not installed (no \`${HARNESS_COMMANDS[harness]}\` on PATH). ` +
|
|
120
|
+
`Install it, then re-run: tinyfish connect ${harness}`, 'harness_not_installed');
|
|
121
|
+
}
|
|
122
|
+
/** The copy after `completed`: what the user does next depends on the auth path. */
|
|
123
|
+
async function printConnectedCopy(spec, displayName, opts) {
|
|
124
|
+
if (opts.keyless) {
|
|
125
|
+
errLine(`TinyFish keyless Search is connected in ${displayName}.`);
|
|
126
|
+
}
|
|
127
|
+
else if (opts.resolvedKey) {
|
|
128
|
+
// Deeplink would embed the key in a URL (process args, LaunchServices logs) — reload instead.
|
|
129
|
+
const verify = await verifyMcpAuth(opts.resolvedKey, apiBaseFromMcpUrl(opts.apiMcpUrl));
|
|
130
|
+
if (verify.ok) {
|
|
131
|
+
errLine(spec.copy.verified);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
errLine(spec.copy.authFailed(verify.reason ?? 'unknown'));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (detectHumanInitiated() && spec.tryLaunch(opts.mcpUrl.toString())) {
|
|
138
|
+
errLine(spec.copy.launched);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
errLine(spec.copy.reload);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
114
144
|
/** Writes the harness's MCP config file itself; no `mcp add` exists. */
|
|
115
145
|
export async function connectConfigFileHarness(harness, options) {
|
|
116
146
|
const spec = CONFIG_FILE_HARNESSES[harness];
|
|
@@ -154,6 +184,7 @@ export async function connectConfigFileHarness(harness, options) {
|
|
|
154
184
|
};
|
|
155
185
|
await runGuarded(state, telemetry, async () => {
|
|
156
186
|
telemetry.track('started');
|
|
187
|
+
requireHarnessInstalled(harness);
|
|
157
188
|
telemetry.track('checkpoint', { phase: 'prerequisite_ok' });
|
|
158
189
|
const resolveAuth = async () => {
|
|
159
190
|
let resolvedKey = validatedApiKey(options.apiKey);
|
|
@@ -202,28 +233,16 @@ export async function connectConfigFileHarness(harness, options) {
|
|
|
202
233
|
saveConnectContext(harness, telemetry.attemptId, AuthMode.Keyless);
|
|
203
234
|
else
|
|
204
235
|
saveConnectContext(harness, telemetry.attemptId);
|
|
205
|
-
|
|
236
|
+
const piAdapterState = spec.adapterState?.();
|
|
237
|
+
settle(state, telemetry, 'completed', { authMode, piAdapterState });
|
|
206
238
|
const postInstallFailed = installSkillPostSettle(spec, displayName, state, telemetry, options);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
errLine(spec.copy.verified);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
errLine(spec.copy.authFailed(verify.reason ?? 'unknown'));
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
else if (detectHumanInitiated() && spec.tryLaunch(mcpUrl.toString())) {
|
|
221
|
-
errLine(spec.copy.launched);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
errLine(spec.copy.reload);
|
|
225
|
-
}
|
|
226
|
-
const note = spec.postConnectNote?.();
|
|
239
|
+
await printConnectedCopy(spec, displayName, {
|
|
240
|
+
keyless,
|
|
241
|
+
resolvedKey,
|
|
242
|
+
mcpUrl,
|
|
243
|
+
apiMcpUrl: options.mcpUrl,
|
|
244
|
+
});
|
|
245
|
+
const note = spec.postConnectNote?.(piAdapterState);
|
|
227
246
|
if (note)
|
|
228
247
|
errLine(note);
|
|
229
248
|
reportPostInstallFailure(harness, postInstallFailed, options.onPostInstallFailed);
|
package/dist/lib/connect-all.js
CHANGED
|
@@ -12,7 +12,7 @@ import { KEYLESS_ONBOARDING_PROMPT } from './connect-clients.js';
|
|
|
12
12
|
import { gateApiKey } from './connect-preflight.js';
|
|
13
13
|
import { ConnectInterruptedError } from './connect-runtime.js';
|
|
14
14
|
import { pickHarnesses } from './connect-picker.js';
|
|
15
|
-
import { commandOnPath, detectInstalledHarnesses, AuthMode, HARNESS_COMMANDS, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, ALL_HARNESSES, } from './harness/harness-detect.js';
|
|
15
|
+
import { commandOnPath, detectInstalledHarnesses, isStaleConfig, AuthMode, HARNESS_COMMANDS, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, ALL_HARNESSES, } from './harness/harness-detect.js';
|
|
16
16
|
import { detectHumanInitiated } from './harness.js';
|
|
17
17
|
import { emitNotice } from './notice.js';
|
|
18
18
|
import { errLine, outLine, setErrIndent } from './output.js';
|
|
@@ -138,13 +138,6 @@ function noWorkOutcome(d, uninstall) {
|
|
|
138
138
|
export function needsFirstRun(detection) {
|
|
139
139
|
return detection.detected && !detection.configDirExists;
|
|
140
140
|
}
|
|
141
|
-
/** A config-writing connect needs no binary; every other harness spawns one. */
|
|
142
|
-
function isStaleConfig(detection) {
|
|
143
|
-
const spec = harnessSpec(detection.harness);
|
|
144
|
-
// Cursor's IDE reads mcp.json binary-less; omp and pi have no second reader.
|
|
145
|
-
const binaryless = spec.cliWritesConfig && spec.readableWithoutBinary;
|
|
146
|
-
return detection.detected && !detection.binaryOnPath && !binaryless;
|
|
147
|
-
}
|
|
148
141
|
async function resolvePick(detections, opts, prompt) {
|
|
149
142
|
let deselected = new Set();
|
|
150
143
|
let mode = 'all';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthMode } from './harness/harness-detect.js';
|
|
2
2
|
import type { Harness, HarnessSupportCheck } from './harness/harness-spec.js';
|
|
3
|
+
import type { PiAdapterState } from './config/pi-config.js';
|
|
3
4
|
export declare const NON_INTERACTIVE_TIMEOUT_MS = 10000;
|
|
4
5
|
export type AgentClient = Harness;
|
|
5
6
|
type ConnectStage = 'started' | 'checkpoint' | 'completed' | 'failed' | 'aborted' | 'post_install_failed';
|
|
@@ -75,6 +76,8 @@ interface ConnectStageDetail {
|
|
|
75
76
|
stageDurationMs?: number;
|
|
76
77
|
/** Dropped from the wire unless `failedStage` is set (route refine). */
|
|
77
78
|
failureDetail?: string;
|
|
79
|
+
/** pi only: without the adapter its MCP entry is dormant. */
|
|
80
|
+
piAdapterState?: PiAdapterState;
|
|
78
81
|
}
|
|
79
82
|
export interface ConnectTelemetry {
|
|
80
83
|
attemptId: string;
|
|
@@ -325,6 +325,7 @@ export function createConnectTelemetry(mcpUrl, client, opts) {
|
|
|
325
325
|
plugin_version: detail?.pluginVersion,
|
|
326
326
|
auth_mode: detail?.authMode,
|
|
327
327
|
harness_degraded: detail?.harnessDegraded,
|
|
328
|
+
pi_adapter_state: detail?.piAdapterState,
|
|
328
329
|
stage_duration_ms: stageDurationMs,
|
|
329
330
|
runtime_platform: process.platform,
|
|
330
331
|
node_version: process.version,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DoctorCheck } from './doctor-report.js';
|
|
2
2
|
import type { RegistrationStatus } from './registration-detect.js';
|
|
3
3
|
import { type VerifyResult } from './verify.js';
|
|
4
4
|
export declare function provesHarnessReach(status: RegistrationStatus, keyAuth: VerifyResult | undefined, healthOk: boolean, mcpUrl: string): boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiKeyStatus } from './auth.js';
|
|
2
2
|
import { CLI_VERSION } from './constants.js';
|
|
3
|
+
import { NOT_INSTALLED_DETAIL } from './doctor-report.js';
|
|
3
4
|
import { AuthMode, Registered } from './harness/harness-detect.js';
|
|
4
5
|
import { resolveHermesHome } from './hermes-env.js';
|
|
5
6
|
import { hermesWebBackendsOurs, readHermesPluginStatus, } from './hermes-plugin.js';
|
|
@@ -100,7 +101,7 @@ function keyedVerdict(status, keyAuth, cliKeyMissing) {
|
|
|
100
101
|
}
|
|
101
102
|
function registrationVerdict(status, mcpUrl, keyAuth, cliKeyMissing) {
|
|
102
103
|
if (!status.detected)
|
|
103
|
-
return { status: 'skip', detail:
|
|
104
|
+
return { status: 'skip', detail: NOT_INSTALLED_DETAIL };
|
|
104
105
|
if (status.registered === Registered.Unknown) {
|
|
105
106
|
return { status: 'warn', detail: status.reason ?? 'could not determine registration' };
|
|
106
107
|
}
|
|
@@ -260,7 +261,7 @@ export function checkPiAdapter(status) {
|
|
|
260
261
|
scope: 'harness',
|
|
261
262
|
};
|
|
262
263
|
if (!status.detected)
|
|
263
|
-
return { ...base, status: 'skip', detail:
|
|
264
|
+
return { ...base, status: 'skip', detail: NOT_INSTALLED_DETAIL };
|
|
264
265
|
if (status.registered !== Registered.Yes) {
|
|
265
266
|
const why = status.registered === Registered.Unknown
|
|
266
267
|
? 'pi registration could not be determined'
|
|
@@ -293,7 +294,7 @@ export function checkHermesPlugin(status) {
|
|
|
293
294
|
scope: 'harness',
|
|
294
295
|
};
|
|
295
296
|
if (!status.detected) {
|
|
296
|
-
return { check: { ...base, status: 'skip', detail:
|
|
297
|
+
return { check: { ...base, status: 'skip', detail: NOT_INSTALLED_DETAIL } };
|
|
297
298
|
}
|
|
298
299
|
if (status.registered !== Registered.Yes) {
|
|
299
300
|
return { check: { ...base, status: 'skip', detail: 'TinyFish is not registered in Hermes' } };
|
|
@@ -219,5 +219,6 @@ export declare function verdictFor(checks: DoctorCheck[]): {
|
|
|
219
219
|
};
|
|
220
220
|
/** Harness scope only: the caller asked about its agent, not us. */
|
|
221
221
|
export declare function exitCodeFor(checks: DoctorCheck[]): 0 | 1;
|
|
222
|
+
export declare const NOT_INSTALLED_DETAIL = "harness not installed";
|
|
222
223
|
export declare function renderPretty(report: DoctorReport): string;
|
|
223
224
|
export {};
|
|
@@ -95,8 +95,17 @@ function footnotes(report) {
|
|
|
95
95
|
}
|
|
96
96
|
return notes;
|
|
97
97
|
}
|
|
98
|
+
export const NOT_INSTALLED_DETAIL = 'harness not installed';
|
|
99
|
+
const isNotInstalled = (c) => c.status === 'skip' && c.detail === NOT_INSTALLED_DETAIL;
|
|
98
100
|
export function renderPretty(report) {
|
|
99
|
-
const lines = report.checks
|
|
101
|
+
const lines = report.checks
|
|
102
|
+
.filter((c) => !isNotInstalled(c))
|
|
103
|
+
.map((c) => `${GLYPHS[c.status]} ${c.title}${c.detail ? ` — ${c.detail}` : ''}`);
|
|
104
|
+
// Per-harness skip rows read as a to-do list; agents ran `connect` on each.
|
|
105
|
+
const absent = new Set(report.checks.filter(isNotInstalled).map((c) => c.harness));
|
|
106
|
+
if (absent.size > 0) {
|
|
107
|
+
lines.push(`${GLYPHS.skip} Not installed, nothing to do: ${[...absent].join(', ')}`);
|
|
108
|
+
}
|
|
100
109
|
if (report.repairs.length > 0) {
|
|
101
110
|
lines.push('', 'Repairs:');
|
|
102
111
|
for (const repair of report.repairs)
|
|
@@ -28,3 +28,5 @@ export interface HarnessDetection {
|
|
|
28
28
|
configDirExists: boolean;
|
|
29
29
|
}
|
|
30
30
|
export declare function detectInstalledHarnesses(): HarnessDetection[];
|
|
31
|
+
/** A config-writing connect needs no binary; every other harness spawns one. */
|
|
32
|
+
export declare function isStaleConfig(detection: HarnessDetection): boolean;
|
|
@@ -71,3 +71,10 @@ export function detectInstalledHarnesses() {
|
|
|
71
71
|
};
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
+
/** A config-writing connect needs no binary; every other harness spawns one. */
|
|
75
|
+
export function isStaleConfig(detection) {
|
|
76
|
+
const spec = harnessSpec(detection.harness);
|
|
77
|
+
// Cursor's IDE reads mcp.json binary-less; omp and pi have no second reader.
|
|
78
|
+
const binaryless = spec.cliWritesConfig && spec.readableWithoutBinary;
|
|
79
|
+
return detection.detected && !detection.binaryOnPath && !binaryless;
|
|
80
|
+
}
|