@alavida/agentpack 0.1.5 → 0.1.6
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/package.json +4 -1
- package/src/application/auth/get-auth-status.js +97 -0
- package/src/application/auth/login.js +110 -0
- package/src/application/auth/logout.js +16 -0
- package/src/application/auth/verify-auth.js +37 -0
- package/src/cli.js +2 -0
- package/src/commands/auth.js +78 -0
- package/src/commands/skills.js +27 -0
- package/src/domain/auth/registry-resolution.js +55 -0
- package/src/domain/skills/skill-model.js +58 -1
- package/src/infrastructure/fs/user-config-repository.js +40 -0
- package/src/infrastructure/fs/user-credentials-repository.js +30 -0
- package/src/infrastructure/fs/user-npmrc-repository.js +74 -0
- package/src/infrastructure/runtime/inspect-materialized-skills.js +153 -0
- package/src/infrastructure/runtime/materialize-skills.js +78 -31
- package/src/infrastructure/runtime/open-browser.js +6 -0
- package/src/lib/skills.js +199 -21
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
|
|
5
|
+
export function parseNpmrc(content) {
|
|
6
|
+
const config = {};
|
|
7
|
+
for (const rawLine of content.split('\n')) {
|
|
8
|
+
const line = rawLine.trim();
|
|
9
|
+
if (!line || line.startsWith('#') || line.startsWith(';')) continue;
|
|
10
|
+
const eqIndex = line.indexOf('=');
|
|
11
|
+
if (eqIndex === -1) continue;
|
|
12
|
+
const key = line.slice(0, eqIndex).trim();
|
|
13
|
+
const value = line.slice(eqIndex + 1).trim();
|
|
14
|
+
config[key] = value;
|
|
15
|
+
}
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getUserNpmrcPath({ env = process.env } = {}) {
|
|
20
|
+
return join(env.HOME || homedir(), '.npmrc');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function readUserNpmrc({ env = process.env } = {}) {
|
|
24
|
+
const npmrcPath = getUserNpmrcPath({ env });
|
|
25
|
+
if (!existsSync(npmrcPath)) return {};
|
|
26
|
+
return parseNpmrc(readFileSync(npmrcPath, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function upsertLine(lines, key, value) {
|
|
30
|
+
const prefix = `${key}=`;
|
|
31
|
+
const nextLine = `${key}=${value}`;
|
|
32
|
+
const index = lines.findIndex((line) => line.trim().startsWith(prefix));
|
|
33
|
+
if (index === -1) {
|
|
34
|
+
lines.push(nextLine);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
lines[index] = nextLine;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function removeLine(lines, key) {
|
|
41
|
+
const prefix = `${key}=`;
|
|
42
|
+
return lines.filter((line) => !line.trim().startsWith(prefix));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function writeManagedNpmrcEntries({
|
|
46
|
+
entries,
|
|
47
|
+
env = process.env,
|
|
48
|
+
} = {}) {
|
|
49
|
+
const npmrcPath = getUserNpmrcPath({ env });
|
|
50
|
+
const lines = existsSync(npmrcPath)
|
|
51
|
+
? readFileSync(npmrcPath, 'utf-8').split('\n').filter((line, index, all) => !(index === all.length - 1 && line === ''))
|
|
52
|
+
: [];
|
|
53
|
+
|
|
54
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
55
|
+
upsertLine(lines, key, value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
writeFileSync(npmrcPath, `${lines.join('\n')}\n`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function removeManagedNpmrcEntries({
|
|
62
|
+
keys,
|
|
63
|
+
env = process.env,
|
|
64
|
+
} = {}) {
|
|
65
|
+
const npmrcPath = getUserNpmrcPath({ env });
|
|
66
|
+
if (!existsSync(npmrcPath)) return;
|
|
67
|
+
|
|
68
|
+
let lines = readFileSync(npmrcPath, 'utf-8').split('\n').filter((line, index, all) => !(index === all.length - 1 && line === ''));
|
|
69
|
+
for (const key of keys) {
|
|
70
|
+
lines = removeLine(lines, key);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
writeFileSync(npmrcPath, `${lines.join('\n')}\n`);
|
|
74
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { lstatSync, readlinkSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { readDevSession } from '../fs/dev-session-repository.js';
|
|
4
|
+
|
|
5
|
+
function readPathType(pathValue) {
|
|
6
|
+
try {
|
|
7
|
+
const stat = lstatSync(pathValue);
|
|
8
|
+
return {
|
|
9
|
+
exists: true,
|
|
10
|
+
isSymlink: stat.isSymbolicLink(),
|
|
11
|
+
type: stat.isDirectory() ? 'directory' : stat.isFile() ? 'file' : 'other',
|
|
12
|
+
};
|
|
13
|
+
} catch {
|
|
14
|
+
return {
|
|
15
|
+
exists: false,
|
|
16
|
+
isSymlink: false,
|
|
17
|
+
type: null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function inspectRecordedMaterialization(repoRoot, {
|
|
23
|
+
target,
|
|
24
|
+
expectedSourcePath,
|
|
25
|
+
packageName,
|
|
26
|
+
runtimeName = null,
|
|
27
|
+
} = {}) {
|
|
28
|
+
const absTarget = resolve(repoRoot, target);
|
|
29
|
+
const expectedTarget = resolve(repoRoot, expectedSourcePath);
|
|
30
|
+
const pathState = readPathType(absTarget);
|
|
31
|
+
|
|
32
|
+
if (!pathState.exists) {
|
|
33
|
+
return {
|
|
34
|
+
packageName,
|
|
35
|
+
runtimeName,
|
|
36
|
+
target,
|
|
37
|
+
expectedSourcePath,
|
|
38
|
+
code: 'missing_path',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!pathState.isSymlink) {
|
|
43
|
+
return {
|
|
44
|
+
packageName,
|
|
45
|
+
runtimeName,
|
|
46
|
+
target,
|
|
47
|
+
expectedSourcePath,
|
|
48
|
+
code: 'wrong_type',
|
|
49
|
+
actualType: pathState.type,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const rawLinkTarget = readlinkSync(absTarget);
|
|
54
|
+
const actualTarget = resolve(join(absTarget, '..'), rawLinkTarget);
|
|
55
|
+
if (actualTarget !== expectedTarget) {
|
|
56
|
+
return {
|
|
57
|
+
packageName,
|
|
58
|
+
runtimeName,
|
|
59
|
+
target,
|
|
60
|
+
expectedSourcePath,
|
|
61
|
+
code: 'wrong_target',
|
|
62
|
+
actualTarget,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const resolvedState = readPathType(actualTarget);
|
|
67
|
+
if (!resolvedState.exists) {
|
|
68
|
+
return {
|
|
69
|
+
packageName,
|
|
70
|
+
runtimeName,
|
|
71
|
+
target,
|
|
72
|
+
expectedSourcePath,
|
|
73
|
+
code: 'dangling_target',
|
|
74
|
+
actualTarget,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function inspectMaterializedSkills(repoRoot, state) {
|
|
82
|
+
const runtimeDrift = [];
|
|
83
|
+
const ownedTargets = new Set();
|
|
84
|
+
const devSession = readDevSession(repoRoot);
|
|
85
|
+
|
|
86
|
+
if (devSession?.status === 'active') {
|
|
87
|
+
for (const target of devSession.links || []) {
|
|
88
|
+
ownedTargets.add(target);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const [packageName, install] of Object.entries(state.installs || {})) {
|
|
93
|
+
const issues = [];
|
|
94
|
+
|
|
95
|
+
for (const skill of install.skills || []) {
|
|
96
|
+
for (const materialization of skill.materializations || []) {
|
|
97
|
+
ownedTargets.add(materialization.target);
|
|
98
|
+
const issue = inspectRecordedMaterialization(repoRoot, {
|
|
99
|
+
target: materialization.target,
|
|
100
|
+
expectedSourcePath: skill.source_skill_path,
|
|
101
|
+
packageName,
|
|
102
|
+
runtimeName: skill.runtime_name,
|
|
103
|
+
});
|
|
104
|
+
if (issue) issues.push(issue);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (issues.length > 0) {
|
|
109
|
+
runtimeDrift.push({
|
|
110
|
+
packageName,
|
|
111
|
+
issues,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const orphanedMaterializations = [];
|
|
117
|
+
for (const root of [
|
|
118
|
+
join(repoRoot, '.claude', 'skills'),
|
|
119
|
+
join(repoRoot, '.agents', 'skills'),
|
|
120
|
+
]) {
|
|
121
|
+
let entries = [];
|
|
122
|
+
try {
|
|
123
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
124
|
+
} catch {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
const relativeTarget = root.startsWith(join(repoRoot, '.claude'))
|
|
130
|
+
? `.claude/skills/${entry.name}`
|
|
131
|
+
: `.agents/skills/${entry.name}`;
|
|
132
|
+
if (ownedTargets.has(relativeTarget)) continue;
|
|
133
|
+
|
|
134
|
+
const absPath = join(root, entry.name);
|
|
135
|
+
const pathState = readPathType(absPath);
|
|
136
|
+
orphanedMaterializations.push({
|
|
137
|
+
target: relativeTarget,
|
|
138
|
+
code: 'orphaned_materialization',
|
|
139
|
+
actualType: pathState.isSymlink ? 'symlink' : pathState.type,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
runtimeDrift.sort((a, b) => a.packageName.localeCompare(b.packageName));
|
|
145
|
+
orphanedMaterializations.sort((a, b) => a.target.localeCompare(b.target));
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
runtimeDriftCount: runtimeDrift.length,
|
|
149
|
+
runtimeDrift,
|
|
150
|
+
orphanedMaterializationCount: orphanedMaterializations.length,
|
|
151
|
+
orphanedMaterializations,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, rmSync, symlinkSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
3
3
|
import { writeInstallState } from '../fs/install-state-repository.js';
|
|
4
4
|
|
|
@@ -7,7 +7,17 @@ function ensureDir(pathValue) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function removePathIfExists(pathValue) {
|
|
10
|
-
|
|
10
|
+
try {
|
|
11
|
+
const stat = lstatSync(pathValue);
|
|
12
|
+
if (stat.isSymbolicLink() || stat.isFile()) {
|
|
13
|
+
unlinkSync(pathValue);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
rmSync(pathValue, { recursive: true, force: true });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error?.code === 'ENOENT') return;
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
export function ensureSkillLink(repoRoot, baseDir, skillName, skillDir, normalizeDisplayPath) {
|
|
@@ -25,9 +35,13 @@ export function removeSkillLinks(repoRoot, name, normalizeDisplayPath) {
|
|
|
25
35
|
join(repoRoot, '.claude', 'skills', name),
|
|
26
36
|
join(repoRoot, '.agents', 'skills', name),
|
|
27
37
|
]) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
try {
|
|
39
|
+
removePathIfExists(pathValue);
|
|
40
|
+
removed.push(normalizeDisplayPath(repoRoot, pathValue));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error?.code === 'ENOENT') continue;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
31
45
|
}
|
|
32
46
|
return removed;
|
|
33
47
|
}
|
|
@@ -50,9 +64,13 @@ export function removeSkillLinksByPaths(repoRoot, paths, normalizeDisplayPath) {
|
|
|
50
64
|
const pathValue = resolve(repoRoot, relativePath);
|
|
51
65
|
const inAllowedRoot = allowedRoots.some((root) => pathValue === root || pathValue.startsWith(`${root}/`));
|
|
52
66
|
if (!inAllowedRoot) continue;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
67
|
+
try {
|
|
68
|
+
removePathIfExists(pathValue);
|
|
69
|
+
removed.push(normalizeDisplayPath(repoRoot, pathValue));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error?.code === 'ENOENT') continue;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
56
74
|
}
|
|
57
75
|
return [...new Set(removed)];
|
|
58
76
|
}
|
|
@@ -63,31 +81,62 @@ function ensureSymlink(targetPath, linkPath) {
|
|
|
63
81
|
symlinkSync(targetPath, linkPath, 'dir');
|
|
64
82
|
}
|
|
65
83
|
|
|
84
|
+
function inferPackageRuntimeNamespace(packageName) {
|
|
85
|
+
return packageName?.split('/').pop() || null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildRuntimeName(packageName, exportedSkills, entry) {
|
|
89
|
+
if (exportedSkills.length <= 1) return entry.name;
|
|
90
|
+
|
|
91
|
+
const namespace = inferPackageRuntimeNamespace(packageName);
|
|
92
|
+
if (!namespace) return entry.name;
|
|
93
|
+
if (entry.name === namespace) return namespace;
|
|
94
|
+
return `${namespace}:${entry.name}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
66
97
|
export function buildInstallRecord(repoRoot, packageDir, directTargetMap, {
|
|
67
|
-
parseSkillFrontmatterFile,
|
|
68
98
|
readPackageMetadata,
|
|
99
|
+
readInstalledSkillExports,
|
|
69
100
|
normalizeRelativePath,
|
|
70
101
|
} = {}) {
|
|
71
102
|
const packageMetadata = readPackageMetadata(packageDir);
|
|
72
103
|
if (!packageMetadata.packageName) return null;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const skillDirName = skillMetadata.name;
|
|
104
|
+
const exportedSkills = readInstalledSkillExports(packageDir);
|
|
105
|
+
if (exportedSkills.length === 0) return null;
|
|
76
106
|
const materializations = [];
|
|
107
|
+
const skills = [];
|
|
77
108
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
target: normalizeRelativePath(relative(repoRoot, claudeTargetAbs)),
|
|
82
|
-
mode: 'symlink',
|
|
83
|
-
});
|
|
109
|
+
for (const entry of exportedSkills) {
|
|
110
|
+
const runtimeName = buildRuntimeName(packageMetadata.packageName, exportedSkills, entry);
|
|
111
|
+
const skillMaterializations = [];
|
|
84
112
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
const claudeTargetAbs = join(repoRoot, '.claude', 'skills', runtimeName);
|
|
114
|
+
ensureSymlink(entry.skillDir, claudeTargetAbs);
|
|
115
|
+
skillMaterializations.push({
|
|
116
|
+
target: normalizeRelativePath(relative(repoRoot, claudeTargetAbs)),
|
|
117
|
+
mode: 'symlink',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const agentsTargetAbs = join(repoRoot, '.agents', 'skills', runtimeName);
|
|
121
|
+
ensureSymlink(entry.skillDir, agentsTargetAbs);
|
|
122
|
+
skillMaterializations.push({
|
|
123
|
+
target: normalizeRelativePath(relative(repoRoot, agentsTargetAbs)),
|
|
124
|
+
mode: 'symlink',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
materializations.push(...skillMaterializations);
|
|
128
|
+
skills.push({
|
|
129
|
+
name: entry.name,
|
|
130
|
+
runtime_name: runtimeName,
|
|
131
|
+
source_skill_path: normalizeRelativePath(relative(repoRoot, entry.skillDir)),
|
|
132
|
+
source_skill_file: normalizeRelativePath(relative(repoRoot, entry.skillFile)),
|
|
133
|
+
requires: entry.requires,
|
|
134
|
+
status: entry.status,
|
|
135
|
+
replacement: entry.replacement,
|
|
136
|
+
message: entry.message,
|
|
137
|
+
materializations: skillMaterializations,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
91
140
|
|
|
92
141
|
return {
|
|
93
142
|
packageName: packageMetadata.packageName,
|
|
@@ -95,26 +144,23 @@ export function buildInstallRecord(repoRoot, packageDir, directTargetMap, {
|
|
|
95
144
|
requestedTarget: directTargetMap.get(packageMetadata.packageName) || null,
|
|
96
145
|
packageVersion: packageMetadata.packageVersion,
|
|
97
146
|
sourcePackagePath: normalizeRelativePath(relative(repoRoot, packageDir)),
|
|
147
|
+
skills,
|
|
98
148
|
materializations,
|
|
99
149
|
};
|
|
100
150
|
}
|
|
101
151
|
|
|
102
152
|
export function rebuildInstallState(repoRoot, directTargetMap, {
|
|
103
|
-
|
|
104
|
-
parseSkillFrontmatterFile,
|
|
153
|
+
packageDirs = [],
|
|
105
154
|
readPackageMetadata,
|
|
155
|
+
readInstalledSkillExports,
|
|
106
156
|
normalizeRelativePath,
|
|
107
157
|
} = {}) {
|
|
108
|
-
const packageDirs = listInstalledPackageDirs(join(repoRoot, 'node_modules'));
|
|
109
158
|
const installs = {};
|
|
110
159
|
|
|
111
160
|
for (const packageDir of packageDirs) {
|
|
112
|
-
const skillFile = join(packageDir, 'SKILL.md');
|
|
113
|
-
if (!existsSync(skillFile)) continue;
|
|
114
|
-
|
|
115
161
|
const record = buildInstallRecord(repoRoot, packageDir, directTargetMap, {
|
|
116
|
-
parseSkillFrontmatterFile,
|
|
117
162
|
readPackageMetadata,
|
|
163
|
+
readInstalledSkillExports,
|
|
118
164
|
normalizeRelativePath,
|
|
119
165
|
});
|
|
120
166
|
if (!record) continue;
|
|
@@ -124,6 +170,7 @@ export function rebuildInstallState(repoRoot, directTargetMap, {
|
|
|
124
170
|
requested_target: record.requestedTarget,
|
|
125
171
|
package_version: record.packageVersion,
|
|
126
172
|
source_package_path: record.sourcePackagePath,
|
|
173
|
+
skills: record.skills,
|
|
127
174
|
materializations: record.materializations,
|
|
128
175
|
};
|
|
129
176
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
1
2
|
import { spawn } from 'node:child_process';
|
|
2
3
|
|
|
3
4
|
export function openBrowser(url) {
|
|
5
|
+
if (process.env.AGENTPACK_BROWSER_CAPTURE_PATH) {
|
|
6
|
+
writeFileSync(process.env.AGENTPACK_BROWSER_CAPTURE_PATH, `${url}\n`);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
if (process.env.AGENTPACK_DISABLE_BROWSER === '1') return;
|
|
5
11
|
|
|
6
12
|
const command = process.platform === 'darwin'
|