Skip to content
Merged
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
42 changes: 42 additions & 0 deletions .github/workflows/gemini-extension.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Gemini extension branch
# Gemini CLI installs a GitHub repository only from its root, so plugins/ditto is
# published unchanged as the root of the gemini-extension branch:
# gemini extensions install https://github.com/ditto-assistant/ditto-plugins --ref gemini-extension
# Fast-forward only; nothing is force-pushed, released or submitted.
on:
push:
branches: [main]
paths:
- 'plugins/ditto/**'
- 'scripts/publish_extension_ref.py'
- '.github/workflows/gemini-extension.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: gemini-extension-branch
cancel-in-progress: false
jobs:
publish:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
Comment thread
ditto-code[bot] marked this conversation as resolved.
Resolved
timeout-minutes: 15
permissions:
contents: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: '3.12'
- name: Verify the package before publishing it
run: |
python3 scripts/shared_skills.py verify --host plugins --repo .
python3 -m unittest discover -s tests
python3 scripts/validate_package.py
- name: Publish plugins/ditto to the gemini-extension branch
env:
GIT_AUTHOR_NAME: github-actions[bot]
GIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
GIT_COMMITTER_NAME: github-actions[bot]
GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
run: python3 scripts/publish_extension_ref.py --source "$GITHUB_SHA" --branch gemini-extension
2 changes: 1 addition & 1 deletion .kimi-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ditto",
"version": "0.3.7",
"version": "0.3.8",
"author": {
"name": "Omni Aura LLC",
"email": "support@heyditto.ai",
Expand Down
11 changes: 8 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,25 @@ host. Every root marketplace lists the same plugins, and
skill needs this repository's host-connection adapter appended to it.
2. **Scaffold `plugins/<name>/`** here by copying `plugins/ditto/` without
`skills/` and `shared-skills.lock.json`. Then:
- set `name` to `<name>` in all seven host manifests (`.claude-plugin`,
- set `name` to `<name>` in all eight host manifests (`.claude-plugin`,
`.cursor-plugin`, `.grok-plugin`, `.codex-plugin`, `.github/plugin`,
`.antigravity-plugin`, `.kimi-plugin`), and give them one shared `version`.
`.antigravity-plugin`, `.kimi-plugin`, `gemini-extension.json`), and give them
one shared `version`.
- write host-tailored descriptions, keywords and display names. For example,
"Sign in through Cursor" goes in the Cursor manifest, and Grok keywords must be
brand-scoped because the Grok catalog uses them for its call-to-action.
- put assets in `assets/` and reference them with relative `./assets/...` paths.
- keep the Agent Plugins `$schema` in `.antigravity-plugin/plugin.json` and leave
out its `skills` field; `agy plugin validate --strict` needs both.
- rename the MCP server key to `<name>` in `.mcp.json`, `mcp.json`,
`mcp_config.json` and the inline Copilot and Kimi entries.
`mcp_config.json` and the inline Copilot, Kimi and Gemini entries.
- Kimi Code installs a GitHub URL only from the repository root, and the root
`.kimi-plugin/plugin.json` serves the `ditto` plugin. Other plugins install in
Kimi from a clone of `plugins/<name>`.
- Gemini CLI installs from GitHub only through the `gemini-extension` branch, which
`.github/workflows/gemini-extension.yml` publishes from `plugins/ditto`. Another
plugin needs its own branch (`scripts/publish_extension_ref.py --plugin <name>
--branch <branch>`) or installs from a clone.
3. **Allow its endpoint.** Add `<name>: <https MCP URL>` to `HOSTED_MCP` in
`scripts/validate_package.py`. Only hosted HTTP servers are allowed: no
commands, headers, env or credentials.
Expand Down
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ own OAuth flow: no API keys, npm helpers, CLI bootstrap or lifecycle hooks.
| Codex | `.agents/plugins/marketplace.json` | `codex plugin marketplace add ditto-assistant/ditto-plugins` then `codex plugin add ditto@ditto-plugins` (or install from `/plugins`) |
| GitHub Copilot CLI / VS Code | `.github/plugin/marketplace.json` | `copilot plugin marketplace add ditto-assistant/ditto-plugins` then `copilot plugin install ditto@ditto-plugins` |
| Antigravity | `plugins/ditto/.antigravity-plugin/` | `agy plugin install https://github.com/ditto-assistant/ditto-plugins`, or `/plugin install ./plugins/ditto` from a clone |
| Gemini CLI | `plugins/ditto/gemini-extension.json` (published to the `gemini-extension` branch) | `gemini extensions install https://github.com/ditto-assistant/ditto-plugins --ref gemini-extension`, or `gemini extensions install ./plugins/ditto` from a clone |
| Kimi Code | `.kimi-plugin/plugin.json` (root, points into `plugins/ditto`) | `/plugins install https://github.com/ditto-assistant/ditto-plugins` in Kimi Code, or install `plugins/ditto` from a clone |

### Claude
Expand Down Expand Up @@ -127,6 +128,24 @@ rebased, and the validator keeps the two identical. To pin a branch or tag, use
`https://github.com/ditto-assistant/ditto-plugins/tree/<ref>`. From a clone, install
`./plugins/ditto` instead.

### Gemini CLI

```sh
gemini extensions install https://github.com/ditto-assistant/ditto-plugins --ref gemini-extension
```

Gemini CLI installs a GitHub repository only from its root (`gemini-extension.json`
at the top, skills in `skills/`) and has no subdirectory option. A root manifest here
would also make Antigravity install the whole repository as a Gemini extension, so
`.github/workflows/gemini-extension.yml` publishes `plugins/ditto` unchanged as the root
of the `gemini-extension` branch after each change on `main`. Gemini first looks for a
GitHub release with that tag, finds none, and asks whether to "attempt to install via
git clone instead": answer yes. `gemini extensions list` then shows the three skills
and the `ditto` MCP server. Sign in with `/mcp auth ditto` in a Gemini CLI session; `gemini mcp
list` reports the server as Disconnected until then. From a clone, run
`gemini extensions install ./plugins/ditto` or `gemini extensions link ./plugins/ditto`.
`gemini extensions update ditto` follows the branch.

The bundled skills are the verified loading path. The hosted MCP server also
offers the canonical skill files through `skills/list`, `skills/get` and
`resources/read` for compatible importers; keep the bundle installed. See the
Expand Down Expand Up @@ -157,11 +176,12 @@ the host-labelled sections of [`adapters/ditto-memory.md`](adapters/ditto-memory
.agents/plugins/marketplace.json # Codex
.github/plugin/marketplace.json # GitHub Copilot / VS Code
.kimi-plugin/plugin.json # Kimi Code GitHub installs (ditto, skills rebased into plugins/ditto)
.github/workflows/gemini-extension.yml # publishes plugins/ditto as the root of the gemini-extension branch
adapters/ditto-memory.md # per-host connection guidance (source for the generated skill)
plugins/<name>/
.claude-plugin/plugin.json .cursor-plugin/plugin.json .grok-plugin/plugin.json
.codex-plugin/plugin.json .github/plugin/plugin.json .antigravity-plugin/plugin.json
.kimi-plugin/plugin.json
.kimi-plugin/plugin.json gemini-extension.json # Gemini CLI (httpUrl, streamable HTTP)
.mcp.json # Claude, Cursor, Grok (type http)
mcp.json # Codex; Antigravity in Agent Plugins mode (portable, streamable-http)
mcp_config.json # Antigravity native mode (serverUrl)
Expand All @@ -181,6 +201,7 @@ python3 scripts/validate_package.py
claude plugin validate ./plugins/ditto
grok plugin validate ./plugins/ditto
agy plugin validate --strict ./plugins/ditto
gemini extensions validate ./plugins/ditto
```

Source, package validation, installed plugin, authenticated MCP and actual tool
Expand Down
5 changes: 3 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ not distribute or refresh a CLI, run hooks or provision agent accounts.
generator automatically patch-bumps changed skill content; avoid a second bump.
3. Run the offline verifier, `python3 -m unittest discover -s tests -v`,
`python3 scripts/validate_package.py`, `claude plugin validate ./plugins/ditto`, and
`grok plugin validate ./plugins/ditto` and `agy plugin validate --strict ./plugins/ditto`
where those CLIs are available.
`grok plugin validate ./plugins/ditto`, `agy plugin validate --strict ./plugins/ditto` and
`gemini extensions validate ./plugins/ditto` where those CLIs are available. Merging a
change under `plugins/ditto` also republishes the `gemini-extension` branch.
4. Open a PR and inspect current-head CI and Ditto Review. Merge only when authorized.
Every host marketplace tracks main, so merging updates the plugin on all hosts.
5. Refresh the marketplace and update Ditto in the actual host. Verify the installed
Expand Down
1 change: 1 addition & 0 deletions adapters/ditto-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Use the hosted Ditto MCP server at https://api.heyditto.ai/mcp, declared by this
- **Codex and ChatGPT**: the server comes from `mcp.json`. Authenticate through the plugin's install or connection prompt; in the Codex CLI, `/mcp` shows its status.
- **GitHub Copilot and VS Code**: the server is declared in the plugin manifest. Start and sign in from the MCP servers view or the Copilot CLI's `/mcp`.
- **Antigravity**: the server comes from `mcp_config.json`. Sign in from the MCP servers panel when prompted.
- **Gemini CLI**: the server is declared in `gemini-extension.json`. Check it with `/mcp` and sign in with `/mcp auth ditto`.
- **Other hosts**: use whatever MCP connection UI the host provides for the plugin's declared server.

Discover the connected server's live tools and resources, including `memory://capabilities` when exposed. Use its MCP tools for search, full-content fetch and authorized saves or updates; do not invent tool names or assume every host exposes every capability. If the server is missing or authentication has expired, explain the gap and ask the user to connect or reconnect through the host. Never request API keys in chat or fall back to shell memory commands.
Expand Down
2 changes: 1 addition & 1 deletion plugins/ditto/.antigravity-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "ditto",
"displayName": "Ditto",
"version": "0.3.7",
"version": "0.3.8",
"description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Antigravity.",
"logo": "assets/icon.png",
"suggestedPrompts": [
Expand Down
2 changes: 1 addition & 1 deletion plugins/ditto/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
"name": "ditto",
"version": "0.3.7",
"version": "0.3.8",
"description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Claude.",
"author": {
"name": "Omni Aura LLC",
Expand Down
2 changes: 1 addition & 1 deletion plugins/ditto/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ditto",
"version": "0.3.7",
"version": "0.3.8",
"author": {
"name": "Omni Aura LLC",
"email": "support@heyditto.ai",
Expand Down
2 changes: 1 addition & 1 deletion plugins/ditto/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ditto",
"displayName": "Ditto",
"description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Cursor.",
"version": "0.3.7",
"version": "0.3.8",
"author": {
"name": "Omni Aura LLC",
"email": "support@heyditto.ai"
Expand Down
2 changes: 1 addition & 1 deletion plugins/ditto/.github/plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ditto",
"version": "0.3.7",
"version": "0.3.8",
"displayName": "Ditto",
"description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through GitHub Copilot.",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/ditto/.grok-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ditto",
"version": "0.3.7",
"version": "0.3.8",
"author": {
"name": "Omni Aura LLC",
"email": "support@heyditto.ai",
Expand Down
2 changes: 1 addition & 1 deletion plugins/ditto/.kimi-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ditto",
"version": "0.3.7",
"version": "0.3.8",
"author": {
"name": "Omni Aura LLC",
"email": "support@heyditto.ai",
Expand Down
10 changes: 10 additions & 0 deletions plugins/ditto/gemini-extension.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "ditto",
"version": "0.3.8",
"description": "Search and save Ditto memories through hosted MCP, curate music, and hand off chats with verified continuation notes. Archival and owned workspace cleanup depend on host capabilities and authorization. Sign in through Gemini CLI.",
"mcpServers": {
"ditto": {
"httpUrl": "https://api.heyditto.ai/mcp"
}
}
}
8 changes: 4 additions & 4 deletions plugins/ditto/shared-skills.lock.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"schema_version": 1,
"source_repository": "https://github.com/ditto-assistant/ditto-skills",
"source_commit": "b65d94a9efaad70f0677550a030bb09ec57c0d91",
"source_commit": "b4065445c5bc4d637503f0982119b957686dbf50",
"host": "plugins",
"core_sha256": "5912af5483c469ec15db27858122fc9128fc25cd239202c0fdd9727de7dd5f6b",
"adapter_sha256": "7cd895ab3feb0581e7e782de46b8b8220e4281a9ebfe6f6b651753e3a545bac1",
"generator_sha256": "c3cab1c78bcbd50b81bc80698d87567921326ee9886d220ff61c0d5bd18128fe",
"skill_sha256": "dc4a46fc6de30ceac400ced65b122895738757cdcdaf1be529d63bec165e5835",
"adapter_sha256": "c44c1d6054861d915c77f6333f1f972b9c3144cbc8d3c387988c3af9b917d9a4",
"generator_sha256": "f0fe5c02da33e861c0962230d0d1486a0542b915808c33b8bd8d4350012467db",
"skill_sha256": "d74135ea9af4d33fe1fb55727b3dbe93e448449ac124f95b333f0aebfdf880ed",
"files_sha256": {
"plugins/ditto/skills/curate-music/SKILL.md": "b498f8639aeb64e59f88830ac162a894f9f128b7e1f2db6a7c528092904c1eb4",
"plugins/ditto/skills/curate-music/references/youtube-music.md": "e763797cc7821114682c7b92cec6919e6f957792cdc54756f670c7086b09e52c",
Expand Down
1 change: 1 addition & 0 deletions plugins/ditto/skills/ditto-memory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Use the hosted Ditto MCP server at https://api.heyditto.ai/mcp, declared by this
- **Codex and ChatGPT**: the server comes from `mcp.json`. Authenticate through the plugin's install or connection prompt; in the Codex CLI, `/mcp` shows its status.
- **GitHub Copilot and VS Code**: the server is declared in the plugin manifest. Start and sign in from the MCP servers view or the Copilot CLI's `/mcp`.
- **Antigravity**: the server comes from `mcp_config.json`. Sign in from the MCP servers panel when prompted.
- **Gemini CLI**: the server is declared in `gemini-extension.json`. Check it with `/mcp` and sign in with `/mcp auth ditto`.
- **Other hosts**: use whatever MCP connection UI the host provides for the plugin's declared server.

Discover the connected server's live tools and resources, including `memory://capabilities` when exposed. Use its MCP tools for search, full-content fetch and authorized saves or updates; do not invent tool names or assume every host exposes every capability. If the server is missing or authentication has expired, explain the gap and ask the user to connect or reconnect through the host. Never request API keys in chat or fall back to shell memory commands.
Expand Down
71 changes: 71 additions & 0 deletions scripts/publish_extension_ref.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Publish one plugin folder as the root of a dedicated branch for Gemini CLI.

Gemini CLI installs a GitHub repository only from its root: the manifest must
be `gemini-extension.json` at the top of the checkout, and skills load only from
`<root>/skills/`. It accepts `--ref` but no subdirectory. A root manifest here
would also make `agy plugin install <repo>` treat the whole repository as a
Gemini extension instead of installing plugins/ditto.

This script commits the tree of `<source>:plugins/<plugin>` unchanged as the
next commit of the `gemini-extension` branch, so

gemini extensions install https://github.com/ditto-assistant/ditto-plugins --ref gemini-extension

installs plugins/ditto with its manifest, skills and MCP server. Each publish
fast-forwards the branch from its previous head. It never force-pushes and does
nothing when the tree is unchanged.
"""
import argparse
import subprocess
import sys

MANIFEST = "gemini-extension.json"


def git(*args, check=True):
result = subprocess.run(["git", *args], capture_output=True, text=True)
if check and result.returncode:
raise RuntimeError(f"git {' '.join(args)} failed: {result.stderr.strip()}")
return result


def publish(source, prefix, branch, remote, dry_run=False):
commit = git("rev-parse", "--verify", f"{source}^{{commit}}").stdout.strip()
tree = git("rev-parse", "--verify", f"{commit}:{prefix}").stdout.strip()
if git("cat-file", "-e", f"{commit}:{prefix}/{MANIFEST}", check=False).returncode:
raise ValueError(f"{prefix}/{MANIFEST} is missing at {commit}")
tracking = f"refs/remotes/{remote}/{branch}"
fetched = git("fetch", "--no-tags", remote, f"+refs/heads/{branch}:{tracking}", check=False)
if fetched.returncode and git("ls-remote", "--exit-code", "--heads", remote, branch, check=False).returncode != 2:
raise RuntimeError(f"could not fetch {branch}: {fetched.stderr.strip()}")
parent = git("rev-parse", "--verify", "--quiet", tracking, check=False).stdout.strip() if not fetched.returncode else ""
if parent and git("rev-parse", f"{parent}^{{tree}}").stdout.strip() == tree:
print(f"{branch}: already {prefix} from {commit[:12]} (tree {tree[:12]}); nothing to publish")
return None
message = f"{prefix} from {commit}\n\nGenerated by scripts/publish_extension_ref.py; do not edit this branch.\n"
new = git("commit-tree", tree, *(["-p", parent] if parent else []), "-m", message).stdout.strip()
if dry_run:
print(f"{branch}: dry run; would push {new[:12]} ({prefix} at {commit[:12]}) on top of {parent[:12] or 'nothing'}")
return new
git("push", remote, f"{new}:refs/heads/{branch}")
print(f"{branch}: published {new} ({prefix} at {commit})")
return new


def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--source", default="HEAD", help="commit to publish from (default HEAD)")
parser.add_argument("--plugin", default="ditto")
parser.add_argument("--branch", default="gemini-extension")
parser.add_argument("--remote", default="origin")
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
try:
publish(args.source, f"plugins/{args.plugin}", args.branch, args.remote, args.dry_run)
except (RuntimeError, ValueError) as error:
sys.exit(str(error))


if __name__ == "__main__":
main()
5 changes: 3 additions & 2 deletions scripts/shared_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
whose generated SKILL.md is the canonical core plus that distribution's
host-connection adapter. The ``plugins`` host is the multi-host monorepo
(ditto-assistant/ditto-plugins): one shared skills folder per plugin serves
Claude, Cursor, Grok, Codex, Copilot, Antigravity and Kimi manifests, so every
versioned manifest in the plugin directory is bumped together.
Claude, Cursor, Grok, Codex, Copilot, Antigravity, Kimi and Gemini CLI manifests,
so every versioned manifest in the plugin directory is bumped together.
"""
import argparse
import hashlib
Expand Down Expand Up @@ -40,6 +40,7 @@
".github/plugin/plugin.json",
".antigravity-plugin/plugin.json",
".kimi-plugin/plugin.json",
"gemini-extension.json",
],
}
MANIFESTS = {host: f"plugins/{DEFAULT_PLUGIN}/{paths[0]}" for host, paths in PLUGIN_MANIFESTS.items()}
Expand Down
Loading
Loading