@tc9011/skills-manager 0.1.0

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,54 @@
1
+ // src/commands/push.ts
2
+ import { AGENTS_DIR } from '../agents.js';
3
+ import { ensureGitHubToken } from '../auth.js';
4
+ import { CliError } from '../errors.js';
5
+ import { pushSkills, getRepoRemoteUrl, ensureGitRepo, ensureRemote, createGitHubRepo, } from '../git-ops.js';
6
+ import * as p from '@clack/prompts';
7
+ export async function pushCommand(options) {
8
+ p.intro('skills-manager push');
9
+ // 1. Check auth
10
+ const token = await ensureGitHubToken();
11
+ // 2. Ensure ~/.agents is a git repo (auto-init if needed)
12
+ await ensureGitRepo(AGENTS_DIR);
13
+ // 3. Check for existing remote
14
+ const remote = await getRepoRemoteUrl(AGENTS_DIR);
15
+ // 4. If no remote, prompt user for repo and configure
16
+ if (!remote) {
17
+ const repo = await p.text({
18
+ message: 'Enter GitHub repo (owner/name):',
19
+ placeholder: 'e.g. tc9011/my-skills',
20
+ });
21
+ if (p.isCancel(repo)) {
22
+ p.cancel('Push cancelled.');
23
+ throw new CliError('Push cancelled by user.');
24
+ }
25
+ await ensureRemote(AGENTS_DIR, repo);
26
+ const shouldCreate = await p.confirm({
27
+ message: 'Create this repo on GitHub? (requires gh CLI)',
28
+ });
29
+ if (shouldCreate === true) {
30
+ createGitHubRepo(repo);
31
+ }
32
+ }
33
+ // 5. Push
34
+ const spinner = p.spinner();
35
+ spinner.start('Pushing skills to GitHub...');
36
+ try {
37
+ const result = await pushSkills(AGENTS_DIR, options.message, token);
38
+ spinner.stop(result.committed
39
+ ? 'Skills pushed successfully!'
40
+ : 'No changes to push — already up to date.');
41
+ // Warn about suspicious files that may contain secrets
42
+ if (result.suspiciousFiles?.length) {
43
+ p.note(`Suspicious files pushed: ${result.suspiciousFiles.join(', ')}
44
+ Consider adding a .gitignore to ${AGENTS_DIR}.`, '⚠ Warning');
45
+ }
46
+ }
47
+ catch (err) {
48
+ spinner.stop('Push failed.');
49
+ p.cancel(String(err));
50
+ throw new CliError(String(err));
51
+ }
52
+ p.outro('Done!');
53
+ }
54
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAA6B;IAC7D,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAE/B,gBAAgB;IAChB,MAAM,KAAK,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAExC,0DAA0D;IAC1D,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;IAEhC,+BAA+B;IAC/B,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAElD,sDAAsD;IACtD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC;YACxB,OAAO,EAAE,iCAAiC;YAC1C,WAAW,EAAE,uBAAuB;SACrC,CAAC,CAAC;QAEH,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;YAC5B,MAAM,IAAI,QAAQ,CAAC,yBAAyB,CAAC,CAAC;QAChD,CAAC;QAED,MAAM,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAErC,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC;YACnC,OAAO,EAAE,+CAA+C;SACzD,CAAC,CAAC;QAEH,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;YAC1B,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,UAAU;IACV,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;IAC5B,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAE7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CACV,MAAM,CAAC,SAAS;YACd,CAAC,CAAC,6BAA6B;YAC/B,CAAC,CAAC,0CAA0C,CAC/C,CAAC;QAEF,uDAAuD;QACvD,IAAI,MAAM,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;YACnC,CAAC,CAAC,IAAI,CACJ,4BAA4B,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;kCACnC,UAAU,GAAG,EACvC,WAAW,CACZ,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC7B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACtB,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,18 @@
1
+ export interface SkillsManagerConfig {
2
+ lastLinkedAgents?: string[];
3
+ }
4
+ /**
5
+ * Returns the config file path: $XDG_CONFIG_HOME/skills-manager/config.json
6
+ * Falls back to ~/.config/skills-manager/config.json if XDG_CONFIG_HOME is unset.
7
+ */
8
+ export declare function getConfigPath(): string;
9
+ /**
10
+ * Reads the skills-manager config file.
11
+ * Returns an empty object if the file does not exist or is invalid JSON.
12
+ */
13
+ export declare function readConfig(configPath?: string): SkillsManagerConfig;
14
+ /**
15
+ * Writes a partial config update (merges with existing config).
16
+ * Creates the config directory if it does not exist.
17
+ */
18
+ export declare function writeConfig(update: Partial<SkillsManagerConfig>, configPath?: string): void;
package/dist/config.js ADDED
@@ -0,0 +1,48 @@
1
+ // src/config.ts
2
+ import { homedir } from 'node:os';
3
+ import { join, dirname } from 'node:path';
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
5
+ /**
6
+ * Returns the config file path: $XDG_CONFIG_HOME/skills-manager/config.json
7
+ * Falls back to ~/.config/skills-manager/config.json if XDG_CONFIG_HOME is unset.
8
+ */
9
+ export function getConfigPath() {
10
+ const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
11
+ return join(configHome, 'skills-manager', 'config.json');
12
+ }
13
+ /**
14
+ * Reads the skills-manager config file.
15
+ * Returns an empty object if the file does not exist or is invalid JSON.
16
+ */
17
+ export function readConfig(configPath) {
18
+ const filePath = configPath ?? getConfigPath();
19
+ if (!existsSync(filePath)) {
20
+ return {};
21
+ }
22
+ try {
23
+ const raw = readFileSync(filePath, 'utf-8');
24
+ const parsed = JSON.parse(raw);
25
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
26
+ return {};
27
+ }
28
+ return parsed;
29
+ }
30
+ catch {
31
+ return {};
32
+ }
33
+ }
34
+ /**
35
+ * Writes a partial config update (merges with existing config).
36
+ * Creates the config directory if it does not exist.
37
+ */
38
+ export function writeConfig(update, configPath) {
39
+ const filePath = configPath ?? getConfigPath();
40
+ const dir = dirname(filePath);
41
+ if (!existsSync(dir)) {
42
+ mkdirSync(dir, { recursive: true });
43
+ }
44
+ const existing = readConfig(filePath);
45
+ const merged = { ...existing, ...update };
46
+ writeFileSync(filePath, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
47
+ }
48
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAM7E;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC,UAAU,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,UAAmB;IAC5C,MAAM,QAAQ,GAAG,UAAU,IAAI,aAAa,EAAE,CAAC;IAC/C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,MAA6B,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAoC,EAAE,UAAmB;IACnF,MAAM,QAAQ,GAAG,UAAU,IAAI,aAAa,EAAE,CAAC;IAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1C,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Custom error class for CLI-level failures.
3
+ * Thrown by command handlers instead of calling process.exit(1) directly,
4
+ * enabling testability. Caught at the top-level entrypoint.
5
+ */
6
+ export declare class CliError extends Error {
7
+ constructor(message: string);
8
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ // src/errors.ts
2
+ /**
3
+ * Custom error class for CLI-level failures.
4
+ * Thrown by command handlers instead of calling process.exit(1) directly,
5
+ * enabling testability. Caught at the top-level entrypoint.
6
+ */
7
+ export class CliError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'CliError';
11
+ }
12
+ }
13
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAEhB;;;;GAIG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Build a clean GitHub HTTPS remote URL (no embedded credentials).
3
+ */
4
+ export declare function buildRemoteUrl(repo: string): string;
5
+ /**
6
+ * Scan a directory (non-recursive) for files that look like secrets.
7
+ * Returns an array of suspicious filenames.
8
+ */
9
+ export declare function detectSuspiciousFiles(dir: string): string[];
10
+ /**
11
+ * Ensure a .gitignore exists in the skills repo with sensible defaults.
12
+ * Appends missing entries without overwriting user content.
13
+ */
14
+ export declare function ensureGitignore(dir: string): void;
15
+ /**
16
+ * Get the remote URL of an existing repo (origin).
17
+ */
18
+ export declare function getRepoRemoteUrl(dir: string): Promise<string | null>;
19
+ /**
20
+ * Push: stage all changes, commit with timestamp, push to remote.
21
+ * Uses token transiently via in-memory authenticated URL — never persisted to .git/config.
22
+ */
23
+ export declare function pushSkills(dir: string, message?: string, token?: string | null): Promise<{
24
+ committed: boolean;
25
+ pushed: boolean;
26
+ suspiciousFiles?: string[];
27
+ }>;
28
+ /**
29
+ * Pull: clone if not exists, pull if exists.
30
+ * Uses token transiently — after clone, resets remote to clean URL.
31
+ */
32
+ export declare function pullSkills(dir: string, remoteUrl: string, token?: string | null): Promise<{
33
+ cloned: boolean;
34
+ pulled: boolean;
35
+ }>;
36
+ /**
37
+ * Ensure a directory is a git repository. If not, runs `git init`.
38
+ * Creates the directory if it doesn't exist.
39
+ */
40
+ export declare function ensureGitRepo(dir: string): Promise<{
41
+ initialized: boolean;
42
+ }>;
43
+ /**
44
+ * Ensure origin remote is configured. If not, add it from the given repo slug.
45
+ * Returns the remote URL and whether it was newly added.
46
+ */
47
+ export declare function ensureRemote(dir: string, repo: string): Promise<{
48
+ remoteUrl: string;
49
+ added: boolean;
50
+ }>;
51
+ /**
52
+ * Create a GitHub repository using the `gh` CLI.
53
+ * Throws if `gh` is not installed or the command fails.
54
+ */
55
+ export declare function createGitHubRepo(repo: string, options?: {
56
+ isPublic?: boolean;
57
+ }): void;
@@ -0,0 +1,272 @@
1
+ // src/git-ops.ts
2
+ import { simpleGit } from 'simple-git';
3
+ import { existsSync, readdirSync, readFileSync, appendFileSync, mkdirSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { execSync } from 'node:child_process';
6
+ /**
7
+ * Build a clean GitHub HTTPS remote URL (no embedded credentials).
8
+ */
9
+ export function buildRemoteUrl(repo) {
10
+ return `https://github.com/${repo}.git`;
11
+ }
12
+ /** Patterns that suggest secret or credential files. */
13
+ const SUSPICIOUS_PATTERNS = [
14
+ /^\.env/,
15
+ /\.key$/,
16
+ /\.pem$/,
17
+ /credentials/i,
18
+ /secret/i,
19
+ /token/i,
20
+ ];
21
+ /**
22
+ * Scan a directory (non-recursive) for files that look like secrets.
23
+ * Returns an array of suspicious filenames.
24
+ */
25
+ export function detectSuspiciousFiles(dir) {
26
+ try {
27
+ const entries = readdirSync(dir, { withFileTypes: true });
28
+ return entries
29
+ .filter(e => e.isFile() && SUSPICIOUS_PATTERNS.some(p => p.test(e.name)))
30
+ .map(e => e.name);
31
+ }
32
+ catch {
33
+ return [];
34
+ }
35
+ }
36
+ /** Default patterns that should always be in .gitignore for a skills repo. */
37
+ const GITIGNORE_DEFAULTS = ['.DS_Store'];
38
+ /**
39
+ * Ensure a .gitignore exists in the skills repo with sensible defaults.
40
+ * Appends missing entries without overwriting user content.
41
+ */
42
+ export function ensureGitignore(dir) {
43
+ const gitignorePath = join(dir, '.gitignore');
44
+ let existing = '';
45
+ if (existsSync(gitignorePath)) {
46
+ existing = readFileSync(gitignorePath, 'utf-8');
47
+ }
48
+ const lines = existing.split('\n').map(l => l.trim());
49
+ const missing = GITIGNORE_DEFAULTS.filter(p => !lines.includes(p));
50
+ if (missing.length > 0) {
51
+ const suffix = existing.endsWith('\n') || existing === '' ? '' : '\n';
52
+ appendFileSync(gitignorePath, suffix + missing.join('\n') + '\n', 'utf-8');
53
+ }
54
+ }
55
+ /**
56
+ * Build an authenticated URL for transient use (clone/push/pull).
57
+ * NEVER persist this — use only in-memory for git operations.
58
+ */
59
+ function buildAuthUrl(cleanUrl, token) {
60
+ if (!token)
61
+ return cleanUrl;
62
+ // Insert token into https://github.com/... → https://x-access-token:TOKEN@github.com/...
63
+ return cleanUrl.replace('https://', `https://x-access-token:${token}@`);
64
+ }
65
+ /**
66
+ * Check if a git repo has uncommitted changes.
67
+ */
68
+ async function _hasUncommittedChanges(dir) {
69
+ const git = simpleGit(dir);
70
+ const status = await git.status();
71
+ return !status.isClean();
72
+ }
73
+ /**
74
+ * Get the remote URL of an existing repo (origin).
75
+ */
76
+ export async function getRepoRemoteUrl(dir) {
77
+ try {
78
+ const git = simpleGit(dir);
79
+ const remotes = await git.getRemotes(true);
80
+ const origin = remotes.find((r) => r.name === 'origin');
81
+ return origin?.refs.fetch ?? null;
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
87
+ /**
88
+ * Get the current branch name of a repo.
89
+ */
90
+ async function getCurrentBranch(git) {
91
+ const branch = await git.branchLocal();
92
+ const current = branch.current || '';
93
+ // Detached HEAD: simple-git returns a commit hash or empty string.
94
+ // A valid branch name won't be a 7-40 char hex string.
95
+ if (!current || /^[0-9a-f]{7,40}$/.test(current)) {
96
+ return 'main';
97
+ }
98
+ return current;
99
+ }
100
+ /**
101
+ * Detect the remote's default branch (e.g. main vs master).
102
+ * Falls back to the local branch name if remote info unavailable.
103
+ */
104
+ async function getRemoteDefaultBranch(git) {
105
+ try {
106
+ const raw = await git.raw(['remote', 'show', 'origin']);
107
+ const match = raw.match(/HEAD branch:\s*(\S+)/);
108
+ if (match?.[1])
109
+ return match[1];
110
+ }
111
+ catch {
112
+ // remote show may fail without network; ignore
113
+ }
114
+ return getCurrentBranch(git);
115
+ }
116
+ /**
117
+ * Push: stage all changes, commit with timestamp, push to remote.
118
+ * Uses token transiently via in-memory authenticated URL — never persisted to .git/config.
119
+ */
120
+ export async function pushSkills(dir, message, token) {
121
+ const git = simpleGit(dir);
122
+ const msg = message ?? `backup: ${new Date().toISOString()}`;
123
+ // Warn about potential secrets before staging
124
+ const suspicious = detectSuspiciousFiles(dir);
125
+ // Stage all
126
+ // Ensure .gitignore has sensible defaults (.DS_Store etc.)
127
+ ensureGitignore(dir);
128
+ await git.add('-A');
129
+ // Check if anything to commit
130
+ const status = await git.status();
131
+ if (status.isClean()) {
132
+ return { committed: false, pushed: false, suspiciousFiles: suspicious.length > 0 ? suspicious : undefined };
133
+ }
134
+ // Commit
135
+ await git.commit(msg);
136
+ // Push — if token provided, temporarily set the remote URL with auth, then restore
137
+ const branch = await getCurrentBranch(git);
138
+ if (token) {
139
+ const remotes = await git.getRemotes(true);
140
+ const origin = remotes.find((r) => r.name === 'origin');
141
+ const cleanUrl = origin?.refs.push ?? origin?.refs.fetch ?? '';
142
+ if (!cleanUrl) {
143
+ throw new Error('No origin remote URL configured — cannot push with authentication.');
144
+ }
145
+ const authUrl = buildAuthUrl(cleanUrl, token);
146
+ try {
147
+ await git.remote(['set-url', 'origin', authUrl]);
148
+ await git.push('origin', branch);
149
+ }
150
+ finally {
151
+ // Always restore the clean URL
152
+ await git.remote(['set-url', 'origin', cleanUrl]);
153
+ }
154
+ }
155
+ else {
156
+ await git.push('origin', branch);
157
+ }
158
+ return { committed: true, pushed: true, suspiciousFiles: suspicious.length > 0 ? suspicious : undefined };
159
+ }
160
+ /**
161
+ * Pull: clone if not exists, pull if exists.
162
+ * Uses token transiently — after clone, resets remote to clean URL.
163
+ */
164
+ export async function pullSkills(dir, remoteUrl, token) {
165
+ const isRepo = existsSync(join(dir, '.git'));
166
+ if (!isRepo) {
167
+ // Clone with auth URL, then reset remote to clean URL
168
+ const authUrl = buildAuthUrl(remoteUrl, token ?? null);
169
+ await simpleGit().clone(authUrl, dir);
170
+ if (token) {
171
+ // Reset remote to clean URL so token isn't persisted in .git/config
172
+ const git = simpleGit(dir);
173
+ await git.remote(['set-url', 'origin', remoteUrl]);
174
+ }
175
+ return { cloned: true, pulled: false };
176
+ }
177
+ // Pull latest — use remote's default branch (handles main vs master)
178
+ const git = simpleGit(dir);
179
+ const branch = await getRemoteDefaultBranch(git);
180
+ // If HEAD is detached, checkout the target branch first
181
+ const localBranch = await git.branchLocal();
182
+ if (!localBranch.current || /^[0-9a-f]{7,40}$/.test(localBranch.current)) {
183
+ await git.checkout(branch);
184
+ }
185
+ const doPull = async () => {
186
+ try {
187
+ await git.pull('origin', branch, { '--rebase': null });
188
+ }
189
+ catch (err) {
190
+ // If rebase conflicts, abort and give user a clear message
191
+ const msg = String(err);
192
+ if (msg.includes('CONFLICT') || msg.includes('could not apply')) {
193
+ try {
194
+ await git.rebase(['--abort']);
195
+ }
196
+ catch { /* already clean */ }
197
+ throw new Error('Rebase conflict detected. Your local skills have diverged from the remote.\n'
198
+ + 'To fix, cd into ~/.agents/ and resolve manually:\n'
199
+ + ' cd ~/.agents\n'
200
+ + ' git fetch origin\n'
201
+ + ` git rebase origin/${branch} # resolve conflicts, then git rebase --continue\n`
202
+ + 'Or force-reset to remote (loses local changes):\n'
203
+ + ` git reset --hard origin/${branch}`, { cause: err });
204
+ }
205
+ throw err;
206
+ }
207
+ };
208
+ if (token) {
209
+ const remotes = await git.getRemotes(true);
210
+ const origin = remotes.find((r) => r.name === 'origin');
211
+ const cleanUrl = origin?.refs.fetch ?? remoteUrl;
212
+ const authUrl = buildAuthUrl(cleanUrl, token);
213
+ try {
214
+ await git.remote(['set-url', 'origin', authUrl]);
215
+ await doPull();
216
+ }
217
+ finally {
218
+ await git.remote(['set-url', 'origin', cleanUrl]);
219
+ }
220
+ }
221
+ else {
222
+ await doPull();
223
+ }
224
+ return { cloned: false, pulled: true };
225
+ }
226
+ /**
227
+ * Ensure a directory is a git repository. If not, runs `git init`.
228
+ * Creates the directory if it doesn't exist.
229
+ */
230
+ export async function ensureGitRepo(dir) {
231
+ if (!existsSync(dir)) {
232
+ mkdirSync(dir, { recursive: true });
233
+ }
234
+ if (existsSync(join(dir, '.git'))) {
235
+ return { initialized: false };
236
+ }
237
+ const git = simpleGit(dir);
238
+ await git.init();
239
+ return { initialized: true };
240
+ }
241
+ /**
242
+ * Ensure origin remote is configured. If not, add it from the given repo slug.
243
+ * Returns the remote URL and whether it was newly added.
244
+ */
245
+ export async function ensureRemote(dir, repo) {
246
+ const git = simpleGit(dir);
247
+ const remotes = await git.getRemotes(true);
248
+ const origin = remotes.find((r) => r.name === 'origin');
249
+ if (origin?.refs.fetch) {
250
+ return { remoteUrl: origin.refs.fetch, added: false };
251
+ }
252
+ const remoteUrl = buildRemoteUrl(repo);
253
+ await git.remote(['add', 'origin', remoteUrl]);
254
+ return { remoteUrl, added: true };
255
+ }
256
+ /**
257
+ * Create a GitHub repository using the `gh` CLI.
258
+ * Throws if `gh` is not installed or the command fails.
259
+ */
260
+ export function createGitHubRepo(repo, options) {
261
+ const visibility = options?.isPublic ? '--public' : '--private';
262
+ try {
263
+ execSync(`gh repo create ${repo} ${visibility} --confirm`, {
264
+ encoding: 'utf-8',
265
+ stdio: ['pipe', 'pipe', 'pipe'],
266
+ });
267
+ }
268
+ catch (err) {
269
+ throw new Error(`Failed to create GitHub repository '${repo}': ${String(err)}`, { cause: err });
270
+ }
271
+ }
272
+ //# sourceMappingURL=git-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-ops.js","sourceRoot":"","sources":["../src/git-ops.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { CliError } from './errors.js';
4
+ import { pushCommand } from './commands/push.js';
5
+ import { pullCommand } from './commands/pull.js';
6
+ import { linkCommand } from './commands/link.js';
7
+ const program = new Command();
8
+ program
9
+ .name('skills-manager')
10
+ .description('Backup and restore AI agent skills to GitHub')
11
+ .version('0.1.0');
12
+ program
13
+ .command('push')
14
+ .description('Push ~/.agents/ (skills + lock file) to GitHub')
15
+ .option('-m, --message <message>', 'Commit message')
16
+ .action(pushCommand);
17
+ program
18
+ .command('pull')
19
+ .description('Pull ~/.agents/ (skills + lock file) from GitHub')
20
+ .option('-r, --repo <repo>', 'GitHub repo (owner/name)')
21
+ .option('--skip-link', 'Skip automatic agent linking after pull')
22
+ .action(pullCommand);
23
+ program
24
+ .command('link')
25
+ .description('Create symlinks from canonical skills to agent directories')
26
+ .option('-a, --agents <agents...>', 'Agent IDs to link (default: from .skill-lock.json)')
27
+ .action(linkCommand);
28
+ // Catch CliError from command handlers and exit with code 1
29
+ program.parseAsync().catch((err) => {
30
+ if (err instanceof CliError) {
31
+ process.exit(1);
32
+ }
33
+ console.error(err);
34
+ process.exit(1);
35
+ });
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,gBAAgB,CAAC;KACtB,WAAW,CAAC,8CAA8C,CAAC;KAC3D,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,gDAAgD,CAAC;KAC7D,MAAM,CAAC,yBAAyB,EAAE,gBAAgB,CAAC;KACnD,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,mBAAmB,EAAE,0BAA0B,CAAC;KACvD,MAAM,CAAC,aAAa,EAAE,yCAAyC,CAAC;KAChE,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,4DAA4D,CAAC;KACzE,MAAM,CAAC,0BAA0B,EAAE,oDAAoD,CAAC;KACxF,MAAM,CAAC,WAAW,CAAC,CAAC;AAGvB,4DAA4D;AAC5D,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC1C,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;QAC5B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ export interface LinkResult {
2
+ skill: string;
3
+ status: 'created' | 'exists' | 'recreated' | 'skipped';
4
+ reason?: string;
5
+ }
6
+ /**
7
+ * Compute relative symlink target from linkPath to targetPath.
8
+ * This matches how vercel-labs/skills creates relative symlinks.
9
+ */
10
+ export declare function computeRelativeSymlinkTarget(linkPath: string, targetPath: string): string;
11
+ /**
12
+ * Create symlinks from agentSkillsDir/<skill> → canonicalDir/<skill>
13
+ * for each skill name in the list.
14
+ *
15
+ * @param canonicalDir - Absolute path to ~/.agents/skills/
16
+ * @param agentSkillsDir - Absolute path to agent's global skills dir
17
+ * @param skillNames - List of skill folder names to link
18
+ */
19
+ export declare function createSkillSymlinks(canonicalDir: string, agentSkillsDir: string, skillNames: string[]): Promise<LinkResult[]>;
20
+ /**
21
+ * Get all skill folder names from the canonical directory.
22
+ */
23
+ export declare function listCanonicalSkills(canonicalDir: string): Promise<string[]>;
package/dist/linker.js ADDED
@@ -0,0 +1,78 @@
1
+ // src/linker.ts
2
+ import { symlink, lstat, readdir, mkdir, readlink, unlink } from 'node:fs/promises';
3
+ import { join, relative, dirname } from 'node:path';
4
+ import { existsSync } from 'node:fs';
5
+ /**
6
+ * Compute relative symlink target from linkPath to targetPath.
7
+ * This matches how vercel-labs/skills creates relative symlinks.
8
+ */
9
+ export function computeRelativeSymlinkTarget(linkPath, targetPath) {
10
+ return relative(dirname(linkPath), targetPath);
11
+ }
12
+ /**
13
+ * Create symlinks from agentSkillsDir/<skill> → canonicalDir/<skill>
14
+ * for each skill name in the list.
15
+ *
16
+ * @param canonicalDir - Absolute path to ~/.agents/skills/
17
+ * @param agentSkillsDir - Absolute path to agent's global skills dir
18
+ * @param skillNames - List of skill folder names to link
19
+ */
20
+ export async function createSkillSymlinks(canonicalDir, agentSkillsDir, skillNames) {
21
+ // Ensure agent dir exists
22
+ await mkdir(agentSkillsDir, { recursive: true });
23
+ const results = [];
24
+ for (const skill of skillNames) {
25
+ const canonicalPath = join(canonicalDir, skill);
26
+ const linkPath = join(agentSkillsDir, skill);
27
+ // Check skill exists in canonical
28
+ if (!existsSync(canonicalPath)) {
29
+ results.push({ skill, status: 'skipped', reason: `not found in ${canonicalDir}` });
30
+ continue;
31
+ }
32
+ // Check if symlink already exists
33
+ try {
34
+ const stats = await lstat(linkPath);
35
+ if (stats.isSymbolicLink()) {
36
+ // Verify the symlink points to the correct target
37
+ const expectedTarget = computeRelativeSymlinkTarget(linkPath, canonicalPath);
38
+ const actualTarget = await readlink(linkPath);
39
+ if (actualTarget === expectedTarget) {
40
+ results.push({ skill, status: 'exists' });
41
+ continue;
42
+ }
43
+ // Stale symlink — remove and recreate
44
+ await unlink(linkPath);
45
+ const target = computeRelativeSymlinkTarget(linkPath, canonicalPath);
46
+ await symlink(target, linkPath);
47
+ results.push({ skill, status: 'recreated', reason: `was pointing to ${actualTarget}` });
48
+ continue;
49
+ }
50
+ // Exists but not a symlink — skip to avoid overwriting
51
+ results.push({ skill, status: 'skipped', reason: 'path exists but is not a symlink' });
52
+ continue;
53
+ }
54
+ catch {
55
+ // Doesn't exist — good, we'll create it
56
+ }
57
+ // Create relative symlink
58
+ const target = computeRelativeSymlinkTarget(linkPath, canonicalPath);
59
+ await symlink(target, linkPath);
60
+ results.push({ skill, status: 'created' });
61
+ }
62
+ return results;
63
+ }
64
+ /**
65
+ * Get all skill folder names from the canonical directory.
66
+ */
67
+ export async function listCanonicalSkills(canonicalDir) {
68
+ try {
69
+ const entries = await readdir(canonicalDir, { withFileTypes: true });
70
+ return entries
71
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
72
+ .map(e => e.name);
73
+ }
74
+ catch {
75
+ return [];
76
+ }
77
+ }
78
+ //# sourceMappingURL=linker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linker.js","sourceRoot":"","sources":["../src/linker.ts"],"names":[],"mappings":"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"}