🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

终端问令 jiangkk/sayrun

DeepSeek Harness
jiangkk9 天前有提交已提供中文文档扫描:含敏感能力

为终端工作流而生的即时 AI 助手

30.4/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/27 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add jiangkk/sayrun
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · dev
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 9 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
⚠︎ 实装验证未通过(unknown · 2026/10/19) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件
查看扫描证据(共 56 处,展示前 50 条)
  • 读取本地文件
    src/application/run-task.ts:44
    const cwd = await realpath(options.cwd ?? process.cwd());
  • 删除本地文件递归
    src/application/run-task.ts:297
    try { if (volatile) await rm(volatile, { recursive: true, force: true }); }
  • 执行外部命令来自配置/变量
    src/cli/main.ts:46
    const child = spawn(bunExecutable(), [cliEntrypoint, ...argv], { stdio: 'inherit' });
  • 读取本地文件
    src/cli/main.ts:87
    const cwd = await realpath(meta.cwd).catch(() => { throw invalid(`旧任务目录不存在:${meta.cwd}。请先恢复该目录或显式创建新任务`); });
  • 读取本地文件
    src/cli/main.ts:88
    if (!(await stat(cwd)).isDirectory()) throw invalid('旧任务目录不可用');
  • 执行外部命令固定命令
    src/cli/manage.ts:36
    const shell = spawnSync('zsh', ['-i'], { stdio: 'inherit' });
  • 读取本地文件
    src/cli/manage.ts:57
    print({ version, node: process.version, bun: JSON.parse(readFileSync(createRequire(import.meta.url).resolve('bun/package.json'), 'utf8')).version, ope
  • 执行外部命令固定命令
    src/cli/manage.ts:63
    const result = spawnSync('npm', ['view', 'sayrun-shell', 'version', '--registry=https://registry.npmjs.org/', '--json', '--fetch-retries=0', '--fetch-
  • 读取本地文件
    src/cli/version.ts:5
    export const version = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')).version;
  • 读取本地文件
    src/config/load.ts:14
    try { return validateConfig(parse(await readFile(path, 'utf8'))) as ConfigPatch; }
  • 写入本地文件
    src/config/load.ts:57
    try { await copyFile(path, `${path}.bak`); } catch (e) { if (e.code !== 'ENOENT') throw e; }
  • 读取本地文件
    src/execution/process-runner.ts:15
    const cwd = await realpath(args.cwd ?? defaultCwd);
  • 读取本地文件
    src/execution/process-runner.ts:16
    if (!(await stat(cwd)).isDirectory()) throw new Error('执行目录不存在');
  • 执行外部命令来自配置/变量
    src/execution/process-runner.ts:33
    const child = spawn(shell, args, {
  • 执行外部命令固定命令
    src/execution/sudo-session.ts:10
    const child = spawn('/usr/bin/sudo', interactive ? ['-v'] : ['-n', '-v'], { stdio: interactive ? 'inherit' : 'ignore' });
  • 读取本地文件
    src/history/migrate-dsh.ts:56
    path = await realpath(path); const root = join(path, 'sessions'), entries: { file: string; content: string; data: ReturnType<typeof convertDsh>; sourc
  • 读取本地文件
    src/history/migrate-dsh.ts:58
    for (const ent of await readdir(dir, { withFileTypes: true })) {
  • 读取本地文件
    src/history/migrate-dsh.ts:62
    try { if ((await stat(file)).size > 64 * 1024 * 1024) throw new Error('记录超过 64 MiB,保留原文件'); const content = await readFile(file, 'utf8'), data = conve
  • 读取本地文件
    src/history/migrate-dsh.ts:62
    try { if ((await stat(file)).size > 64 * 1024 * 1024) throw new Error('记录超过 64 MiB,保留原文件'); const content = await readFile(file, 'utf8'), data = conve
  • 读取本地文件
    src/history/store.ts:23
    await privateDir(this.base); this.base = await realpath(this.base);
  • 删除本地文件
    src/history/store.ts:30
    finally { await rm(temp, { force: true }); }
  • 读取本地文件
    src/history/store.ts:40
    if ((await lstat(path)).isSymbolicLink() || await realpath(path) !== path) throw invalid('拒绝访问历史目录符号链接');
  • 读取本地文件
    src/history/store.ts:40
    if ((await lstat(path)).isSymbolicLink() || await realpath(path) !== path) throw invalid('拒绝访问历史目录符号链接');
  • 删除本地文件
    src/history/store.ts:51
    return async () => { const owner = await readJson<{ pid: number; token: string } | null>(path, null); if (owner?.token === token) await rm(path, { for
  • 写入本地文件
    src/history/store.ts:57
    await mkdir(gate, { mode: 0o700 }); recovery = true;
  • 删除本地文件
    src/history/store.ts:60
    await rm(path);
  • 删除本地文件递归
    src/history/store.ts:64
    } finally { if (recovery) await rm(gate, { recursive: true, force: true }); }
  • 删除本地文件
    src/history/store.ts:67
    } finally { await rm(temp, { force: true }); }
  • 读取本地文件
    src/history/store.ts:95
    for (const entry of await readdir(this.base, { withFileTypes: true })) {
  • 读取本地文件
    src/history/store.ts:109
    const content = await readFile(join(path, 'events.jsonl'), 'utf8'), lines = content.split('\n'), events: HistoryEvent[] = [];
  • 读取本地文件
    src/history/store.ts:122
    if ((await lstat(file)).isSymbolicLink()) throw new Error('历史输出不允许符号链接');
  • 删除本地文件递归
    src/history/store.ts:148
    try { await rm(await this.owned(meta.sessionId), { recursive: true }); }
  • 写入本地文件
    src/history/store.ts:196
    } finally { db.close(); if (succeeded) { await chmod(temp, 0o600); await rename(temp, index); } else await rm(temp, { force: true }); }
  • 写入本地文件
    src/history/store.ts:196
    } finally { db.close(); if (succeeded) { await chmod(temp, 0o600); await rename(temp, index); } else await rm(temp, { force: true }); }
  • 写入本地文件
    src/platform/atomic-file.ts:6
    await mkdir(path, { recursive: true, mode: 0o700 });
  • 读取本地文件
    src/platform/atomic-file.ts:7
    if ((await lstat(path)).isSymbolicLink()) throw new Error(`拒绝使用符号链接目录:${path}`);
  • 写入本地文件
    src/platform/atomic-file.ts:16
    await rename(temp, path);
  • 删除本地文件
    src/platform/atomic-file.ts:17
    } finally { await file?.close(); await rm(temp, { force: true }); }
  • 读取本地文件
    src/platform/atomic-file.ts:21
    try { return JSON.parse(await readFile(path, 'utf8')) as T; }
  • 读取本地文件
    src/platform/bun-runtime.ts:11
    accessSync(executable, constants.X_OK);
  • 读取本地文件
    src/platform/project-root.ts:5
    const path = await realpath(cwd);
  • 执行外部命令固定命令
    src/platform/project-root.ts:6
    const result = spawnSync('git', ['-C', path, 'rev-parse', '--show-toplevel'], { encoding: 'utf8', timeout: 2000 });
  • 读取本地文件
    src/platform/project-root.ts:7
    return result.status === 0 ? realpath(result.stdout.trim()) : path;
  • 读取本地文件
    src/platform/shell-setup.ts:32
    try { path = await realpath(path); before = await readFile(path, 'utf8'); } catch (e) { if (e.code !== 'ENOENT') throw e; }
  • 读取本地文件
    src/platform/shell-setup.ts:32
    try { path = await realpath(path); before = await readFile(path, 'utf8'); } catch (e) { if (e.code !== 'ENOENT') throw e; }
  • 读取本地文件
    src/platform/shell-setup.ts:37
    let current = ''; try { current = await readFile(plan.path, 'utf8'); } catch (e) { if (e.code !== 'ENOENT') throw e; }
  • 写入本地文件
    src/platform/shell-setup.ts:40
    if (current) await copyFile(plan.path, backup);
  • 执行外部命令来自配置/变量
    src/platform/shell-setup.ts:123
    const result = spawnSync(binary, [...args, path], { stdio: 'inherit' });
  • 读取本地文件
    src/terminal/plain.ts:48
    const command = await readFile(file, 'utf8');
  • 删除本地文件递归
    src/terminal/plain.ts:51
    } finally { this.readline.resume(); await rm(temp, { recursive: true, force: true }); }
扫描于 2026/11/1(0 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

为现有 zsh 工作流配的 AI 助手。用自然语言提问,逐条确认命令,继续干活。由 DeepSeek Harness 驱动。

README

由 DeepSeek 最新模型翻译生成
Sayrun 为终端工作流而生的即时 AI 助手 为用着 DSH、也习惯亲手敲命令的你,把 AI 帮助接进日常终端工作流。 命令照常敲,AI 随时问。 Sayrun(sayrun-shell)是 zsh 的 AI 终端助手,由你已安装的 DeepSeek Harness(DSH)驱动。用自然语言提问,逐条审阅并批准命令,然后接着用你原有的 shell 工作流。 当前支持 zsh 集成 · 复用已认证的 DSH 安装使用 · 快速开始 · 功能特性 · 常见问题 · 文档索引 操作到一半,想查一个命令、理解一段报错,或者让 AI 帮忙检查项目?Sayrun 让你在原来的 zsh 提示符中直接提问,无需先进入独立的 AI 对话界面。需要执行命令时,先查看、编辑或批准;拿到结果后,接着做手头的事。 普通命令、别名、函数和多行输入照常交给 zsh。你可以自己敲命令,也可以随手用自然语言求助,在同一个终端里交替进行。 Sayrun 把你已有的 DSH 能力接入日常 shell 操作。当前内置 DSH 后端,复用已配置的认证,无需重复配置 API Key。 当前为 0.1.x 早期版本,已在 macOS / Apple Silicon 验证。欢迎试用和反馈;平台、后端及执行边界见下文和 安全说明。
展开完整文档(README 剩余 1 节)
Sayrun

为终端工作流而生的即时 AI 助手

为用着 DSH、也习惯亲手敲命令的你,把 AI 帮助接进日常终端工作流。

命令照常敲,AI 随时问。

Sayrun(sayrun-shell)是 zsh 的 AI 终端助手,由你已安装的 DeepSeek Harness(DSH)驱动。用自然语言提问,逐条审阅并批准命令,然后接着用你原有的 shell 工作流。

当前支持 zsh 集成 · 复用已认证的 DSH

安装使用 · 快速开始 · 功能特性 · 常见问题 · 文档索引

操作到一半,想查一个命令、理解一段报错,或者让 AI 帮忙检查项目?Sayrun 让你在原来的 zsh 提示符中直接提问,无需先进入独立的 AI 对话界面。需要执行命令时,先查看、编辑或批准;拿到结果后,接着做手头的事。

普通命令、别名、函数和多行输入照常交给 zsh。你可以自己敲命令,也可以随手用自然语言求助,在同一个终端里交替进行。

Sayrun 把你已有的 DSH 能力接入日常 shell 操作。当前内置 DSH 后端,复用已配置的认证,无需重复配置 API Key。

当前为 0.1.x 早期版本,已在 macOS / Apple Silicon 验证。欢迎试用和反馈;平台、后端及执行边界见下文和 安全说明。

效果预览

命令审批界面:任务描述、命令预览,以及批准、拒绝和编辑操作

上图由项目实际的 OpenTUI 渲染器生成,使用演示任务和路径,未调用模型或执行命令。

安装后可运行 sayrun preview 体验审批与编辑;预览无需配置模型凭证。

安装使用

环境要求

| 项目 | 要求 |
| 系统 | 已在 macOS / Apple Silicon 验证;Linux 和其他架构待验证 |
| 终端 | 交互式终端;当前自然语言输入集成仅支持 zsh |
| Node.js | 22 或更高版本 |
| DSH | 已安装、完成认证且位于 PATH;使用 sdk-minimal,不限制版本号 |

Windows 用户请注意:当前不支持 Windows 原生环境(PowerShell、CMD 等)。WSL 尚未验证,后续考虑支持。

Bun 和 OpenTUI 由 npm 依赖安装,无需额外全局安装 Bun。日常使用不需要 Python。

当前 OpenTUI 依赖声明 Node.js >= 26.4.0。Sayrun 的界面在 Bun 中运行,Node.js 22 已通过安装与终端回归;npm 默认设置下安装可能出现 EBADENGINE 警告。若你的 npm 开启了 engine-strict,请使用 Node.js 26.4.0 或更高版本。

尚未安装 DSH 时,请先按 DeepSeek Harness 官方说明 安装并完成模型认证。Sayrun 需要 dsh --version 能在当前终端运行;只有 npx 启动记录、但 PATH 中没有 dsh 时,可全局安装官方包:

npm install -g @deepseek-ai/dsh
dsh --version

DSH 仍在快速迭代。Sayrun 不按版本号拦截,实际运行需要 sdk-minimal、JSON-RPC 和桥接插件接口兼容;可用 sayrun backend check dsh 检查本地启动情况。认证设置见 官方模型配置文档。

安装到本机

通过 npm 全局安装:

npm install -g sayrun-shell

npm 包名为 sayrun-shell,安装后的终端命令为 sayrun。安装包包含构建后的 JavaScript,无需下载源码或手动构建。

启用终端集成

Sayrun 复用 DSH 已配置的凭证,无需重复配置 API Key。安装完成后执行:

sayrun setup

setup 使用 OpenTUI 展示 Sayrun 字标、安装预览和确认选项,经确认后备份 zsh 配置文件,并写入终端集成。首次在交互式终端运行时,会自动进入已启用 Sayrun 的新 zsh,随后即可直接输入自然语言;exit 返回原终端。已经加载集成的终端运行 setup 会直接刷新当前集成。

脚本调用或使用 --no-shell、--temporary、--zshrc 时不会自动启动新 shell。也可以手动在当前交互式 zsh 中启用:

eval "$(sayrun shell init zsh)"
sayrun doctor

终端集成只需配置一次,以后新开的 zsh 终端会自动加载。配置、历史和诊断等管理操作使用 sayrun 命令。

快速开始

直接输入需求

在原来的 zsh 提示符中直接输入,然后回车:

帮我找出当前目录最大的十个文件,只读取文件信息

也可以尝试:

检查这个项目的目录结构,并说明主要模块的职责查看当前 Git 工作区的变更,帮我整理一份摘要

以上示例分别输入,每次发起一个任务。常规命令、别名、函数和多行命令继续交给 zsh;歧义输入会尝试分类,无法确定时询问你。

日常使用无需添加命令前缀。未启用终端集成,或希望明确发起任务时,也可以使用:

sayrun ask "帮我找出当前目录最大的十个文件,只读取文件信息"

审批与查看结果

1. 查看任务、执行目录和完整命令。
2. 选择批准、拒绝,或先编辑命令;每条新命令都需要审批。
3. 查看命令输出和退出状态,必要时回答补充问题。
4. 阅读任务结论,回到提示符继续操作;启用集成后,同一 zsh 实例和项目中的后续提问默认延续当前会话。

| 按键 | 操作 |
| y / n | 批准 / 拒绝当前命令 |
| ← → / Tab,再按 Enter | 切换并确认操作,默认选中批准 |
| e | 编辑命令;编辑后按 Enter 默认执行修改后的命令 |
| Shift-Enter / Ctrl-S | 编辑时换行 / 仅保存并返回审批 |
| ↑ ↓ / PgUp PgDn | 滚动命令预览 |
| Ctrl-C | 取消当前任务并恢复终端 |

功能特性

| 功能 | 能做什么 |
| 即用即问 | 在原有 zsh 提示符直接输入需求,普通命令与自然语言交替使用 |
| 逐条命令审批 | 展示执行目录和命令,支持批准、拒绝、编辑后执行 |
| 终端交互界面 | OpenTUI 提供键盘选择、多行编辑、追问和滚动;也可切换纯文本模式 |
| 执行反馈 | 展示输出摘要、退出状态和 Markdown 结论,支持取消与超时限制 |
| 统一任务历史 | 按项目查看、搜索、重命名、固定、归档、导入和导出会话 |
| 连续对话 | 同一 zsh 实例和项目复用 DSH 原生会话,上下文自动压缩由 DSH 管理 |
| 分层配置 | 支持用户配置、项目配置和命名配置组合,可查看生效值及其来源 |
| 后端扩展与诊断 | 内置 DSH,支持显式注册外部进程适配器,提供能力检查和环境诊断 |

回看与继续任务

sayrun sessions
sayrun sessions search "构建失败" sayrun sessions show <会话ID> --full sayrun ask --new "开始一个新任务" sayrun resume --last --prompt "根据刚才的结果,再检查一次" sayrun resume <会话ID> --context-only --prompt "使用可见历史继续" sayrun sessions export <会话ID> --format markdown --output /tmp/task.md

启用 zsh 集成后,同一 shell 实例、项目和后端中的提问默认延续当前会话,每次提问记录为独立 run。普通命令(如 ls、git status)和重新加载集成不会切断对话;新终端或新启动的 zsh 使用独立会话。同一 Git 项目内切换子目录保留会话,切换项目会使用独立会话,切回来可继续。普通命令的输出不会自动发送给模型,环境变化需在下一轮重新检查。使用 sayrun ask --new "任务" 主动开始新对话。

DSH 通过 session/resume 恢复原生历史,保留其上下文压缩结果;每轮结束正常关闭后端并保存历史。恢复失败会明确报错,不会静默丢失上下文;可显式使用 --context-only 将可见历史作为上下文创建新分支。旧命令不会自动重放,后续执行仍需重新审批。关闭本工具历史时不自动续接会话;未加载 zsh 集成的独立调用仍默认新建,可用 resume 显式继续。

调整常用配置

sayrun config show --effective # 查看实际配置及来源 sayrun config set ui.mode plain # 所有命令使用纯文本界面 sayrun config set ui.mode opentui # 切回 OpenTUI sayrun config set agent.max_commands 20 sayrun config set agent.max_steps 24 sayrun config set execution.timeout_seconds 60 sayrun backend list sayrun backend check dsh

用户配置默认位于 ~/.config/terminal-agent/config.toml,项目配置使用 .terminal-agent.toml;这些路径沿用旧版命名,以兼容已有配置和历史。涉及后端、模型、预算等字段时,需要先执行 sayrun config trust .。

默认每次任务最多 15 条命令、15 个模型步骤、5 次追问和 600 秒活动时间,等待人工输入时暂停计时;同一会话再次提问会重新计数。预算用完时先汇总进展、卡点和下一步;模型无法收尾时根据执行记录汇总。较长任务可调整预算。详见执行与异常。

常见问题

已经在用 DSH,为什么还需要 Sayrun?

如果你用着 DSH,也习惯在 zsh 里自己敲命令,Sayrun 可以把两种操作接在一起。遇到临时问题,直接在当前提示符提问;需要执行命令时逐条确认,处理完回到提示符,继续手头的工作。模型调用沿用已认证的 DSH,无需为每次求助先进入独立的 AI 对话界面。

安装后为什么自然语言没有生效?

npm 安装提供命令行程序;直接输入自然语言需要 zsh 已加载终端集成。执行 sayrun setup,确认后会自动进入已启用的新 zsh;已加载集成时则刷新当前终端。通过 sayrun shell status 可以查看状态。使用 --no-shell 仅完成配置后,可打开新的 zsh 终端,或执行上面的 shell init zsh 命令立即启用。

postinstall 无法修改安装命令所在父 shell 的回车处理,因此自动配置和启用由 sayrun setup 完成。

管理命令共用 OpenTUI 的配色和展示方式:doctor 显示检查清单,配置、认证、后端和历史显示分组结果;sessions 支持方向键选择任务。默认输出面向阅读,显式 --json、历史导出和 shell init zsh 保留原始输出。窄终端会自动换行和收起大字标,NO_COLOR 禁用强调色,非 TTY、TERM=dumb 或 ui.mode=plain 使用纯文本。可以先运行 sayrun setup --dry-run 查看安装界面。

如果手动关闭过,可用 sayrun shell on 开启;如果配置过仅显式触发模式,使用 sayrun config set routing.mode auto 恢复自动识别。未加载终端集成时,也可以通过 sayrun ask "任务" 使用。

命令会自动执行吗?

每条命令都需要你明确批准。默认执行器在本机启动独立的 zsh -f -c,不加载别名、函数或启动配置;不同命令之间不保留 cd / export。当前不支持需要交互输入的命令或后台任务,也没有提供操作系统沙箱。

需要哪些运行时?

Node.js 运行命令行入口,npm 安装的 Bun 运行 OpenTUI 界面,DSH 提供模型后端。使用者运行构建后的 JavaScript;TypeScript 和 tsx 用于开发,Python 仅用于终端和安装回归测试。

如何排查环境或认证问题?

先执行 sayrun doctor、sayrun backend check dsh 和 sayrun auth status dsh。默认检查不发送模型请求;需要在线验证时使用 sayrun doctor --live,可能产生模型用量。

如何卸载?

sayrun shell uninstall --dry-run
sayrun shell uninstall
npm uninstall -g sayrun-shell

卸载集成会备份 zsh 配置并移除标记区块,已有配置和历史保留。

开发与反馈

git clone https://github.com/jiangkk/sayrun.git
cd sayrun
npm ci --registry=https://registry.npmjs.org
npm test

常规测试使用离线 DSH 替身,无需模型密钥。完整资料从文档索引进入;日常命令见使用手册,参与开发见贡献指南,实现结构见架构文档。

问题反馈请使用 GitHub Issues,并移除日志中的密钥、业务信息和私人路径。安全问题请按 安全策略 私密报告。

许可证

Sayrun 使用 MIT 许可证。DSH 及其他依赖遵循各自的许可证,DSH 由使用者单独安装。
预览分享卡片长什么样 ↓jiangkk/sayrun
分享 · DeepSeek Harness Hub
jiangkk/sayrun

为终端工作流而生的即时 AI 助手

回到顶部