Файлы, буфер обмена и расход
Файлы в папке воркспейса
Для чтения нужно fs.read, для изменений —
fs.write. Каждый путь задаётся относительно папки
воркспейса — папки проекта, которую пользователь выбрал для воркспейса, — через / или \;
'' или '.' — сама папка.
// 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 МБ за раз.
Слежение:
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 — хороший кандидат на
необязательное разрешение.
await card.copyText('npm test -- --grep checkout')Не чаще раза в секунду, до 1 МБ. Собственный navigator.clipboard браузера в карточке не работает,
а читать буфер обмена нельзя вовсе.
Расход токенов и стоимость
Нужно usage.read.
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')
}Те же числа, что на странице Расход приложения, для воркспейса карточки: period, from и
to (локальные сутки, конец не включается) и по каждому агенту inputTokens, outputTokens,
cacheReadTokens, cacheWriteTokens и costMicroUsd. Деньги — в целых микродолларах
(1 000 000 = $1). У модели без известной цены costMicroUsd: null — никогда не 0, — она не входит
в totalCostMicroUsd и выставляет partial: true.