Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Parallel",
"version": "0.10.0",
"description": "Search the live web and fetch page content with Parallel's hosted MCP server. Free access with no Parallel account, API key, or local CLI required.",
"logo": "icon.svg",
"logo": "assets/antigravity.svg",
"suggestedPrompts": [
"Use Parallel to find the upgrade guide for this project's main framework and tell me what we'd need to change.",
"Use Parallel to find official docs and examples for the feature we're building. Match the versions in this project.",
Expand All @@ -17,5 +17,10 @@
"homepage": "https://docs.parallel.ai/search/search-mcp",
"repository": "https://github.com/parallel-web/parallel-agent-skills",
"license": "MIT",
"keywords": ["parallel", "web-search", "page-extraction", "mcp"]
"keywords": [
"parallel",
"web-search",
"page-extraction",
"mcp"
]
}
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,20 @@ Then run the setup skill to install/auth the CLI:

### Google Antigravity

Install the standalone Search MCP plugin with the Antigravity CLI:
Install the Search MCP plugin from the `parallel-antigravity-<version>.zip`
[release asset](https://github.com/parallel-web/parallel-agent-skills/releases).
Extract the archive, then run:

```bash
git clone https://github.com/parallel-web/parallel-agent-skills.git
cd parallel-agent-skills
agy plugin install ./plugins/antigravity/parallel
agy plugin install ./parallel
```

This package provides free web search and page fetching without a Parallel account,
API key, or local Parallel CLI. Use the package directory above so Antigravity
loads only the Search MCP connection. See the [Antigravity package README](plugins/antigravity/parallel/README.md)
for project-local installation, validation, example prompts, and removal.
API key, or local Parallel CLI. The manifest lives at
`.antigravity-plugin/plugin.json`; the release archive includes only the Search MCP
files. Installing the full repository also discovers its separate CLI skills.
See the [Antigravity guide](docs/antigravity.md) for building the package locally,
validation, example prompts, and removal.

## CDN and discovery

Expand Down
File renamed without changes
56 changes: 40 additions & 16 deletions plugins/antigravity/parallel/README.md → docs/antigravity.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,53 @@ Anonymous access is subject to free-tier rate limits.

## Install

With the [Antigravity CLI](https://antigravity.google/docs/cli/install) available,
clone the repository and install **this package directory**:
Download `parallel-antigravity-<version>.zip` from the repository's
[releases](https://github.com/parallel-web/parallel-agent-skills/releases), extract
it, and use the [Antigravity CLI](https://antigravity.google/docs/cli/plugins/):

```bash
git clone https://github.com/parallel-web/parallel-agent-skills.git
cd parallel-agent-skills
agy plugin validate ./plugins/antigravity/parallel --strict
agy plugin install ./plugins/antigravity/parallel
agy plugin validate ./parallel --strict
agy plugin install ./parallel
agy plugin list --json
```

The installed plugin is named `parallel`. Its component list should contain one
MCP server and no skills, agents, rules, or hooks. Antigravity may prefix the
server name with `parallel`.

For a project-local installation in the Antigravity app or IDE, copy this entire
directory to `.agents/plugins/parallel/` in the target project. Keep `plugin.json`
and `mcp_config.json` at that directory's root, together with the logo and README.
For a project-local installation in the Antigravity app or IDE, copy the extracted
`parallel/` directory to `.agents/plugins/parallel/` in the target project. Keep
`.antigravity-plugin/plugin.json`, `mcp_config.json`, and `assets/antigravity.svg`
in their relative locations. Only the manifest belongs inside
`.antigravity-plugin/`; component and logo paths resolve from the plugin root.
See [Google's plugin documentation](https://antigravity.google/docs/plugins).

### Build from source

```bash
git clone https://github.com/parallel-web/parallel-agent-skills.git
cd parallel-agent-skills
pnpm install --frozen-lockfile
pnpm build:archives --version "$(cat VERSION)" --output release-assets
unzip "release-assets/parallel-antigravity-$(cat VERSION).zip" -d release-assets/unpacked
agy plugin validate ./release-assets/unpacked/parallel --strict
agy plugin install ./release-assets/unpacked/parallel
```

The source manifest is at `.antigravity-plugin/plugin.json` in the repository
root. The archive uses that same manifest and includes only the files needed
for Search MCP. Install the extracted archive to avoid loading this repository's
separate CLI skills and agent.

### Repository URL installation

Direct installation of the repository is not yet verified for Search-only use.
Antigravity CLI 1.4.2 discovers all 14 root skills even when the manifest specifies
an empty or alternate skills directory. Its validator also rejects the existing
Claude agent's `tools` frontmatter. Moving the manifest alone does not isolate
these components. Use the release archive until the publishing client supports
selecting only the Search MCP package files.

If you already enabled Parallel through the MCP store or a manual connection,
keep just one Parallel connection enabled while testing to avoid duplicate tools.
Installing this plugin does not remove or change another connection.
Expand Down Expand Up @@ -66,13 +93,10 @@ The repository's existing release process includes
The CLI and marketplace release versions can differ, so repeat the installation
and conversation checks on the build used for publication.

For marketplace submission, use `plugins/antigravity/parallel` or the extracted
`parallel/` directory as the plugin root. The repository root also contains
separate CLI skills and other client packages. A GitHub directory link is a
source reference; the installation commands above use a local directory.
Confirm with the publisher how they accept a package from a repository
subdirectory before submitting a Git URL.

For marketplace submission, the repository-root manifest is
`.antigravity-plugin/plugin.json`. The Search-only distributable is the extracted
`parallel/` release directory. Repository URL installation needs the component
isolation issue above resolved before it can replace the archive.
To remove a CLI-installed copy:

```bash
Expand Down
File renamed without changes.
21 changes: 0 additions & 21 deletions plugins/antigravity/parallel/LICENSE

This file was deleted.

2 changes: 1 addition & 1 deletion scripts/open-release-pr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const FILES_TO_COMMIT = [
".claude-plugin/plugin.json",
".claude-plugin/marketplace.json",
".codex-plugin/plugin.json",
"plugins/antigravity/parallel/plugin.json",
".antigravity-plugin/plugin.json",
];
const PR_BODY = `## Release checklist
- [ ] Review version bumps and changelog-relevant changes
Expand Down
39 changes: 22 additions & 17 deletions scripts/site-lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
export const REPO_ROOT = resolve(__dirname, "..");
export const SKILLS_DIR = join(REPO_ROOT, "skills");
export const VERSION_FILE = join(REPO_ROOT, "VERSION");
const ANTIGRAVITY_PLUGIN_DIR = join(REPO_ROOT, "plugins", "antigravity", "parallel");
const ANTIGRAVITY_MANIFEST = join(REPO_ROOT, ".antigravity-plugin", "plugin.json");
export const SITE_URL = "https://skills.parallel.ai";
export const REPOSITORY = "https://github.com/parallel-web/parallel-agent-skills";
export const OWNER = "parallel-web";
Expand Down Expand Up @@ -422,27 +422,33 @@ export async function buildArchives(outputDirInput: string, version: string): Pr
throw new Error(`Version must be semver, got: ${version}`);
}

const pluginManifest = JSON.parse(readFileSync(join(ANTIGRAVITY_PLUGIN_DIR, "plugin.json"), "utf8"));
const pluginManifest = JSON.parse(readFileSync(ANTIGRAVITY_MANIFEST, "utf8"));
if (pluginManifest.version !== version.replace(/^v/, "")) {
throw new Error(`Antigravity plugin version ${pluginManifest.version} does not match archive version ${version}`);
}

const packages = collectSkills().map((skill) => {
const skillDir = join(SKILLS_DIR, skill.directory);
return { filename: `${skill.name}-${version}.zip`, directory: skillDir, prefix: skill.name, files: walkFiles(skillDir) };
return {
filename: `${skill.name}-${version}.zip`,
files: walkFiles(skillDir).map((path) => ({
path, name: `${skill.name}/${relative(skillDir, path).replaceAll("\\", "/")}`,
})),
};
});
// Keep the standalone package independent of root skills and authoring files.
const pluginFiles = ["plugin.json", "mcp_config.json", "README.md", "LICENSE", "icon.svg"]
.map((name) => join(ANTIGRAVITY_PLUGIN_DIR, name));
for (const path of pluginFiles) {
// Package only Search MCP files, preserving paths relative to the plugin root.
// The repository also contains CLI skills that must not enter this archive.
const pluginFiles = [
[".antigravity-plugin/plugin.json", ".antigravity-plugin/plugin.json"],
["mcp_config.json", "mcp_config.json"],
["assets/antigravity.svg", "assets/antigravity.svg"],
["docs/antigravity.md", "README.md"],
["LICENSE", "LICENSE"],
].map(([source, destination]) => ({ path: join(REPO_ROOT, source!), name: `parallel/${destination}` }));
for (const { path } of pluginFiles) {
if (!statSync(path).isFile()) throw new Error(`Missing Antigravity package file: ${path}`);
}
packages.push({
filename: `parallel-antigravity-${version}.zip`,
directory: ANTIGRAVITY_PLUGIN_DIR,
prefix: "parallel",
files: pluginFiles,
});
packages.push({ filename: `parallel-antigravity-${version}.zip`, files: pluginFiles });

const outputDir = resolve(outputDirInput);
ensureCleanDirectory(outputDir);
Expand All @@ -459,9 +465,8 @@ export async function buildArchives(outputDirInput: string, version: string): Pr
archive.on("error", rejectPromise);

archive.pipe(output);
for (const filePath of pkg.files) {
const arcname = `${pkg.prefix}/${relative(pkg.directory, filePath).replaceAll("\\", "/")}`;
archive.file(filePath, { name: arcname });
for (const { path, name } of pkg.files) {
archive.file(path, { name });
}
void archive.finalize();
});
Expand Down Expand Up @@ -509,7 +514,7 @@ export function bumpVersion(part: "major" | "minor" | "patch"): string {
codexPlugin.version = newVersion;
writeJsonFile(codexPluginPath, codexPlugin);

const antigravityPluginPath = join(ANTIGRAVITY_PLUGIN_DIR, "plugin.json");
const antigravityPluginPath = ANTIGRAVITY_MANIFEST;
const antigravityPlugin = JSON.parse(readFileSync(antigravityPluginPath, "utf8")) as Record<string, unknown>;
antigravityPlugin.version = newVersion;
writeJsonFile(antigravityPluginPath, antigravityPlugin);
Expand Down
41 changes: 25 additions & 16 deletions tests/test_antigravity_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,25 @@


REPO_ROOT = Path(__file__).resolve().parents[1]
PLUGIN_PATH = Path("plugins/antigravity/parallel")
PACKAGE_FILES = {"plugin.json", "mcp_config.json", "README.md", "LICENSE", "icon.svg"}
MANIFEST_PATH = Path(".antigravity-plugin/plugin.json")
PACKAGE_FILES = {
".antigravity-plugin/plugin.json": ".antigravity-plugin/plugin.json",
"mcp_config.json": "mcp_config.json",
"assets/antigravity.svg": "assets/antigravity.svg",
"README.md": "docs/antigravity.md",
"LICENSE": "LICENSE",
}


class AntigravityPluginTestCase(unittest.TestCase):
def test_package_is_self_contained_and_exposes_only_anonymous_search(self):
plugin = REPO_ROOT / PLUGIN_PATH
self.assertTrue(plugin.is_dir(), "the standalone Antigravity package is missing")
self.assertEqual(PACKAGE_FILES, {p.name for p in plugin.iterdir()})
self.assertFalse(any(p.is_symlink() for p in plugin.rglob("*")))

manifest = json.loads((plugin / "plugin.json").read_text())
self.assertEqual(plugin.name, manifest["name"])
def test_root_manifest_resolves_search_config_and_logo_from_repository_root(self):
plugin = REPO_ROOT
self.assertEqual({"plugin.json"}, {p.name for p in (plugin / ".antigravity-plugin").iterdir()})
self.assertFalse((plugin / "plugins/antigravity/parallel").exists())
self.assertFalse(any((plugin / source).is_symlink() for source in PACKAGE_FILES.values()))

manifest = json.loads((plugin / MANIFEST_PATH).read_text())
self.assertEqual("parallel", manifest["name"])
self.assertEqual((REPO_ROOT / "VERSION").read_text().strip(), manifest["version"])
for field in ("displayName", "description", "author", "suggestedPrompts"):
self.assertTrue(manifest[field], field)
Expand Down Expand Up @@ -59,7 +65,6 @@ def test_package_is_self_contained_and_exposes_only_anonymous_search(self):
)
# An exact native connection shape also excludes credential prompts,
# auth headers, local subprocesses, and the legacy url transport field.
self.assertEqual((REPO_ROOT / "LICENSE").read_bytes(), (plugin / "LICENSE").read_bytes())


class AntigravityReleaseTestCase(unittest.TestCase):
Expand Down Expand Up @@ -89,8 +94,7 @@ def run_script(self, script, *args, **kwargs):
return self.run_command("node", "node_modules/tsx/dist/cli.mjs", script, *args, **kwargs)

def test_archives_preserve_skills_and_ship_only_installable_plugin_files(self):
plugin = self.repo / PLUGIN_PATH
self.assertTrue(plugin.is_dir(), "the standalone Antigravity package is missing")
plugin = self.repo
# Unrelated authoring files must never enter a distributed package.
(plugin / "private-notes.txt").write_text("must not ship\n")
self.run_script("scripts/build.ts", "archives", "--version", self.version, "--output", "release-assets")
Expand All @@ -111,8 +115,13 @@ def test_archives_preserve_skills_and_ship_only_installable_plugin_files(self):
self.assertEqual({f"parallel/{p}" for p in PACKAGE_FILES}, set(archive.namelist()))
archive.extractall(self.root / "unpacked")
installed = self.root / "unpacked/parallel"
for name in PACKAGE_FILES:
self.assertEqual((plugin / name).read_bytes(), (installed / name).read_bytes())
for name, source in PACKAGE_FILES.items():
self.assertEqual((plugin / source).read_bytes(), (installed / name).read_bytes())
manifest = json.loads((installed / MANIFEST_PATH).read_text())
self.assertTrue((installed / manifest["logo"]).is_file())
self.assertEqual({"plugin.json"}, {p.name for p in (installed / ".antigravity-plugin").iterdir()})
for component in ("skills", "agents", "rules", "hooks.json"):
self.assertFalse((installed / component).exists())

def test_mismatched_archive_version_preserves_existing_output(self):
output = self.repo / "release-assets"
Expand Down Expand Up @@ -162,7 +171,7 @@ def test_release_pr_commits_every_version_update(self):
major, minor, patch = self.version.split(".")
expected = f"{major}.{minor}.{int(patch) + 1}"
self.assertEqual(expected, (self.repo / "VERSION").read_text().strip())
for path in (".claude-plugin/plugin.json", ".codex-plugin/plugin.json", PLUGIN_PATH / "plugin.json"):
for path in (".claude-plugin/plugin.json", ".codex-plugin/plugin.json", MANIFEST_PATH):
committed = self.run_command("git", "show", f"HEAD:{path}").stdout
self.assertEqual(expected, json.loads(committed)["version"], str(path))
marketplace = json.loads(self.run_command("git", "show", "HEAD:.claude-plugin/marketplace.json").stdout)
Expand Down
Loading