@tiny-fish/cli 0.13.1-next.145 → 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 };
|
|
@@ -686,7 +732,9 @@ export async function connectCursor(options) {
|
|
|
686
732
|
state.stage = "authentication";
|
|
687
733
|
ensureCliAuthenticated("cursor", options.apiKey);
|
|
688
734
|
telemetry.track("checkpoint", { phase: "authenticated" });
|
|
689
|
-
|
|
735
|
+
// loadConfig() is unvalidated on read — a hand-edited key must not reach mcp.json.
|
|
736
|
+
const storedKey = options.apiKey ?? process.env["TINYFISH_API_KEY"] ?? loadConfig().api_key;
|
|
737
|
+
const resolvedKey = storedKey && validateKeyFormat(storedKey) ? storedKey : undefined;
|
|
690
738
|
state.stage = "registration";
|
|
691
739
|
errLine("Adding TinyFish to Cursor...");
|
|
692
740
|
const mcpUrl = new URL(options.mcpUrl);
|
|
@@ -747,6 +795,9 @@ export function launchAgent(client) {
|
|
|
747
795
|
else if (client === "hermes") {
|
|
748
796
|
launchNativeMcpClient(HERMES);
|
|
749
797
|
}
|
|
798
|
+
else if (client === "opencode") {
|
|
799
|
+
launchNativeMcpClient(OPENCODE);
|
|
800
|
+
}
|
|
750
801
|
else if (client === "openclaw") {
|
|
751
802
|
launchOpenClawWalkthrough();
|
|
752
803
|
}
|
|
@@ -763,7 +814,7 @@ export function registerConnect(program) {
|
|
|
763
814
|
program
|
|
764
815
|
.command("connect")
|
|
765
816
|
.description("Connect TinyFish to an AI agent")
|
|
766
|
-
.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)")
|
|
767
818
|
.option("--all", "Detect and connect every supported harness found on this machine")
|
|
768
819
|
.option("--dry-run", "Print planned writes per harness without touching anything (--all only)")
|
|
769
820
|
.option("--uninstall", "Remove TinyFish entries written by connect --all")
|
|
@@ -808,8 +859,8 @@ export function registerConnect(program) {
|
|
|
808
859
|
process.exitCode = exitCode;
|
|
809
860
|
return;
|
|
810
861
|
}
|
|
811
|
-
if (options.dryRun || options.uninstall) {
|
|
812
|
-
throw new Error("--dry-run and --
|
|
862
|
+
if (options.dryRun || options.uninstall || options.skipLaunch) {
|
|
863
|
+
throw new Error("--dry-run, --uninstall and --skip-launch are only supported with --all.");
|
|
813
864
|
}
|
|
814
865
|
if (!client) {
|
|
815
866
|
throw new Error("Pass a client (claude-code, codex, cursor, hermes, openclaw) or --all.");
|
|
@@ -824,6 +875,9 @@ export function registerConnect(program) {
|
|
|
824
875
|
else if (client === "hermes") {
|
|
825
876
|
await connectHermes(connectOptions);
|
|
826
877
|
}
|
|
878
|
+
else if (client === "opencode") {
|
|
879
|
+
await connectOpencode(connectOptions);
|
|
880
|
+
}
|
|
827
881
|
else if (client === "openclaw") {
|
|
828
882
|
await connectOpenClaw({ mcpUrl: connectOptions.mcpUrl, launch: connectOptions.launch });
|
|
829
883
|
}
|
|
@@ -838,7 +892,7 @@ export function registerConnect(program) {
|
|
|
838
892
|
else {
|
|
839
893
|
throw new Error("Unsupported client: " +
|
|
840
894
|
client +
|
|
841
|
-
". Supported clients: claude-code, codex, cursor, hermes, openclaw");
|
|
895
|
+
". Supported clients: claude-code, codex, cursor, hermes, openclaw, opencode");
|
|
842
896
|
}
|
|
843
897
|
// Deferred to here so the notice lands after install output, not interleaved with it.
|
|
844
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"]),
|