@tiny-fish/cli 0.40.0 → 0.40.1-next.318

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 CHANGED
@@ -278,6 +278,13 @@ tinyfish fetch content get https://agentql.com --links --image-links
278
278
  # Bound each URL independently
279
279
  tinyfish fetch content get https://agentql.com --per-url-timeout-ms 45000
280
280
 
281
+ # Ranked verbatim snippets answering a query (beta; markdown only, max 10 URLs)
282
+ tinyfish fetch content get https://agentql.com --highlights "pricing tiers"
283
+
284
+ # Tune snippets: count (1-20, default 5), total characters, keep full-page text
285
+ tinyfish fetch content get https://agentql.com --highlights "pricing tiers" \
286
+ --max-snippets 3 --max-characters 1200 --include-full-page-text
287
+
281
288
  # Human-readable output
282
289
  tinyfish fetch content get https://agentql.com --pretty
283
290
  ```
@@ -4,14 +4,64 @@ import { fetchContentGet } from '../lib/client.js';
4
4
  import { err, handleApiError, out, outLine } from '../lib/output.js';
5
5
  const MIN_PER_URL_TIMEOUT_MS = 1;
6
6
  const MAX_PER_URL_TIMEOUT_MS = 110000;
7
- function parsePerUrlTimeoutMs(value) {
8
- const parsed = Number(value);
9
- if (!Number.isInteger(parsed) ||
10
- parsed < MIN_PER_URL_TIMEOUT_MS ||
11
- parsed > MAX_PER_URL_TIMEOUT_MS) {
12
- throw new InvalidArgumentError(`must be an integer between ${MIN_PER_URL_TIMEOUT_MS} and ${MAX_PER_URL_TIMEOUT_MS}`);
7
+ const MIN_HIGHLIGHT_SNIPPETS = 1;
8
+ const MAX_HIGHLIGHT_SNIPPETS = 20;
9
+ const MAX_URLS = 10;
10
+ function integerInRange(min, max) {
11
+ return (value) => {
12
+ const parsed = Number(value);
13
+ if (!Number.isSafeInteger(parsed) || parsed < min || (max !== undefined && parsed > max)) {
14
+ throw new InvalidArgumentError(max === undefined
15
+ ? 'must be a positive integer'
16
+ : `must be an integer between ${min} and ${max}`);
17
+ }
18
+ return parsed;
19
+ };
20
+ }
21
+ function validateHighlightsOptions(opts) {
22
+ const hasSubFlag = opts.maxSnippets !== undefined ||
23
+ opts.maxCharacters !== undefined ||
24
+ opts.includeFullPageText !== undefined;
25
+ if (opts.highlights === undefined) {
26
+ return hasSubFlag
27
+ ? '--max-snippets, --max-characters and --include-full-page-text require --highlights'
28
+ : null;
29
+ }
30
+ if (opts.highlights.trim() === '') {
31
+ return '--highlights requires a non-empty query';
13
32
  }
14
- return parsed;
33
+ if (opts.format !== undefined && opts.format !== 'markdown') {
34
+ return '--highlights requires --format markdown';
35
+ }
36
+ return null;
37
+ }
38
+ function buildHighlightsParams(opts) {
39
+ if (opts.highlights === undefined)
40
+ return undefined;
41
+ return {
42
+ query: opts.highlights,
43
+ ...(opts.maxSnippets !== undefined ? { max_snippets: opts.maxSnippets } : {}),
44
+ ...(opts.maxCharacters !== undefined ? { max_characters: opts.maxCharacters } : {}),
45
+ ...(opts.includeFullPageText !== undefined
46
+ ? { include_full_page_text: opts.includeFullPageText }
47
+ : {}),
48
+ };
49
+ }
50
+ function buildFetchParams(urls, opts) {
51
+ const highlights = buildHighlightsParams(opts);
52
+ return {
53
+ urls,
54
+ ...(opts.format !== undefined ? { format: opts.format } : {}),
55
+ ...(opts.links !== undefined ? { links: opts.links } : {}),
56
+ ...(opts.imageLinks !== undefined ? { image_links: opts.imageLinks } : {}),
57
+ ...(opts.perUrlTimeoutMs !== undefined ? { per_url_timeout_ms: opts.perUrlTimeoutMs } : {}),
58
+ ...(opts.ifNoneMatch !== undefined ? { if_none_match: opts.ifNoneMatch } : {}),
59
+ ...(opts.ifModifiedSince !== undefined ? { if_modified_since: opts.ifModifiedSince } : {}),
60
+ ...(opts.includeEtagAndLastModified !== undefined
61
+ ? { include_etag_and_last_modified: opts.includeEtagAndLastModified }
62
+ : {}),
63
+ ...(highlights !== undefined ? { highlights } : {}),
64
+ };
15
65
  }
16
66
  function printPrettyFetch(response) {
17
67
  outLine(`Results: ${response.results.length}`);
@@ -32,6 +82,12 @@ function printPrettyFetch(response) {
32
82
  outLine(` ETag: ${result.etag}`);
33
83
  if (result.last_modified)
34
84
  outLine(` Last-Modified: ${result.last_modified}`);
85
+ if (result.highlights && result.highlights.length > 0) {
86
+ outLine(' Highlights:');
87
+ result.highlights.forEach((snippet, index) => {
88
+ outLine(` ${index + 1}. ${snippet.text}`);
89
+ });
90
+ }
35
91
  outLine('');
36
92
  }
37
93
  }
@@ -57,12 +113,26 @@ export function registerFetch(program) {
57
113
  .option('--format <format>', 'Output format: markdown, html, or json')
58
114
  .option('--links', 'Include extracted links')
59
115
  .option('--image-links', 'Include extracted image links')
60
- .option('--per-url-timeout-ms <milliseconds>', 'Per-URL timeout budget in milliseconds', parsePerUrlTimeoutMs)
116
+ .option('--per-url-timeout-ms <milliseconds>', 'Per-URL timeout budget in milliseconds', integerInRange(MIN_PER_URL_TIMEOUT_MS, MAX_PER_URL_TIMEOUT_MS))
61
117
  .option('--if-none-match <etag>', 'ETag validator for a conditional GET (single URL only)')
62
118
  .option('--if-modified-since <http-date>', 'Last-Modified validator for a conditional GET (single URL only)')
63
119
  .option('--include-etag-and-last-modified', 'Include etag / last_modified validators on each result')
120
+ .option('--highlights <query>', '(beta) Return ranked verbatim snippets answering the query instead of full-page text (markdown only, max 10 URLs)')
121
+ .option('--max-snippets <n>', 'Maximum snippets per URL, 1-20 (default 5; requires --highlights)', integerInRange(MIN_HIGHLIGHT_SNIPPETS, MAX_HIGHLIGHT_SNIPPETS))
122
+ .option('--max-characters <n>', 'Maximum total characters across snippets (requires --highlights)', integerInRange(1))
123
+ .option('--include-full-page-text', 'Also return full-page text alongside snippets (requires --highlights)')
64
124
  .option('--pretty', 'Human-readable output')
65
125
  .action(async (urls, opts) => {
126
+ // Mirrors the SDK cap the raw post path no longer enforces.
127
+ if (urls.length > MAX_URLS) {
128
+ err({ error: `urls must contain at most ${MAX_URLS} items` });
129
+ process.exit(1);
130
+ }
131
+ const highlightsError = validateHighlightsOptions(opts);
132
+ if (highlightsError !== null) {
133
+ err({ error: highlightsError });
134
+ process.exit(1);
135
+ }
66
136
  if ((opts.ifNoneMatch !== undefined || opts.ifModifiedSince !== undefined) &&
67
137
  urls.length > 1) {
68
138
  err({
@@ -72,22 +142,7 @@ export function registerFetch(program) {
72
142
  }
73
143
  const apiKey = getApiKey();
74
144
  try {
75
- const response = await fetchContentGet({
76
- urls,
77
- ...(opts.format !== undefined ? { format: opts.format } : {}),
78
- ...(opts.links !== undefined ? { links: opts.links } : {}),
79
- ...(opts.imageLinks !== undefined ? { image_links: opts.imageLinks } : {}),
80
- ...(opts.perUrlTimeoutMs !== undefined
81
- ? { per_url_timeout_ms: opts.perUrlTimeoutMs }
82
- : {}),
83
- ...(opts.ifNoneMatch !== undefined ? { if_none_match: opts.ifNoneMatch } : {}),
84
- ...(opts.ifModifiedSince !== undefined
85
- ? { if_modified_since: opts.ifModifiedSince }
86
- : {}),
87
- ...(opts.includeEtagAndLastModified !== undefined
88
- ? { include_etag_and_last_modified: opts.includeEtagAndLastModified }
89
- : {}),
90
- }, apiKey);
145
+ const response = await fetchContentGet(buildFetchParams(urls, opts), apiKey);
91
146
  if (opts.pretty) {
92
147
  printPrettyFetch(response);
93
148
  }
package/dist/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { buildProgram } from './program.js';
3
- const { program } = buildProgram();
4
3
  // Await parseAsync so async command handlers complete before the process exits
5
- program.parseAsync(process.argv).catch((e) => {
4
+ buildProgram()
5
+ .then(({ program }) => program.parseAsync(process.argv))
6
+ .catch((e) => {
6
7
  process.stderr.write(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) + '\n');
8
+ // A failed command import reads as a bare message; the stack names the module.
9
+ if (process.env['TINYFISH_DEBUG'] && e instanceof Error && e.stack) {
10
+ process.stderr.write(e.stack + '\n');
11
+ }
7
12
  process.exit(1);
8
13
  });
@@ -1,5 +1,6 @@
1
- import { type AgentRunAsyncResponse, type AgentRunResponse, type AgentRunWithStreamingResponse, type FetchGetContentsParams, type FetchResponse, type BrowserSession, type Run, type RunListParams, type RunListResponse, type SearchQueryParams, type SearchQueryResponse, type WalletResponse } from '@tiny-fish/sdk';
2
- import type { BatchCancelResponse, BatchGetResponse, BatchRunRequest, BatchRunResponse, CancelRunResponse, CliAgentRunParams, CliBrowserSessionCreateParams, CookieRecord, ProfileCreateResponse, ProfileUploadResponse, RunStepsResponse, VaultConnectRequest, VaultConnectResponse, VaultConnectionsListResponse, VaultDisconnectResponse, VaultItemsListResponse, VaultItemsSyncResponse } from './types.js';
1
+ import { type AgentRunAsyncResponse, type AgentRunResponse, type AgentRunWithStreamingResponse, type BrowserSession, type Run, type RunListParams, type RunListResponse, type SearchQueryParams, type SearchQueryResponse, type WalletResponse } from '@tiny-fish/sdk';
2
+ import type { BatchCancelResponse, BatchGetResponse, BatchRunRequest, BatchRunResponse, CancelRunResponse, CliAgentRunParams, CliBrowserSessionCreateParams, CliFetchGetContentsParams, CookieRecord, ProfileCreateResponse, ProfileUploadResponse, RunStepsResponse, VaultConnectRequest, VaultConnectResponse, VaultConnectionsListResponse, VaultDisconnectResponse, VaultItemsListResponse, VaultItemsSyncResponse } from './types.js';
3
+ import { z } from 'zod';
3
4
  type ConnectMap = Record<string, {
4
5
  attempt_id: string;
5
6
  }> | undefined;
@@ -12,7 +13,116 @@ export declare function listRuns(opts: RunListParams, apiKey: string, timeout?:
12
13
  export declare function getRun(runId: string, apiKey: string): Promise<Run>;
13
14
  export declare function getRunSteps(runId: string, apiKey: string): Promise<RunStepsResponse>;
14
15
  export declare function searchQuery(params: SearchQueryParams, apiKey: string): Promise<SearchQueryResponse>;
15
- export declare function fetchContentGet(params: FetchGetContentsParams, apiKey: string): Promise<FetchResponse>;
16
+ declare const cliFetchResponseSchema: z.ZodObject<{
17
+ results: z.ZodArray<z.ZodIntersection<z.ZodDiscriminatedUnion<[z.ZodObject<{
18
+ url: z.ZodString;
19
+ final_url: z.ZodNullable<z.ZodString>;
20
+ title: z.ZodNullable<z.ZodString>;
21
+ description: z.ZodNullable<z.ZodString>;
22
+ language: z.ZodNullable<z.ZodString>;
23
+ author: z.ZodNullable<z.ZodString>;
24
+ published_date: z.ZodNullable<z.ZodString>;
25
+ links: z.ZodOptional<z.ZodArray<z.ZodString>>;
26
+ image_links: z.ZodOptional<z.ZodArray<z.ZodString>>;
27
+ latency_ms: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
28
+ not_modified: z.ZodOptional<z.ZodBoolean>;
29
+ etag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
30
+ last_modified: z.ZodOptional<z.ZodNullable<z.ZodString>>;
31
+ format: z.ZodLiteral<"markdown">;
32
+ text: z.ZodNullable<z.ZodString>;
33
+ }, z.core.$strip>, z.ZodObject<{
34
+ url: z.ZodString;
35
+ final_url: z.ZodNullable<z.ZodString>;
36
+ title: z.ZodNullable<z.ZodString>;
37
+ description: z.ZodNullable<z.ZodString>;
38
+ language: z.ZodNullable<z.ZodString>;
39
+ author: z.ZodNullable<z.ZodString>;
40
+ published_date: z.ZodNullable<z.ZodString>;
41
+ links: z.ZodOptional<z.ZodArray<z.ZodString>>;
42
+ image_links: z.ZodOptional<z.ZodArray<z.ZodString>>;
43
+ latency_ms: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
44
+ not_modified: z.ZodOptional<z.ZodBoolean>;
45
+ etag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
46
+ last_modified: z.ZodOptional<z.ZodNullable<z.ZodString>>;
47
+ format: z.ZodLiteral<"html">;
48
+ text: z.ZodNullable<z.ZodString>;
49
+ }, z.core.$strip>, z.ZodObject<{
50
+ url: z.ZodString;
51
+ final_url: z.ZodNullable<z.ZodString>;
52
+ title: z.ZodNullable<z.ZodString>;
53
+ description: z.ZodNullable<z.ZodString>;
54
+ language: z.ZodNullable<z.ZodString>;
55
+ author: z.ZodNullable<z.ZodString>;
56
+ published_date: z.ZodNullable<z.ZodString>;
57
+ links: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
+ image_links: z.ZodOptional<z.ZodArray<z.ZodString>>;
59
+ latency_ms: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
60
+ not_modified: z.ZodOptional<z.ZodBoolean>;
61
+ etag: z.ZodOptional<z.ZodNullable<z.ZodString>>;
62
+ last_modified: z.ZodOptional<z.ZodNullable<z.ZodString>>;
63
+ format: z.ZodLiteral<"json">;
64
+ text: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodType<string | number | boolean | {
65
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
66
+ } | (string | number | boolean | {
67
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
68
+ } | (string | number | boolean | {
69
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
70
+ } | (string | number | boolean | {
71
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
72
+ } | (string | number | boolean | {
73
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
74
+ } | (string | number | boolean | {
75
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
76
+ } | (string | number | boolean | {
77
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null;
78
+ } | (string | number | boolean | {
79
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null;
80
+ } | (string | number | boolean | {
81
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null;
82
+ } | (string | number | boolean | {
83
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null;
84
+ } | (string | number | boolean | {
85
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null;
86
+ } | (string | number | boolean | {
87
+ [key: string]: string | number | boolean | /*elided*/ any | /*elided*/ any | null;
88
+ } | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null, unknown, z.core.$ZodTypeInternals<string | number | boolean | {
89
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
90
+ } | (string | number | boolean | {
91
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
92
+ } | (string | number | boolean | {
93
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
94
+ } | (string | number | boolean | {
95
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
96
+ } | (string | number | boolean | {
97
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
98
+ } | (string | number | boolean | {
99
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
100
+ } | (string | number | boolean | {
101
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null;
102
+ } | (string | number | boolean | {
103
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null;
104
+ } | (string | number | boolean | {
105
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null;
106
+ } | (string | number | boolean | {
107
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null;
108
+ } | (string | number | boolean | {
109
+ [key: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null;
110
+ } | (string | number | boolean | {
111
+ [key: string]: string | number | boolean | /*elided*/ any | /*elided*/ any | null;
112
+ } | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null, unknown>>>>;
113
+ }, z.core.$strip>], "format">, z.ZodObject<{
114
+ highlights: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
115
+ text: z.ZodString;
116
+ rank: z.ZodNumber;
117
+ }, z.core.$strip>>>>;
118
+ }, z.core.$strip>>>;
119
+ errors: z.ZodArray<z.ZodObject<{
120
+ url: z.ZodString;
121
+ error: z.ZodString;
122
+ }, z.core.$strip>>;
123
+ }, z.core.$strip>;
124
+ export type CliFetchResponse = z.infer<typeof cliFetchResponseSchema>;
125
+ export declare function fetchContentGet(params: CliFetchGetContentsParams, apiKey: string): Promise<CliFetchResponse>;
16
126
  export declare function browserSessionCreate(params: CliBrowserSessionCreateParams, apiKey: string): Promise<BrowserSession>;
17
127
  export declare function cancelRun(runId: string, apiKey: string): Promise<CancelRunResponse>;
18
128
  export declare function getWallet(apiKey: string): Promise<WalletResponse>;
@@ -1,4 +1,4 @@
1
- import { APIStatusError, agentRunAsyncResponseSchema, agentRunResponseSchema, agentRunWithStreamingResponseSchema, fetchResponseSchema, browserSessionSchema, searchQueryResponseSchema, TinyFish, runSchema, runStatusSchema, } from '@tiny-fish/sdk';
1
+ import { APIStatusError, agentRunAsyncResponseSchema, agentRunResponseSchema, agentRunWithStreamingResponseSchema, fetchErrorSchema, fetchResultSchema, browserSessionSchema, searchQueryResponseSchema, TinyFish, runSchema, runStatusSchema, } from '@tiny-fish/sdk';
2
2
  import { CONNECT_SOURCE, loadConfig } from './auth.js';
3
3
  import { API_URL_OVERRIDE, CLI_AGENT_IDENTITY, CLI_VERSION } from './constants.js';
4
4
  import { detectHarness, detectHumanInitiated } from './harness.js';
@@ -281,10 +281,21 @@ export function searchQuery(params, apiKey) {
281
281
  return searchQueryResponseSchema.parse(response);
282
282
  });
283
283
  }
284
+ const highlightSnippetSchema = z.object({
285
+ text: z.string(),
286
+ rank: z.number().int().min(1),
287
+ });
288
+ // Published SDK result schema strips `highlights`; intersect to keep it.
289
+ // nullish, not optional: a null must not fail the whole response parse.
290
+ const cliFetchResponseSchema = z.object({
291
+ results: z.array(z.intersection(fetchResultSchema, z.object({ highlights: z.array(highlightSnippetSchema).nullish() }))),
292
+ errors: z.array(fetchErrorSchema),
293
+ });
284
294
  export function fetchContentGet(params, apiKey) {
285
295
  return sdk(apiKey, async (client) => {
286
- const response = await client.fetch.getContents(params);
287
- return fetchResponseSchema.parse(response);
296
+ // TODO(PROD-4260): use client.fetch.getContents once #4786 lands in an SDK release.
297
+ const response = await client.post(client.productUrl('fetch'), { json: params });
298
+ return parseWithSchema(cliFetchResponseSchema, response, 'Invalid fetch response');
288
299
  });
289
300
  }
290
301
  export function browserSessionCreate(params, apiKey) {
@@ -4,6 +4,8 @@ export type HermesHomeReason = 'timeout' | 'exit' | 'no_line' | 'relative';
4
4
  export interface HermesHomeFailure {
5
5
  reason: HermesHomeReason;
6
6
  }
7
+ /** Tests share one process; the resolver result must not. */
8
+ export declare function resetHermesHomeCache(): void;
7
9
  /** Asking Hermes beats reimplementing its profile override, which we'd drift from. */
8
10
  export declare function resolveHermesHome(): string | HermesHomeFailure;
9
11
  export declare const HERMES_KEY_VAR = "MCP_TINYFISH_API_KEY";
@@ -7,8 +7,24 @@ import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
7
7
  import { sanitizeLine } from './output.js';
8
8
  // display_hermes_home() abbreviates under $HOME, so the value is not always absolute.
9
9
  const HERMES_HOME_LINE = /^\s*hermes_home:\s*(\S.*?)\s*$/m;
10
+ // Cached: `hermes dump` starts Python, and doctor asks repeatedly.
11
+ let cachedHermesHome;
12
+ /** Tests share one process; the resolver result must not. */
13
+ export function resetHermesHomeCache() {
14
+ cachedHermesHome = undefined;
15
+ }
10
16
  /** Asking Hermes beats reimplementing its profile override, which we'd drift from. */
11
17
  export function resolveHermesHome() {
18
+ if (cachedHermesHome)
19
+ return cachedHermesHome.value;
20
+ const value = probeHermesHome();
21
+ // A timeout is load-dependent; caching it would sink a run a retry still saves.
22
+ if (typeof value === 'string' || value.reason !== 'timeout') {
23
+ cachedHermesHome = { value };
24
+ }
25
+ return value;
26
+ }
27
+ function probeHermesHome() {
12
28
  const result = spawn.sync('hermes', ['dump'], {
13
29
  encoding: 'utf8',
14
30
  env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
@@ -1,4 +1,4 @@
1
- import type { AgentRunParams, BrowserProfile, Run, RunStatus } from '@tiny-fish/sdk';
1
+ import type { AgentRunParams, BrowserProfile, FetchGetContentsParams, Run, RunStatus } from '@tiny-fish/sdk';
2
2
  export type OutputSchema = Record<string, unknown>;
3
3
  export interface CliAgentRunParams extends AgentRunParams {
4
4
  output_schema?: OutputSchema;
@@ -91,6 +91,15 @@ export interface RunStepsResponse {
91
91
  status: RunStatus;
92
92
  steps: RunStep[];
93
93
  }
94
+ export interface CliFetchHighlightsParams {
95
+ query: string;
96
+ max_snippets?: number;
97
+ max_characters?: number;
98
+ include_full_page_text?: boolean;
99
+ }
100
+ export interface CliFetchGetContentsParams extends FetchGetContentsParams {
101
+ highlights?: CliFetchHighlightsParams;
102
+ }
94
103
  export interface CliBrowserSessionCreateParams {
95
104
  url?: string;
96
105
  browser_profile?: 'lite' | 'stealth';
package/dist/program.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  /** Split from index.ts so tests can drive the real program instead of hand-assembling one. */
3
- export declare function buildProgram(): {
3
+ export declare function buildProgram(argv?: string[]): Promise<{
4
4
  program: Command;
5
5
  uninstallNotice: () => void;
6
- };
6
+ }>;
package/dist/program.js CHANGED
@@ -1,28 +1,55 @@
1
1
  import { Command } from 'commander';
2
2
  import { CLI_VERSION } from './lib/constants.js';
3
- import { registerAuth } from './commands/auth.js';
4
- import { registerBatch } from './commands/batch.js';
5
- import { registerFetch } from './commands/fetch.js';
6
- import { registerBrowser } from './commands/browser.js';
7
- import { registerProfile } from './commands/profile.js';
8
- import { registerVault } from './commands/vault.js';
9
- import { registerWallet } from './commands/wallet.js';
10
- import { registerRun } from './commands/run.js';
11
- import { registerRuns } from './commands/runs.js';
12
- import { registerConfigureClaude } from './commands/config-claude.js';
13
- import { registerSearch } from './commands/search.js';
14
- import { registerConnect } from './commands/connect.js';
15
- import { registerDoctor } from './commands/doctor.js';
16
- import { registerUpgrade } from './commands/upgrade.js';
17
- import { registerOnboard } from './commands/onboard.js';
18
3
  import { installNoticeEmit } from './lib/notice.js';
4
+ // Loaded on demand: static imports pull zod, yaml, which, cross-spawn always.
5
+ const GROUPS = {
6
+ auth: async (p) => (await import('./commands/auth.js')).registerAuth(p),
7
+ onboard: async (p) => (await import('./commands/onboard.js')).registerOnboard(p),
8
+ connect: async (p) => (await import('./commands/connect.js')).registerConnect(p),
9
+ doctor: async (p) => (await import('./commands/doctor.js')).registerDoctor(p),
10
+ upgrade: async (p) => (await import('./commands/upgrade.js')).registerUpgrade(p),
11
+ 'config-claude': async (p) => (await import('./commands/config-claude.js')).registerConfigureClaude(p),
12
+ browser: async (p) => (await import('./commands/browser.js')).registerBrowser(p),
13
+ profile: async (p) => (await import('./commands/profile.js')).registerProfile(p),
14
+ vault: async (p) => (await import('./commands/vault.js')).registerVault(p),
15
+ wallet: async (p) => (await import('./commands/wallet.js')).registerWallet(p),
16
+ agent: async (p) => {
17
+ const agentCmd = p
18
+ .command('agent')
19
+ .description('Agent automation commands')
20
+ .enablePositionalOptions();
21
+ // Measured: Promise.all is slower here; ESM loading is CPU-bound.
22
+ // registerRuns hangs off the `run` command registerRun returns.
23
+ const runCmd = (await import('./commands/run.js')).registerRun(agentCmd);
24
+ (await import('./commands/runs.js')).registerRuns(runCmd);
25
+ (await import('./commands/batch.js')).registerBatch(agentCmd);
26
+ },
27
+ search: async (p) => (await import('./commands/search.js')).registerSearch(p),
28
+ fetch: async (p) => (await import('./commands/fetch.js')).registerFetch(p),
29
+ };
30
+ /** Commander answers these before routing, so no command module is needed. */
31
+ const VERSION_FLAGS = ['-V', '--version'];
32
+ // Unknown names need the full list to suggest against.
33
+ /** First bare token is the command. Assumes no global option takes a value. */
34
+ function groupsFor(argv) {
35
+ for (const arg of argv.slice(2)) {
36
+ if (arg === '--')
37
+ break;
38
+ if (!arg.startsWith('-')) {
39
+ return Object.hasOwn(GROUPS, arg) ? [arg] : Object.keys(GROUPS);
40
+ }
41
+ if (VERSION_FLAGS.includes(arg))
42
+ return [];
43
+ }
44
+ return Object.keys(GROUPS);
45
+ }
19
46
  /** Split from index.ts so tests can drive the real program instead of hand-assembling one. */
20
- export function buildProgram() {
47
+ export async function buildProgram(argv = process.argv) {
21
48
  const program = new Command();
22
49
  program
23
50
  .name('tinyfish')
24
51
  .description('TinyFish CLI — run web automations from your terminal or agent.')
25
- .version(CLI_VERSION, '-V, --version', 'Show version')
52
+ .version(CLI_VERSION, VERSION_FLAGS.join(', '), 'Show version')
26
53
  .helpOption('-h, --help', 'Show help')
27
54
  .addHelpCommand(false)
28
55
  .enablePositionalOptions()
@@ -32,25 +59,10 @@ export function buildProgram() {
32
59
  process.env['TINYFISH_DEBUG'] = '1';
33
60
  }
34
61
  });
35
- registerAuth(program);
36
- registerOnboard(program);
37
- registerConnect(program);
38
- registerDoctor(program);
39
- registerUpgrade(program);
40
- registerConfigureClaude(program);
41
- registerBrowser(program);
42
- registerProfile(program);
43
- registerVault(program);
44
- registerWallet(program);
45
- const agentCmd = program
46
- .command('agent')
47
- .description('Agent automation commands')
48
- .enablePositionalOptions();
49
- const runCmd = registerRun(agentCmd);
50
- registerRuns(runCmd);
51
- registerBatch(agentCmd);
52
- registerSearch(program);
53
- registerFetch(program);
62
+ // Order is user-visible: help lists commands as registered.
63
+ for (const name of groupsFor(argv)) {
64
+ await GROUPS[name](program);
65
+ }
54
66
  const uninstallNotice = installNoticeEmit(program);
55
67
  return { program, uninstallNotice };
56
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.40.0",
3
+ "version": "0.40.1-next.318",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {