@nucleoicons/skills 0.1.5 → 0.1.7
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 +12 -2
- package/dist/api.d.ts +5 -0
- package/dist/api.js +20 -1
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +31 -10
- package/dist/commands/status.js +1 -0
- package/dist/commands/update.js +27 -4
- package/dist/config.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/install.d.ts +6 -0
- package/dist/install.js +40 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ CLI to install and update **Nucleo** MCP + icon skills on your machine.
|
|
|
4
4
|
|
|
5
5
|
Once installed, your AI coding tool can help you **use Nucleo icons in code**. Typical uses:
|
|
6
6
|
|
|
7
|
-
- Search icons by name or idea across your
|
|
7
|
+
- Search icons by name or idea across your installed families (Pro and/or free)
|
|
8
8
|
- Pick an icon and get the right React component or SVG for your project
|
|
9
9
|
- Customize icons when inserting them (color, secondary color, stroke width, duo opacity, corners)
|
|
10
10
|
|
|
@@ -14,15 +14,23 @@ Requires **Node.js 20+**.
|
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
+
**Pro** (Nucleo account — licensed families):
|
|
18
|
+
|
|
17
19
|
```bash
|
|
18
20
|
npx @nucleoicons/skills setup --target react --tools cursor
|
|
19
21
|
```
|
|
20
22
|
|
|
23
|
+
**Free** (no account — glass, arcade, isometric, flags, cursors, social-media, credit-cards):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx @nucleoicons/skills setup --free --target react --tools cursor
|
|
27
|
+
```
|
|
28
|
+
|
|
21
29
|
**Required for setup:** `--target` (`react` or `vanilla`) and `--tools` (`cursor`, `claude`, `codex`).
|
|
22
30
|
|
|
23
31
|
```bash
|
|
24
32
|
npx @nucleoicons/skills setup --target react --tools cursor
|
|
25
|
-
npx @nucleoicons/skills setup --target vanilla --tools cursor,claude
|
|
33
|
+
npx @nucleoicons/skills setup --free --target vanilla --tools cursor,claude
|
|
26
34
|
```
|
|
27
35
|
|
|
28
36
|
Setup installs the package under your user home (`~/.nucleo/skills`) and configures the editors you choose (skills + MCP, no manual config step).
|
|
@@ -35,6 +43,8 @@ What Nucleo icon families do I have?
|
|
|
35
43
|
|
|
36
44
|
## Update
|
|
37
45
|
|
|
46
|
+
Same command for free and Pro — the CLI remembers which package you installed (`--free` is not needed again):
|
|
47
|
+
|
|
38
48
|
```bash
|
|
39
49
|
npx @nucleoicons/skills update
|
|
40
50
|
```
|
package/dist/api.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface SkillsReleaseFile {
|
|
|
27
27
|
export interface SkillsReleaseData {
|
|
28
28
|
version: string;
|
|
29
29
|
target: PackageTarget | string;
|
|
30
|
+
tier?: "free" | "pro" | string;
|
|
30
31
|
families: string[];
|
|
31
32
|
downloadReady: boolean;
|
|
32
33
|
files: SkillsReleaseFile[];
|
|
@@ -47,6 +48,10 @@ export declare function skillsLogin(email: string, password: string): Promise<Sk
|
|
|
47
48
|
* Response: { data: { version, families, files, downloadReady } }
|
|
48
49
|
*/
|
|
49
50
|
export declare function fetchSkillsRelease(token: string, target: PackageTarget): Promise<SkillsReleaseData>;
|
|
51
|
+
/**
|
|
52
|
+
* GET /skills/release-free?target=react|vanilla (no auth)
|
|
53
|
+
*/
|
|
54
|
+
export declare function fetchSkillsFreeRelease(target: PackageTarget): Promise<SkillsReleaseData>;
|
|
50
55
|
declare function formatBytes(bytes: number): string;
|
|
51
56
|
/** Download a remote file (signed R2 URL) to disk, with optional progress. */
|
|
52
57
|
export declare function downloadUrlToFile(url: string, destPath: string, onProgress?: (progress: DownloadProgress) => void): Promise<{
|
package/dist/api.js
CHANGED
|
@@ -83,7 +83,26 @@ export async function fetchSkillsRelease(token, target) {
|
|
|
83
83
|
if (!json.data?.version || !Array.isArray(json.data.files)) {
|
|
84
84
|
throw new ApiError("Release response missing version/files.", res.status, body);
|
|
85
85
|
}
|
|
86
|
-
return json.data;
|
|
86
|
+
return { ...json.data, tier: json.data.tier ?? "pro" };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* GET /skills/release-free?target=react|vanilla (no auth)
|
|
90
|
+
*/
|
|
91
|
+
export async function fetchSkillsFreeRelease(target) {
|
|
92
|
+
const url = new URL(`${getApiBaseUrl()}/skills/release-free`);
|
|
93
|
+
url.searchParams.set("target", target);
|
|
94
|
+
const res = await fetch(url, {
|
|
95
|
+
headers: { Accept: "application/json" },
|
|
96
|
+
});
|
|
97
|
+
const body = await readErrorBody(res);
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
throw new ApiError(parseApiErrorMessage(body, `Could not fetch free release (${res.status}).`), res.status, body);
|
|
100
|
+
}
|
|
101
|
+
const json = JSON.parse(body);
|
|
102
|
+
if (!json.data?.version || !Array.isArray(json.data.files)) {
|
|
103
|
+
throw new ApiError("Free release response missing version/files.", res.status, body);
|
|
104
|
+
}
|
|
105
|
+
return { ...json.data, tier: json.data.tier ?? "free" };
|
|
87
106
|
}
|
|
88
107
|
function formatBytes(bytes) {
|
|
89
108
|
if (bytes < 1024) {
|
package/dist/commands/setup.d.ts
CHANGED
package/dist/commands/setup.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
1
|
import { confirm, select, checkbox } from "@inquirer/prompts";
|
|
3
|
-
import {
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import { ApiError, fetchSkillsFreeRelease, fetchSkillsRelease } from "../api.js";
|
|
4
4
|
import { installRoot, nucleoHomeDir } from "../config.js";
|
|
5
5
|
import { buildMcp, ensureManifestTarget, installFromLocalDir, installFromRelease, readInstallManifest, } from "../install.js";
|
|
6
6
|
import { configureTool } from "../adapters/agents.js";
|
|
7
7
|
import { ensureSkillsSession, isInvalidTokenError } from "../session.js";
|
|
8
|
-
async function
|
|
8
|
+
async function fetchProReleaseWithSession(target, emailHint) {
|
|
9
9
|
let session = await ensureSkillsSession(emailHint);
|
|
10
10
|
try {
|
|
11
11
|
return await fetchSkillsRelease(session.token, target);
|
|
@@ -20,8 +20,9 @@ async function fetchReleaseWithSession(target, emailHint) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
export async function runSetup(options = {}) {
|
|
23
|
-
|
|
24
|
-
console.log("
|
|
23
|
+
const free = Boolean(options.free);
|
|
24
|
+
console.log(free ? "\nNucleo Skills setup (free)" : "\nNucleo Skills setup");
|
|
25
|
+
console.log(free ? "─────────────────────────\n" : "───────────────────\n");
|
|
25
26
|
const target = options.target ??
|
|
26
27
|
(await select({
|
|
27
28
|
message: "Which package?",
|
|
@@ -57,13 +58,34 @@ export async function runSetup(options = {}) {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
await installFromLocalDir(options.from);
|
|
60
|
-
await ensureManifestTarget(target
|
|
61
|
+
await ensureManifestTarget(target, {
|
|
62
|
+
tier: free ? "free" : "pro",
|
|
63
|
+
product: free ? "nucleo-skills-free" : "nucleo-skills",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else if (free) {
|
|
67
|
+
console.log(`Fetching free ${target} release…`);
|
|
68
|
+
let release;
|
|
69
|
+
try {
|
|
70
|
+
release = await fetchSkillsFreeRelease(target);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (error instanceof ApiError) {
|
|
74
|
+
throw new Error(error.message);
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
console.log(` Version: ${release.version}`);
|
|
79
|
+
console.log(` Families: ${release.families.join(", ")}`);
|
|
80
|
+
console.log(` Files: ${release.files.map((f) => f.id).join(", ")}`);
|
|
81
|
+
console.log("\nDownloading…");
|
|
82
|
+
await installFromRelease(release, target);
|
|
61
83
|
}
|
|
62
84
|
else {
|
|
63
85
|
console.log(`Fetching ${target} release…`);
|
|
64
86
|
let release;
|
|
65
87
|
try {
|
|
66
|
-
release = await
|
|
88
|
+
release = await fetchProReleaseWithSession(target, options.email);
|
|
67
89
|
}
|
|
68
90
|
catch (error) {
|
|
69
91
|
if (error instanceof ApiError) {
|
|
@@ -87,8 +109,7 @@ export async function runSetup(options = {}) {
|
|
|
87
109
|
console.log("\n✓ Done");
|
|
88
110
|
console.log(` Install path: ${installRoot()}`);
|
|
89
111
|
console.log(` Target: ${manifest?.target ?? target}`);
|
|
112
|
+
console.log(` Tier: ${manifest?.tier ?? (free ? "free" : "pro")}`);
|
|
90
113
|
console.log(` Version: ${manifest?.version ?? "(none)"}`);
|
|
91
|
-
console.log(
|
|
92
|
-
console.log("\nNext: reload MCP in your editor, then ask:");
|
|
93
|
-
console.log(' “What Nucleo icon families do I have?”\n');
|
|
114
|
+
console.log("\nReload MCP in your editor, then try asking about Nucleo icons.\n");
|
|
94
115
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -23,6 +23,7 @@ export async function runStatus() {
|
|
|
23
23
|
const manifest = await readInstallManifest();
|
|
24
24
|
console.log(`Version: ${manifest?.version ?? "(missing)"}`);
|
|
25
25
|
console.log(`Target: ${manifest?.target ?? "(missing)"}`);
|
|
26
|
+
console.log(`Tier: ${manifest?.tier ?? "(missing)"}`);
|
|
26
27
|
console.log(`Product: ${manifest?.product ?? "(missing)"}`);
|
|
27
28
|
console.log(`Families: ${(manifest?.families ?? []).join(", ") || "(none)"}`);
|
|
28
29
|
console.log(`MCP entry: ${existsSync(mcpEntryPath()) ? mcpEntryPath() : "(missing — run setup/update)"}`);
|
package/dist/commands/update.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { confirm } from "@inquirer/prompts";
|
|
2
|
-
import { ApiError, fetchSkillsRelease } from "../api.js";
|
|
2
|
+
import { ApiError, fetchSkillsFreeRelease, fetchSkillsRelease } from "../api.js";
|
|
3
3
|
import { installRoot } from "../config.js";
|
|
4
4
|
import { buildMcp, ensureManifestTarget, installFromLocalDir, installFromRelease, readInstallManifest, } from "../install.js";
|
|
5
5
|
import { configureTool } from "../adapters/agents.js";
|
|
6
6
|
import { ensureSkillsSession, isInvalidTokenError } from "../session.js";
|
|
7
|
-
async function
|
|
7
|
+
async function fetchProReleaseWithSession(target) {
|
|
8
8
|
let session = await ensureSkillsSession();
|
|
9
9
|
try {
|
|
10
10
|
return await fetchSkillsRelease(session.token, target);
|
|
@@ -18,6 +18,18 @@ async function fetchReleaseWithSession(target) {
|
|
|
18
18
|
throw error;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
function isFreeInstall(manifest) {
|
|
22
|
+
if (!manifest) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (manifest.tier === "free") {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (typeof manifest.product === "string" && manifest.product.includes("free")) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
21
33
|
export async function runUpdate(options = {}) {
|
|
22
34
|
console.log("\nNucleo Skills update");
|
|
23
35
|
console.log("────────────────────\n");
|
|
@@ -27,10 +39,14 @@ export async function runUpdate(options = {}) {
|
|
|
27
39
|
}
|
|
28
40
|
const target = manifest?.target ?? "react";
|
|
29
41
|
const localVersion = manifest?.version ?? "0.0.0";
|
|
42
|
+
const free = isFreeInstall(manifest);
|
|
30
43
|
if (options.from) {
|
|
31
44
|
console.log(`Updating from local folder:\n ${options.from}\n`);
|
|
32
45
|
await installFromLocalDir(options.from);
|
|
33
|
-
await ensureManifestTarget(target
|
|
46
|
+
await ensureManifestTarget(target, {
|
|
47
|
+
tier: free ? "free" : "pro",
|
|
48
|
+
product: free ? "nucleo-skills-free" : "nucleo-skills",
|
|
49
|
+
});
|
|
34
50
|
console.log("Building MCP…");
|
|
35
51
|
await buildMcp();
|
|
36
52
|
await reconfigure(options.tools);
|
|
@@ -41,7 +57,14 @@ export async function runUpdate(options = {}) {
|
|
|
41
57
|
}
|
|
42
58
|
let release;
|
|
43
59
|
try {
|
|
44
|
-
|
|
60
|
+
if (free) {
|
|
61
|
+
console.log("Channel: free");
|
|
62
|
+
release = await fetchSkillsFreeRelease(target);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log("Channel: pro");
|
|
66
|
+
release = await fetchProReleaseWithSession(target);
|
|
67
|
+
}
|
|
45
68
|
}
|
|
46
69
|
catch (error) {
|
|
47
70
|
if (error instanceof ApiError) {
|
package/dist/config.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface NucleoCredentials {
|
|
|
15
15
|
}
|
|
16
16
|
export interface InstallManifest {
|
|
17
17
|
product?: string;
|
|
18
|
+
/** free | pro — drives update channel without requiring --free again */
|
|
19
|
+
tier?: "free" | "pro" | string;
|
|
18
20
|
target?: PackageTarget | string;
|
|
19
21
|
version?: string;
|
|
20
22
|
families?: string[];
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,7 @@ program
|
|
|
54
54
|
.description("Sign in, download your licensed package, configure your editor")
|
|
55
55
|
.option("-t, --target <target>", "react | vanilla")
|
|
56
56
|
.option("--tools <list>", "comma-separated: cursor,claude,codex (default: prompt, or cursor with --yes)")
|
|
57
|
+
.option("--free", "Install free icon families (no Nucleo login)")
|
|
57
58
|
.option("--from <dir>", "Install from a local prepared package (skip API download)")
|
|
58
59
|
.option("--email <email>", "Nucleo account email (skip prompt)")
|
|
59
60
|
.option("-y, --yes", "Skip confirmation prompts")
|
|
@@ -70,6 +71,7 @@ program
|
|
|
70
71
|
tools,
|
|
71
72
|
email: opts.email,
|
|
72
73
|
yes: Boolean(opts.yes),
|
|
74
|
+
free: Boolean(opts.free),
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
77
|
catch (error) {
|
package/dist/install.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { type SkillsReleaseData } from "./api.js";
|
|
2
2
|
import { type InstallManifest, type PackageTarget } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a real npm binary. Prefer the one next to the running Node
|
|
5
|
+
* (`process.execPath`) so we don't depend on PATH / shell aliases
|
|
6
|
+
* (common cause of `spawn npm ENOENT` even when `npm -v` works in zsh).
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveNpmBin(): string;
|
|
3
9
|
export declare function readInstallManifest(root?: string): Promise<InstallManifest | null>;
|
|
4
10
|
export declare function cleanInstallRoot(root?: string): Promise<void>;
|
|
5
11
|
/**
|
package/dist/install.js
CHANGED
|
@@ -1,12 +1,47 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import extract from "extract-zip";
|
|
7
7
|
import { downloadUrlToFile, formatBytes } from "./api.js";
|
|
8
8
|
import { installRoot, nucleoHomeDir } from "./config.js";
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a real npm binary. Prefer the one next to the running Node
|
|
12
|
+
* (`process.execPath`) so we don't depend on PATH / shell aliases
|
|
13
|
+
* (common cause of `spawn npm ENOENT` even when `npm -v` works in zsh).
|
|
14
|
+
*/
|
|
15
|
+
export function resolveNpmBin() {
|
|
16
|
+
const binDir = dirname(process.execPath);
|
|
17
|
+
const candidates = process.platform === "win32"
|
|
18
|
+
? [join(binDir, "npm.cmd"), join(binDir, "npm.exe"), "npm.cmd", "npm"]
|
|
19
|
+
: [join(binDir, "npm"), "npm"];
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
if (candidate === "npm" || candidate === "npm.cmd" || existsSync(candidate)) {
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
26
|
+
}
|
|
27
|
+
async function runNpm(args, cwd) {
|
|
28
|
+
const npmBin = resolveNpmBin();
|
|
29
|
+
try {
|
|
30
|
+
await execFileAsync(npmBin, args, {
|
|
31
|
+
cwd,
|
|
32
|
+
env: process.env,
|
|
33
|
+
// Windows .cmd shims need a shell; Unix absolute paths do not.
|
|
34
|
+
shell: process.platform === "win32",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
39
|
+
throw new Error(`${detail}\n` +
|
|
40
|
+
`Tried to run: ${npmBin} ${args.join(" ")}\n` +
|
|
41
|
+
`If npm is installed, ensure it sits next to node (${process.execPath}), or run:\n` +
|
|
42
|
+
` cd ${cwd} && npm ${args.join(" ")}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
10
45
|
export async function readInstallManifest(root = installRoot()) {
|
|
11
46
|
const path = join(root, "manifest.json");
|
|
12
47
|
if (!existsSync(path)) {
|
|
@@ -138,7 +173,8 @@ export async function installFromRelease(release, target, root = installRoot())
|
|
|
138
173
|
await rm(downloadsDir, { recursive: true, force: true }).catch(() => undefined);
|
|
139
174
|
}
|
|
140
175
|
await writeInstallManifest({
|
|
141
|
-
product: "nucleo-skills",
|
|
176
|
+
product: release.tier === "free" ? "nucleo-skills-free" : "nucleo-skills",
|
|
177
|
+
tier: release.tier === "free" ? "free" : "pro",
|
|
142
178
|
target,
|
|
143
179
|
version: release.version,
|
|
144
180
|
families: release.families,
|
|
@@ -191,10 +227,7 @@ export async function buildMcp(root = installRoot()) {
|
|
|
191
227
|
const hasDist = existsSync(distEntry);
|
|
192
228
|
console.log(" npm install (mcp)…");
|
|
193
229
|
try {
|
|
194
|
-
await
|
|
195
|
-
cwd: mcpDir,
|
|
196
|
-
env: process.env,
|
|
197
|
-
});
|
|
230
|
+
await runNpm(["install", "--omit=dev"], mcpDir);
|
|
198
231
|
}
|
|
199
232
|
catch (error) {
|
|
200
233
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -203,10 +236,7 @@ export async function buildMcp(root = installRoot()) {
|
|
|
203
236
|
if (!hasDist || existsSync(join(mcpDir, "src"))) {
|
|
204
237
|
console.log(" npm run build (mcp)…");
|
|
205
238
|
try {
|
|
206
|
-
await
|
|
207
|
-
cwd: mcpDir,
|
|
208
|
-
env: process.env,
|
|
209
|
-
});
|
|
239
|
+
await runNpm(["run", "build"], mcpDir);
|
|
210
240
|
}
|
|
211
241
|
catch (error) {
|
|
212
242
|
if (!existsSync(distEntry)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nucleoicons/skills",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "CLI to install and update Nucleo
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "CLI to install and update Nucleo MCP + icon skills (Pro and free)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nucleo-skills": "./dist/index.js"
|