@tiny-fish/cli 0.37.1-next.301 → 0.37.1-next.302
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 +1 -1
- package/dist/commands/connect.js +37 -2
- package/dist/lib/connect-all-uninstall.js +25 -7
- package/dist/lib/connect-all.js +5 -1
- package/dist/lib/connect-clients.d.ts +2 -1
- package/dist/lib/connect-clients.js +4 -0
- package/dist/lib/doctor-repairs.js +4 -3
- package/dist/lib/doctor-report.d.ts +6 -0
- package/dist/lib/harness-spec.d.ts +15 -1
- package/dist/lib/harness-spec.js +12 -0
- package/dist/lib/mcp-json-config.d.ts +2 -0
- package/dist/lib/mcp-json-config.js +4 -0
- package/dist/lib/omp-config.d.ts +16 -0
- package/dist/lib/omp-config.js +71 -0
- package/dist/lib/registration-detect.js +34 -0
- package/dist/lib/setup-telemetry.d.ts +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ npx -y @tiny-fish/cli@latest connect
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
It detects the agent configs on your machine (Claude Code, Codex, Cursor, Grok, Hermes,
|
|
22
|
-
OpenClaw, OpenCode) and opens a checklist with everything pre-selected. Press enter to connect
|
|
22
|
+
omp, OpenClaw, OpenCode) and opens a checklist with everything pre-selected. Press enter to connect
|
|
23
23
|
them all, or use the arrow keys and space to narrow the set first; `q` cancels without touching
|
|
24
24
|
anything. Terminals without raw-key support get a numbered prompt instead.
|
|
25
25
|
|
package/dist/commands/connect.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import spawn from 'cross-spawn';
|
|
2
2
|
import { CONNECT_SOURCE, persistApiKeyToEnvironment, saveConnectContext, validateKeyFormat, validatedApiKey, } from '../lib/auth.js';
|
|
3
|
-
import { CURSOR_SKILL_TARGET, OPENCLAW, OPENCLAW_SKILL_INSTALL_ARGS, NATIVE_BY_HARNESS, launchNativeMcpClient, launchOpenClawWalkthrough, openExternalUrl, } from '../lib/connect-clients.js';
|
|
3
|
+
import { CURSOR_SKILL_TARGET, OPENCLAW, OPENCLAW_SKILL_INSTALL_ARGS, NATIVE_BY_HARNESS, launchNativeMcpClient, launchOmpWalkthrough, launchOpenClawWalkthrough, openExternalUrl, } from '../lib/connect-clients.js';
|
|
4
4
|
import { captureStdio, capturedOutput, HoistedFailureReportedError, installTinyFishCli, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from '../lib/cli-install.js';
|
|
5
5
|
import { ensureCliAuthenticated } from '../lib/connect-auth.js';
|
|
6
6
|
import { createStdinPrompt, runCliFallback } from '../lib/connect-fallback.js';
|
|
@@ -8,6 +8,7 @@ import { installWebSkill } from '../lib/skill-install.js';
|
|
|
8
8
|
import { ConnectInterruptedError, ConnectStepError, createConnectTelemetry, finishSetupCommand, requireCommandSupport, runGuarded, settle, SignInTimeoutError, spawnStepError, stageDurationOf, withStageDuration, spawnRemoval, } from '../lib/connect-runtime.js';
|
|
9
9
|
import { cursorInstallDeeplink, cursorMcpPath, readCursorTinyfishEntry, writeCursorMcpConfig, } from '../lib/cursor-config.js';
|
|
10
10
|
import { runConnectAll } from '../lib/connect-all.js';
|
|
11
|
+
import { ompMcpPath, readOmpTinyfishEntry, writeOmpMcpConfig } from '../lib/omp-config.js';
|
|
11
12
|
import { ALL_HARNESSES, AuthMode, HARNESS_DISPLAY_NAMES, RELOAD_ACTION, } from '../lib/harness-detect.js';
|
|
12
13
|
import { detectHumanInitiated } from '../lib/harness.js';
|
|
13
14
|
import { TINYFISH_API_KEY_VAR } from '../lib/constants.js';
|
|
@@ -507,6 +508,9 @@ export async function connectOpenClaw(options) {
|
|
|
507
508
|
}
|
|
508
509
|
});
|
|
509
510
|
}
|
|
511
|
+
// Only interactive omp wires the OAuth handler; headless cannot sign in.
|
|
512
|
+
const OMP_KEYLESS_COPY = 'TinyFish is configured in omp. Open omp and sign in to TinyFish when prompted, or run ' +
|
|
513
|
+
'`/mcp reauth tinyfish` there.';
|
|
510
514
|
const CONFIG_FILE_HARNESSES = {
|
|
511
515
|
cursor: {
|
|
512
516
|
skillTarget: CURSOR_SKILL_TARGET,
|
|
@@ -522,6 +526,22 @@ const CONFIG_FILE_HARNESSES = {
|
|
|
522
526
|
reload: 'TinyFish is connected. Reload the Cursor window, then approve/sign in under Settings → MCP.',
|
|
523
527
|
},
|
|
524
528
|
},
|
|
529
|
+
omp: {
|
|
530
|
+
// No skill exists for omp; installWebSkill returns without one.
|
|
531
|
+
skillTarget: { displayName: HARNESS_DISPLAY_NAMES.omp },
|
|
532
|
+
registeredUrl: () => readOmpTinyfishEntry()?.url,
|
|
533
|
+
write: writeOmpMcpConfig,
|
|
534
|
+
configPath: ompMcpPath,
|
|
535
|
+
// No install deeplink exists; the keyless copy carries the sign-in.
|
|
536
|
+
tryLaunch: () => false,
|
|
537
|
+
copy: {
|
|
538
|
+
verified: "TinyFish is connected and verified (health+auth). Restart omp and it's live.",
|
|
539
|
+
authFailed: (reason) => `TinyFish is configured, but the authenticated check failed (${reason}). ` +
|
|
540
|
+
'Fix: rotate/re-enter your key with `tinyfish auth login`, then re-run: tinyfish connect omp',
|
|
541
|
+
launched: OMP_KEYLESS_COPY,
|
|
542
|
+
reload: OMP_KEYLESS_COPY,
|
|
543
|
+
},
|
|
544
|
+
},
|
|
525
545
|
};
|
|
526
546
|
/** Writes the harness's MCP config file itself; no `mcp add` exists. */
|
|
527
547
|
async function connectConfigFileHarness(harness, options) {
|
|
@@ -629,6 +649,7 @@ function launchCursorDeeplink(mcpUrl) {
|
|
|
629
649
|
}
|
|
630
650
|
/** Total over the non-native harnesses: a new one fails to compile until wired here. */
|
|
631
651
|
const LAUNCH_OVERRIDES = {
|
|
652
|
+
omp: launchOmpWalkthrough,
|
|
632
653
|
openclaw: launchOpenClawWalkthrough,
|
|
633
654
|
// Cursor has no chat CLI to launch.
|
|
634
655
|
cursor: () => {
|
|
@@ -655,6 +676,20 @@ const CONNECTOR_OVERRIDES = {
|
|
|
655
676
|
}
|
|
656
677
|
return undefined;
|
|
657
678
|
},
|
|
679
|
+
omp: async (options) => {
|
|
680
|
+
await connectConfigFileHarness('omp', options);
|
|
681
|
+
if (options.launch) {
|
|
682
|
+
try {
|
|
683
|
+
await launchOmpWalkthrough();
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
// Post-settle, like the native walkthrough step: never fail the connect.
|
|
687
|
+
errLine(error instanceof Error ? error.message : String(error));
|
|
688
|
+
errLine(finishSetupHint('omp'));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return undefined;
|
|
692
|
+
},
|
|
658
693
|
};
|
|
659
694
|
function requireNative(harness) {
|
|
660
695
|
const client = NATIVE_BY_HARNESS.get(harness);
|
|
@@ -764,7 +799,7 @@ export function registerConnect(program) {
|
|
|
764
799
|
program
|
|
765
800
|
.command('connect')
|
|
766
801
|
.description('Connect TinyFish to an AI agent')
|
|
767
|
-
.argument('[client]', 'Agent client to connect (claude-code, codex, cursor, grok, hermes, openclaw, or opencode); omit to pick from the agents detected on this machine')
|
|
802
|
+
.argument('[client]', 'Agent client to connect (claude-code, codex, cursor, grok, hermes, omp, openclaw, or opencode); omit to pick from the agents detected on this machine')
|
|
768
803
|
.option('--all', 'Detect and connect every supported harness found on this machine')
|
|
769
804
|
.option('--dry-run', 'Print planned writes per harness without touching anything (--all only)')
|
|
770
805
|
.option('--uninstall', 'Remove TinyFish entries written by connect --all')
|
|
@@ -2,6 +2,7 @@ import { clearConnectContext, connectContextState } from './auth.js';
|
|
|
2
2
|
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
|
+
import { planOmpWrite, removeOmpMcpServer } from './omp-config.js';
|
|
5
6
|
import { HERMES_KEY_VAR, hermesEnvPath, removeHermesKey, resolveHermesHome } from './hermes-env.js';
|
|
6
7
|
import { clearHermesWebBackends, HERMES_PLUGIN_SHA } from './hermes-plugin.js';
|
|
7
8
|
import { HARNESS_DISPLAY_NAMES as DISPLAY_NAMES } from './harness-detect.js';
|
|
@@ -11,6 +12,8 @@ const OPENCODE_UNINSTALL_POINTER = 'remove the tinyfish entry from ~/.config/ope
|
|
|
11
12
|
export function planText(harness, mcpUrl, apiKey) {
|
|
12
13
|
if (harness === 'cursor')
|
|
13
14
|
return planCursorWrite(mcpUrl, apiKey);
|
|
15
|
+
if (harness === 'omp')
|
|
16
|
+
return planOmpWrite(mcpUrl, apiKey);
|
|
14
17
|
// Only the keyed path seeds the .env; a keyless Hermes install writes nothing.
|
|
15
18
|
if (harness === 'hermes' && apiKey) {
|
|
16
19
|
return (`would run \`tinyfish connect hermes\` (harness-owned MCP write; the CLI writes ` +
|
|
@@ -23,6 +26,9 @@ export function uninstallPlanText(harness) {
|
|
|
23
26
|
if (harness === 'cursor') {
|
|
24
27
|
return 'would remove the tinyfish entry from ~/.cursor/mcp.json';
|
|
25
28
|
}
|
|
29
|
+
if (harness === 'omp') {
|
|
30
|
+
return "would remove the tinyfish entry from omp's mcp.json (located via `omp config path`)";
|
|
31
|
+
}
|
|
26
32
|
if (!uninstallRuns(harness)) {
|
|
27
33
|
return `would print \`${uninstallPointer(harness)}\` (no removal command exists)`;
|
|
28
34
|
}
|
|
@@ -170,10 +176,21 @@ function uninstallViaCommands(detection, verbose, binaryOnPath) {
|
|
|
170
176
|
fixCommand: `Retry by hand: ${uninstallPointer(harness)}`,
|
|
171
177
|
};
|
|
172
178
|
}
|
|
173
|
-
|
|
179
|
+
/** The CLI wrote these mcp.json entries, so it removes them. */
|
|
180
|
+
const MCP_JSON_UNINSTALLS = {
|
|
181
|
+
cursor: {
|
|
182
|
+
remove: removeCursorMcpServer,
|
|
183
|
+
fix: 'Fix ~/.cursor/mcp.json by hand, then re-run: tinyfish connect --all --uninstall',
|
|
184
|
+
},
|
|
185
|
+
omp: {
|
|
186
|
+
remove: removeOmpMcpServer,
|
|
187
|
+
fix: "Fix omp's mcp.json (at `omp config path`) by hand, then re-run: tinyfish connect --all --uninstall",
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
function uninstallMcpJson(detection, spec) {
|
|
174
191
|
let result;
|
|
175
192
|
try {
|
|
176
|
-
result =
|
|
193
|
+
result = spec.remove();
|
|
177
194
|
}
|
|
178
195
|
catch (e) {
|
|
179
196
|
result = { status: 'corrupt_skip', error: e instanceof Error ? e.message : String(e) };
|
|
@@ -183,20 +200,21 @@ function uninstallCursor(detection) {
|
|
|
183
200
|
...detection,
|
|
184
201
|
outcome: 'uninstalled',
|
|
185
202
|
removalFailed: true,
|
|
186
|
-
errorMessage: `could not update
|
|
187
|
-
fixCommand:
|
|
203
|
+
errorMessage: `could not update ${DISPLAY_NAMES[detection.harness]}'s mcp.json (${result.error}) — nothing removed.`,
|
|
204
|
+
fixCommand: spec.fix,
|
|
188
205
|
};
|
|
189
206
|
}
|
|
190
207
|
if (result.status === 'unchanged') {
|
|
191
208
|
return { ...detection, outcome: 'uninstall_noop' };
|
|
192
209
|
}
|
|
193
|
-
clearConnectContext(
|
|
210
|
+
clearConnectContext(detection.harness);
|
|
194
211
|
return { ...detection, outcome: 'uninstalled' };
|
|
195
212
|
}
|
|
196
213
|
export function uninstallHarness(harness, configPath, verbose, binaryOnPath) {
|
|
197
214
|
const detection = { harness, detected: true, configPath, installed: false };
|
|
198
|
-
const
|
|
199
|
-
|
|
215
|
+
const mcpJson = MCP_JSON_UNINSTALLS[harness];
|
|
216
|
+
const result = mcpJson
|
|
217
|
+
? uninstallMcpJson(detection, mcpJson)
|
|
200
218
|
: uninstallViaCommands(detection, verbose, binaryOnPath);
|
|
201
219
|
if (harness !== 'hermes')
|
|
202
220
|
return result;
|
package/dist/lib/connect-all.js
CHANGED
|
@@ -143,7 +143,10 @@ function noWorkOutcome(d, uninstall) {
|
|
|
143
143
|
}
|
|
144
144
|
/** A config-writing connect needs no binary; every other harness spawns one. */
|
|
145
145
|
function isStaleConfig(detection) {
|
|
146
|
-
|
|
146
|
+
const spec = harnessSpec(detection.harness);
|
|
147
|
+
// Cursor's IDE reads mcp.json binary-less; omp's path needs the binary.
|
|
148
|
+
const binaryless = spec.cliWritesConfig && !spec.configPathFromBinary;
|
|
149
|
+
return detection.detected && !detection.binaryOnPath && !binaryless;
|
|
147
150
|
}
|
|
148
151
|
async function resolvePick(detections, opts, prompt) {
|
|
149
152
|
let deselected = new Set();
|
|
@@ -404,6 +407,7 @@ export const FIRST_TASK_PROMPT = 'My TinyFish setup just finished — verify it
|
|
|
404
407
|
const LAUNCHERS = [
|
|
405
408
|
{ harness: 'cursor', args: [FIRST_TASK_PROMPT] },
|
|
406
409
|
{ harness: 'claude-code', args: [FIRST_TASK_PROMPT] },
|
|
410
|
+
{ harness: 'omp', args: [FIRST_TASK_PROMPT] },
|
|
407
411
|
];
|
|
408
412
|
async function launchFirstTask(results) {
|
|
409
413
|
const candidates = LAUNCHERS.filter((l) => results.some((r) => r.harness === l.harness && r.installed) &&
|
|
@@ -95,11 +95,12 @@ export declare function hermesRegistrationEnabled(home: string): {
|
|
|
95
95
|
};
|
|
96
96
|
export declare const NATIVE_MCP_CLIENTS: readonly NativeMcpClient[];
|
|
97
97
|
/** Native descriptor by harness id; Cursor and OpenClaw have none. */
|
|
98
|
-
export declare const NATIVE_BY_HARNESS: Map<"openclaw" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code", NativeMcpClient>;
|
|
98
|
+
export declare const NATIVE_BY_HARNESS: Map<"openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code", NativeMcpClient>;
|
|
99
99
|
export declare const OPENCLAW: SupportedCommand;
|
|
100
100
|
/** "printed" = handed to the user to paste; the walkthrough was never started for them. */
|
|
101
101
|
export type WalkthroughOutcome = 'launched' | 'printed';
|
|
102
102
|
/** Awaited before the handover; track+flush must land pre-block. */
|
|
103
103
|
type OnWalkthroughDecided = (outcome: WalkthroughOutcome) => void | Promise<void>;
|
|
104
104
|
export declare function launchNativeMcpClient(client: NativeMcpClient, onDecided?: OnWalkthroughDecided): Promise<WalkthroughOutcome>;
|
|
105
|
+
export declare function launchOmpWalkthrough(onDecided?: OnWalkthroughDecided): Promise<WalkthroughOutcome>;
|
|
105
106
|
export declare function launchOpenClawWalkthrough(onDecided?: OnWalkthroughDecided): Promise<WalkthroughOutcome>;
|
|
@@ -293,6 +293,10 @@ export async function launchNativeMcpClient(client, onDecided) {
|
|
|
293
293
|
? client.launchWalkthrough()
|
|
294
294
|
: handOverTerminal(client.command, [DEFAULT_ONBOARDING_PROMPT], client.displayName), onDecided);
|
|
295
295
|
}
|
|
296
|
+
// Via deliverWalkthrough: handOverTerminal alone would wedge a headless --launch.
|
|
297
|
+
export function launchOmpWalkthrough(onDecided) {
|
|
298
|
+
return deliverWalkthrough('omp', 'omp', DEFAULT_ONBOARDING_PROMPT, () => handOverTerminal('omp', [DEFAULT_ONBOARDING_PROMPT], 'omp'), onDecided);
|
|
299
|
+
}
|
|
296
300
|
export function launchOpenClawWalkthrough(onDecided) {
|
|
297
301
|
return deliverWalkthrough('OpenClaw', 'openclaw', OPENCLAW_ONBOARDING_PROMPT, () => handOverTerminal('openclaw', ['chat', '--message', OPENCLAW_ONBOARDING_PROMPT], 'OpenClaw'), onDecided);
|
|
298
302
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import spawn from 'cross-spawn';
|
|
2
2
|
import { connectHarness } from '../commands/connect.js';
|
|
3
|
-
import { Registered } from './harness-detect.js';
|
|
3
|
+
import { ALL_HARNESSES, Registered } from './harness-detect.js';
|
|
4
|
+
import { harnessSpec } from './harness-spec.js';
|
|
4
5
|
import { detectHumanInitiated } from './harness.js';
|
|
5
6
|
import { errLine } from './output.js';
|
|
6
|
-
//
|
|
7
|
-
const UNATTENDED_SAFE = new Set(
|
|
7
|
+
// Config-file repairs are local writes; the rest need browser sign-in.
|
|
8
|
+
const UNATTENDED_SAFE = new Set(ALL_HARNESSES.filter((harness) => harnessSpec(harness).cliWritesConfig));
|
|
8
9
|
export function repairsFor(checks, statuses) {
|
|
9
10
|
const repairs = [];
|
|
10
11
|
// A revoked-but-well-formed key passes the credential check and fails the call; both need login.
|
|
@@ -25,6 +25,7 @@ declare const doctorCheckSchema: z.ZodObject<{
|
|
|
25
25
|
detail: z.ZodString;
|
|
26
26
|
harness: z.ZodNullable<z.ZodEnum<{
|
|
27
27
|
openclaw: "openclaw";
|
|
28
|
+
omp: "omp";
|
|
28
29
|
grok: "grok";
|
|
29
30
|
cursor: "cursor";
|
|
30
31
|
codex: "codex";
|
|
@@ -41,6 +42,7 @@ declare const doctorCheckSchema: z.ZodObject<{
|
|
|
41
42
|
declare const doctorHarnessSchema: z.ZodObject<{
|
|
42
43
|
harness: z.ZodEnum<{
|
|
43
44
|
openclaw: "openclaw";
|
|
45
|
+
omp: "omp";
|
|
44
46
|
grok: "grok";
|
|
45
47
|
cursor: "cursor";
|
|
46
48
|
codex: "codex";
|
|
@@ -66,6 +68,7 @@ declare const doctorRepairSchema: z.ZodObject<{
|
|
|
66
68
|
}>;
|
|
67
69
|
harness: z.ZodNullable<z.ZodEnum<{
|
|
68
70
|
openclaw: "openclaw";
|
|
71
|
+
omp: "omp";
|
|
69
72
|
grok: "grok";
|
|
70
73
|
cursor: "cursor";
|
|
71
74
|
codex: "codex";
|
|
@@ -93,6 +96,7 @@ export declare const doctorReportSchema: z.ZodObject<{
|
|
|
93
96
|
detail: z.ZodString;
|
|
94
97
|
harness: z.ZodNullable<z.ZodEnum<{
|
|
95
98
|
openclaw: "openclaw";
|
|
99
|
+
omp: "omp";
|
|
96
100
|
grok: "grok";
|
|
97
101
|
cursor: "cursor";
|
|
98
102
|
codex: "codex";
|
|
@@ -109,6 +113,7 @@ export declare const doctorReportSchema: z.ZodObject<{
|
|
|
109
113
|
harnesses: z.ZodArray<z.ZodObject<{
|
|
110
114
|
harness: z.ZodEnum<{
|
|
111
115
|
openclaw: "openclaw";
|
|
116
|
+
omp: "omp";
|
|
112
117
|
grok: "grok";
|
|
113
118
|
cursor: "cursor";
|
|
114
119
|
codex: "codex";
|
|
@@ -134,6 +139,7 @@ export declare const doctorReportSchema: z.ZodObject<{
|
|
|
134
139
|
}>;
|
|
135
140
|
harness: z.ZodNullable<z.ZodEnum<{
|
|
136
141
|
openclaw: "openclaw";
|
|
142
|
+
omp: "omp";
|
|
137
143
|
grok: "grok";
|
|
138
144
|
cursor: "cursor";
|
|
139
145
|
codex: "codex";
|
|
@@ -68,6 +68,8 @@ export interface HarnessSpec {
|
|
|
68
68
|
keyHeldByCli: boolean;
|
|
69
69
|
/** Connect writes the MCP config file; no harness binary is spawned. */
|
|
70
70
|
cliWritesConfig: boolean;
|
|
71
|
+
/** Binary resolves the config path; absent binary, nothing reads it. */
|
|
72
|
+
configPathFromBinary?: true;
|
|
71
73
|
/** `mcp add` succeeds unauthenticated; OAuth lands at first tool use. */
|
|
72
74
|
authDeferredAtInstall: boolean;
|
|
73
75
|
}
|
|
@@ -199,6 +201,18 @@ export declare const HARNESS_SPECS: {
|
|
|
199
201
|
cliWritesConfig: false;
|
|
200
202
|
authDeferredAtInstall: false;
|
|
201
203
|
};
|
|
204
|
+
omp: {
|
|
205
|
+
command: string;
|
|
206
|
+
displayName: string;
|
|
207
|
+
configDir: string;
|
|
208
|
+
reloadAction: string;
|
|
209
|
+
configPathFromBinary: true;
|
|
210
|
+
signInViaCliLogin: false;
|
|
211
|
+
canVerifyAuth: true;
|
|
212
|
+
keyHeldByCli: true;
|
|
213
|
+
cliWritesConfig: true;
|
|
214
|
+
authDeferredAtInstall: false;
|
|
215
|
+
};
|
|
202
216
|
openclaw: {
|
|
203
217
|
command: string;
|
|
204
218
|
displayName: string;
|
|
@@ -252,4 +266,4 @@ export type NonNativeHarness = {
|
|
|
252
266
|
[K in Harness]: 'urlStyle' extends keyof (typeof HARNESS_SPECS)[K] ? never : K;
|
|
253
267
|
}[Harness];
|
|
254
268
|
/** Native MCP harnesses carry add-generation fields; the rest override connect. */
|
|
255
|
-
export declare const NATIVE_HARNESSES: ("openclaw" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code")[];
|
|
269
|
+
export declare const NATIVE_HARNESSES: ("openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code")[];
|
package/dist/lib/harness-spec.js
CHANGED
|
@@ -183,6 +183,18 @@ export const HARNESS_SPECS = {
|
|
|
183
183
|
cliWritesConfig: false,
|
|
184
184
|
authDeferredAtInstall: false,
|
|
185
185
|
},
|
|
186
|
+
omp: {
|
|
187
|
+
command: 'omp',
|
|
188
|
+
displayName: 'omp',
|
|
189
|
+
configDir: '.omp',
|
|
190
|
+
reloadAction: 'restart it',
|
|
191
|
+
configPathFromBinary: true,
|
|
192
|
+
signInViaCliLogin: false,
|
|
193
|
+
canVerifyAuth: true,
|
|
194
|
+
keyHeldByCli: true,
|
|
195
|
+
cliWritesConfig: true,
|
|
196
|
+
authDeferredAtInstall: false,
|
|
197
|
+
},
|
|
186
198
|
openclaw: {
|
|
187
199
|
command: 'openclaw',
|
|
188
200
|
displayName: 'OpenClaw',
|
|
@@ -20,6 +20,8 @@ export interface McpJsonServerEntry {
|
|
|
20
20
|
/** Registered endpoint, so a caller can tell "registered" from "registered at the right place". */
|
|
21
21
|
url?: string;
|
|
22
22
|
keyMatchesCliKey?: boolean;
|
|
23
|
+
/** Whole-value `${VAR}` template: the variable name, never a key. */
|
|
24
|
+
keyTemplateVar?: string;
|
|
23
25
|
error?: string;
|
|
24
26
|
}
|
|
25
27
|
/** Reports the header's shape, never its value. */
|
|
@@ -56,6 +56,8 @@ export function planWrite(target, mcpUrl, apiKey) {
|
|
|
56
56
|
? `${filePath}: would create with a "${target.serverKey}" MCP server entry${authNote}`
|
|
57
57
|
: `${filePath}: would back up to a timestamped copy, then merge in the "${target.serverKey}" MCP server entry${authNote}`;
|
|
58
58
|
}
|
|
59
|
+
// omp expands `${VAR}` / `${VAR:-default}` header values at load.
|
|
60
|
+
const ENV_TEMPLATE_VALUE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}$/;
|
|
59
61
|
/** Reports the header's shape, never its value. */
|
|
60
62
|
export function readTinyfishEntry(target) {
|
|
61
63
|
const existing = readExisting(target);
|
|
@@ -71,10 +73,12 @@ export function readTinyfishEntry(target) {
|
|
|
71
73
|
const keyHeader = isPlainRecord(headers)
|
|
72
74
|
? Object.entries(headers).find((entry) => entry[0].toLowerCase() === 'x-api-key' && typeof entry[1] === 'string')
|
|
73
75
|
: undefined;
|
|
76
|
+
const templateVar = keyHeader ? ENV_TEMPLATE_VALUE.exec(keyHeader[1])?.[1] : undefined;
|
|
74
77
|
return {
|
|
75
78
|
present: true,
|
|
76
79
|
hasApiKeyHeader: keyHeader !== undefined,
|
|
77
80
|
...(matchesCliKey(keyHeader?.[1]) ? { keyMatchesCliKey: true } : {}),
|
|
81
|
+
...(templateVar ? { keyTemplateVar: templateVar } : {}),
|
|
78
82
|
...(typeof entry.url === 'string' ? { url: entry.url } : {}),
|
|
79
83
|
};
|
|
80
84
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type McpJsonServerEntry, type McpJsonTarget, type McpJsonWriteResult } from './mcp-json-config.js';
|
|
2
|
+
/** Tests share one process; the resolver result must not. */
|
|
3
|
+
export declare function resetOmpAgentDirCache(): void;
|
|
4
|
+
/** Profiles move the dir; a guessed path is written unread. */
|
|
5
|
+
export declare function ompAgentDir(): string | undefined;
|
|
6
|
+
/** Undefined means unresolved; connect fails closed via requireOmpTarget. */
|
|
7
|
+
export declare function ompMcpTarget(): McpJsonTarget | undefined;
|
|
8
|
+
export declare function ompMcpPath(): string;
|
|
9
|
+
/** Reports the header's shape, never its value; undefined means unresolved. */
|
|
10
|
+
export declare function readOmpTinyfishEntry(): McpJsonServerEntry | undefined;
|
|
11
|
+
/** Merges only the `tinyfish` key; throws when the dir is unresolved. */
|
|
12
|
+
export declare function writeOmpMcpConfig(mcpUrl: string, apiKey?: string): McpJsonWriteResult;
|
|
13
|
+
/** Dry-run description of the pending write; touches nothing. */
|
|
14
|
+
export declare function planOmpWrite(mcpUrl: string, apiKey?: string): string;
|
|
15
|
+
/** Removes only the `tinyfish` key. */
|
|
16
|
+
export declare function removeOmpMcpServer(): McpJsonWriteResult;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import spawn from 'cross-spawn';
|
|
3
|
+
import { ConnectStepError } from './connect-runtime.js';
|
|
4
|
+
import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
|
|
5
|
+
import { planWrite, readTinyfishEntry, removeServer, writeMcpConfig, } from './mcp-json-config.js';
|
|
6
|
+
const UNRESOLVED_REASON = '`omp config path` did not report a config directory';
|
|
7
|
+
// Cached: every later read must agree with the write.
|
|
8
|
+
let cachedAgentDir;
|
|
9
|
+
/** Tests share one process; the resolver result must not. */
|
|
10
|
+
export function resetOmpAgentDirCache() {
|
|
11
|
+
cachedAgentDir = undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Profiles move the dir; a guessed path is written unread. */
|
|
14
|
+
export function ompAgentDir() {
|
|
15
|
+
cachedAgentDir ??= { dir: resolveAgentDir() };
|
|
16
|
+
return cachedAgentDir.dir;
|
|
17
|
+
}
|
|
18
|
+
function resolveAgentDir() {
|
|
19
|
+
const result = spawn.sync('omp', ['config', 'path'], {
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
timeout: HARNESS_PROBE_TIMEOUT_MS,
|
|
22
|
+
});
|
|
23
|
+
if (result.error || result.status !== 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
const dir = (result.stdout ?? '')
|
|
26
|
+
.split('\n')
|
|
27
|
+
.map((line) => line.trim())
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.at(-1);
|
|
30
|
+
return dir && path.isAbsolute(dir) ? dir : undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Undefined means unresolved; connect fails closed via requireOmpTarget. */
|
|
33
|
+
export function ompMcpTarget() {
|
|
34
|
+
const dir = ompAgentDir();
|
|
35
|
+
if (!dir)
|
|
36
|
+
return undefined;
|
|
37
|
+
return { serverKey: 'tinyfish', dir: () => dir, file: () => path.join(dir, 'mcp.json') };
|
|
38
|
+
}
|
|
39
|
+
function requireOmpTarget() {
|
|
40
|
+
const target = ompMcpTarget();
|
|
41
|
+
if (target)
|
|
42
|
+
return target;
|
|
43
|
+
throw new ConnectStepError("Could not locate omp's config directory: `omp config path` did not print one. " +
|
|
44
|
+
'Check that omp is installed and `omp config path` works, then re-run: tinyfish connect omp', 'invalid_config', { failureDetail: 'omp_agent_dir_unresolved' });
|
|
45
|
+
}
|
|
46
|
+
export function ompMcpPath() {
|
|
47
|
+
return requireOmpTarget().file();
|
|
48
|
+
}
|
|
49
|
+
/** Reports the header's shape, never its value; undefined means unresolved. */
|
|
50
|
+
export function readOmpTinyfishEntry() {
|
|
51
|
+
const target = ompMcpTarget();
|
|
52
|
+
return target ? readTinyfishEntry(target) : undefined;
|
|
53
|
+
}
|
|
54
|
+
/** Merges only the `tinyfish` key; throws when the dir is unresolved. */
|
|
55
|
+
export function writeOmpMcpConfig(mcpUrl, apiKey) {
|
|
56
|
+
return writeMcpConfig(requireOmpTarget(), mcpUrl, apiKey);
|
|
57
|
+
}
|
|
58
|
+
/** Dry-run description of the pending write; touches nothing. */
|
|
59
|
+
export function planOmpWrite(mcpUrl, apiKey) {
|
|
60
|
+
const target = ompMcpTarget();
|
|
61
|
+
if (!target)
|
|
62
|
+
return `${UNRESOLVED_REASON} — connect omp would fail the same way`;
|
|
63
|
+
return planWrite(target, mcpUrl, apiKey);
|
|
64
|
+
}
|
|
65
|
+
/** Removes only the `tinyfish` key. */
|
|
66
|
+
export function removeOmpMcpServer() {
|
|
67
|
+
const target = ompMcpTarget();
|
|
68
|
+
if (!target)
|
|
69
|
+
return { status: 'corrupt_skip', error: UNRESOLVED_REASON };
|
|
70
|
+
return removeServer(target);
|
|
71
|
+
}
|
|
@@ -7,6 +7,7 @@ import { NATIVE_BY_HARNESS } from './connect-clients.js';
|
|
|
7
7
|
import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
|
|
8
8
|
import { errLine } from './output.js';
|
|
9
9
|
import { readCursorTinyfishEntry } from './cursor-config.js';
|
|
10
|
+
import { readOmpTinyfishEntry } from './omp-config.js';
|
|
10
11
|
import { readHermesKey, resolveHermesHome } from './hermes-env.js';
|
|
11
12
|
import { readHermesEntry } from './hermes-config.js';
|
|
12
13
|
import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from './harness-detect.js';
|
|
@@ -293,6 +294,38 @@ function probeCursor() {
|
|
|
293
294
|
...(entry.keyMatchesCliKey ? { keyMatchesCliKey: true } : {}),
|
|
294
295
|
};
|
|
295
296
|
}
|
|
297
|
+
// omp shares Cursor's mcp.json shape; only `omp config path` resolves it.
|
|
298
|
+
function probeOmp() {
|
|
299
|
+
const entry = readOmpTinyfishEntry();
|
|
300
|
+
if (!entry) {
|
|
301
|
+
return {
|
|
302
|
+
registered: Registered.Unknown,
|
|
303
|
+
authMode: AuthMode.Unknown,
|
|
304
|
+
reason: '`omp config path` did not report a config directory',
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (entry.error) {
|
|
308
|
+
return {
|
|
309
|
+
registered: Registered.Unknown,
|
|
310
|
+
authMode: AuthMode.Unknown,
|
|
311
|
+
reason: 'mcp.json exists but could not be read or parsed',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (!entry.present)
|
|
315
|
+
return { registered: Registered.No, authMode: AuthMode.Unknown };
|
|
316
|
+
// A hand-written `${VAR}` header resolves like Codex's env-var key.
|
|
317
|
+
const keyVerdict = entry.keyTemplateVar
|
|
318
|
+
? envKeyVerdict(entry.keyTemplateVar)
|
|
319
|
+
: entry.keyMatchesCliKey
|
|
320
|
+
? { keyMatchesCliKey: true }
|
|
321
|
+
: {};
|
|
322
|
+
return {
|
|
323
|
+
registered: Registered.Yes,
|
|
324
|
+
authMode: entry.hasApiKeyHeader ? AuthMode.ApiKey : AuthMode.Unknown,
|
|
325
|
+
...(entry.url ? { registeredUrl: entry.url } : {}),
|
|
326
|
+
...keyVerdict,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
296
329
|
// Both list commands print this header in every state, including "no servers configured".
|
|
297
330
|
// Exit 0 with output we cannot recognise is not evidence of absence: reporting `no` there would
|
|
298
331
|
// earn a connect repair off a parse failure, which is the bug class this command exists to kill.
|
|
@@ -486,6 +519,7 @@ const PROBES = {
|
|
|
486
519
|
cursor: probeCursor,
|
|
487
520
|
grok: probeGrok,
|
|
488
521
|
hermes: probeHermes,
|
|
522
|
+
omp: probeOmp,
|
|
489
523
|
openclaw: probeOpenClaw,
|
|
490
524
|
opencode: probeOpencode,
|
|
491
525
|
};
|
|
@@ -26,6 +26,7 @@ export declare function postConnectEvent(options: {
|
|
|
26
26
|
declare const harnessResultSchema: z.ZodObject<{
|
|
27
27
|
harness: z.ZodEnum<{
|
|
28
28
|
openclaw: "openclaw";
|
|
29
|
+
omp: "omp";
|
|
29
30
|
grok: "grok";
|
|
30
31
|
cursor: "cursor";
|
|
31
32
|
codex: "codex";
|
|
@@ -59,6 +60,7 @@ export declare const setupCompletedPayloadSchema: z.ZodObject<{
|
|
|
59
60
|
harnesses: z.ZodArray<z.ZodObject<{
|
|
60
61
|
harness: z.ZodEnum<{
|
|
61
62
|
openclaw: "openclaw";
|
|
63
|
+
omp: "omp";
|
|
62
64
|
grok: "grok";
|
|
63
65
|
cursor: "cursor";
|
|
64
66
|
codex: "codex";
|
|
@@ -91,7 +93,7 @@ export type UpgradeScope = 'both' | 'cli' | 'skill';
|
|
|
91
93
|
/** `toVersion` is null when the run was interrupted or the installed version could not be read. */
|
|
92
94
|
export declare function sendUpgradeCompleted(fromVersion: string, toVersion: string | null, outcome: UpgradeOutcome, scope: UpgradeScope, apiKey?: string): Promise<void>;
|
|
93
95
|
export declare function sendSetupCompleted(mcpUrl: string, harnesses: HarnessResult[], apiKey?: string, mode?: SetupMode, attemptId?: string): Promise<void>;
|
|
94
|
-
export declare const DOCTOR_HARNESS_SCOPES: readonly ["all", ...("openclaw" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code")[]];
|
|
96
|
+
export declare const DOCTOR_HARNESS_SCOPES: readonly ["all", ...("openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code")[]];
|
|
95
97
|
export declare const DOCTOR_ERROR_CLASSES: readonly ["command_not_found", "timeout", "spawn_error", "nonzero_exit", "unexpected_error"];
|
|
96
98
|
export type DoctorErrorClass = (typeof DOCTOR_ERROR_CLASSES)[number];
|
|
97
99
|
/** Derived, not read: the catch is generic, so only the code is trustworthy. */
|
|
@@ -120,6 +122,7 @@ export declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
|
120
122
|
}>;
|
|
121
123
|
harness_scope: z.ZodEnum<{
|
|
122
124
|
openclaw: "openclaw";
|
|
125
|
+
omp: "omp";
|
|
123
126
|
grok: "grok";
|
|
124
127
|
cursor: "cursor";
|
|
125
128
|
codex: "codex";
|
|
@@ -132,6 +135,7 @@ export declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
|
132
135
|
failed_check_ids: z.ZodArray<z.ZodString>;
|
|
133
136
|
failed_harnesses: z.ZodArray<z.ZodEnum<{
|
|
134
137
|
openclaw: "openclaw";
|
|
138
|
+
omp: "omp";
|
|
135
139
|
grok: "grok";
|
|
136
140
|
cursor: "cursor";
|
|
137
141
|
codex: "codex";
|
|
@@ -142,6 +146,7 @@ export declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
|
142
146
|
harnesses: z.ZodArray<z.ZodObject<{
|
|
143
147
|
harness: z.ZodEnum<{
|
|
144
148
|
openclaw: "openclaw";
|
|
149
|
+
omp: "omp";
|
|
145
150
|
grok: "grok";
|
|
146
151
|
cursor: "cursor";
|
|
147
152
|
codex: "codex";
|
|
@@ -174,6 +179,7 @@ export declare const doctorCouldNotRunPayloadSchema: z.ZodObject<{
|
|
|
174
179
|
}>;
|
|
175
180
|
harness_scope: z.ZodEnum<{
|
|
176
181
|
openclaw: "openclaw";
|
|
182
|
+
omp: "omp";
|
|
177
183
|
grok: "grok";
|
|
178
184
|
cursor: "cursor";
|
|
179
185
|
codex: "codex";
|
|
@@ -209,6 +215,7 @@ declare const doctorPayloadSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
209
215
|
}>;
|
|
210
216
|
harness_scope: z.ZodEnum<{
|
|
211
217
|
openclaw: "openclaw";
|
|
218
|
+
omp: "omp";
|
|
212
219
|
grok: "grok";
|
|
213
220
|
cursor: "cursor";
|
|
214
221
|
codex: "codex";
|
|
@@ -221,6 +228,7 @@ declare const doctorPayloadSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
221
228
|
failed_check_ids: z.ZodArray<z.ZodString>;
|
|
222
229
|
failed_harnesses: z.ZodArray<z.ZodEnum<{
|
|
223
230
|
openclaw: "openclaw";
|
|
231
|
+
omp: "omp";
|
|
224
232
|
grok: "grok";
|
|
225
233
|
cursor: "cursor";
|
|
226
234
|
codex: "codex";
|
|
@@ -231,6 +239,7 @@ declare const doctorPayloadSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
231
239
|
harnesses: z.ZodArray<z.ZodObject<{
|
|
232
240
|
harness: z.ZodEnum<{
|
|
233
241
|
openclaw: "openclaw";
|
|
242
|
+
omp: "omp";
|
|
234
243
|
grok: "grok";
|
|
235
244
|
cursor: "cursor";
|
|
236
245
|
codex: "codex";
|
|
@@ -262,6 +271,7 @@ declare const doctorPayloadSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
262
271
|
}>;
|
|
263
272
|
harness_scope: z.ZodEnum<{
|
|
264
273
|
openclaw: "openclaw";
|
|
274
|
+
omp: "omp";
|
|
265
275
|
grok: "grok";
|
|
266
276
|
cursor: "cursor";
|
|
267
277
|
codex: "codex";
|