smart-web-mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +3 -0
- package/dist/lib.js +3 -0
- package/dist/lib.js.map +1 -0
- package/dist/shared.d.ts +105 -0
- package/dist/shared.js +283 -0
- package/dist/shared.js.map +1 -0
- package/dist/smartfetch.d.ts +11 -0
- package/dist/smartfetch.js +722 -0
- package/dist/smartfetch.js.map +1 -0
- package/dist/smartsearch.d.ts +11 -0
- package/dist/smartsearch.js +213 -0
- package/dist/smartsearch.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rich-jojo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# smart-web
|
|
2
|
+
|
|
3
|
+
`smart-web` is a portable MCP server that exposes two tools:
|
|
4
|
+
|
|
5
|
+
- `smartsearch`: fallback web search with API-key tiers and no-key fallbacks
|
|
6
|
+
- `smartfetch`: browser-aware page retrieval with escalation to Playwright when needed
|
|
7
|
+
|
|
8
|
+
It is built for Claude Code, OpenCode, and any other MCP client that can run a local stdio server.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
Most AI coding setups still treat search and fetch as separate, inconsistent integrations.
|
|
13
|
+
|
|
14
|
+
- Good search APIs are often paid
|
|
15
|
+
- no-key fallbacks are usually weak or privacy-unclear
|
|
16
|
+
- page fetchers often fail on JS-heavy or bot-protected sites
|
|
17
|
+
- tool setups are usually client-specific instead of portable
|
|
18
|
+
|
|
19
|
+
`smart-web` packages a practical default stack into one MCP server with two focused tools.
|
|
20
|
+
|
|
21
|
+
## What you get
|
|
22
|
+
|
|
23
|
+
- One MCP install, two tools
|
|
24
|
+
- Official MCP SDK over stdio
|
|
25
|
+
- Structured outputs plus readable text/markdown rendering
|
|
26
|
+
- Search fallback chain: Exa -> Brave -> SearXNG -> DuckDuckGo HTML
|
|
27
|
+
- Fetch fallback chain: impit -> stealth Playwright
|
|
28
|
+
- Target-aware normalization for Reddit, X, and DCInside
|
|
29
|
+
- Default blocking for localhost, private IP ranges, and reserved address space
|
|
30
|
+
- Shared setup examples for Claude Code and OpenCode
|
|
31
|
+
- `server.json` metadata for MCP Registry alignment
|
|
32
|
+
|
|
33
|
+
## Why MCP is the right abstraction
|
|
34
|
+
|
|
35
|
+
For your goal, yes: MCP is the best primary interface.
|
|
36
|
+
|
|
37
|
+
- Claude Code already treats MCP as the standard way to add real tools
|
|
38
|
+
- OpenCode can consume local MCP servers too, so one implementation works across both
|
|
39
|
+
- plugins/marketplaces are packaging layers; MCP is the actual interoperability layer
|
|
40
|
+
- if you later want Claude marketplace distribution, the plugin should wrap this MCP server, not replace it
|
|
41
|
+
|
|
42
|
+
Short version:
|
|
43
|
+
|
|
44
|
+
- `MCP` = the core product
|
|
45
|
+
- `Claude plugin` = a distribution wrapper for Claude users
|
|
46
|
+
- `Marketplace` = discovery/install channel
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
Canonical packaging is npm-first because MCP Registry publication and the lowest-friction Claude/OpenCode install path both expect a normal npm package.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install
|
|
54
|
+
npm run build
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Run locally:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
node dist/index.js
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Once the npm package is published, the intended install path is:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx -y smart-web-mcp
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
Claude Code:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
claude mcp add smart-web -- node /absolute/path/to/smart-web/dist/index.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
OpenCode:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"$schema": "https://opencode.ai/config.json",
|
|
82
|
+
"mcp": {
|
|
83
|
+
"smart-web": {
|
|
84
|
+
"type": "local",
|
|
85
|
+
"command": ["node", "/absolute/path/to/smart-web/dist/index.js"],
|
|
86
|
+
"enabled": true
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
More complete examples live in `examples/claude.mcp.json` and `examples/opencode.json`.
|
|
93
|
+
|
|
94
|
+
## Tool behavior
|
|
95
|
+
|
|
96
|
+
### `smartsearch`
|
|
97
|
+
|
|
98
|
+
- uses `EXA_API_KEY` when available
|
|
99
|
+
- falls back to `BRAVE_SEARCH_API_KEY` when available
|
|
100
|
+
- falls back to self-hosted `SEARXNG_BASE_URL` when available
|
|
101
|
+
- falls back to DuckDuckGo HTML when no better option succeeds
|
|
102
|
+
|
|
103
|
+
### `smartfetch`
|
|
104
|
+
|
|
105
|
+
- starts with `impit` for lightweight fetch realism
|
|
106
|
+
- escalates to stealth Playwright when content looks blocked or JS-rendered
|
|
107
|
+
- returns normalized output for Reddit, X, and DCInside
|
|
108
|
+
- can render `json`, `text`, or `markdown`
|
|
109
|
+
- blocks localhost/private/reserved network targets unless explicitly overridden
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
|
|
113
|
+
Environment variables:
|
|
114
|
+
|
|
115
|
+
- `EXA_API_KEY`: optional first-tier search provider
|
|
116
|
+
- `BRAVE_SEARCH_API_KEY`: optional second-tier search provider
|
|
117
|
+
- `SEARXNG_BASE_URL`: optional self-hosted private fallback before public no-key search
|
|
118
|
+
- `SMARTFETCH_CHROME_CHANNEL`: optional Playwright browser channel override
|
|
119
|
+
- `SMARTFETCH_CHROME_PATH`: optional Playwright executable path override
|
|
120
|
+
- `SMARTFETCH_ENABLE_FXTWITTER`: opt in to FxTwitter relay enrichment for X posts
|
|
121
|
+
- `SMARTFETCH_ENABLE_X_OEMBED`: enable or disable X oEmbed fallback
|
|
122
|
+
- `SMARTFETCH_ENABLE_REDDIT_JSON`: enable or disable Reddit JSON enrichment
|
|
123
|
+
- `SMART_WEB_ALLOW_PRIVATE_HOSTS`: opt in to fetching localhost/private network targets
|
|
124
|
+
- `SMART_WEB_LOCAL_ONLY`: disable public no-key or relay-style fallbacks and keep traffic limited to explicitly chosen providers and direct targets
|
|
125
|
+
|
|
126
|
+
Provider toggles:
|
|
127
|
+
|
|
128
|
+
- `SMARTSEARCH_ENABLE_EXA`
|
|
129
|
+
- `SMARTSEARCH_ENABLE_BRAVE`
|
|
130
|
+
- `SMARTSEARCH_ENABLE_SEARXNG`
|
|
131
|
+
- `SMARTSEARCH_ENABLE_DUCKDUCKGO`
|
|
132
|
+
|
|
133
|
+
For local development, copy `.env.example` and export the values in your shell before starting your MCP client.
|
|
134
|
+
|
|
135
|
+
## Security and privacy
|
|
136
|
+
|
|
137
|
+
- For stdio MCP servers, secrets should be injected through environment variables, not committed config
|
|
138
|
+
- Claude Code supports env expansion in `.mcp.json` with `${VAR}` syntax
|
|
139
|
+
- OpenCode supports env expansion in `opencode.json` with `{env:VAR}` syntax
|
|
140
|
+
- `smartsearch` sends only the query to the active backend
|
|
141
|
+
- `smartfetch` sends the target URL to the destination site and, for some enrichments, may call additional public endpoints like Reddit JSON or X oEmbed
|
|
142
|
+
- FxTwitter enrichment is opt-in via `SMARTFETCH_ENABLE_FXTWITTER`
|
|
143
|
+
- provider enable/disable env vars let you turn individual backends on or off without code changes
|
|
144
|
+
- Playwright may use browser-managed temporary state during execution; this project does not intentionally persist its own fetch history or secrets
|
|
145
|
+
- localhost/private/reserved targets are blocked by default to reduce agent-side SSRF risk
|
|
146
|
+
|
|
147
|
+
If query privacy matters, prefer self-hosted `SearXNG` and avoid public no-key fallbacks for sensitive searches.
|
|
148
|
+
|
|
149
|
+
## Claude Code plugin and marketplace
|
|
150
|
+
|
|
151
|
+
This repo now includes an optional Claude plugin scaffold in `plugins/claude-smart-web` and a marketplace manifest in `.claude-plugin/marketplace.json`.
|
|
152
|
+
|
|
153
|
+
Recommendation:
|
|
154
|
+
|
|
155
|
+
- use raw MCP config first for fastest adoption
|
|
156
|
+
- add the plugin/marketplace path once the npm package is published
|
|
157
|
+
|
|
158
|
+
That gives you the right layering:
|
|
159
|
+
|
|
160
|
+
- MCP for cross-client compatibility
|
|
161
|
+
- plugin for Claude-native install UX
|
|
162
|
+
- marketplace for discovery and team rollout
|
|
163
|
+
|
|
164
|
+
## Publish path
|
|
165
|
+
|
|
166
|
+
This project is already structured for:
|
|
167
|
+
|
|
168
|
+
1. npm publish of `smart-web-mcp`
|
|
169
|
+
2. MCP Registry publish using `server.json`
|
|
170
|
+
3. Claude marketplace/plugin distribution using the included plugin scaffold
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm run check
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
CI runs the same checks on GitHub Actions, including metadata consistency and `npm pack --dry-run`.
|
|
179
|
+
|
|
180
|
+
## Design notes
|
|
181
|
+
|
|
182
|
+
- small shared library first, client wrappers second
|
|
183
|
+
- one server with two tools is the best default UX
|
|
184
|
+
- split packages remain possible later if demand appears
|
|
185
|
+
|
|
186
|
+
## Roadmap
|
|
187
|
+
|
|
188
|
+
- publish `smart-web-mcp` to npm
|
|
189
|
+
- publish `smart-web` to the MCP Registry
|
|
190
|
+
- improve site-specific normalizers and ranking heuristics
|
|
191
|
+
- add more client wrappers only where they improve installation, not core behavior
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { runSmartfetch, renderSmartfetch } from "./smartfetch.js";
|
|
6
|
+
import { runSmartsearch, renderSmartsearch } from "./smartsearch.js";
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: "smart-web",
|
|
9
|
+
version: "0.1.0",
|
|
10
|
+
});
|
|
11
|
+
server.tool("smartsearch", "Search the web with fallback engines. Uses Exa if EXA_API_KEY is set, then Brave if BRAVE_SEARCH_API_KEY is set, then DuckDuckGo HTML as a no-key fallback.", {
|
|
12
|
+
query: z.string().min(1).describe("Web search query"),
|
|
13
|
+
numResults: z.number().int().min(1).max(20).optional().describe("Number of search results to return"),
|
|
14
|
+
type: z.enum(["auto", "fast", "deep"]).optional().describe("Search depth hint"),
|
|
15
|
+
contextMaxCharacters: z.number().int().min(1000).max(20000).optional().describe("Maximum snippet/context size hint"),
|
|
16
|
+
}, async ({ query, numResults, type, contextMaxCharacters }) => {
|
|
17
|
+
const output = await runSmartsearch({
|
|
18
|
+
query,
|
|
19
|
+
...(numResults === undefined ? {} : { numResults }),
|
|
20
|
+
...(type === undefined ? {} : { searchType: type }),
|
|
21
|
+
...(contextMaxCharacters === undefined ? {} : { contextMaxCharacters }),
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
structuredContent: output,
|
|
25
|
+
content: [{ type: "text", text: renderSmartsearch(output) }],
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
server.tool("smartfetch", "Fetch a URL with browser-aware fallbacks. Uses impit first, then stealth Playwright when the page looks JS-rendered or blocked.", {
|
|
29
|
+
url: z.string().min(1).describe("Target URL to fetch"),
|
|
30
|
+
target: z.enum(["auto", "generic", "reddit_post", "x_post", "dcinside_post"]).optional().describe("Content type hint"),
|
|
31
|
+
timeout_ms: z.number().int().min(3000).max(120000).optional().describe("Fetch timeout in milliseconds"),
|
|
32
|
+
force_dynamic: z.boolean().optional().describe("Skip light fetches and go straight to Playwright"),
|
|
33
|
+
strict_schema: z.boolean().optional().describe("Return schema-safe fallback output when normalization breaks"),
|
|
34
|
+
format: z.enum(["json", "text", "markdown"]).optional().describe("Text rendering format for the human-readable content"),
|
|
35
|
+
}, async ({ url, target, timeout_ms, force_dynamic, strict_schema, format }) => {
|
|
36
|
+
const output = await runSmartfetch({
|
|
37
|
+
url,
|
|
38
|
+
...(target === undefined ? {} : { target }),
|
|
39
|
+
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
40
|
+
...(force_dynamic === undefined ? {} : { forceDynamic: force_dynamic }),
|
|
41
|
+
...(strict_schema === undefined ? {} : { strictSchema: strict_schema }),
|
|
42
|
+
});
|
|
43
|
+
const requestedFormat = format || "json";
|
|
44
|
+
return {
|
|
45
|
+
structuredContent: output,
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: requestedFormat === "json" ? JSON.stringify(output, null, 2) : renderSmartfetch(output, requestedFormat),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
async function main() {
|
|
55
|
+
const transport = new StdioServerTransport();
|
|
56
|
+
await server.connect(transport);
|
|
57
|
+
}
|
|
58
|
+
main().catch((error) => {
|
|
59
|
+
console.error(error);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { runSmartfetch, renderSmartfetch } from "./smartfetch.js";
|
|
2
|
+
export { runSmartsearch, renderSmartsearch } from "./smartsearch.js";
|
|
3
|
+
export type { AdapterResult, ErrorCategory, SearchResponse, SearchResult, SmartfetchOutput, SmartfetchRenderFormat, Target, ToolError, } from "./shared.js";
|
package/dist/lib.js
ADDED
package/dist/lib.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACjE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export type Target = "auto" | "generic" | "reddit_post" | "x_post" | "dcinside_post";
|
|
2
|
+
export type ResolvedTarget = Exclude<Target, "auto">;
|
|
3
|
+
export type ErrorCategory = "timeout" | "block" | "parse_error" | "unavailable";
|
|
4
|
+
export type ToolError = {
|
|
5
|
+
category: ErrorCategory;
|
|
6
|
+
code: string;
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
export type AdapterResult = {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
method: string;
|
|
12
|
+
content: string;
|
|
13
|
+
links: string[];
|
|
14
|
+
error?: ToolError;
|
|
15
|
+
};
|
|
16
|
+
export type SearchResult = {
|
|
17
|
+
title: string;
|
|
18
|
+
url: string;
|
|
19
|
+
snippet: string;
|
|
20
|
+
engine: string;
|
|
21
|
+
};
|
|
22
|
+
export type SearchResponse = {
|
|
23
|
+
engine: string;
|
|
24
|
+
query: string;
|
|
25
|
+
results: SearchResult[];
|
|
26
|
+
fallbacks_tried: string[];
|
|
27
|
+
notes: string[];
|
|
28
|
+
};
|
|
29
|
+
export type SmartfetchOutput = {
|
|
30
|
+
source: "smartfetch";
|
|
31
|
+
url: string;
|
|
32
|
+
target: ResolvedTarget;
|
|
33
|
+
retrieval_method: string[];
|
|
34
|
+
risk_level: "low" | "medium" | "high";
|
|
35
|
+
partial: boolean;
|
|
36
|
+
errors: ToolError[];
|
|
37
|
+
post: Record<string, unknown> | null;
|
|
38
|
+
thread: Record<string, unknown>[];
|
|
39
|
+
comments: Record<string, unknown>[];
|
|
40
|
+
outbound_links: string[];
|
|
41
|
+
evidence: {
|
|
42
|
+
impit_attempted: boolean;
|
|
43
|
+
impit_success: boolean;
|
|
44
|
+
playwright_attempted: boolean;
|
|
45
|
+
playwright_success: boolean;
|
|
46
|
+
generated_at: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export type SmartfetchRenderFormat = "text" | "markdown";
|
|
50
|
+
export declare function asString(value: unknown): string;
|
|
51
|
+
export declare function asNumber(value: unknown): number;
|
|
52
|
+
export declare function decodeHtml(value: string): string;
|
|
53
|
+
export declare function stripTags(value: string): string;
|
|
54
|
+
export declare function trimUrlToken(value: string): string;
|
|
55
|
+
export declare function dedupeUrls(values: string[]): string[];
|
|
56
|
+
export declare function cleanLinks(values: string[]): string[];
|
|
57
|
+
export declare function extractUrls(value: string): string[];
|
|
58
|
+
export declare function extractAnchorHrefs(value: string): string[];
|
|
59
|
+
export declare function uniqueResults(results: SearchResult[]): SearchResult[];
|
|
60
|
+
export declare function resolveDuckDuckGoUrl(input: string): string;
|
|
61
|
+
export declare function capture(value: string, regex: RegExp): string;
|
|
62
|
+
export declare function extractTitleFromHtml(html: string): string;
|
|
63
|
+
export declare function extractMetaDescription(html: string): string;
|
|
64
|
+
export declare function inferTarget(url: string, target: Target): ResolvedTarget;
|
|
65
|
+
export declare function envFlag(name: string): boolean;
|
|
66
|
+
export declare function envEnabled(name: string, defaultValue: boolean): boolean;
|
|
67
|
+
export declare function validateOutboundUrl(url: string, options?: {
|
|
68
|
+
allowPrivateHosts?: boolean;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
ok: false;
|
|
71
|
+
reason: string;
|
|
72
|
+
message: string;
|
|
73
|
+
} | {
|
|
74
|
+
ok: true;
|
|
75
|
+
reason?: never;
|
|
76
|
+
message?: never;
|
|
77
|
+
}>;
|
|
78
|
+
export declare function isXUrl(url: string): boolean;
|
|
79
|
+
export declare function isRedditUrl(url: string): boolean;
|
|
80
|
+
export declare function isDcinsideUrl(url: string): boolean;
|
|
81
|
+
export declare function needsDynamicCrawl(result: AdapterResult): boolean;
|
|
82
|
+
export declare function fetchText(url: string, timeoutMs: number, init?: RequestInit): Promise<{
|
|
83
|
+
ok: boolean;
|
|
84
|
+
status: number;
|
|
85
|
+
text: string;
|
|
86
|
+
error?: never;
|
|
87
|
+
} | {
|
|
88
|
+
ok: boolean;
|
|
89
|
+
status: number;
|
|
90
|
+
text: string;
|
|
91
|
+
error: string;
|
|
92
|
+
}>;
|
|
93
|
+
export declare function fetchJson(url: string, timeoutMs: number, init?: RequestInit): Promise<{
|
|
94
|
+
data: any;
|
|
95
|
+
ok: boolean;
|
|
96
|
+
status: number;
|
|
97
|
+
text: string;
|
|
98
|
+
error?: never;
|
|
99
|
+
} | {
|
|
100
|
+
data: any;
|
|
101
|
+
ok: boolean;
|
|
102
|
+
status: number;
|
|
103
|
+
text: string;
|
|
104
|
+
error: string;
|
|
105
|
+
}>;
|