# 文件、剪贴板与用量

> 读取、写入、列出和监视工作区文件夹中的文件；复制到剪贴板；工作区的令牌用量和费用。

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

## 工作区文件夹中的文件

读取需要 [`fs.read`](https://docs.neurosquad.ai/zh/card-sdk/permissions#fs-read)，修改需要 [`fs.write`](https://docs.neurosquad.ai/zh/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 MB。

**监视：**

```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`。

当工作区文件夹是用户的主文件夹、某个磁盘的根目录，或包含 NeuroSquad 自己的数据文件夹时，**完全没有文件访问权限**——
每个 `fs.*` 调用都会以 `FS_DENIED` 失败。

### 受保护的路径

对以下路径的写入、`mkdir` 和 `trash` 会被拒绝（`FS_DENIED`），无论位于哪一层，检查的既是你给出的路径，也是任何链接背后的真实路径——
因为它们会让卡片借助 git、用户的智能体或他们的工具来运行代码：

- 名为 `.git` 的文件夹中的任何内容（包括子模块或 worktree 的 `.git` 文件）、`.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/zh/card-sdk/permissions#clipboard-write)——很适合设为可选权限。

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

每秒最多一次，≤ 1 MB。浏览器自带的 `navigator.clipboard` 在卡片中不可用，而且完全无法读取剪贴板。

## 令牌用量和费用

需要 [`usage.read`](https://docs.neurosquad.ai/zh/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/zh/usage)页面相同，针对卡片所在的工作区：`period`、`from` 和 `to`（按本地日期，结束日不含），以及每个智能体的
`inputTokens`、`outputTokens`、`cacheReadTokens`、`cacheWriteTokens` 和 `costMicroUsd`。金额以**整数微美元**计（1 000 000 = 1 美元）。
价格未知的模型 `costMicroUsd` 为 `null`——从不为 `0`——不计入 `totalCostMicroUsd`，并会把 `partial` 设为 `true`。
