@ghl-ai/aw 0.1.57 → 0.1.58

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.
Files changed (2) hide show
  1. package/link.mjs +69 -3
  2. package/package.json +1 -1
package/link.mjs CHANGED
@@ -11,11 +11,29 @@ function forceSymlink(target, linkPath) {
11
11
  symlinkSync(target, linkPath);
12
12
  }
13
13
 
14
+ function linkNestedSkillRoot(target, linkPath, { silent }) {
15
+ try {
16
+ forceSymlink(target, linkPath);
17
+ return true;
18
+ } catch (error) {
19
+ if (!silent) {
20
+ const message = error instanceof Error ? error.message : String(error);
21
+ throw new Error(`Could not link nested skill ${linkPath} -> ${target}: ${message}`);
22
+ }
23
+ return false;
24
+ }
25
+ }
26
+
14
27
  const IDE_DIRS = ['.claude', '.cursor', '.codex'];
15
28
  // Per-file symlink types
16
29
  const FILE_TYPES = ['agents'];
17
30
  const ALL_KNOWN_TYPES = new Set([...FILE_TYPES, 'skills', 'commands', 'evals', 'references', 'docs']);
18
31
 
32
+ function realHomeDir() {
33
+ const HOME = homedir();
34
+ try { return realpathSync(HOME); } catch { return HOME; }
35
+ }
36
+
19
37
  /**
20
38
  * List namespace directories inside .aw_registry/ (skip dotfiles).
21
39
  */
@@ -36,6 +54,32 @@ function listDirs(dir) {
36
54
  .map(d => d.name);
37
55
  }
38
56
 
57
+ /**
58
+ * Recursively find directories below a skills/ folder that are real skill roots.
59
+ * A real skill root is a directory containing SKILL.md. Once found, do not
60
+ * descend further so bundled references/scripts inside that skill are preserved.
61
+ */
62
+ function findSkillRootDirs(skillsDir) {
63
+ const results = [];
64
+ function walk(dir, segments) {
65
+ if (!existsSync(dir)) return;
66
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
67
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
68
+
69
+ const childDir = join(dir, entry.name);
70
+ const childSegments = [...segments, entry.name];
71
+ if (existsSync(join(childDir, 'SKILL.md'))) {
72
+ results.push({ skillDirPath: childDir, segments: childSegments });
73
+ continue;
74
+ }
75
+
76
+ walk(childDir, childSegments);
77
+ }
78
+ }
79
+ walk(skillsDir, []);
80
+ return results;
81
+ }
82
+
39
83
  /**
40
84
  * Recursively find directories named `typeName` within `nsDir`,
41
85
  * skipping other known type directories to avoid false matches.
@@ -77,7 +121,7 @@ function cleanIdeSymlinks(cwd) {
77
121
  cleanSymlinksRecursive(ideDir);
78
122
  }
79
123
  // Also clean .agents/skills/ (global only — Codex reads from ~/.agents/skills/)
80
- const HOME = homedir();
124
+ const HOME = realHomeDir();
81
125
  if (cwd === HOME) {
82
126
  const agentsSkillsDir = join(cwd, '.agents', 'skills');
83
127
  if (existsSync(agentsSkillsDir)) cleanSymlinksRecursive(agentsSkillsDir);
@@ -140,7 +184,8 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
140
184
  // where $HOME may be /var/... but process.cwd() resolves to /private/var/...
141
185
  try { cwd = realpathSync(cwd); } catch { /* use as-is */ }
142
186
 
143
- const GLOBAL_AW_DIR = join(homedir(), '.aw_registry');
187
+ const HOME = realHomeDir();
188
+ const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
144
189
  let awDir = awDirOverride || getLocalRegistryDir(cwd, GLOBAL_AW_DIR);
145
190
  try { awDir = realpathSync(awDir); } catch { /* use as-is if it doesn't exist */ }
146
191
  if (!existsSync(awDir)) return 0;
@@ -185,6 +230,19 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
185
230
  try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
186
231
  }
187
232
  }
233
+
234
+ for (const { skillDirPath, segments: skillSegments } of findSkillRootDirs(skillsDir)) {
235
+ if (skillSegments.length <= 1) continue;
236
+ const flat = [ns, ...segments, ...skillSegments].join('-');
237
+
238
+ for (const ide of IDE_DIRS) {
239
+ const linkDir = join(cwd, ide, 'skills');
240
+ mkdirSync(linkDir, { recursive: true });
241
+ const linkPath = join(linkDir, flat);
242
+ const relTarget = relative(linkDir, skillDirPath);
243
+ if (linkNestedSkillRoot(relTarget, linkPath, { silent })) created++;
244
+ }
245
+ }
188
246
  }
189
247
  }
190
248
 
@@ -246,7 +304,7 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
246
304
  }
247
305
 
248
306
  // Codex per-skill symlinks: ~/.agents/skills/<name> (global only)
249
- if (cwd === homedir()) {
307
+ if (cwd === HOME) {
250
308
  const agentsSkillsDir = join(cwd, '.agents/skills');
251
309
  for (const ns of namespaces) {
252
310
  for (const { typeDirPath: skillsDir, segments } of findNestedTypeDirs(join(awDir, ns), 'skills')) {
@@ -258,6 +316,14 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
258
316
  const relTarget = relative(agentsSkillsDir, targetPath);
259
317
  try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
260
318
  }
319
+
320
+ for (const { skillDirPath, segments: skillSegments } of findSkillRootDirs(skillsDir)) {
321
+ if (skillSegments.length <= 1) continue;
322
+ const flat = [ns, ...segments, ...skillSegments].join('-');
323
+ const linkPath = join(agentsSkillsDir, flat);
324
+ const relTarget = relative(agentsSkillsDir, skillDirPath);
325
+ if (linkNestedSkillRoot(relTarget, linkPath, { silent })) created++;
326
+ }
261
327
  }
262
328
  }
263
329
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {