skills 1.6.0 → 1.7.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 +1 -3
- package/dist/cli.mjs +421 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -290,7 +290,7 @@ Skills can be installed to any of these agents:
|
|
|
290
290
|
| IBM Bob | `bob` | `.bob/skills/` | `~/.bob/skills/` |
|
|
291
291
|
| Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
|
|
292
292
|
| OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` |
|
|
293
|
-
| Cline, Dexto, Kimi Code CLI, Loaf, Sarvam Code, Warp, Zed | `cline`, `dexto`, `kimi-code-cli`, `loaf`, `sarvam-code`, `warp`, `zed` | `.agents/skills/` | `~/.agents/skills/` |
|
|
293
|
+
| Cline, Dexto, Kimi Code CLI, Loaf, Pi, Sarvam Code, Warp, Zed | `cline`, `dexto`, `kimi-code-cli`, `loaf`, `pi`, `sarvam-code`, `warp`, `zed` | `.agents/skills/` | `~/.agents/skills/` |
|
|
294
294
|
| CodeArts Agent | `codearts-agent` | `.codeartsdoer/skills/` | `~/.codeartsdoer/skills/` |
|
|
295
295
|
| CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` |
|
|
296
296
|
| Codemaker | `codemaker` | `.codemaker/skills/` | `~/.codemaker/skills/` |
|
|
@@ -330,7 +330,6 @@ Skills can be installed to any of these agents:
|
|
|
330
330
|
| OpenCode | `opencode` | `.agents/skills/` | `~/.config/opencode/skills/` |
|
|
331
331
|
| OpenHands | `openhands` | `.openhands/skills/` | `~/.openhands/skills/` |
|
|
332
332
|
| Ona | `ona` | `.ona/skills/` | `~/.ona/skills/` |
|
|
333
|
-
| Pi | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` |
|
|
334
333
|
| Posit Assistant | `posit-assistant` | `.posit/assistant/skills/` | `~/.posit/assistant/skills/` |
|
|
335
334
|
| Qoder | `qoder` | `.qoder/skills/` | `~/.qoder/skills/` |
|
|
336
335
|
| Qoder CN | `qoder-cn` | `.qoder/skills/` | `~/.qoder-cn/skills/` |
|
|
@@ -463,7 +462,6 @@ discover `SKILL.md` files outside these container directories (e.g. under
|
|
|
463
462
|
- `.mux/skills/`
|
|
464
463
|
- `.openhands/skills/`
|
|
465
464
|
- `.ona/skills/`
|
|
466
|
-
- `.pi/skills/`
|
|
467
465
|
- `.posit/assistant/skills/`
|
|
468
466
|
- `.qoder/skills/`
|
|
469
467
|
- `.qwen/skills/`
|
package/dist/cli.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { basename, dirname, extname, isAbsolute, join, normalize, relative, reso
|
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
12
|
import { stripVTControlCharacters } from "node:util";
|
|
13
13
|
import { createWriteStream } from "node:fs";
|
|
14
|
-
import { dirname as dirname$1, join as join$1, normalize as normalize$1, resolve as resolve$1, sep as sep$1 } from "node:path";
|
|
14
|
+
import { dirname as dirname$1, join as join$1, normalize as normalize$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "node:path";
|
|
15
15
|
import { homedir, platform, tmpdir } from "os";
|
|
16
16
|
import * as readline from "readline";
|
|
17
17
|
import { Writable } from "stream";
|
|
@@ -20,13 +20,13 @@ import { execFile, spawn, spawnSync } from "child_process";
|
|
|
20
20
|
import { access, chmod, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
21
21
|
import { parse, stringify } from "yaml";
|
|
22
22
|
import { createHash } from "crypto";
|
|
23
|
-
import { mkdir as mkdir$1, mkdtemp as mkdtemp$1, readFile as readFile$1, rm as rm$1, stat as stat$1, writeFile as writeFile$1 } from "node:fs/promises";
|
|
23
|
+
import { cp as cp$1, mkdir as mkdir$1, mkdtemp as mkdtemp$1, readFile as readFile$1, rm as rm$1, stat as stat$1, writeFile as writeFile$1 } from "node:fs/promises";
|
|
24
24
|
import { createHash as createHash$1 } from "node:crypto";
|
|
25
25
|
import { crc32, gunzipSync, inflateRawSync } from "node:zlib";
|
|
26
26
|
import { tmpdir as tmpdir$1 } from "node:os";
|
|
27
27
|
import { pipeline } from "node:stream/promises";
|
|
28
28
|
import * as tar from "tar";
|
|
29
|
-
import { execFile as execFile$1 } from "node:child_process";
|
|
29
|
+
import { execFile as execFile$1, spawn as spawn$1 } from "node:child_process";
|
|
30
30
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
31
31
|
const DEFAULT_GITHUB_HOST = "github.com";
|
|
32
32
|
function getGitHubHost() {
|
|
@@ -1228,7 +1228,7 @@ function normalizeSkillName$1(name) {
|
|
|
1228
1228
|
function normalizeRelativePath(path) {
|
|
1229
1229
|
return path.split(sep).join("/").replace(/\/+/g, "/");
|
|
1230
1230
|
}
|
|
1231
|
-
function isRecord(value) {
|
|
1231
|
+
function isRecord$1(value) {
|
|
1232
1232
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1233
1233
|
}
|
|
1234
1234
|
function shouldInstallInternalSkills() {
|
|
@@ -1271,7 +1271,7 @@ async function parseSkillMd(skillMdPath, options) {
|
|
|
1271
1271
|
warnSkippedSkill(skillMdPath, `frontmatter "name" and "description" must be strings (got ${typeof data.name} and ${typeof data.description})`);
|
|
1272
1272
|
return null;
|
|
1273
1273
|
}
|
|
1274
|
-
const metadata = isRecord(data.metadata) ? data.metadata : void 0;
|
|
1274
|
+
const metadata = isRecord$1(data.metadata) ? data.metadata : void 0;
|
|
1275
1275
|
if (metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
|
|
1276
1276
|
return {
|
|
1277
1277
|
name: sanitizeMetadata(data.name),
|
|
@@ -1918,8 +1918,8 @@ const agents = {
|
|
|
1918
1918
|
pi: {
|
|
1919
1919
|
name: "pi",
|
|
1920
1920
|
displayName: "Pi",
|
|
1921
|
-
skillsDir: ".
|
|
1922
|
-
globalSkillsDir: join(home, ".
|
|
1921
|
+
skillsDir: ".agents/skills",
|
|
1922
|
+
globalSkillsDir: join(home, ".agents/skills"),
|
|
1923
1923
|
detectInstalled: async () => {
|
|
1924
1924
|
return existsSync(join(home, ".pi/agent"));
|
|
1925
1925
|
}
|
|
@@ -2785,8 +2785,8 @@ async function listInstalledSkills(options = {}) {
|
|
|
2785
2785
|
} catch {}
|
|
2786
2786
|
return Array.from(skillsMap.values());
|
|
2787
2787
|
}
|
|
2788
|
-
const TELEMETRY_URL = "https://
|
|
2789
|
-
const AUDIT_URL = "https://
|
|
2788
|
+
const TELEMETRY_URL = "https://www.skills.sh/tele/t";
|
|
2789
|
+
const AUDIT_URL = "https://www.skills.sh/tele/audit";
|
|
2790
2790
|
let cliVersion = null;
|
|
2791
2791
|
let detectedAgentName = null;
|
|
2792
2792
|
function setDetectedAgent(agentName) {
|
|
@@ -3560,17 +3560,20 @@ const DEFAULT_DOWNLOAD_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
3560
3560
|
const DEFAULT_EXTRACT_MAX_BYTES = 25 * 1024 * 1024;
|
|
3561
3561
|
const DEFAULT_EXTRACT_MAX_FILES = 1e3;
|
|
3562
3562
|
const FETCH_TIMEOUT_MS = 3e4;
|
|
3563
|
+
function positiveIntegerOr(value, fallback) {
|
|
3564
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : fallback;
|
|
3565
|
+
}
|
|
3563
3566
|
function getPositiveIntegerEnv(name, fallback) {
|
|
3564
3567
|
const raw = process.env[name];
|
|
3565
3568
|
if (!raw) return fallback;
|
|
3566
3569
|
const parsed = Number.parseInt(raw, 10);
|
|
3567
3570
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
3568
3571
|
}
|
|
3569
|
-
function getDownloadLimits() {
|
|
3572
|
+
function getDownloadLimits(options) {
|
|
3570
3573
|
return {
|
|
3571
|
-
downloadMaxBytes: getPositiveIntegerEnv("SKILLS_DOWNLOAD_MAX_BYTES", DEFAULT_DOWNLOAD_MAX_BYTES),
|
|
3572
|
-
extractMaxBytes: getPositiveIntegerEnv("SKILLS_EXTRACT_MAX_BYTES", DEFAULT_EXTRACT_MAX_BYTES),
|
|
3573
|
-
extractMaxFiles: getPositiveIntegerEnv("SKILLS_EXTRACT_MAX_FILES", DEFAULT_EXTRACT_MAX_FILES)
|
|
3574
|
+
downloadMaxBytes: getPositiveIntegerEnv("SKILLS_DOWNLOAD_MAX_BYTES", positiveIntegerOr(options.downloadMaxBytes, DEFAULT_DOWNLOAD_MAX_BYTES)),
|
|
3575
|
+
extractMaxBytes: getPositiveIntegerEnv("SKILLS_EXTRACT_MAX_BYTES", positiveIntegerOr(options.extractMaxBytes, DEFAULT_EXTRACT_MAX_BYTES)),
|
|
3576
|
+
extractMaxFiles: getPositiveIntegerEnv("SKILLS_EXTRACT_MAX_FILES", positiveIntegerOr(options.extractMaxFiles, DEFAULT_EXTRACT_MAX_FILES))
|
|
3574
3577
|
};
|
|
3575
3578
|
}
|
|
3576
3579
|
function isPathSafe$1(basePath, targetPath) {
|
|
@@ -3704,8 +3707,8 @@ async function getSingleTopLevelDirectory(dir) {
|
|
|
3704
3707
|
if (visibleEntries.length !== 1 || !visibleEntries[0].isDirectory()) return null;
|
|
3705
3708
|
return join$1(dir, visibleEntries[0].name);
|
|
3706
3709
|
}
|
|
3707
|
-
async function downloadSource(url) {
|
|
3708
|
-
const limits = getDownloadLimits();
|
|
3710
|
+
async function downloadSource(url, options = {}) {
|
|
3711
|
+
const limits = getDownloadLimits(options);
|
|
3709
3712
|
const tempDir = await mkdtemp$1(join$1(tmpdir$1(), "skills-download-"));
|
|
3710
3713
|
const downloadedFile = join$1(tempDir, "source.download");
|
|
3711
3714
|
const extractDir = join$1(tempDir, "extract");
|
|
@@ -4169,7 +4172,348 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
4169
4172
|
tree
|
|
4170
4173
|
};
|
|
4171
4174
|
}
|
|
4172
|
-
var version$1 = "1.
|
|
4175
|
+
var version$1 = "1.7.1";
|
|
4176
|
+
const NOTION_API_VERSION = "2026-03-11";
|
|
4177
|
+
const NTN_TIMEOUT_MS = 3e4;
|
|
4178
|
+
const NTN_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
4179
|
+
const NOTION_DOWNLOAD_MAX_BYTES = 50 * 1024 * 1024;
|
|
4180
|
+
const NOTION_EXTRACT_MAX_BYTES = 100 * 1024 * 1024;
|
|
4181
|
+
const NOTION_EXTRACT_MAX_FILES = 5e3;
|
|
4182
|
+
function isRecord(value) {
|
|
4183
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4184
|
+
}
|
|
4185
|
+
function assertString(value, field) {
|
|
4186
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`Notion Agent Plugins response is missing ${field}`);
|
|
4187
|
+
return value;
|
|
4188
|
+
}
|
|
4189
|
+
function optionalMetadata(value, field) {
|
|
4190
|
+
if (value === void 0 || value === null) return "";
|
|
4191
|
+
if (typeof value !== "string") throw new Error(`Notion Agent Plugins response has an invalid ${field}`);
|
|
4192
|
+
return sanitizeMetadata(value);
|
|
4193
|
+
}
|
|
4194
|
+
function parsePack(value) {
|
|
4195
|
+
if (!isRecord(value)) throw new Error("Notion Agent Plugins response contains an invalid pack");
|
|
4196
|
+
if (typeof value.name !== "string") throw new Error("Notion Agent Plugins response is missing pack.name");
|
|
4197
|
+
const name = sanitizeMetadata(value.name);
|
|
4198
|
+
if (!name) return null;
|
|
4199
|
+
return {
|
|
4200
|
+
id: assertString(value.id, "pack.id"),
|
|
4201
|
+
name,
|
|
4202
|
+
description: optionalMetadata(value.description, "pack.description"),
|
|
4203
|
+
version_id: assertString(value.version_id, "pack.version_id")
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
function parsePackList(value) {
|
|
4207
|
+
if (!isRecord(value) || !Array.isArray(value.results)) throw new Error("Notion Agent Plugins list response is invalid");
|
|
4208
|
+
if (typeof value.has_more !== "boolean") throw new Error("Notion Agent Plugins list response is missing has_more");
|
|
4209
|
+
const nextCursor = value.next_cursor;
|
|
4210
|
+
if (nextCursor !== null && typeof nextCursor !== "string") throw new Error("Notion Agent Plugins list response has an invalid next_cursor");
|
|
4211
|
+
return {
|
|
4212
|
+
results: value.results.map(parsePack).filter((pack) => pack !== null),
|
|
4213
|
+
next_cursor: nextCursor,
|
|
4214
|
+
has_more: value.has_more
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
function parseDirectory(value, label) {
|
|
4218
|
+
if (!isRecord(value)) throw new Error(`Notion ${label} directory response is invalid`);
|
|
4219
|
+
return {
|
|
4220
|
+
id: assertString(value.id, `${label} directory id`),
|
|
4221
|
+
version_id: assertString(value.version_id, `${label} directory version_id`),
|
|
4222
|
+
url: assertString(value.url, `${label} directory url`)
|
|
4223
|
+
};
|
|
4224
|
+
}
|
|
4225
|
+
function downloadNotionDirectory(url) {
|
|
4226
|
+
return downloadSource(url, {
|
|
4227
|
+
downloadMaxBytes: NOTION_DOWNLOAD_MAX_BYTES,
|
|
4228
|
+
extractMaxBytes: NOTION_EXTRACT_MAX_BYTES,
|
|
4229
|
+
extractMaxFiles: NOTION_EXTRACT_MAX_FILES
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
function debugNtn(args) {
|
|
4233
|
+
if (process.env.SKILLS_DEBUG === "1") console.error(`[notion] ntn ${args.join(" ")}`);
|
|
4234
|
+
}
|
|
4235
|
+
async function runNtnApi(args) {
|
|
4236
|
+
debugNtn(args);
|
|
4237
|
+
return new Promise((resolve, reject) => {
|
|
4238
|
+
const child = spawn$1("ntn", args, {
|
|
4239
|
+
env: process.env,
|
|
4240
|
+
stdio: [
|
|
4241
|
+
"ignore",
|
|
4242
|
+
"pipe",
|
|
4243
|
+
"pipe"
|
|
4244
|
+
]
|
|
4245
|
+
});
|
|
4246
|
+
const stdoutChunks = [];
|
|
4247
|
+
const stderrChunks = [];
|
|
4248
|
+
let outputBytes = 0;
|
|
4249
|
+
let settled = false;
|
|
4250
|
+
const finish = (callback) => {
|
|
4251
|
+
if (settled) return;
|
|
4252
|
+
settled = true;
|
|
4253
|
+
clearTimeout(timeout);
|
|
4254
|
+
callback();
|
|
4255
|
+
};
|
|
4256
|
+
const fail = (message) => finish(() => reject(new Error(message)));
|
|
4257
|
+
const append = (chunks, chunk) => {
|
|
4258
|
+
outputBytes += chunk.length;
|
|
4259
|
+
if (outputBytes > NTN_MAX_BUFFER_BYTES) {
|
|
4260
|
+
child.kill("SIGTERM");
|
|
4261
|
+
fail("ntn api output exceeded 10 MiB");
|
|
4262
|
+
return;
|
|
4263
|
+
}
|
|
4264
|
+
chunks.push(chunk);
|
|
4265
|
+
};
|
|
4266
|
+
const timeout = setTimeout(() => {
|
|
4267
|
+
child.kill("SIGTERM");
|
|
4268
|
+
fail(`ntn api timed out after ${NTN_TIMEOUT_MS / 1e3} seconds`);
|
|
4269
|
+
}, NTN_TIMEOUT_MS);
|
|
4270
|
+
child.stdout.on("data", (chunk) => append(stdoutChunks, chunk));
|
|
4271
|
+
child.stderr.on("data", (chunk) => append(stderrChunks, chunk));
|
|
4272
|
+
child.on("error", (error) => {
|
|
4273
|
+
if (error.code === "ENOENT") {
|
|
4274
|
+
fail("Notion CLI (ntn) is required. Install it from:\nhttps://developers.notion.com/cli/get-started/overview\nThen run `ntn login`.");
|
|
4275
|
+
return;
|
|
4276
|
+
}
|
|
4277
|
+
fail(`Unable to start ntn: ${stripTerminalEscapes(error.message)}`);
|
|
4278
|
+
});
|
|
4279
|
+
child.on("close", (code) => {
|
|
4280
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
4281
|
+
const stderr = stripTerminalEscapes(Buffer.concat(stderrChunks).toString("utf8")).trim();
|
|
4282
|
+
if (code === 0) {
|
|
4283
|
+
finish(() => resolve(stdout));
|
|
4284
|
+
return;
|
|
4285
|
+
}
|
|
4286
|
+
fail(`ntn api failed${stderr ? `: ${stderr}` : ` with exit code ${code ?? "unknown"}`}`);
|
|
4287
|
+
});
|
|
4288
|
+
});
|
|
4289
|
+
}
|
|
4290
|
+
async function fetchNotionJson(args, label, runNtn = runNtnApi) {
|
|
4291
|
+
const output = await runNtn([
|
|
4292
|
+
"api",
|
|
4293
|
+
...args,
|
|
4294
|
+
"--notion-version",
|
|
4295
|
+
NOTION_API_VERSION
|
|
4296
|
+
]);
|
|
4297
|
+
try {
|
|
4298
|
+
return JSON.parse(output);
|
|
4299
|
+
} catch (error) {
|
|
4300
|
+
if (error instanceof SyntaxError) throw new Error(`ntn returned invalid JSON for ${label}`);
|
|
4301
|
+
throw error;
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
async function fetchNotionPacks(options = {}) {
|
|
4305
|
+
const runNtn = options.runNtn ?? runNtnApi;
|
|
4306
|
+
const packs = [];
|
|
4307
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
4308
|
+
let cursor = null;
|
|
4309
|
+
do {
|
|
4310
|
+
const args = ["/v1/ai/plugins", "page_size==100"];
|
|
4311
|
+
if (cursor) args.push(`start_cursor==${cursor}`);
|
|
4312
|
+
const page = parsePackList(await fetchNotionJson(args, "the Notion packs list", runNtn));
|
|
4313
|
+
packs.push(...page.results);
|
|
4314
|
+
if (!page.has_more) break;
|
|
4315
|
+
if (!page.next_cursor || seenCursors.has(page.next_cursor)) throw new Error("Notion Agent Plugins pagination returned an invalid cursor");
|
|
4316
|
+
cursor = page.next_cursor;
|
|
4317
|
+
seenCursors.add(cursor);
|
|
4318
|
+
} while (cursor);
|
|
4319
|
+
return packs;
|
|
4320
|
+
}
|
|
4321
|
+
async function fetchNotionPackDirectory(pack, options = {}) {
|
|
4322
|
+
const directory = parseDirectory(await fetchNotionJson([`/v1/ai/plugins/${encodeURIComponent(pack.id)}`], `Notion pack ${JSON.stringify(pack.name)}`, options.runNtn), "pack");
|
|
4323
|
+
if (directory.id !== pack.id || directory.version_id !== pack.version_id) throw new Error(`Notion pack ${JSON.stringify(pack.name)} changed while preparing installation`);
|
|
4324
|
+
return directory;
|
|
4325
|
+
}
|
|
4326
|
+
async function fetchNotionWorkspaceName(options = {}) {
|
|
4327
|
+
const runNtn = options.runNtn ?? runNtnApi;
|
|
4328
|
+
try {
|
|
4329
|
+
const value = JSON.parse(await runNtn([
|
|
4330
|
+
"api",
|
|
4331
|
+
"/v1/users/me",
|
|
4332
|
+
"--notion-version",
|
|
4333
|
+
NOTION_API_VERSION
|
|
4334
|
+
]));
|
|
4335
|
+
if (!isRecord(value) || !isRecord(value.bot)) return null;
|
|
4336
|
+
const name = value.bot.workspace_name;
|
|
4337
|
+
return typeof name === "string" && name.length > 0 ? sanitizeMetadata(name) : null;
|
|
4338
|
+
} catch {
|
|
4339
|
+
return null;
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
function inWorkspace(workspace) {
|
|
4343
|
+
return workspace ? ` in workspace ${import_picocolors.default.cyan(workspace)}` : "";
|
|
4344
|
+
}
|
|
4345
|
+
function isNotionSource(source) {
|
|
4346
|
+
return source.toLowerCase() === "notion";
|
|
4347
|
+
}
|
|
4348
|
+
const NOTION_HOSTNAME = /(^|\.)notion\.(so|com)$/;
|
|
4349
|
+
const NOTION_PAGE_ID = /(?:^|-)([0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
|
|
4350
|
+
function formatPageId(rawId) {
|
|
4351
|
+
const hex = rawId.replace(/-/g, "");
|
|
4352
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
4353
|
+
}
|
|
4354
|
+
function parseNotionSkillUrl(source) {
|
|
4355
|
+
let url;
|
|
4356
|
+
try {
|
|
4357
|
+
url = new URL(source);
|
|
4358
|
+
} catch {
|
|
4359
|
+
return null;
|
|
4360
|
+
}
|
|
4361
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
4362
|
+
if (!NOTION_HOSTNAME.test(url.hostname.toLowerCase())) return null;
|
|
4363
|
+
const lastSegment = url.pathname.split("/").filter(Boolean).pop();
|
|
4364
|
+
if (!lastSegment) return null;
|
|
4365
|
+
let decoded;
|
|
4366
|
+
try {
|
|
4367
|
+
decoded = decodeURIComponent(lastSegment);
|
|
4368
|
+
} catch {
|
|
4369
|
+
decoded = lastSegment;
|
|
4370
|
+
}
|
|
4371
|
+
const match = decoded.toLowerCase().match(NOTION_PAGE_ID);
|
|
4372
|
+
return match ? formatPageId(match[1]) : null;
|
|
4373
|
+
}
|
|
4374
|
+
async function fetchNotionSkillDirectory(pageId, options = {}) {
|
|
4375
|
+
return parseDirectory(await fetchNotionJson([`/v1/ai/skills/${encodeURIComponent(pageId)}`], `Notion skill ${pageId}`, options.runNtn), "skill");
|
|
4376
|
+
}
|
|
4377
|
+
async function prepareNotionSkillSource(pageId, options = {}) {
|
|
4378
|
+
const download = options.download ?? downloadNotionDirectory;
|
|
4379
|
+
const spinner$4 = spinner();
|
|
4380
|
+
spinner$4.start("Fetching Notion skill with ntn…");
|
|
4381
|
+
try {
|
|
4382
|
+
const [directory, workspace] = await Promise.all([fetchNotionSkillDirectory(pageId, { runNtn: options.runNtn }), fetchNotionWorkspaceName({ runNtn: options.runNtn })]);
|
|
4383
|
+
const downloaded = await download(directory.url);
|
|
4384
|
+
spinner$4.stop(`Downloaded skill from Notion${inWorkspace(workspace)}`);
|
|
4385
|
+
return {
|
|
4386
|
+
rootDir: downloaded.rootDir,
|
|
4387
|
+
tempDir: downloaded.tempDir
|
|
4388
|
+
};
|
|
4389
|
+
} catch (error) {
|
|
4390
|
+
spinner$4.stop(import_picocolors.default.red("Failed to prepare Notion skill"));
|
|
4391
|
+
throw error;
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
function normalizeSelector(value) {
|
|
4395
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4396
|
+
}
|
|
4397
|
+
function filterPacks(packs, selectors) {
|
|
4398
|
+
if (selectors.includes("*")) return packs;
|
|
4399
|
+
const normalized = new Set(selectors.map(normalizeSelector));
|
|
4400
|
+
return packs.filter((pack) => normalized.has(normalizeSelector(pack.name)) || normalized.has(pack.id.toLowerCase()));
|
|
4401
|
+
}
|
|
4402
|
+
function sortedPacks(packs) {
|
|
4403
|
+
return [...packs].sort((a, b) => a.name.localeCompare(b.name));
|
|
4404
|
+
}
|
|
4405
|
+
function printAvailablePacks(packs) {
|
|
4406
|
+
console.log();
|
|
4407
|
+
log.step(import_picocolors.default.bold("Available Notion packs"));
|
|
4408
|
+
for (const pack of sortedPacks(packs)) {
|
|
4409
|
+
log.message(import_picocolors.default.cyan(pack.name));
|
|
4410
|
+
if (pack.description) log.message(` ${import_picocolors.default.dim(pack.description)}`);
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
async function prepareNotionPackSource(options = {}) {
|
|
4414
|
+
const spinner$5 = spinner();
|
|
4415
|
+
spinner$5.start("Fetching Notion packs with ntn…");
|
|
4416
|
+
let packs;
|
|
4417
|
+
let workspace = null;
|
|
4418
|
+
try {
|
|
4419
|
+
[packs, workspace] = await Promise.all([fetchNotionPacks({ runNtn: options.runNtn }), fetchNotionWorkspaceName({ runNtn: options.runNtn })]);
|
|
4420
|
+
} catch (error) {
|
|
4421
|
+
spinner$5.stop(import_picocolors.default.red("Failed to load Notion packs"));
|
|
4422
|
+
throw error;
|
|
4423
|
+
}
|
|
4424
|
+
spinner$5.stop(`Found ${import_picocolors.default.green(packs.length)} Notion pack${packs.length === 1 ? "" : "s"}${inWorkspace(workspace)}`);
|
|
4425
|
+
if (packs.length === 0) throw new Error("Notion returned no packs for the authenticated workspace");
|
|
4426
|
+
if (options.list) {
|
|
4427
|
+
printAvailablePacks(packs);
|
|
4428
|
+
outro(import_picocolors.default.dim("Select a pack by running the command without --list."));
|
|
4429
|
+
return null;
|
|
4430
|
+
}
|
|
4431
|
+
let selectedPacks;
|
|
4432
|
+
if (options.skill && options.skill.length > 0) {
|
|
4433
|
+
selectedPacks = filterPacks(packs, options.skill);
|
|
4434
|
+
if (selectedPacks.length === 0) throw new Error(`No matching Notion packs found for: ${options.skill.join(", ")}`);
|
|
4435
|
+
} else if (options.yes || !process.stdin.isTTY) selectedPacks = packs;
|
|
4436
|
+
else {
|
|
4437
|
+
const selected = await searchMultiselect({
|
|
4438
|
+
message: `Select Notion packs to install ${import_picocolors.default.dim("(space to toggle)")}`,
|
|
4439
|
+
items: sortedPacks(packs).map((pack) => ({
|
|
4440
|
+
value: pack,
|
|
4441
|
+
label: pack.name,
|
|
4442
|
+
detail: pack.description || "Notion plugin pack"
|
|
4443
|
+
})),
|
|
4444
|
+
required: true,
|
|
4445
|
+
maxVisible: 20,
|
|
4446
|
+
searchable: false,
|
|
4447
|
+
showDetail: true,
|
|
4448
|
+
showSelectedSummary: false
|
|
4449
|
+
});
|
|
4450
|
+
if (typeof selected === "symbol") {
|
|
4451
|
+
cancel("Selection cancelled");
|
|
4452
|
+
return null;
|
|
4453
|
+
}
|
|
4454
|
+
selectedPacks = selected;
|
|
4455
|
+
}
|
|
4456
|
+
note(selectedPacks.map((pack) => import_picocolors.default.cyan(pack.name)).join("\n"), `Selected ${selectedPacks.length} Notion pack${selectedPacks.length === 1 ? "" : "s"}`);
|
|
4457
|
+
const stagingDir = await mkdtemp$1(join$1(tmpdir$1(), "skills-notion-"));
|
|
4458
|
+
const packsDir = join$1(stagingDir, "packs");
|
|
4459
|
+
const manifestPlugins = [];
|
|
4460
|
+
const download = options.download ?? downloadNotionDirectory;
|
|
4461
|
+
const discover = options.discover ?? discoverSkills;
|
|
4462
|
+
let skillCount = 0;
|
|
4463
|
+
spinner$5.start("Preparing selected Notion packs…");
|
|
4464
|
+
try {
|
|
4465
|
+
await mkdir$1(packsDir, { recursive: true });
|
|
4466
|
+
for (const [index, pack] of selectedPacks.entries()) {
|
|
4467
|
+
let downloaded = null;
|
|
4468
|
+
try {
|
|
4469
|
+
downloaded = await download((await fetchNotionPackDirectory(pack, { runNtn: options.runNtn })).url);
|
|
4470
|
+
const skills = await discover(downloaded.rootDir, void 0, {
|
|
4471
|
+
includeInternal: true,
|
|
4472
|
+
fullDepth: true
|
|
4473
|
+
});
|
|
4474
|
+
if (skills.length === 0) throw new Error("pack contains no valid skills");
|
|
4475
|
+
const directoryName = `pack-${index + 1}-${sanitizeName(pack.name).slice(0, 100)}`;
|
|
4476
|
+
const targetDir = join$1(packsDir, directoryName);
|
|
4477
|
+
await cp$1(downloaded.rootDir, targetDir, {
|
|
4478
|
+
recursive: true,
|
|
4479
|
+
force: false,
|
|
4480
|
+
errorOnExist: true
|
|
4481
|
+
});
|
|
4482
|
+
manifestPlugins.push({
|
|
4483
|
+
name: pack.name,
|
|
4484
|
+
source: `./${directoryName}`,
|
|
4485
|
+
skills: skills.map((skill) => {
|
|
4486
|
+
const skillPath = relative$1(downloaded.rootDir, skill.path).split(sep$1).join("/");
|
|
4487
|
+
return skillPath ? `./${skillPath}` : "./.";
|
|
4488
|
+
})
|
|
4489
|
+
});
|
|
4490
|
+
skillCount += skills.length;
|
|
4491
|
+
} catch (error) {
|
|
4492
|
+
const detail = error instanceof Error ? error.message : "Unknown error";
|
|
4493
|
+
throw new Error(`Failed to prepare Notion pack ${JSON.stringify(pack.name)}: ${detail}`);
|
|
4494
|
+
} finally {
|
|
4495
|
+
if (downloaded) await cleanupTempDir(downloaded.tempDir).catch(() => {});
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
const manifestDir = join$1(stagingDir, ".claude-plugin");
|
|
4499
|
+
await mkdir$1(manifestDir, { recursive: true });
|
|
4500
|
+
await writeFile$1(join$1(manifestDir, "marketplace.json"), `${JSON.stringify({
|
|
4501
|
+
metadata: { pluginRoot: "./packs" },
|
|
4502
|
+
plugins: manifestPlugins
|
|
4503
|
+
}, null, 2)}\n`, "utf8");
|
|
4504
|
+
spinner$5.stop(`Prepared ${import_picocolors.default.green(selectedPacks.length)} Notion pack${selectedPacks.length === 1 ? "" : "s"} with ${import_picocolors.default.green(skillCount)} skill${skillCount === 1 ? "" : "s"}`);
|
|
4505
|
+
return {
|
|
4506
|
+
rootDir: stagingDir,
|
|
4507
|
+
tempDir: stagingDir,
|
|
4508
|
+
packCount: selectedPacks.length,
|
|
4509
|
+
skillCount
|
|
4510
|
+
};
|
|
4511
|
+
} catch (error) {
|
|
4512
|
+
spinner$5.stop(import_picocolors.default.red("Failed to prepare Notion packs"));
|
|
4513
|
+
await cleanupTempDir(stagingDir).catch(() => {});
|
|
4514
|
+
throw error;
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4173
4517
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
4174
4518
|
const EVE_AGENT_LABEL = "eve agent";
|
|
4175
4519
|
async function isSourcePrivate(source) {
|
|
@@ -4410,6 +4754,15 @@ function isSkillsShPackUrl(url) {
|
|
|
4410
4754
|
return false;
|
|
4411
4755
|
}
|
|
4412
4756
|
}
|
|
4757
|
+
function logAutoSelectedSkills(entries) {
|
|
4758
|
+
const only = entries.length === 1 ? entries[0] : null;
|
|
4759
|
+
if (!only) {
|
|
4760
|
+
log.info(`Installing all ${entries.length} skills`);
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
log.info(`Skill: ${import_picocolors.default.cyan(only.label)}`);
|
|
4764
|
+
if (only.description) log.message(import_picocolors.default.dim(only.description));
|
|
4765
|
+
}
|
|
4413
4766
|
async function handleWellKnownSkills(source, url, options, spinner) {
|
|
4414
4767
|
spinner.start("Discovering skills from well-known endpoint...");
|
|
4415
4768
|
let skills = [];
|
|
@@ -4445,9 +4798,13 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4445
4798
|
process.exit(0);
|
|
4446
4799
|
}
|
|
4447
4800
|
let selectedSkills;
|
|
4801
|
+
const logWellKnown = (chosen) => logAutoSelectedSkills(chosen.map((s) => ({
|
|
4802
|
+
label: s.installName,
|
|
4803
|
+
description: s.description
|
|
4804
|
+
})));
|
|
4448
4805
|
if (options.skill?.includes("*")) {
|
|
4449
4806
|
selectedSkills = skills;
|
|
4450
|
-
|
|
4807
|
+
logWellKnown(selectedSkills);
|
|
4451
4808
|
} else if (options.skill && options.skill.length > 0) {
|
|
4452
4809
|
selectedSkills = skills.filter((s) => options.skill.some((name) => s.installName.toLowerCase() === name.toLowerCase() || s.name.toLowerCase() === name.toLowerCase()));
|
|
4453
4810
|
if (selectedSkills.length === 0) {
|
|
@@ -4456,13 +4813,9 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4456
4813
|
for (const s of skills) log.message(` - ${s.installName}`);
|
|
4457
4814
|
process.exit(1);
|
|
4458
4815
|
}
|
|
4459
|
-
} else if (skills.length === 1) {
|
|
4816
|
+
} else if (skills.length === 1 || options.yes) {
|
|
4460
4817
|
selectedSkills = skills;
|
|
4461
|
-
|
|
4462
|
-
log.info(`Skill: ${import_picocolors.default.cyan(firstSkill.installName)}`);
|
|
4463
|
-
} else if (options.yes) {
|
|
4464
|
-
selectedSkills = skills;
|
|
4465
|
-
log.info(`Installing all ${skills.length} skills`);
|
|
4818
|
+
logWellKnown(selectedSkills);
|
|
4466
4819
|
} else {
|
|
4467
4820
|
const selected = await searchMultiselect({
|
|
4468
4821
|
message: "Select skills to install",
|
|
@@ -4771,15 +5124,26 @@ async function runAdd(args, options = {}) {
|
|
|
4771
5124
|
else if (!process.stdin.isTTY) showInstallTip();
|
|
4772
5125
|
let tempDir = null;
|
|
4773
5126
|
try {
|
|
5127
|
+
let effectiveSource = source;
|
|
5128
|
+
let notionSourceLabel = null;
|
|
5129
|
+
const notionSkillPageId = parseNotionSkillUrl(source);
|
|
5130
|
+
if (isNotionSource(source) || notionSkillPageId) {
|
|
5131
|
+
const prepared = notionSkillPageId ? await prepareNotionSkillSource(notionSkillPageId) : await prepareNotionPackSource(options);
|
|
5132
|
+
if (!prepared) return;
|
|
5133
|
+
effectiveSource = prepared.rootDir;
|
|
5134
|
+
tempDir = prepared.tempDir;
|
|
5135
|
+
notionSourceLabel = "packCount" in prepared ? `${prepared.packCount} selected Notion pack${prepared.packCount === 1 ? "" : "s"}` : "Notion page";
|
|
5136
|
+
options.skill = ["*"];
|
|
5137
|
+
}
|
|
4774
5138
|
const spinner$3 = jsonMode ? {
|
|
4775
5139
|
start: () => {},
|
|
4776
5140
|
stop: () => {},
|
|
4777
5141
|
message: () => {}
|
|
4778
5142
|
} : spinner();
|
|
4779
5143
|
spinner$3.start("Parsing source…");
|
|
4780
|
-
const parsed = parseSource(
|
|
4781
|
-
let directDownload = parsed.type === "download";
|
|
4782
|
-
spinner$3.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
|
|
5144
|
+
const parsed = parseSource(effectiveSource);
|
|
5145
|
+
let directDownload = parsed.type === "download" || notionSourceLabel !== null;
|
|
5146
|
+
spinner$3.stop(notionSourceLabel !== null ? `Source: ${notionSourceLabel}` : `Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
|
|
4783
5147
|
const ownerRepoRaw = parsed.type === "well-known" || parsed.type === "download" ? null : getOwnerRepo(parsed);
|
|
4784
5148
|
const repoPrivacyPromise = (() => {
|
|
4785
5149
|
if (parsed.type !== "github") return Promise.resolve(null);
|
|
@@ -4909,9 +5273,13 @@ async function runAdd(args, options = {}) {
|
|
|
4909
5273
|
emitJsonAndExit(0);
|
|
4910
5274
|
}
|
|
4911
5275
|
let selectedSkills;
|
|
5276
|
+
const logChosen = (chosen) => logAutoSelectedSkills(chosen.map((s) => ({
|
|
5277
|
+
label: getSkillDisplayName(s),
|
|
5278
|
+
description: s.description
|
|
5279
|
+
})));
|
|
4912
5280
|
if (options.skill?.includes("*")) {
|
|
4913
5281
|
selectedSkills = skills;
|
|
4914
|
-
|
|
5282
|
+
logChosen(selectedSkills);
|
|
4915
5283
|
} else if (options.skill && options.skill.length > 0) {
|
|
4916
5284
|
selectedSkills = filterSkills(skills, options.skill);
|
|
4917
5285
|
if (jsonMode) {
|
|
@@ -4929,14 +5297,9 @@ async function runAdd(args, options = {}) {
|
|
|
4929
5297
|
emitJsonAndExit(1, `No matching skills found for: ${options.skill.join(", ")}`);
|
|
4930
5298
|
}
|
|
4931
5299
|
log.info(`Selected ${selectedSkills.length} skill${selectedSkills.length !== 1 ? "s" : ""}: ${selectedSkills.map((s) => import_picocolors.default.cyan(getSkillDisplayName(s))).join(", ")}`);
|
|
4932
|
-
} else if (skills.length === 1) {
|
|
5300
|
+
} else if (skills.length === 1 || options.yes) {
|
|
4933
5301
|
selectedSkills = skills;
|
|
4934
|
-
|
|
4935
|
-
log.info(`Skill: ${import_picocolors.default.cyan(getSkillDisplayName(firstSkill))}`);
|
|
4936
|
-
log.message(import_picocolors.default.dim(firstSkill.description));
|
|
4937
|
-
} else if (options.yes) {
|
|
4938
|
-
selectedSkills = skills;
|
|
4939
|
-
log.info(`Installing all ${skills.length} skills`);
|
|
5302
|
+
logChosen(selectedSkills);
|
|
4940
5303
|
} else {
|
|
4941
5304
|
const sortedSkills = [...skills].sort((a, b) => {
|
|
4942
5305
|
if (a.pluginName && !b.pluginName) return -1;
|
|
@@ -5260,9 +5623,9 @@ async function runAdd(args, options = {}) {
|
|
|
5260
5623
|
installedSkillHashes.set(skillDisplayName, computedHash);
|
|
5261
5624
|
} catch {}
|
|
5262
5625
|
}
|
|
5263
|
-
if (successful.length > 0 && installGlobally && normalizedSource) {
|
|
5626
|
+
if (successful.length > 0 && installGlobally && (normalizedSource || parsed.type === "local" && !directDownload)) {
|
|
5264
5627
|
let cachedTree;
|
|
5265
|
-
if (parsed.type === "github" && !blobResult) cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, getGitHubToken);
|
|
5628
|
+
if (parsed.type === "github" && !blobResult && normalizedSource) cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, getGitHubToken);
|
|
5266
5629
|
for (const skill of selectedSkills) {
|
|
5267
5630
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
5268
5631
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
@@ -5277,9 +5640,12 @@ async function runAdd(args, options = {}) {
|
|
|
5277
5640
|
} else if (skillPathValue && tempDir) {
|
|
5278
5641
|
const hash = await computeSkillFolderHash(join(tempDir, dirname(skillPathValue)));
|
|
5279
5642
|
if (hash) skillFolderHash = hash;
|
|
5643
|
+
} else if (parsed.type === "local") {
|
|
5644
|
+
const hash = await computeSkillFolderHash(skill.path);
|
|
5645
|
+
if (hash) skillFolderHash = hash;
|
|
5280
5646
|
}
|
|
5281
5647
|
await addSkillToLock(skill.name, {
|
|
5282
|
-
source: lockSource || normalizedSource,
|
|
5648
|
+
source: lockSource || normalizedSource || parsed.url,
|
|
5283
5649
|
sourceType: parsed.type,
|
|
5284
5650
|
sourceUrl: parsed.url,
|
|
5285
5651
|
ref: parsed.ref,
|
|
@@ -6562,7 +6928,7 @@ async function removeCommand(skillNames, options) {
|
|
|
6562
6928
|
if (successful.length > 0) {
|
|
6563
6929
|
const bySource = /* @__PURE__ */ new Map();
|
|
6564
6930
|
for (const r of successful) {
|
|
6565
|
-
const source = r.source || "local";
|
|
6931
|
+
const source = r.sourceType === "local" ? "local" : r.source || "local";
|
|
6566
6932
|
const existing = bySource.get(source) || { skills: [] };
|
|
6567
6933
|
existing.skills.push(r.skill);
|
|
6568
6934
|
existing.sourceType = r.sourceType;
|
|
@@ -7182,7 +7548,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
7182
7548
|
if (hasUniversal) targetParts.push("Universal");
|
|
7183
7549
|
targetParts.push(...targetAgentNames);
|
|
7184
7550
|
if (targetParts.length > 0) console.log(`${TEXT$1}Updating for: ${targetParts.join(", ")}${RESET$1}`);
|
|
7185
|
-
console.log(`${TEXT$1}
|
|
7551
|
+
console.log(`${TEXT$1}Checking ${updatable.length + wellKnownCount} skill(s)…${RESET$1}`);
|
|
7186
7552
|
console.log();
|
|
7187
7553
|
const { successCount: wkSuccessCount, failCount: wkFailCount } = await processWellKnownUpdates(wellKnownGroups, false, options);
|
|
7188
7554
|
successCount += wkSuccessCount;
|
|
@@ -7196,6 +7562,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
7196
7562
|
}
|
|
7197
7563
|
const localLock = await readLocalLock();
|
|
7198
7564
|
const cliEntry = join(__dirname$1, "..", "bin", "cli.mjs");
|
|
7565
|
+
let upToDateCount = 0;
|
|
7199
7566
|
if (updatable.length > 0 && !existsSync(cliEntry)) {
|
|
7200
7567
|
console.log(`${DIM$1}✗ CLI entrypoint not found at ${cliEntry}${RESET$1}`);
|
|
7201
7568
|
return {
|
|
@@ -7213,6 +7580,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
7213
7580
|
let tempDir = null;
|
|
7214
7581
|
let deletedSkills = [];
|
|
7215
7582
|
let resolvedPaths = null;
|
|
7583
|
+
const latestHashes = /* @__PURE__ */ new Map();
|
|
7216
7584
|
if (cloneSource === null) {
|
|
7217
7585
|
failCount += skillsForSource.length;
|
|
7218
7586
|
console.log(`${DIM$1}✗ Cannot update ${source}: skills-lock.json is missing sourceUrl for this generic Git source${RESET$1}`);
|
|
@@ -7230,6 +7598,10 @@ async function updateProjectSkills(options = {}) {
|
|
|
7230
7598
|
const resolution = await checkAndPromptForDeletions(source, allLockedForSource, localLock.skills, false, options, discoveredLocations);
|
|
7231
7599
|
deletedSkills = resolution.deletedSkills;
|
|
7232
7600
|
resolvedPaths = resolution.resolvedPaths;
|
|
7601
|
+
for (const [name, skillPath] of resolution.resolvedPaths) try {
|
|
7602
|
+
const hash = await computeSkillFolderHash(join(tempDir, dirname(skillPath)));
|
|
7603
|
+
if (hash) latestHashes.set(name, hash);
|
|
7604
|
+
} catch {}
|
|
7233
7605
|
} catch (error) {
|
|
7234
7606
|
console.log(`${DIM$1}✗ Failed to check for deleted skills from ${source}${RESET$1}`);
|
|
7235
7607
|
} finally {
|
|
@@ -7248,6 +7620,12 @@ async function updateProjectSkills(options = {}) {
|
|
|
7248
7620
|
...skill.entry,
|
|
7249
7621
|
skillPath: resolvedPath
|
|
7250
7622
|
} : skill.entry;
|
|
7623
|
+
const relocated = entry.skillPath !== skill.entry.skillPath;
|
|
7624
|
+
const latestHash = latestHashes.get(skill.name);
|
|
7625
|
+
if (!relocated && latestHash && latestHash === skill.entry.computedHash) {
|
|
7626
|
+
upToDateCount++;
|
|
7627
|
+
continue;
|
|
7628
|
+
}
|
|
7251
7629
|
console.log(`${TEXT$1}Updating ${safeName}…${RESET$1}`);
|
|
7252
7630
|
const installUrl = buildLocalUpdateSource(entry);
|
|
7253
7631
|
if (!installUrl) {
|
|
@@ -7284,6 +7662,8 @@ async function updateProjectSkills(options = {}) {
|
|
|
7284
7662
|
}
|
|
7285
7663
|
}
|
|
7286
7664
|
}
|
|
7665
|
+
if (upToDateCount > 0) if (upToDateCount === updatable.length && wkSuccessCount === 0 && wkFailCount === 0) console.log(`${TEXT$1}✓ All project skills are up to date${RESET$1}`);
|
|
7666
|
+
else console.log(`${DIM$1}${upToDateCount} project skill(s) already up to date${RESET$1}`);
|
|
7287
7667
|
printLegacyProjectSkills(legacy);
|
|
7288
7668
|
return {
|
|
7289
7669
|
successCount,
|
|
@@ -7794,6 +8174,8 @@ ${BOLD}Usage:${RESET} skills <command> [options]
|
|
|
7794
8174
|
${BOLD}Manage Skills:${RESET}
|
|
7795
8175
|
add <package> Add a skill package (alias: a)
|
|
7796
8176
|
e.g. vercel-labs/agent-skills
|
|
8177
|
+
notion
|
|
8178
|
+
https://notion.so/<skill-page>
|
|
7797
8179
|
https://github.com/vercel-labs/agent-skills
|
|
7798
8180
|
use <package>@<skill>
|
|
7799
8181
|
Generate a prompt for using one skill without installing it
|