@rbbtsn0w/adg 0.1.0-beta.2 → 0.1.0-beta.3
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/README.md +35 -35
- package/dist/src/commands/remove.js +1 -2
- package/dist/src/commands/update.js +2 -1
- package/dist/src/commands/validate.js +1 -2
- package/dist/src/semver.js +8 -2
- package/dist/vendor/skills/src/providers/wellknown.js +7 -0
- package/dist/vendor/skills/src/source-parser.js +12 -1
- package/dist/vendor/skills/src/use.js +23 -15
- package/package.json +1 -1
- package/vendor/skills/src/providers/wellknown.ts +8 -0
- package/vendor/skills/src/source-parser.ts +12 -1
- package/vendor/skills/src/use.ts +22 -15
package/README.md
CHANGED
|
@@ -33,6 +33,40 @@ See [docs/authoring.md](docs/authoring.md) to author a plugin, and
|
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
+
# Install and quick start
|
|
37
|
+
|
|
38
|
+
Install the CLI once, then run `adg` from anywhere:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g @rbbtsn0w/adg # stable channel
|
|
42
|
+
npm install -g @rbbtsn0w/adg@beta # pre-release channel
|
|
43
|
+
# or run ad-hoc, no install:
|
|
44
|
+
npx @rbbtsn0w/adg --help
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Typical end-user flow — pull a marketplace into your global store, then load it
|
|
48
|
+
into the runtimes you use:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 1) collect plugins into the global store (~/.agents/plugins)
|
|
52
|
+
adg plugins add anthropics/knowledge-work-plugins --ref main --global
|
|
53
|
+
# large monorepo? fetch only what you need:
|
|
54
|
+
adg plugins add anthropics/knowledge-work-plugins --ref main --sparse engineering --global
|
|
55
|
+
|
|
56
|
+
# 2) load into the runtimes you use
|
|
57
|
+
adg plugins link --target codex --global # Codex discovers ~/.agents/plugins natively
|
|
58
|
+
adg plugins link --target claude --global # Claude loads via ~/.claude/skills symlinks
|
|
59
|
+
|
|
60
|
+
# 3) keep it current
|
|
61
|
+
adg plugins update --global
|
|
62
|
+
adg plugins list --global
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`adg` is the only command you invoke — no Node build step beyond the global
|
|
66
|
+
install. To hack on the CLI itself, see [Developing from source](#developing-from-source).
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
36
70
|
# Concepts (common)
|
|
37
71
|
|
|
38
72
|
These apply the same whether you run a released build or the source tree.
|
|
@@ -99,7 +133,7 @@ The command surface is identical in both modes — **only the launcher differs**
|
|
|
99
133
|
|
|
100
134
|
| Mode | Launcher | Setup |
|
|
101
135
|
|------|----------|-------|
|
|
102
|
-
| Released build | `adg …` | install the package (see [
|
|
136
|
+
| Released build | `adg …` | install the package (see [Install and quick start](#install-and-quick-start)) |
|
|
103
137
|
| From source (debug) | `node bin/adg.ts …` | clone + `npm install` (see [Developing from source](#developing-from-source)) |
|
|
104
138
|
|
|
105
139
|
The examples below use the released `adg` launcher. **When running from source,
|
|
@@ -233,40 +267,6 @@ private discovery path:
|
|
|
233
267
|
|
|
234
268
|
---
|
|
235
269
|
|
|
236
|
-
# Using a released build
|
|
237
|
-
|
|
238
|
-
> Pre-release: not yet published to npm. This is the intended end-user flow.
|
|
239
|
-
|
|
240
|
-
Install the CLI once, then use the `adg` command from anywhere:
|
|
241
|
-
|
|
242
|
-
```bash
|
|
243
|
-
npm install -g adg # or: npx adg <command>
|
|
244
|
-
adg --help
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
Typical end-user workflow — bring a marketplace into your global environment and
|
|
248
|
-
load it into your runtimes:
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
# 1) collect plugins into the global store (~/.agents/plugins)
|
|
252
|
-
adg plugins add anthropics/knowledge-work-plugins --ref main --global
|
|
253
|
-
# large monorepo? fetch only what you need:
|
|
254
|
-
adg plugins add anthropics/knowledge-work-plugins --ref main --sparse engineering --global
|
|
255
|
-
|
|
256
|
-
# 2) load into the runtimes you use
|
|
257
|
-
adg plugins link --target codex --global # Codex discovers ~/.agents/plugins natively
|
|
258
|
-
adg plugins link --target claude --global # Claude loads via ~/.claude/skills symlinks
|
|
259
|
-
|
|
260
|
-
# 3) keep it current
|
|
261
|
-
adg plugins update --global
|
|
262
|
-
adg plugins list --global
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
No Node toolchain step is required beyond the global install; `adg` is the only
|
|
266
|
-
command you invoke.
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
270
|
# Developing from source
|
|
271
271
|
|
|
272
272
|
For working on the CLI itself, or testing a plugin before release. The CLI runs
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { existsSync, lstatSync, readdirSync, readlinkSync, rmdirSync, rmSync } from "node:fs";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { claudeSkillsDir, lockPath, marketplacePath, pluginDir } from "../paths.js";
|
|
4
4
|
import { readLock, removeEntry, writeLock } from "../lock.js";
|
|
5
5
|
import { readMarketplace, removeMarketplacePlugin, writeMarketplace } from "../marketplace.js";
|
|
6
|
-
import { basename } from "node:path";
|
|
7
6
|
import { resolveAgents } from "../agents/index.js";
|
|
8
7
|
/**
|
|
9
8
|
* Remove an installed plugin: delete its directory, drop it from
|
|
@@ -12,7 +12,8 @@ import { resolveAgents } from "../agents/index.js";
|
|
|
12
12
|
* changed are reported as `changed`. A missing plugin directory is reported as
|
|
13
13
|
* an issue rather than silently dropped.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* Changed plugins always have their runtime manifests regenerated (honoring
|
|
16
|
+
* their selection). With `resync`, the regenerated content is additionally
|
|
16
17
|
* re-installed into the agents so Claude/Codex reflect the new content.
|
|
17
18
|
*/
|
|
18
19
|
export function updateLock(pluginsDir, now = new Date().toISOString(), opts = {}) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { ADG_MANIFEST_PATH, collectIssues } from "../manifest.js";
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
5
4
|
/**
|
|
6
5
|
* Validate a plugin's manifest against the ADG schema and check that the
|
|
7
6
|
* directories/files it references actually exist.
|
package/dist/src/semver.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
export function parseVersion(v) {
|
|
2
2
|
const core = v.trim().replace(/^[v=]/, "").split(/[-+]/)[0] ?? "";
|
|
3
3
|
const parts = core.split(".");
|
|
4
|
-
|
|
4
|
+
// Accept partial ranges (`1`, `1.2`) by defaulting missing minor/patch to 0,
|
|
5
|
+
// matching how semver ranges are commonly written in manifests.
|
|
6
|
+
if (parts.length < 1 || parts.length > 3 || parts.some((p) => !/^\d+$/.test(p))) {
|
|
5
7
|
throw new Error(`invalid semantic version: "${v}"`);
|
|
6
8
|
}
|
|
7
|
-
return [
|
|
9
|
+
return [
|
|
10
|
+
Number(parts[0]),
|
|
11
|
+
parts[1] !== undefined ? Number(parts[1]) : 0,
|
|
12
|
+
parts[2] !== undefined ? Number(parts[2]) : 0,
|
|
13
|
+
];
|
|
8
14
|
}
|
|
9
15
|
export function compare(a, b) {
|
|
10
16
|
for (let i = 0; i < 3; i++) {
|
|
@@ -552,6 +552,13 @@ export class WellKnownProvider {
|
|
|
552
552
|
const localExtraLength = buffer.readUInt16LE(localHeaderOffset + 28);
|
|
553
553
|
const dataStart = localHeaderOffset + 30 + localFileNameLength + localExtraLength;
|
|
554
554
|
const compressed = buffer.subarray(dataStart, dataStart + compressedSize);
|
|
555
|
+
// ADG patch: reject zip bombs before decompressing. The central directory
|
|
556
|
+
// declares the uncompressed size, so check it against the running budget
|
|
557
|
+
// up front — inflateRawSync would otherwise allocate the full (potentially
|
|
558
|
+
// multi-GB) output and OOM the process before addArchiveFile's check runs.
|
|
559
|
+
if (runningTotal.bytes + uncompressedSize > MAX_ARCHIVE_UNPACKED_BYTES) {
|
|
560
|
+
throw new Error('Archive exceeds maximum unpacked size');
|
|
561
|
+
}
|
|
555
562
|
let content;
|
|
556
563
|
if (method === 0) {
|
|
557
564
|
content = compressed;
|
|
@@ -72,7 +72,18 @@ export function parseOwnerRepo(ownerRepo) {
|
|
|
72
72
|
*/
|
|
73
73
|
export async function isRepoPrivate(owner, repo) {
|
|
74
74
|
try {
|
|
75
|
-
|
|
75
|
+
// ADG patch: GitHub's API rejects requests without a User-Agent (403), and
|
|
76
|
+
// authenticating raises rate limits and reaches private repos. Read the
|
|
77
|
+
// token from env directly to keep this best-effort check silent (no gh spawn).
|
|
78
|
+
const headers = {
|
|
79
|
+
'User-Agent': 'skills-cli',
|
|
80
|
+
Accept: 'application/vnd.github.v3+json',
|
|
81
|
+
};
|
|
82
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
83
|
+
if (token) {
|
|
84
|
+
headers.Authorization = `Bearer ${token}`;
|
|
85
|
+
}
|
|
86
|
+
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, { headers });
|
|
76
87
|
// If repo doesn't exist or we don't have access, assume private to be safe
|
|
77
88
|
if (!res.ok) {
|
|
78
89
|
return null; // Unable to determine
|
|
@@ -87,23 +87,31 @@ export function buildUsePrompt(input) {
|
|
|
87
87
|
}
|
|
88
88
|
export async function materializeUseSkill(skill) {
|
|
89
89
|
const tempRoot = await mkdtemp(join(tmpdir(), 'skills-use-'));
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
// ADG patch: clean up tempRoot if any setup step fails. runUse only tracks
|
|
91
|
+
// cloneTempDir, so without this a failed materialize would leak the temp dir.
|
|
92
|
+
try {
|
|
93
|
+
const skillDir = join(tempRoot, sanitizeName(skill.directoryName || skill.name));
|
|
94
|
+
if (!isPathSafe(tempRoot, skillDir)) {
|
|
95
|
+
throw new Error('Invalid skill name: potential path traversal detected');
|
|
96
|
+
}
|
|
97
|
+
await mkdir(skillDir, { recursive: true });
|
|
98
|
+
if (skill.kind === 'blob') {
|
|
99
|
+
await writeSnapshotFiles(skillDir, skill.files);
|
|
100
|
+
}
|
|
101
|
+
else if (skill.kind === 'well-known') {
|
|
102
|
+
await writeMapFiles(skillDir, skill.files);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
await copySkillDirectory(skill.path, skillDir);
|
|
106
|
+
}
|
|
107
|
+
const skillMd = skill.rawContent ?? (await readFile(join(skillDir, 'SKILL.md'), 'utf-8'));
|
|
108
|
+
const hasSupportingFiles = await containsSupportingFiles(skillDir, skillDir);
|
|
109
|
+
return { tempRoot, skillDir, skillMd, hasSupportingFiles };
|
|
100
110
|
}
|
|
101
|
-
|
|
102
|
-
await
|
|
111
|
+
catch (error) {
|
|
112
|
+
await cleanupTempDir(tempRoot).catch(() => { });
|
|
113
|
+
throw error;
|
|
103
114
|
}
|
|
104
|
-
const skillMd = skill.rawContent ?? (await readFile(join(skillDir, 'SKILL.md'), 'utf-8'));
|
|
105
|
-
const hasSupportingFiles = await containsSupportingFiles(skillDir, skillDir);
|
|
106
|
-
return { tempRoot, skillDir, skillMd, hasSupportingFiles };
|
|
107
115
|
}
|
|
108
116
|
export async function runUse(sourceArgs, options = {}, parseErrors = []) {
|
|
109
117
|
let cloneTempDir = null;
|
package/package.json
CHANGED
|
@@ -725,6 +725,14 @@ export class WellKnownProvider implements HostProvider {
|
|
|
725
725
|
const dataStart = localHeaderOffset + 30 + localFileNameLength + localExtraLength;
|
|
726
726
|
const compressed = buffer.subarray(dataStart, dataStart + compressedSize);
|
|
727
727
|
|
|
728
|
+
// ADG patch: reject zip bombs before decompressing. The central directory
|
|
729
|
+
// declares the uncompressed size, so check it against the running budget
|
|
730
|
+
// up front — inflateRawSync would otherwise allocate the full (potentially
|
|
731
|
+
// multi-GB) output and OOM the process before addArchiveFile's check runs.
|
|
732
|
+
if (runningTotal.bytes + uncompressedSize > MAX_ARCHIVE_UNPACKED_BYTES) {
|
|
733
|
+
throw new Error('Archive exceeds maximum unpacked size');
|
|
734
|
+
}
|
|
735
|
+
|
|
728
736
|
let content: Buffer;
|
|
729
737
|
if (method === 0) {
|
|
730
738
|
content = compressed;
|
|
@@ -82,7 +82,18 @@ export function parseOwnerRepo(ownerRepo: string): { owner: string; repo: string
|
|
|
82
82
|
*/
|
|
83
83
|
export async function isRepoPrivate(owner: string, repo: string): Promise<boolean | null> {
|
|
84
84
|
try {
|
|
85
|
-
|
|
85
|
+
// ADG patch: GitHub's API rejects requests without a User-Agent (403), and
|
|
86
|
+
// authenticating raises rate limits and reaches private repos. Read the
|
|
87
|
+
// token from env directly to keep this best-effort check silent (no gh spawn).
|
|
88
|
+
const headers: Record<string, string> = {
|
|
89
|
+
'User-Agent': 'skills-cli',
|
|
90
|
+
Accept: 'application/vnd.github.v3+json',
|
|
91
|
+
};
|
|
92
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
93
|
+
if (token) {
|
|
94
|
+
headers.Authorization = `Bearer ${token}`;
|
|
95
|
+
}
|
|
96
|
+
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, { headers });
|
|
86
97
|
|
|
87
98
|
// If repo doesn't exist or we don't have access, assume private to be safe
|
|
88
99
|
if (!res.ok) {
|
package/vendor/skills/src/use.ts
CHANGED
|
@@ -157,26 +157,33 @@ export function buildUsePrompt(input: {
|
|
|
157
157
|
|
|
158
158
|
export async function materializeUseSkill(skill: UseSkill): Promise<MaterializedUseSkill> {
|
|
159
159
|
const tempRoot = await mkdtemp(join(tmpdir(), 'skills-use-'));
|
|
160
|
-
|
|
160
|
+
// ADG patch: clean up tempRoot if any setup step fails. runUse only tracks
|
|
161
|
+
// cloneTempDir, so without this a failed materialize would leak the temp dir.
|
|
162
|
+
try {
|
|
163
|
+
const skillDir = join(tempRoot, sanitizeName(skill.directoryName || skill.name));
|
|
161
164
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
if (!isPathSafe(tempRoot, skillDir)) {
|
|
166
|
+
throw new Error('Invalid skill name: potential path traversal detected');
|
|
167
|
+
}
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
await mkdir(skillDir, { recursive: true });
|
|
167
170
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
if (skill.kind === 'blob') {
|
|
172
|
+
await writeSnapshotFiles(skillDir, skill.files);
|
|
173
|
+
} else if (skill.kind === 'well-known') {
|
|
174
|
+
await writeMapFiles(skillDir, skill.files);
|
|
175
|
+
} else {
|
|
176
|
+
await copySkillDirectory(skill.path, skillDir);
|
|
177
|
+
}
|
|
175
178
|
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
const skillMd = skill.rawContent ?? (await readFile(join(skillDir, 'SKILL.md'), 'utf-8'));
|
|
180
|
+
const hasSupportingFiles = await containsSupportingFiles(skillDir, skillDir);
|
|
178
181
|
|
|
179
|
-
|
|
182
|
+
return { tempRoot, skillDir, skillMd, hasSupportingFiles };
|
|
183
|
+
} catch (error) {
|
|
184
|
+
await cleanupTempDir(tempRoot).catch(() => {});
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
export async function runUse(
|