@svgicons-com/cli 0.1.0-alpha.1
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 +281 -0
- package/RELEASE_NOTES.md +42 -0
- package/bin/svgicons.js +13 -0
- package/package.json +26 -0
- package/src/api.js +194 -0
- package/src/cli.js +1900 -0
- package/src/config.js +134 -0
- package/src/downloads.js +173 -0
- package/src/errors.js +127 -0
- package/src/format.js +121 -0
- package/src/project.js +270 -0
- package/src/redact.js +43 -0
- package/src/scanner.js +247 -0
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Svg/icons CLI Alpha
|
|
2
|
+
|
|
3
|
+
The Svg/icons CLI alpha is a developer workflow client for Pro Plan API tokens. It can search icons, recommend icons for a UI brief, create Icon Collections, add icons to collections, queue exports, and scan a local codebase for UI concepts.
|
|
4
|
+
|
|
5
|
+
The scanner is read-only by default. It never edits project files unless a future command explicitly adds that behavior.
|
|
6
|
+
|
|
7
|
+
Current package version: `0.1.0-alpha.1`.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Node.js 18.18 or newer
|
|
12
|
+
- A Svg/icons Pro API token for collection and export commands
|
|
13
|
+
|
|
14
|
+
## Login
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
svgicons login --token YOUR_PRO_API_TOKEN
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The explicit auth namespace is also supported:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
svgicons auth login --token YOUR_PRO_API_TOKEN
|
|
24
|
+
svgicons auth status
|
|
25
|
+
svgicons doctor
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Use `--base-url http://127.0.0.1:8000` when testing against a local Laravel server.
|
|
29
|
+
|
|
30
|
+
You can also avoid writing a config file by setting `SVGICONS_TOKEN` or `SVGICONS_API_TOKEN`.
|
|
31
|
+
|
|
32
|
+
Inspect local config without exposing the stored token:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
svgicons config list
|
|
36
|
+
svgicons config get baseUrl
|
|
37
|
+
svgicons config set baseUrl https://svgicons.com
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
svgicons version
|
|
44
|
+
svgicons auth login --token YOUR_PRO_API_TOKEN
|
|
45
|
+
svgicons auth status --json
|
|
46
|
+
svgicons config list --json
|
|
47
|
+
svgicons doctor --json
|
|
48
|
+
svgicons search "arrow left" --limit 10
|
|
49
|
+
svgicons search "arrow left" --anonymous
|
|
50
|
+
svgicons recommend "billing settings dashboard" --limit 12
|
|
51
|
+
svgicons recommend "billing settings dashboard" --create-collection --collection-name "Billing icons"
|
|
52
|
+
svgicons pick "settings gear" --download --output ./icons
|
|
53
|
+
svgicons pick "credit card" --add "Billing icons"
|
|
54
|
+
svgicons pick "settings gear" --interactive
|
|
55
|
+
svgicons icon show 33716
|
|
56
|
+
svgicons icon url 33716-arrow-circle-up-fill
|
|
57
|
+
svgicons icon raw 33716-arrow-circle-up-fill
|
|
58
|
+
svgicons icon download 33716-arrow-circle-up-fill --output ./icons
|
|
59
|
+
svgicons collection list
|
|
60
|
+
svgicons collection create --name "Dashboard icons" --description "Navigation and status icons"
|
|
61
|
+
svgicons collection show "Dashboard icons" --icons
|
|
62
|
+
svgicons collection rename "Dashboard icons" --name "Billing icons"
|
|
63
|
+
svgicons collection update "Billing icons" --framework vue --color-policy preserve
|
|
64
|
+
svgicons collection add "Dashboard icons" 33716 240297
|
|
65
|
+
svgicons collection remove "Dashboard icons" 33716-arrow-circle-up-fill
|
|
66
|
+
svgicons collection delete "Dashboard icons" --yes
|
|
67
|
+
svgicons collection export "Dashboard icons" --formats react-ts,vue --color-policy currentColor --output ./exports
|
|
68
|
+
svgicons collection export "Dashboard icons" --formats react-ts --no-size-props --no-typescript --output ./exports
|
|
69
|
+
svgicons export status 55 --collection "Dashboard icons"
|
|
70
|
+
svgicons export download 55 --collection "Dashboard icons" --output ./exports
|
|
71
|
+
svgicons init --collection "Dashboard icons" --output ./src/icons
|
|
72
|
+
svgicons sync
|
|
73
|
+
svgicons build --ci
|
|
74
|
+
svgicons update --check
|
|
75
|
+
svgicons license check --allow MIT,Apache-2.0,ISC --fail
|
|
76
|
+
svgicons license export --format markdown --output THIRD_PARTY_ICONS.md
|
|
77
|
+
svgicons scan .
|
|
78
|
+
svgicons scan . --write-manifest
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use `--json` on commands when integrating with scripts.
|
|
82
|
+
|
|
83
|
+
## Release status
|
|
84
|
+
|
|
85
|
+
This package is an alpha release. It is safe to test in real projects, but command names and JSON response shapes may still change before a stable `1.0.0`.
|
|
86
|
+
|
|
87
|
+
See `RELEASE_NOTES.md` for included workflows, safety notes, and known limitations.
|
|
88
|
+
|
|
89
|
+
Maintainers should use `../docs/svgicons-cli-npm-publishing.md` before publishing a new npm version.
|
|
90
|
+
|
|
91
|
+
## Download one icon
|
|
92
|
+
|
|
93
|
+
Download a single SVG file to the current folder:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
svgicons icon download 33716-arrow-circle-up-fill
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Download to a folder:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
svgicons icon download 33716-arrow-circle-up-fill --output ./icons
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Download to a specific file:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
svgicons icon download 33716-arrow-circle-up-fill --output ./icons/arrow-up.svg
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The icon reference must include both the numeric ID and the icon name, such as `33716-arrow-circle-up-fill`. The CLI checks the returned icon metadata before writing the file. This makes sequential ID-only download scripts less useful.
|
|
112
|
+
|
|
113
|
+
This command uses the MCP `get_icon` tool with raw SVG output, so the token needs `mcp:use` and `icons:read`. Existing files are not overwritten unless you add `--force`.
|
|
114
|
+
|
|
115
|
+
Read-only icon commands accept a numeric ID, an `id-name` reference, or a full svgicons.com icon URL:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
svgicons icon show 33716
|
|
119
|
+
svgicons icon url 33716-arrow-circle-up-fill
|
|
120
|
+
svgicons icon raw https://svgicons.com/icon/33716/arrow-circle-up-fill
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Recommend and pick icons
|
|
124
|
+
|
|
125
|
+
Ask Svg/icons for icon candidates based on a UI brief:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
svgicons recommend "billing settings dashboard with invoices, payment methods, and security"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`recommend` uses the hosted MCP `recommend_icons_for_ui` tool. It returns metadata by default, so it can work without writing files. Add `--json` for automation.
|
|
132
|
+
|
|
133
|
+
Create a Pro Icon Collection directly from a recommendation:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
svgicons recommend "billing settings dashboard" --create-collection --collection-name "Billing icons"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This uses the MCP `generate_icon_kit_for_project` tool and requires a Pro token with `mcp:use` and `collections:write`.
|
|
140
|
+
|
|
141
|
+
For deterministic scripts, `pick` selects the first search result:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
svgicons pick "settings gear"
|
|
145
|
+
svgicons pick "settings gear" --download --output ./icons
|
|
146
|
+
svgicons pick "credit card" --add "Billing icons"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use `--download` when you want the selected SVG written locally. Use `--add <collection>` when you want the selected icon added to an Icon Collection.
|
|
150
|
+
|
|
151
|
+
For manual terminal selection, opt in to the interactive picker:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
svgicons pick "settings gear" --interactive
|
|
155
|
+
svgicons pick "settings gear" --interactive --download --output ./icons
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`--interactive` requires a real terminal and is rejected in CI/non-interactive shells. Omit it for deterministic scripts.
|
|
159
|
+
|
|
160
|
+
## Export and download a collection ZIP
|
|
161
|
+
|
|
162
|
+
Queue an export, wait for the background job to finish, and download the ZIP:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
svgicons collection export "Dashboard icons" --formats react-ts,vue --output ./exports
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The command polls every 2 seconds for up to 180 seconds by default. Use `--timeout 300` for larger collections, or `--no-wait` if you only want to queue the export and manually download it later.
|
|
169
|
+
|
|
170
|
+
Supported export flags:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
--formats react-ts,vue
|
|
174
|
+
--color-policy currentColor|preserve|strip
|
|
175
|
+
--naming-policy kebab|pascal|camel
|
|
176
|
+
--size-props / --no-size-props
|
|
177
|
+
--typescript / --no-typescript
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Collection commands accept a numeric ID, exact slug, or exact case-insensitive collection name. The legacy `kit` alias remains available for old scripts.
|
|
181
|
+
|
|
182
|
+
Lifecycle commands are available for collection maintenance:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
svgicons collection show "Dashboard icons" --icons
|
|
186
|
+
svgicons collection update "Dashboard icons" --description "Core product UI icons"
|
|
187
|
+
svgicons collection rename "Dashboard icons" --name "Billing icons"
|
|
188
|
+
svgicons collection remove "Billing icons" 33716-arrow-circle-up-fill
|
|
189
|
+
svgicons collection delete "Billing icons" --yes
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`collection delete` prompts for confirmation in an interactive shell. Use `--yes` in CI or scripts.
|
|
193
|
+
|
|
194
|
+
If you queued an export with `--no-wait`, check and download it later:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
svgicons export status 55 --collection "Dashboard icons"
|
|
198
|
+
svgicons export download 55 --collection "Dashboard icons" --output ./exports
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Local project manifest and lockfile
|
|
202
|
+
|
|
203
|
+
Initialize a local project:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
svgicons init --collection "Dashboard icons" --output ./src/icons
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This creates `svgicons.json`. The manifest can reference individual icon IDs or Icon Collections:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"version": 1,
|
|
214
|
+
"format": "svg",
|
|
215
|
+
"output": "./src/icons",
|
|
216
|
+
"icons": [{ "ref": "33716-arrow-circle-up-fill" }],
|
|
217
|
+
"collections": [{ "ref": "Dashboard icons" }]
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Sync the manifest into a deterministic lockfile:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
svgicons sync
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This writes `svgicons.lock` with icon SVG markup, source icon set metadata, license data, and SVG hashes. Build local SVG files from the lockfile:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
svgicons build --ci
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`build` uses only `svgicons.lock`, so CI builds do not depend on live API responses. Re-running `build` writes stable SVG files with stable names.
|
|
234
|
+
|
|
235
|
+
Check whether locked icons changed upstream:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
svgicons update --check
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## License audit
|
|
242
|
+
|
|
243
|
+
Check local icon licenses from `svgicons.lock`:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
svgicons license check --allow MIT,Apache-2.0,ISC,CC0-1.0 --fail
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Use `--deny` for forbidden licenses:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
svgicons license check --deny GPL,CC-BY-NC-SA-4.0 --fail
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Export a license summary for your repository:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
svgicons license export --format markdown --output THIRD_PARTY_ICONS.md
|
|
259
|
+
svgicons license export --format json --output third-party-icons.json
|
|
260
|
+
svgicons license export --format csv --output third-party-icons.csv
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Scanner
|
|
264
|
+
|
|
265
|
+
The scanner reads common frontend files, detects UI concepts such as dashboard, search, billing, upload, auth, and settings, and reports a proposed Icon Collection. It also detects existing `svgicons.com/icon/{id}/{name}` links, generated SVG imports, and inline SVG blocks.
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
svgicons scan ./src --max-files 300
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
To update a local `svgicons.json` manifest from detected icon refs, opt in explicitly:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
svgicons scan . --write-manifest
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
To create a collection from existing icon URLs found in the scan, opt in explicitly:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
svgicons scan . --create-collection
|
|
281
|
+
```
|
package/RELEASE_NOTES.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Svg/icons CLI 0.1.0-alpha.1
|
|
2
|
+
|
|
3
|
+
This alpha release prepares the CLI for real Pro Plan workflows against the svgicons.com Laravel backend.
|
|
4
|
+
|
|
5
|
+
## Included
|
|
6
|
+
|
|
7
|
+
- Token login/logout plus `auth status`, known scopes, config inspection, and `doctor`.
|
|
8
|
+
- Public icon search through the hosted MCP endpoint, with authenticated search when a token is configured.
|
|
9
|
+
- Icon metadata, URL, raw SVG, browser open, and strict `id-name` SVG download.
|
|
10
|
+
- Icon Collection list, create, show, rename, update, add, remove, delete, export, status, and ZIP download.
|
|
11
|
+
- Backward-compatible `kit` alias for collection commands.
|
|
12
|
+
- Local `svgicons.json` manifest, deterministic `svgicons.lock`, SVG build output, update checks, and license audits.
|
|
13
|
+
- Project scanner that detects UI concepts, svgicons.com URLs, generated SVG imports, and inline SVG blocks.
|
|
14
|
+
- `recommend` for UI-brief icon recommendations and Pro generated collections.
|
|
15
|
+
- `pick` for deterministic first-result selection, plus opt-in interactive terminal selection.
|
|
16
|
+
|
|
17
|
+
## Safety Notes
|
|
18
|
+
|
|
19
|
+
- The scanner is read-only by default. It writes only when `--write-manifest` or `--create-collection` is used.
|
|
20
|
+
- Destructive collection deletion requires confirmation or `--yes`.
|
|
21
|
+
- `--interactive` is rejected in CI/non-interactive shells.
|
|
22
|
+
- Tokens and authorization headers are redacted from config and diagnostic output.
|
|
23
|
+
- SVG download remains strict: the reference must include both icon ID and name.
|
|
24
|
+
|
|
25
|
+
## Known Limitations
|
|
26
|
+
|
|
27
|
+
- The package is still alpha. Command names and JSON response shapes may change before a stable `1.0.0`.
|
|
28
|
+
- The interactive picker is intentionally simple and terminal-only.
|
|
29
|
+
- Search and recommendation quality depend on the current MCP metadata search implementation.
|
|
30
|
+
- Local `build` currently writes SVG files. Framework component ZIPs are produced through collection exports.
|
|
31
|
+
- Live Pro workflows require an active Pro account, verified email, and API token scopes matching the command.
|
|
32
|
+
|
|
33
|
+
## Verification Checklist
|
|
34
|
+
|
|
35
|
+
Before publishing, run:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm --prefix cli test
|
|
39
|
+
npm pack --dry-run
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
When backend changes are also included, run the relevant Laravel tests for Pro API tokens, MCP, Icon Collections, and collection exports.
|
package/bin/svgicons.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { run } from '../src/cli.js';
|
|
4
|
+
import { serializeError } from '../src/errors.js';
|
|
5
|
+
|
|
6
|
+
run(process.argv.slice(2)).catch((error) => {
|
|
7
|
+
if (process.argv.includes('--json')) {
|
|
8
|
+
console.error(JSON.stringify(serializeError(error), null, 2));
|
|
9
|
+
} else {
|
|
10
|
+
console.error(error?.message || String(error));
|
|
11
|
+
}
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@svgicons-com/cli",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Svg/icons CLI alpha for icon search, Pro collections, exports, project scanning, and license workflows.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"svgicons": "./bin/svgicons.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"RELEASE_NOTES.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test tests"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18.18"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"license": "UNLICENSED",
|
|
25
|
+
"private": false
|
|
26
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { credentials } from './config.js';
|
|
2
|
+
import { httpError, invalidJsonError, mcpError, missingTokenError, networkError } from './errors.js';
|
|
3
|
+
|
|
4
|
+
let rpcId = 1;
|
|
5
|
+
|
|
6
|
+
export async function callMcpTool(name, args = {}, options = {}) {
|
|
7
|
+
const response = await mcpRequest('tools/call', {
|
|
8
|
+
name,
|
|
9
|
+
arguments: args,
|
|
10
|
+
}, options);
|
|
11
|
+
|
|
12
|
+
const result = response.result || {};
|
|
13
|
+
const content = result.structuredContent || parseMcpText(result.content);
|
|
14
|
+
|
|
15
|
+
if (result.isError || content?.error) {
|
|
16
|
+
throw mcpError(content?.error?.message || 'MCP tool returned an error', {
|
|
17
|
+
tool: name,
|
|
18
|
+
error: content?.error,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return content;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function mcpRequest(method, params = {}, options = {}) {
|
|
26
|
+
const creds = await credentials();
|
|
27
|
+
const token = options.token ?? creds.token;
|
|
28
|
+
const baseUrl = options.baseUrl ?? creds.baseUrl;
|
|
29
|
+
|
|
30
|
+
const response = await request(`${baseUrl}/mcp`, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
token,
|
|
33
|
+
body: {
|
|
34
|
+
jsonrpc: '2.0',
|
|
35
|
+
id: rpcId++,
|
|
36
|
+
method,
|
|
37
|
+
params,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (response.error) {
|
|
42
|
+
throw mcpError(response.error.message || 'MCP request failed', {
|
|
43
|
+
code: response.error.code,
|
|
44
|
+
data: response.error.data,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function proApi(path, options = {}) {
|
|
52
|
+
const creds = await credentials();
|
|
53
|
+
const token = options.token ?? creds.token;
|
|
54
|
+
const baseUrl = options.baseUrl ?? creds.baseUrl;
|
|
55
|
+
|
|
56
|
+
if (!token) {
|
|
57
|
+
throw missingTokenError();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return request(`${baseUrl}${path}`, {
|
|
61
|
+
...options,
|
|
62
|
+
token,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function proApiDownload(pathOrUrl, options = {}) {
|
|
67
|
+
const creds = await credentials();
|
|
68
|
+
const token = options.token ?? creds.token;
|
|
69
|
+
const baseUrl = options.baseUrl ?? creds.baseUrl;
|
|
70
|
+
|
|
71
|
+
if (!token) {
|
|
72
|
+
throw missingTokenError();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const url = /^https?:\/\//i.test(pathOrUrl)
|
|
76
|
+
? pathOrUrl
|
|
77
|
+
: `${baseUrl}${pathOrUrl}`;
|
|
78
|
+
|
|
79
|
+
let response;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
response = await fetch(url, {
|
|
83
|
+
method: options.method || 'GET',
|
|
84
|
+
headers: {
|
|
85
|
+
Accept: 'application/zip, application/octet-stream',
|
|
86
|
+
Authorization: `Bearer ${token}`,
|
|
87
|
+
'User-Agent': 'svgicons-cli/0.1.0-alpha',
|
|
88
|
+
...options.headers,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw networkError(error, url);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
const text = await response.text();
|
|
97
|
+
let data = {};
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
data = text ? JSON.parse(text) : {};
|
|
101
|
+
} catch {
|
|
102
|
+
data = {};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw httpError(response.status, url, data);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
bytes: Buffer.from(await response.arrayBuffer()),
|
|
110
|
+
filename: filenameFromContentDisposition(response.headers.get('content-disposition')),
|
|
111
|
+
contentType: response.headers.get('content-type'),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function request(url, options = {}) {
|
|
116
|
+
const headers = {
|
|
117
|
+
Accept: 'application/json',
|
|
118
|
+
'User-Agent': 'svgicons-cli/0.1.0-alpha',
|
|
119
|
+
...options.headers,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const init = {
|
|
123
|
+
method: options.method || 'GET',
|
|
124
|
+
headers,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (options.token) {
|
|
128
|
+
headers.Authorization = `Bearer ${options.token}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (options.body !== undefined) {
|
|
132
|
+
headers['Content-Type'] = 'application/json';
|
|
133
|
+
init.body = JSON.stringify(options.body);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let response;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
response = await fetch(url, init);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw networkError(error, url);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const text = await response.text();
|
|
145
|
+
const data = text ? safeJson(text, url) : {};
|
|
146
|
+
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw httpError(response.status, url, data);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return data;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function filenameFromContentDisposition(header) {
|
|
155
|
+
if (!header) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const utf8Match = header.match(/filename\*=UTF-8''([^;]+)/i);
|
|
160
|
+
if (utf8Match) {
|
|
161
|
+
return decodeURIComponent(utf8Match[1].trim().replace(/^"|"$/g, ''));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const asciiMatch = header.match(/filename="?([^";]+)"?/i);
|
|
165
|
+
if (asciiMatch) {
|
|
166
|
+
return asciiMatch[1].trim();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function safeJson(text, url) {
|
|
173
|
+
try {
|
|
174
|
+
return JSON.parse(text);
|
|
175
|
+
} catch {
|
|
176
|
+
throw invalidJsonError(url);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function parseMcpText(content) {
|
|
181
|
+
const firstText = Array.isArray(content)
|
|
182
|
+
? content.find((item) => item?.type === 'text')?.text
|
|
183
|
+
: '';
|
|
184
|
+
|
|
185
|
+
if (!firstText) {
|
|
186
|
+
return {};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
return JSON.parse(firstText);
|
|
191
|
+
} catch {
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
194
|
+
}
|