# Файлы, буфер обмена и расход

> Чтение, запись, список и слежение за файлами в папке воркспейса; копирование в буфер обмена; расход токенов и стоимость воркспейса.

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

## Файлы в папке воркспейса

Для чтения нужно [`fs.read`](https://docs.neurosquad.ai/ru/card-sdk/permissions#fs-read), для изменений —
[`fs.write`](https://docs.neurosquad.ai/ru/card-sdk/permissions#fs-write). Каждый путь задаётся **относительно папки
воркспейса** — папки проекта, которую пользователь выбрал для воркспейса, — через `/` или `\`;
`''` или `'.'` — сама папка.

```ts
// Read
const pkg = JSON.parse(await card.fs.readText('package.json')) as { name: string }
const logo = await card.fs.readBytes('public/logo.png')
const info = await card.fs.stat('src/index.ts')          // { path, type, size, mtimeMs }
const { entries, truncated } = await card.fs.list('src', { recursive: true, maxEntries: 2000 })
render(pkg.name, logo.byteLength, info.size, entries.length, truncated)

// Write (atomically: a temporary file, then a rename)
await card.fs.writeText('reports/latest.md', '# Report\n', { createDirs: true })
await card.fs.mkdir('reports/archive')
await card.fs.trash('reports/old.md')                     // to the OS trash; there is no hard delete

// Write only if nobody changed it since you read it
const before = await card.fs.stat('TODO.md')
await card.fs.writeText('TODO.md', '- [ ] ship it\n', { ifMtimeMs: before.mtimeMs })
```

| Метод | Результат |
| --- | --- |
| `stat(path)` | `{ path, type: 'file' or 'directory', size, mtimeMs }` |
| `list(path?, { recursive?, maxEntries? })` | `{ entries: { path, type, size? }[], truncated }` — до 5 000 записей; рекурсивный список пропускает `.git` и `node_modules`, если только вы не просите содержимое внутри них |
| `readText(path, { maxBytes? })` | текст UTF-8 |
| `readBytes(path, { maxBytes? })` | `Uint8Array` |
| `read(path, { encoding?, maxBytes? })` | `{ data, encoding, size, truncated }` — результат как есть |
| `writeText(path, text, { createDirs?, ifMtimeMs? })` | новый `FsStat` |
| `writeBytes(path, bytes, { createDirs?, ifMtimeMs? })` | новый `FsStat` |
| `mkdir(path)` | `FsStat` |
| `trash(path)` | переносит файл или папку в корзину ОС (60 в минуту) |
| `watch(path, handler, { recursive? })` | возвращает функцию, которая прекращает слежение |

Чтение и запись — до 10 МБ за раз.

**Слежение:**

```ts
const stop = await card.fs.watch('reports', ({ path, type }) => {
  render(`${path} ${type === 'rename' ? 'was added or removed' : 'changed'}`)
}, { recursive: true })
// later
await stop()
```

Изменения объединяются с задержкой 100 мс; не больше 20 наблюдателей на карточку; наблюдатели
останавливаются, когда страница карточки выгружается (подпишитесь снова после `launch === 'resumed'`).

**Ограда.** Абсолютные пути и любые `..` отклоняются сразу. Дальше приложение находит настоящий путь
цели (а для нового файла — ближайшей существующей папки) и отказывает, если он вне папки воркспейса,
— так что символическая ссылка или junction наружу не помогут. Всё это падает с `FS_DENIED`; прочие
проблемы файловой системы (не найдено, не папка…) — с `FS_ERROR`, а неудачная проверка `ifMtimeMs` —
с `FS_ERROR` и `data.conflict`.

**Доступа к файлам нет вовсе** — любой вызов `fs.*` падает с `FS_DENIED`, — если папка воркспейса —
это домашняя папка пользователя, корень диска или в ней лежит папка данных самого NeuroSquad.

### Защищённые пути

Запись, `mkdir` и `trash` для них отклоняются (`FS_DENIED`) на любой глубине; проверяется и путь,
который вы передали, и настоящий путь за любой ссылкой — иначе карточка могла бы выполнить код через
git, агентов пользователя или их инструменты:

- всё внутри папки с именем `.git` (включая файл `.git` подмодуля или worktree), `.gitmodules`,
`.gitattributes`;
- папки `.claude`, `.codex`, `.cursor`, `.gemini`, `.qwen`, `.opencode`, `.kilocode`, `.windsurf`,
`.continue`, `.vscode`, `.idea`, `.husky`, `.devcontainer` и `.github/workflows`;
- файлы `.mcp.json`, `CLAUDE.md`, `CLAUDE.local.md`, `AGENTS.md`, `GEMINI.md`, `QWEN.md`,
`.cursorrules`, `.windsurfrules`, `opencode.json`, `opencode.jsonc`, `.envrc`, `.npmrc`, `.yarnrc`,
`.yarnrc.yml`, `.pnpmfile.cjs`.

Читать их с `fs.read` можно.

> Выбора файла в версии 1 нет: карточка работает только с папкой воркспейса. Чтобы указать карточке
> на файл, дайте пользователю ввести путь в настройке или получайте `ns:file-ref` через порт.

## Буфер обмена

Нужно [`clipboard.write`](https://docs.neurosquad.ai/ru/card-sdk/permissions#clipboard-write) — хороший кандидат на
необязательное разрешение.

```ts
await card.copyText('npm test -- --grep checkout')
```

Не чаще раза в секунду, до 1 МБ. Собственный `navigator.clipboard` браузера в карточке не работает,
а читать буфер обмена нельзя вовсе.

## Расход токенов и стоимость

Нужно [`usage.read`](https://docs.neurosquad.ai/ru/card-sdk/permissions#usage-read).

```ts
const summary = await card.usage('7d') // 'today' (default), '7d' or '30d'
const dollars = (summary.totalCostMicroUsd / 1_000_000).toFixed(2)
render(`$${dollars}${summary.partial ? ' + unpriced models' : ''}`)
for (const row of summary.rows) {
  render(row.name, row.harness, row.inputTokens, row.outputTokens, row.costMicroUsd ?? 'no price')
}
```

Те же числа, что на странице [Расход](https://docs.neurosquad.ai/ru/usage) приложения, для воркспейса карточки: `period`, `from` и
`to` (локальные сутки, конец не включается) и по каждому агенту `inputTokens`, `outputTokens`,
`cacheReadTokens`, `cacheWriteTokens` и `costMicroUsd`. Деньги — в **целых микродолларах**
(1 000 000 = $1). У модели без известной цены `costMicroUsd: null` — никогда не `0`, — она не входит
в `totalCostMicroUsd` и выставляет `partial: true`.
