@rbbtsn0w/adg 0.3.0-beta.1 → 0.3.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 CHANGED
@@ -49,7 +49,7 @@ const FLAGS = {
49
49
  dir: { type: "string", short: "d", hint: "<dir>", help: "install into an explicit directory" },
50
50
  global: { type: "boolean", short: "g", help: "use ~/.agents/plugins (across all projects)" },
51
51
  project: { type: "boolean", help: "use <repo>/.agents/plugins (default)" },
52
- target: { type: "string", short: "t", hint: "claude|codex|all", help: "runtime(s) to adapt for" },
52
+ target: { type: "string", short: "t", hint: "claude|codex|antigravity|all", help: "runtime(s) to adapt for" },
53
53
  all: { type: "boolean", short: "a", help: "select all available plugins" },
54
54
  plugin: { type: "string", short: "p", multiple: true, hint: "<name>", help: "select a specific plugin (repeatable)" },
55
55
  "no-deps": { type: "boolean", short: "n", help: "don't install dependencies" },
@@ -137,7 +137,7 @@ const PLUGIN_COMMANDS = {
137
137
  },
138
138
  link: {
139
139
  summary: "link installed plugins into a runtime",
140
- synopsis: "adg plugins link --target claude|codex",
140
+ synopsis: "adg plugins link --target claude|codex|antigravity",
141
141
  flags: ["target", ...SCOPE],
142
142
  },
143
143
  migrate: {
@@ -253,12 +253,20 @@ function reportAgents(agents, verb) {
253
253
  console.log(ui.warn(`note: \`${r.agent}\` CLI not found — run \`adg plugins link --target ${r.agent}\` after installing it.`));
254
254
  }
255
255
  }
256
+ /** Friendly `--target` aliases mapped onto canonical adapter target ids. */
257
+ const TARGET_ALIASES = {
258
+ anthropic: "claude",
259
+ openai: "codex",
260
+ agy: "antigravity",
261
+ gemini: "antigravity",
262
+ };
256
263
  function resolveTargets(target) {
257
264
  if (!target || target === "all")
258
265
  return [...ADAPTER_TARGETS];
259
- if (target === "claude" || target === "codex")
260
- return [target];
261
- fail(`invalid --target "${String(target)}" (expected claude|codex|all)`);
266
+ const t = TARGET_ALIASES[target] ?? target;
267
+ if (ADAPTER_TARGETS.includes(t))
268
+ return [t];
269
+ fail(`invalid --target "${target}" (expected ${[...ADAPTER_TARGETS, "all"].join("|")})`);
262
270
  }
263
271
  /** Parse a `--only skills,commands` list into validated component types. */
264
272
  function resolveComponents(only) {
@@ -461,9 +469,10 @@ async function runPlugins(rawVerb, rest) {
461
469
  }
462
470
  case "link": {
463
471
  const { values } = parseVerb(verb, cmd.flags, rest);
464
- const target = values.target;
465
- if (target !== "claude" && target !== "codex")
466
- fail("plugins link requires --target claude|codex");
472
+ if (values.target === undefined || values.target === "all") {
473
+ fail(`plugins link requires a single --target (${ADAPTER_TARGETS.join("|")})`);
474
+ }
475
+ const target = resolveTargets(values.target)[0]; // validates + maps aliases (agy → antigravity); always non-empty
467
476
  const res = linkPlugins({ pluginsDir: resolveScopeDir(values), target, global: Boolean(values.global) });
468
477
  for (const a of res.actions) {
469
478
  console.log(`${ui.ok("linked")} ${ui.name(a.name)} ${ui.meta(`[${res.target}]`)}${a.linkedTo ? ui.meta(` -> ${a.linkedTo}`) : ""}`);
@@ -584,7 +593,7 @@ Commands:
584
593
  adg plugins marketplace list [--verbose] [--global | --project | --dir <dir>]
585
594
  Group installed plugins by source. --verbose expands each plugin to its
586
595
  components (skills, agents, commands, …).
587
- adg plugins marketplace upgrade [<source>] [--all] [--target claude|codex|all] [--global | --project | --dir <dir>]
596
+ adg plugins marketplace upgrade [<source>] [--all] [--target claude|codex|antigravity|all] [--global | --project | --dir <dir>]
588
597
  Re-fetch a source and update its installed plugins (--all also installs
589
598
  anything new it now offers). No <source> upgrades every remote source.
590
599
  adg plugins marketplace remove <source> [--force] [--global | --project | --dir <dir>]
@@ -0,0 +1,18 @@
1
+ import { join } from "node:path";
2
+ /** Projection subdirectory that holds the self-contained agy plugin root. */
3
+ export const ANTIGRAVITY_PROJECTION_DIR = ".antigravity-plugin";
4
+ /**
5
+ * Generate an Antigravity (`agy`) plugin.json from an ADG manifest.
6
+ *
7
+ * Antigravity's manifest is minimal: it reads only `name` from a `plugin.json`
8
+ * and discovers components by convention (sibling `skills/`, `agents/`,
9
+ * `commands/`, `hooks/` directories plus a `mcp_config.json`) — all resolved
10
+ * relative to the directory handed to `agy plugin install`, with no manifest
11
+ * path indirection. We therefore project a self-contained agy plugin root under
12
+ * `.antigravity-plugin/`: this pure transform emits its `plugin.json`, while the
13
+ * agent materializes the rest (mcp_config.json + symlinked component dirs), so a
14
+ * partial-install `selection` is not expressible for this target.
15
+ */
16
+ export function toAntigravityManifest(_pluginDir, manifest, _selection) {
17
+ return { defaultPath: join(ANTIGRAVITY_PROJECTION_DIR, "plugin.json"), manifest: { name: manifest.name } };
18
+ }
@@ -1,21 +1,28 @@
1
1
  import { toAnthropicManifest } from "./anthropic.js";
2
2
  import { toCodexManifest } from "./openai.js";
3
+ import { toAntigravityManifest } from "./antigravity.js";
3
4
  export const ADAPTERS = {
4
5
  claude: toAnthropicManifest,
5
6
  anthropic: toAnthropicManifest,
6
7
  codex: toCodexManifest,
7
8
  openai: toCodexManifest,
9
+ antigravity: toAntigravityManifest,
10
+ agy: toAntigravityManifest,
11
+ gemini: toAntigravityManifest,
8
12
  };
9
- export const ADAPTER_TARGETS = ["claude", "codex"];
13
+ export const ADAPTER_TARGETS = ["claude", "codex", "antigravity"];
10
14
  /**
11
15
  * Component categories each adapter target can actually express, mirroring what
12
16
  * the adapters emit: the Claude manifest carries skills/agents/commands/hooks/mcp
13
17
  * (`toAnthropicManifest`), while Codex only consumes skills (`toCodexManifest`).
14
- * `apps` is emitted by neither, so it maps to no target. Used to derive which
15
- * agents a plugin is adaptable to from its exposed component types.
18
+ * Antigravity (`agy`) discovers the same superset as Claude via convention
19
+ * (skills/agents/commands/hooks dirs + mcp_config.json). `apps` is emitted by
20
+ * none, so it maps to no target. Used to derive which agents a plugin is
21
+ * adaptable to from its exposed component types.
16
22
  */
17
23
  export const ADAPTER_COMPONENTS = {
18
24
  claude: ["skills", "agents", "commands", "hooks", "mcp"],
19
25
  codex: ["skills"],
26
+ antigravity: ["skills", "agents", "commands", "hooks", "mcp"],
20
27
  };
21
- export { toAnthropicManifest, toCodexManifest };
28
+ export { toAnthropicManifest, toCodexManifest, toAntigravityManifest };
@@ -0,0 +1,185 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { cpSync, existsSync, rmSync, statSync, symlinkSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, relative } from "node:path";
5
+ import { ensureDir, writeJson } from "../fsutil.js";
6
+ import { readManifest } from "../manifest.js";
7
+ import { resolveSkillEntries } from "../skills.js";
8
+ import { isExposed } from "../components.js";
9
+ import { installedPluginDir, lockPath } from "../paths.js";
10
+ import { readLock } from "../lock.js";
11
+ import { ANTIGRAVITY_PROJECTION_DIR } from "../adapters/antigravity.js";
12
+ /**
13
+ * Antigravity (`agy`) agent.
14
+ *
15
+ * Antigravity discovers a plugin by convention relative to the directory handed
16
+ * to `agy plugin install` — `plugin.json` plus sibling `skills/`, `agents/`,
17
+ * `commands/`, `hooks/` dirs and a `mcp_config.json`, with no manifest path
18
+ * indirection. We therefore project a self-contained agy plugin root under
19
+ * `<store>/.antigravity-plugin/`: generated `plugin.json` + `mcp_config.json`
20
+ * (the ADG `mcp/.mcp.json` shape is exactly agy's, so it passes through) and
21
+ * *symlinks* to the real component dirs one level up, so nothing is duplicated
22
+ * on disk. agy follows these symlinks; where the platform forbids them (e.g.
23
+ * Windows without privilege) we fall back to a copy. We then drive
24
+ * `agy plugin install/uninstall` (which owns `~/.gemini/antigravity-cli`).
25
+ */
26
+ const ID = "antigravity";
27
+ /**
28
+ * Single-directory component fields: agy reads each as a sibling dir named by
29
+ * convention. `skills` is handled separately because it can be a path-list and
30
+ * supports per-skill subsetting.
31
+ */
32
+ const DIR_FIELDS = ["agents", "commands", "hooks"];
33
+ function geminiHome(env) {
34
+ return env.GEMINI_HOME?.trim() || join(homedir(), ".gemini");
35
+ }
36
+ /**
37
+ * agy's config/store home: `<GEMINI_HOME>/antigravity-cli` (defaulting to
38
+ * `~/.gemini/antigravity-cli`). Exported so the resolver itself is testable
39
+ * without depending on the host filesystem state.
40
+ */
41
+ export function antigravityHome(env = process.env) {
42
+ return join(geminiHome(env), "antigravity-cli");
43
+ }
44
+ function available() {
45
+ // `--help` is rejected by `install` (it parses it as a target), so probe the
46
+ // plugin command group with its own `help` subcommand instead.
47
+ return spawnSync("agy", ["plugin", "help"], { stdio: "ignore" }).status === 0;
48
+ }
49
+ function run(args) {
50
+ const r = spawnSync("agy", args, { encoding: "utf8" });
51
+ const ok = r.status === 0;
52
+ // Surface the CLI's own diagnostics on failure instead of swallowing them.
53
+ if (!ok && r.stderr)
54
+ console.error(r.stderr.trim());
55
+ return { ok, out: `${r.stdout ?? ""}${r.stderr ?? ""}` };
56
+ }
57
+ /** Resolve a plugin's on-disk store directory and selection from the lock's provenance. */
58
+ function pluginStore(pluginsDir, name) {
59
+ const entry = readLock(lockPath(pluginsDir)).plugins[name];
60
+ if (!entry)
61
+ return undefined;
62
+ const dir = installedPluginDir(pluginsDir, name, entry.origin);
63
+ return existsSync(dir) ? { dir, selection: entry.selection } : undefined;
64
+ }
65
+ /** First on-disk top segment of a declared component path (e.g. "./agents/" -> "agents"). */
66
+ function componentSegment(value) {
67
+ const first = Array.isArray(value) ? value[0] : value;
68
+ if (typeof first !== "string")
69
+ return undefined;
70
+ const seg = first.replace(/^\.?[/\\]/, "").split(/[/\\]/)[0];
71
+ return seg || undefined;
72
+ }
73
+ /**
74
+ * Symlink `linkPath` at `absTarget` (target stored relative so the projection
75
+ * survives the whole plugin dir being moved), copying instead where symlinks are
76
+ * unavailable (e.g. Windows without privilege). Idempotent.
77
+ */
78
+ function linkOrCopy(linkPath, absTarget) {
79
+ rmSync(linkPath, { recursive: true, force: true });
80
+ ensureDir(dirname(linkPath));
81
+ try {
82
+ symlinkSync(relative(dirname(linkPath), absTarget), linkPath, "dir");
83
+ }
84
+ catch {
85
+ cpSync(absTarget, linkPath, { recursive: true });
86
+ }
87
+ }
88
+ /**
89
+ * Build the agy-native projection under `<dir>/.antigravity-plugin/`: a
90
+ * `plugin.json` (name only), a `mcp_config.json` copied verbatim from the
91
+ * manifest's mcp file when present, and relative symlinks (copy fallback) to the
92
+ * declared component dirs named for agy's convention.
93
+ *
94
+ * An optional `selection` (the plugin's partial install) narrows what is
95
+ * projected: component categories outside it are dropped, and `skills` is pinned
96
+ * to the selected subset. `skills` is also projected per-skill into a real
97
+ * `skills/` dir, so a path-list spanning multiple roots is fully honored rather
98
+ * than collapsing to its first root. Idempotent; safe to re-run.
99
+ */
100
+ export function writeAntigravityProjection(dir, selection) {
101
+ const manifest = readManifest(dir);
102
+ const stage = join(dir, ANTIGRAVITY_PROJECTION_DIR);
103
+ ensureDir(stage);
104
+ writeJson(join(stage, "plugin.json"), { name: manifest.name });
105
+ const mcpConfig = join(stage, "mcp_config.json");
106
+ rmSync(mcpConfig, { force: true });
107
+ if (manifest.mcp && isExposed(selection, "mcp")) {
108
+ const mcpFile = join(dir, manifest.mcp);
109
+ // The ADG mcp file shape is exactly agy's `mcp_config.json`, so copy it
110
+ // verbatim — preserving formatting and avoiding a parse/re-serialize round-trip.
111
+ if (existsSync(mcpFile))
112
+ cpSync(mcpFile, mcpConfig);
113
+ }
114
+ const skillsDir = join(stage, "skills");
115
+ rmSync(skillsDir, { recursive: true, force: true });
116
+ if (manifest.skills !== undefined && isExposed(selection, "skills")) {
117
+ const pick = selection?.skills;
118
+ for (const e of resolveSkillEntries(dir, manifest)) {
119
+ if (pick && !pick.includes(e.name))
120
+ continue;
121
+ if (!e.skillMd)
122
+ continue;
123
+ const srcSkillDir = dirname(e.skillMd);
124
+ if (existsSync(srcSkillDir) && statSync(srcSkillDir).isDirectory()) {
125
+ linkOrCopy(join(skillsDir, e.name), srcSkillDir);
126
+ }
127
+ }
128
+ }
129
+ for (const field of DIR_FIELDS) {
130
+ const link = join(stage, field);
131
+ rmSync(link, { recursive: true, force: true });
132
+ if (!isExposed(selection, field))
133
+ continue;
134
+ const seg = componentSegment(manifest[field]);
135
+ if (!seg)
136
+ continue;
137
+ const srcDir = join(dir, seg);
138
+ // agy reads the component by its convention name (`field`); point that at
139
+ // the real source dir so a non-conventional source name still resolves.
140
+ if (existsSync(srcDir))
141
+ linkOrCopy(link, srcDir);
142
+ }
143
+ }
144
+ export const antigravityAgent = {
145
+ id: ID,
146
+ displayName: "Antigravity",
147
+ adaptTarget: "antigravity",
148
+ detect: (env = process.env) => existsSync(antigravityHome(env)),
149
+ available,
150
+ activate(ctx) {
151
+ if (!available())
152
+ return { agent: ID, affected: [], skipped: true };
153
+ const affected = [];
154
+ for (const p of ctx.plugins) {
155
+ // Isolate each plugin: a malformed manifest or a filesystem error must not
156
+ // abort activation of the remaining (valid) plugins.
157
+ try {
158
+ const store = pluginStore(ctx.pluginsDir, p);
159
+ if (!store)
160
+ continue;
161
+ writeAntigravityProjection(store.dir, store.selection);
162
+ if (run(["plugin", "install", join(store.dir, ANTIGRAVITY_PROJECTION_DIR)]).ok)
163
+ affected.push(p);
164
+ }
165
+ catch (err) {
166
+ console.error(`failed to enable "${p}" in Antigravity:`, err);
167
+ }
168
+ }
169
+ return { agent: ID, affected, skipped: false };
170
+ },
171
+ deactivate(ctx) {
172
+ if (!available())
173
+ return { agent: ID, affected: [], skipped: true };
174
+ const affected = [];
175
+ for (const p of ctx.plugins) {
176
+ if (run(["plugin", "uninstall", p]).ok)
177
+ affected.push(p);
178
+ }
179
+ return { agent: ID, affected, skipped: false };
180
+ },
181
+ // `agy plugin install` re-imports the source dir, so re-running it is the refresh.
182
+ refresh(ctx) {
183
+ return antigravityAgent.activate(ctx);
184
+ },
185
+ };
@@ -1,10 +1,12 @@
1
1
  import { registerAgent } from "./registry.js";
2
2
  import { claudeAgent } from "./claude.js";
3
3
  import { codexAgent } from "./codex.js";
4
+ import { antigravityAgent } from "./antigravity.js";
4
5
  // Built-in agents register on import. Third-party agents can `registerAgent()`
5
6
  // their own implementation (stage 2: discover from config without core edits).
6
7
  registerAgent(claudeAgent);
7
8
  registerAgent(codexAgent);
9
+ registerAgent(antigravityAgent);
8
10
  export * from "./types.js";
9
11
  export { registerAgent, getAgent, allAgents, detectedAgents, resolveAgents, agentsForComponents } from "./registry.js";
10
12
  export { writeClaudeCatalog } from "./claude.js";
@@ -6,7 +6,7 @@ import { fromNativeManifest } from "../adapters/reverse.js";
6
6
  import { adaptPlugin } from "./adapt.js";
7
7
  import { copyPluginDir, writeJson } from "../fsutil.js";
8
8
  import { folderHash } from "../hash.js";
9
- import { packageFilter } from "../package.js";
9
+ import { packageFilter, PROJECTION_DIRS } from "../package.js";
10
10
  import { lockPath, marketplacePath, marketplaceSourcePath, pluginDir } from "../paths.js";
11
11
  import { readLock, upsertEntry, writeLock } from "../lock.js";
12
12
  import { ADG_MANIFEST_PATH, readManifest } from "../manifest.js";
@@ -17,7 +17,8 @@ import { sameSource, COMPONENT_TYPES } from "../types.js";
17
17
  import { pluginContents, presentComponents } from "../components.js";
18
18
  import { skillDescriptionLoader } from "../skills.js";
19
19
  import { resolveAgents } from "../agents/index.js";
20
- const HASH_IGNORE = [".claude-plugin", ".codex-plugin"];
20
+ // Generated runtime projections never count toward a plugin's content hash.
21
+ const HASH_IGNORE = PROJECTION_DIRS;
21
22
  function toPosix(p) {
22
23
  return p.split("\\").join("/");
23
24
  }
@@ -1,10 +1,11 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { folderHash } from "../hash.js";
3
- import { packageFilter } from "../package.js";
3
+ import { packageFilter, PROJECTION_DIRS } from "../package.js";
4
4
  import { lockPath, installedPluginDir } from "../paths.js";
5
5
  import { readLock, writeLock } from "../lock.js";
6
6
  import { readManifest } from "../manifest.js";
7
7
  import { adaptPlugin } from "./adapt.js";
8
+ import { ADAPTER_TARGETS } from "../adapters/index.js";
8
9
  import { resolveAgents } from "../agents/index.js";
9
10
  /**
10
11
  * Re-scan installed plugins in a plugins directory, refreshing each lock
@@ -29,14 +30,16 @@ export function updateLock(pluginsDir, now = new Date().toISOString(), opts = {}
29
30
  continue;
30
31
  }
31
32
  const manifest = readManifest(dir);
32
- const hash = folderHash(dir, [".claude-plugin", ".codex-plugin"], packageFilter(manifest, { includeProjections: false }));
33
+ const hash = folderHash(dir, PROJECTION_DIRS, packageFilter(manifest, { includeProjections: false }));
33
34
  const changed = hash !== entry.folderHash || manifest.version !== entry.version;
34
35
  if (changed) {
35
36
  entry.folderHash = hash;
36
37
  entry.version = manifest.version;
37
38
  entry.updatedAt = now;
38
39
  // Regenerate runtime manifests from the updated source, honoring selection.
39
- adaptPlugin(dir, ["claude", "codex"], entry.selection);
40
+ // Covers every registered adapter target (claude/codex/antigravity) so a
41
+ // projection can't go stale after an update.
42
+ adaptPlugin(dir, [...ADAPTER_TARGETS], entry.selection);
40
43
  changedNames.push(name);
41
44
  }
42
45
  results.push({ name, changed, version: manifest.version, folderHash: hash });
@@ -11,7 +11,7 @@
11
11
  /** Top-level metadata files always shipped with a plugin, matched case-insensitively. */
12
12
  const META_RE = /^(README|LICEN[CS]E|CHANGELOG|NOTICE)(\..+)?$/i;
13
13
  /** Generated runtime projections — shipped, but excluded from the content hash. */
14
- export const PROJECTION_DIRS = [".claude-plugin", ".codex-plugin"];
14
+ export const PROJECTION_DIRS = [".claude-plugin", ".codex-plugin", ".antigravity-plugin"];
15
15
  /** Extract the first path segment of a manifest component value (e.g. "./skills/" -> "skills"). */
16
16
  function topSegment(p) {
17
17
  return p.replace(/^\.?[/\\]/, "").split(/[/\\]/)[0] ?? "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbbtsn0w/adg",
3
- "version": "0.3.0-beta.1",
3
+ "version": "0.3.0-beta.2",
4
4
  "description": "Agent Directory Group (ADG) toolkit — two domains: plugins and skills.",
5
5
  "type": "module",
6
6
  "license": "MIT",