dgxay-code-cli 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README-zh.md CHANGED
@@ -66,17 +66,48 @@ dgxay run -p "总结这个仓库的架构" -o json | jq -r .text
66
66
 
67
67
  ## 交互模式
68
68
 
69
- 进��� `dgxay` 后:
69
+ 进入 `dgxay` 后,界面由三部分组成:
70
+
71
+ - **输入框**:一个带边框的方框,宽度随终端自适应,长文本自动折行
72
+ - **命令菜单**:输入 `/` 时在输入框上方/下方弹出,可上下选择
73
+ - **思考区**:模型思考时实时显示计时与思考内容
70
74
 
71
75
  | 输入 | 作用 |
72
76
  | --- | --- |
73
77
  | 直接输入文字 | 发送给模型 |
78
+ | `/` | 弹出命令菜单 |
79
+ | `↑` / `↓` | 菜单打开时选择命令;菜单关闭时 `↑` 回填上一条提问 |
80
+ | `Tab` | 补全当前选中的命令 |
81
+ | `Enter` | 菜单打开时执行选中命令;否则发送消息 |
82
+ | `Esc` | 关闭菜单 |
74
83
  | `!命令` | 执行终端命令,输出进入对话上下文,例如 `!git status` |
75
- | `/` | 打开斜杠命令补全 |
76
84
  | 行尾 `\` | 续行,输入多行消息 |
77
85
  | `Ctrl+C` | 生成中中断;空闲时退出;输入框有内容时清空 |
78
86
  | `Ctrl+D` | 退出 |
79
- | `↑` / `↓` | 浏览历史输入 |
87
+
88
+ ### 语言
89
+
90
+ 界面默认英文。想切回中文:`DGXAY_LANG=zh dgxay`。
91
+ AI 的回复语言跟随你的提问语言。
92
+
93
+ ### 思考过程
94
+
95
+ 模型返回思考内容时,终端会实时显示:
96
+
97
+ ```
98
+ ⠿ Thinking 2.4s 当前正在考虑的部分…
99
+ ```
100
+
101
+ 思考结束后折叠为一行摘要,并把完整思考内容以灰色缩进段落列出:
102
+
103
+ ```
104
+ ⌁ Thought for 12.8s · 1450 chars
105
+ │ 先看一下目录结构……
106
+ │ 然后读入口文件……
107
+ ```
108
+
109
+ 想只看摘要不显示详细内容:`DGXAY_SHOW_REASONING=0`。
110
+
80
111
 
81
112
  ### 斜杠命令
82
113
 
@@ -155,7 +186,11 @@ dgxay doctor 自检
155
186
  | `DGXAY_HOME` | 覆盖数据目录 |
156
187
  | `DGXAY_API_KEY` | `config init` 时用作 API Key |
157
188
  | `DGXAY_TOKEN_KEY` | 给 OAuth 令牌加解密口令(不设置则按明文 base64 保存) |
158
- | `DGXAY_SHOW_REASONING=1` | 实时显示思考过程(默认折叠为一行摘要) |
189
+ | `DGXAY_LANG=zh` | 界面切成中文(默认英文) |
190
+ | `DGXAY_SHOW_REASONING=0` | 只显示思考摘要,不列出思考内容 |
191
+ | `DGXAY_REASONING_LINES=6` | 思考内容最多显示几行 |
192
+ | `DGXAY_NO_CURSOR_UP=1` | 强制降级为单行输入框 |
193
+ | `DGXAY_ASCII=1` | 强制用 ASCII 框线(+ - |) |
159
194
  | `DGXAY_COLOR=always\|never` | 强制开关颜色 |
160
195
  | `NO_COLOR` | 关闭颜色 |
161
196
  | `DGXAY_DEBUG=1` | 输出调试信息与堆栈 |
@@ -163,11 +198,18 @@ dgxay doctor 自检
163
198
 
164
199
  ## 终端兼容性
165
200
 
166
- 流式输出只使用 `\r`(回到行首)和空格覆盖两种所有终端都支持的能力,**不依赖光标上移或清行序列**——
167
- 这两类序列在部分 Windows 控制台组合下会被忽略,导致每段增量都变成新的一行。
201
+ 启动时会探测终端能力,分两种模式:
202
+
203
+ **支持光标上移(Windows Terminal、VSCode、iTerm、大多数 Linux 终端)**
204
+ 输入框是真正的方框,命令菜单可以原地刷新、上下选择不残留。
205
+
206
+ **不支持光标上移(老式 conhost)**
207
+ 自动降级为单行 `❯` 提示,只做行内刷新,不会出现无法擦除的叠加错位。
208
+
209
+ 流式正文始终只使用 `\r` 回行首 + 空格擦尾,不依赖光标上移;生成过程中只在最后一行刷新预览,
210
+ 结束时一次性按 Markdown 完整排版。所以正文每段只会出现一次。
168
211
 
169
- 渲染分两步:生成过程中只在最后一行实时刷新预览;生成结束时一次性地按 Markdown 完整排版输出
170
- (标题、列表、表格、代码块对齐)。所以在任何终端里每段正文都只会出现一次,不会堆积。
212
+ 想强制降级:`DGXAY_NO_CURSOR_UP=1`;想强制 ASCII 框线:`DGXAY_ASCII=1`。
171
213
 
172
214
  ## 与桌面版的差异
173
215
 
package/README.md CHANGED
@@ -66,15 +66,47 @@ dgxay run -p "summarize this repo's architecture" -o json | jq -r .text
66
66
 
67
67
  ## Interactive mode
68
68
 
69
+ The interface has three parts:
70
+
71
+ - **Input box** — a bordered box that adapts to the terminal width and wraps long input
72
+ - **Command menu** — appears when you type `/`, navigable with the arrow keys
73
+ - **Thinking area** — shows a live timer and the model's reasoning while it works
74
+
69
75
  | Input | Action |
70
76
  | --- | --- |
71
77
  | text | Send to the model |
78
+ | `/` | Open the command menu |
79
+ | `↑` / `↓` | Menu open: move the selection. Menu closed: `↑` recalls your previous prompt |
80
+ | `Tab` | Complete the selected command |
81
+ | `Enter` | Menu open: run the selected command. Otherwise: send the message |
82
+ | `Esc` | Close the menu |
72
83
  | `!command` | Run a shell command; output joins the context, e.g. `!git status` |
73
- | `/` | Slash-command completion |
74
84
  | trailing `\` | Continue on the next line |
75
85
  | `Ctrl+C` | Abort while generating; clear the input when non-empty; exit when idle |
76
86
  | `Ctrl+D` | Exit |
77
- | `↑` / `↓` | Input history |
87
+
88
+ ### Language
89
+
90
+ The interface is English by default. Switch to Chinese with `DGXAY_LANG=zh dgxay`.
91
+ The AI replies in the language you write in.
92
+
93
+ ### Thinking
94
+
95
+ While the model reasons, the terminal shows a live timer:
96
+
97
+ ```
98
+ ⠿ Thinking 2.4s latest reasoning fragment…
99
+ ```
100
+
101
+ When it finishes, that folds into a one-line summary followed by the full reasoning in grey:
102
+
103
+ ```
104
+ ⌁ Thought for 12.8s · 1450 chars
105
+ │ First I'll look at the directory layout…
106
+ │ Then read the entry file…
107
+ ```
108
+
109
+ Set `DGXAY_SHOW_REASONING=0` to keep only the summary.
78
110
 
79
111
  ### Slash commands
80
112
 
@@ -138,7 +170,11 @@ API keys are stored **in plain text** in `config.json`. Use `DGXAY_HOME=/tmp/tes
138
170
  | `DGXAY_HOME` | Override the data directory |
139
171
  | `DGXAY_API_KEY` | API key used by `config init` |
140
172
  | `DGXAY_TOKEN_KEY` | Passphrase that encrypts OAuth tokens (plain base64 otherwise) |
141
- | `DGXAY_SHOW_REASONING=1` | Stream the reasoning text (collapsed to one line by default) |
173
+ | `DGXAY_LANG=zh` | Use the Chinese interface (English by default) |
174
+ | `DGXAY_SHOW_REASONING=0` | Show only the thinking summary, not the reasoning text |
175
+ | `DGXAY_REASONING_LINES=6` | How many reasoning lines to display |
176
+ | `DGXAY_NO_CURSOR_UP=1` | Force the single-line fallback input |
177
+ | `DGXAY_ASCII=1` | Force ASCII box drawing characters |
142
178
  | `DGXAY_COLOR=always\|never` | Force colours on/off |
143
179
  | `NO_COLOR` | Disable colours |
144
180
  | `DGXAY_DEBUG=1` | Debug output and stack traces |
@@ -146,13 +182,18 @@ API keys are stored **in plain text** in `config.json`. Use `DGXAY_HOME=/tmp/tes
146
182
 
147
183
  ## Terminal compatibility
148
184
 
149
- Streaming output relies only on `\r` (carriage return) and space padding — capabilities every terminal supports.
150
- It never uses cursor-up or erase-line sequences: some Windows console combinations silently ignore them, which
151
- makes every delta arrive as a new line.
185
+ The terminal is probed at startup and the UI adapts:
186
+
187
+ **Cursor-up supported** (Windows Terminal, VSCode, iTerm, most Linux terminals)
188
+ The input is a real box and the command menu refreshes in place without leftovers.
189
+
190
+ **Cursor-up unsupported** (legacy conhost)
191
+ Falls back to a single-line `❯` prompt with in-place redraw, so nothing can stack up.
192
+
193
+ Streaming text always uses only `\r` and space padding; while generating, just the last line is refreshed, and
194
+ the whole reply is laid out once as Markdown at the end. Every paragraph appears exactly once.
152
195
 
153
- Rendering happens in two steps: while generating, only the current line is refreshed as a plain-text preview;
154
- when generation ends, the whole reply is laid out once as Markdown (headings, lists, tables, code blocks).
155
- Every paragraph therefore appears exactly once in any terminal.
196
+ Force the fallback with `DGXAY_NO_CURSOR_UP=1`, or ASCII borders with `DGXAY_ASCII=1`.
156
197
 
157
198
  ## Differences from the desktop app
158
199
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dgxay-code-cli",
3
3
  "productName": "DgxayCode CLI",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "description": "DgxayCode 命令行版:多协议 AI 编码助手,支持工作空间、四档权限、自动审查、技能与 MCP。",
6
6
  "author": "Dgxay",
7
7
  "license": "GPL-3.0-only",
package/src/cli/ansi.js CHANGED
@@ -87,25 +87,51 @@ function isInteractive() {
87
87
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
88
88
  }
89
89
 
90
- function termWidth() {
91
- const width = Number(process.stdout.columns) || 0;
90
+ function termWidth(stream = process.stdout) {
91
+ const width = Number(stream.columns) || 0;
92
92
  if (width > 0) return Math.max(20, width);
93
93
  return 80;
94
94
  }
95
95
 
96
- function useUnicode() {
96
+ /**
97
+ * 终端能否安全输出 Unicode 框线字符。
98
+ * Windows Terminal / VSCode / 现代 shell 都支持;只有老的 conhost 需要降级成 ASCII。
99
+ */
100
+ function useUnicode(stream = process.stdout) {
97
101
  if (process.env.DGXAY_ASCII === '1') return false;
98
- if (process.platform === 'win32') {
99
- const cp = process.stdout.isTTY ? 65001 : 0;
100
- return cp === 65001 || Number(process.stdout.columns) > 0;
101
- }
102
- return true;
102
+ if (process.platform !== 'win32') return true;
103
+ if (process.env.WT_SESSION || process.env.TERM_PROGRAM || process.env.ConEmuANSI) return true;
104
+ if (/UTF-?8/i.test(process.env.LANG || process.env.LC_ALL || '')) return true;
105
+ // 能拿到列数说明是真实交互终端;Windows 10+ 的 PowerShell 用 Consolas 渲染框线没有问题
106
+ return Boolean(stream.columns);
103
107
  }
104
108
 
105
109
  function clearLine() {
106
110
  if (process.stdout.isTTY) process.stdout.write('\r\x1b[2K');
107
111
  }
108
112
 
113
+ /* --- 光标与屏幕控制(供输入框 / 区域重绘使用) --- */
114
+
115
+ /** 上移 n 行(n <= 0 时返回空串) */
116
+ function cursorUp(n = 1) {
117
+ return n > 0 ? `\x1b[${n}A` : '';
118
+ }
119
+
120
+ /** 下移 n 行 */
121
+ function cursorDown(n = 1) {
122
+ return n > 0 ? `\x1b[${n}B` : '';
123
+ }
124
+
125
+ /** 清整行(光标不动) */
126
+ function eraseLine() {
127
+ return '\x1b[2K';
128
+ }
129
+
130
+ /** 清屏并把光标移到左上角 */
131
+ function clearScreen(stream = process.stdout) {
132
+ stream.write('\x1b[2J\x1b[H');
133
+ }
134
+
109
135
  function hideCursor() {
110
136
  if (process.stdout.isTTY) process.stdout.write('\x1b[?25l');
111
137
  }
@@ -493,6 +519,10 @@ module.exports = {
493
519
  termWidth,
494
520
  useUnicode,
495
521
  clearLine,
522
+ eraseLine,
523
+ clearScreen,
524
+ cursorUp,
525
+ cursorDown,
496
526
  hideCursor,
497
527
  showCursor,
498
528
  stripAnsi,
@@ -126,14 +126,14 @@ async function askApproval(request, options = {}) {
126
126
  if (!interactive) {
127
127
  // 非交互:一律拒绝,并提示如何绕过
128
128
  process.stderr.write(
129
- `${COLORS.yellow('[需授权]')} ${request.title}:${request.operation}\n` +
130
- `${COLORS.dim(' 当前不是交互终端,已自动拒绝。请改用完全权限(--permission full)或交互式运行。')}\n`
129
+ `${COLORS.yellow('[permission]')} ${request.title}:${request.operation}\n` +
130
+ `${COLORS.dim(' Not an interactive terminal — denied. Use --permission full or run interactively.')}\n`
131
131
  );
132
132
  return false;
133
133
  }
134
134
 
135
135
  const lines = [];
136
- lines.push({ text: `${COLORS.faint('操作')} ${COLORS.bold(request.operation)}`, raw: `操作 ${request.operation}` });
136
+ lines.push({ text: `${COLORS.faint('op')} ${COLORS.bold(request.operation)}`, raw: `op ${request.operation}` });
137
137
  for (const line of String(request.detail || '').split('\n')) {
138
138
  if (!line.trim()) continue;
139
139
  const idx = line.indexOf(':');
@@ -152,12 +152,12 @@ async function askApproval(request, options = {}) {
152
152
  }
153
153
  if (request.workspace) {
154
154
  lines.push({ text: '', raw: '' });
155
- lines.push({ text: COLORS.faint(`工作空间 ${request.workspace}`), raw: `工作空间 ${request.workspace}` });
155
+ lines.push({ text: COLORS.faint(`workspace ${request.workspace}`), raw: `workspace ${request.workspace}` });
156
156
  }
157
157
  if (request.review) {
158
158
  lines.push({ text: '', raw: '' });
159
- const who = request.reviewer ? `审查模型 ${request.reviewer}` : '自动审查';
160
- lines.push({ text: COLORS.red(`${who} 判定可疑`), raw: `${who} 判定可疑` });
159
+ const who = request.reviewer ? `reviewer ${request.reviewer}` : 'auto-review';
160
+ lines.push({ text: COLORS.red(`${who} flagged this`), raw: `${who} flagged this` });
161
161
  for (const piece of wrapText(request.review, 58)) {
162
162
  lines.push({ text: COLORS.dim(` ${piece}`), raw: ` ${piece}` });
163
163
  }
@@ -165,20 +165,20 @@ async function askApproval(request, options = {}) {
165
165
 
166
166
  process.stdout.write(`${frameText(lines, { title: request.title })}\n`);
167
167
 
168
- const promptText = `${COLORS.accent('允许执行吗?')} ${COLORS.faint('[y] 允许 [n] 拒绝')} `;
168
+ const promptText = `${COLORS.accent('Allow this?')} ${COLORS.faint('[y] yes [n] no')} `;
169
169
  process.stdout.write(promptText);
170
170
  const result = await readKey({ timeout: options.timeout || APPROVAL_TIMEOUT });
171
171
 
172
172
  if (result.key === 'y') {
173
- process.stdout.write(`${COLORS.green('允许')}\n`);
173
+ process.stdout.write(`${COLORS.green('allowed')}\n`);
174
174
  return true;
175
175
  }
176
176
  const label =
177
177
  result.key === 'timeout'
178
- ? COLORS.yellow('等待超时,已拒绝')
178
+ ? COLORS.yellow('timed out — denied')
179
179
  : result.key === 'escape'
180
- ? COLORS.dim('已拒绝')
181
- : COLORS.red('已拒绝');
180
+ ? COLORS.dim('denied')
181
+ : COLORS.red('denied');
182
182
  process.stdout.write(`${label}\n`);
183
183
  return false;
184
184
  }