@rbbtsn0w/adg 0.1.0-alpha.1 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/bin/adg.js +703 -0
  2. package/dist/src/adapters/anthropic.js +54 -0
  3. package/dist/src/adapters/index.js +10 -0
  4. package/dist/src/adapters/openai.js +30 -0
  5. package/dist/src/adapters/reverse.js +53 -0
  6. package/dist/src/agents/claude.js +118 -0
  7. package/dist/src/agents/codex.js +61 -0
  8. package/{src/agents/index.ts → dist/src/agents/index.js} +6 -8
  9. package/dist/src/agents/registry.js +24 -0
  10. package/dist/src/agents/types.js +1 -0
  11. package/dist/src/commands/adapt.js +26 -0
  12. package/dist/src/commands/import.js +51 -0
  13. package/dist/src/commands/init.js +104 -0
  14. package/dist/src/commands/install.js +257 -0
  15. package/dist/src/commands/link.js +34 -0
  16. package/dist/src/commands/list.js +19 -0
  17. package/dist/src/commands/marketplace.js +124 -0
  18. package/dist/src/commands/migrate.js +60 -0
  19. package/dist/src/commands/multiselect-skills.js +103 -0
  20. package/dist/src/commands/remove.js +102 -0
  21. package/dist/src/commands/select-agents.js +40 -0
  22. package/dist/src/commands/select-components.js +61 -0
  23. package/dist/src/commands/select-plugins.js +25 -0
  24. package/dist/src/commands/select-scope.js +20 -0
  25. package/dist/src/commands/update.js +50 -0
  26. package/dist/src/commands/validate.js +50 -0
  27. package/dist/src/components.js +90 -0
  28. package/dist/src/deps.js +46 -0
  29. package/dist/src/fsutil.js +32 -0
  30. package/dist/src/hash.js +51 -0
  31. package/dist/src/lock.js +51 -0
  32. package/dist/src/manifest.js +110 -0
  33. package/dist/src/marketplace.js +39 -0
  34. package/{src/package.ts → dist/src/package.js} +37 -42
  35. package/{src/paths.ts → dist/src/paths.js} +54 -60
  36. package/dist/src/semver.js +55 -0
  37. package/dist/src/skills.js +79 -0
  38. package/dist/src/sources.js +122 -0
  39. package/dist/src/types.js +19 -0
  40. package/dist/vendor/skills/package.json +143 -0
  41. package/dist/vendor/skills/src/add.js +1663 -0
  42. package/dist/vendor/skills/src/agents.js +729 -0
  43. package/dist/vendor/skills/src/blob.js +436 -0
  44. package/dist/vendor/skills/src/cli.js +340 -0
  45. package/dist/vendor/skills/src/constants.js +3 -0
  46. package/dist/vendor/skills/src/detect-agent.js +56 -0
  47. package/dist/vendor/skills/src/find.js +294 -0
  48. package/dist/vendor/skills/src/frontmatter.js +13 -0
  49. package/dist/vendor/skills/src/git-tree.js +32 -0
  50. package/dist/vendor/skills/src/git.js +235 -0
  51. package/dist/vendor/skills/src/install.js +75 -0
  52. package/dist/vendor/skills/src/installer.js +924 -0
  53. package/dist/vendor/skills/src/list.js +201 -0
  54. package/dist/vendor/skills/src/local-lock.js +109 -0
  55. package/dist/vendor/skills/src/plugin-manifest.js +152 -0
  56. package/dist/vendor/skills/src/prompts/search-multiselect.js +312 -0
  57. package/dist/vendor/skills/src/providers/index.js +4 -0
  58. package/dist/vendor/skills/src/providers/registry.js +42 -0
  59. package/dist/vendor/skills/src/providers/types.js +1 -0
  60. package/dist/vendor/skills/src/providers/wellknown.js +625 -0
  61. package/dist/vendor/skills/src/remove.js +263 -0
  62. package/dist/vendor/skills/src/sanitize.js +57 -0
  63. package/dist/vendor/skills/src/self-cli.js +15 -0
  64. package/dist/vendor/skills/src/skill-lock.js +237 -0
  65. package/dist/vendor/skills/src/skills.js +264 -0
  66. package/dist/vendor/skills/src/source-parser.js +367 -0
  67. package/dist/vendor/skills/src/sync.js +404 -0
  68. package/dist/vendor/skills/src/telemetry.js +101 -0
  69. package/dist/vendor/skills/src/test-utils.js +59 -0
  70. package/dist/vendor/skills/src/types.js +1 -0
  71. package/dist/vendor/skills/src/update-source.js +76 -0
  72. package/dist/vendor/skills/src/update.js +590 -0
  73. package/dist/vendor/skills/src/use.js +505 -0
  74. package/package.json +15 -7
  75. package/bin/adg.ts +0 -758
  76. package/src/adapters/anthropic.ts +0 -54
  77. package/src/adapters/index.ts +0 -24
  78. package/src/adapters/openai.ts +0 -37
  79. package/src/adapters/reverse.ts +0 -60
  80. package/src/agents/claude.ts +0 -124
  81. package/src/agents/codex.ts +0 -67
  82. package/src/agents/registry.ts +0 -30
  83. package/src/agents/types.ts +0 -47
  84. package/src/commands/adapt.ts +0 -36
  85. package/src/commands/import.ts +0 -69
  86. package/src/commands/init.ts +0 -146
  87. package/src/commands/install.ts +0 -411
  88. package/src/commands/link.ts +0 -61
  89. package/src/commands/list.ts +0 -28
  90. package/src/commands/marketplace.ts +0 -198
  91. package/src/commands/migrate.ts +0 -84
  92. package/src/commands/multiselect-skills.ts +0 -137
  93. package/src/commands/remove.ts +0 -136
  94. package/src/commands/select-agents.ts +0 -45
  95. package/src/commands/select-components.ts +0 -66
  96. package/src/commands/select-plugins.ts +0 -28
  97. package/src/commands/select-scope.ts +0 -21
  98. package/src/commands/update.ts +0 -85
  99. package/src/commands/validate.ts +0 -57
  100. package/src/components.ts +0 -90
  101. package/src/deps.ts +0 -64
  102. package/src/fsutil.ts +0 -38
  103. package/src/hash.ts +0 -61
  104. package/src/lock.ts +0 -57
  105. package/src/manifest.ts +0 -113
  106. package/src/marketplace.ts +0 -41
  107. package/src/semver.ts +0 -67
  108. package/src/skills.ts +0 -88
  109. package/src/sources.ts +0 -159
  110. package/src/types.ts +0 -140
@@ -0,0 +1,13 @@
1
+ import { parse as parseYaml } from 'yaml';
2
+ /**
3
+ * Minimal frontmatter parser. Only supports YAML (the `---` delimiter).
4
+ * Does NOT support `---js` / `---javascript` to avoid eval()-based RCE
5
+ * that exists in gray-matter's built-in JS engine.
6
+ */
7
+ export function parseFrontmatter(raw) {
8
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
9
+ if (!match)
10
+ return { data: {}, content: raw };
11
+ const data = parseYaml(match[1]) ?? {};
12
+ return { data, content: match[2] ?? '' };
13
+ }
@@ -0,0 +1,32 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ /**
5
+ * ADG-added file (see vendor/skills/PROVENANCE.md → Local patches).
6
+ *
7
+ * Return the git *tree object SHA* for a folder inside a freshly cloned repo —
8
+ * the SAME 40-hex value GitHub's Trees API returns and that
9
+ * `getSkillFolderHashFromTree` (blob.ts) compares against at update time.
10
+ *
11
+ * Used by `add.ts` so a github source that fell back to a `git clone` at install
12
+ * still records a tree SHA, not a sha256 content hash. Otherwise the install and
13
+ * update hashing schemes diverge and every update perpetually re-flags the skill
14
+ * (the bug that made a collection repo "fully update" on every run).
15
+ *
16
+ * `folder === ''` means the repo root. Returns null if git can't resolve it.
17
+ *
18
+ * Deliberately uses `child_process` rather than simple-git: this keeps the file
19
+ * free of simple-git's typings, which don't satisfy ADG's strict tsconfig when a
20
+ * test pulls the module into the typecheck graph.
21
+ */
22
+ export async function gitTreeShaForFolder(repoDir, folder) {
23
+ const spec = folder ? `HEAD:${folder}` : 'HEAD^{tree}';
24
+ try {
25
+ const { stdout } = await execFileAsync('git', ['-C', repoDir, 'rev-parse', spec]);
26
+ const sha = stdout.trim();
27
+ return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
@@ -0,0 +1,235 @@
1
+ // ADG patch: named import. The default import is not callable under the root
2
+ // tsconfig (NodeNext + verbatimModuleSyntax, no esModuleInterop); simple-git
3
+ // exposes `simpleGit` as a named export. See vendor/skills/PROVENANCE.md.
4
+ import { simpleGit } from 'simple-git';
5
+ import { join, normalize, resolve, sep } from 'path';
6
+ import { mkdtemp, mkdir, rm } from 'fs/promises';
7
+ import { tmpdir } from 'os';
8
+ import { execFile } from 'child_process';
9
+ import { promisify } from 'util';
10
+ const DEFAULT_CLONE_TIMEOUT_MS = 300_000; // 5 minutes
11
+ const CLONE_TIMEOUT_MS = (() => {
12
+ const raw = process.env.SKILLS_CLONE_TIMEOUT_MS;
13
+ if (!raw)
14
+ return DEFAULT_CLONE_TIMEOUT_MS;
15
+ const parsed = Number.parseInt(raw, 10);
16
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CLONE_TIMEOUT_MS;
17
+ })();
18
+ const execFileAsync = promisify(execFile);
19
+ export class GitCloneError extends Error {
20
+ url;
21
+ isTimeout;
22
+ isAuthError;
23
+ constructor(message, url, isTimeout = false, isAuthError = false) {
24
+ super(message);
25
+ this.name = 'GitCloneError';
26
+ this.url = url;
27
+ this.isTimeout = isTimeout;
28
+ this.isAuthError = isAuthError;
29
+ }
30
+ }
31
+ export function parseGitHubRepoUrl(url) {
32
+ const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
33
+ if (sshMatch) {
34
+ const owner = sshMatch[1];
35
+ const repo = sshMatch[2];
36
+ return {
37
+ owner,
38
+ repo,
39
+ slug: `${owner}/${repo}`,
40
+ sshUrl: `git@github.com:${owner}/${repo}.git`,
41
+ };
42
+ }
43
+ try {
44
+ const parsed = new URL(url);
45
+ if (parsed.hostname !== 'github.com')
46
+ return null;
47
+ const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
48
+ if (!match)
49
+ return null;
50
+ const owner = match[1];
51
+ const repo = match[2];
52
+ return {
53
+ owner,
54
+ repo,
55
+ slug: `${owner}/${repo}`,
56
+ sshUrl: `git@github.com:${owner}/${repo}.git`,
57
+ };
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ export function isGitHubHttpsCloneUrl(url) {
64
+ try {
65
+ const parsed = new URL(url);
66
+ return parsed.protocol === 'https:' && parsed.hostname === 'github.com';
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ export function isGitHubSsoAuthError(message) {
73
+ const lower = message.toLowerCase();
74
+ return (lower.includes('saml sso') ||
75
+ lower.includes('enforced sso') ||
76
+ lower.includes('enabled or enforced saml') ||
77
+ lower.includes('re-authorize the oauth application'));
78
+ }
79
+ function isAuthFailure(message) {
80
+ return (message.includes('Authentication failed') ||
81
+ message.includes('could not read Username') ||
82
+ message.includes('Permission denied') ||
83
+ message.includes('Repository not found') ||
84
+ message.includes('requested URL returned error: 403') ||
85
+ isGitHubSsoAuthError(message));
86
+ }
87
+ function createGitClient(extraEnv) {
88
+ return simpleGit({
89
+ timeout: { block: CLONE_TIMEOUT_MS },
90
+ // When git-lfs is NOT installed, GIT_LFS_SKIP_SMUDGE has no effect —
91
+ // git sees `filter=lfs` in .gitattributes, tries to run
92
+ // `git-lfs filter-process`, and aborts the checkout with:
93
+ // git-lfs filter-process: git-lfs: command not found
94
+ // fatal: the remote end hung up unexpectedly
95
+ // warning: Clone succeeded, but checkout failed.
96
+ // Overriding filter.lfs.* at the command level disables the filter
97
+ // entirely for this clone, so checkout succeeds regardless of whether
98
+ // git-lfs is installed. LFS-tracked files are left as ~130-byte
99
+ // pointer files, which the skills installer doesn't read anyway
100
+ // (skills are plain text — HTML/MD/JSON — never LFS-tracked).
101
+ //
102
+ // Reported downstream: heygen-com/hyperframes#407.
103
+ config: [
104
+ 'filter.lfs.required=false',
105
+ 'filter.lfs.smudge=',
106
+ 'filter.lfs.clean=',
107
+ 'filter.lfs.process=',
108
+ ],
109
+ // ADG patch: simple-git >=3.36 blocks `filter.*.smudge/clean` configs (an RCE
110
+ // vector via a malicious filter command) unless explicitly allowed, failing
111
+ // every clone with "Configuring filter.smudge is not permitted without
112
+ // enabling allowUnsafeFilter". Here the filters are set to EMPTY — we are
113
+ // *disabling* the LFS filter, not running one — so opting in is safe and is
114
+ // exactly the intent of the config above. See vendor/skills/PROVENANCE.md.
115
+ unsafe: {
116
+ allowUnsafeFilter: true,
117
+ },
118
+ // ADG patch: env must be applied via `.env()`, not as a constructor option.
119
+ // simple-git's factory only reads baseDir/maxConcurrentProcesses/trimmed from
120
+ // the options object and silently drops `env`, so the GIT_TERMINAL_PROMPT /
121
+ // GIT_LFS_SKIP_SMUDGE / GIT_SSH_COMMAND overrides below never reached the
122
+ // spawned git before this. See vendor/skills/PROVENANCE.md.
123
+ }).env({
124
+ ...process.env,
125
+ GIT_TERMINAL_PROMPT: '0',
126
+ // When git-lfs IS installed, tell it not to download LFS content during
127
+ // checkout. See #952 for context and empirical impact.
128
+ GIT_LFS_SKIP_SMUDGE: '1',
129
+ ...extraEnv,
130
+ });
131
+ }
132
+ async function resetTempDir(dir) {
133
+ await rm(dir, { recursive: true, force: true }).catch(() => { });
134
+ await mkdir(dir, { recursive: true });
135
+ }
136
+ async function tryGhClone(repo, tempDir, ref) {
137
+ let cloneTarget = repo.slug;
138
+ try {
139
+ const { stdout, stderr } = await execFileAsync('gh', ['auth', 'status', '-h', 'github.com'], {
140
+ timeout: 5000,
141
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
142
+ });
143
+ const statusOutput = `${stdout}${stderr}`;
144
+ if (/Git operations protocol:\s+ssh/i.test(statusOutput)) {
145
+ cloneTarget = repo.sshUrl;
146
+ }
147
+ }
148
+ catch {
149
+ return false;
150
+ }
151
+ const gitFlags = ref ? ['--depth=1', '--branch', ref] : ['--depth=1'];
152
+ await execFileAsync('gh', ['repo', 'clone', cloneTarget, tempDir, '--', ...gitFlags], {
153
+ timeout: CLONE_TIMEOUT_MS,
154
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
155
+ });
156
+ return true;
157
+ }
158
+ function buildGitHubAuthError(url, repo, message) {
159
+ if (repo && isGitHubSsoAuthError(message)) {
160
+ return (`GitHub blocked HTTPS access to ${url} because the organization enforces SAML SSO.\n` +
161
+ ` skills tried your existing git credentials and available fallbacks, but none succeeded.\n` +
162
+ ` - Re-authorize your GitHub credentials/app for that org's SSO policy\n` +
163
+ ` - Or rerun with SSH: npx skills add ${repo.sshUrl}\n` +
164
+ ` - Verify access with: gh auth status -h github.com or ssh -T git@github.com`);
165
+ }
166
+ if (repo) {
167
+ return (`Authentication failed for ${url}.\n` +
168
+ ` - For private repos, ensure you have access\n` +
169
+ ` - Retry with SSH: npx skills add ${repo.sshUrl}\n` +
170
+ ` - Check access with: gh auth status -h github.com or ssh -T git@github.com`);
171
+ }
172
+ return (`Authentication failed for ${url}.\n` +
173
+ ` - For private repos, ensure you have access\n` +
174
+ ` - For SSH: Check your keys with 'ssh -T git@github.com'\n` +
175
+ ` - For HTTPS: Run 'gh auth login' or configure git credentials`);
176
+ }
177
+ export async function cloneRepo(url, ref) {
178
+ const tempDir = await mkdtemp(join(tmpdir(), 'skills-'));
179
+ const cloneOptions = ref ? ['--depth', '1', '--branch', ref] : ['--depth', '1'];
180
+ const repo = parseGitHubRepoUrl(url);
181
+ try {
182
+ await createGitClient().clone(url, tempDir, cloneOptions);
183
+ return tempDir;
184
+ }
185
+ catch (error) {
186
+ const errorMessage = error instanceof Error ? error.message : String(error);
187
+ const isTimeout = errorMessage.includes('block timeout') || errorMessage.includes('timed out');
188
+ const isAuthError = isAuthFailure(errorMessage);
189
+ if (isTimeout) {
190
+ await rm(tempDir, { recursive: true, force: true }).catch(() => { });
191
+ const seconds = Math.round(CLONE_TIMEOUT_MS / 1000);
192
+ throw new GitCloneError(`Clone timed out after ${seconds}s. Common causes:\n` +
193
+ ` - Large repository: raise the timeout with SKILLS_CLONE_TIMEOUT_MS=600000 (10m)\n` +
194
+ ` - Slow network: retry, or clone manually and pass the local path to 'skills add'\n` +
195
+ ` - Private repo without credentials: ensure auth is configured\n` +
196
+ ` - For SSH: ssh-add -l (to check loaded keys)\n` +
197
+ ` - For HTTPS: gh auth status (if using GitHub CLI)`, url, true, false);
198
+ }
199
+ if (isAuthError && repo && isGitHubHttpsCloneUrl(url)) {
200
+ try {
201
+ await resetTempDir(tempDir);
202
+ if (await tryGhClone(repo, tempDir, ref)) {
203
+ return tempDir;
204
+ }
205
+ }
206
+ catch {
207
+ // Fall through to SSH retry.
208
+ }
209
+ try {
210
+ await resetTempDir(tempDir);
211
+ await createGitClient({
212
+ GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND ?? 'ssh -o BatchMode=yes',
213
+ }).clone(repo.sshUrl, tempDir, cloneOptions);
214
+ return tempDir;
215
+ }
216
+ catch {
217
+ // Fall through to the targeted auth error below.
218
+ }
219
+ }
220
+ await rm(tempDir, { recursive: true, force: true }).catch(() => { });
221
+ if (isAuthError) {
222
+ throw new GitCloneError(buildGitHubAuthError(url, repo, errorMessage), url, false, true);
223
+ }
224
+ throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
225
+ }
226
+ }
227
+ export async function cleanupTempDir(dir) {
228
+ // Validate that the directory path is within tmpdir to prevent deletion of arbitrary paths
229
+ const normalizedDir = normalize(resolve(dir));
230
+ const normalizedTmpDir = normalize(resolve(tmpdir()));
231
+ if (!normalizedDir.startsWith(normalizedTmpDir + sep) && normalizedDir !== normalizedTmpDir) {
232
+ throw new Error('Attempted to clean up directory outside of temp directory');
233
+ }
234
+ await rm(dir, { recursive: true, force: true });
235
+ }
@@ -0,0 +1,75 @@
1
+ import * as p from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import { readLocalLock } from "./local-lock.js";
4
+ import { runAdd } from "./add.js";
5
+ import { runSync, parseSyncOptions } from "./sync.js";
6
+ import { getUniversalAgents } from "./agents.js";
7
+ /**
8
+ * Install all skills from the local skills-lock.json.
9
+ * Groups skills by source and calls `runAdd` for each group.
10
+ *
11
+ * Only installs to .agents/skills/ (universal agents) -- the canonical
12
+ * project-level location. Does not install to agent-specific directories.
13
+ *
14
+ * node_modules skills are handled via experimental_sync.
15
+ */
16
+ export async function runInstallFromLock(args) {
17
+ const cwd = process.cwd();
18
+ const lock = await readLocalLock(cwd);
19
+ const skillEntries = Object.entries(lock.skills);
20
+ if (skillEntries.length === 0) {
21
+ p.log.warn('No project skills found in skills-lock.json');
22
+ p.log.info(`Add project-level skills with ${pc.cyan('npx skills add <package>')} (without ${pc.cyan('-g')})`);
23
+ return;
24
+ }
25
+ // Only install to .agents/skills/ (universal agents)
26
+ const universalAgentNames = getUniversalAgents();
27
+ // Separate node_modules skills from remote skills
28
+ const nodeModuleSkills = [];
29
+ const bySource = new Map();
30
+ for (const [skillName, entry] of skillEntries) {
31
+ if (entry.sourceType === 'node_modules') {
32
+ nodeModuleSkills.push(skillName);
33
+ continue;
34
+ }
35
+ const installSource = entry.ref ? `${entry.source}#${entry.ref}` : entry.source;
36
+ const existing = bySource.get(installSource);
37
+ if (existing) {
38
+ existing.skills.push(skillName);
39
+ }
40
+ else {
41
+ bySource.set(installSource, {
42
+ sourceType: entry.sourceType,
43
+ skills: [skillName],
44
+ });
45
+ }
46
+ }
47
+ const remoteCount = skillEntries.length - nodeModuleSkills.length;
48
+ if (remoteCount > 0) {
49
+ p.log.info(`Restoring ${pc.cyan(String(remoteCount))} skill${remoteCount !== 1 ? 's' : ''} from skills-lock.json into ${pc.dim('.agents/skills/')}`);
50
+ }
51
+ // Install remote skills grouped by source
52
+ for (const [source, { skills }] of bySource) {
53
+ try {
54
+ await runAdd([source], {
55
+ skill: skills,
56
+ agent: universalAgentNames,
57
+ yes: true,
58
+ });
59
+ }
60
+ catch (error) {
61
+ p.log.error(`Failed to install from ${pc.cyan(source)}: ${error instanceof Error ? error.message : 'Unknown error'}`);
62
+ }
63
+ }
64
+ // Handle node_modules skills via sync
65
+ if (nodeModuleSkills.length > 0) {
66
+ p.log.info(`${pc.cyan(String(nodeModuleSkills.length))} skill${nodeModuleSkills.length !== 1 ? 's' : ''} from node_modules`);
67
+ try {
68
+ const { options: syncOptions } = parseSyncOptions(args);
69
+ await runSync(args, { ...syncOptions, yes: true, agent: universalAgentNames });
70
+ }
71
+ catch (error) {
72
+ p.log.error(`Failed to sync node_modules skills: ${error instanceof Error ? error.message : 'Unknown error'}`);
73
+ }
74
+ }
75
+ }