@tiny-fish/cli 0.13.1-next.147 → 0.13.1-next.148
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.
|
@@ -3,7 +3,7 @@ export declare const DEFAULT_MCP_URL = "https://agent.tinyfish.ai/mcp";
|
|
|
3
3
|
export declare const UPGRADE_HINT = "Run `tinyfish upgrade` any time to update the CLI and skill.";
|
|
4
4
|
export declare const DEFAULT_ONBOARDING_PROMPT: string;
|
|
5
5
|
export declare const OPENCLAW_ONBOARDING_PROMPT: string;
|
|
6
|
-
export type AgentClient = "claude-code" | "codex" | "cursor" | "hermes" | "openclaw";
|
|
6
|
+
export type AgentClient = "claude-code" | "codex" | "cursor" | "hermes" | "openclaw" | "opencode";
|
|
7
7
|
/** Ctrl+C/SIGTERM killed a setup child — abandonment, not error. */
|
|
8
8
|
export declare class ConnectInterruptedError extends Error {
|
|
9
9
|
}
|
|
@@ -32,6 +32,11 @@ export declare function connectHermes(options: {
|
|
|
32
32
|
mcpUrl: string;
|
|
33
33
|
launch: boolean;
|
|
34
34
|
}): Promise<void>;
|
|
35
|
+
export declare function connectOpencode(options: {
|
|
36
|
+
apiKey?: string;
|
|
37
|
+
mcpUrl: string;
|
|
38
|
+
launch: boolean;
|
|
39
|
+
}): Promise<void>;
|
|
35
40
|
export declare function connectOpenClaw(options: {
|
|
36
41
|
mcpUrl: string;
|
|
37
42
|
launch: boolean;
|
package/dist/commands/connect.js
CHANGED
|
@@ -24,6 +24,13 @@ const HERMES_OAUTH_UNAVAILABLE_MESSAGE = "This Hermes installation does not supp
|
|
|
24
24
|
"retry, or add TinyFish manually with `hermes mcp add`.";
|
|
25
25
|
const OPENCLAW_SKILL_UNAVAILABLE_MESSAGE = "This OpenClaw installation does not support global skill installation. Update OpenClaw and " +
|
|
26
26
|
"retry.";
|
|
27
|
+
const OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE = "This OpenCode installation does not support one-command MCP setup (`opencode mcp add --url`). " +
|
|
28
|
+
"Update OpenCode and retry, or add TinyFish manually with `opencode mcp add`.";
|
|
29
|
+
// OpenCode is model-agnostic (unlike Claude Code / Codex), so a fresh install can default to a
|
|
30
|
+
// model with no tool support — TinyFish runs entirely on tool calls, so it would fail there.
|
|
31
|
+
const OPENCODE_MODEL_NOTE = "Note: TinyFish runs on tool calls, so OpenCode needs a model that supports tool use. Image-only " +
|
|
32
|
+
'models (e.g. Nano Banana Pro) will show "No endpoints found that support tool use" — switch ' +
|
|
33
|
+
"OpenCode's model if the walkthrough can't start.";
|
|
27
34
|
const TINYFISH_CLI_NOT_FOUND_MESSAGE = "TinyFish CLI installed but is not available on PATH. Open a new terminal and retry.";
|
|
28
35
|
const TINYFISH_CLI_PACKAGE = "@tiny-fish/cli@latest";
|
|
29
36
|
export const UPGRADE_HINT = "Run `tinyfish upgrade` any time to update the CLI and skill.";
|
|
@@ -217,6 +224,40 @@ const HERMES = {
|
|
|
217
224
|
addArgs: (mcpUrl) => ["mcp", "add", "tinyfish", "--url", mcpUrl, "--auth", "oauth"],
|
|
218
225
|
launchWalkthrough: launchHermesWalkthrough,
|
|
219
226
|
};
|
|
227
|
+
// OpenCode's TUI takes a positional as a project directory, so a bare `opencode "<prompt>"`
|
|
228
|
+
// would be read as a folder. The `--prompt` flag seeds the interactive TUI with a first message
|
|
229
|
+
// (opencode.ai/docs/cli), so the onboarding guide fires just like the other agents.
|
|
230
|
+
function launchOpencode() {
|
|
231
|
+
const result = spawn.sync("opencode", ["--prompt", DEFAULT_ONBOARDING_PROMPT], {
|
|
232
|
+
stdio: "inherit",
|
|
233
|
+
});
|
|
234
|
+
if (result.error) {
|
|
235
|
+
throw new Error("Could not launch OpenCode", { cause: result.error });
|
|
236
|
+
}
|
|
237
|
+
if (result.signal === "SIGINT" || result.signal === "SIGTERM")
|
|
238
|
+
return;
|
|
239
|
+
if (result.status !== 0) {
|
|
240
|
+
throw new Error(`OpenCode exited with status ${result.status ?? "unknown"}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const OPENCODE = {
|
|
244
|
+
command: "opencode",
|
|
245
|
+
connectClient: "opencode",
|
|
246
|
+
skillAgent: "opencode",
|
|
247
|
+
displayName: "OpenCode",
|
|
248
|
+
supportCheck: {
|
|
249
|
+
args: ["mcp", "add", "--help"],
|
|
250
|
+
patterns: [/--url(?:\s|$)/m],
|
|
251
|
+
unavailableMessage: OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE,
|
|
252
|
+
},
|
|
253
|
+
// `opencode mcp add` upserts by name and there is no `opencode mcp remove`, so no cleanup step.
|
|
254
|
+
removals: [],
|
|
255
|
+
// OpenCode writes ~/.config/opencode/opencode.jsonc itself; `mcp auth` is the separate OAuth step.
|
|
256
|
+
addArgs: (mcpUrl) => ["mcp", "add", "tinyfish", "--url", mcpUrl],
|
|
257
|
+
loginArgs: ["mcp", "auth", "tinyfish"],
|
|
258
|
+
launchWalkthrough: launchOpencode,
|
|
259
|
+
postConnectNote: OPENCODE_MODEL_NOTE,
|
|
260
|
+
};
|
|
220
261
|
const OPENCLAW = {
|
|
221
262
|
command: "openclaw",
|
|
222
263
|
displayName: "OpenClaw",
|
|
@@ -380,7 +421,7 @@ const SKILL_INSTALL_FAILURE_PATTERN = /Failed to install/;
|
|
|
380
421
|
export function updateWebSkill() {
|
|
381
422
|
// One entry per `tinyfish connect <client>`, persisted by PF-3169.
|
|
382
423
|
const connected = loadConfig().connect ?? {};
|
|
383
|
-
const skillAgents = [CLAUDE_CODE, CODEX, HERMES]
|
|
424
|
+
const skillAgents = [CLAUDE_CODE, CODEX, HERMES, OPENCODE]
|
|
384
425
|
.filter((client) => connected[client.connectClient])
|
|
385
426
|
.map((client) => client.skillAgent);
|
|
386
427
|
const hasOpenClaw = Boolean(connected["openclaw"]);
|
|
@@ -594,6 +635,8 @@ async function connectNativeMcpClient(client, options) {
|
|
|
594
635
|
// or not, record the install; buildConnectHeaders reads it on subsequent requests.
|
|
595
636
|
saveConnectContext(client.connectClient, telemetry.attemptId);
|
|
596
637
|
errLine(UPGRADE_HINT);
|
|
638
|
+
if (client.postConnectNote)
|
|
639
|
+
errLine(client.postConnectNote);
|
|
597
640
|
if (!options.launch) {
|
|
598
641
|
errLine(`TinyFish is connected. Open ${client.displayName} to start using it.`);
|
|
599
642
|
settle(state, telemetry, "completed");
|
|
@@ -614,6 +657,9 @@ export async function connectCodex(options) {
|
|
|
614
657
|
export async function connectHermes(options) {
|
|
615
658
|
await connectNativeMcpClient(HERMES, options);
|
|
616
659
|
}
|
|
660
|
+
export async function connectOpencode(options) {
|
|
661
|
+
await connectNativeMcpClient(OPENCODE, options);
|
|
662
|
+
}
|
|
617
663
|
export async function connectOpenClaw(options) {
|
|
618
664
|
const telemetry = createConnectTelemetry(options.mcpUrl, "openclaw");
|
|
619
665
|
const state = { stage: "prerequisite_check", settled: false };
|
|
@@ -749,6 +795,9 @@ export function launchAgent(client) {
|
|
|
749
795
|
else if (client === "hermes") {
|
|
750
796
|
launchNativeMcpClient(HERMES);
|
|
751
797
|
}
|
|
798
|
+
else if (client === "opencode") {
|
|
799
|
+
launchNativeMcpClient(OPENCODE);
|
|
800
|
+
}
|
|
752
801
|
else if (client === "openclaw") {
|
|
753
802
|
launchOpenClawWalkthrough();
|
|
754
803
|
}
|
|
@@ -765,7 +814,7 @@ export function registerConnect(program) {
|
|
|
765
814
|
program
|
|
766
815
|
.command("connect")
|
|
767
816
|
.description("Connect TinyFish to an AI agent")
|
|
768
|
-
.argument("[client]", "Agent client to connect (claude-code, codex, cursor, hermes, or
|
|
817
|
+
.argument("[client]", "Agent client to connect (claude-code, codex, cursor, hermes, openclaw, or opencode)")
|
|
769
818
|
.option("--all", "Detect and connect every supported harness found on this machine")
|
|
770
819
|
.option("--dry-run", "Print planned writes per harness without touching anything (--all only)")
|
|
771
820
|
.option("--uninstall", "Remove TinyFish entries written by connect --all")
|
|
@@ -826,6 +875,9 @@ export function registerConnect(program) {
|
|
|
826
875
|
else if (client === "hermes") {
|
|
827
876
|
await connectHermes(connectOptions);
|
|
828
877
|
}
|
|
878
|
+
else if (client === "opencode") {
|
|
879
|
+
await connectOpencode(connectOptions);
|
|
880
|
+
}
|
|
829
881
|
else if (client === "openclaw") {
|
|
830
882
|
await connectOpenClaw({ mcpUrl: connectOptions.mcpUrl, launch: connectOptions.launch });
|
|
831
883
|
}
|
|
@@ -840,7 +892,7 @@ export function registerConnect(program) {
|
|
|
840
892
|
else {
|
|
841
893
|
throw new Error("Unsupported client: " +
|
|
842
894
|
client +
|
|
843
|
-
". Supported clients: claude-code, codex, cursor, hermes, openclaw");
|
|
895
|
+
". Supported clients: claude-code, codex, cursor, hermes, openclaw, opencode");
|
|
844
896
|
}
|
|
845
897
|
// Deferred to here so the notice lands after install output, not interleaved with it.
|
|
846
898
|
emitNotice();
|
package/dist/commands/onboard.js
CHANGED
|
@@ -3,7 +3,7 @@ import { getApiKey, saveConfig } from "../lib/auth.js";
|
|
|
3
3
|
import { fetchContentGet, searchQuery } from "../lib/client.js";
|
|
4
4
|
import { BASE_URL } from "../lib/constants.js";
|
|
5
5
|
import { errLine, handleApiError, outLine } from "../lib/output.js";
|
|
6
|
-
import { connectClaudeCode, connectCodex, connectHermes, connectOpenClaw, launchAgent, } from "./connect.js";
|
|
6
|
+
import { connectClaudeCode, connectCodex, connectHermes, connectOpenClaw, connectOpencode, launchAgent, } from "./connect.js";
|
|
7
7
|
const EXAMPLE_QUERIES = [
|
|
8
8
|
"What are recent changes in the AI dev stack? Give me the top 5 changes that matter, why they matter, and any action needed.",
|
|
9
9
|
"Find a new-grad software engineering role posted in the last 24 hours and fetch the job description.",
|
|
@@ -14,6 +14,7 @@ const AGENTS = [
|
|
|
14
14
|
{ id: "codex", label: "Codex", connect: connectCodex },
|
|
15
15
|
{ id: "hermes", label: "Hermes", connect: connectHermes },
|
|
16
16
|
{ id: "openclaw", label: "OpenClaw", connect: connectOpenClaw },
|
|
17
|
+
{ id: "opencode", label: "OpenCode", connect: connectOpencode },
|
|
17
18
|
];
|
|
18
19
|
function cleanFetchedContent(text) {
|
|
19
20
|
const cleaned = text
|
|
@@ -208,7 +209,7 @@ export function registerOnboard(program) {
|
|
|
208
209
|
.argument("[query]", "A real topic to research")
|
|
209
210
|
.addHelpText("after", "\nExamples:\n" +
|
|
210
211
|
' tinyfish onboard "What changed in the AI dev stack this week?"\n' +
|
|
211
|
-
"\nAgents: claude-code, codex, hermes, openclaw.\n" +
|
|
212
|
+
"\nAgents: claude-code, codex, hermes, openclaw, opencode.\n" +
|
|
212
213
|
"Run `tinyfish connect <client> --launch` later to add one directly.")
|
|
213
214
|
.action(async (query) => {
|
|
214
215
|
const apiKey = getApiKey();
|
package/dist/lib/harness.js
CHANGED
|
@@ -21,6 +21,10 @@ const HARNESS_FINGERPRINTS = [
|
|
|
21
21
|
// prefix so we catch a Codex run regardless of which of its vars are set.
|
|
22
22
|
{ name: "codex", matches: (env) => hasVarWithPrefix(env, "CODEX_") },
|
|
23
23
|
{ name: "hermes", matches: (env) => hasVarWithPrefix(env, "HERMES_") },
|
|
24
|
+
// OpenCode sets a bare `OPENCODE` env var globally at startup (opencode#9292), inherited by
|
|
25
|
+
// the commands it shells out. Exact key, not a prefix — `OPENCODE_*` are user config inputs.
|
|
26
|
+
// Sourced from OpenCode's docs, not yet confirmed against scripts/harness-probe.
|
|
27
|
+
{ name: "opencode", matches: (env) => Boolean(env["OPENCODE"]) },
|
|
24
28
|
{
|
|
25
29
|
name: "claude-code",
|
|
26
30
|
matches: (env) => env["CLAUDECODE"] === "1" || Boolean(env["CLAUDE_CODE_ENTRYPOINT"]),
|