@tiny-fish/cli 0.45.2-next.357 → 0.45.2-next.359
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 +1 -1
- package/dist/lib/config/mcp-json-config.d.ts +2 -0
- package/dist/lib/config/mcp-json-config.js +1 -1
- package/dist/lib/config/opencode-config.d.ts +9 -0
- package/dist/lib/config/opencode-config.js +34 -0
- package/dist/lib/connect-all-uninstall.js +11 -5
- package/dist/lib/connect-clients.d.ts +1 -1
- package/dist/lib/connect-clients.js +7 -6
- package/dist/lib/doctor-report.d.ts +6 -0
- package/dist/lib/harness-spec.d.ts +23 -2
- package/dist/lib/harness-spec.js +26 -0
- package/dist/lib/harness.js +2 -0
- package/dist/lib/registration-detect.js +26 -8
- package/dist/lib/setup-telemetry.d.ts +10 -0
- package/dist/lib/skill-paths.js +2 -0
- 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
|
-
omp, OpenClaw, OpenCode) and opens a checklist with everything pre-selected. Press enter to connect
|
|
22
|
+
Kilo Code, 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
|
@@ -15,7 +15,7 @@ function applyConfigDir(client, configDir) {
|
|
|
15
15
|
if (client !== 'cline') {
|
|
16
16
|
throw new Error('--config-dir is supported only by `tinyfish connect cline`.');
|
|
17
17
|
}
|
|
18
|
-
const resolved = path.resolve(configDir);
|
|
18
|
+
const resolved = path.resolve(configDir); // nosemgrep: path-join-resolve-traversal -- the operator's own --config-dir, statted below
|
|
19
19
|
let isDir = false;
|
|
20
20
|
try {
|
|
21
21
|
isDir = fs.statSync(resolved).isDirectory();
|
|
@@ -10,6 +10,8 @@ export interface McpJsonTarget {
|
|
|
10
10
|
};
|
|
11
11
|
/** Cline nests url and headers under this key; every other harness is flat. */
|
|
12
12
|
transportKey?: string;
|
|
13
|
+
/** Read-side only; OpenCode and its Kilo fork nest servers under `mcp`. */
|
|
14
|
+
serversKey?: string;
|
|
13
15
|
}
|
|
14
16
|
export interface McpJsonWriteResult {
|
|
15
17
|
status: 'written' | 'unchanged' | 'corrupt_skip';
|
|
@@ -76,7 +76,7 @@ export function readTinyfishEntry(target) {
|
|
|
76
76
|
if ('error' in existing) {
|
|
77
77
|
return { present: false, hasApiKeyHeader: false, error: existing.error };
|
|
78
78
|
}
|
|
79
|
-
const servers = existing.parsed.mcpServers;
|
|
79
|
+
const servers = existing.parsed[target.serversKey ?? 'mcpServers'];
|
|
80
80
|
const entry = isPlainRecord(servers) ? servers[target.serverKey] : undefined;
|
|
81
81
|
if (!isPlainRecord(entry))
|
|
82
82
|
return { present: false, hasApiKeyHeader: false };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Harness } from '../harness-detect.js';
|
|
2
|
+
import { type McpJsonServerEntry } from './mcp-json-config.js';
|
|
3
|
+
export type OpencodeStyleHarness = 'kilo' | 'opencode';
|
|
4
|
+
/** Narrows before any path is built, so a future removal-less harness cannot borrow these. */
|
|
5
|
+
export declare function isOpencodeStyle(harness: Harness): harness is OpencodeStyleHarness;
|
|
6
|
+
/** The file on disk, else the `.jsonc` default so messages still name something. */
|
|
7
|
+
export declare function opencodeStyleConfigPath(harness: OpencodeStyleHarness): string;
|
|
8
|
+
/** Reports the header's shape, never its value. JSONC comments read as unparseable. */
|
|
9
|
+
export declare function readOpencodeStyleEntry(harness: OpencodeStyleHarness): McpJsonServerEntry;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// OpenCode and its Kilo fork share a config file the CLI reads but never writes.
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { harnessConfigPath } from '../harness-detect.js';
|
|
5
|
+
import { harnessSpec } from '../harness-spec.js';
|
|
6
|
+
import { readTinyfishEntry, } from './mcp-json-config.js';
|
|
7
|
+
/** Narrows before any path is built, so a future removal-less harness cannot borrow these. */
|
|
8
|
+
export function isOpencodeStyle(harness) {
|
|
9
|
+
return harness === 'kilo' || harness === 'opencode';
|
|
10
|
+
}
|
|
11
|
+
/** Both extensions are valid; `mcp add` keeps whichever already exists. */
|
|
12
|
+
const EXTENSIONS = ['.jsonc', '.json'];
|
|
13
|
+
function candidatePaths(harness) {
|
|
14
|
+
const dir = harnessConfigPath(harness);
|
|
15
|
+
const stem = harnessSpec(harness).command;
|
|
16
|
+
return EXTENSIONS.map((ext) => path.join(dir, `${stem}${ext}`)); // nosemgrep: path-join-resolve-traversal -- fixed names from the harness spec
|
|
17
|
+
}
|
|
18
|
+
/** The file on disk, else the `.jsonc` default so messages still name something. */
|
|
19
|
+
export function opencodeStyleConfigPath(harness) {
|
|
20
|
+
const candidates = candidatePaths(harness);
|
|
21
|
+
return candidates.find((file) => fs.existsSync(file)) ?? candidates[0];
|
|
22
|
+
}
|
|
23
|
+
function target(harness) {
|
|
24
|
+
return {
|
|
25
|
+
serverKey: 'tinyfish',
|
|
26
|
+
serversKey: 'mcp',
|
|
27
|
+
dir: () => harnessConfigPath(harness),
|
|
28
|
+
file: () => opencodeStyleConfigPath(harness),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Reports the header's shape, never its value. JSONC comments read as unparseable. */
|
|
32
|
+
export function readOpencodeStyleEntry(harness) {
|
|
33
|
+
return readTinyfishEntry(target(harness));
|
|
34
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
1
2
|
import { clearConnectContext, connectContextState } from './auth.js';
|
|
2
3
|
import { NATIVE_BY_HARNESS, openclawSkillUninstall } from './connect-clients.js';
|
|
3
4
|
import { ConnectInterruptedError, spawnRemoval } from './connect-runtime.js';
|
|
@@ -6,10 +7,9 @@ import { planOmpWrite, removeOmpMcpServer } from './config/omp-config.js';
|
|
|
6
7
|
import { piMcpPath, planPiWrite, removePiMcpServer } from './config/pi-config.js';
|
|
7
8
|
import { HERMES_KEY_VAR, hermesEnvPath, removeHermesKey, resolveHermesHome } from './hermes-env.js';
|
|
8
9
|
import { clearHermesWebBackends } from './hermes-plugin.js';
|
|
9
|
-
import { HARNESS_DISPLAY_NAMES as DISPLAY_NAMES } from './harness-detect.js';
|
|
10
|
+
import { HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, harnessDisplayPath, } from './harness-detect.js';
|
|
11
|
+
import { isOpencodeStyle, opencodeStyleConfigPath } from './config/opencode-config.js';
|
|
10
12
|
import { errLine } from './output.js';
|
|
11
|
-
// OpenCode ships no `mcp remove`, so the only removal is editing the config it wrote.
|
|
12
|
-
const OPENCODE_UNINSTALL_POINTER = 'remove the tinyfish entry from ~/.config/opencode/opencode.json';
|
|
13
13
|
export function planText(harness, mcpUrl, apiKey) {
|
|
14
14
|
if (harness === 'cursor')
|
|
15
15
|
return planCursorWrite(mcpUrl, apiKey);
|
|
@@ -61,8 +61,14 @@ function uninstallRuns(harness) {
|
|
|
61
61
|
/** Derived from the same args the spawn runs, so the advice cannot drift. */
|
|
62
62
|
function uninstallPointer(harness) {
|
|
63
63
|
const removal = uninstallRuns(harness);
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
// No `mcp remove` ships, so the only removal is editing the config.
|
|
65
|
+
if (!removal) {
|
|
66
|
+
const file = isOpencodeStyle(harness)
|
|
67
|
+
? `/${path.basename(opencodeStyleConfigPath(harness))}`
|
|
68
|
+
: "'s MCP config";
|
|
69
|
+
const where = `${harnessDisplayPath(harness)}${file}`;
|
|
70
|
+
return `remove the tinyfish entry from ${where}`;
|
|
71
|
+
}
|
|
66
72
|
// `;` runs every removal but breaks cmd.exe; `&&` is the reverse — see
|
|
67
73
|
// internal-docs/install-command-shell-quirks.md (PS 5.1 stays a known gap).
|
|
68
74
|
const joiner = process.platform === 'win32' ? ' && ' : ' ; ';
|
|
@@ -104,7 +104,7 @@ export declare function hermesRegistrationEnabled(home: string, mode?: 'api-key'
|
|
|
104
104
|
};
|
|
105
105
|
export declare const NATIVE_MCP_CLIENTS: readonly NativeMcpClient[];
|
|
106
106
|
/** Native descriptor by harness id; Cursor and OpenClaw have none. */
|
|
107
|
-
export declare const NATIVE_BY_HARNESS: Map<"openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "command-code" | "opencode" | "pi" | "cline" | "claude-code", NativeMcpClient>;
|
|
107
|
+
export declare const NATIVE_BY_HARNESS: Map<"openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "command-code" | "kilo" | "opencode" | "pi" | "cline" | "claude-code", NativeMcpClient>;
|
|
108
108
|
export declare const OPENCLAW: SupportedCommand;
|
|
109
109
|
/** "printed" = handed to the user to paste; the walkthrough was never started for them. */
|
|
110
110
|
export type WalkthroughOutcome = 'launched' | 'printed';
|
|
@@ -209,12 +209,12 @@ function installHermesWebPlugin({ apiKey, verbose, seededHome, }) {
|
|
|
209
209
|
setHermesWebBackends(home);
|
|
210
210
|
return pluginVersion;
|
|
211
211
|
}
|
|
212
|
-
//
|
|
213
|
-
//
|
|
212
|
+
// These TUIs read a positional as a project directory, so a bare `<cmd> "<prompt>"` would be
|
|
213
|
+
// read as a folder. `--prompt` seeds the interactive TUI with a first message
|
|
214
214
|
// (opencode.ai/docs/cli), so the onboarding guide fires just like the other agents.
|
|
215
|
-
|
|
216
|
-
handOverTerminal(
|
|
217
|
-
}
|
|
215
|
+
const launchWithPromptFlag = (command, displayName) => (prompt) => {
|
|
216
|
+
handOverTerminal(command, ['--prompt', prompt], displayName);
|
|
217
|
+
};
|
|
218
218
|
// A bare `cline "<prompt>"` is a one-shot act run that auto-approves every tool, so it would
|
|
219
219
|
// bill TinyFish calls unattended and cannot answer the walkthrough's questions. `-i` is the
|
|
220
220
|
// only interactive mode.
|
|
@@ -286,7 +286,8 @@ const HARNESS_OVERRIDES = {
|
|
|
286
286
|
seedKey: seedHermesKey,
|
|
287
287
|
prepareKeylessInstall: prepareHermesKeylessInstall,
|
|
288
288
|
},
|
|
289
|
-
|
|
289
|
+
kilo: { launchWalkthrough: launchWithPromptFlag('kilo', 'Kilo Code') },
|
|
290
|
+
opencode: { launchWalkthrough: launchWithPromptFlag('opencode', 'OpenCode') },
|
|
290
291
|
};
|
|
291
292
|
function toNativeClient(harness) {
|
|
292
293
|
const spec = harnessSpec(harness);
|
|
@@ -41,6 +41,7 @@ declare const doctorCheckSchema: z.ZodObject<{
|
|
|
41
41
|
codex: "codex";
|
|
42
42
|
hermes: "hermes";
|
|
43
43
|
"command-code": "command-code";
|
|
44
|
+
kilo: "kilo";
|
|
44
45
|
opencode: "opencode";
|
|
45
46
|
pi: "pi";
|
|
46
47
|
cline: "cline";
|
|
@@ -61,6 +62,7 @@ declare const doctorHarnessSchema: z.ZodObject<{
|
|
|
61
62
|
codex: "codex";
|
|
62
63
|
hermes: "hermes";
|
|
63
64
|
"command-code": "command-code";
|
|
65
|
+
kilo: "kilo";
|
|
64
66
|
opencode: "opencode";
|
|
65
67
|
pi: "pi";
|
|
66
68
|
cline: "cline";
|
|
@@ -91,6 +93,7 @@ declare const doctorRepairSchema: z.ZodObject<{
|
|
|
91
93
|
codex: "codex";
|
|
92
94
|
hermes: "hermes";
|
|
93
95
|
"command-code": "command-code";
|
|
96
|
+
kilo: "kilo";
|
|
94
97
|
opencode: "opencode";
|
|
95
98
|
pi: "pi";
|
|
96
99
|
cline: "cline";
|
|
@@ -130,6 +133,7 @@ export declare const doctorReportSchema: z.ZodObject<{
|
|
|
130
133
|
codex: "codex";
|
|
131
134
|
hermes: "hermes";
|
|
132
135
|
"command-code": "command-code";
|
|
136
|
+
kilo: "kilo";
|
|
133
137
|
opencode: "opencode";
|
|
134
138
|
pi: "pi";
|
|
135
139
|
cline: "cline";
|
|
@@ -150,6 +154,7 @@ export declare const doctorReportSchema: z.ZodObject<{
|
|
|
150
154
|
codex: "codex";
|
|
151
155
|
hermes: "hermes";
|
|
152
156
|
"command-code": "command-code";
|
|
157
|
+
kilo: "kilo";
|
|
153
158
|
opencode: "opencode";
|
|
154
159
|
pi: "pi";
|
|
155
160
|
cline: "cline";
|
|
@@ -180,6 +185,7 @@ export declare const doctorReportSchema: z.ZodObject<{
|
|
|
180
185
|
codex: "codex";
|
|
181
186
|
hermes: "hermes";
|
|
182
187
|
"command-code": "command-code";
|
|
188
|
+
kilo: "kilo";
|
|
183
189
|
opencode: "opencode";
|
|
184
190
|
pi: "pi";
|
|
185
191
|
cline: "cline";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Our skill-path slugs, inherited from the `skills` CLI era. */
|
|
2
|
-
export type SkillAgent = 'claude-code' | 'cline' | 'codex' | 'command-code' | 'cursor' | 'hermes-agent' | 'opencode' | 'pi';
|
|
2
|
+
export type SkillAgent = 'claude-code' | 'cline' | 'codex' | 'command-code' | 'cursor' | 'hermes-agent' | 'kilo' | 'opencode' | 'pi';
|
|
3
3
|
export interface HarnessSupportCheck {
|
|
4
4
|
args: string[];
|
|
5
5
|
/** Missing → nothing can work; setup fails. */
|
|
@@ -237,6 +237,27 @@ export declare const HARNESS_SPECS: {
|
|
|
237
237
|
postConnectNote: string;
|
|
238
238
|
keyHeldByCli: true;
|
|
239
239
|
};
|
|
240
|
+
kilo: {
|
|
241
|
+
command: string;
|
|
242
|
+
displayName: string;
|
|
243
|
+
configDir: string;
|
|
244
|
+
reloadAction: string;
|
|
245
|
+
skillAgent: "kilo";
|
|
246
|
+
nonInteractiveAdd: true;
|
|
247
|
+
supportCheck: {
|
|
248
|
+
args: string[];
|
|
249
|
+
patterns: RegExp[];
|
|
250
|
+
keyAuthPattern: RegExp;
|
|
251
|
+
unavailableMessage: string;
|
|
252
|
+
};
|
|
253
|
+
urlStyle: "flag";
|
|
254
|
+
keyRequired: true;
|
|
255
|
+
header: {
|
|
256
|
+
name: string;
|
|
257
|
+
sep: "=";
|
|
258
|
+
};
|
|
259
|
+
removals: never[];
|
|
260
|
+
};
|
|
240
261
|
omp: {
|
|
241
262
|
command: string;
|
|
242
263
|
displayName: string;
|
|
@@ -307,4 +328,4 @@ export type NonNativeHarness = {
|
|
|
307
328
|
[K in Harness]: 'urlStyle' extends keyof (typeof HARNESS_SPECS)[K] ? never : 'keyRequired' extends keyof (typeof HARNESS_SPECS)[K] ? never : K;
|
|
308
329
|
}[Harness];
|
|
309
330
|
/** Native MCP harnesses generate an add; key-only ones generate only the keyed form. */
|
|
310
|
-
export declare const NATIVE_HARNESSES: ("openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "command-code" | "opencode" | "pi" | "cline" | "claude-code")[];
|
|
331
|
+
export declare const NATIVE_HARNESSES: ("openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "command-code" | "kilo" | "opencode" | "pi" | "cline" | "claude-code")[];
|
package/dist/lib/harness-spec.js
CHANGED
|
@@ -45,6 +45,10 @@ const OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this OpenCode in
|
|
|
45
45
|
const OPENCODE_MODEL_NOTE = 'Note: TinyFish runs on tool calls, so OpenCode needs a model that supports tool use. Image-only ' +
|
|
46
46
|
'models (e.g. Nano Banana Pro) will show "No endpoints found that support tool use" — switch ' +
|
|
47
47
|
"OpenCode's model if the walkthrough can't start.";
|
|
48
|
+
const KILO_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Kilo Code installation supports remote MCP servers: `kilo mcp add ' +
|
|
49
|
+
'--help` did not list `--url`. Update Kilo Code with `kilo upgrade` and retry, or add ' +
|
|
50
|
+
'TinyFish manually with `kilo mcp add`.' +
|
|
51
|
+
SUPPORT_CHECK_DEBUG_HINT;
|
|
48
52
|
const HERMES_RESTART_NOTE = 'Restart your Hermes session to pick up TinyFish — Hermes discovers MCP servers at startup.';
|
|
49
53
|
const CLINE_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Cline installation supports non-interactive MCP setup: `cline mcp add ' +
|
|
50
54
|
'--help` did not list `--transport` and `--yes`. Update Cline with `cline --update` and retry.' +
|
|
@@ -218,6 +222,28 @@ export const HARNESS_SPECS = {
|
|
|
218
222
|
// No canVerifyAuth: keyed already covers auth; true breaks keyless installs.
|
|
219
223
|
keyHeldByCli: true,
|
|
220
224
|
},
|
|
225
|
+
// Kilo Code forks OpenCode, so the add flags, config shape and list renderer match it.
|
|
226
|
+
kilo: {
|
|
227
|
+
command: 'kilo',
|
|
228
|
+
displayName: 'Kilo Code',
|
|
229
|
+
configDir: '.config/kilo',
|
|
230
|
+
reloadAction: 'restart it',
|
|
231
|
+
skillAgent: 'kilo',
|
|
232
|
+
nonInteractiveAdd: true,
|
|
233
|
+
supportCheck: {
|
|
234
|
+
args: ['mcp', 'add', '--help'],
|
|
235
|
+
patterns: [/--url(?:\s|$)/m],
|
|
236
|
+
keyAuthPattern: HEADER_FLAG,
|
|
237
|
+
unavailableMessage: KILO_MCP_ADD_UNAVAILABLE_MESSAGE,
|
|
238
|
+
},
|
|
239
|
+
urlStyle: 'flag',
|
|
240
|
+
// `kilo mcp auth` exists, but a stored key skips the browser.
|
|
241
|
+
keyRequired: true,
|
|
242
|
+
// Kilo takes `--header NAME=VALUE`, not Claude Code's `NAME: VALUE`.
|
|
243
|
+
header: { name: 'X-API-Key', sep: '=' },
|
|
244
|
+
// `kilo mcp add` upserts by name and ships no `mcp remove`.
|
|
245
|
+
removals: [],
|
|
246
|
+
},
|
|
221
247
|
omp: {
|
|
222
248
|
command: 'omp',
|
|
223
249
|
displayName: 'omp',
|
package/dist/lib/harness.js
CHANGED
|
@@ -28,6 +28,8 @@ const HARNESS_FINGERPRINTS = [
|
|
|
28
28
|
{ name: 'hermes', matches: (env) => hasVarWithPrefix(env, 'HERMES_') },
|
|
29
29
|
// COMMANDCODE_SCRATCHPAD lands on every child; a disabled scratchpad leaves no marker.
|
|
30
30
|
{ name: 'command-code', matches: (env) => hasVarWithPrefix(env, 'COMMANDCODE_') },
|
|
31
|
+
// Kilo forks opencode and sets OPENCODE=1 too, so it must precede opencode.
|
|
32
|
+
{ name: 'kilo', matches: (env) => env['KILO'] === '1' },
|
|
31
33
|
// Exact key, not a prefix: opencode writes OPENCODE=1 at startup, reads every OPENCODE_* as config.
|
|
32
34
|
{ name: 'opencode', matches: (env) => Boolean(env['OPENCODE']) },
|
|
33
35
|
// Observed: pi launched from Claude Code inherits CLAUDECODE=1.
|
|
@@ -10,7 +10,9 @@ import { readClineTinyfishEntry } from './config/cline-config.js';
|
|
|
10
10
|
import { readCommandCodeTinyfishEntry } from './config/command-code-config.js';
|
|
11
11
|
import { readCursorTinyfishEntry } from './config/cursor-config.js';
|
|
12
12
|
import { readOmpTinyfishEntry } from './config/omp-config.js';
|
|
13
|
+
import { readOpencodeStyleEntry } from './config/opencode-config.js';
|
|
13
14
|
import { readPiTinyfishEntry } from './config/pi-config.js';
|
|
15
|
+
import { harnessSpec } from './harness-spec.js';
|
|
14
16
|
import { readHermesKey, resolveHermesHome } from './hermes-env.js';
|
|
15
17
|
import { readHermesEntry } from './config/hermes-config.js';
|
|
16
18
|
import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from './harness-detect.js';
|
|
@@ -382,8 +384,8 @@ function probePi() {
|
|
|
382
384
|
// Exit 0 with output we cannot recognise is not evidence of absence: reporting `no` there would
|
|
383
385
|
// earn a connect repair off a parse failure, which is the bug class this command exists to kill.
|
|
384
386
|
const MCP_LIST_HEADER = /mcp servers/i;
|
|
385
|
-
// OpenCode
|
|
386
|
-
const
|
|
387
|
+
// OpenCode and its Kilo fork print the endpoint under the row; Hermes truncates its column.
|
|
388
|
+
const MCP_LIST_ROW_URL = /^[^\w]*tinyfish(?![\w-]).*\n[^\w]*(https?:\S+)/m;
|
|
387
389
|
function fromMcpList(command, output, registeredMode, urlPattern) {
|
|
388
390
|
if (LISTED_AS_TINYFISH.test(output)) {
|
|
389
391
|
const url = urlPattern?.exec(output)?.[1];
|
|
@@ -444,15 +446,30 @@ function probeOpenClaw() {
|
|
|
444
446
|
? { registered: Registered.Yes, authMode: AuthMode.ApiKey, usesCliKey: true }
|
|
445
447
|
: NOT_REGISTERED;
|
|
446
448
|
}
|
|
447
|
-
// `
|
|
448
|
-
function
|
|
449
|
+
// `mcp list` prints no headers, so the config file is the only place the key's shape shows.
|
|
450
|
+
function configFileAuth(harness) {
|
|
451
|
+
const entry = readOpencodeStyleEntry(harness);
|
|
452
|
+
if (entry.error || !entry.present || !entry.hasApiKeyHeader)
|
|
453
|
+
return {};
|
|
454
|
+
return {
|
|
455
|
+
authMode: AuthMode.ApiKey,
|
|
456
|
+
...(entry.keyMatchesCliKey ? { keyMatchesCliKey: true } : {}),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
// `mcp list` resolves project config too, so it stays the registration source of truth.
|
|
460
|
+
function probeMcpList(harness) {
|
|
461
|
+
const command = harnessSpec(harness).command;
|
|
449
462
|
const args = ['mcp', 'list'];
|
|
450
|
-
const probe = runProbe(
|
|
463
|
+
const probe = runProbe(command, args);
|
|
451
464
|
if (probe.outcome !== 'ran' || probe.exitCode !== 0) {
|
|
452
|
-
const reason = probeReason(
|
|
465
|
+
const reason = probeReason(command, args, probe);
|
|
453
466
|
return unverified(reason);
|
|
454
467
|
}
|
|
455
|
-
|
|
468
|
+
const listed = fromMcpList(command, probe.output, AuthMode.Unknown, MCP_LIST_ROW_URL);
|
|
469
|
+
if (listed.registered !== Registered.Yes)
|
|
470
|
+
return listed;
|
|
471
|
+
// A project-scoped entry is listed but absent here, so auth mode stays unknown.
|
|
472
|
+
return { ...listed, ...configFileAuth(harness) };
|
|
456
473
|
}
|
|
457
474
|
// Verified against grok 0.0.60: a list. Header values stay `unknown`; only names are read.
|
|
458
475
|
const grokEntrySchema = z.looseObject({
|
|
@@ -545,9 +562,10 @@ const PROBES = {
|
|
|
545
562
|
cursor: probeCursor,
|
|
546
563
|
grok: probeGrok,
|
|
547
564
|
hermes: probeHermes,
|
|
565
|
+
kilo: () => probeMcpList('kilo'),
|
|
548
566
|
omp: probeOmp,
|
|
549
567
|
openclaw: probeOpenClaw,
|
|
550
|
-
opencode:
|
|
568
|
+
opencode: () => probeMcpList('opencode'),
|
|
551
569
|
pi: probePi,
|
|
552
570
|
};
|
|
553
571
|
/** Never throws; a failed probe is `unknown` with a reason, so no caller can read it as healthy. */
|
|
@@ -29,6 +29,7 @@ declare const harnessResultSchema: z.ZodObject<{
|
|
|
29
29
|
codex: "codex";
|
|
30
30
|
hermes: "hermes";
|
|
31
31
|
"command-code": "command-code";
|
|
32
|
+
kilo: "kilo";
|
|
32
33
|
opencode: "opencode";
|
|
33
34
|
pi: "pi";
|
|
34
35
|
cline: "cline";
|
|
@@ -66,6 +67,7 @@ export declare const setupCompletedPayloadSchema: z.ZodObject<{
|
|
|
66
67
|
codex: "codex";
|
|
67
68
|
hermes: "hermes";
|
|
68
69
|
"command-code": "command-code";
|
|
70
|
+
kilo: "kilo";
|
|
69
71
|
opencode: "opencode";
|
|
70
72
|
pi: "pi";
|
|
71
73
|
cline: "cline";
|
|
@@ -130,6 +132,7 @@ declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
|
130
132
|
codex: "codex";
|
|
131
133
|
hermes: "hermes";
|
|
132
134
|
"command-code": "command-code";
|
|
135
|
+
kilo: "kilo";
|
|
133
136
|
opencode: "opencode";
|
|
134
137
|
pi: "pi";
|
|
135
138
|
cline: "cline";
|
|
@@ -146,6 +149,7 @@ declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
|
146
149
|
codex: "codex";
|
|
147
150
|
hermes: "hermes";
|
|
148
151
|
"command-code": "command-code";
|
|
152
|
+
kilo: "kilo";
|
|
149
153
|
opencode: "opencode";
|
|
150
154
|
pi: "pi";
|
|
151
155
|
cline: "cline";
|
|
@@ -160,6 +164,7 @@ declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
|
160
164
|
codex: "codex";
|
|
161
165
|
hermes: "hermes";
|
|
162
166
|
"command-code": "command-code";
|
|
167
|
+
kilo: "kilo";
|
|
163
168
|
opencode: "opencode";
|
|
164
169
|
pi: "pi";
|
|
165
170
|
cline: "cline";
|
|
@@ -203,6 +208,7 @@ declare const doctorCouldNotRunPayloadSchema: z.ZodObject<{
|
|
|
203
208
|
codex: "codex";
|
|
204
209
|
hermes: "hermes";
|
|
205
210
|
"command-code": "command-code";
|
|
211
|
+
kilo: "kilo";
|
|
206
212
|
opencode: "opencode";
|
|
207
213
|
pi: "pi";
|
|
208
214
|
cline: "cline";
|
|
@@ -242,6 +248,7 @@ declare const doctorPayloadSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
242
248
|
codex: "codex";
|
|
243
249
|
hermes: "hermes";
|
|
244
250
|
"command-code": "command-code";
|
|
251
|
+
kilo: "kilo";
|
|
245
252
|
opencode: "opencode";
|
|
246
253
|
pi: "pi";
|
|
247
254
|
cline: "cline";
|
|
@@ -258,6 +265,7 @@ declare const doctorPayloadSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
258
265
|
codex: "codex";
|
|
259
266
|
hermes: "hermes";
|
|
260
267
|
"command-code": "command-code";
|
|
268
|
+
kilo: "kilo";
|
|
261
269
|
opencode: "opencode";
|
|
262
270
|
pi: "pi";
|
|
263
271
|
cline: "cline";
|
|
@@ -272,6 +280,7 @@ declare const doctorPayloadSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
272
280
|
codex: "codex";
|
|
273
281
|
hermes: "hermes";
|
|
274
282
|
"command-code": "command-code";
|
|
283
|
+
kilo: "kilo";
|
|
275
284
|
opencode: "opencode";
|
|
276
285
|
pi: "pi";
|
|
277
286
|
cline: "cline";
|
|
@@ -314,6 +323,7 @@ declare const doctorPayloadSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
314
323
|
codex: "codex";
|
|
315
324
|
hermes: "hermes";
|
|
316
325
|
"command-code": "command-code";
|
|
326
|
+
kilo: "kilo";
|
|
317
327
|
opencode: "opencode";
|
|
318
328
|
pi: "pi";
|
|
319
329
|
cline: "cline";
|
package/dist/lib/skill-paths.js
CHANGED
|
@@ -19,6 +19,8 @@ const SKILL_DIR_BY_AGENT = {
|
|
|
19
19
|
// Not the canonical dir: `skills` writes this one under the harness's own config dir.
|
|
20
20
|
'command-code': () => path.join(os.homedir(), '.commandcode', 'skills'), // nosemgrep: path-join-resolve-traversal -- fixed dir names under os.homedir()
|
|
21
21
|
cursor: canonicalSkillsDir,
|
|
22
|
+
// Verified with `kilo debug skill`: Kilo scans the canonical dir too.
|
|
23
|
+
kilo: canonicalSkillsDir,
|
|
22
24
|
opencode: canonicalSkillsDir,
|
|
23
25
|
// `skills` writes pi's here whatever PI_CODING_AGENT_DIR says.
|
|
24
26
|
pi: () => path.join(os.homedir(), '.pi', 'agent', 'skills'), // nosemgrep: path-join-resolve-traversal -- fixed dir names under os.homedir()
|