@tiny-fish/cli 0.6.2-next.104 → 0.6.2-next.105
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/commands/connect.d.ts +3 -0
- package/dist/commands/connect.js +73 -32
- package/dist/commands/onboard.d.ts +2 -0
- package/dist/commands/onboard.js +230 -0
- package/dist/index.js +2 -0
- package/dist/lib/constants.js +3 -2
- package/dist/lib/output.js +12 -3
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
export declare const DEFAULT_ONBOARDING_PROMPT: string;
|
|
3
3
|
export declare const OPENCLAW_ONBOARDING_PROMPT: string;
|
|
4
|
+
export type AgentClient = "claude-code" | "codex" | "hermes" | "openclaw";
|
|
4
5
|
export declare function connectClaudeCode(options: {
|
|
5
6
|
mcpUrl: string;
|
|
6
7
|
launch: boolean;
|
|
@@ -16,5 +17,7 @@ export declare function connectHermes(options: {
|
|
|
16
17
|
export declare function connectOpenClaw(options: {
|
|
17
18
|
mcpUrl: string;
|
|
18
19
|
launch: boolean;
|
|
20
|
+
installCli?: boolean;
|
|
19
21
|
}): Promise<void>;
|
|
22
|
+
export declare function launchAgent(client: AgentClient): void;
|
|
20
23
|
export declare function registerConnect(program: Command): void;
|
package/dist/commands/connect.js
CHANGED
|
@@ -44,6 +44,21 @@ const CLAUDE_CODE = {
|
|
|
44
44
|
],
|
|
45
45
|
addArgs: (mcpUrl) => ["mcp", "add", "--scope", "user", "--transport", "http", "tinyfish", mcpUrl],
|
|
46
46
|
};
|
|
47
|
+
function launchCodexWalkthrough() {
|
|
48
|
+
const deepLink = new URL("codex://new");
|
|
49
|
+
deepLink.searchParams.set("prompt", DEFAULT_ONBOARDING_PROMPT);
|
|
50
|
+
deepLink.searchParams.set("path", process.cwd());
|
|
51
|
+
const [command, args] = process.platform === "darwin"
|
|
52
|
+
? ["open", [deepLink.toString()]]
|
|
53
|
+
: process.platform === "win32"
|
|
54
|
+
? ["cmd", ["/c", "start", "", deepLink.toString()]]
|
|
55
|
+
: ["xdg-open", [deepLink.toString()]];
|
|
56
|
+
const result = spawn.sync(command, args, { stdio: "ignore" });
|
|
57
|
+
if (result.error || result.status !== 0) {
|
|
58
|
+
throw new Error("Could not open Codex", { cause: result.error });
|
|
59
|
+
}
|
|
60
|
+
errLine("Codex opened with the TinyFish walkthrough ready. Send the prompt to start.");
|
|
61
|
+
}
|
|
47
62
|
const CODEX = {
|
|
48
63
|
command: "codex",
|
|
49
64
|
connectClient: "codex",
|
|
@@ -53,6 +68,7 @@ const CODEX = {
|
|
|
53
68
|
patterns: [/--oauth-resource(?:\s|<)/],
|
|
54
69
|
unavailableMessage: CODEX_OAUTH_RESOURCE_UNAVAILABLE_MESSAGE,
|
|
55
70
|
},
|
|
71
|
+
launchWalkthrough: launchCodexWalkthrough,
|
|
56
72
|
removals: [{ args: ["mcp", "remove", "tinyfish"], label: "TinyFish registration from Codex" }],
|
|
57
73
|
addArgs: (mcpUrl, oauthResource) => [
|
|
58
74
|
"mcp",
|
|
@@ -182,6 +198,38 @@ function removeExistingRegistration(client, removal) {
|
|
|
182
198
|
}
|
|
183
199
|
throw new Error(`Could not remove existing ${removal.label}: ${details}`);
|
|
184
200
|
}
|
|
201
|
+
function launchNativeMcpClient(client) {
|
|
202
|
+
errLine(`Starting the TinyFish walkthrough in ${client.displayName}...`);
|
|
203
|
+
if (client.launchWalkthrough) {
|
|
204
|
+
client.launchWalkthrough();
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
const result = spawn.sync(client.command, [DEFAULT_ONBOARDING_PROMPT], { stdio: "inherit" });
|
|
208
|
+
if (result.error) {
|
|
209
|
+
throw new Error(`Could not launch ${client.displayName}`, { cause: result.error });
|
|
210
|
+
}
|
|
211
|
+
if (result.signal === "SIGINT" || result.signal === "SIGTERM")
|
|
212
|
+
return false;
|
|
213
|
+
if (result.status !== 0) {
|
|
214
|
+
throw new Error(`${client.displayName} walkthrough exited with status ${result.status ?? "unknown"}`);
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
function launchOpenClawWalkthrough() {
|
|
219
|
+
errLine("Starting the TinyFish walkthrough in OpenClaw...");
|
|
220
|
+
const result = spawn.sync("openclaw", ["chat", "--message", OPENCLAW_ONBOARDING_PROMPT], {
|
|
221
|
+
stdio: "inherit",
|
|
222
|
+
});
|
|
223
|
+
if (result.error) {
|
|
224
|
+
throw new Error("Could not launch OpenClaw", { cause: result.error });
|
|
225
|
+
}
|
|
226
|
+
if (result.signal === "SIGINT" || result.signal === "SIGTERM")
|
|
227
|
+
return false;
|
|
228
|
+
if (result.status !== 0) {
|
|
229
|
+
throw new Error(`OpenClaw walkthrough exited with status ${result.status ?? "unknown"}`);
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
185
233
|
async function connectNativeMcpClient(client, options) {
|
|
186
234
|
const telemetry = createConnectTelemetry(options.mcpUrl, client.connectClient);
|
|
187
235
|
let failureStage = "prerequisite_check";
|
|
@@ -223,21 +271,8 @@ async function connectNativeMcpClient(client, options) {
|
|
|
223
271
|
return;
|
|
224
272
|
}
|
|
225
273
|
failureStage = "walkthrough_launch";
|
|
226
|
-
|
|
227
|
-
if (client.launchWalkthrough) {
|
|
228
|
-
client.launchWalkthrough();
|
|
229
|
-
await telemetry.track("completed");
|
|
274
|
+
if (!launchNativeMcpClient(client))
|
|
230
275
|
return;
|
|
231
|
-
}
|
|
232
|
-
const result = spawn.sync(client.command, [DEFAULT_ONBOARDING_PROMPT], { stdio: "inherit" });
|
|
233
|
-
if (result.error) {
|
|
234
|
-
throw new Error(`Could not launch ${client.displayName}`, { cause: result.error });
|
|
235
|
-
}
|
|
236
|
-
if (result.signal === "SIGINT" || result.signal === "SIGTERM")
|
|
237
|
-
return;
|
|
238
|
-
if (result.status !== 0) {
|
|
239
|
-
throw new Error(`${client.displayName} walkthrough exited with status ${result.status ?? "unknown"}`);
|
|
240
|
-
}
|
|
241
276
|
await telemetry.track("completed");
|
|
242
277
|
}
|
|
243
278
|
catch (error) {
|
|
@@ -260,13 +295,15 @@ export async function connectOpenClaw(options) {
|
|
|
260
295
|
try {
|
|
261
296
|
await telemetry.track("started");
|
|
262
297
|
requireCommandSupport(OPENCLAW);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
298
|
+
if (options.installCli !== false) {
|
|
299
|
+
failureStage = "cli_install";
|
|
300
|
+
errLine("Installing the TinyFish CLI...");
|
|
301
|
+
const cliInstallResult = spawn.sync("npm", ["install", "--global", "@tiny-fish/cli@latest"], {
|
|
302
|
+
stdio: "inherit",
|
|
303
|
+
});
|
|
304
|
+
if (cliInstallResult.error || cliInstallResult.status !== 0) {
|
|
305
|
+
throw new Error("Could not install the TinyFish CLI", { cause: cliInstallResult.error });
|
|
306
|
+
}
|
|
270
307
|
}
|
|
271
308
|
failureStage = "skill_install";
|
|
272
309
|
errLine("Installing the TinyFish skill in OpenClaw...");
|
|
@@ -301,18 +338,8 @@ export async function connectOpenClaw(options) {
|
|
|
301
338
|
return;
|
|
302
339
|
}
|
|
303
340
|
failureStage = "walkthrough_launch";
|
|
304
|
-
|
|
305
|
-
const launchResult = spawn.sync("openclaw", ["chat", "--message", OPENCLAW_ONBOARDING_PROMPT], {
|
|
306
|
-
stdio: "inherit",
|
|
307
|
-
});
|
|
308
|
-
if (launchResult.error) {
|
|
309
|
-
throw new Error("Could not launch OpenClaw", { cause: launchResult.error });
|
|
310
|
-
}
|
|
311
|
-
if (launchResult.signal === "SIGINT" || launchResult.signal === "SIGTERM")
|
|
341
|
+
if (!launchOpenClawWalkthrough())
|
|
312
342
|
return;
|
|
313
|
-
if (launchResult.status !== 0) {
|
|
314
|
-
throw new Error(`OpenClaw walkthrough exited with status ${launchResult.status ?? "unknown"}`);
|
|
315
|
-
}
|
|
316
343
|
await telemetry.track("completed");
|
|
317
344
|
}
|
|
318
345
|
catch (error) {
|
|
@@ -320,6 +347,20 @@ export async function connectOpenClaw(options) {
|
|
|
320
347
|
throw error;
|
|
321
348
|
}
|
|
322
349
|
}
|
|
350
|
+
export function launchAgent(client) {
|
|
351
|
+
if (client === "claude-code") {
|
|
352
|
+
launchNativeMcpClient(CLAUDE_CODE);
|
|
353
|
+
}
|
|
354
|
+
else if (client === "codex") {
|
|
355
|
+
launchNativeMcpClient(CODEX);
|
|
356
|
+
}
|
|
357
|
+
else if (client === "hermes") {
|
|
358
|
+
launchNativeMcpClient(HERMES);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
launchOpenClawWalkthrough();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
323
364
|
export function registerConnect(program) {
|
|
324
365
|
program
|
|
325
366
|
.command("connect")
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import * as readline from "readline/promises";
|
|
2
|
+
import { getApiKey, saveConfig } from "../lib/auth.js";
|
|
3
|
+
import { fetchContentGet, searchQuery } from "../lib/client.js";
|
|
4
|
+
import { BASE_URL } from "../lib/constants.js";
|
|
5
|
+
import { errLine, handleApiError, outLine } from "../lib/output.js";
|
|
6
|
+
import { connectClaudeCode, connectCodex, connectHermes, connectOpenClaw, launchAgent, } from "./connect.js";
|
|
7
|
+
const EXAMPLE_QUERIES = [
|
|
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
|
+
"Find a new-grad software engineering role posted in the last 24 hours and fetch the job description.",
|
|
10
|
+
"Analyse today's market and recommend the best investments for $5,000 with expected returns and risks.",
|
|
11
|
+
];
|
|
12
|
+
const AGENTS = [
|
|
13
|
+
{ id: "claude-code", label: "Claude Code", connect: connectClaudeCode },
|
|
14
|
+
{ id: "codex", label: "Codex", connect: connectCodex },
|
|
15
|
+
{ id: "hermes", label: "Hermes", connect: connectHermes },
|
|
16
|
+
{ id: "openclaw", label: "OpenClaw", connect: connectOpenClaw },
|
|
17
|
+
];
|
|
18
|
+
function cleanFetchedContent(text) {
|
|
19
|
+
const cleaned = text
|
|
20
|
+
.replace(/!\[[^\]]*\]\([^)]+\)/g, "")
|
|
21
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
22
|
+
.replace(/^#{1,6}\s+/gm, "")
|
|
23
|
+
.replace(/[ \t]+/g, " ")
|
|
24
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
25
|
+
.trim();
|
|
26
|
+
if (cleaned.length <= 1_200)
|
|
27
|
+
return cleaned;
|
|
28
|
+
const shortened = cleaned.slice(0, 1_200);
|
|
29
|
+
return `${shortened.slice(0, shortened.lastIndexOf(" "))}…`;
|
|
30
|
+
}
|
|
31
|
+
function isUsefulFetchedContent(text) {
|
|
32
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
33
|
+
return (normalized.length >= 120 &&
|
|
34
|
+
normalized.split(" ").length >= 20 &&
|
|
35
|
+
!/about press copyright contact us creators|there was an error while loading/i.test(normalized));
|
|
36
|
+
}
|
|
37
|
+
function parseAgents(value) {
|
|
38
|
+
if (!value.trim())
|
|
39
|
+
return [];
|
|
40
|
+
const selections = value.split(",").map((item) => item.trim());
|
|
41
|
+
return selections.map((selection) => {
|
|
42
|
+
const agent = AGENTS[Number(selection) - 1];
|
|
43
|
+
if (!agent)
|
|
44
|
+
throw new Error(`Unknown agent: ${selection}`);
|
|
45
|
+
return agent.id;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function ask(question) {
|
|
49
|
+
const prompt = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
50
|
+
try {
|
|
51
|
+
return (await prompt.question(question)).trim();
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
prompt.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function sdkSnippet(language, query) {
|
|
58
|
+
if (language === "python") {
|
|
59
|
+
return `from tinyfish import TinyFish
|
|
60
|
+
|
|
61
|
+
client = TinyFish()
|
|
62
|
+
|
|
63
|
+
# TinyFish Search: discover the right live source
|
|
64
|
+
search = client.search.query(${JSON.stringify(query)})
|
|
65
|
+
top_url = search.results[0].url
|
|
66
|
+
|
|
67
|
+
# TinyFish Fetch: retrieve clean content from that source
|
|
68
|
+
page = client.fetch.get_contents([top_url], format="markdown")
|
|
69
|
+
print(page.results[0].text)`;
|
|
70
|
+
}
|
|
71
|
+
return `import { TinyFish } from "@tiny-fish/sdk";
|
|
72
|
+
|
|
73
|
+
const client = new TinyFish();
|
|
74
|
+
|
|
75
|
+
// TinyFish Search: discover the right live source
|
|
76
|
+
const search = await client.search.query({ query: ${JSON.stringify(query)} });
|
|
77
|
+
const topUrl = search.results[0].url;
|
|
78
|
+
|
|
79
|
+
// TinyFish Fetch: retrieve clean content from that source
|
|
80
|
+
const page = await client.fetch.getContents({ urls: [topUrl], format: "markdown" });
|
|
81
|
+
console.log(page.results[0]?.text);`;
|
|
82
|
+
}
|
|
83
|
+
async function connectAgent(id) {
|
|
84
|
+
const agent = AGENTS.find((candidate) => candidate.id === id);
|
|
85
|
+
if (!agent)
|
|
86
|
+
return;
|
|
87
|
+
const mcpUrl = new URL("/mcp", BASE_URL).toString();
|
|
88
|
+
if (id === "openclaw") {
|
|
89
|
+
await connectOpenClaw({ mcpUrl, launch: false, installCli: false });
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
await agent.connect({ mcpUrl, launch: false });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function parseSelectedAgent(value, selected) {
|
|
96
|
+
if (!value.trim())
|
|
97
|
+
return undefined;
|
|
98
|
+
const agent = selected[Number(value) - 1];
|
|
99
|
+
if (!agent)
|
|
100
|
+
throw new Error("Choose one of the agents you connected.");
|
|
101
|
+
return agent;
|
|
102
|
+
}
|
|
103
|
+
async function chooseQuery(query, interactive) {
|
|
104
|
+
if (query)
|
|
105
|
+
return query;
|
|
106
|
+
if (!interactive)
|
|
107
|
+
throw new Error("Pass a query, or run `tinyfish onboard` in a terminal.");
|
|
108
|
+
outLine("\nChoose a live-web job, or type your own:");
|
|
109
|
+
EXAMPLE_QUERIES.forEach((example, index) => outLine(` ${index + 1}. ${example}`));
|
|
110
|
+
const answer = await ask("Job [1]: ");
|
|
111
|
+
return EXAMPLE_QUERIES[Number(answer || "1") - 1] ?? answer;
|
|
112
|
+
}
|
|
113
|
+
async function runSearchStep(query, apiKey, persistEnvKey) {
|
|
114
|
+
outLine(`\n1. Searching the live web for: ${query}`);
|
|
115
|
+
const search = await searchQuery({ query }, apiKey);
|
|
116
|
+
if (persistEnvKey) {
|
|
117
|
+
saveConfig(apiKey);
|
|
118
|
+
outLine("✓ API key saved to ~/.tinyfish/config.json");
|
|
119
|
+
}
|
|
120
|
+
if (!search.results.length) {
|
|
121
|
+
throw new Error("Search returned no results. Try a more specific query.");
|
|
122
|
+
}
|
|
123
|
+
outLine("\nSearch results");
|
|
124
|
+
for (const result of search.results.slice(0, 3)) {
|
|
125
|
+
outLine(` ${result.position}. ${result.title}\n` +
|
|
126
|
+
` ${result.site_name}${result.date ? ` · ${result.date}` : ""}\n` +
|
|
127
|
+
` ${result.snippet}\n` +
|
|
128
|
+
` ${result.url}`);
|
|
129
|
+
}
|
|
130
|
+
return search.results;
|
|
131
|
+
}
|
|
132
|
+
async function runFetchStep(results, apiKey) {
|
|
133
|
+
const candidates = results.slice(0, 5);
|
|
134
|
+
let failedFetches = 0;
|
|
135
|
+
let lastFetchError;
|
|
136
|
+
for (const candidate of candidates) {
|
|
137
|
+
try {
|
|
138
|
+
const fetched = await fetchContentGet({ urls: [candidate.url], format: "markdown" }, apiKey);
|
|
139
|
+
const page = fetched.results[0];
|
|
140
|
+
if (page && typeof page.text === "string" && isUsefulFetchedContent(page.text)) {
|
|
141
|
+
return { source: candidate, text: cleanFetchedContent(page.text) };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
failedFetches += 1;
|
|
146
|
+
lastFetchError = error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (failedFetches === candidates.length)
|
|
150
|
+
throw lastFetchError;
|
|
151
|
+
throw new Error("Search found sources, but Fetch did not return useful page content.");
|
|
152
|
+
}
|
|
153
|
+
function renderFetchedResult({ source, text }) {
|
|
154
|
+
outLine(`\n2. Fetched source\n ${source.title}\n ${source.site_name} · ${source.url}`);
|
|
155
|
+
outLine(`\nWhy it matched\n${source.snippet}`);
|
|
156
|
+
outLine(`\nFetched content\n${text}`);
|
|
157
|
+
outLine("\n✓ Search found relevant sources; Fetch returned clean, usable content.");
|
|
158
|
+
}
|
|
159
|
+
function agentLabel(id) {
|
|
160
|
+
return AGENTS.find((agent) => agent.id === id)?.label ?? id;
|
|
161
|
+
}
|
|
162
|
+
function errorMessage(error) {
|
|
163
|
+
return error instanceof Error ? error.message : String(error);
|
|
164
|
+
}
|
|
165
|
+
async function runAgentSteps(interactive) {
|
|
166
|
+
if (!interactive)
|
|
167
|
+
return;
|
|
168
|
+
outLine("\n3. Add TinyFish to your agents (choose any, or press Enter to skip):");
|
|
169
|
+
AGENTS.forEach((agent, index) => outLine(` ${index + 1}. ${agent.label}`));
|
|
170
|
+
const selected = parseAgents(await ask("Agents [e.g. 1,2]: "));
|
|
171
|
+
const connected = [];
|
|
172
|
+
for (const id of selected) {
|
|
173
|
+
try {
|
|
174
|
+
await connectAgent(id);
|
|
175
|
+
connected.push(id);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
errLine(` Could not connect ${agentLabel(id)}: ${errorMessage(error)}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!connected.length)
|
|
182
|
+
return;
|
|
183
|
+
outLine("\n4. Open an agent now:");
|
|
184
|
+
connected.forEach((id, index) => outLine(` ${index + 1}. ${agentLabel(id)}`));
|
|
185
|
+
const launchId = parseSelectedAgent(await ask("Open [number or Enter to skip]: "), connected);
|
|
186
|
+
if (!launchId)
|
|
187
|
+
return;
|
|
188
|
+
try {
|
|
189
|
+
launchAgent(launchId);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
errLine(` Could not open ${agentLabel(launchId)}: ${errorMessage(error)}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function runSdkStep(interactive, query) {
|
|
196
|
+
if (!interactive)
|
|
197
|
+
return;
|
|
198
|
+
const choice = await ask("\nShow this as SDK code? [t] TypeScript, [p] Python, [Enter] skip: ");
|
|
199
|
+
if (!choice)
|
|
200
|
+
return;
|
|
201
|
+
const language = choice === "p" || choice === "python" ? "python" : "typescript";
|
|
202
|
+
outLine(`\n${sdkSnippet(language, query)}\n`);
|
|
203
|
+
}
|
|
204
|
+
export function registerOnboard(program) {
|
|
205
|
+
program
|
|
206
|
+
.command("onboard")
|
|
207
|
+
.description("Run a guided Search → Fetch demo, then add TinyFish to your agents or SDK")
|
|
208
|
+
.argument("[query]", "A real topic to research")
|
|
209
|
+
.addHelpText("after", "\nExamples:\n" +
|
|
210
|
+
' tinyfish onboard "What changed in the AI dev stack this week?"\n' +
|
|
211
|
+
"\nAgents: claude-code, codex, hermes, openclaw.\n" +
|
|
212
|
+
"Run `tinyfish connect <client> --launch` later to add one directly.")
|
|
213
|
+
.action(async (query) => {
|
|
214
|
+
const apiKey = getApiKey();
|
|
215
|
+
const persistEnvKey = Boolean(process.env["TINYFISH_API_KEY"]);
|
|
216
|
+
delete process.env["TINYFISH_API_KEY"];
|
|
217
|
+
try {
|
|
218
|
+
const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
219
|
+
const selectedQuery = await chooseQuery(query, interactive);
|
|
220
|
+
const searchResults = await runSearchStep(selectedQuery, apiKey, persistEnvKey);
|
|
221
|
+
renderFetchedResult(await runFetchStep(searchResults, apiKey));
|
|
222
|
+
await runAgentSteps(interactive);
|
|
223
|
+
await runSdkStep(interactive, selectedQuery);
|
|
224
|
+
outLine('Try another target with `tinyfish onboard "<query>"`, or run `tinyfish onboard --help`.');
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
handleApiError(error);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { registerRuns } from "./commands/runs.js";
|
|
|
12
12
|
import { registerConfigureClaude } from "./commands/config-claude.js";
|
|
13
13
|
import { registerSearch } from "./commands/search.js";
|
|
14
14
|
import { registerConnect } from "./commands/connect.js";
|
|
15
|
+
import { registerOnboard } from "./commands/onboard.js";
|
|
15
16
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
16
17
|
const program = new Command();
|
|
17
18
|
program
|
|
@@ -28,6 +29,7 @@ program
|
|
|
28
29
|
}
|
|
29
30
|
});
|
|
30
31
|
registerAuth(program);
|
|
32
|
+
registerOnboard(program);
|
|
31
33
|
registerConnect(program);
|
|
32
34
|
registerConfigureClaude(program);
|
|
33
35
|
registerBrowser(program);
|
package/dist/lib/constants.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire } from "module";
|
|
|
2
2
|
const _require = createRequire(import.meta.url);
|
|
3
3
|
const _pkg = _require("../../package.json");
|
|
4
4
|
export const CLI_VERSION = _pkg.version;
|
|
5
|
-
/** Base URL for the TinyFish API. Override with TINYFISH_API_URL for staging/self-hosted
|
|
5
|
+
/** Base URL for the TinyFish API. Override with TINYFISH_API_URL for staging/self-hosted. */
|
|
6
6
|
const apiUrlOverride = process.env["TINYFISH_API_URL"]?.trim();
|
|
7
7
|
if (apiUrlOverride) {
|
|
8
8
|
let parsed;
|
|
@@ -13,7 +13,8 @@ if (apiUrlOverride) {
|
|
|
13
13
|
process.stderr.write("Error: TINYFISH_API_URL must be a valid https URL\n");
|
|
14
14
|
process.exit(1);
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
const loopbackHttp = parsed.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname);
|
|
17
|
+
if (parsed.protocol !== "https:" && !loopbackHttp) {
|
|
17
18
|
process.stderr.write(`Error: TINYFISH_API_URL must use https:// (got protocol "${parsed.protocol || "unknown"}")\n`);
|
|
18
19
|
process.exit(1);
|
|
19
20
|
}
|
package/dist/lib/output.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripVTControlCharacters } from "node:util";
|
|
1
2
|
export class ApiError extends Error {
|
|
2
3
|
status;
|
|
3
4
|
code;
|
|
@@ -11,21 +12,29 @@ export class ApiError extends Error {
|
|
|
11
12
|
export function out(data) {
|
|
12
13
|
process.stdout.write(JSON.stringify(data) + "\n");
|
|
13
14
|
}
|
|
15
|
+
function safe(text) {
|
|
16
|
+
// eslint-disable-next-line no-control-regex
|
|
17
|
+
return stripVTControlCharacters(text).replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "");
|
|
18
|
+
}
|
|
14
19
|
export function outLine(line) {
|
|
15
|
-
process.stdout.write(line + "\n");
|
|
20
|
+
process.stdout.write(safe(line) + "\n");
|
|
16
21
|
}
|
|
17
22
|
export function errLine(line) {
|
|
18
|
-
process.stderr.write(line + "\n");
|
|
23
|
+
process.stderr.write(safe(line) + "\n");
|
|
19
24
|
}
|
|
20
25
|
export function err(data) {
|
|
21
26
|
const payload = typeof data === "string" ? { error: data } : data;
|
|
22
|
-
|
|
27
|
+
const serialized = JSON.stringify(payload) ?? "undefined";
|
|
28
|
+
process.stderr.write(safe(serialized) + "\n");
|
|
23
29
|
}
|
|
24
30
|
export function handleApiError(e) {
|
|
25
31
|
if (e instanceof ApiError) {
|
|
26
32
|
const payload = { error: e.message, status: e.status };
|
|
27
33
|
if (e.code)
|
|
28
34
|
payload.code = e.code;
|
|
35
|
+
if (e.status === 401)
|
|
36
|
+
payload.hint =
|
|
37
|
+
"Clear the TINYFISH_API_KEY environment variable, run `tinyfish auth login`, then try again.";
|
|
29
38
|
err(payload);
|
|
30
39
|
}
|
|
31
40
|
else if (e instanceof Error) {
|