@alavida/agentpack 0.1.1 → 0.1.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.
@@ -0,0 +1,117 @@
1
+ import { existsSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
2
+ import { dirname, join, relative } from 'node:path';
3
+ import { writeInstallState } from '../fs/install-state-repository.js';
4
+
5
+ function ensureDir(pathValue) {
6
+ mkdirSync(pathValue, { recursive: true });
7
+ }
8
+
9
+ export function removePathIfExists(pathValue) {
10
+ rmSync(pathValue, { recursive: true, force: true });
11
+ }
12
+
13
+ export function ensureSkillLink(repoRoot, baseDir, skillName, skillDir, normalizeDisplayPath) {
14
+ const skillsDir = join(repoRoot, baseDir, 'skills');
15
+ ensureDir(skillsDir);
16
+ const linkPath = join(skillsDir, skillName);
17
+ removePathIfExists(linkPath);
18
+ symlinkSync(skillDir, linkPath, 'dir');
19
+ return normalizeDisplayPath(repoRoot, linkPath);
20
+ }
21
+
22
+ export function removeSkillLinks(repoRoot, name, normalizeDisplayPath) {
23
+ const removed = [];
24
+ for (const pathValue of [
25
+ join(repoRoot, '.claude', 'skills', name),
26
+ join(repoRoot, '.agents', 'skills', name),
27
+ ]) {
28
+ if (!existsSync(pathValue)) continue;
29
+ removePathIfExists(pathValue);
30
+ removed.push(normalizeDisplayPath(repoRoot, pathValue));
31
+ }
32
+ return removed;
33
+ }
34
+
35
+ export function removeSkillLinksByNames(repoRoot, names, normalizeDisplayPath) {
36
+ const removed = [];
37
+ for (const name of names) {
38
+ removed.push(...removeSkillLinks(repoRoot, name, normalizeDisplayPath));
39
+ }
40
+ return [...new Set(removed)];
41
+ }
42
+
43
+ function ensureSymlink(targetPath, linkPath) {
44
+ removePathIfExists(linkPath);
45
+ mkdirSync(dirname(linkPath), { recursive: true });
46
+ symlinkSync(targetPath, linkPath, 'dir');
47
+ }
48
+
49
+ export function buildInstallRecord(repoRoot, packageDir, directTargetMap, {
50
+ parseSkillFrontmatterFile,
51
+ readPackageMetadata,
52
+ normalizeRelativePath,
53
+ } = {}) {
54
+ const packageMetadata = readPackageMetadata(packageDir);
55
+ if (!packageMetadata.packageName) return null;
56
+
57
+ const skillMetadata = parseSkillFrontmatterFile(join(packageDir, 'SKILL.md'));
58
+ const skillDirName = skillMetadata.name;
59
+ const materializations = [];
60
+
61
+ const claudeTargetAbs = join(repoRoot, '.claude', 'skills', skillDirName);
62
+ ensureSymlink(packageDir, claudeTargetAbs);
63
+ materializations.push({
64
+ target: normalizeRelativePath(relative(repoRoot, claudeTargetAbs)),
65
+ mode: 'symlink',
66
+ });
67
+
68
+ const agentsTargetAbs = join(repoRoot, '.agents', 'skills', skillDirName);
69
+ ensureSymlink(packageDir, agentsTargetAbs);
70
+ materializations.push({
71
+ target: normalizeRelativePath(relative(repoRoot, agentsTargetAbs)),
72
+ mode: 'symlink',
73
+ });
74
+
75
+ return {
76
+ packageName: packageMetadata.packageName,
77
+ direct: directTargetMap.has(packageMetadata.packageName),
78
+ requestedTarget: directTargetMap.get(packageMetadata.packageName) || null,
79
+ packageVersion: packageMetadata.packageVersion,
80
+ sourcePackagePath: normalizeRelativePath(relative(repoRoot, packageDir)),
81
+ materializations,
82
+ };
83
+ }
84
+
85
+ export function rebuildInstallState(repoRoot, directTargetMap, {
86
+ listInstalledPackageDirs,
87
+ parseSkillFrontmatterFile,
88
+ readPackageMetadata,
89
+ normalizeRelativePath,
90
+ } = {}) {
91
+ const packageDirs = listInstalledPackageDirs(join(repoRoot, 'node_modules'));
92
+ const installs = {};
93
+
94
+ for (const packageDir of packageDirs) {
95
+ const skillFile = join(packageDir, 'SKILL.md');
96
+ if (!existsSync(skillFile)) continue;
97
+
98
+ const record = buildInstallRecord(repoRoot, packageDir, directTargetMap, {
99
+ parseSkillFrontmatterFile,
100
+ readPackageMetadata,
101
+ normalizeRelativePath,
102
+ });
103
+ if (!record) continue;
104
+
105
+ installs[record.packageName] = {
106
+ direct: record.direct,
107
+ requested_target: record.requestedTarget,
108
+ package_version: record.packageVersion,
109
+ source_package_path: record.sourcePackagePath,
110
+ materializations: record.materializations,
111
+ };
112
+ }
113
+
114
+ const state = { version: 1, installs };
115
+ writeInstallState(repoRoot, state);
116
+ return state;
117
+ }
@@ -0,0 +1,44 @@
1
+ import { existsSync, readdirSync, watch } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ export function watchDirectoryTree(rootDir, onChange) {
5
+ const watchers = new Map();
6
+
7
+ const watchDir = (dirPath) => {
8
+ if (watchers.has(dirPath) || !existsSync(dirPath)) return;
9
+
10
+ let entries = [];
11
+ try {
12
+ entries = readdirSync(dirPath, { withFileTypes: true });
13
+ } catch {
14
+ return;
15
+ }
16
+
17
+ const watcher = watch(dirPath, (_eventType, filename) => {
18
+ if (filename) {
19
+ const changedPath = join(dirPath, String(filename));
20
+ if (existsSync(changedPath)) {
21
+ watchDir(changedPath);
22
+ }
23
+ }
24
+
25
+ onChange();
26
+ });
27
+
28
+ watchers.set(dirPath, watcher);
29
+
30
+ for (const entry of entries) {
31
+ if (!entry.isDirectory()) continue;
32
+ watchDir(join(dirPath, entry.name));
33
+ }
34
+ };
35
+
36
+ watchDir(rootDir);
37
+
38
+ return {
39
+ close() {
40
+ for (const watcher of watchers.values()) watcher.close();
41
+ watchers.clear();
42
+ },
43
+ };
44
+ }
@@ -1,11 +1,11 @@
1
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, watch, writeFileSync } from 'node:fs';
1
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join, resolve } from 'node:path';
3
3
  import { createRequire } from 'node:module';
4
+ import { resolveDependencyClosure } from '../domain/skills/skill-graph.js';
5
+ import { normalizeRepoPath, parseSkillFrontmatterFile, readPackageMetadata } from '../domain/skills/skill-model.js';
6
+ import { watchDirectoryTree } from '../infrastructure/runtime/watch-tree.js';
4
7
  import {
5
8
  findPackageDirByName,
6
- normalizeRepoPath,
7
- parseSkillFrontmatterFile,
8
- readPackageMetadata,
9
9
  syncSkillDependencies,
10
10
  } from './skills.js';
11
11
  import { findRepoRoot } from './context.js';
@@ -76,50 +76,30 @@ function collectPluginLocalSkillDirs(pluginDir) {
76
76
  }
77
77
 
78
78
  function resolveBundleClosure(repoRoot, pluginDir, directRequires) {
79
- const seen = new Set();
80
- const queue = [...directRequires];
81
- const bundled = [];
82
- const unresolved = [];
83
-
84
- while (queue.length > 0) {
85
- const packageName = queue.shift();
86
- if (seen.has(packageName)) continue;
87
- seen.add(packageName);
88
-
89
- const resolvedPackage = resolvePackageDir(repoRoot, pluginDir, packageName);
90
- if (!resolvedPackage) {
91
- unresolved.push(packageName);
92
- continue;
93
- }
94
-
95
- const skillFile = join(resolvedPackage.packageDir, 'SKILL.md');
96
- if (!existsSync(skillFile)) {
97
- unresolved.push(packageName);
98
- continue;
99
- }
100
-
101
- const metadata = parseSkillFrontmatterFile(skillFile);
102
- const packageMetadata = readPackageMetadata(resolvedPackage.packageDir);
103
-
104
- bundled.push({
105
- packageName,
106
- packageVersion: packageMetadata.packageVersion,
107
- skillName: metadata.name,
108
- skillFile,
109
- packageDir: resolvedPackage.packageDir,
110
- source: resolvedPackage.source,
111
- requires: metadata.requires,
112
- });
113
-
114
- for (const requirement of metadata.requires) {
115
- if (!seen.has(requirement)) queue.push(requirement);
116
- }
117
- }
118
-
119
- bundled.sort((a, b) => a.packageName.localeCompare(b.packageName));
120
- unresolved.sort();
79
+ const { resolved, unresolved } = resolveDependencyClosure(directRequires, {
80
+ resolveNode(packageName) {
81
+ const resolvedPackage = resolvePackageDir(repoRoot, pluginDir, packageName);
82
+ if (!resolvedPackage) return null;
83
+
84
+ const skillFile = join(resolvedPackage.packageDir, 'SKILL.md');
85
+ if (!existsSync(skillFile)) return null;
86
+
87
+ const metadata = parseSkillFrontmatterFile(skillFile);
88
+ const packageMetadata = readPackageMetadata(resolvedPackage.packageDir);
89
+
90
+ return {
91
+ packageName,
92
+ packageVersion: packageMetadata.packageVersion,
93
+ skillName: metadata.name,
94
+ skillFile,
95
+ packageDir: resolvedPackage.packageDir,
96
+ source: resolvedPackage.source,
97
+ requires: metadata.requires,
98
+ };
99
+ },
100
+ });
121
101
 
122
- return { bundled, unresolved };
102
+ return { bundled: resolved, unresolved };
123
103
  }
124
104
 
125
105
  function stagePluginRuntimeFiles(pluginDir, stageDir) {
@@ -130,48 +110,6 @@ function stagePluginRuntimeFiles(pluginDir, stageDir) {
130
110
  }
131
111
  }
132
112
 
133
- function watchDirectoryTree(rootDir, onChange) {
134
- const watchers = new Map();
135
-
136
- const watchDir = (dirPath) => {
137
- if (watchers.has(dirPath) || !existsSync(dirPath)) return;
138
-
139
- let entries = [];
140
- try {
141
- entries = readdirSync(dirPath, { withFileTypes: true });
142
- } catch {
143
- return;
144
- }
145
-
146
- const watcher = watch(dirPath, (_eventType, filename) => {
147
- if (filename) {
148
- const changedPath = join(dirPath, String(filename));
149
- if (existsSync(changedPath)) {
150
- watchDir(changedPath);
151
- }
152
- }
153
-
154
- onChange();
155
- });
156
-
157
- watchers.set(dirPath, watcher);
158
-
159
- for (const entry of entries) {
160
- if (!entry.isDirectory()) continue;
161
- watchDir(join(dirPath, entry.name));
162
- }
163
- };
164
-
165
- watchDir(rootDir);
166
-
167
- return {
168
- close() {
169
- for (const watcher of watchers.values()) watcher.close();
170
- watchers.clear();
171
- },
172
- };
173
- }
174
-
175
113
  export function buildPlugin(target, {
176
114
  cwd = process.cwd(),
177
115
  clean = false,