@klhapp/skillmux 0.2.1 → 0.4.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/src/types.ts CHANGED
@@ -83,16 +83,23 @@ export interface RateLimitConfig {
83
83
  trust_proxy?: boolean;
84
84
  }
85
85
 
86
+ export interface AdminConfig {
87
+ enabled: boolean;
88
+ token_env: string;
89
+ }
90
+
86
91
  export interface ServerConfig {
87
92
  auth_enabled: boolean;
88
93
  auth_token_env: string;
89
94
  allowed_origins: string[];
90
95
  hostname?: string;
91
96
  rate_limit?: RateLimitConfig;
97
+ admin?: AdminConfig;
92
98
  }
93
99
 
94
100
  export interface Config {
95
101
  vault_path: string;
102
+ local_vault_paths: string[];
96
103
  state_dir: string;
97
104
  recall: RecallConfig;
98
105
  thresholds: Thresholds;
package/src/vault.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { readdirSync, statSync } from "node:fs";
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
 
4
4
  export const SKILL_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,127}$/;
@@ -66,6 +66,64 @@ export async function scanVault(
66
66
  return skills;
67
67
  }
68
68
 
69
+ /** Local overlays checked first (in order), canonical vault_path is the fallback. */
70
+ export function vaultResolutionOrder(vaultPath: string, localVaultPaths: string[]): string[] {
71
+ return [...localVaultPaths, vaultPath];
72
+ }
73
+
74
+ /** Which configured root actually backs skillId, per local-overrides-first precedence. */
75
+ export function resolveSkillRoot(skillId: string, vaultPath: string, localVaultPaths: string[]): string | null {
76
+ for (const root of vaultResolutionOrder(vaultPath, localVaultPaths)) {
77
+ if (existsSync(join(root, skillId, "SKILL.md"))) return root;
78
+ }
79
+ return null;
80
+ }
81
+
82
+ /** Scans every configured root and merges by skill_id — first-seen (per resolution order) wins. */
83
+ export async function scanVaults(
84
+ vaultPath: string,
85
+ localVaultPaths: string[],
86
+ onInvalid?: (skillId: string, error: unknown) => void,
87
+ ): Promise<VaultSkill[]> {
88
+ const seen = new Set<string>();
89
+ const merged: VaultSkill[] = [];
90
+ for (const root of vaultResolutionOrder(vaultPath, localVaultPaths)) {
91
+ if (!existsSync(root)) continue;
92
+ for (const skill of await scanVault(root, onInvalid)) {
93
+ if (seen.has(skill.skill_id)) continue;
94
+ seen.add(skill.skill_id);
95
+ merged.push(skill);
96
+ }
97
+ }
98
+ return merged;
99
+ }
100
+
101
+ export interface ShadowedSkill {
102
+ skill_id: string;
103
+ winner: string;
104
+ shadowed: string[];
105
+ }
106
+
107
+ /** skill_ids present in more than one configured root — every root after the winner is silently shadowed. */
108
+ export function findShadowedSkills(vaultPath: string, localVaultPaths: string[]): ShadowedSkill[] {
109
+ const rootsBySkillId = new Map<string, string[]>();
110
+ for (const root of vaultResolutionOrder(vaultPath, localVaultPaths)) {
111
+ if (!existsSync(root)) continue;
112
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
113
+ if (!entry.isDirectory() || !SKILL_ID_PATTERN.test(entry.name)) continue;
114
+ if (!existsSync(join(root, entry.name, "SKILL.md"))) continue;
115
+ const roots = rootsBySkillId.get(entry.name) ?? [];
116
+ roots.push(root);
117
+ rootsBySkillId.set(entry.name, roots);
118
+ }
119
+ }
120
+ const shadowed: ShadowedSkill[] = [];
121
+ for (const [skillId, roots] of rootsBySkillId) {
122
+ if (roots.length > 1) shadowed.push({ skill_id: skillId, winner: roots[0]!, shadowed: roots.slice(1) });
123
+ }
124
+ return shadowed.sort((a, b) => a.skill_id.localeCompare(b.skill_id));
125
+ }
126
+
69
127
  /** Relative paths of everything under the skill dir except SKILL.md itself, sorted. */
70
128
  export function listSupportingFiles(vaultPath: string, skillId: string): string[] {
71
129
  const root = join(vaultPath, skillId);