@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 CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CommandDefinition, CommandSnapshot, ExtensionFactory } from "@crustjs/core";
2
+ import "@crustjs/utils/primitive";
2
3
  //#region src/agents.d.ts
3
4
  type AgentClass = "universal" | "additional";
4
5
  type Scope = "global" | "project";
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, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
3
- import { accessSync, constants, existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
4
- import { fileURLToPath } from "node:url";
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: await Promise.all(collected.map(async (entry) => ({
235
- path: entry.relPath,
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.0",
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.1",
49
- "@crustjs/prompts": "^0.2.2",
50
- "@crustjs/style": "^0.3.2"
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.0",
55
- "@crustjs/extensions": "0.3.0",
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.0",
61
+ "@crustjs/core": "^0.3.2",
62
62
  "typescript": "^7.0.0"
63
63
  },
64
64
  "peerDependenciesMeta": {