@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 CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@alavida/agentpack",
3
- "version": "0.1.5",
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
+ }
@@ -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
+ }