diff --git a/README.md b/README.md index 8f72d4a..f7ef615 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ Installation, sign-in, and usage instructions are in the documentation: Supported platforms: macOS (x64, arm64), Linux (x64, arm64), and Windows (x64, arm64). +## Migrate from Tabnine CLI + +If you're switching from Tabnine CLI (or Gemini CLI), the [Migration helper](migration_helper/README.md) copies your MCP servers, skills, subagents, slash commands, and TABNINE.md context files into opencode. It runs as interactive wizards inside opencode itself and never overwrites files without asking. + ## Report a bug or request a feature Please use the issue templates: diff --git a/migration_helper/LICENSE b/migration_helper/LICENSE new file mode 100644 index 0000000..3d95e80 --- /dev/null +++ b/migration_helper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Tabnine Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/migration_helper/README.md b/migration_helper/README.md new file mode 100644 index 0000000..b5d7e9a --- /dev/null +++ b/migration_helper/README.md @@ -0,0 +1,129 @@ +# Migration helper: Tabnine CLI to opencode + +Copies your Tabnine CLI (or Gemini CLI) configuration into an opencode configuration directory. Runs as interactive wizards inside opencode itself: they scan your disk, show what they found, ask what to migrate, translate fields that differ between the two systems, and never overwrite an existing file without asking. + +Nothing is deleted from your Tabnine CLI installation. This is a copy-and-translate flow. You can keep using Tabnine CLI after. + +Two skills are included: + +- **`migrate-from-tabnine-cli`** (`/migrate`) — MCP servers, skills, subagents, slash commands, and extension contents. Run once per target scope (global or project). +- **`migrate-tabnine-context`** (`/migrate-context`) — context/memory files (`TABNINE.md`, `GEMINI.md`, or custom `context.fileName` files) into opencode's `AGENTS.md`. Re-runnable in every repository you work in. + +## What gets migrated + +MCP servers, skills, subagents, slash commands, the contents of Tabnine CLI extensions, and context files (`TABNINE.md` → `AGENTS.md`, via the second skill). Fields that have no opencode equivalent are dropped with a note. See `skills/migrate-from-tabnine-cli/references/mapping.md` for the complete field-by-field translation table. + +## What does NOT get migrated + +OAuth tokens (`~/.tabnine/agent/mcp-oauth-tokens.json`), Tabnine credentials, and Tabnine-specific admin policy fields — you will re-authenticate each remote MCP server on first use. Also out of scope: hooks, themes, keybindings, and general settings (model selection, approval mode); configure those directly in opencode. + +## Prerequisites + +An installed and working opencode. The wizard is a skill that loads inside opencode; the installer below only copies files into place. + +The installer script requires `bash`, `cp`, `diff`, and `mv`, which are standard on macOS and Linux. Windows users should follow the manual copy instructions below. + +## Install with the script + +Clone the repo and run the installer: + +```bash +git clone https://github.com/codota/tabnine-opencode-public +cd tabnine-opencode-public/migration_helper +./install.sh +``` + +By default this installs globally into `~/.config/opencode/`. To install into the current project instead: + +```bash +./install.sh --project +``` + +To overwrite existing files without diff prompts: + +```bash +./install.sh --overwrite +``` + +When a target file already exists and differs from the incoming copy, the installer prints a unified diff and asks whether to skip, overwrite, or rename the existing file with a timestamped `.bak-YYYYMMDD-HHMMSS` suffix before installing the new one. + +## Install manually + +Manual copy is a fully supported alternative. It is the recommended path on Windows and works on macOS and Linux equally well. + +For a global install on macOS or Linux: + +```bash +mkdir -p ~/.config/opencode/skills ~/.config/opencode/commands +cp -r migration_helper/skills/migrate-from-tabnine-cli migration_helper/skills/migrate-tabnine-context ~/.config/opencode/skills/ +cp migration_helper/commands/migrate.md migration_helper/commands/migrate-context.md ~/.config/opencode/commands/ +``` + +For a project install on macOS or Linux: + +```bash +mkdir -p .opencode/skills .opencode/commands +cp -r migration_helper/skills/migrate-from-tabnine-cli migration_helper/skills/migrate-tabnine-context .opencode/skills/ +cp migration_helper/commands/migrate.md migration_helper/commands/migrate-context.md .opencode/commands/ +``` + +For a global install on Windows (PowerShell): + +```powershell +New-Item -ItemType Directory -Force "$env:USERPROFILE\.config\opencode\skills", "$env:USERPROFILE\.config\opencode\commands" | Out-Null +Copy-Item -Recurse migration_helper\skills\migrate-from-tabnine-cli, migration_helper\skills\migrate-tabnine-context "$env:USERPROFILE\.config\opencode\skills\" +Copy-Item migration_helper\commands\migrate.md, migration_helper\commands\migrate-context.md "$env:USERPROFILE\.config\opencode\commands\" +``` + +If any of the target files already exist, back them up first. The installer script does this automatically; the manual commands above do not. + +## Usage after install + +Quit and restart opencode so it picks up the new skill and slash command. opencode does not hot-reload its configuration. + +Once restarted, run the config wizard in either of two ways: + +Run the slash command directly: + +``` +/migrate +``` + +Or ask opencode in natural language: + +``` +migrate my tabnine cli config to opencode +``` + +Either entry point activates the same skill. The wizard scans for Tabnine CLI configuration, prints a compact inventory, and then asks you category by category (MCP servers, skills, subagents, commands, extensions) which items to migrate and where to write them. + +To migrate your `TABNINE.md` context files into `AGENTS.md`, run `/migrate-context` (or ask "migrate my tabnine context files"). That skill is scoped per repository — re-run it in each project whose context files you want to bring over. + +## Uninstall + +Remove the paths the installer created: + +```bash +rm -rf ~/.config/opencode/skills/migrate-from-tabnine-cli ~/.config/opencode/skills/migrate-tabnine-context +rm ~/.config/opencode/commands/migrate.md ~/.config/opencode/commands/migrate-context.md +``` + +For a project install, replace `~/.config/opencode` with `.opencode`. Restart opencode after. + +## Troubleshooting + +The most common issue is forgetting to restart. Opencode loads skills and commands at startup. If `/migrate` is not recognized or the wizard behaviour is stale, quit opencode fully and start it again. + +If opencode fails to start after the migration with a `ConfigInvalidError`, one of the migrated fields has been rejected. Recover with either of these: + +```bash +OPENCODE_DISABLE_PROJECT_CONFIG=1 opencode +``` + +Or edit the offending file directly, using the field-by-field rules in `skills/migrate-from-tabnine-cli/references/mapping.md` as a reference. + +A `duplicate skill name` warning at load time means two skills with the same `name` field exist under paths opencode scans (`~/.config/opencode/skills/`, `~/.claude/skills/`, and the equivalent workspace paths). Rename one or delete the older copy. + +## License + +Licensed under the MIT License. See `LICENSE`. diff --git a/migration_helper/commands/migrate-context.md b/migration_helper/commands/migrate-context.md new file mode 100644 index 0000000..daab381 --- /dev/null +++ b/migration_helper/commands/migrate-context.md @@ -0,0 +1,5 @@ +--- +description: Migrate Tabnine CLI context files (TABNINE.md) into opencode's AGENTS.md. +--- + +Load the migrate-tabnine-context skill and run it. Discover the user's Tabnine CLI context/memory files (TABNINE.md, GEMINI.md, or custom context.fileName files) in this repository and globally, show what was found, and ask before writing anything. Never overwrite an existing AGENTS.md without asking and never modify the source files. diff --git a/migration_helper/commands/migrate.md b/migration_helper/commands/migrate.md new file mode 100644 index 0000000..919a195 --- /dev/null +++ b/migration_helper/commands/migrate.md @@ -0,0 +1,5 @@ +--- +description: Migrate Tabnine CLI (or Gemini CLI) configuration into opencode. +--- + +Load the migrate-from-tabnine-cli skill and run the interactive migration wizard. Scan the user's disk for Tabnine CLI configuration (MCP servers, skills, agents, slash commands, extensions), show a compact inventory, and ask per-category what to migrate. Never overwrite existing opencode files without asking. Remind the user to restart opencode when done. diff --git a/migration_helper/install.sh b/migration_helper/install.sh new file mode 100755 index 0000000..1afa12f --- /dev/null +++ b/migration_helper/install.sh @@ -0,0 +1,207 @@ +#!/usr/bin/env bash +# Install the Tabnine CLI migration skills (migrate-from-tabnine-cli, +# migrate-tabnine-context) and their slash commands (/migrate, +# /migrate-context) into an opencode configuration directory. +# +# Usage: +# ./install.sh # install globally into ~/.config/opencode/ +# ./install.sh --project # install into ./.opencode/ in the current directory +# ./install.sh --overwrite # skip diff prompts, overwrite existing files +# ./install.sh --help # show usage +# +# For each file this installer wants to place, one of three things happens: +# - target is missing -> file is copied +# - target is byte-identical -> file is skipped +# - target differs -> a diff is shown and the user is prompted +# to (s)kip, (o)verwrite, or (r)ename the +# existing target before copying + +set -euo pipefail + +# ----------------------------------------------------------------------------- +# Argument parsing +# ----------------------------------------------------------------------------- + +TARGET_SCOPE="global" +OVERWRITE=0 + +usage() { + cat <<'EOF' +Install the Tabnine CLI -> opencode migration helper. + +Usage: + install.sh [--project] [--overwrite] [--help] + +Options: + --project Install into ./.opencode/ (project scope) instead of + ~/.config/opencode/ (global scope, the default). + --overwrite Overwrite existing target files without prompting. + --help, -h Show this message and exit. + +The installer copies: + + skills/migrate-from-tabnine-cli/ -> /skills/migrate-from-tabnine-cli/ + skills/migrate-tabnine-context/ -> /skills/migrate-tabnine-context/ + commands/migrate.md -> /commands/migrate.md + commands/migrate-context.md -> /commands/migrate-context.md + +Where is either ~/.config/opencode or ./.opencode. + +You can also install manually. See the README for the copy commands. +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --project) TARGET_SCOPE="project"; shift ;; + --overwrite) OVERWRITE=1; shift ;; + --help|-h) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;; + esac +done + +# ----------------------------------------------------------------------------- +# Locate source (the directory this script lives in) +# ----------------------------------------------------------------------------- + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +SKILLS="migrate-from-tabnine-cli migrate-tabnine-context" +COMMANDS="migrate.md migrate-context.md" + +MISSING=0 +for skill in $SKILLS; do + [[ -d "$SCRIPT_DIR/skills/$skill" ]] || MISSING=1 +done +for cmd in $COMMANDS; do + [[ -f "$SCRIPT_DIR/commands/$cmd" ]] || MISSING=1 +done + +if [[ "$MISSING" -eq 1 ]]; then + echo "Error: could not find source files next to this script." >&2 + echo "Expected under $SCRIPT_DIR: skills/{$(echo $SKILLS | tr ' ' ',')}/ and commands/{$(echo $COMMANDS | tr ' ' ',')}" >&2 + echo "" >&2 + echo "If you ran this via 'curl | bash', download the repo first:" >&2 + echo " git clone https://github.com/codota/tabnine-opencode-public" >&2 + echo " cd tabnine-opencode-public/migration_helper" >&2 + echo " ./install.sh" >&2 + exit 1 +fi + +# ----------------------------------------------------------------------------- +# Resolve target +# ----------------------------------------------------------------------------- + +if [[ "$TARGET_SCOPE" == "global" ]]; then + TARGET_DIR="$HOME/.config/opencode" +else + TARGET_DIR="$PWD/.opencode" +fi + +echo "Installing into: $TARGET_DIR ($TARGET_SCOPE scope)" +echo "" + +mkdir -p "$TARGET_DIR/skills" "$TARGET_DIR/commands" + +# ----------------------------------------------------------------------------- +# Per-file install helper +# ----------------------------------------------------------------------------- +# Args: +install_file() { + local src="$1" + local dst="$2" + + mkdir -p "$(dirname "$dst")" + + if [[ ! -e "$dst" ]]; then + cp "$src" "$dst" + echo " installed $dst" + return + fi + + if cmp -s "$src" "$dst"; then + echo " up to date $dst" + return + fi + + if [[ "$OVERWRITE" -eq 1 ]]; then + cp "$src" "$dst" + echo " overwrote $dst" + return + fi + + echo "" + echo " Target exists and differs: $dst" + echo " Diff (existing -> incoming):" + echo " ---" + diff -u "$dst" "$src" || true + echo " ---" + + # -r /dev/tty is true even without a controlling terminal; test the open itself. + if ! ( : < /dev/tty ) 2>/dev/null; then + echo " skipped $dst (no terminal to prompt on; re-run interactively or use --overwrite)" + return + fi + + while true; do + read -rp " [s]kip, [o]verwrite, or [r]ename existing and install? " choice AGENTS.md)" +echo "or by asking opencode to migrate your Tabnine CLI configuration." diff --git a/migration_helper/skills/migrate-from-tabnine-cli/SKILL.md b/migration_helper/skills/migrate-from-tabnine-cli/SKILL.md new file mode 100644 index 0000000..a40892d --- /dev/null +++ b/migration_helper/skills/migrate-from-tabnine-cli/SKILL.md @@ -0,0 +1,186 @@ +--- +name: migrate-from-tabnine-cli +description: Wizard that migrates Tabnine CLI (a Gemini CLI fork) configuration into opencode. Use ONLY when the user asks to migrate, import, copy, or move Tabnine CLI (or Gemini CLI) skills, agents, subagents, MCP servers, slash commands, or extensions into opencode, or mentions moving from `~/.tabnine/agent`, `.tabnine/agent`, `.gemini`, or similar dirs. Not for migrating code, repos, or data; not for Claude Code skills (opencode reads `~/.claude/skills` natively); not for copying config between machines; not for context/memory files (TABNINE.md) — that's the migrate-tabnine-context skill. +--- + +# Migrate from Tabnine CLI to opencode + +You are running an interactive migration wizard. The user has Tabnine CLI (or Gemini CLI) configuration on disk and wants it available in opencode. Your job is to discover what they have, ask exactly what to move where, translate incompatible fields, and install the results without breaking opencode's strict config validation. + +## Core rules + +1. **Never migrate blindly.** Always list what you found and ask the user to pick, per category, before writing anything. +2. **Never invent MCP servers, skills, or agents that aren't on disk.** Only migrate what discovery actually finds. +3. **Never overwrite an existing opencode file without asking.** If a target file already exists, offer skip / overwrite / rename. +4. **Never touch the source files.** This is a copy-and-translate flow, not a move. The user should be able to keep using Tabnine CLI after. +5. **Validate translations against opencode's schema before writing.** If unsure about a field's shape, fetch `https://opencode.ai/config.json`; the built-in `customize-opencode` skill (bundled with opencode) is a faster shortcut when available. +6. **Remind the user to restart opencode at the end.** opencode does not hot-reload config. + +## Phase 1 — Discover + +Before asking anything, scan the source locations and build an inventory. Report the counts back to the user before the first question. + +Source locations to check (in this order, all optional): + +| Source | Path | +| --- | --- | +| User settings (MCPs live here) | `~/.tabnine/agent/settings.json` — read the `mcpServers` object | +| Per-server enablement | `~/.tabnine/agent/mcp-server-enablement.json` — `{ name: { enabled: false } }` means user disabled it | +| User skills | `~/.tabnine/agent/skills/*/SKILL.md` | +| User agents/subagents | `~/.tabnine/agent/agents/*.md` | +| User slash commands | `~/.tabnine/agent/commands/**/*.toml` | +| User extensions | `~/.tabnine/agent/extensions/*/tabnine-extension.json` (may bundle MCPs, skills, agents, commands) | +| Agent-alias skills | `~/.agents/skills/*/SKILL.md` | +| Workspace equivalents | `/.tabnine/agent/{skills,agents,commands,extensions}/…`, `/.agents/skills/…` | +| Legacy Gemini paths (if the user hasn't switched to Tabnine mode) | Same layout under `.gemini/` and `~/.gemini/` | +| Legacy Claude Code skills | `~/.claude/skills/*/SKILL.md` (opencode already auto-scans this location — see note in Phase 4) | + +Use the Read/Glob tools to check each path. If a path doesn't exist, silently skip it — don't error. + +For each SKILL.md and each agent `.md`, read only the frontmatter (top of file up to the second `---`) to get `name` and `description`. Don't slurp full bodies during discovery. Exception: if an agent file's frontmatter parses as a YAML **array**, it's a remote-agent (A2A) bundle — record the whole file as "remote agents: skipped (no opencode equivalent)" and don't look for `name`/`description` in it. + +If discovery also notices Tabnine context files (`TABNINE.md` in the project or `~/.tabnine/agent/`), don't inventory them here — mention once that the separate `/migrate-context` command handles those. + +After discovery, print a compact inventory like: + +``` +Found in ~/.tabnine/agent: + MCPs (3): github-mcp [enabled], playwright [disabled], tabnine-context (built-in, skipped) + Skills (2): release-notes, code-review-checklist + Agents (1): issue-triager + Commands (0) + Extensions (0) + +Found in ~/.agents/skills: + Skills (0) + +Found in /.tabnine/agent: (nothing) +``` + +Show the built-in `tabnine-context` / `tabnine-coaching` servers greyed-out as `(built-in, skipped)` — never as selectable items — so the user isn't confused when they don't appear in the multi-select. + +Then ask what the user wants to migrate — one category at a time. + +## Phase 2 — Ask per category + +Use the `question` tool. Order: + +1. **Target scope for this session** — global (`~/.config/opencode/`) or project (`./.opencode/` in the current worktree). Ask once at the start of the session. If the user later says something like "put this one in the project instead", re-scope only that category and keep the session default for the rest. +2. **MCP servers** — multi-select from the discovered list. Warn on any name conflict with an existing `mcp.` in the target `opencode.json`. +3. **Skills** — multi-select. Warn on any target-folder collision. +4. **Agents** — multi-select. For each selected agent, ask a follow-up: `subagent` (default) or `primary` mode, using exactly this explanation: "primary agents are user-facing entry points the user can switch to and chat with directly; subagents are only invoked by another agent as a delegated task." (opencode also has `mode: all` — don't offer it; suggest it only if the user asks for both behaviors.) +5. **Commands** — multi-select if any were found. +6. **Extensions** — for each **enabled** extension found (skip ones disabled in `extension-enablement.json`), list what it bundles (MCPs / skills / agents / commands) and ask whether to unpack each component into the target. Do not migrate the extension manifest itself; opencode has no equivalent. + +Never present a "migrate all" shortcut without also showing the individual list. After printing the inventory, the very next message must be the target-scope question followed by the MCP multi-select — not a yes/no "shall I migrate everything?" confirmation. The user asked for a wizard — respect that. + +## Phase 3 — Translate and write + +Per opencode's schema (see `references/mapping.md` next to this SKILL.md for the full field-by-field table). Highlights: + +### MCP servers + +Tabnine stores `mcpServers: { name: { url?, httpUrl?, command?, args?, env?, cwd?, timeout?, headers?, type? } }` in `settings.json`. + +Translate to opencode `mcp: { name: { type, url|command, headers?, environment?, cwd?, timeout?, enabled } }`: + +- If the source has a `url` or `httpUrl` field → `type: "remote"`, `url: `. +- If the source has a `command` field → `type: "local"`, `command: [, ...args]` (opencode requires an array). +- Rename `env` → `environment` — **opencode's key is `environment`; an `env` key is silently ignored and the server starts without its variables.** Preserve `headers`, `cwd`, and `timeout` under their own names. +- Rewrite `$VAR` / `${VAR}` placeholders inside values to opencode's `{env:VAR}` syntax — unconditionally, wherever they appear, including inside larger strings (`"Bearer $TOKEN"` → `"Bearer {env:TOKEN}"`) and in `headers` as much as `environment` (see `references/mapping.md`). +- Set `enabled: false` if the enablement file marks this server disabled; otherwise `enabled: true` (opencode's default). Ignore enablement entries with no matching server. +- Never copy `mcp-oauth-tokens.json`. OAuth tokens will not carry over; the user will re-authenticate on first use. Tell them this after writing. + +If the target `opencode.json` exists, copy it to `opencode.json.bak-` first, then merge into its `mcp` object rather than replacing it. Preserve `$schema`, `plugin`, and any other keys already present. If the file doesn't exist, create it with `"$schema": "https://opencode.ai/config.json"` (no backup needed). + +### Skills + +Copy the entire skill folder (SKILL.md and all sibling files) to `/skills//`. Frontmatter is compatible verbatim — both systems require `name` and `description`. Do not edit the SKILL.md, with one exception below. + +Name normalization (the exception): opencode's documented name format is `^[a-z0-9]+(-[a-z0-9]+)*$` (lowercase, hyphen-separated). Tabnine allows underscores, uppercase, and spaces in skill and agent names. Current opencode builds load nonconforming names anyway, but they're outside the documented contract and may break in a future version. If a selected skill or agent has a nonconforming `name`, offer to normalize it (lowercase, `_` and spaces → `-`) in both the `name` field and the target folder/file name — with the user's confirmation, never silently. + +After copying each selected skill, grep its body (and sibling files) for Gemini-specific tokens: `gemini -p`, `gemini `, `tui-tester`, `GEMINI.md`, `.gemini/`. Do **not** rewrite anything — rewriting prompts changes semantics. Record each hit and report the affected skills in the Phase 4 summary, one line each, so the user can fix them later. + +### Agents + +Read the source `.md`, split frontmatter from body, translate frontmatter, keep body verbatim. + +Field mapping (drop anything not listed): + +| Tabnine frontmatter | opencode frontmatter | Notes | +| --- | --- | --- | +| `name` | `name` | Keep. | +| `description` | `description` | Keep. | +| `display_name` | — | Drop. opencode has no equivalent. | +| `model` | `model` | Keep only if it's already `provider/model-id`. Values like `inherit`, `claude-4-opus`, `Claude 4.8 Opus` must be dropped (subagents inherit by default; primaries fall back to global `model`). | +| `temperature` | `temperature` | Keep. | +| `max_turns` | `steps` | Rename. Keep integer value. | +| `timeout_mins` | — | Drop. opencode has no per-agent timeout. Mention this to the user for that agent. | +| `tools` | — | Drop. opencode uses per-tool `permission` instead. Suggest an equivalent permission block only if the user asks — do not guess. | +| `mcp_servers` | — | Drop. opencode agents cannot declare private MCPs. Offer to move the definitions into top-level `mcp` in `opencode.json`. | +| — | `mode` | Add. Use the value the user chose in Phase 2 (`subagent` or `primary`). | + +Write to `/agent/.md` (opencode also accepts `agents/`, but the singular form is canonical in the schema examples — pick one and stick with it; if the target already has a plural folder, use that). + +### Commands + +Tabnine command TOMLs look like: + +```toml +description = "..." +prompt = """ +Multi-line prompt with {{args}} or $ARGUMENTS references +""" +``` + +Translate to opencode markdown: + +```markdown +--- +description: "…" +--- + + +``` + +Rewrite all three Tabnine placeholder syntaxes in the prompt body — not just `{{args}}`: + +- `{{args}}` → `$ARGUMENTS` +- `!{shell command}` → `` !`shell command` `` (backticks, no braces) +- `@{file/path}` → `@file/path` (drop the braces) + +Leave `$ARGUMENTS`, `$1`, `$2` alone if already present. If the prompt has no placeholder at all, copy it as-is — both systems auto-append the user's arguments; do not insert `$ARGUMENTS`. + +Write to `/command/.md`, mirroring nested source folders: `commands/foo/bar.toml` → `/command/foo/bar.md`. The invocation changes from Tabnine's `/foo:bar` to opencode's `/foo/bar` — mention this in the summary. + +### Extensions + +Do not migrate `tabnine-extension.json` as a unit. For each component the user opted into, apply the rules above to the extension's `mcpServers`, `skills/`, `agents/`, `commands/` entries. Prepend the extension name to the migrated item's `name` field if a collision exists (`-`), and mention this to the user. + +## Phase 4 — Post-write summary and warnings + +After all writes succeed, print a summary: + +- What was written (grouped by category, with target paths), including the `opencode.json.bak-*` backup path if one was made. +- Any items that were **skipped** due to collisions or user opt-out, and any names or invocations that changed (normalized names, `/foo:bar` → `/foo/bar`). +- Any agents where `tools`, `mcp_servers`, `timeout_mins`, or `model` fields were dropped, with a one-liner suggesting where the user should look next. For a dropped `model`, point at `opencode models` / the provider list so the user can set a `provider/model-id` value themselves. +- Any skills whose bodies reference Gemini-specific tools, flagged for manual review. +- OAuth reminder for any migrated remote MCP servers (Atlassian, Mixpanel, GitHub, etc.): tokens do not carry over. +- If Tabnine context files exist (`TABNINE.md` in the project or `~/.tabnine/agent/`): "Your TABNINE.md context files weren't part of this migration — run `/migrate-context` to move them into AGENTS.md." +- **Restart reminder**: "Quit and restart opencode for these changes to take effect. Running sessions keep using the already-loaded config." + +Note about Claude Code skills at `~/.claude/skills`: opencode auto-scans this path already. Do NOT copy skills from there into `~/.config/opencode/skills/` unless the user explicitly asks — you'd end up with two copies of the same name, and opencode resolves duplicates by a coin-flip (the "winner" is non-deterministic and the warning is only written to logs). If discovery finds Claude Code skills, tell the user they're already visible to opencode and skip them by default. + +## When things go wrong + +- **`ConfigInvalidError` on startup after migration**: the user's `opencode.json` has a rejected field. Recover with `OPENCODE_DISABLE_PROJECT_CONFIG=1 opencode` (project) or by manually editing the global file. Point them at the escape hatches in the `customize-opencode` skill. +- **A migrated skill behaves inconsistently or seems to "flip" between versions**: two skills with the same `name` exist in scanned paths — opencode only logs a warning and which copy wins is non-deterministic. Rename one or delete the older copy. +- **`ConfigInvalidError` after the MCP merge specifically**: restore the `opencode.json.bak-` backup written before the merge, then retry. +- **MCP server appears but returns auth errors**: normal on first use — re-authenticate via the MCP's OAuth flow. Do not attempt to copy tokens from `~/.tabnine/agent/mcp-oauth-tokens.json`. +- **User wants to reverse the migration**: the wizard doesn't delete Tabnine sources, so reversing means deleting the newly created files under `/{mcp entries, skills/*, agent/*.md, command/*.md}`. Offer to list them if asked. + +## Reference material + +See `references/source-map.md` for the exhaustive list of Tabnine CLI config paths (with the code-verified precedence rules), and `references/mapping.md` for the complete field translation table with edge cases. + +If you need to verify an opencode field shape before writing, fetch `https://opencode.ai/config.json` or load the built-in `customize-opencode` skill. diff --git a/migration_helper/skills/migrate-from-tabnine-cli/references/mapping.md b/migration_helper/skills/migrate-from-tabnine-cli/references/mapping.md new file mode 100644 index 0000000..c87b531 --- /dev/null +++ b/migration_helper/skills/migrate-from-tabnine-cli/references/mapping.md @@ -0,0 +1,254 @@ +# Field translation reference + +Complete mapping from Tabnine CLI to opencode. When in doubt about opencode's shape, load the `customize-opencode` skill or fetch `https://opencode.ai/config.json`. + +## Contents + +- MCP servers (field-by-field table, per-server enablement, example translation) +- Skills (direct copy rules) +- Agents (allowed opencode frontmatter, local-agent field mapping, remote A2A handling, example translation) +- Commands (example translation, placeholder mapping, namespacing) +- Extensions (unpacking rules) +- Fields that are always dropped + +## MCP servers + +Tabnine settings.json → opencode `opencode.json`: + +``` +mcpServers[name] { … } → mcp[name] { type, …, enabled } +``` + +Field-by-field: + +| Tabnine key | opencode key | Rule | +| --- | --- | --- | +| `url` | `url` | Set `type: "remote"`. | +| `httpUrl` | `url` | Same as `url` (deprecated Tabnine alias). Set `type: "remote"`. | +| `command` (string) + `args` (array) | `command` (array) | Combine into a single array: `[command, ...args]`. Set `type: "local"`. | +| `env` | `environment` | **Rename — opencode's key is `environment`, not `env`.** An `env` key is silently ignored and the server starts without its variables. Values: see the env-var interpolation rule below. | +| `headers` | `headers` | Copy, applying the env-var interpolation rule below. Remote servers only. | +| `type: "sse" \| "http"` | — | Not needed. opencode uses `type: "remote"` for both; the client negotiates transport. | +| `cwd` | `cwd` | Copy verbatim. Local servers only. | +| `timeout` | `timeout` | Copy verbatim. Both are milliseconds; opencode's default is 5000 if absent. | +| `trust` | — | opencode uses permissions instead. Drop; the user grants tool access at runtime. | +| `description` | — | Drop (opencode ignores it). | +| `includeTools` / `excludeTools` | — | Not supported. Drop; opencode surfaces all tools from an MCP. | +| `authProviderType` | — | Not supported. Drop. | +| `oauth` (Tabnine's built-in OAuth flow) | — | Drop. opencode expects the MCP itself to handle OAuth on first connect. | + +### Env-var interpolation in values + +The two systems use different placeholder syntax inside string values: + +- Tabnine CLI expands `$VAR` and `${VAR}` when it loads settings. +- opencode expands `{env:VAR}` (and `{file:path}`) when it loads `opencode.json`. A literal `$VAR` is passed through untouched. + +When a migrated value (in `environment`, `headers`, `url`, or `command`) contains `$VAR` or `${VAR}`, rewrite it to `{env:VAR}`. Example: `"Authorization": "Bearer $MCP_TOKEN"` → `"Authorization": "Bearer {env:MCP_TOKEN}"`. Values that contain no `$` placeholders copy verbatim. + +This rule is unconditional: it applies to every `$NAME`/`${NAME}` substring anywhere inside a value — including inside larger strings like `"Bearer $TOKEN"`, and equally in `headers` and `environment`. Do not reason that a particular `$NAME` "looks like a literal" and keep it — Tabnine expanded it at load time, so a kept `$NAME` reaches the server as a dead literal in opencode. The only exception is a value the user explicitly confirms is a literal dollar string. + +Per-server enablement (`~/.tabnine/agent/mcp-server-enablement.json`): + +``` +{ name: { enabled: false } } → mcp[name].enabled: false +``` + +Absent name → `enabled: true` (opencode default; you can omit the field). + +Edge cases: + +- Tabnine normalizes enablement keys to lowercase and trims whitespace — match case-insensitively against server names. +- Extension-bundled servers appear under an `ext:` key (plain `` also accepted for back-compat). +- Stale entries happen (a key with no matching server in `mcpServers`, e.g. a server the user deleted). Ignore them — never invent a server to match an enablement entry. + +### Example translation + +Source (`~/.tabnine/agent/settings.json`): + +```json +{ + "mcpServers": { + "AtlassianMCP": { "url": "https://mcp.atlassian.com/v1/mcp" }, + "playwright": { + "command": "npx", + "args": ["-y", "@playwright/mcp"], + "env": { "PW_TOKEN": "$PLAYWRIGHT_TOKEN" }, + "cwd": "/Users/me/proj", + "timeout": 30000 + } + } +} +``` + +Enablement (`~/.tabnine/agent/mcp-server-enablement.json`): + +```json +{ "playwright": { "enabled": false } } +``` + +Target (`~/.config/opencode/opencode.json`): + +```json +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "AtlassianMCP": { + "type": "remote", + "url": "https://mcp.atlassian.com/v1/mcp", + "enabled": true + }, + "playwright": { + "type": "local", + "command": ["npx", "-y", "@playwright/mcp"], + "environment": { "PW_TOKEN": "{env:PLAYWRIGHT_TOKEN}" }, + "cwd": "/Users/me/proj", + "timeout": 30000, + "enabled": false + } + } +} +``` + +## Skills + +Direct copy. Both systems use `SKILL.md` with the same required frontmatter fields (`name`, `description`). + +Do not rewrite: + +- Skill bodies. If a skill references Gemini binaries, that's a semantic change and the user should decide. +- The `name` field. It must stay unique across all scanned paths. On collision opencode only logs a warning (the user never sees it) and which copy wins is non-deterministic — so a duplicate is a silent coin-flip, not a visible error. Avoid creating one. + +Optional frontmatter fields opencode also accepts (see `customize-opencode`): `license`, `compatibility`, `metadata`. Preserve if present, remove none. + +Location: `/skills//SKILL.md`. Copy the whole directory including any sibling scripts, examples, `references/`, etc. + +## Agents + +Split frontmatter and body. Translate frontmatter. Body copies verbatim. + +### Allowed opencode frontmatter fields + +`name, model, variant, description, mode, hidden, color, steps, options, permission, disable, temperature, top_p`. Unknown fields are silently routed into `options` where they have no effect — so drop them explicitly. + +### Local-agent field mapping + +| Tabnine (snake_case) | opencode | Rule | +| --- | --- | --- | +| `kind: local` | — | Drop. It's the default. | +| `name` | `name` | Keep. Both use lowercase-hyphen slug. | +| `description` | `description` | Keep. | +| `display_name` | — | Drop. | +| `tools` | — | Drop. opencode uses `permission` (per-tool allow/ask/deny). If the user wants an equivalent, ask before generating a permission block; don't guess. | +| `mcp_servers` | — | Drop from the agent frontmatter. Ask the user if these should be hoisted into top-level `mcp` in `opencode.json` (they'll then be visible to all agents, not just this one). Note the frontmatter variant uses snake_case keys (`http_url`, `include_tools`, `exclude_tools`) — translate them like their camelCase settings.json equivalents. | +| `model: inherit` | — | Drop. Subagents inherit from parent by default; primaries fall back to global `model`. | +| `model: /` | `model` | Keep if the value is already `provider/model-id` format. | +| `model: ` (e.g. `claude-4-opus`, `Claude 4.8 Opus`) | — | Drop. opencode requires the provider prefix; a plain name will fail validation. | +| `temperature` | `temperature` | Keep. | +| `max_turns` | `steps` | Rename. Preserve integer value. | +| `timeout_mins` | — | Drop. No opencode equivalent. Note this to the user. | +| — | `mode` | Add. Value from the wizard's Phase-2 per-agent prompt (`subagent` or `primary`). | + +Body → agent prompt, no changes. + +### Remote (A2A) agents + +opencode has no built-in A2A remote agent kind. Two options: + +1. **Skip** with a warning. Simplest. +2. If the remote agent is really important, offer to create a subagent whose body calls the remote via `webfetch` or a bespoke MCP. This is a manual step — do not auto-generate. + +### Example translation + +Source (`~/.tabnine/agent/agents/jira-issue-manager.md`): + +```markdown +--- +name: jira-issue-manager +model: inherit +max_turns: 15 +timeout_mins: 5 +description: Manage Jira issues … +--- + +You are a Jira and Confluence management specialist. … +``` + +Target (`~/.config/opencode/agent/jira-issue-manager.md`), with the user picking `subagent` mode: + +```markdown +--- +name: jira-issue-manager +mode: subagent +steps: 15 +description: Manage Jira issues … +--- + +You are a Jira and Confluence management specialist. … +``` + +Dropped: `model: inherit` (no-op), `timeout_mins: 5` (no equivalent). + +## Commands + +Tabnine TOML → opencode Markdown-with-frontmatter. + +### Example translation + +Source (`~/.tabnine/agent/commands/deploy.toml`): + +```toml +description = "Deploy to staging" +prompt = """ +Deploy branch {{args}} to staging. +Run tests first with !{npm test}. +Check @{README.md} for the runbook. +""" +``` + +Target (`~/.config/opencode/command/deploy.md`): + +```markdown +--- +description: Deploy to staging +--- + +Deploy branch $ARGUMENTS to staging. +Run tests first with !`npm test`. +Check @README.md for the runbook. +``` + +### Placeholder mapping + +| Tabnine | opencode | Notes | +| --- | --- | --- | +| `{{args}}` | `$ARGUMENTS` | Both mean "everything the user typed after the command". | +| — | `$1`, `$2`, … | opencode adds positional args. Tabnine has no direct equivalent; if the source uses split-args logic in `prompt`, leave a TODO comment for the user. | +| `!{shell command}` | ``!`shell command` `` | Both allow shell injection. opencode uses backtick syntax. | +| `@{file/path}` | `@file/path` | Both allow file injection. opencode drops the braces. | +| (no placeholder at all) | (no placeholder at all) | Copy as-is. Both systems automatically append the user's arguments when the prompt contains no placeholder — do not insert `$ARGUMENTS`. | + +### Namespacing + +Tabnine derives namespaced command names from nested folders using `:` (`commands/foo/bar.toml` → `foo:bar`). opencode derives them from folder structure using `/` (`command/foo/bar.md` → `/foo/bar`). Mirror the source folder structure — `commands/foo/bar.toml` becomes `/command/foo/bar.md` — so `foo:bar` in Tabnine is `/foo/bar` in opencode. Mention the renamed invocation in the summary. Do not flatten names. + +## Extensions + +opencode has no extension bundle format. Before unpacking, check `~/.tabnine/agent/extensions/extension-enablement.json` — skip extensions the user has disabled there (offer them only if the user asks). Then unpack: + +- Each `mcpServers` entry → treat as a top-level MCP (rules above). If the extension name should be preserved, prefix: `-`. +- Each `skills/*/SKILL.md` → treat as a normal skill. +- Each `agents/*.md` → treat as a normal agent. +- Each `commands/*.toml` → treat as a normal command. + +The `contextFileName` field (extension-provided AGENTS.md-like context) can be migrated as-is into the target's `instructions` array in `opencode.json`, if the user wants: `"instructions": [ ..., "/path/to/extension/context.md" ]`. + +## Fields that are always dropped + +Regardless of category, these Tabnine fields have no opencode counterpart and should never be preserved: + +- Any `governanceExempt` markers (Tabnine-only enterprise policy). +- Admin policy fields (`admin.mcp.enabled`, `admin.skills.enabled`, `admin.mcp.config`) — these are runtime admin controls, not portable config. +- Trust markers (`trust: true` on MCPs, trusted-folder logic). opencode uses `permission` instead. +- Acknowledgement hashes (`~/.tabnine/agent/acknowledgments/agents.json`). opencode doesn't require per-agent acknowledgement. +- Tabnine credentials, IDs, and OAuth token stores. diff --git a/migration_helper/skills/migrate-from-tabnine-cli/references/source-map.md b/migration_helper/skills/migrate-from-tabnine-cli/references/source-map.md new file mode 100644 index 0000000..6c048c6 --- /dev/null +++ b/migration_helper/skills/migrate-from-tabnine-cli/references/source-map.md @@ -0,0 +1,216 @@ +# Tabnine CLI source map + +Verified against the Tabnine CLI source. All paths resolve `GEMINI_DIR = .tabnine/agent` in Tabnine mode (default) or `.gemini` in original Gemini mode. + +## Contents + +- Config directory resolution +- MCP servers (settings tiers, extensions, agent-declared, built-ins, enablement, filters) +- Skills (discovery order, filename glob, frontmatter, gating settings) +- Agents (discovery order, local frontmatter, remote A2A frontmatter, overrides) +- Commands (loader, discovery order, placeholders) +- Post-migration reminders + +## Config directory resolution + +`packages/core/src/utils/paths.ts:14-20` sets the config directory based on build/env: + +- Default (Tabnine mode): `.tabnine/agent` +- `ORIG_GEMINI` build flag: `.gemini` +- `TABNINE_NODE_ENV=development`: `.tabnine-dev/agent` (or the value of `TABNINE_DEV_CONFIG_DIR`) +- Home dir can be overridden with `GEMINI_CLI_HOME` + +Assume Tabnine mode unless the user says otherwise. If they mention "Gemini CLI" specifically, also check `~/.gemini/` and `/.gemini/` with the same subdirectory layout. + +## MCP servers + +MCP servers come from four sources and only these four. There is no `.mcp.json`, no `mcp_servers.json`, no `.tabnine/mcp_servers.json`. + +### Source 1: `mcpServers` key in settings.json + +Settings are loaded from four tiers, deep-merged (`packages/cli/src/config/settings.ts:781-950`): + +| Tier | Path | +| --- | --- | +| System | `/Library/Application Support/TabnineCli/settings.json` (macOS), `C:\ProgramData\tabnine-cli\settings.json` (Windows), `/etc/tabnine-cli/settings.json` (Linux). Overridable via `TABNINE_CLI_SYSTEM_SETTINGS_PATH`. | +| System defaults | Same directory as System, filename `system-defaults.json`. Env: `TABNINE_CLI_SYSTEM_DEFAULTS_PATH`. | +| User | `~/.tabnine/agent/settings.json` | +| Workspace | `/.tabnine/agent/settings.json` | + +Read the `mcpServers` object from each. Merge precedence (from `mergeSettings`, `settings.ts:277-302`): schema defaults → system defaults → user → workspace → **system last, which wins over everything** — on managed machines an admin's system settings override the user's. The wizard should read at minimum User and Workspace; if a system file exists, mention that its entries take precedence in Tabnine and may be admin-managed (probably not the user's to migrate). + +Shape of each entry (`packages/cli/src/config/settingsSchema.ts:161-174`, values from Gemini upstream `MCPServerConfig`): + +```json +{ + "url": "https://…", // remote SSE/HTTP + "httpUrl": "https://…", // alternate remote key some servers use + "command": "npx", // local — a single string, not an array + "args": ["-y", "some-mcp"], // local — array of strings + "env": { "KEY": "VAL" }, + "cwd": "/path", + "headers": { "Authorization": "…" }, + "type": "sse" | "http", + "timeout": 30000, + "trust": true, + "description": "…", + "includeTools": ["tool_a"], + "excludeTools": ["tool_b"], + "authProviderType": "…", + "oauth": { … } +} +``` + +### Source 2: Extensions + +Extensions live at `~/.tabnine/agent/extensions//` and `/.tabnine/agent/extensions//`. Manifest filename: `tabnine-extension.json` in Tabnine mode, `gemini-extension.json` in Gemini mode (`packages/core/src/config/storage.ts:446-456`). + +Manifest schema (`packages/cli/src/config/extension.ts:24-49`): `{ name, version, mcpServers?, contextFileName?, excludeTools?, settings?, themes?, plan? }`. Install metadata lives in a separate sibling file (`.tabnine-extension-install.json`), not in the manifest. Extensions can also ship `/skills/`, `/agents/`, `/commands/` directories that the loaders pick up (`packages/cli/src/config/extension-manager.ts:837-994`, `FileCommandLoader.ts:231-243`). + +Per-extension enable/disable state lives in `~/.tabnine/agent/extensions/extension-enablement.json` — skip disabled extensions by default when unpacking. + +### Source 3: Agent-declared MCP servers (`mcp_servers` frontmatter) + +Local agents can embed `mcp_servers:` in their YAML frontmatter (`packages/core/src/agents/agentLoader.ts:54-90`). These are scoped to that agent only. opencode has no equivalent — surface them to the user and offer to hoist them into top-level `mcp`. + +### Source 4: Tabnine built-in MCP servers (code-registered) + +`packages/core/src/tabnine/mcp/builtin-mcp-servers.ts` registers: + +- `tabnine-context` → `{tabnineHost}/indexer/mcp` +- `tabnine-coaching` → `{tabnineHost}/coaching/api/mcp` + +These are already the same servers opencode's Tabnine plugin registers. **Do not migrate them** — they'd conflict with the plugin. + +### Per-server enablement + +`~/.tabnine/agent/mcp-server-enablement.json` (`packages/cli/src/config/mcp/mcpServerEnablement.ts`): + +```json +{ + "server-name": { "enabled": false } +} +``` + +Absence of a key means enabled. This is user disables, not admin policy. Apply directly to opencode's `mcp..enabled`. + +Gotchas: keys are normalized to lowercase/trimmed, so match server names case-insensitively; extension-bundled servers appear as `ext:` (plain `` also accepted); stale keys with no matching server can linger after a server is deleted — ignore them. + +### Additional filters (rarely present, worth checking) + +Settings can also carry `mcp.allowed` (allowlist) and `mcp.excluded` (blocklist) arrays (`settingsSchema.ts:1916-1955`), and `admin.mcp.enabled` (kill switch). If any of these are set, respect them when building the migration list: don't migrate servers the user has explicitly excluded, and warn if `admin.mcp.enabled: false` is set (Tabnine had MCPs disabled entirely — the user probably still wants to migrate the definitions, but should know). + +## Skills + +Loader: `packages/core/src/skills/skillLoader.ts`. Discovery order in `packages/core/src/skills/skillManager.ts:54-99` (later overrides earlier on name conflict): + +1. Built-in skills bundled in `packages/core/src/skills/builtin/*` — **do not migrate**, opencode has its own built-ins. +2. Extension skills: `/skills/*/SKILL.md`. +3. User skills: `~/.tabnine/agent/skills/*/SKILL.md`. +4. User agent-alias: `~/.agents/skills/*/SKILL.md` (a plain `.agents` folder — not `.claude`). +5. Workspace skills: `/.tabnine/agent/skills/*/SKILL.md` (trusted folders only). +6. Workspace agent-alias: `/.agents/skills/*/SKILL.md` (trusted only). + +Filename glob (`skillLoader.ts:127`): `['SKILL.md', '*/SKILL.md']` — SKILL.md must be uppercase, at the root or one level deep in the skills dir. + +Frontmatter validation: only `name` (required) and `description` (required) are checked (`skillLoader.ts:34-192`). Same requirements as opencode, so bodies copy verbatim. + +Skill names have **no format regex** in Tabnine (only filesystem-hostile characters `: \ / < > * ? " |` are sanitized to `-`), so underscores, uppercase, and spaces can appear. opencode's code accepts these too, but its documented contract is `^[a-z0-9]+(-[a-z0-9]+)*$` — see the normalization step in the skill. + +Settings that gate skills: + +- `skills.enabled` (bool, default true) — kill switch, requires restart. +- `skills.disabled` (string[]) — names to skip at runtime. + +There is **no** `skills.paths` or `skills.urls` setting in Tabnine CLI. Skills live only in the six directories above. + +## Agents + +Loader: `packages/core/src/agents/agentLoader.ts`. Registry: `packages/core/src/agents/registry.ts:173-295`. + +Discovery order (first-registered wins for duplicate names, unlike skills): + +1. Built-in agents (registered in code) — `CodebaseInvestigatorAgent`, `GeneralistAgent`, `remote-codebase-investigator` (Tabnine), `BrowserAgentDefinition`, etc. **Do not migrate.** +2. Project agents: `/.tabnine/agent/agents/*.md` (trusted folders + per-agent acknowledgement). +3. User agents: `~/.tabnine/agent/agents/*.md`. +4. Extension agents: `/agents/*.md`. + +Directory scan (`agentLoader.ts:640-697`) is **non-recursive** and only picks up top-level `*.md`. Files starting with `_` are ignored. + +### Local agent frontmatter (Zod schema at `agentLoader.ts:92-116`) + +Strict — unknown keys are rejected. Keys are snake_case in YAML. + +```yaml +kind: local # optional, defaults to 'local' +name: string # required, /^[a-z0-9-_]+$/ +description: string # required +display_name: string # optional +tools: [string, …] # optional, tool-name allowlist (wildcards allowed) +mcp_servers: # optional, private MCPs for this agent + name: + command: … + args: … + env: … + url: … + http_url: … + headers: … + type: sse | http + timeout: … + trust: … + description: … + include_tools: … + exclude_tools: … + auth: { type: google-credentials | oauth, … } +model: string # optional, default 'inherit' +temperature: number # optional, default 1 +max_turns: int # optional, default 30 +timeout_mins: int # optional, default 10 +``` + +Body (post-frontmatter) is the agent's system prompt. + +### Remote (A2A) agent frontmatter (`agentLoader.ts:208-243`) + +```yaml +kind: remote +name: string +description: string # optional (falls back to Agent Card) +display_name: string # optional +auth: # optional + type: apiKey | http | google-credentials | oauth + … +agent_card_url: url # exactly one of these two required +agent_card_json: string +``` + +Array frontmatter is also accepted (multiple remote agents in one file). Discovery implication: if the first frontmatter block of an agent `.md` parses as a YAML array, treat the whole file as a remote-agent bundle immediately — don't try to read `name`/`description` off it. opencode has no direct equivalent for A2A agents — skip these with a warning, or convert to a subagent that calls the remote endpoint via a tool if the user asks. + +### Agent overrides in settings + +`agents.overrides` (`settingsSchema.ts:1305-1437`) lets the user override any registered agent's `enabled` / `modelConfig` / `runConfig` / `tools` / `mcpServers`. Read these when translating so the effective config is what gets migrated, not just what's in the `.md`. + +## Commands + +Loader: `packages/cli/src/services/FileCommandLoader.ts:209-248`. Format: TOML with `prompt` (required) and `description` (optional). + +Discovery order (later can conflict with earlier): + +1. User: `~/.tabnine/agent/commands/` +2. Workspace: `/.tabnine/agent/commands/` +3. Extensions: `/commands/` + +Files are TOML. Nested folders become namespaced names (colon separator, e.g. `commands/foo/bar.toml` → `foo:bar`). Placeholders inside `prompt`: + +- `{{args}}` — Tabnine's shorthand for all args +- `!{shell command}` — shell injection +- `@{file/path}` — file injection + +Skill-as-command loader (`packages/cli/src/services/SkillCommandLoader.ts`) also exposes each skill as a slash command that activates the skill — that's not a "command" for migration purposes. + +## Post-migration reminders + +- opencode's Tabnine plugin already registers `tabnine-context` and `tabnine-coaching` MCP servers. Do not migrate those. +- opencode's Tabnine plugin already provides Tabnine authentication, so `~/.tabnine/tabnine_creds.json` and `~/.tabnine/agent/tabnine-credentials.json` should not be touched. +- OAuth tokens in `~/.tabnine/agent/mcp-oauth-tokens.json` are Tabnine-CLI-specific and will not carry over to opencode. The user re-authenticates each MCP on first use. +- Context/memory files (`TABNINE.md` at the project root and `~/.tabnine/agent/TABNINE.md` globally, plus any custom `context.fileName` names) are handled by the separate `migrate-tabnine-context` skill (`/migrate-context`), not this wizard. If discovery notices them, point the user there. diff --git a/migration_helper/skills/migrate-tabnine-context/SKILL.md b/migration_helper/skills/migrate-tabnine-context/SKILL.md new file mode 100644 index 0000000..10ee5c2 --- /dev/null +++ b/migration_helper/skills/migrate-tabnine-context/SKILL.md @@ -0,0 +1,48 @@ +--- +name: migrate-tabnine-context +description: Migrates Tabnine CLI context/memory files (TABNINE.md, GEMINI.md, or custom context.fileName files) into opencode's AGENTS.md. Use ONLY when the user asks to migrate, import, or copy Tabnine CLI (or Gemini CLI) context files, memory files, TABNINE.md, or GEMINI.md into opencode or AGENTS.md. Re-runnable per repository. Not for MCP servers, skills, agents, or slash commands — that's the migrate-from-tabnine-cli skill. +--- + +# Migrate Tabnine CLI context files to opencode + +You are migrating the user's Tabnine CLI context/memory files into opencode's `AGENTS.md` format. This skill is scoped per repository so it can be re-run in each project the user works on. The global file is offered too, but only needs migrating once. + +## Core rules + +1. **Never touch the source files.** Copy only. The user can keep using Tabnine CLI after. +2. **Never overwrite an existing `AGENTS.md` silently.** If the target exists, offer merge or skip. +3. **Never rewrite content.** Context files are instructions the user wrote; changing their wording changes behavior. Copy verbatim (a merge header line is the only text you add). +4. **Show what you found and ask before writing.** + +## Phase 1 — Discover + +1. Determine the context filename(s). Default is `TABNINE.md` (`GEMINI.md` if the user is on plain Gemini CLI). Check `context.fileName` in `~/.tabnine/agent/settings.json` and `/.tabnine/agent/settings.json` — it may be a single string or an array of names (e.g. `["AGENTS.md", "TABNINE.md"]`). +2. Find source files: + - **Project**: `/` for each configured name, plus subdirectory matches (`**/`, skipping `node_modules`, `.git`, and other vendored dirs). Tabnine reads these hierarchically; opencode reads `AGENTS.md` per directory, so subdirectory files map to a sibling `AGENTS.md` in the same directory. + - **Global**: `~/.tabnine/agent/TABNINE.md` (or the Gemini equivalent). Target: `~/.config/opencode/AGENTS.md`. Offer this only if it hasn't been migrated already — if the target exists and already contains the source content, report "already migrated" and skip. +3. If a configured name is already `AGENTS.md`, opencode reads it natively — report it as "no migration needed". + +Print what was found (path, size, target) and ask which files to migrate. If nothing was found, say so and stop. + +## Phase 2 — Write + +For each selected source file, target is `AGENTS.md` in the same directory (project) or `~/.config/opencode/AGENTS.md` (global): + +- **Target missing** → copy the content as-is. +- **Target exists** → ask: merge or skip. On merge, append to the existing `AGENTS.md`: + + ```markdown + + + + + ``` + +- If the source uses Tabnine's import syntax (`@./relative/path.md` lines), copy it unchanged and flag the file in the summary — opencode does not process Tabnine imports, so the user may want to inline or restructure those sections. + +## Phase 3 — Summary + +- List what was written, merged, and skipped, with paths. +- List any files flagged for import-syntax review. +- Remind the user: sources were not modified; re-run this skill in other repositories as needed. +- Restart reminder: "Restart opencode (or start a new session) to pick up the new AGENTS.md."