@svgicons-com/cli 0.1.0-alpha.2 → 0.1.0-alpha.4

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
@@ -8,7 +8,7 @@ Public icon search works without changing the default endpoint. Pro workflows re
8
8
 
9
9
  The scanner is read-only by default. It never edits project files unless a future command explicitly adds that behavior.
10
10
 
11
- Current package version: `0.1.0-alpha.2`.
11
+ Current package version: `0.1.0-alpha.3`.
12
12
 
13
13
  ## Requirements
14
14
 
@@ -18,17 +18,19 @@ Current package version: `0.1.0-alpha.2`.
18
18
  ## Login
19
19
 
20
20
  ```bash
21
- svgicons login --token YOUR_PRO_API_TOKEN
21
+ svgicons login --token "YOUR_PRO_API_TOKEN"
22
22
  ```
23
23
 
24
24
  The explicit auth namespace is also supported:
25
25
 
26
26
  ```bash
27
- svgicons auth login --token YOUR_PRO_API_TOKEN
27
+ svgicons auth login --token "YOUR_PRO_API_TOKEN"
28
28
  svgicons auth status
29
29
  svgicons doctor
30
30
  ```
31
31
 
32
+ Pro API tokens may start with a numeric prefix and a pipe character, such as `10|...`. Keep the full token and wrap it in quotes when using a shell command.
33
+
32
34
  You can also avoid writing a config file by setting `SVGICONS_TOKEN` or `SVGICONS_API_TOKEN`.
33
35
 
34
36
  Inspect local config without exposing the stored token:
@@ -43,7 +45,7 @@ svgicons config set baseUrl https://svgicons.com
43
45
 
44
46
  ```bash
45
47
  svgicons version
46
- svgicons auth login --token YOUR_PRO_API_TOKEN
48
+ svgicons auth login --token "YOUR_PRO_API_TOKEN"
47
49
  svgicons auth status --json
48
50
  svgicons config list --json
49
51
  svgicons doctor --json
@@ -58,6 +60,7 @@ svgicons icon show 33716
58
60
  svgicons icon url 33716-arrow-circle-up-fill
59
61
  svgicons icon raw 33716-arrow-circle-up-fill
60
62
  svgicons icon download 33716-arrow-circle-up-fill --output ./icons
63
+ svgicons icon png 33716-arrow-circle-up-fill --size 512 --output ./icons
61
64
  svgicons collection list
62
65
  svgicons collection create --name "Dashboard icons" --description "Navigation and status icons"
63
66
  svgicons collection show "Dashboard icons" --icons
@@ -67,6 +70,7 @@ svgicons collection add "Dashboard icons" 33716 240297
67
70
  svgicons collection remove "Dashboard icons" 33716-arrow-circle-up-fill
68
71
  svgicons collection delete "Dashboard icons" --yes
69
72
  svgicons collection export "Dashboard icons" --formats react-ts,vue --color-policy currentColor --output ./exports
73
+ svgicons collection export "Dashboard icons" --formats png-pack --png-sizes 24,48,512 --output ./exports
70
74
  svgicons collection export "Dashboard icons" --formats react-ts --no-size-props --no-typescript --output ./exports
71
75
  svgicons export status 55 --collection "Dashboard icons"
72
76
  svgicons export download 55 --collection "Dashboard icons" --output ./exports
@@ -114,6 +118,16 @@ The icon reference must include both the numeric ID and the icon name, such as `
114
118
 
115
119
  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`.
116
120
 
121
+ Export a Pro PNG asset from a single icon:
122
+
123
+ ```bash
124
+ svgicons icon png 33716-arrow-circle-up-fill --size 512 --output ./icons
125
+ svgicons icon png 33716-arrow-circle-up-fill --sizes 128,256,512 --density 1,2 --zip --output ./icons
126
+ svgicons icon png 33716-arrow-circle-up-fill --size 512 --color black --background solid --background-color "#ffffff"
127
+ ```
128
+
129
+ Single-icon PNG export uses the Pro REST API, not the public anonymous search API. The token needs `icons:read` and `exports:create`. The icon reference must include both ID and name, and the server verifies that the name matches before rendering.
130
+
117
131
  Read-only icon commands accept a numeric ID, an `id-name` reference, or a full svgicons.com icon URL:
118
132
 
119
133
  ```bash
@@ -172,13 +186,22 @@ The command polls every 2 seconds for up to 180 seconds by default. Use `--timeo
172
186
  Supported export flags:
173
187
 
174
188
  ```bash
175
- --formats react-ts,vue
189
+ --formats react-ts,vue,png-pack
176
190
  --color-policy currentColor|preserve|strip
177
191
  --naming-policy kebab|pascal|camel
178
192
  --size-props / --no-size-props
179
193
  --typescript / --no-typescript
194
+ --png-sizes 24,48,512
195
+ --png-densities 1,2
196
+ --png-background transparent|solid
197
+ --png-background-color "#ffffff"
198
+ --png-color preserve|black|white|custom
199
+ --png-icon-color "#2563eb"
200
+ --png-padding 48
180
201
  ```
181
202
 
203
+ Use `--formats png` as a shorthand for `--formats png-pack`. PNG pack exports are generated by the server queue worker and downloaded as part of the collection ZIP.
204
+
182
205
  Collection commands accept a numeric ID, exact slug, or exact case-insensitive collection name. The legacy `kit` alias remains available for old scripts.
183
206
 
184
207
  Lifecycle commands are available for collection maintenance:
package/RELEASE_NOTES.md CHANGED
@@ -1,4 +1,8 @@
1
- # Svg/icons CLI 0.1.0-alpha.2
1
+ # Svg/icons CLI 0.1.0-alpha.3
2
+
3
+ This alpha refresh improves token setup safety. `login` now rejects obviously truncated numeric tokens, which commonly happens when a shell command is run without quoting a Sanctum-style token containing `|`.
4
+
5
+ ## 0.1.0-alpha.2
2
6
 
3
7
  This alpha refresh removes local-development wording from the public npm README. Runtime behavior is unchanged from `0.1.0-alpha.1`.
4
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svgicons-com/cli",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "Svg/icons CLI alpha for icon search, Pro collections, exports, project scanning, and license workflows.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -79,14 +79,26 @@ export async function proApiDownload(pathOrUrl, options = {}) {
79
79
  let response;
80
80
 
81
81
  try {
82
- response = await fetch(url, {
82
+ const headers = {
83
+ Accept: 'image/png, application/zip, application/octet-stream',
84
+ Authorization: `Bearer ${token}`,
85
+ 'User-Agent': 'svgicons-cli/0.1.0-alpha',
86
+ ...options.headers,
87
+ };
88
+ const init = {
83
89
  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
+ headers,
91
+ };
92
+
93
+ if (options.body !== undefined) {
94
+ headers['Content-Type'] = headers['Content-Type'] || 'application/json';
95
+ init.body = typeof options.body === 'string' || options.body instanceof Buffer
96
+ ? options.body
97
+ : JSON.stringify(options.body);
98
+ }
99
+
100
+ response = await fetch(url, {
101
+ ...init,
90
102
  });
91
103
  } catch (error) {
92
104
  throw networkError(error, url);
package/src/cli.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  parseIconReference,
23
23
  resolveArchiveDownloadPath,
24
24
  resolveIconDownloadPath,
25
+ resolvePngDownloadPath,
25
26
  sanitizeFileSegment,
26
27
  writeBinaryFile,
27
28
  writeIconSvg,
@@ -56,10 +57,15 @@ const KNOWN_SCOPES = [
56
57
  'exports:create',
57
58
  'mcp:use',
58
59
  ];
59
- const EXPORT_FORMATS = ['svg-folder', 'svg-sprite', 'json-manifest', 'license-manifest', 'react-ts', 'vue'];
60
+ const EXPORT_FORMATS = ['svg-folder', 'svg-sprite', 'json-manifest', 'license-manifest', 'react-ts', 'vue', 'png-pack'];
60
61
  const FRAMEWORKS = ['svg', 'react-ts', 'vue', 'sprite'];
61
62
  const COLOR_POLICIES = ['currentColor', 'preserve', 'strip'];
62
63
  const NAMING_POLICIES = ['kebab', 'pascal', 'camel'];
64
+ const PNG_SIZES = [16, 24, 32, 48, 96, 128, 256, 512, 1024];
65
+ const PNG_DENSITIES = [1, 2, 3, 4];
66
+ const PNG_BACKGROUNDS = ['transparent', 'solid'];
67
+ const PNG_COLORS = ['preserve', 'black', 'white', 'custom'];
68
+ const PNG_FITS = ['contain'];
63
69
 
64
70
  export async function run(argv) {
65
71
  const { args, options: globalOptions } = extractGlobalOptions(argv);
@@ -181,11 +187,9 @@ export async function run(argv) {
181
187
 
182
188
  async function login(args, globalOptions = {}) {
183
189
  const options = parseOptions(args);
184
- const token = options.token || globalOptions.token || process.env.SVGICONS_TOKEN || process.env.SVGICONS_API_TOKEN;
190
+ const rawToken = options.token || globalOptions.token || process.env.SVGICONS_TOKEN || process.env.SVGICONS_API_TOKEN;
185
191
 
186
- if (!token) {
187
- throw new Error('Missing token. Use: svgicons login --token <token>');
188
- }
192
+ const token = normalizeLoginToken(rawToken);
189
193
 
190
194
  const baseUrl = normalizeBaseUrl(options['base-url'] || globalOptions['base-url'] || defaultBaseUrl());
191
195
  await writeConfig({
@@ -197,6 +201,28 @@ async function login(args, globalOptions = {}) {
197
201
  console.log(`Svg/icons CLI token saved for ${baseUrl}.`);
198
202
  }
199
203
 
204
+ function normalizeLoginToken(token) {
205
+ if (!token) {
206
+ throw usageError('Missing token. Use: svgicons login --token "<token>"');
207
+ }
208
+
209
+ const value = String(token).trim();
210
+
211
+ if (value === '') {
212
+ throw usageError('Missing token. Use: svgicons login --token "<token>"');
213
+ }
214
+
215
+ if (/^\d+$/.test(value)) {
216
+ throw usageError('The token looks truncated. Svg/icons Pro API tokens can start with an id prefix like "10|". Wrap the full token in quotes: svgicons auth login --token "10|..."');
217
+ }
218
+
219
+ if (value.length < 20) {
220
+ throw usageError('The token value is too short. Copy the full Pro API token from your account page and wrap it in quotes when it contains "|".');
221
+ }
222
+
223
+ return value;
224
+ }
225
+
200
226
  async function logout() {
201
227
  await clearConfig();
202
228
  console.log('Svg/icons CLI credentials removed.');
@@ -613,7 +639,12 @@ async function icon(args) {
613
639
  return;
614
640
  }
615
641
 
616
- throw new Error('Unknown icon command. Use: show, raw, url, open, download.');
642
+ if (subcommand === 'png') {
643
+ await iconPng(rest);
644
+ return;
645
+ }
646
+
647
+ throw new Error('Unknown icon command. Use: show, raw, url, open, download, png.');
617
648
  }
618
649
 
619
650
  async function iconShow(args) {
@@ -695,6 +726,40 @@ async function iconDownload(args) {
695
726
  console.log(` ${outputPath}`);
696
727
  }
697
728
 
729
+ async function iconPng(args) {
730
+ const options = parseOptions(args);
731
+ const [iconReference] = options._;
732
+ const reference = parseIconReference(iconReference);
733
+ const payload = buildIconPngPayload(reference, options);
734
+ const download = await proApiDownload(`/api/pro/icons/${encodeURIComponent(reference.id)}/png-export`, {
735
+ method: 'POST',
736
+ body: payload,
737
+ headers: {
738
+ 'Content-Type': 'application/json',
739
+ },
740
+ });
741
+ const outputPath = resolvePngDownloadPath(options.output || options.o, download.filename, process.cwd());
742
+
743
+ await writeBinaryFile(outputPath, download.bytes, booleanOption(options.force, false));
744
+
745
+ if (options.json) {
746
+ printJson({
747
+ icon: {
748
+ id: reference.id,
749
+ name: reference.slug,
750
+ },
751
+ outputPath,
752
+ contentType: download.contentType,
753
+ filename: download.filename,
754
+ options: payload,
755
+ });
756
+ return;
757
+ }
758
+
759
+ console.log(`Downloaded PNG export`);
760
+ console.log(` ${outputPath}`);
761
+ }
762
+
698
763
  async function collection(args) {
699
764
  const [subcommand, ...rest] = args;
700
765
 
@@ -1810,17 +1875,104 @@ function extensionSet(value) {
1810
1875
  }
1811
1876
 
1812
1877
  function buildExportOptions(options) {
1878
+ const formats = exportFormatsOption(options.formats);
1879
+ const png = buildCollectionPngOptions(options);
1880
+ const includesPng = formats?.includes('png-pack') || Object.keys(png).length > 0;
1881
+ const normalizedFormats = includesPng && formats === undefined
1882
+ ? ['png-pack']
1883
+ : formats;
1884
+
1813
1885
  return {
1814
- formats: enumCsvOption(options.formats, 'export format', EXPORT_FORMATS),
1886
+ formats: normalizedFormats,
1815
1887
  options: {
1816
1888
  colorPolicy: enumOption(options['color-policy'], 'color policy', COLOR_POLICIES),
1817
1889
  namingPolicy: enumOption(options['naming-policy'], 'naming policy', NAMING_POLICIES),
1818
1890
  sizeProps: booleanFlagPair(options, 'size-props', 'no-size-props'),
1819
1891
  typescript: booleanFlagPair(options, 'typescript', 'no-typescript'),
1892
+ png: includesPng ? png : undefined,
1820
1893
  },
1821
1894
  };
1822
1895
  }
1823
1896
 
1897
+ function buildIconPngPayload(reference, options) {
1898
+ return {
1899
+ iconName: reference.slug,
1900
+ sizes: integerCsvOption(options.sizes ?? options.size, 'PNG size', PNG_SIZES, [512]),
1901
+ densities: integerCsvOption(options.densities ?? options.density, 'PNG density', PNG_DENSITIES, [1]),
1902
+ backgroundType: enumOption(options.background, 'PNG background', PNG_BACKGROUNDS),
1903
+ backgroundColor: options['background-color'],
1904
+ iconColorMode: enumOption(options.color, 'PNG icon color mode', PNG_COLORS),
1905
+ iconColor: options['icon-color'],
1906
+ padding: integerOption(options.padding, 'PNG padding'),
1907
+ fit: enumOption(options.fit, 'PNG fit mode', PNG_FITS),
1908
+ filename: options.filename,
1909
+ zip: booleanOption(options.zip, undefined),
1910
+ };
1911
+ }
1912
+
1913
+ function buildCollectionPngOptions(options) {
1914
+ return stripUndefined({
1915
+ sizes: integerCsvOption(options['png-sizes'] ?? options['png-size'], 'PNG size', PNG_SIZES, undefined),
1916
+ densities: integerCsvOption(options['png-densities'] ?? options['png-density'], 'PNG density', [1, 2, 3], undefined),
1917
+ backgroundType: enumOption(options['png-background'], 'PNG background', PNG_BACKGROUNDS),
1918
+ backgroundColor: options['png-background-color'],
1919
+ iconColorMode: enumOption(options['png-color'], 'PNG icon color mode', PNG_COLORS),
1920
+ iconColor: options['png-icon-color'],
1921
+ padding: integerOption(options['png-padding'], 'PNG padding'),
1922
+ fit: enumOption(options['png-fit'], 'PNG fit mode', PNG_FITS),
1923
+ });
1924
+ }
1925
+
1926
+ function exportFormatsOption(value) {
1927
+ const values = csvOption(value);
1928
+
1929
+ if (!values) {
1930
+ return undefined;
1931
+ }
1932
+
1933
+ const normalized = values.map((item) => item === 'png' ? 'png-pack' : item);
1934
+ const invalid = normalized.filter((item) => !EXPORT_FORMATS.includes(item));
1935
+ if (invalid.length > 0) {
1936
+ throw usageError(`Invalid export format: ${invalid.join(', ')}. Allowed values: ${EXPORT_FORMATS.join(', ')}, png`);
1937
+ }
1938
+
1939
+ return [...new Set(normalized)];
1940
+ }
1941
+
1942
+ function integerCsvOption(value, label, allowed, fallback) {
1943
+ const values = csvOption(value);
1944
+
1945
+ if (!values) {
1946
+ return fallback;
1947
+ }
1948
+
1949
+ const parsed = values.map((item) => Number(item));
1950
+ const invalid = parsed.filter((item) => !Number.isInteger(item) || !allowed.includes(item));
1951
+
1952
+ if (invalid.length > 0) {
1953
+ throw usageError(`Invalid ${label}: ${invalid.join(', ')}. Allowed values: ${allowed.join(', ')}`);
1954
+ }
1955
+
1956
+ return [...new Set(parsed)];
1957
+ }
1958
+
1959
+ function integerOption(value, label) {
1960
+ if (value === undefined || value === true || value === '') {
1961
+ return undefined;
1962
+ }
1963
+
1964
+ const parsed = Number(value);
1965
+ if (!Number.isInteger(parsed)) {
1966
+ throw usageError(`Invalid ${label}: ${value}. Expected an integer.`);
1967
+ }
1968
+
1969
+ return parsed;
1970
+ }
1971
+
1972
+ function stripUndefined(value) {
1973
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
1974
+ }
1975
+
1824
1976
  function enumOption(value, label, allowed) {
1825
1977
  if (value === undefined || value === true || value === '') {
1826
1978
  return undefined;
@@ -1874,6 +2026,7 @@ Usage:
1874
2026
  svgicons icon url <icon-ref>
1875
2027
  svgicons icon open <icon-ref>
1876
2028
  svgicons icon download <icon-id-name> [--output ./icons] [--force]
2029
+ svgicons icon png <icon-id-name> [--size 512] [--density 1,2] [--output ./icons] [--force]
1877
2030
  svgicons collection list [--json]
1878
2031
  svgicons collection create --name "Dashboard icons"
1879
2032
  svgicons collection show <collection-id-or-name> [--icons] [--json]
@@ -1882,7 +2035,7 @@ Usage:
1882
2035
  svgicons collection add <collection-id-or-name> <icon-id...>
1883
2036
  svgicons collection remove <collection-id-or-name> <icon-id...>
1884
2037
  svgicons collection delete <collection-id-or-name> [--yes]
1885
- svgicons collection export <collection-id-or-name> [--formats react-ts,vue] [--no-size-props] [--no-typescript] [--output ./exports]
2038
+ svgicons collection export <collection-id-or-name> [--formats react-ts,vue,png-pack] [--png-sizes 24,48,512] [--no-size-props] [--no-typescript] [--output ./exports]
1886
2039
  svgicons export status <export-id> --collection <collection-id-or-name>
1887
2040
  svgicons export download <export-id> --collection <collection-id-or-name> [--output ./exports]
1888
2041
  svgicons init [--collection <collection-id-or-name>] [--output svgicons-icons]
package/src/downloads.js CHANGED
@@ -95,6 +95,25 @@ export function resolveArchiveDownloadPath(output, filename, cwd = process.cwd()
95
95
  return join(target, safeFilename);
96
96
  }
97
97
 
98
+ export function resolvePngDownloadPath(output, filename, cwd = process.cwd()) {
99
+ const safeFilename = sanitizeFileName(filename || 'svgicons-icon.png', 'png');
100
+
101
+ if (!output || output === true) {
102
+ return resolve(cwd, safeFilename);
103
+ }
104
+
105
+ const target = isAbsolute(String(output))
106
+ ? String(output)
107
+ : resolve(cwd, String(output));
108
+
109
+ const extension = extname(target).toLowerCase();
110
+ if (extension === '.png' || extension === '.zip') {
111
+ return target;
112
+ }
113
+
114
+ return join(target, safeFilename);
115
+ }
116
+
98
117
  export async function writeIconSvg(path, svg, force = false) {
99
118
  if (!svg || typeof svg !== 'string') {
100
119
  throw new Error('The icon response did not include SVG markup.');