Skip to content

Latest commit

 

History

History
217 lines (152 loc) · 8.46 KB

File metadata and controls

217 lines (152 loc) · 8.46 KB

Phase 1:本地跑通——入口与交互 / 无 UI

前置:Phase 0 地图已通
目标:本机跑起 CLI;能指出交互与 headless 各自从哪进 main(),以及 UI 如何摸到 GeminiClient
对后端同学:把终端 UI 当「前端」略读,搞清为什么有无 UI,然后尽快进 core

官方对照:

1. 先读这些文件(顺序)

顺序 文件 看什么
1 packages/cli/index.ts 进程入口 / supervisor:memory、relaunch、吞 pty 竞态;再加载真正的 main
2 packages/cli/src/gemini.tsx export async function main():参数、settings、auth、选交互/无 UI
3 packages/cli/src/interactiveCli.tsx ink.render(...) 挂上交互 UI;知入口即可
4 packages/cli/src/nonInteractiveCli.ts headless:一条 prompt 跑完退出
5 packages/cli/src/ui/hooks/useGeminiStream.ts submitQuery / sendMessageStream——UI 如何调用 core

浏览:scripts/start.js(根 npm start 调它)、packages/cli/package.json"bin": { "gemini": ... }

可选扫一眼(不必深读):ui/contexts/KeypressContext.tsxsetRawMode + 解析按键)。

2. 本机步骤

cd /Users/qychen98/Projects/gemini-cli

# 建议 Node 20
node -v   # 应对齐 .nvmrc

npm install
npm run build
npm start

首次运行通常会:

  1. 选主题(交互模式)
  2. 做 Google 账号 / API key 等认证(按你本机环境)
  3. 进入对话提示符

常用用法(开发态把参数透传给 gemini):

npm start                      # 交互
npm start -- -p "summarize README.md"   # 无 UI,跑完退出
npm run debug                  # node --inspect-brk

安装后的用户命令同理:gemini / gemini -p "..." / echo "..." | gemini

只验证「能启动、能发出一轮请求」即可;本阶段不要求改代码。

3. 入口调用链

3.1 进程 → main

npm start
  → scripts/start.js          # 检查 build、设 DEV、spawn node packages/cli
  → packages/cli/index.ts     # 进程壳(见下)
       → import main from ./src/gemini.js
       → main()

生产:@google/gemini-clibin.geminidist/index.js(同源)。

index.ts 做什么(不是业务 main):

  • 轻量父进程 / 重量子进程:默认先 spawn 自己,避免一上来 import Ink/React 拖慢启动(~1.5s)
  • 按机器内存调 --max-old-space-size(可用 settings advanced.autoConfigureMemory 关掉)
  • 子进程以 RELAUNCH_EXIT_CODE(199)退出时循环 relaunch(更新等场景)
  • 吞掉 node-pty 已知 resize/EBADF 竞态;其它 uncaught → stderr + exit
  • 真正业务在子进程分支:动态加载 gemini.tsxmain()

心智:index.ts = bootstrap / process supervisorgemini.tsx = 应用入口

3.2 main 如何分流

gemini.tsxmain() 里(概念上):

main()
  → 解析 argv / settings / workspace
  → 认证与 config 初始化(构造 core 侧 Config / GeminiClient 等)
  → 若交互:startInteractiveUI / interactiveCli
  → 若无 UI(`-p`、piped stdin、非 TTY 等):runNonInteractive(...)

对照 mini 的 run/hello_world.py

mini Gemini CLI
脚本里 DefaultAgent(...).run(task) main() 拼好 config + client,再进 UI 或 headless
无独立 UI 层 Ink 是默认体验,但 loop 不在 React 里实现

3.3 UI 如何碰到 core

交互路径的关键一跳在 useGeminiStream.ts

用户回车
  → submitQuery(...)
  → geminiClient.sendMessageStream(request, signal, prompt_id, ...)
  → for await (event of stream) 更新 UI

工具执行不在 sendMessageStream 内部「偷偷跑完」就结束——CLI 会消费 ToolCallRequest 等事件,经 useToolScheduler(包装 core Scheduler)执行,再把结果送回下一轮。细节见 Phase 2 / 3。

4. 终端交互是怎么做到的(知概念即可)

后端同学可把这一层当成 前端:展示 + 采集输入;agent 核心不在这里。

packages/cli(Ink / 按键 / 渲染)  ≈  前端
packages/core(client / tools / loop)≈  后端

4.1 不是「Ink 让终端有了交互」

交互能力的根基是操作系统 + Node 进程,Ink 只是本项目的 TUI 框架选型:

1. TTY(终端设备)
   stdin/stdout 连着真实终端(process.stdin.isTTY)
   → 才能 raw mode、光标控制、全屏重绘
   pipe / 重定向时 isTTY=false → 走 headless

2. 常驻进程
   不退出,一直听键盘、持续写屏

3. Raw mode + 按键解析
   setRawMode(true):按键立刻进进程(非「回车才送一行」)
   KeypressContext 把 ESC 序列解析成 enter / 方向键 / …
   InputPrompt + text-buffer 维护输入框

4. Ink = 终端版 React(本仓库的 UI 实现)
   ink.render(<App />) → ANSI 字符画;状态变了就重绘
   可选 alternate buffer(独立全屏,退出还原)

一句话:TTY + raw mode + 常驻进程 = 能交互;Ink = Gemini CLI 用来组织「画屏 / 组件状态」的库。

旁注(不必深究):Claude Code 也是 React/Ink 路线(深度定制);Codex CLI 当前主路径是 Rust + Ratatui——共同点是 TUI,不是都必须 Ink。

4.2 为何还要无 UI(headless)

Agent 不只要给人聊,还要给脚本 / CI / 其它程序当「一次函数调」。

场景 交互 UI 的问题 无 UI
管道 cat log | gemini 没有完整 TTY 读 stdin,跑完 exit
CI / 自动化 没人点确认、画不了全屏 gemini -p "..." + exit code
下游解析 ANSI 难 parse --output-format json / stream-json
嵌入 UI 绑死进程 同一套 core;再往上是 SDK

官方叫法:Headless mode —— programmatic interface。
无 UI 不是阉割版,而是第二种客户端:仍走 core loop,只是不挂 Ink。

有 UI  ≈ Web 控制台(给人)
无 UI  ≈ HTTP API / 批处理入口(给机器)
core   ≈ 两边共用的业务服务

5. 两种运行形态对照

形态 入口文件 典型场景
交互 interactiveCli.tsx + Ink hooks 日常 npm start / gemini
无 UI nonInteractiveCli.ts -p、pipe、脚本、CI
SDK(本阶段只知道存在) packages/sdk GeminiCliAgent 嵌入别的程序(Phase 5 可选)

验收时至少亲手跑过 交互;有余力再用一条非交互命令感受 headless(例如 npm start -- -p "hello")。

6. 和 mini Hello World 的对照练习

用一句话填空:

mini:  hello_world 拼装 Agent/Model/Env,然后 agent.run(task)
Gemini: gemini.tsx 拼装 Config/Client/…,然后 ______ 或 ______

参考答案:interactive UI(submitQuery → sendMessageStream) / runNonInteractive

再填一句(心智):

终端 Ink 层对我而言相当于 ______;我该深挖的是 ______。

参考答案:前端 / 展示层core 的 loop 与扩展挂载点(tools / MCP / GEMINI.md / hooks)

7. 验收清单

  • npm install + npm run build + npm start 能起来
  • 能指出进程入口是 packages/cli/index.ts(supervisor),业务 maingemini.tsx
  • 能说出交互 vs nonInteractiveCli 的分流,以及无 UI 存在的原因(脚本 / CI / pipe / 可编程输出)
  • 能用三层解释「为什么终端能交互」:TTY → raw mode → Ink(并知道 Ink ≠ 交互根基)
  • 能在 useGeminiStream.ts 里找到 sendMessageStream 调用点
  • 明白:改 loop / 扩展主要去 core,不是去 Ink 组件

跑通之后请记住:

接下来重点是扩展面(tools / MCP / GEMINI.md / hooks / policy / SDK),不是继续读 Ink。
Phase 2 只花半天到一天摸清 loop 形状与挂载点,然后尽快进 Phase 3–4。

8. 下一阶段

PHASE2-AGENT-LOOP.md:轻读 GeminiClient / Turn,标出扩展挂载点。