@crewhaus/slash-commands 0.2.4 → 0.3.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/dist/index.d.ts CHANGED
@@ -8,6 +8,15 @@ export type SlashCommand = {
8
8
  };
9
9
  export type LoadCommandsOptions = {
10
10
  readonly cwd?: string;
11
+ /** Home directory override for the user-level root (tests). */
12
+ readonly homeDir?: string;
13
+ /**
14
+ * Builtin command directories, merged at LOWEST precedence. Each entry is
15
+ * read as a flat directory of `<name>.md` files (no `.crewhaus/commands`
16
+ * suffix is appended). User (`~/.crewhaus/commands`) and project
17
+ * (`<cwd>/.crewhaus/commands`) commands override builtins by name.
18
+ */
19
+ readonly builtinDirs?: ReadonlyArray<string>;
11
20
  };
12
21
  export type ExpandResult = {
13
22
  readonly handled: boolean;
@@ -20,9 +29,11 @@ export declare class SlashCommandError extends CrewhausError {
20
29
  constructor(message: string, cause?: unknown);
21
30
  }
22
31
  /**
23
- * Read every `.md` file directly under `<cwd>/.crewhaus/commands/`. Each
24
- * file's basename (sans extension) becomes its key in the returned map.
25
- * Subdirectories are not currently walked (v1 keeps the namespace flat).
32
+ * Read every `.md` file directly under each command root — builtin dirs
33
+ * first, then `~/.crewhaus/commands/`, then `<cwd>/.crewhaus/commands/`
34
+ * so later roots override earlier ones by name. Each file's basename (sans
35
+ * extension) becomes its key in the returned map. Subdirectories are not
36
+ * currently walked (v1 keeps the namespace flat).
26
37
  */
27
38
  export declare function loadCommands(opts?: LoadCommandsOptions): Promise<Map<string, SlashCommand>>;
28
39
  type ParsedCommand = {
package/dist/index.js CHANGED
@@ -1,6 +1,17 @@
1
1
  /**
2
2
  * Catalog R9 `slash-commands` — markdown-templated user-input shortcuts
3
- * loaded from `<cwd>/.crewhaus/commands/<name>.md`.
3
+ * loaded from three kinds of roots, lowest precedence first (later roots
4
+ * override earlier ones by command name):
5
+ *
6
+ * 0. builtin directories (`opts.builtinDirs`) — shipped defaults, e.g.
7
+ * `@crewhaus/default-skills`' `commands/` dir. Each entry is used as
8
+ * the commands directory itself (it is NOT suffixed with
9
+ * `.crewhaus/commands`).
10
+ * 1. `~/.crewhaus/commands/<name>.md` — user-level commands.
11
+ * 2. `<cwd>/.crewhaus/commands/<name>.md` — project commands.
12
+ *
13
+ * A user or project command with a builtin's name replaces it wholesale, so
14
+ * builtins are overridable (and, with an empty body, effectively disabled).
4
15
  *
5
16
  * Each file is a markdown body with optional YAML frontmatter:
6
17
  *
@@ -25,6 +36,7 @@
25
36
  * package is pure: filesystem read + string templating, no I/O elsewhere.
26
37
  */
27
38
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
39
+ import { homedir } from "node:os";
28
40
  import { basename, join } from "node:path";
29
41
  import { CrewhausError } from "@crewhaus/errors";
30
42
  import { parse as parseYaml } from "yaml";
@@ -37,22 +49,36 @@ export class SlashCommandError extends CrewhausError {
37
49
  }
38
50
  }
39
51
  /**
40
- * Read every `.md` file directly under `<cwd>/.crewhaus/commands/`. Each
41
- * file's basename (sans extension) becomes its key in the returned map.
42
- * Subdirectories are not currently walked (v1 keeps the namespace flat).
52
+ * Read every `.md` file directly under each command root — builtin dirs
53
+ * first, then `~/.crewhaus/commands/`, then `<cwd>/.crewhaus/commands/`
54
+ * so later roots override earlier ones by name. Each file's basename (sans
55
+ * extension) becomes its key in the returned map. Subdirectories are not
56
+ * currently walked (v1 keeps the namespace flat).
43
57
  */
44
58
  export async function loadCommands(opts = {}) {
45
59
  const cwd = opts.cwd ?? process.cwd();
46
- const root = join(cwd, COMMANDS_RELATIVE);
47
- if (!existsSync(root))
48
- return new Map();
60
+ const home = opts.homeDir ?? homedir();
61
+ const userRoot = join(home, COMMANDS_RELATIVE);
62
+ const projectRoot = join(cwd, COMMANDS_RELATIVE);
63
+ const roots = [...(opts.builtinDirs ?? []), userRoot];
64
+ if (projectRoot !== userRoot)
65
+ roots.push(projectRoot);
49
66
  const out = new Map();
67
+ for (const root of roots) {
68
+ readCommandsUnder(root, out);
69
+ }
70
+ return out;
71
+ }
72
+ /** Read one commands root into `out` (later writers override by name). */
73
+ function readCommandsUnder(root, out) {
74
+ if (!existsSync(root))
75
+ return;
50
76
  let entries;
51
77
  try {
52
78
  entries = readdirSync(root);
53
79
  }
54
80
  catch {
55
- return out;
81
+ return;
56
82
  }
57
83
  for (const entry of entries) {
58
84
  if (!entry.endsWith(".md"))
@@ -92,7 +118,6 @@ export async function loadCommands(opts = {}) {
92
118
  ...(parsed.argumentHint !== undefined ? { argumentHint: parsed.argumentHint } : {}),
93
119
  });
94
120
  }
95
- return out;
96
121
  }
97
122
  /**
98
123
  * Split a slash-command markdown file into its optional frontmatter and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/slash-commands",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "Markdown-templated slash commands with $ARGUMENTS substitution",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "test": "bun test src"
16
16
  },
17
17
  "dependencies": {
18
- "@crewhaus/errors": "0.2.4",
18
+ "@crewhaus/errors": "0.3.1",
19
19
  "yaml": "^2.6.0"
20
20
  },
21
21
  "license": "Apache-2.0",