@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
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alavida/agentpack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Package-backed skills lifecycle CLI for agent skills and plugins",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"packages/*"
|
|
8
|
+
],
|
|
6
9
|
"bin": {
|
|
7
10
|
"agentpack": "bin/agentpack.js",
|
|
8
11
|
"intent": "bin/intent.js"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readUserConfig } from '../../infrastructure/fs/user-config-repository.js';
|
|
4
|
+
import { readUserCredentials } from '../../infrastructure/fs/user-credentials-repository.js';
|
|
5
|
+
import { getUserNpmrcPath, parseNpmrc, readUserNpmrc } from '../../infrastructure/fs/user-npmrc-repository.js';
|
|
6
|
+
import { resolveRegistryConfig } from '../../domain/auth/registry-resolution.js';
|
|
7
|
+
import { verifyAuth } from './verify-auth.js';
|
|
8
|
+
|
|
9
|
+
function findRepoNpmrc(cwd) {
|
|
10
|
+
let current = cwd;
|
|
11
|
+
|
|
12
|
+
while (true) {
|
|
13
|
+
const npmrcPath = join(current, '.npmrc');
|
|
14
|
+
if (existsSync(npmrcPath)) {
|
|
15
|
+
return {
|
|
16
|
+
path: npmrcPath,
|
|
17
|
+
config: parseNpmrc(readFileSync(npmrcPath, 'utf-8')),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const gitPath = join(current, '.git');
|
|
22
|
+
if (existsSync(gitPath)) break;
|
|
23
|
+
|
|
24
|
+
const parent = join(current, '..');
|
|
25
|
+
if (parent === current) break;
|
|
26
|
+
current = parent;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
path: null,
|
|
31
|
+
config: {},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function getAuthStatus({
|
|
36
|
+
cwd = process.cwd(),
|
|
37
|
+
env = process.env,
|
|
38
|
+
verify = false,
|
|
39
|
+
} = {}) {
|
|
40
|
+
const config = readUserConfig({ env });
|
|
41
|
+
const credentials = readUserCredentials({ env });
|
|
42
|
+
const userNpmrc = readUserNpmrc({ env });
|
|
43
|
+
const repoNpmrc = findRepoNpmrc(cwd);
|
|
44
|
+
|
|
45
|
+
const resolved = resolveRegistryConfig({
|
|
46
|
+
scope: config.scope,
|
|
47
|
+
defaults: {
|
|
48
|
+
registry: config.registry,
|
|
49
|
+
verificationPackage: config.verificationPackage,
|
|
50
|
+
},
|
|
51
|
+
userNpmrc,
|
|
52
|
+
repoNpmrc: repoNpmrc.config,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const userNpmrcPath = getUserNpmrcPath({ env });
|
|
56
|
+
const requiredRegistryKey = `${config.scope}:registry`;
|
|
57
|
+
const requiredTokenKey = resolved.registry
|
|
58
|
+
? `//${new URL(resolved.registry).host}/:_authToken`
|
|
59
|
+
: null;
|
|
60
|
+
|
|
61
|
+
const npmWired = Boolean(
|
|
62
|
+
userNpmrc[requiredRegistryKey]
|
|
63
|
+
&& requiredTokenKey
|
|
64
|
+
&& userNpmrc[requiredTokenKey]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const result = {
|
|
68
|
+
provider: config.provider,
|
|
69
|
+
configured: Boolean(credentials?.token && npmWired),
|
|
70
|
+
scope: config.scope,
|
|
71
|
+
registry: resolved.registry,
|
|
72
|
+
storage: {
|
|
73
|
+
mode: credentials?.token ? 'file' : 'missing',
|
|
74
|
+
},
|
|
75
|
+
npmConfig: {
|
|
76
|
+
path: userNpmrcPath,
|
|
77
|
+
wired: npmWired,
|
|
78
|
+
source: resolved.source,
|
|
79
|
+
repoOverridePath: repoNpmrc.path,
|
|
80
|
+
},
|
|
81
|
+
verification: {
|
|
82
|
+
status: 'not_checked',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (!verify) {
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
result.verification = await verifyAuth({
|
|
91
|
+
registry: resolved.registry,
|
|
92
|
+
authToken: credentials?.token || null,
|
|
93
|
+
verificationPackage: resolved.verificationPackage,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import readline from 'node:readline/promises';
|
|
2
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
3
|
+
import { readUserConfig, writeUserConfig } from '../../infrastructure/fs/user-config-repository.js';
|
|
4
|
+
import { writeUserCredentials } from '../../infrastructure/fs/user-credentials-repository.js';
|
|
5
|
+
import { writeManagedNpmrcEntries } from '../../infrastructure/fs/user-npmrc-repository.js';
|
|
6
|
+
import { openBrowser } from '../../infrastructure/runtime/open-browser.js';
|
|
7
|
+
import { verifyAuth } from './verify-auth.js';
|
|
8
|
+
import { AgentpackError, EXIT_CODES } from '../../utils/errors.js';
|
|
9
|
+
|
|
10
|
+
const GITHUB_TOKEN_URL = 'https://github.com/settings/tokens';
|
|
11
|
+
|
|
12
|
+
function buildVerificationFailure(verification) {
|
|
13
|
+
if (verification.status === 'invalid') {
|
|
14
|
+
return new AgentpackError('The GitHub personal access token was rejected by GitHub Packages', {
|
|
15
|
+
code: 'auth_verification_failed',
|
|
16
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (verification.status === 'insufficient_permissions') {
|
|
21
|
+
return new AgentpackError('The GitHub personal access token does not have package read access', {
|
|
22
|
+
code: 'auth_verification_failed',
|
|
23
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (verification.status === 'unreachable') {
|
|
28
|
+
return new AgentpackError('GitHub Packages could not be reached during verification', {
|
|
29
|
+
code: 'auth_verification_failed',
|
|
30
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (verification.status === 'not_configured') {
|
|
35
|
+
return new AgentpackError('Authentication verification is not configured for this machine', {
|
|
36
|
+
code: 'auth_verification_not_configured',
|
|
37
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return new AgentpackError('The saved credential was rejected by the configured registry', {
|
|
42
|
+
code: 'auth_verification_failed',
|
|
43
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function login({
|
|
48
|
+
env = process.env,
|
|
49
|
+
scope = null,
|
|
50
|
+
registry = null,
|
|
51
|
+
verificationPackage = null,
|
|
52
|
+
} = {}) {
|
|
53
|
+
const current = readUserConfig({ env });
|
|
54
|
+
const nextConfig = {
|
|
55
|
+
...current,
|
|
56
|
+
scope: scope || current.scope,
|
|
57
|
+
registry: registry || current.registry,
|
|
58
|
+
verificationPackage: verificationPackage || current.verificationPackage,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
openBrowser(GITHUB_TOKEN_URL);
|
|
62
|
+
output.write(`Configuring GitHub Packages auth for ${nextConfig.scope}`);
|
|
63
|
+
output.write('Use a GitHub personal access token with package read access.');
|
|
64
|
+
|
|
65
|
+
const rl = readline.createInterface({ input, output });
|
|
66
|
+
try {
|
|
67
|
+
const token = (await rl.question('Token: ')).trim();
|
|
68
|
+
if (!token) {
|
|
69
|
+
throw new AgentpackError('A GitHub credential is required to continue', {
|
|
70
|
+
code: 'auth_token_missing',
|
|
71
|
+
exitCode: EXIT_CODES.GENERAL,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const verification = await verifyAuth({
|
|
76
|
+
registry: nextConfig.registry,
|
|
77
|
+
authToken: token,
|
|
78
|
+
verificationPackage: nextConfig.verificationPackage,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (verification.status !== 'valid') {
|
|
82
|
+
throw buildVerificationFailure(verification);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const managedEntries = {
|
|
86
|
+
[`${nextConfig.scope}:registry`]: nextConfig.registry,
|
|
87
|
+
[`//${new URL(nextConfig.registry).host}/:_authToken`]: token,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
writeManagedNpmrcEntries({ entries: managedEntries, env });
|
|
91
|
+
writeUserCredentials({ token }, { env });
|
|
92
|
+
writeUserConfig({
|
|
93
|
+
...nextConfig,
|
|
94
|
+
managedNpmKeys: Object.keys(managedEntries),
|
|
95
|
+
}, { env });
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
configured: true,
|
|
99
|
+
provider: nextConfig.provider,
|
|
100
|
+
scope: nextConfig.scope,
|
|
101
|
+
registry: nextConfig.registry,
|
|
102
|
+
verificationPackage: nextConfig.verificationPackage,
|
|
103
|
+
storage: {
|
|
104
|
+
mode: 'file',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
} finally {
|
|
108
|
+
rl.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { deleteUserCredentials } from '../../infrastructure/fs/user-credentials-repository.js';
|
|
2
|
+
import { removeManagedNpmrcEntries } from '../../infrastructure/fs/user-npmrc-repository.js';
|
|
3
|
+
import { readUserConfig } from '../../infrastructure/fs/user-config-repository.js';
|
|
4
|
+
|
|
5
|
+
export function logout({ env = process.env } = {}) {
|
|
6
|
+
const config = readUserConfig({ env });
|
|
7
|
+
const keys = config.managedNpmKeys || [];
|
|
8
|
+
|
|
9
|
+
deleteUserCredentials({ env });
|
|
10
|
+
removeManagedNpmrcEntries({ keys, env });
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
removedCredentials: true,
|
|
14
|
+
removedNpmKeys: keys.length,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export async function verifyAuth({
|
|
2
|
+
registry,
|
|
3
|
+
authToken,
|
|
4
|
+
verificationPackage,
|
|
5
|
+
} = {}) {
|
|
6
|
+
if (!registry || !authToken || !verificationPackage) {
|
|
7
|
+
return { status: 'not_configured' };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const url = `${registry.replace(/\/+$/, '')}/${encodeURIComponent(verificationPackage)}`;
|
|
11
|
+
|
|
12
|
+
let response;
|
|
13
|
+
try {
|
|
14
|
+
response = await fetch(url, {
|
|
15
|
+
headers: {
|
|
16
|
+
accept: 'application/json',
|
|
17
|
+
authorization: `Bearer ${authToken}`,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
} catch {
|
|
21
|
+
return { status: 'unreachable' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (response.ok) {
|
|
25
|
+
return { status: 'valid' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (response.status === 401) {
|
|
29
|
+
return { status: 'invalid' };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (response.status === 403) {
|
|
33
|
+
return { status: 'insufficient_permissions' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { status: 'unreachable' };
|
|
37
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { formatError, AgentpackError, EXIT_CODES } from './utils/errors.js';
|
|
4
4
|
import { output } from './utils/output.js';
|
|
5
|
+
import { authCommand } from './commands/auth.js';
|
|
5
6
|
import { skillsCommand } from './commands/skills.js';
|
|
6
7
|
import { pluginCommand } from './commands/plugin.js';
|
|
7
8
|
|
|
@@ -21,6 +22,7 @@ export function createProgram() {
|
|
|
21
22
|
.option('--workbench <path>', 'Override workbench context (name or path)');
|
|
22
23
|
|
|
23
24
|
program.addCommand(skillsCommand());
|
|
25
|
+
program.addCommand(authCommand());
|
|
24
26
|
program.addCommand(pluginCommand());
|
|
25
27
|
|
|
26
28
|
program.addHelpText('after', `
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { login } from '../application/auth/login.js';
|
|
3
|
+
import { logout } from '../application/auth/logout.js';
|
|
4
|
+
import { output } from '../utils/output.js';
|
|
5
|
+
import { getAuthStatus } from '../application/auth/get-auth-status.js';
|
|
6
|
+
|
|
7
|
+
export function authCommand() {
|
|
8
|
+
const cmd = new Command('auth')
|
|
9
|
+
.description('Configure and inspect package registry authentication');
|
|
10
|
+
|
|
11
|
+
cmd.addHelpText('after', `
|
|
12
|
+
Defaults:
|
|
13
|
+
Scope: @alavida-ai
|
|
14
|
+
Registry: https://npm.pkg.github.com
|
|
15
|
+
Token: GitHub personal access token with package read access
|
|
16
|
+
`);
|
|
17
|
+
|
|
18
|
+
cmd
|
|
19
|
+
.command('login')
|
|
20
|
+
.description('Configure GitHub Packages authentication for this machine')
|
|
21
|
+
.option('--scope <scope>', 'Override the package scope to configure')
|
|
22
|
+
.option('--registry <url>', 'Override the package registry URL')
|
|
23
|
+
.option('--verify-package <packageName>', 'Override the package used for live verification')
|
|
24
|
+
.action(async (opts, command) => {
|
|
25
|
+
const globalOpts = command.optsWithGlobals();
|
|
26
|
+
const result = await login({
|
|
27
|
+
scope: opts.scope || null,
|
|
28
|
+
registry: opts.registry || null,
|
|
29
|
+
verificationPackage: opts.verifyPackage || null,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (globalOpts.json) {
|
|
33
|
+
output.json(result);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
output.write(`Configured auth for ${result.scope}`);
|
|
38
|
+
output.write(`Registry: ${result.registry}`);
|
|
39
|
+
output.write(`Storage: ${result.storage.mode}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
cmd
|
|
43
|
+
.command('status')
|
|
44
|
+
.description('Show authentication status for the configured package registry')
|
|
45
|
+
.option('--verify', 'Check whether the stored credential works against the configured registry')
|
|
46
|
+
.action(async (opts, command) => {
|
|
47
|
+
const globalOpts = command.optsWithGlobals();
|
|
48
|
+
const result = await getAuthStatus({ verify: opts.verify });
|
|
49
|
+
|
|
50
|
+
if (globalOpts.json) {
|
|
51
|
+
output.json(result);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
output.write(`Provider: ${result.provider}`);
|
|
56
|
+
output.write(`Configured: ${result.configured}`);
|
|
57
|
+
output.write(`Storage: ${result.storage.mode}`);
|
|
58
|
+
output.write(`Verification: ${result.verification.status}`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
cmd
|
|
62
|
+
.command('logout')
|
|
63
|
+
.description('Remove configured package registry authentication')
|
|
64
|
+
.action((opts, command) => {
|
|
65
|
+
const globalOpts = command.optsWithGlobals();
|
|
66
|
+
const result = logout();
|
|
67
|
+
|
|
68
|
+
if (globalOpts.json) {
|
|
69
|
+
output.json(result);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
output.write(`Removed Credentials: ${result.removedCredentials}`);
|
|
74
|
+
output.write(`Removed npm Keys: ${result.removedNpmKeys}`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return cmd;
|
|
78
|
+
}
|
package/src/commands/skills.js
CHANGED
|
@@ -136,6 +136,8 @@ export function skillsCommand() {
|
|
|
136
136
|
output.write(`Outdated Skills: ${result.outdatedCount}`);
|
|
137
137
|
output.write(`Deprecated Skills: ${result.deprecatedCount}`);
|
|
138
138
|
output.write(`Incomplete Skills: ${result.incompleteCount}`);
|
|
139
|
+
output.write(`Runtime Drifted Skills: ${result.runtimeDriftCount}`);
|
|
140
|
+
output.write(`Orphaned Materializations: ${result.orphanedMaterializationCount}`);
|
|
139
141
|
output.write(`Registry Configured: ${result.registry.configured}`);
|
|
140
142
|
|
|
141
143
|
if (result.outdated.length > 0) {
|
|
@@ -170,6 +172,28 @@ export function skillsCommand() {
|
|
|
170
172
|
}
|
|
171
173
|
}
|
|
172
174
|
}
|
|
175
|
+
|
|
176
|
+
if (result.runtimeDrift.length > 0) {
|
|
177
|
+
output.write('');
|
|
178
|
+
output.write('Runtime Drift:');
|
|
179
|
+
for (const install of result.runtimeDrift) {
|
|
180
|
+
output.write(`- ${install.packageName}`);
|
|
181
|
+
for (const issue of install.issues) {
|
|
182
|
+
output.write(` issue: ${issue.code}`);
|
|
183
|
+
output.write(` target: ${issue.target}`);
|
|
184
|
+
if (issue.runtimeName) output.write(` runtime: ${issue.runtimeName}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (result.orphanedMaterializations.length > 0) {
|
|
190
|
+
output.write('');
|
|
191
|
+
output.write('Orphaned Materializations:');
|
|
192
|
+
for (const entry of result.orphanedMaterializations) {
|
|
193
|
+
output.write(`- ${entry.target}`);
|
|
194
|
+
output.write(` issue: ${entry.code}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
173
197
|
});
|
|
174
198
|
|
|
175
199
|
cmd
|
|
@@ -459,6 +483,9 @@ export function skillsCommand() {
|
|
|
459
483
|
output.write(` direct: ${install.direct}`);
|
|
460
484
|
output.write(` version: ${install.packageVersion}`);
|
|
461
485
|
output.write(` source: ${install.sourcePackagePath}`);
|
|
486
|
+
if (install.skills?.length > 0) {
|
|
487
|
+
output.write(` skills: ${install.skills.map((skill) => skill.name).join(', ')}`);
|
|
488
|
+
}
|
|
462
489
|
for (const materialization of install.materializations) {
|
|
463
490
|
output.write(` materialized: ${materialization.target} (${materialization.mode})`);
|
|
464
491
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
function getScopedRegistry(config, scope) {
|
|
2
|
+
return config?.[`${scope}:registry`] || null;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function getRegistryHostKey(registry) {
|
|
6
|
+
if (!registry) return null;
|
|
7
|
+
try {
|
|
8
|
+
const url = new URL(registry);
|
|
9
|
+
return `//${url.host}/:_authToken`;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getAuthToken(config, registry) {
|
|
16
|
+
const hostKey = getRegistryHostKey(registry);
|
|
17
|
+
return hostKey ? (config?.[hostKey] || null) : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveRegistryConfig({
|
|
21
|
+
scope,
|
|
22
|
+
defaults = {},
|
|
23
|
+
userNpmrc = {},
|
|
24
|
+
repoNpmrc = {},
|
|
25
|
+
} = {}) {
|
|
26
|
+
const repoRegistry = getScopedRegistry(repoNpmrc, scope);
|
|
27
|
+
if (repoRegistry) {
|
|
28
|
+
return {
|
|
29
|
+
scope,
|
|
30
|
+
registry: repoRegistry,
|
|
31
|
+
authToken: getAuthToken(repoNpmrc, repoRegistry),
|
|
32
|
+
verificationPackage: defaults.verificationPackage || null,
|
|
33
|
+
source: 'repo',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const userRegistry = getScopedRegistry(userNpmrc, scope);
|
|
38
|
+
if (userRegistry) {
|
|
39
|
+
return {
|
|
40
|
+
scope,
|
|
41
|
+
registry: userRegistry,
|
|
42
|
+
authToken: getAuthToken(userNpmrc, userRegistry),
|
|
43
|
+
verificationPackage: defaults.verificationPackage || null,
|
|
44
|
+
source: 'user',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
scope,
|
|
50
|
+
registry: defaults.registry || null,
|
|
51
|
+
authToken: null,
|
|
52
|
+
verificationPackage: defaults.verificationPackage || null,
|
|
53
|
+
source: 'default',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { join, relative } from 'node:path';
|
|
2
|
+
import { dirname, join, relative } from 'node:path';
|
|
3
3
|
import { NotFoundError, ValidationError } from '../../utils/errors.js';
|
|
4
4
|
|
|
5
5
|
function parseScalar(value) {
|
|
@@ -171,6 +171,7 @@ export function readPackageMetadata(packageDir) {
|
|
|
171
171
|
files: null,
|
|
172
172
|
repository: null,
|
|
173
173
|
publishConfigRegistry: null,
|
|
174
|
+
exportedSkills: null,
|
|
174
175
|
};
|
|
175
176
|
}
|
|
176
177
|
|
|
@@ -183,5 +184,61 @@ export function readPackageMetadata(packageDir) {
|
|
|
183
184
|
files: Array.isArray(pkg.files) ? pkg.files : null,
|
|
184
185
|
repository: pkg.repository || null,
|
|
185
186
|
publishConfigRegistry: pkg.publishConfig?.registry || null,
|
|
187
|
+
exportedSkills: pkg.agentpack?.skills || null,
|
|
186
188
|
};
|
|
187
189
|
}
|
|
190
|
+
|
|
191
|
+
export function buildCanonicalSkillRequirement(packageName, skillName) {
|
|
192
|
+
if (!packageName || !skillName) return null;
|
|
193
|
+
return `${packageName}:${skillName}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function readInstalledSkillExports(packageDir) {
|
|
197
|
+
const packageMetadata = readPackageMetadata(packageDir);
|
|
198
|
+
const exports = [];
|
|
199
|
+
|
|
200
|
+
if (packageMetadata.exportedSkills && typeof packageMetadata.exportedSkills === 'object') {
|
|
201
|
+
for (const [declaredName, entry] of Object.entries(packageMetadata.exportedSkills)) {
|
|
202
|
+
const relativeSkillFile = typeof entry === 'string' ? entry : entry?.path;
|
|
203
|
+
if (!relativeSkillFile) continue;
|
|
204
|
+
|
|
205
|
+
const skillFile = join(packageDir, relativeSkillFile);
|
|
206
|
+
if (!existsSync(skillFile)) continue;
|
|
207
|
+
|
|
208
|
+
const metadata = parseSkillFrontmatterFile(skillFile);
|
|
209
|
+
exports.push({
|
|
210
|
+
declaredName,
|
|
211
|
+
name: metadata.name,
|
|
212
|
+
description: metadata.description,
|
|
213
|
+
requires: metadata.requires,
|
|
214
|
+
status: metadata.status,
|
|
215
|
+
replacement: metadata.replacement,
|
|
216
|
+
message: metadata.message,
|
|
217
|
+
skillDir: dirname(skillFile),
|
|
218
|
+
skillFile,
|
|
219
|
+
relativeSkillFile,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (exports.length > 0) {
|
|
225
|
+
return exports.sort((a, b) => a.name.localeCompare(b.name));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const rootSkillFile = join(packageDir, 'SKILL.md');
|
|
229
|
+
if (!existsSync(rootSkillFile)) return [];
|
|
230
|
+
|
|
231
|
+
const metadata = parseSkillFrontmatterFile(rootSkillFile);
|
|
232
|
+
return [{
|
|
233
|
+
declaredName: metadata.name,
|
|
234
|
+
name: metadata.name,
|
|
235
|
+
description: metadata.description,
|
|
236
|
+
requires: metadata.requires,
|
|
237
|
+
status: metadata.status,
|
|
238
|
+
replacement: metadata.replacement,
|
|
239
|
+
message: metadata.message,
|
|
240
|
+
skillDir: packageDir,
|
|
241
|
+
skillFile: rootSkillFile,
|
|
242
|
+
relativeSkillFile: 'SKILL.md',
|
|
243
|
+
}];
|
|
244
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CONFIG = {
|
|
6
|
+
version: 1,
|
|
7
|
+
provider: 'github-packages',
|
|
8
|
+
scope: '@alavida-ai',
|
|
9
|
+
registry: 'https://npm.pkg.github.com',
|
|
10
|
+
verificationPackage: '@alavida-ai/agentpack-auth-probe',
|
|
11
|
+
managedNpmKeys: [],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function resolveConfigDir(env = process.env) {
|
|
15
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME;
|
|
16
|
+
if (xdgConfigHome) return join(xdgConfigHome, 'agentpack');
|
|
17
|
+
return join(env.HOME || homedir(), '.config', 'agentpack');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getUserConfigPath({ env = process.env } = {}) {
|
|
21
|
+
return join(resolveConfigDir(env), 'config.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readUserConfig({ env = process.env } = {}) {
|
|
25
|
+
const configPath = getUserConfigPath({ env });
|
|
26
|
+
if (!existsSync(configPath)) {
|
|
27
|
+
return { ...DEFAULT_CONFIG };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
...DEFAULT_CONFIG,
|
|
32
|
+
...JSON.parse(readFileSync(configPath, 'utf-8')),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function writeUserConfig(config, { env = process.env } = {}) {
|
|
37
|
+
const configPath = getUserConfigPath({ env });
|
|
38
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
39
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
40
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
|
|
5
|
+
function resolveConfigDir(env = process.env) {
|
|
6
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME;
|
|
7
|
+
if (xdgConfigHome) return join(xdgConfigHome, 'agentpack');
|
|
8
|
+
return join(env.HOME || homedir(), '.config', 'agentpack');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getUserCredentialsPath({ env = process.env } = {}) {
|
|
12
|
+
return join(resolveConfigDir(env), 'credentials.json');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function readUserCredentials({ env = process.env } = {}) {
|
|
16
|
+
const credentialsPath = getUserCredentialsPath({ env });
|
|
17
|
+
if (!existsSync(credentialsPath)) return null;
|
|
18
|
+
return JSON.parse(readFileSync(credentialsPath, 'utf-8'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function writeUserCredentials(credentials, { env = process.env } = {}) {
|
|
22
|
+
const credentialsPath = getUserCredentialsPath({ env });
|
|
23
|
+
mkdirSync(dirname(credentialsPath), { recursive: true });
|
|
24
|
+
writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2) + '\n', { mode: 0o600 });
|
|
25
|
+
chmodSync(credentialsPath, 0o600);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function deleteUserCredentials({ env = process.env } = {}) {
|
|
29
|
+
rmSync(getUserCredentialsPath({ env }), { force: true });
|
|
30
|
+
}
|