@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.
- package/dist/bin/adg.js +703 -0
- package/dist/src/adapters/anthropic.js +54 -0
- package/dist/src/adapters/index.js +10 -0
- package/dist/src/adapters/openai.js +30 -0
- package/dist/src/adapters/reverse.js +53 -0
- package/dist/src/agents/claude.js +118 -0
- package/dist/src/agents/codex.js +61 -0
- package/{src/agents/index.ts → dist/src/agents/index.js} +6 -8
- package/dist/src/agents/registry.js +24 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/commands/adapt.js +26 -0
- package/dist/src/commands/import.js +51 -0
- package/dist/src/commands/init.js +104 -0
- package/dist/src/commands/install.js +257 -0
- package/dist/src/commands/link.js +34 -0
- package/dist/src/commands/list.js +19 -0
- package/dist/src/commands/marketplace.js +124 -0
- package/dist/src/commands/migrate.js +60 -0
- package/dist/src/commands/multiselect-skills.js +103 -0
- package/dist/src/commands/remove.js +102 -0
- package/dist/src/commands/select-agents.js +40 -0
- package/dist/src/commands/select-components.js +61 -0
- package/dist/src/commands/select-plugins.js +25 -0
- package/dist/src/commands/select-scope.js +20 -0
- package/dist/src/commands/update.js +50 -0
- package/dist/src/commands/validate.js +50 -0
- package/dist/src/components.js +90 -0
- package/dist/src/deps.js +46 -0
- package/dist/src/fsutil.js +32 -0
- package/dist/src/hash.js +51 -0
- package/dist/src/lock.js +51 -0
- package/dist/src/manifest.js +110 -0
- package/dist/src/marketplace.js +39 -0
- package/{src/package.ts → dist/src/package.js} +37 -42
- package/{src/paths.ts → dist/src/paths.js} +54 -60
- package/dist/src/semver.js +55 -0
- package/dist/src/skills.js +79 -0
- package/dist/src/sources.js +122 -0
- package/dist/src/types.js +19 -0
- package/dist/vendor/skills/package.json +143 -0
- package/dist/vendor/skills/src/add.js +1663 -0
- package/dist/vendor/skills/src/agents.js +729 -0
- package/dist/vendor/skills/src/blob.js +436 -0
- package/dist/vendor/skills/src/cli.js +340 -0
- package/dist/vendor/skills/src/constants.js +3 -0
- package/dist/vendor/skills/src/detect-agent.js +56 -0
- package/dist/vendor/skills/src/find.js +294 -0
- package/dist/vendor/skills/src/frontmatter.js +13 -0
- package/dist/vendor/skills/src/git-tree.js +32 -0
- package/dist/vendor/skills/src/git.js +235 -0
- package/dist/vendor/skills/src/install.js +75 -0
- package/dist/vendor/skills/src/installer.js +924 -0
- package/dist/vendor/skills/src/list.js +201 -0
- package/dist/vendor/skills/src/local-lock.js +109 -0
- package/dist/vendor/skills/src/plugin-manifest.js +152 -0
- package/dist/vendor/skills/src/prompts/search-multiselect.js +312 -0
- package/dist/vendor/skills/src/providers/index.js +4 -0
- package/dist/vendor/skills/src/providers/registry.js +42 -0
- package/dist/vendor/skills/src/providers/types.js +1 -0
- package/dist/vendor/skills/src/providers/wellknown.js +625 -0
- package/dist/vendor/skills/src/remove.js +263 -0
- package/dist/vendor/skills/src/sanitize.js +57 -0
- package/dist/vendor/skills/src/self-cli.js +15 -0
- package/dist/vendor/skills/src/skill-lock.js +237 -0
- package/dist/vendor/skills/src/skills.js +264 -0
- package/dist/vendor/skills/src/source-parser.js +367 -0
- package/dist/vendor/skills/src/sync.js +404 -0
- package/dist/vendor/skills/src/telemetry.js +101 -0
- package/dist/vendor/skills/src/test-utils.js +59 -0
- package/dist/vendor/skills/src/types.js +1 -0
- package/dist/vendor/skills/src/update-source.js +76 -0
- package/dist/vendor/skills/src/update.js +590 -0
- package/dist/vendor/skills/src/use.js +505 -0
- package/package.json +15 -7
- package/bin/adg.ts +0 -758
- package/src/adapters/anthropic.ts +0 -54
- package/src/adapters/index.ts +0 -24
- package/src/adapters/openai.ts +0 -37
- package/src/adapters/reverse.ts +0 -60
- package/src/agents/claude.ts +0 -124
- package/src/agents/codex.ts +0 -67
- package/src/agents/registry.ts +0 -30
- package/src/agents/types.ts +0 -47
- package/src/commands/adapt.ts +0 -36
- package/src/commands/import.ts +0 -69
- package/src/commands/init.ts +0 -146
- package/src/commands/install.ts +0 -411
- package/src/commands/link.ts +0 -61
- package/src/commands/list.ts +0 -28
- package/src/commands/marketplace.ts +0 -198
- package/src/commands/migrate.ts +0 -84
- package/src/commands/multiselect-skills.ts +0 -137
- package/src/commands/remove.ts +0 -136
- package/src/commands/select-agents.ts +0 -45
- package/src/commands/select-components.ts +0 -66
- package/src/commands/select-plugins.ts +0 -28
- package/src/commands/select-scope.ts +0 -21
- package/src/commands/update.ts +0 -85
- package/src/commands/validate.ts +0 -57
- package/src/components.ts +0 -90
- package/src/deps.ts +0 -64
- package/src/fsutil.ts +0 -38
- package/src/hash.ts +0 -61
- package/src/lock.ts +0 -57
- package/src/manifest.ts +0 -113
- package/src/marketplace.ts +0 -41
- package/src/semver.ts +0 -67
- package/src/skills.ts +0 -88
- package/src/sources.ts +0 -159
- package/src/types.ts +0 -140
package/src/commands/init.ts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { ADG_MANIFEST_PATH, ADG_MARKETPLACE_PATH } from "../manifest.ts";
|
|
4
|
-
import { writeJson, writeText } from "../fsutil.ts";
|
|
5
|
-
import { ADG_SCHEMA_VERSION, type AdgManifest, type Marketplace } from "../types.ts";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Authoring scenario — what `.agents/` artifact to scaffold. Vendor projections
|
|
9
|
-
* (.claude-plugin / .codex-plugin) are never produced here: they are a
|
|
10
|
-
* consumption/publish concern, generated at install time or via explicit
|
|
11
|
-
* `adapt`.
|
|
12
|
-
*/
|
|
13
|
-
export type InitType = "plugin" | "marketplace" | "all";
|
|
14
|
-
|
|
15
|
-
export interface InitOptions {
|
|
16
|
-
name: string;
|
|
17
|
-
dir: string;
|
|
18
|
-
description?: string;
|
|
19
|
-
author?: string;
|
|
20
|
-
skill?: string;
|
|
21
|
-
/** Which `.agents/` artifact(s) to scaffold. Default: "plugin". */
|
|
22
|
-
type?: InitType;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface InitResult {
|
|
26
|
-
/** The root directory created for this scaffold. */
|
|
27
|
-
pluginDir: string;
|
|
28
|
-
created: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Dispatch on the authoring scenario: a plugin (`.agents/.plugin.json`), a
|
|
35
|
-
* marketplace catalog (`.agents/.marketplace.json`), or `all` — a catalog root
|
|
36
|
-
* with one starter member plugin in a subdirectory.
|
|
37
|
-
*/
|
|
38
|
-
export function initScaffold(opts: InitOptions): InitResult {
|
|
39
|
-
switch (opts.type ?? "plugin") {
|
|
40
|
-
case "plugin":
|
|
41
|
-
return initPlugin(opts);
|
|
42
|
-
case "marketplace":
|
|
43
|
-
return initMarketplace(opts);
|
|
44
|
-
case "all":
|
|
45
|
-
return initAll(opts);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Scaffold a new ADG plugin directory: .agents/.plugin.json, a starter
|
|
51
|
-
* skill (SKILL.md) and README.md.
|
|
52
|
-
*/
|
|
53
|
-
export function initPlugin(opts: InitOptions): InitResult {
|
|
54
|
-
if (!NAME_RE.test(opts.name)) {
|
|
55
|
-
throw new Error(`plugin name must be kebab-case, got "${opts.name}"`);
|
|
56
|
-
}
|
|
57
|
-
const pluginDir = join(opts.dir, opts.name);
|
|
58
|
-
const manifestFile = join(pluginDir, ADG_MANIFEST_PATH);
|
|
59
|
-
if (existsSync(manifestFile)) {
|
|
60
|
-
throw new Error(`${manifestFile} already exists; refusing to overwrite`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const skillName = opts.skill ?? "getting-started";
|
|
64
|
-
const manifest: AdgManifest = {
|
|
65
|
-
schemaVersion: ADG_SCHEMA_VERSION,
|
|
66
|
-
name: opts.name,
|
|
67
|
-
version: "0.1.0",
|
|
68
|
-
description: opts.description ?? `${opts.name} plugin.`,
|
|
69
|
-
author: { name: opts.author ?? "Agent Directory Group" },
|
|
70
|
-
license: "Apache-2.0",
|
|
71
|
-
skills: "./skills/",
|
|
72
|
-
strict: true,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const created: string[] = [];
|
|
76
|
-
|
|
77
|
-
writeJson(manifestFile, manifest);
|
|
78
|
-
created.push(manifestFile);
|
|
79
|
-
|
|
80
|
-
const skillFile = join(pluginDir, "skills", skillName, "SKILL.md");
|
|
81
|
-
writeText(
|
|
82
|
-
skillFile,
|
|
83
|
-
`---\nname: ${skillName}\ndescription: Describe when this skill should trigger.\n---\n\n# ${skillName}\n\nDocument the skill's behavior here.\n`,
|
|
84
|
-
);
|
|
85
|
-
created.push(skillFile);
|
|
86
|
-
|
|
87
|
-
const readme = join(pluginDir, "README.md");
|
|
88
|
-
writeText(readme, `# ${opts.name}\n\n${manifest.description}\n`);
|
|
89
|
-
created.push(readme);
|
|
90
|
-
|
|
91
|
-
return { pluginDir, created };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Scaffold a marketplace catalog: .agents/.marketplace.json (empty member list)
|
|
96
|
-
* and README.md. Members are added later, or scaffold them with `--type all`.
|
|
97
|
-
*/
|
|
98
|
-
export function initMarketplace(opts: InitOptions): InitResult {
|
|
99
|
-
if (!NAME_RE.test(opts.name)) {
|
|
100
|
-
throw new Error(`marketplace name must be kebab-case, got "${opts.name}"`);
|
|
101
|
-
}
|
|
102
|
-
const catalogDir = join(opts.dir, opts.name);
|
|
103
|
-
const catalogFile = join(catalogDir, ADG_MARKETPLACE_PATH);
|
|
104
|
-
if (existsSync(catalogFile)) {
|
|
105
|
-
throw new Error(`${catalogFile} already exists; refusing to overwrite`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const description = opts.description ?? `${opts.name} marketplace.`;
|
|
109
|
-
const catalog: Marketplace = {
|
|
110
|
-
name: opts.name,
|
|
111
|
-
description,
|
|
112
|
-
...(opts.author ? { owner: { name: opts.author } } : {}),
|
|
113
|
-
plugins: [],
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const created: string[] = [];
|
|
117
|
-
writeJson(catalogFile, catalog);
|
|
118
|
-
created.push(catalogFile);
|
|
119
|
-
|
|
120
|
-
const readme = join(catalogDir, "README.md");
|
|
121
|
-
writeText(readme, `# ${opts.name}\n\n${description}\n`);
|
|
122
|
-
created.push(readme);
|
|
123
|
-
|
|
124
|
-
return { pluginDir: catalogDir, created };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Scaffold both: a catalog root plus one starter member plugin in a
|
|
129
|
-
* subdirectory (a plugin and a marketplace cannot share one `.agents/` dir, so
|
|
130
|
-
* the member lives under `<name>/<name>/`). The catalog lists the member.
|
|
131
|
-
*/
|
|
132
|
-
function initAll(opts: InitOptions): InitResult {
|
|
133
|
-
const market = initMarketplace(opts);
|
|
134
|
-
const member = initPlugin({ ...opts, dir: join(opts.dir, opts.name) });
|
|
135
|
-
// Link the starter member into the catalog (source relative to the catalog's
|
|
136
|
-
// grandparent, i.e. the repo root that holds `.agents/`).
|
|
137
|
-
const catalogFile = join(opts.dir, opts.name, ADG_MARKETPLACE_PATH);
|
|
138
|
-
const catalog: Marketplace = {
|
|
139
|
-
name: opts.name,
|
|
140
|
-
description: opts.description ?? `${opts.name} marketplace.`,
|
|
141
|
-
...(opts.author ? { owner: { name: opts.author } } : {}),
|
|
142
|
-
plugins: [{ name: opts.name, source: { source: "local", path: `./${opts.name}` } }],
|
|
143
|
-
};
|
|
144
|
-
writeJson(catalogFile, catalog);
|
|
145
|
-
return { pluginDir: join(opts.dir, opts.name), created: [...market.created, ...member.created] };
|
|
146
|
-
}
|
package/src/commands/install.ts
DELETED
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { basename, join, relative, resolve } from "node:path";
|
|
4
|
-
import { ADAPTER_TARGETS, type AdapterTarget } from "../adapters/index.ts";
|
|
5
|
-
import { fromNativeManifest } from "../adapters/reverse.ts";
|
|
6
|
-
import { adaptPlugin } from "./adapt.ts";
|
|
7
|
-
import { copyPluginDir, writeJson } from "../fsutil.ts";
|
|
8
|
-
import { folderHash } from "../hash.ts";
|
|
9
|
-
import { packageFilter } from "../package.ts";
|
|
10
|
-
import { lockPath, marketplacePath, marketplaceSourcePath, pluginDir } from "../paths.ts";
|
|
11
|
-
import { readLock, upsertEntry, writeLock } from "../lock.ts";
|
|
12
|
-
import { ADG_MANIFEST_PATH, readManifest } from "../manifest.ts";
|
|
13
|
-
import { readMarketplace, upsertMarketplacePlugin, writeMarketplace } from "../marketplace.ts";
|
|
14
|
-
import { resolveInstallOrder, type PluginCandidate } from "../deps.ts";
|
|
15
|
-
import { cloneGitHub, parseSource, scanNativePlugins, scanPlugins, type GitRunner } from "../sources.ts";
|
|
16
|
-
import { sameSource, COMPONENT_TYPES, type ComponentType, type LockEntry, type PluginSelection, type PluginSource } from "../types.ts";
|
|
17
|
-
import { pluginContents, presentComponents } from "../components.ts";
|
|
18
|
-
import { skillDescriptionLoader } from "../skills.ts";
|
|
19
|
-
import { resolveAgents, type Agent, type AgentScope, type AgentSyncResult } from "../agents/index.ts";
|
|
20
|
-
|
|
21
|
-
export interface InstallOneOptions {
|
|
22
|
-
/** Local directory containing the plugin (already fetched). */
|
|
23
|
-
source: string;
|
|
24
|
-
/** Destination plugins directory. */
|
|
25
|
-
pluginsDir: string;
|
|
26
|
-
/** Upstream provenance recorded in the lock; defaults to local copy-in. */
|
|
27
|
-
origin?: PluginSource;
|
|
28
|
-
marketplaceName?: string;
|
|
29
|
-
targets?: AdapterTarget[];
|
|
30
|
-
now?: string;
|
|
31
|
-
/**
|
|
32
|
-
* Partial-install selection narrowing what the generated manifests expose.
|
|
33
|
-
* When omitted, a prior lock entry's selection is reused (so it survives
|
|
34
|
-
* re-installs / upgrades); absent on both = expose everything.
|
|
35
|
-
*/
|
|
36
|
-
selection?: PluginSelection;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface InstallResult {
|
|
40
|
-
name: string;
|
|
41
|
-
installedTo: string;
|
|
42
|
-
folderHash: string;
|
|
43
|
-
adapted: string[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const HASH_IGNORE = [".claude-plugin", ".codex-plugin"];
|
|
47
|
-
|
|
48
|
-
function toPosix(p: string): string {
|
|
49
|
-
return p.split("\\").join("/");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Install a single local plugin directory into a plugins directory: copy the
|
|
54
|
-
* source, generate adapter manifests, compute the folder hash, and update both
|
|
55
|
-
* .plugin-lock.json and marketplace.json (with denormalized discovery metadata
|
|
56
|
-
* and an integrity digest).
|
|
57
|
-
*
|
|
58
|
-
* Refuses to overwrite a same-named plugin that came from a different upstream
|
|
59
|
-
* source (cross-marketplace name collision). Only files under `pluginsDir` are
|
|
60
|
-
* written — sibling files such as AGENTS.md or a global skills/ are untouched.
|
|
61
|
-
*/
|
|
62
|
-
export function installPlugin(opts: InstallOneOptions): InstallResult {
|
|
63
|
-
const source = resolve(opts.source);
|
|
64
|
-
const manifest = readManifest(source);
|
|
65
|
-
const name = manifest.name;
|
|
66
|
-
// Local installs stay flat; remote sources derive a per-marketplace dir from
|
|
67
|
-
// their origin. The default (no origin) is a flat local copy-in.
|
|
68
|
-
const origin: PluginSource =
|
|
69
|
-
opts.origin ?? { type: "local", path: `./${toPosix(name)}` };
|
|
70
|
-
const dest = pluginDir(opts.pluginsDir, name, origin);
|
|
71
|
-
|
|
72
|
-
// When the source already lives at the destination (e.g. adapting an
|
|
73
|
-
// in-repo reference plugin) skip the copy to avoid copying onto itself.
|
|
74
|
-
// Only the manifest-declared payload is copied (projections included) — dev
|
|
75
|
-
// cruft like src/ or test/ never ships.
|
|
76
|
-
if (resolve(dest) !== source) {
|
|
77
|
-
copyPluginDir(source, dest, packageFilter(manifest, { includeProjections: true }));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const lockFile = lockPath(opts.pluginsDir);
|
|
81
|
-
const lock = readLock(lockFile);
|
|
82
|
-
const prev = lock.plugins[name];
|
|
83
|
-
if (prev && !sameSource(prev.origin, origin)) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
`name collision: "${name}" is already installed from a different source ` +
|
|
86
|
-
`(${describe(prev.origin)} vs ${describe(origin)}). Rename one to avoid the conflict.`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// A new selection wins; otherwise keep whatever a prior install recorded so
|
|
91
|
-
// partial installs survive re-install / `marketplace upgrade`.
|
|
92
|
-
const selection = opts.selection ?? prev?.selection;
|
|
93
|
-
|
|
94
|
-
const targets = opts.targets ?? [...ADAPTER_TARGETS];
|
|
95
|
-
const adapted = adaptPlugin(dest, targets, selection).map((r) => r.file);
|
|
96
|
-
|
|
97
|
-
const hash = folderHash(dest, HASH_IGNORE, packageFilter(manifest, { includeProjections: false }));
|
|
98
|
-
|
|
99
|
-
const entry: Omit<LockEntry, "installedAt" | "updatedAt"> = {
|
|
100
|
-
origin,
|
|
101
|
-
version: manifest.version,
|
|
102
|
-
folderHash: hash,
|
|
103
|
-
};
|
|
104
|
-
if (manifest.dependencies?.length) {
|
|
105
|
-
entry.dependencies = Object.fromEntries(manifest.dependencies.map((d) => [d.name, d.version]));
|
|
106
|
-
}
|
|
107
|
-
if (selection) entry.selection = selection;
|
|
108
|
-
upsertEntry(lock, name, entry, opts.now);
|
|
109
|
-
writeLock(lockFile, lock);
|
|
110
|
-
|
|
111
|
-
const marketFile = marketplacePath(opts.pluginsDir);
|
|
112
|
-
const fallbackName = opts.marketplaceName ?? basename(opts.pluginsDir);
|
|
113
|
-
const market = readMarketplace(marketFile, fallbackName);
|
|
114
|
-
// marketplace.json is a pure runtime-facing export in the de-facto shape;
|
|
115
|
-
// version/integrity/provenance live in the lock, not here.
|
|
116
|
-
upsertMarketplacePlugin(market, {
|
|
117
|
-
name,
|
|
118
|
-
source: { source: "local", path: marketplaceSourcePath(opts.pluginsDir, dest) },
|
|
119
|
-
policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" },
|
|
120
|
-
...(manifest.category ? { category: manifest.category } : {}),
|
|
121
|
-
});
|
|
122
|
-
writeMarketplace(marketFile, market);
|
|
123
|
-
|
|
124
|
-
return { name, installedTo: dest, folderHash: hash, adapted };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function describe(s: PluginSource): string {
|
|
128
|
-
switch (s.type) {
|
|
129
|
-
case "local": return `local:${s.path}`;
|
|
130
|
-
case "github": return `github:${s.repo}${s.path ? `/${s.path}` : ""}`;
|
|
131
|
-
case "git": return `git:${s.url}${s.path ? `/${s.path}` : ""}`;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** One installable plugin discovered in a source, shown to an interactive picker. */
|
|
136
|
-
export interface PluginChoice {
|
|
137
|
-
name: string;
|
|
138
|
-
description: string;
|
|
139
|
-
/** True when reverse-adapted from a native Claude/Codex manifest. */
|
|
140
|
-
native: boolean;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** What an interactive component picker is shown for one plugin. */
|
|
144
|
-
export interface SelectComponentsRequest {
|
|
145
|
-
name: string;
|
|
146
|
-
/** Member names per category (skills/agents/commands/…). */
|
|
147
|
-
contents: import("../components.ts").PluginContents;
|
|
148
|
-
/** Categories the plugin actually has (non-empty). */
|
|
149
|
-
present: ComponentType[];
|
|
150
|
-
/** Lazy, cached `description` lookup for a skill (drives the on-demand toggle). */
|
|
151
|
-
skillDescription?: (name: string) => string | undefined;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export interface AddOptions {
|
|
155
|
-
/** Local path or owner/repo[@ref] / github URL. */
|
|
156
|
-
spec: string;
|
|
157
|
-
pluginsDir: string;
|
|
158
|
-
/** Override the ref parsed from the spec. */
|
|
159
|
-
ref?: string;
|
|
160
|
-
/** Restrict a GitHub checkout to these sub-paths (sparse checkout). */
|
|
161
|
-
sparse?: string[];
|
|
162
|
-
/** Injectable git clone runner (for offline testing). */
|
|
163
|
-
gitRunner?: GitRunner;
|
|
164
|
-
|
|
165
|
-
// ── selection (a source may hold one plugin or a whole marketplace) ──
|
|
166
|
-
/** Install every plugin found in the source. */
|
|
167
|
-
all?: boolean;
|
|
168
|
-
/** Install only these plugin names. */
|
|
169
|
-
plugins?: string[];
|
|
170
|
-
/** Install the single plugin at this sub-path. */
|
|
171
|
-
path?: string;
|
|
172
|
-
/** Resolve and install transitive plugin dependencies. Default true. */
|
|
173
|
-
withDeps?: boolean;
|
|
174
|
-
/**
|
|
175
|
-
* Interactive picker, used only when the source holds multiple plugins and
|
|
176
|
-
* none of all/plugins/path narrowed the selection. Returns chosen names.
|
|
177
|
-
*/
|
|
178
|
-
selectPlugins?: (choices: PluginChoice[]) => Promise<string[]> | string[];
|
|
179
|
-
|
|
180
|
-
targets?: AdapterTarget[];
|
|
181
|
-
/**
|
|
182
|
-
* Resolve adapter targets after plugins are chosen (so an interactive agent
|
|
183
|
-
* picker runs second, once the user knows what they're installing). Ignored
|
|
184
|
-
* when `targets` is set.
|
|
185
|
-
*/
|
|
186
|
-
selectTargets?: () => Promise<AdapterTarget[]> | AdapterTarget[];
|
|
187
|
-
marketplaceName?: string;
|
|
188
|
-
now?: string;
|
|
189
|
-
|
|
190
|
-
// ── partial install: narrow which component categories / skills are exposed ──
|
|
191
|
-
/** Non-interactive: expose only these component categories. */
|
|
192
|
-
only?: ComponentType[];
|
|
193
|
-
/** Non-interactive: expose only these skill names (implies skills selected). */
|
|
194
|
-
skillsSubset?: string[];
|
|
195
|
-
/**
|
|
196
|
-
* Interactive gate (the "install everything?" question). Returning false
|
|
197
|
-
* drops into per-plugin component selection. Skipped when only/skillsSubset
|
|
198
|
-
* are set. Applies only to the user-chosen plugins, not auto-deps.
|
|
199
|
-
*/
|
|
200
|
-
confirmFull?: (plugins: string[]) => Promise<boolean> | boolean;
|
|
201
|
-
/** Interactive per-plugin component picker; returns the selection to expose. */
|
|
202
|
-
selectComponents?: (req: SelectComponentsRequest) => Promise<PluginSelection> | PluginSelection;
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* After installing, make the plugins usable in the selected agents (not just
|
|
206
|
-
* recorded in the store) by enabling them via each agent's CLI. A no-op for an
|
|
207
|
-
* agent whose CLI isn't installed. Off by default (kept out of tests).
|
|
208
|
-
*/
|
|
209
|
-
activate?: boolean;
|
|
210
|
-
/** Install scope for activation; "user" (global) or "project". Default project. */
|
|
211
|
-
scope?: AgentScope;
|
|
212
|
-
/** Injection seam for tests; defaults to the agents matching `targets`. */
|
|
213
|
-
agents?: Agent[];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export interface AddResult {
|
|
217
|
-
order: string[];
|
|
218
|
-
installed: InstallResult[];
|
|
219
|
-
/** Plugins reverse-adapted from a native manifest during discovery. */
|
|
220
|
-
converted: string[];
|
|
221
|
-
/** Every plugin name discovered in the source (installed or not). */
|
|
222
|
-
available: string[];
|
|
223
|
-
/** Per-agent activation outcome (when `activate` was requested). */
|
|
224
|
-
agents?: AgentSyncResult[];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Reverse-adapt any native (Claude/Codex) manifests under `root` into
|
|
229
|
-
* `.agents/.plugin.json`, then return every ADG plugin found. After this the
|
|
230
|
-
* whole source speaks ADG, so selection and install treat all plugins uniformly.
|
|
231
|
-
*/
|
|
232
|
-
function discoverPlugins(root: string): { candidates: Map<string, PluginCandidate>; converted: string[] } {
|
|
233
|
-
const converted: string[] = [];
|
|
234
|
-
for (const native of scanNativePlugins(root)) {
|
|
235
|
-
if (native.kind === "adg") continue;
|
|
236
|
-
const raw = JSON.parse(readFileSync(native.manifestFile, "utf8"));
|
|
237
|
-
const manifest = fromNativeManifest(raw, native.kind);
|
|
238
|
-
writeJson(join(native.dir, ADG_MANIFEST_PATH), manifest);
|
|
239
|
-
converted.push(manifest.name);
|
|
240
|
-
}
|
|
241
|
-
return { candidates: scanPlugins(root), converted };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** Resolve which discovered plugins to install from the selection options. */
|
|
245
|
-
async function selectPluginNames(
|
|
246
|
-
opts: AddOptions,
|
|
247
|
-
candidates: Map<string, PluginCandidate>,
|
|
248
|
-
workRoot: string,
|
|
249
|
-
converted: string[],
|
|
250
|
-
): Promise<string[]> {
|
|
251
|
-
const names = [...candidates.keys()];
|
|
252
|
-
|
|
253
|
-
if (opts.plugins?.length) {
|
|
254
|
-
const missing = opts.plugins.filter((p) => !candidates.has(p));
|
|
255
|
-
if (missing.length) {
|
|
256
|
-
throw new Error(`plugin(s) not found in source: ${missing.join(", ")}.\nAvailable: ${names.join(", ")}`);
|
|
257
|
-
}
|
|
258
|
-
return opts.plugins;
|
|
259
|
-
}
|
|
260
|
-
if (opts.path) {
|
|
261
|
-
const target = resolve(join(workRoot, opts.path));
|
|
262
|
-
const hit = [...candidates.values()].find((c) => resolve(c.dir) === target);
|
|
263
|
-
if (!hit) throw new Error(`no plugin found at --path ${opts.path}`);
|
|
264
|
-
return [hit.manifest.name];
|
|
265
|
-
}
|
|
266
|
-
if (opts.all || candidates.size === 1) return names;
|
|
267
|
-
|
|
268
|
-
if (opts.selectPlugins) {
|
|
269
|
-
const convertedSet = new Set(converted);
|
|
270
|
-
const choices: PluginChoice[] = [...candidates.values()].map((c) => ({
|
|
271
|
-
name: c.manifest.name,
|
|
272
|
-
description: c.manifest.description,
|
|
273
|
-
native: convertedSet.has(c.manifest.name),
|
|
274
|
-
}));
|
|
275
|
-
return opts.selectPlugins(choices);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
throw new Error(
|
|
279
|
-
`source "${opts.spec}" contains ${candidates.size} plugins: ${names.join(", ")}.\n` +
|
|
280
|
-
`Pick with --plugin <name> (repeatable), --all for everything, or run in a terminal to choose interactively.`,
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Decide a partial-install selection for each user-chosen plugin.
|
|
286
|
-
*
|
|
287
|
-
* Precedence: explicit flags (--only / --skill) win and apply to every chosen
|
|
288
|
-
* plugin; otherwise an interactive gate asks whether to install in full, and if
|
|
289
|
-
* not, a per-plugin component picker runs (skipped for plugins with nothing
|
|
290
|
-
* meaningful to choose). No selection for a plugin = expose everything.
|
|
291
|
-
*/
|
|
292
|
-
async function resolveSelections(
|
|
293
|
-
opts: AddOptions,
|
|
294
|
-
selected: string[],
|
|
295
|
-
candidates: Map<string, PluginCandidate>,
|
|
296
|
-
): Promise<Map<string, PluginSelection>> {
|
|
297
|
-
const selections = new Map<string, PluginSelection>();
|
|
298
|
-
|
|
299
|
-
if (opts.only || opts.skillsSubset) {
|
|
300
|
-
const flagSelection: PluginSelection = {
|
|
301
|
-
components: opts.only ?? [...COMPONENT_TYPES],
|
|
302
|
-
...(opts.skillsSubset ? { skills: opts.skillsSubset } : {}),
|
|
303
|
-
};
|
|
304
|
-
for (const name of selected) selections.set(name, flagSelection);
|
|
305
|
-
return selections;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (!opts.confirmFull || !opts.selectComponents) return selections; // non-interactive default: full
|
|
309
|
-
if (await opts.confirmFull(selected)) return selections; // user kept everything
|
|
310
|
-
|
|
311
|
-
for (const name of selected) {
|
|
312
|
-
const cand = candidates.get(name)!;
|
|
313
|
-
const contents = pluginContents(cand.dir, cand.manifest);
|
|
314
|
-
const present = presentComponents(contents);
|
|
315
|
-
// Nothing meaningful to pick: a lone category with at most one member.
|
|
316
|
-
if (present.length <= 1 && contents.skills.length <= 1) continue;
|
|
317
|
-
const skillDescription = skillDescriptionLoader(cand.dir, cand.manifest);
|
|
318
|
-
selections.set(name, await opts.selectComponents({ name, contents, present, skillDescription }));
|
|
319
|
-
}
|
|
320
|
-
return selections;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* The unified install entrypoint. Treats any source as a marketplace: clone or
|
|
325
|
-
* read it, discover every plugin (ADG plus reverse-adapted native), choose a
|
|
326
|
-
* subset (--all / --plugin / --path / sole plugin / interactive picker), then
|
|
327
|
-
* install the selection in dependency-first order.
|
|
328
|
-
*/
|
|
329
|
-
export async function addPlugins(opts: AddOptions): Promise<AddResult> {
|
|
330
|
-
const parsed = parseSource(opts.spec);
|
|
331
|
-
let workRoot: string;
|
|
332
|
-
let buildOrigin: (dir: string) => PluginSource;
|
|
333
|
-
let cleanup: (() => void) | undefined;
|
|
334
|
-
|
|
335
|
-
if (parsed.kind === "local") {
|
|
336
|
-
workRoot = resolve(parsed.dir);
|
|
337
|
-
buildOrigin = (dir) => ({ type: "local", path: `./${toPosix(relative(workRoot, dir)) || basename(dir)}` });
|
|
338
|
-
} else {
|
|
339
|
-
const ref = opts.ref ?? parsed.ref;
|
|
340
|
-
const tmp = mkdtempSync(join(tmpdir(), "adg-clone-"));
|
|
341
|
-
cleanup = () => rmSync(tmp, { recursive: true, force: true });
|
|
342
|
-
cloneGitHub({ ...parsed, ref }, tmp, { sparse: opts.sparse, runner: opts.gitRunner });
|
|
343
|
-
workRoot = tmp;
|
|
344
|
-
buildOrigin = (dir) => ({
|
|
345
|
-
type: "github",
|
|
346
|
-
repo: parsed.source,
|
|
347
|
-
...(ref ? { ref } : {}),
|
|
348
|
-
path: toPosix(relative(tmp, dir)) || ".",
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
try {
|
|
353
|
-
const { candidates, converted } = discoverPlugins(workRoot);
|
|
354
|
-
if (candidates.size === 0) {
|
|
355
|
-
throw new Error(
|
|
356
|
-
`no plugin found in "${opts.spec}" (no .agents/.plugin.json, .claude-plugin or .codex-plugin manifest).`,
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const selected = await selectPluginNames(opts, candidates, workRoot, converted);
|
|
361
|
-
if (selected.length === 0) throw new Error("no plugins selected");
|
|
362
|
-
|
|
363
|
-
// Resolve adapter targets after the plugin choice (lets a CLI agent picker
|
|
364
|
-
// run once we know what's being installed). undefined → installPlugin's all.
|
|
365
|
-
const targets = opts.targets ?? (opts.selectTargets ? await opts.selectTargets() : undefined);
|
|
366
|
-
|
|
367
|
-
// Partial-install selection per user-chosen plugin (auto-deps install full).
|
|
368
|
-
const selections = await resolveSelections(opts, selected, candidates);
|
|
369
|
-
|
|
370
|
-
// Dependency-first order across every selected plugin (chains deduped).
|
|
371
|
-
const order: string[] = [];
|
|
372
|
-
const seen = new Set<string>();
|
|
373
|
-
for (const name of selected) {
|
|
374
|
-
const chain = opts.withDeps === false ? [name] : resolveInstallOrder(name, candidates);
|
|
375
|
-
for (const n of chain) {
|
|
376
|
-
if (!seen.has(n)) {
|
|
377
|
-
seen.add(n);
|
|
378
|
-
order.push(n);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const installed: InstallResult[] = [];
|
|
384
|
-
for (const name of order) {
|
|
385
|
-
const candidate = candidates.get(name)!;
|
|
386
|
-
installed.push(
|
|
387
|
-
installPlugin({
|
|
388
|
-
source: candidate.dir,
|
|
389
|
-
pluginsDir: opts.pluginsDir,
|
|
390
|
-
origin: buildOrigin(candidate.dir),
|
|
391
|
-
marketplaceName: opts.marketplaceName,
|
|
392
|
-
targets,
|
|
393
|
-
selection: selections.get(name),
|
|
394
|
-
now: opts.now,
|
|
395
|
-
}),
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
// Activate into the selected agents so the plugins are actually usable, not
|
|
399
|
-
// just recorded/discoverable — each agent enables them via its own CLI.
|
|
400
|
-
// undefined targets = all registered agents.
|
|
401
|
-
let agents: AgentSyncResult[] | undefined;
|
|
402
|
-
if (opts.activate) {
|
|
403
|
-
const ctx = { pluginsDir: opts.pluginsDir, plugins: installed.map((r) => r.name), scope: opts.scope ?? "project" };
|
|
404
|
-
agents = (opts.agents ?? resolveAgents(targets)).map((a) => a.activate(ctx));
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return { order, installed, converted, available: [...candidates.keys()], agents };
|
|
408
|
-
} finally {
|
|
409
|
-
cleanup?.();
|
|
410
|
-
}
|
|
411
|
-
}
|
package/src/commands/link.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { adaptPlugin } from "./adapt.ts";
|
|
3
|
-
import { listPlugins } from "./list.ts";
|
|
4
|
-
import { installedPluginDir } from "../paths.ts";
|
|
5
|
-
import { getAgent, type Agent } from "../agents/index.ts";
|
|
6
|
-
|
|
7
|
-
export type LinkTarget = "claude" | "codex";
|
|
8
|
-
|
|
9
|
-
export interface LinkOptions {
|
|
10
|
-
/** Source plugins directory (where installed plugins live). */
|
|
11
|
-
pluginsDir: string;
|
|
12
|
-
target: LinkTarget;
|
|
13
|
-
/** Use the user (global) scope instead of project scope. */
|
|
14
|
-
global?: boolean;
|
|
15
|
-
/** Injection seam for tests; defaults to the registered agent for `target`. */
|
|
16
|
-
agent?: Agent;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface LinkAction {
|
|
20
|
-
name: string;
|
|
21
|
-
/** Adapter manifest(s) (re)generated. */
|
|
22
|
-
adapted: string[];
|
|
23
|
-
/** The agent the plugin was enabled in, if activation succeeded. */
|
|
24
|
-
linkedTo?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface LinkResult {
|
|
28
|
-
target: LinkTarget;
|
|
29
|
-
actions: LinkAction[];
|
|
30
|
-
/** True when the target agent's CLI was missing (manifests written, nothing enabled). */
|
|
31
|
-
cliSkipped?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Project installed plugins into an agent: (re)generate that agent's manifest
|
|
36
|
-
* from each plugin (honoring its partial-install selection), then enable the
|
|
37
|
-
* plugins through the agent's CLI. The pure manifest transform comes from
|
|
38
|
-
* `ADAPTERS[agent.adaptTarget]`; the enable step is the agent's `activate`.
|
|
39
|
-
*/
|
|
40
|
-
export function linkPlugins(opts: LinkOptions): LinkResult {
|
|
41
|
-
const agent = opts.agent ?? getAgent(opts.target);
|
|
42
|
-
const adaptTarget = agent?.adaptTarget ?? opts.target;
|
|
43
|
-
|
|
44
|
-
const actions: LinkAction[] = [];
|
|
45
|
-
for (const p of listPlugins(opts.pluginsDir)) {
|
|
46
|
-
const dir = installedPluginDir(opts.pluginsDir, p.name, p.origin);
|
|
47
|
-
if (!existsSync(dir)) continue;
|
|
48
|
-
const adapted = adaptPlugin(dir, [adaptTarget], p.selection).map((r) => r.file);
|
|
49
|
-
actions.push({ name: p.name, adapted });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!agent) return { target: opts.target, actions };
|
|
53
|
-
|
|
54
|
-
const res = agent.activate({
|
|
55
|
-
pluginsDir: opts.pluginsDir,
|
|
56
|
-
plugins: actions.map((a) => a.name),
|
|
57
|
-
scope: opts.global ? "user" : "project",
|
|
58
|
-
});
|
|
59
|
-
for (const a of actions) if (res.affected.includes(a.name)) a.linkedTo = agent.displayName;
|
|
60
|
-
return { target: opts.target, actions, cliSkipped: res.skipped };
|
|
61
|
-
}
|
package/src/commands/list.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { installedPluginDir, lockPath } from "../paths.ts";
|
|
2
|
-
import { readLock } from "../lock.ts";
|
|
3
|
-
import { readManifest } from "../manifest.ts";
|
|
4
|
-
import { exposedContents, pluginContents, type PluginContents } from "../components.ts";
|
|
5
|
-
import type { LockEntry } from "../types.ts";
|
|
6
|
-
|
|
7
|
-
export type { PluginContents };
|
|
8
|
-
|
|
9
|
-
export interface ListedPlugin extends LockEntry {
|
|
10
|
-
name: string;
|
|
11
|
-
/** What the installed plugin exposes (honoring any partial-install selection). */
|
|
12
|
-
contents?: PluginContents;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** List plugins recorded in a plugins directory's .plugin-lock.json. */
|
|
16
|
-
export function listPlugins(pluginsDir: string): ListedPlugin[] {
|
|
17
|
-
const lock = readLock(lockPath(pluginsDir));
|
|
18
|
-
return Object.entries(lock.plugins).map(([name, entry]) => {
|
|
19
|
-
const dir = installedPluginDir(pluginsDir, name, entry.origin);
|
|
20
|
-
let contents: PluginContents | undefined;
|
|
21
|
-
try {
|
|
22
|
-
contents = exposedContents(pluginContents(dir, readManifest(dir)), entry.selection);
|
|
23
|
-
} catch {
|
|
24
|
-
contents = undefined; // no/invalid manifest on disk — show provenance only
|
|
25
|
-
}
|
|
26
|
-
return { name, ...entry, contents };
|
|
27
|
-
});
|
|
28
|
-
}
|