> ## Documentation Index
> Fetch the complete documentation index at: https://ekso.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Countersoft Gemini

> Move Countersoft Gemini projects, issues, time entries, attachments, sprints, and versions into Ekso — in two commands.

If you're a Countersoft Gemini customer reading this — welcome. Ekso is built by the same team that built Gemini. The migration story is the upgrade story: every concept you rely on in Gemini has a clean home in Ekso, and the migrator was designed with your install in mind.

`ekso migrate gemini` pulls projects, issues, comments, attachments, time entries, sprints, versions, and components from Countersoft Gemini into your Ekso tenant. Two commands — `collect` and `apply`. Read [Migrate overview](/cli/migrate/overview) and [Before you start](/cli/migrate/before-you-start) first.

## What gets imported

Because the Gemini schema and Ekso's canonical migration schema were designed by the same team, the mapping is the cleanest of any source — most fields transfer 1:1.

| Gemini concept                          | Ekso shape                                                                                   |
| --------------------------------------- | -------------------------------------------------------------------------------------------- |
| Project                                 | [`DataContainer`](/data-model/data-container) (`Code = project.code`, `Name = project.name`) |
| Issue                                   | [`DataItem`](/data-model/data-item)                                                          |
| Comment                                 | [`DataAnnotation`](/data-model/data-annotation)                                              |
| Attachment                              | [`DataFile`](/data-model/data-file) (multipart upload)                                       |
| **Time entry**                          | [`DataItem.Time[]`](/data-model/data-item) (in the same item POST — billable flag preserved) |
| Component                               | [`ConfigLabel`](/data-model/config-label) (prefix `component:`) + item `Tags`                |
| Version (release / fix)                 | [`DataCycle`](/data-model/data-cycle)                                                        |
| Sprint                                  | [`DataCycle`](/data-model/data-cycle)                                                        |
| Custom field                            | [`DataItem.Field[]`](/data-model/data-item-field-value) (via `--field-map`) or `Meta`        |
| Resource (watcher)                      | `DataItem.Meta.gemini_resources[]`                                                           |
| Follower (subscriber)                   | preserved in `Meta`                                                                          |
| Link (parent/child, related, duplicate) | [`DataLink`](/data-model/data-link)                                                          |
| User / Group                            | [`DataUser`](/data-model/data-user)                                                          |

Authoring fidelity preserved on every comment, every time entry, every issue.

## What does NOT get imported

* **Workflow definitions / state machines.** v1 imports the *current* status of each issue. State-transition rules don't migrate — Ekso's process system is a successor; redesign the workflow at migration time.
* **License data.** Out of scope for the data migration; that's a sales conversation.
* **Saved reports / queries.** Re-author on the Ekso side.

## Before you start

Pre-flight checklist on top of [the general one](/cli/migrate/before-you-start):

* A running Gemini install (v7+ recommended).
* One of:
  * **SQL access** to the Gemini DB (recommended — fastest, most complete), or
  * **API access** to the Gemini REST API (works behind the firewall).

You don't need both — pick whichever your network and policies allow. See "Two modes" below.

Add a `gemini` block to your `migration.config.json`:

```json theme={null}
{
  "ekso": { "url": "https://ekso.acme.com", "apiKey": "ek_live_..." },

  "source": "gemini",

  "gemini": {
    "connectionMode": "sql",

    "connectionString": "Server=gemini-db.local;Database=Gemini761;User Id=migrate;Password=...;TrustServerCertificate=True;",

    "url":      "https://gemini.acme.local",
    "username": "manager",
    "apiKey":   "..."
  }
}
```

If `connectionMode=sql`, `url`/`username`/`apiKey` are ignored. If `connectionMode=api`, `connectionString` is ignored. You can configure both and switch via `--connection-mode` at runtime.

Env-var overrides for CI:

* `EKSO_MIGRATE_GEMINI_KEY` overrides `gemini.apiKey`.
* `EKSO_MIGRATE_GEMINI_DB_PWD` is read by the connection string when interpolated as `${EKSO_MIGRATE_GEMINI_DB_PWD}`.

## Two modes — when to use which

| Mode                                            | When                                             | Pros                                                                                               | Cons                                                                         |
| ----------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| **SQL Server direct** (`connectionMode: "sql"`) | The CLI can reach the Gemini DB on TCP 1433.     | Fastest. Reads attachment bytes directly from `varbinary` / FILESTREAM. Every column is queryable. | Many Gemini installs don't expose SQL externally. Needs DB read credentials. |
| **REST API** (`connectionMode: "api"`)          | The CLI can reach the Gemini web app over HTTPS. | Works through firewalls. No DB credentials. Schema-stable.                                         | Slower for large data sets. Some internal fields aren't exposed.             |

**Recommendation:** SQL mode for migrations done by the Gemini admin on-site; API mode for everyone else.

Override the configured mode at runtime:

```bash theme={null}
ekso migrate gemini collect --connection-mode api ...
ekso migrate gemini apply   --connection-mode api ...
```

## Step 1 — list projects

```bash theme={null}
ekso migrate gemini list-projects \
    --config migration.config.json \
    --url https://ekso.acme.com
```

Sample output:

```
NAME              CODE   ID    USERS
Platform          PLAT   1     14
Mobile            MOB    2     7
```

If this fails with a SQL connection error, switch to `--connection-mode api`.

## Step 2 — collect

```bash theme={null}
ekso migrate gemini collect \
    --config migration.config.json \
    --project PLAT \
    --url https://ekso.acme.com
```

Repeat `--project` for multiple. Sample run:

```
fetching projects... 1 fetched (SQL mode)
fetching PLAT issues... 3214/3214  ok
fetching comments... 8104/8104  ok
fetching time entries... 9421/9421  ok
fetching attachments... 481/481  ok (varbinary read direct)
saved cache: ~/.ekso/migrate/gemini-2026-04-29T143205.sqlite
```

Useful flags:

* `--connection-mode {sql,api}` — override the configured mode.
* `--no-attachments` — skip downloads.
* `--no-comments` — skip comments.
* `--exclude-closed` — skip items in final-state statuses.
* `--resume` — pick up where a killed collect left off.

SQL mode is fast — a 10K-issue project usually completes in 5–10 minutes including attachments. API mode is slower, especially for attachment-heavy projects.

## Step 3 — dry-run apply

```bash theme={null}
ekso migrate gemini apply \
    --config migration.config.json \
    --process proc_engineering \
    --url https://ekso.acme.com \
    --dry-run
```

## Step 4 — apply for real

```bash theme={null}
ekso migrate gemini apply \
    --config migration.config.json \
    --process proc_engineering \
    --url https://ekso.acme.com
```

If interrupted, re-run with `--resume`.

## Time tracking — the strongest fidelity point

Gemini has first-class time tracking, and Ekso preserves it natively. Every Gemini time entry becomes one row in `DataItem.Time[]` on its parent item — written **in the same `POST /api/item` call** that creates the item. No separate POST, no time-tracking applier.

The billable flag is preserved on each entry's `Meta.source_billable`. After migration:

* `ekso time list --item <id>` lists every imported entry.
* `ekso time list --user <id>` lists every entry by user, across all migrated projects.
* Standard Ekso reporting (profitability, cost-center, billable hours) works on the imported data the same way it works on natively-created data.

This is Gemini's strongest fidelity point — none of the other adapters preserve time history this completely, because none of the other source platforms model it as well.

## Identity resolution for Gemini

Gemini users have email — `--user-strategy match-or-create` is reliable. No special handling needed.

Read [Identity resolution](/cli/migrate/identity-resolution).

## Custom fields for Gemini

Gemini custom fields are accessed by name. Map them to Ekso process fields:

```yaml theme={null}
gemini:
  PullRequestUrl: { ekso: PullRequestUrl, kind: text }
  Severity:
    ekso: Severity
    kind: picker
    picker:
      Critical: P0
      High:     P1
      Medium:   P2
      Low:      P3
```

Pass it to apply:

```bash theme={null}
ekso migrate gemini apply \
    --config migration.config.json \
    --process proc_engineering \
    --field-map migration.fields.yaml
```

In SQL mode, custom field values come from `dbo.IssueCustomFields`. In API mode, they come from the issue payload. Both modes route to the same Ekso `Field[]` slot.

See [Field mapping](/cli/migrate/field-mapping).

## Sprints, Versions, Components

| Gemini                  | Ekso                                                  | Notes                                        |
| ----------------------- | ----------------------------------------------------- | -------------------------------------------- |
| Sprint                  | `DataCycle`                                           | Always — no flag.                            |
| Version (release / fix) | `DataCycle`                                           | Sprint-shaped (name + dates + member items). |
| Component               | `ConfigLabel` (`component:Backend`) + `DataItem.Tags` | Always — no flag.                            |

Cycles attach to a board on the Ekso side. Pass `--board <BOARD_ID>` if the tenant has more than one board.

## Iron rule — dual-mode dispatch

The Gemini adapter holds two repository implementations: `SqlSrvGeminiRepository` (T-SQL queries) and `ApiGeminiRepository` (HTTP client). The mode is fixed at adapter construction — once you pick SQL or API, the same mode is used for the entire run.

If you need to switch modes mid-migration (e.g. SQL for collect, API for re-collect on a different machine), run a fresh `collect` in the new mode. The cache files are interchangeable on apply — apply doesn't care which mode produced the cache.

The recommended path is SQL mode whenever your network allows it — it's faster and pulls full-fidelity data including attachment bytes directly from the DB. API mode is the firewall-friendly fallback.

## Older Gemini versions

The migrator targets Gemini v7+ schemas. Older versions (v5, v6) work in API mode if your install's API supports it. Schema drift is surfaced in `Meta.gemini_unmapped_columns` — the migration completes; data shape just differs slightly. v2 TODO: explicit version-aware schema modules if a customer needs first-class v5/v6 support.

## Multiple Gemini installations on one SQL Server

If your SQL Server hosts multiple Gemini DBs, set the right `Database=` in the connection string:

```json theme={null}
"connectionString": "Server=gemini-db;Database=Gemini761_Production;..."
```

## Self-hosted Ekso

Ekso runs self-hosted as well as SaaS. If you've been running Gemini on-prem and want to keep that posture, deploy Ekso to the same network and migrate locally — neither the source data nor the Ekso credentials need to leave your infrastructure.

## Troubleshooting Gemini-specific issues

| Symptom                                              | Fix                                                                                                                         |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `SQL connection refused`                             | Network can't reach the DB. Switch to `--connection-mode api`, or run from a host inside the corporate network.             |
| `SQL credentials invalid`                            | DB user lacks read on Gemini tables. Grant `db_datareader`.                                                                 |
| `API 401`                                            | Wrong / disabled API key. Get a fresh one from Gemini admin.                                                                |
| `Schema drift — unknown column`                      | Older or newer Gemini schema than the supported set. Surfaced in `Meta.gemini_unmapped_columns`; migration still completes. |
| `Mode mismatch — SQL configured but only API access` | Pass `--connection-mode api` at the command line.                                                                           |

See [Troubleshooting](/cli/migrate/troubleshooting) for the full per-source error table.

## Why move to Ekso

Ekso is what the Gemini team would build today — same DNA, modern stack, AI native, financial intelligence built in. Self-host or SaaS. See [Ekso vs Countersoft Gemini](https://ekso.app/compare/ekso-vs-gemini) for the full upgrade story.

## Where to next

* **[Command reference](/cli/migrate/command-reference)** — full flag surface.
* **[Identity resolution](/cli/migrate/identity-resolution)** — `--user-strategy` deep-dive.
* **[Field mapping](/cli/migrate/field-mapping)** — Gemini custom fields.
* **[Troubleshooting](/cli/migrate/troubleshooting)** — exit codes and recovery.
