# connect() и объект карточки

> Как подключить карточку к NeuroSquad, типизированный объект Card, его живой контекст, вызов любого метода и подписка на любое событие.

Source: https://docs.neurosquad.ai/ru/card-sdk/api

Всё, что делает карточка, идёт через один объект — **карточку**, которую возвращает `connect()`:

```ts
import { connect } from '@neurosquad/card-sdk'

const card = await connect()
card.setStatus(`Hello, ${card.workspace.name}`, { tone: 'success' })
```

`connect()` ждёт, пока приложение передаст карточке её личный канал, представляется и возвращает
[`Card`](#the-card-object). Повторный вызов возвращает ту же карточку.

> **Импортируйте SDK из входного скрипта.** Он начинает слушать рукопожатие приложения сразу после
> загрузки. Карточка, которая грузит SDK поздно (динамическим `import()` после таймера), может его
> пропустить и упасть с `UNAVAILABLE`.

## `connect(options?)`

```ts
import { connect } from '@neurosquad/card-sdk'

const card = await connect({
  theme: true,          // apply the app theme as --ns-* CSS variables on <html> (default true)
  syncLang: true,       // keep <html lang> equal to the app language (default true)
  forwardErrors: true,  // send uncaught errors and rejections to the card log (default true)
  timeoutMs: 10_000     // how long to wait for the app (default 10 000)
})
```

| Параметр | По умолчанию | Значение |
| --- | --- | --- |
| `theme` | `true` | `false` — не трогать ваш CSS; элемент — положить переменные `--ns-*` на него, а не на `<html>`. См. [Тема](https://docs.neurosquad.ai/ru/card-sdk/api/environment#theme). |
| `syncLang` | `true` | Держать `<html lang>` равным языку приложения. |
| `forwardErrors` | `true` | Пересылать `error` и `unhandledrejection` в [журнал карточки](https://docs.neurosquad.ai/ru/card-sdk/api/environment#log). |
| `timeoutMs` | `10000` | Сколько ждать канал от приложения, а потом его ответ. |
| `port` | — | Использовать этот канал вместо ожидания приложения — для [мок-хоста](https://docs.neurosquad.ai/ru/card-sdk/testing). |

Отклоняется с [`CardSdkError`](https://docs.neurosquad.ai/ru/card-sdk/api/errors):

- `UNAVAILABLE` — страница открыта не внутри карточки NeuroSquad (сама по себе в браузере). Для
превью используйте [мок-хост](https://docs.neurosquad.ai/ru/card-sdk/testing), как это делают шаблоны.
- `PROTOCOL_MISMATCH` — приложение старше протокола карточек, на котором говорит ваш SDK. Карточка
показывает «нужен более новый NeuroSquad».

## Объект карточки

| Член | Что это |
| --- | --- |
| `card.context` | Всё, что приложение сообщило карточке, [поддерживается в актуальном виде](#context). |
| `card.setTitle / setStatus / setBadge / setOverview / attention / requestResize / openLink / focusCard / spawn` | [Шапка карточки](https://docs.neurosquad.ai/ru/card-sdk/api/card-ui) |
| `card.ui` | [Тосты, диалог подтверждения, меню карточки](https://docs.neurosquad.ai/ru/card-sdk/api/card-ui#ui) |
| `card.storage` | [Хранилище карточки и пакета](https://docs.neurosquad.ai/ru/card-sdk/api/storage-settings#storage) |
| `card.settings` | [Значения формы настроек](https://docs.neurosquad.ai/ru/card-sdk/api/storage-settings#settings) |
| `card.agents` | [Агенты, их статусы, вывод и промпты](https://docs.neurosquad.ai/ru/card-sdk/api/agents) |
| `card.terminals` | [Команды в подключённых терминалах](https://docs.neurosquad.ai/ru/card-sdk/api/agents#terminals) |
| `card.ports` | [Типизированные данные к подключённым карточкам и от них](https://docs.neurosquad.ai/ru/card-sdk/api/ports) |
| `card.tools` | [Инструменты для подключённых агентов](https://docs.neurosquad.ai/ru/card-sdk/api/tools) |
| `card.net` | [HTTP через прокси приложения](https://docs.neurosquad.ai/ru/card-sdk/api/network) |
| `card.fs` | [Файлы в папке воркспейса](https://docs.neurosquad.ai/ru/card-sdk/api/files) |
| `card.permissions` | [Состояние разрешений, запрос необязательных](https://docs.neurosquad.ai/ru/card-sdk/api/environment#permissions) |
| `card.lifecycle` | [Видимость, усыпление, разворот, изменение размера](https://docs.neurosquad.ai/ru/card-sdk/api/environment#lifecycle) |
| `card.log` | [Строки журнала карточки](https://docs.neurosquad.ai/ru/card-sdk/api/environment#log) |
| `card.copyText / usage / getWorkspace / getTheme / getI18n` | [Буфер обмена, расход](https://docs.neurosquad.ai/ru/card-sdk/api/files), свежие снимки |
| `card.host` | [Определение возможностей](#feature-detection) |
| `card.call / on / once / waitFor` | [Любой метод, любое событие](#any-method-any-event) |
| `card.close() / closed` | Закрыть канал; последующие вызовы падают с `UNAVAILABLE`. |

## Контекст

`card.context` — это `HostContext`: то, что приложение отправило при подключении карточки,
**обновляемое по каждому событию** (видимость, размер, тема, язык, настройки, разрешения,
соседи). При каждом изменении объект заменяется, а не мутирует, — его безопасно использовать с
`useSyncExternalStore` из React.

```ts
import type { HostContext } from '@neurosquad/card-sdk'

function describe(context: HostContext): string {
  const { instance, workspace, visibility, i18n, launch } = context
  return `${instance.displayName} ${instance.version} in "${workspace.name}", ${visibility}, ${i18n.language}, started: ${launch}`
}

card.onContextChange((context) => render(describe(context)))
```

| Поле | Тип | Значение |
| --- | --- | --- |
| `instance` | `CardInstanceInfo` | `instanceId` (идентификатор этой карточки на холсте), `packageId`, `name`, `displayName`, `version`, `commit` (`null` для подключённой папки), `title`, `size`, `dev`. |
| `workspace` | `WorkspaceInfo` | `id`, `name` и `path` (абсолютный) — только с `fs.read`. |
| `visibility` | `'visible'`, `'offscreen'`, `'overview'` или `'hidden'` | См. [Жизненный цикл](https://docs.neurosquad.ai/ru/card-sdk/api/environment#lifecycle). |
| `expanded` | `boolean` | Карточка развёрнута на весь холст. |
| `theme` | `ThemeSnapshot` | Цвета, скругления и шрифты приложения. |
| `i18n` | `{ language, locale }` | `en`, `ru` или `zh` и локаль для `Intl`. |
| `settings` | `SettingsSnapshot` | `values` и `secrets` (какие секретные ключи заданы). |
| `permissions` | `PermissionState[]` | Каждое объявленное разрешение с `granted`, `optional`, `hosts`, `reason`. |
| `ports` | `{ inputs, outputs }` | Ваши собственные порты, подписи уже на нужном языке. |
| `peers` | `PeerInfo[]` | Карточки, соединённые стрелками. |
| `launch` | `'created'`, `'opened'`, `'resumed'`, `'reloaded'` или `'updated'` | Почему запустилась эта страница. |
| `spawnInit` | JSON или нет | Данные от карточки, которая [создала](https://docs.neurosquad.ai/ru/card-sdk/api/card-ui#spawn) эту, при первом запуске. |
| `chrome` | `CardChromeState` или нет | Что приложение показывает для этой карточки прямо сейчас — `title`, `status`, `badge`, `overview`, `attention`; сохраняется между перезагрузками. См. [Шапка карточки](https://docs.neurosquad.ai/ru/card-sdk/api/card-ui#attention). В старых версиях приложения отсутствует. |
| `appVersion` | `string` | Версия NeuroSquad. |
| `limits` | `LIMITS` | Все лимиты протокола, см. [Лимиты](https://docs.neurosquad.ai/ru/card-sdk/api/errors#limits). |
| `protocol` | `number` | Протокол карточек, на котором говорит приложение. |

Сокращения на самой карточке: `card.instanceId`, `card.instance`, `card.workspace`,
`card.visibility`, `card.expanded`, `card.size`, `card.theme`, `card.i18n`, `card.language`,
`card.launch`, `card.spawnInit`, `card.limits`, `card.appVersion`.

**`launch`** говорит, что произошло: `created` (карточку только что добавили на холст), `opened`
(открыли воркспейс), `resumed` (вернулась после [усыпления](https://docs.neurosquad.ai/ru/card-sdk/api/environment#lifecycle)),
`reloaded` (пользователь нажал Reload, в режиме разработчика изменился файл или отозвали
разрешение), `updated` (установлена новая версия вашей карточки).

## Любой метод, любое событие

Пространства имён — это удобная обёртка над двумя примитивами, оба полностью типизированы по
протоколу:

```ts
// Any method: params and result are typed from the method name.
const { keys } = await card.call('storage.keys', { scope: 'instance', prefix: 'draft:' })
const info = await card.call('card.getInfo')

// Any event: the payload is typed from the event name. Returns a function that removes the listener.
const off = card.on('agents.turn', (turn) => {
  if (turn.phase === 'end') render(`${turn.agentId} finished a turn`)
})
off()

// Once, or as a promise.
card.once('lifecycle.expanded', ({ expanded }) => render(expanded))
const next = await card.waitFor('agents.status', (e) => e.status === 'needs-input', { timeoutMs: 60_000 })
render(next.agentId)
```

Темы с подпиской (`agents.status`, `agents.turn`, `agents.changed`, `storage.changed`) подписываются
в приложении автоматически, пока есть хотя бы один слушатель, и отписываются, когда уходит
последний. Для `agents.output` нужны идентификаторы агентов — используйте
[`card.agents.onOutput()`](https://docs.neurosquad.ai/ru/card-sdk/api/agents#output). Если для темы нужно разрешение, которого у
вас нет, слушатель просто ничего не получает, а в журнал карточки уходит предупреждение.

События, которые могут прийти раньше, чем вы подпишетесь, — `ports.message`, `ui.menu`,
`fs.changed`, — сохраняются (до 100) и доставляются первому слушателю, так что между `connect()` и
вашим `on()` ничего не теряется.

### Все события

| Событие | Данные | Когда |
| --- | --- | --- |
| `lifecycle.visibility` | `{ state }` | Изменилась видимость. |
| `lifecycle.suspend` | `{ graceMs }` | Страницу вот-вот выгрузят. Используйте `card.lifecycle.onSuspend`. |
| `lifecycle.expanded` | `{ expanded }` | Карточку развернули или вернули. |
| `lifecycle.resized` | `{ w, h }` | Изменился размер карточки. |
| `settings.changed` | `SettingsSnapshot` | Изменились настройки (форма или `settings.set`). |
| `theme.changed` | `ThemeSnapshot` | Изменилась тема приложения. |
| `i18n.changed` | `{ language, locale }` | Изменился язык приложения. |
| `permissions.changed` | `PermissionState[]` | Изменилась выдача разрешений. |
| `ui.menu` | `{ id }` | Выбран добавленный вами пункт меню. |
| `ports.message` | см. [Порты](https://docs.neurosquad.ai/ru/card-sdk/api/ports#receiving) | На вход пришло значение. |
| `ports.request` | см. [Порты](https://docs.neurosquad.ai/ru/card-sdk/api/ports#requests) | Соседняя карточка обращается к входу-запросу. Используйте `card.ports.onRequest`. |
| `ports.peersChanged` | `PeerInfo[]` | Изменились стрелки или соседи. |
| `tools.call`, `tools.cancel` | см. [Инструменты](https://docs.neurosquad.ai/ru/card-sdk/api/tools) | Агент вызывает инструмент. Используйте `card.tools.handle`. |
| `net.chunk` | см. [Сеть](https://docs.neurosquad.ai/ru/card-sdk/api/network#streaming) | Кусок потокового ответа. Используйте `response.chunks()`. |
| `fs.changed` | `{ watchId, path, type }` | Изменился отслеживаемый файл. Используйте `card.fs.watch`. |
| `storage.changed` | `{ scope, key, byInstance }` | Другая копия карточки записала ключ пакета. Тема. |
| `agents.status` | `{ agentId, status, at }` | Изменился статус агента. Тема, `agents.read`. |
| `agents.turn` | `{ agentId, phase, at }` | Ход начался или закончился. Тема, `agents.read`. |
| `agents.changed` | `{ agents }` | Агентов добавили, удалили или переименовали. Тема, `agents.read`. |
| `agents.output` | `{ agentId, text, at }` | Вывод подключённого агента. `agents.output`. |
| `host.ping` | `{ seq }` | Пульс — SDK отвечает за вас. |

## Определение возможностей

Новые методы и события появляются внутри одной версии протокола. Прежде чем пользоваться тем, чего в
приложении пользователя может ещё не быть, проверьте:

```ts
if (await card.host.supports('usage.summary')) {
  const usage = await card.usage('today')
  render(usage.totalCostMicroUsd)
}

const { protocol, methods, events } = await card.host.capabilities()
render(protocol, methods.length, events.length)

// A method newer than your SDK's types:
const result = await card.callUnchecked('some.newMethod', { any: 'params' })
render(result)
```

## Соглашения

- **Идентификаторы.** Идентификаторы карточек и агентов — это идентификаторы на холсте, тот же
`cardId`, что вы получаете из `card.ports.peers`, `card.agents.list()` или `card.spawn()`.
- **Подключена** — значит, между двумя карточками есть стрелка, в любом направлении. Для портов
направление ещё и важно (см. [Порты](https://docs.neurosquad.ai/ru/card-sdk/api/ports#direction)).
- **Каждый вызов проверяется дважды.** SDK проверяет параметры по тем же схемам, что и приложение, и
сразу падает с `INVALID_PARAMS` и точным путём; приложение проверяет снова — плюс разрешения,
видимость, стрелки и ограничения частоты.
- **Передаётся только JSON.** Никаких `Blob` и `ArrayBuffer`: помощники, которые принимают байты
(`fs.writeBytes`, тела `net.fetch`), сами кодируют их в base64.
- **Ответы могут прийти не по порядку;** события приходят в том порядке, в каком их отправило
приложение.
- **Никогда не доверяйте событиям `message` у `window`.** Любая другая карточка на холсте может
сделать `postMessage` в ваш фрейм. Доверенный канал только один — тот, что SDK получает от
приложения в `connect()`; не добавляйте свои слушатели `message`, которые что-то делают с
пришедшим.
