@crustjs/skills 0.3.1 → 0.3.3

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";
@@ -259,77 +174,6 @@ var SkillConflictError = class extends Error {
259
174
  }
260
175
  };
261
176
  //#endregion
262
- //#region ../utils/src/artifacts.ts
263
- /**
264
- * Set by `crust build` while it prepares the Command Snapshot: the absolute
265
- * entry-isolated directory Extension build hooks write into. Core reads it
266
- * to run the hooks; `resolveArtifactDir` reads it so sections evaluated during
267
- * that run see the artifacts being built instead of the wiped `.crust/root`.
268
- */
269
- const BUILD_OUT_DIR_ENV = "CRUST_INTERNAL_BUILD_OUT_DIR";
270
- /** True inside a `bun build --compile` or `deno compile` executable. */
271
- function isCompiledExecutable() {
272
- const { Bun, Deno } = globalThis;
273
- const bunMain = Bun?.main ?? "";
274
- return bunMain.startsWith("/$bunfs/") || /^[A-Za-z]:[\\/]~BUN[\\/]/.test(bunMain) || Deno?.build?.standalone === true;
275
- }
276
- /**
277
- * Absolute path of a build artifact or `crust.include` directory shipped with
278
- * this CLI. `name` is a top-level directory name such as `"skills"`.
279
- *
280
- * The artifact path is computed from how the CLI is running — never probed:
281
- * - Compiled executable (Bun or Deno): `<dir of the executable>/<name>`, which
282
- * is a platform package's `bin/` or wherever the binary was placed.
283
- * - Crust-built Node bundle: `<name>` next to the bundle's `bin/` directory,
284
- * i.e. `.crust/root/<name>` in place and `<installed root>/<name>` after install.
285
- * - Snapshot preparation inside `crust build`: `<build output dir>/<name>`, the
286
- * artifacts earlier Extension build hooks wrote in this same build.
287
- * - Source (`bun run`, `node`, `deno run`): `.crust/root/<name>` under the
288
- * nearest package root of the real `process.argv[1]` entrypoint (following source
289
- * links) — the output of the last `crust build`.
290
- *
291
- * @throws {Error} when `name` is not a single path segment, or in source mode
292
- * when the entrypoint cannot be resolved or has no enclosing `package.json`.
293
- */
294
- function resolveArtifactDir(name) {
295
- if (name === "" || name === "." || name === ".." || /[\\/]/.test(name)) throw new Error(`Artifact name must be a single directory name, got ${JSON.stringify(name)}.`);
296
- if (isCompiledExecutable()) return join(dirname(process.execPath), name);
297
- if (process.env.CRUST_INTERNAL_BUILD === "1") return resolve(fileURLToPath(import.meta.url), "..", "..", name);
298
- const buildOutDir = process.env[BUILD_OUT_DIR_ENV];
299
- if (buildOutDir) return join(buildOutDir, name);
300
- const entrypoint = process.argv[1];
301
- let sourceEntrypoint = entrypoint;
302
- if (entrypoint) try {
303
- sourceEntrypoint = realpathSync(entrypoint);
304
- } catch (cause) {
305
- throw new Error(`Could not resolve artifact "${name}": could not resolve source entrypoint "${entrypoint}".`, { cause });
306
- }
307
- const packageRoot = sourceEntrypoint ? findNearestPackageRoot(sourceEntrypoint) : null;
308
- if (!packageRoot) throw new Error(`Could not resolve artifact "${name}": no package.json was found above ${entrypoint ? `entrypoint "${entrypoint}"` : "process.argv[1] (unset)"}.`);
309
- return join(packageRoot, ".crust", "root", name);
310
- }
311
- //#endregion
312
- //#region ../utils/src/process.ts
313
- /** Resolve a bare executable name from PATH (and PATHEXT on Windows). */
314
- function which(command) {
315
- if (command.includes(sep) || command.includes("/")) {
316
- try {
317
- accessSync(command, constants.X_OK);
318
- if (statSync(command).isFile()) return command;
319
- } catch {}
320
- return null;
321
- }
322
- const extensions = process.platform === "win32" && !extname(command) ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
323
- for (const directory of process.env.PATH?.split(delimiter) ?? []) for (const extension of extensions) {
324
- const candidate = resolve(join(directory, command + extension));
325
- try {
326
- accessSync(candidate, constants.X_OK);
327
- if (statSync(candidate).isFile()) return candidate;
328
- } catch {}
329
- }
330
- return null;
331
- }
332
- //#endregion
333
177
  //#region src/agents.ts
334
178
  const PROJECT_UNIVERSAL_SKILLS_DIR = join(".agents", "skills");
335
179
  function configHome(home) {
@@ -618,12 +462,6 @@ function resolveAgentPath(agent, scope, name) {
618
462
  return join(cfg.globalSkillsDir(homedir()), name);
619
463
  }
620
464
  //#endregion
621
- //#region ../utils/src/error.ts
622
- /** Narrows a caught Node.js system error to its stable errno contract. */
623
- function isErrnoException(error) {
624
- return error instanceof Error && "code" in error && typeof error.code === "string";
625
- }
626
- //#endregion
627
465
  //#region src/link.ts
628
466
  /** Returns whether a symlink target carries Crust's skill ownership signature. */
629
467
  function isOwnedSkillLink(target, name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crustjs/skills",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
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.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.1",
55
- "@crustjs/extensions": "0.3.1",
55
+ "@crustjs/core": "0.3.3",
56
+ "@crustjs/extensions": "0.3.3",
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.1",
61
+ "@crustjs/core": "^0.3.3",
62
62
  "typescript": "^7.0.0"
63
63
  },
64
64
  "peerDependenciesMeta": {