@crustjs/skills 0.3.0 → 0.3.2
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/dist/index.d.ts +1 -0
- package/dist/index.js +13 -174
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { lstat, mkdir, readFile, readdir, readlink, realpath, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
2
|
-
import { basename,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { basename, dirname, join, posix, relative, resolve } from "node:path";
|
|
3
|
+
import { isWithin } from "@crustjs/utils/path";
|
|
4
|
+
import { resolveSourceDir } from "@crustjs/utils/source";
|
|
5
5
|
import { buildCommandDocumentation, formatDefault, sectionsFor } from "@crustjs/core/tooling";
|
|
6
6
|
import { defineCommand, defineExtension, defineExtensionId } from "@crustjs/core";
|
|
7
7
|
import { spinner } from "@crustjs/progress";
|
|
8
8
|
import { confirm, multiselect, select } from "@crustjs/prompts";
|
|
9
9
|
import { bold, dim, yellow } from "@crustjs/style";
|
|
10
|
+
import { resolveArtifactDir } from "@crustjs/utils/artifacts";
|
|
10
11
|
import { homedir } from "node:os";
|
|
12
|
+
import { which } from "@crustjs/utils/process";
|
|
13
|
+
import { isErrnoException } from "@crustjs/utils/error";
|
|
14
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
11
15
|
//#region \0rolldown/runtime.js
|
|
12
16
|
var __defProp = Object.defineProperty;
|
|
13
17
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -20,95 +24,6 @@ var __exportAll = (all, no_symbols) => {
|
|
|
20
24
|
return target;
|
|
21
25
|
};
|
|
22
26
|
//#endregion
|
|
23
|
-
//#region ../utils/src/path.ts
|
|
24
|
-
/** Returns whether child is parent itself or lies below it. */
|
|
25
|
-
function isWithin(parent, child) {
|
|
26
|
-
const path = relative(parent, child);
|
|
27
|
-
return path === "" || !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
//#region ../utils/src/source.ts
|
|
31
|
-
/**
|
|
32
|
-
* Find the nearest directory containing `package.json` by walking up the
|
|
33
|
-
* filesystem from `startPath`.
|
|
34
|
-
*
|
|
35
|
-
* If `startPath` points at an existing file, its parent directory is used as
|
|
36
|
-
* the starting point. The directory walk uses `path.resolve()` (lexical), not
|
|
37
|
-
* `fs.realpath()`, so a symlink's parent chain is walked rather than the
|
|
38
|
-
* symlink target's parent chain.
|
|
39
|
-
*
|
|
40
|
-
* @param startPath - Directory or file path to start walking from.
|
|
41
|
-
* @returns The absolute path of the nearest enclosing directory containing
|
|
42
|
-
* `package.json`, or `null` if the filesystem root is reached first.
|
|
43
|
-
*
|
|
44
|
-
* @internal Shared by {@link resolveSourceDir} and `resolveArtifactDir`.
|
|
45
|
-
*/
|
|
46
|
-
function findNearestPackageRoot(startPath) {
|
|
47
|
-
let current = resolve(startPath);
|
|
48
|
-
if (existsSync(current) && !statSync(current).isDirectory()) current = dirname(current);
|
|
49
|
-
while (true) {
|
|
50
|
-
if (existsSync(join(current, "package.json"))) return current;
|
|
51
|
-
const parent = dirname(current);
|
|
52
|
-
if (parent === current) return null;
|
|
53
|
-
current = parent;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Resolves an absolute filesystem path from a caller-supplied source
|
|
58
|
-
* directory descriptor. Designed for tools that ship reference assets
|
|
59
|
-
* (templates, skill bundles, etc.) alongside their published package and
|
|
60
|
-
* need a uniform way to locate them at runtime regardless of how the
|
|
61
|
-
* consumer expressed the path.
|
|
62
|
-
*
|
|
63
|
-
* Three input modes are supported:
|
|
64
|
-
* - **`URL`** — must use the `file:` protocol. Resolved via
|
|
65
|
-
* `url.fileURLToPath()`. The intended idiom is
|
|
66
|
-
* `new URL("./relative/path", import.meta.url)`.
|
|
67
|
-
* - **Absolute string path** — returned as `path.resolve(input)`.
|
|
68
|
-
* - **Relative string path** — resolved against the nearest `package.json`
|
|
69
|
-
* directory walking up from `process.argv[1]`. This makes
|
|
70
|
-
* `"templates/base"` resolve to `<consumer-package-root>/templates/base`
|
|
71
|
-
* regardless of cwd.
|
|
72
|
-
*
|
|
73
|
-
* Three failure modes throw descriptive `Error`s:
|
|
74
|
-
* - URL with non-`file:` protocol — message names the offending protocol.
|
|
75
|
-
* - Relative string path with `process.argv[1]` unset — message suggests
|
|
76
|
-
* switching to an absolute path or a `file:` URL.
|
|
77
|
-
* - Relative string path but no `package.json` found walking up — message
|
|
78
|
-
* names the entrypoint and the relative input, and suggests switching to
|
|
79
|
-
* an absolute path or a `file:` URL.
|
|
80
|
-
*
|
|
81
|
-
* @param input - File URL, absolute path, or package-relative path.
|
|
82
|
-
* @returns Absolute filesystem path.
|
|
83
|
-
* @throws {Error} For any of the three failure modes above.
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* ```ts
|
|
87
|
-
* import { resolveSourceDir } from "@crustjs/utils/source";
|
|
88
|
-
*
|
|
89
|
-
* // 1. file: URL — relative to the calling module
|
|
90
|
-
* const a = resolveSourceDir(new URL("../templates/base", import.meta.url));
|
|
91
|
-
*
|
|
92
|
-
* // 2. Absolute path
|
|
93
|
-
* const b = resolveSourceDir("/abs/path/to/templates/base");
|
|
94
|
-
*
|
|
95
|
-
* // 3. Relative path — resolved from the consuming package's root
|
|
96
|
-
* const c = resolveSourceDir("templates/base");
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
function resolveSourceDir(input) {
|
|
100
|
-
if (input instanceof URL) {
|
|
101
|
-
if (input.protocol !== "file:") throw new Error(`sourceDir URL must use file: protocol, got "${input.protocol}".`);
|
|
102
|
-
return fileURLToPath(input);
|
|
103
|
-
}
|
|
104
|
-
if (isAbsolute(input)) return resolve(input);
|
|
105
|
-
const entrypoint = process.argv[1];
|
|
106
|
-
if (!entrypoint) throw new Error(`Could not resolve relative sourceDir "${input}" because process.argv[1] is not set. Pass an absolute path or a file: URL.`);
|
|
107
|
-
const packageRoot = findNearestPackageRoot(resolve(entrypoint));
|
|
108
|
-
if (!packageRoot) throw new Error(`Could not resolve relative sourceDir "${input}" from entrypoint "${entrypoint}" because no package.json was found in its parent directories. Pass an absolute path or a file: URL.`);
|
|
109
|
-
return resolve(packageRoot, input);
|
|
110
|
-
}
|
|
111
|
-
//#endregion
|
|
112
27
|
//#region src/bundle.ts
|
|
113
28
|
/** Filename of the entrypoint markdown file required at the bundle root. */
|
|
114
29
|
const SKILL_MD = "SKILL.md";
|
|
@@ -230,12 +145,13 @@ async function loadBundleFiles(sourceDir) {
|
|
|
230
145
|
const collected = await collectBundleEntries(canonicalRoot, canonicalRoot, "", /* @__PURE__ */ new Set([canonicalRoot]));
|
|
231
146
|
const skillMd = collected.find((f) => f.relPath === SKILL_MD);
|
|
232
147
|
if (!skillMd) throw new Error(`Extra skill directory is missing SKILL.md at its root "${canonicalRoot}". Every extra skill directory must contain a top-level SKILL.md file.`);
|
|
148
|
+
const files = await Promise.all(collected.map(async (entry) => ({
|
|
149
|
+
path: entry.relPath,
|
|
150
|
+
content: await readFile(entry.absPath)
|
|
151
|
+
})));
|
|
233
152
|
return {
|
|
234
|
-
files
|
|
235
|
-
|
|
236
|
-
content: await readFile(entry.absPath)
|
|
237
|
-
}))),
|
|
238
|
-
frontmatter: requireSkillFrontmatter(probeFrontmatter(await readFile(skillMd.absPath, "utf-8")), `Extra skill SKILL.md at "${join(canonicalRoot, SKILL_MD)}"`)
|
|
153
|
+
files,
|
|
154
|
+
frontmatter: requireSkillFrontmatter(probeFrontmatter(files[collected.indexOf(skillMd)].content.toString("utf-8")), `Extra skill SKILL.md at "${join(canonicalRoot, SKILL_MD)}"`)
|
|
239
155
|
};
|
|
240
156
|
}
|
|
241
157
|
//#endregion
|
|
@@ -258,77 +174,6 @@ var SkillConflictError = class extends Error {
|
|
|
258
174
|
}
|
|
259
175
|
};
|
|
260
176
|
//#endregion
|
|
261
|
-
//#region ../utils/src/artifacts.ts
|
|
262
|
-
/**
|
|
263
|
-
* Set by `crust build` while it prepares the Command Snapshot: the absolute
|
|
264
|
-
* entry-isolated directory Extension build hooks write into. Core reads it
|
|
265
|
-
* to run the hooks; `resolveArtifactDir` reads it so sections evaluated during
|
|
266
|
-
* that run see the artifacts being built instead of the wiped `.crust/root`.
|
|
267
|
-
*/
|
|
268
|
-
const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
|
|
269
|
-
/** True inside a `bun build --compile` or `deno compile` executable. */
|
|
270
|
-
function isCompiledExecutable() {
|
|
271
|
-
const { Bun, Deno } = globalThis;
|
|
272
|
-
const bunMain = Bun?.main ?? "";
|
|
273
|
-
return bunMain.startsWith("/$bunfs/") || /^[A-Za-z]:[\\/]~BUN[\\/]/.test(bunMain) || Deno?.build?.standalone === true;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Absolute path of a build artifact or `crust.include` directory shipped with
|
|
277
|
-
* this CLI. `name` is a top-level directory name such as `"skills"`.
|
|
278
|
-
*
|
|
279
|
-
* The artifact path is computed from how the CLI is running — never probed:
|
|
280
|
-
* - Compiled executable (Bun or Deno): `<dir of the executable>/<name>`, which
|
|
281
|
-
* is a platform package's `bin/` or wherever the binary was placed.
|
|
282
|
-
* - Crust-built Node bundle: `<name>` next to the bundle's `bin/` directory,
|
|
283
|
-
* i.e. `.crust/root/<name>` in place and `<installed root>/<name>` after install.
|
|
284
|
-
* - Snapshot preparation inside `crust build`: `<build output dir>/<name>`, the
|
|
285
|
-
* artifacts earlier Extension build hooks wrote in this same build.
|
|
286
|
-
* - Source (`bun run`, `node`, `deno run`): `.crust/root/<name>` under the
|
|
287
|
-
* nearest package root of the real `process.argv[1]` entrypoint (following source
|
|
288
|
-
* links) — the output of the last `crust build`.
|
|
289
|
-
*
|
|
290
|
-
* @throws {Error} when `name` is not a single path segment, or in source mode
|
|
291
|
-
* when the entrypoint cannot be resolved or has no enclosing `package.json`.
|
|
292
|
-
*/
|
|
293
|
-
function resolveArtifactDir(name) {
|
|
294
|
-
if (name === "" || name === "." || name === ".." || /[\\/]/.test(name)) throw new Error(`Artifact name must be a single directory name, got ${JSON.stringify(name)}.`);
|
|
295
|
-
if (isCompiledExecutable()) return join(dirname(process.execPath), name);
|
|
296
|
-
if (process.env.CRUST_INTERNAL_BUILD === "1") return resolve(fileURLToPath(import.meta.url), "..", "..", name);
|
|
297
|
-
const buildOutDir = process.env[BUILD_OUT_DIR_ENV];
|
|
298
|
-
if (buildOutDir) return join(buildOutDir, name);
|
|
299
|
-
const entrypoint = process.argv[1];
|
|
300
|
-
let sourceEntrypoint = entrypoint;
|
|
301
|
-
if (entrypoint) try {
|
|
302
|
-
sourceEntrypoint = realpathSync(entrypoint);
|
|
303
|
-
} catch (cause) {
|
|
304
|
-
throw new Error(`Could not resolve artifact "${name}": could not resolve source entrypoint "${entrypoint}".`, { cause });
|
|
305
|
-
}
|
|
306
|
-
const packageRoot = sourceEntrypoint ? findNearestPackageRoot(sourceEntrypoint) : null;
|
|
307
|
-
if (!packageRoot) throw new Error(`Could not resolve artifact "${name}": no package.json was found above ${entrypoint ? `entrypoint "${entrypoint}"` : "process.argv[1] (unset)"}.`);
|
|
308
|
-
return join(packageRoot, ".crust", "root", name);
|
|
309
|
-
}
|
|
310
|
-
//#endregion
|
|
311
|
-
//#region ../utils/src/process.ts
|
|
312
|
-
/** Resolve a bare executable name from PATH (and PATHEXT on Windows). */
|
|
313
|
-
function which(command) {
|
|
314
|
-
if (command.includes(sep) || command.includes("/")) {
|
|
315
|
-
try {
|
|
316
|
-
accessSync(command, constants.X_OK);
|
|
317
|
-
if (statSync(command).isFile()) return command;
|
|
318
|
-
} catch {}
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
const extensions = process.platform === "win32" && !extname(command) ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
|
|
322
|
-
for (const directory of process.env.PATH?.split(delimiter) ?? []) for (const extension of extensions) {
|
|
323
|
-
const candidate = resolve(join(directory, command + extension));
|
|
324
|
-
try {
|
|
325
|
-
accessSync(candidate, constants.X_OK);
|
|
326
|
-
if (statSync(candidate).isFile()) return candidate;
|
|
327
|
-
} catch {}
|
|
328
|
-
}
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
//#endregion
|
|
332
177
|
//#region src/agents.ts
|
|
333
178
|
const PROJECT_UNIVERSAL_SKILLS_DIR = join(".agents", "skills");
|
|
334
179
|
function configHome(home) {
|
|
@@ -617,12 +462,6 @@ function resolveAgentPath(agent, scope, name) {
|
|
|
617
462
|
return join(cfg.globalSkillsDir(homedir()), name);
|
|
618
463
|
}
|
|
619
464
|
//#endregion
|
|
620
|
-
//#region ../utils/src/error.ts
|
|
621
|
-
/** Narrows a caught Node.js system error to its stable errno contract. */
|
|
622
|
-
function isErrnoException(error) {
|
|
623
|
-
return error instanceof Error && "code" in error && typeof error.code === "string";
|
|
624
|
-
}
|
|
625
|
-
//#endregion
|
|
626
465
|
//#region src/link.ts
|
|
627
466
|
/** Returns whether a symlink target carries Crust's skill ownership signature. */
|
|
628
467
|
function isOwnedSkillLink(target, name) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crustjs/skills",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Package and install agent skills for AI coding assistants.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -45,20 +45,20 @@
|
|
|
45
45
|
"postpack": "rm -f LICENSE"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@crustjs/progress": "^0.1.
|
|
49
|
-
"@crustjs/prompts": "^0.2.
|
|
50
|
-
"@crustjs/style": "^0.3.
|
|
48
|
+
"@crustjs/progress": "^0.1.2",
|
|
49
|
+
"@crustjs/prompts": "^0.2.3",
|
|
50
|
+
"@crustjs/style": "^0.3.3",
|
|
51
|
+
"@crustjs/utils": "^0.1.0"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@crustjs/config": "0.0.0",
|
|
54
|
-
"@crustjs/core": "0.3.
|
|
55
|
-
"@crustjs/extensions": "0.3.
|
|
55
|
+
"@crustjs/core": "0.3.2",
|
|
56
|
+
"@crustjs/extensions": "0.3.2",
|
|
56
57
|
"@crustjs/testing": "0.1.2",
|
|
57
|
-
"@crustjs/utils": "0.0.0",
|
|
58
58
|
"tsdown": "^0.23.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@crustjs/core": "^0.3.
|
|
61
|
+
"@crustjs/core": "^0.3.2",
|
|
62
62
|
"typescript": "^7.0.0"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|