@gird/ccli 1.0.1 → 2.0.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.
Files changed (46) hide show
  1. package/README.md +0 -5
  2. package/dist/args.js +35 -0
  3. package/dist/args.js.map +1 -0
  4. package/dist/config.js +120 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/env.js +39 -0
  7. package/dist/env.js.map +1 -0
  8. package/dist/index.js +36 -226
  9. package/dist/index.js.map +1 -0
  10. package/dist/repl.js +59 -0
  11. package/dist/repl.js.map +1 -0
  12. package/dist/version.js +13 -0
  13. package/dist/version.js.map +1 -0
  14. package/package.json +4 -14
  15. package/.env.example +0 -23
  16. package/CCLI.md +0 -15
  17. package/dist/agent/client-openai.js +0 -166
  18. package/dist/agent/client.js +0 -63
  19. package/dist/agent/factory.js +0 -39
  20. package/dist/agent/loop.js +0 -163
  21. package/dist/agent/pricing.js +0 -28
  22. package/dist/agent/prompt.js +0 -43
  23. package/dist/agent/provider.js +0 -18
  24. package/dist/cli/headless.js +0 -50
  25. package/dist/cli/mentions.js +0 -60
  26. package/dist/cli/session.js +0 -432
  27. package/dist/commands/custom.js +0 -46
  28. package/dist/commands/slash.js +0 -148
  29. package/dist/config/config.js +0 -128
  30. package/dist/config/dotenv.js +0 -59
  31. package/dist/context/project.js +0 -155
  32. package/dist/hooks/hooks.js +0 -82
  33. package/dist/permissions/permissions.js +0 -87
  34. package/dist/session/store.js +0 -81
  35. package/dist/tools/bash.js +0 -97
  36. package/dist/tools/filesystem.js +0 -215
  37. package/dist/tools/index.js +0 -45
  38. package/dist/tools/search.js +0 -0
  39. package/dist/tools/task.js +0 -35
  40. package/dist/tools/todo.js +0 -68
  41. package/dist/tools/types.js +0 -8
  42. package/dist/tools/web.js +0 -108
  43. package/dist/ui/diff.js +0 -105
  44. package/dist/ui/markdown.js +0 -137
  45. package/dist/ui/spinner.js +0 -64
  46. package/dist/ui/theme.js +0 -106
@@ -1,137 +0,0 @@
1
- /**
2
- * 轻量级 Markdown → 终端渲染器。
3
- * 支持标题、粗体/斜体/行内代码、代码块(带语法高亮着色)、列表、引用、分隔线、链接。
4
- */
5
- import { c, brand, visibleWidth } from './theme.js';
6
- // 极简语法着色:对常见关键字 / 字符串 / 注释 / 数字着色
7
- function highlightCode(line, lang) {
8
- let s = line;
9
- // 注释
10
- s = s.replace(/(^|\s)(\/\/.*|#.*)$/g, (_m, p1, p2) => p1 + c.gray(p2));
11
- // 字符串
12
- s = s.replace(/(["'`])(?:\\.|(?!\1).)*\1/g, (m) => c.green(m));
13
- // 数字
14
- s = s.replace(/\b(\d+(?:\.\d+)?)\b/g, (m) => c.brightYellow(m));
15
- // 关键字
16
- const kw = /\b(const|let|var|function|return|if|else|for|while|class|import|export|from|async|await|new|try|catch|finally|throw|def|print|public|private|static|void|int|string|bool|true|false|null|undefined|None|True|False)\b/g;
17
- s = s.replace(kw, (m) => brand.accent(m));
18
- return s;
19
- }
20
- function renderInline(text) {
21
- let s = text;
22
- // 行内代码
23
- s = s.replace(/`([^`]+)`/g, (_m, code) => c.bgGray(c.white(` ${code} `)));
24
- // 粗体
25
- s = s.replace(/\*\*([^*]+)\*\*/g, (_m, t) => c.bold(t));
26
- s = s.replace(/__([^_]+)__/g, (_m, t) => c.bold(t));
27
- // 斜体
28
- s = s.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_m, t) => c.italic(t));
29
- // 链接 [text](url)
30
- s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, t, url) => `${c.underline(brand.accent(t))} ${c.dim(`(${url})`)}`);
31
- return s;
32
- }
33
- export function newMdState() {
34
- return { inCode: false, codeLang: '' };
35
- }
36
- /** 渲染单行 markdown,并就地更新跨行状态。供整体渲染与流式渲染共用。 */
37
- export function renderMarkdownLine(line, state) {
38
- // 代码块围栏
39
- const fence = line.match(/^```(\w*)\s*$/);
40
- if (fence) {
41
- if (!state.inCode) {
42
- state.inCode = true;
43
- state.codeLang = fence[1] || '';
44
- return c.dim(' ┌' + '─'.repeat(48) + (state.codeLang ? ` ${state.codeLang} ` : ''));
45
- }
46
- state.inCode = false;
47
- state.codeLang = '';
48
- return c.dim(' └' + '─'.repeat(48));
49
- }
50
- if (state.inCode) {
51
- return c.dim(' │ ') + highlightCode(line, state.codeLang);
52
- }
53
- // 标题
54
- const h = line.match(/^(#{1,6})\s+(.*)$/);
55
- if (h) {
56
- const level = h[1].length;
57
- const txt = h[2];
58
- if (level === 1)
59
- return brand.primary(c.bold('# ' + txt));
60
- if (level === 2)
61
- return brand.accent(c.bold('## ' + txt));
62
- return c.bold('#'.repeat(level) + ' ' + txt);
63
- }
64
- // 分隔线
65
- if (/^(\s*[-*_]){3,}\s*$/.test(line)) {
66
- return c.dim('─'.repeat(50));
67
- }
68
- // 引用
69
- const q = line.match(/^>\s?(.*)$/);
70
- if (q) {
71
- return c.dim(' ▏ ') + c.italic(c.gray(renderInline(q[1])));
72
- }
73
- // 无序列表
74
- const ul = line.match(/^(\s*)[-*+]\s+(.*)$/);
75
- if (ul) {
76
- return ul[1] + brand.primary('•') + ' ' + renderInline(ul[2]);
77
- }
78
- // 有序列表
79
- const ol = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
80
- if (ol) {
81
- return ol[1] + brand.primary(ol[2] + '.') + ' ' + renderInline(ol[3]);
82
- }
83
- return renderInline(line);
84
- }
85
- /** 将 markdown 文本渲染为带 ANSI 样式的字符串。 */
86
- export function renderMarkdown(md) {
87
- const state = newMdState();
88
- return md
89
- .split('\n')
90
- .map((line) => renderMarkdownLine(line, state))
91
- .join('\n');
92
- }
93
- /**
94
- * 流式 Markdown 渲染器 —— 对流式 token 按「完整行」实时套用 markdown。
95
- * 每收到一个换行就立刻渲染输出该行;末尾未完成的行缓存,待 flush 时渲染。
96
- * 行级粒度可避免终端换行导致的重绘错位,稳健可靠。
97
- */
98
- export class MarkdownStream {
99
- state = newMdState();
100
- partial = '';
101
- write;
102
- constructor(write) {
103
- this.write = write;
104
- }
105
- feed(delta) {
106
- this.partial += delta;
107
- let nl;
108
- while ((nl = this.partial.indexOf('\n')) !== -1) {
109
- const line = this.partial.slice(0, nl);
110
- this.partial = this.partial.slice(nl + 1);
111
- this.write(renderMarkdownLine(line, this.state) + '\n');
112
- }
113
- }
114
- /** 流结束:渲染收尾未完成的行。 */
115
- flush() {
116
- if (this.partial) {
117
- this.write(renderMarkdownLine(this.partial, this.state) + '\n');
118
- this.partial = '';
119
- }
120
- }
121
- /** 是否还有缓存的未渲染内容。 */
122
- get hasPending() {
123
- return this.partial.length > 0;
124
- }
125
- }
126
- /** 画一个简单的方框包裹标题(用于欢迎横幅等)。 */
127
- export function box(lines, opts) {
128
- const color = opts?.color ?? brand.primary;
129
- const width = Math.max(...lines.map((l) => visibleWidth(l)));
130
- const top = color('╭' + '─'.repeat(width + 2) + '╮');
131
- const bottom = color('╰' + '─'.repeat(width + 2) + '╯');
132
- const body = lines.map((l) => {
133
- const pad = ' '.repeat(width - visibleWidth(l));
134
- return color('│ ') + l + pad + color(' │');
135
- });
136
- return [top, ...body, bottom].join('\n');
137
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * 思考动画(Spinner)——在 Agent 推理 / 等待 API 时显示。
3
- */
4
- import { c, brand, cursor } from './theme.js';
5
- const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
6
- const WORDS = [
7
- '思考中',
8
- '推理中',
9
- '规划中',
10
- '分析中',
11
- '处理中',
12
- '组织语言',
13
- ];
14
- export class Spinner {
15
- timer = null;
16
- frame = 0;
17
- label;
18
- startedAt = 0;
19
- active = false;
20
- constructor(label) {
21
- this.label = label ?? WORDS[Math.floor(Math.random() * WORDS.length)];
22
- }
23
- start(label) {
24
- if (this.active)
25
- return;
26
- if (label)
27
- this.label = label;
28
- this.active = true;
29
- this.startedAt = Date.now();
30
- if (!process.stdout.isTTY) {
31
- // 非交互环境:只打印一次提示
32
- process.stdout.write(c.dim(`${this.label}...\n`));
33
- return;
34
- }
35
- cursor.hide();
36
- this.timer = setInterval(() => this.render(), 80);
37
- this.render();
38
- }
39
- setLabel(label) {
40
- this.label = label;
41
- }
42
- render() {
43
- if (!process.stdout.isTTY)
44
- return;
45
- const sym = brand.primary(FRAMES[this.frame % FRAMES.length]);
46
- this.frame++;
47
- const secs = ((Date.now() - this.startedAt) / 1000).toFixed(0);
48
- const line = `${sym} ${c.dim(this.label)} ${c.dim(`(${secs}s · esc 中断)`)}`;
49
- process.stdout.write(`\r${cursor.clearLine}${line}`);
50
- }
51
- stop() {
52
- if (!this.active)
53
- return;
54
- this.active = false;
55
- if (this.timer) {
56
- clearInterval(this.timer);
57
- this.timer = null;
58
- }
59
- if (process.stdout.isTTY) {
60
- process.stdout.write(`\r${cursor.clearLine}`);
61
- cursor.show();
62
- }
63
- }
64
- }
package/dist/ui/theme.js DELETED
@@ -1,106 +0,0 @@
1
- /**
2
- * 终端配色与 ANSI 样式工具。
3
- * 复刻 Claude Code 的「自定义终端 UI」——不依赖 Ink,直接用 ANSI 转义序列渲染。
4
- */
5
- const ESC = '\x1b[';
6
- function code(...n) {
7
- return `${ESC}${n.join(';')}m`;
8
- }
9
- const RESET = code(0);
10
- // 是否启用颜色(尊重 NO_COLOR 与非 TTY 环境)
11
- let colorEnabled = process.env.NO_COLOR === undefined &&
12
- process.env.TERM !== 'dumb' &&
13
- (process.stdout.isTTY ?? false);
14
- export function setColorEnabled(v) {
15
- colorEnabled = v;
16
- }
17
- export function isColorEnabled() {
18
- return colorEnabled;
19
- }
20
- function wrap(open, close) {
21
- return (s) => {
22
- const str = String(s);
23
- if (!colorEnabled)
24
- return str;
25
- return open + str + close;
26
- };
27
- }
28
- // 前景色
29
- export const c = {
30
- reset: RESET,
31
- bold: wrap(code(1), code(22)),
32
- dim: wrap(code(2), code(22)),
33
- italic: wrap(code(3), code(23)),
34
- underline: wrap(code(4), code(24)),
35
- inverse: wrap(code(7), code(27)),
36
- strike: wrap(code(9), code(29)),
37
- black: wrap(code(30), code(39)),
38
- red: wrap(code(31), code(39)),
39
- green: wrap(code(32), code(39)),
40
- yellow: wrap(code(33), code(39)),
41
- blue: wrap(code(34), code(39)),
42
- magenta: wrap(code(35), code(39)),
43
- cyan: wrap(code(36), code(39)),
44
- white: wrap(code(37), code(39)),
45
- gray: wrap(code(90), code(39)),
46
- brightRed: wrap(code(91), code(39)),
47
- brightGreen: wrap(code(92), code(39)),
48
- brightYellow: wrap(code(93), code(39)),
49
- brightBlue: wrap(code(94), code(39)),
50
- brightMagenta: wrap(code(95), code(39)),
51
- brightCyan: wrap(code(96), code(39)),
52
- // 背景色
53
- bgRed: wrap(code(41), code(49)),
54
- bgGreen: wrap(code(42), code(49)),
55
- bgYellow: wrap(code(43), code(49)),
56
- bgBlue: wrap(code(44), code(49)),
57
- bgGray: wrap(code(100), code(49)),
58
- // 256 色 / RGB
59
- rgb: (r, g, b) => wrap(`${ESC}38;2;${r};${g};${b}m`, code(39)),
60
- };
61
- // 品牌主色调(Claude 橙)
62
- export const brand = {
63
- primary: c.rgb(217, 119, 87), // Claude 橙
64
- accent: c.cyan,
65
- ok: c.green,
66
- warn: c.yellow,
67
- err: c.brightRed,
68
- info: c.blue,
69
- };
70
- // 控制序列
71
- export const cursor = {
72
- hide: () => process.stdout.write(`${ESC}?25l`),
73
- show: () => process.stdout.write(`${ESC}?25h`),
74
- up: (n = 1) => `${ESC}${n}A`,
75
- down: (n = 1) => `${ESC}${n}B`,
76
- toColumn: (n = 0) => `${ESC}${n}G`,
77
- clearLine: `${ESC}2K`,
78
- clearToEnd: `${ESC}0J`,
79
- };
80
- /** 去除字符串中的 ANSI 序列,用于计算可见宽度。 */
81
- export function stripAnsi(s) {
82
- // eslint-disable-next-line no-control-regex
83
- return s.replace(/\x1b\[[0-9;]*m/g, '');
84
- }
85
- /** 估算字符串的可见宽度(中文/全角算 2)。 */
86
- export function visibleWidth(s) {
87
- const plain = stripAnsi(s);
88
- let w = 0;
89
- for (const ch of plain) {
90
- const cp = ch.codePointAt(0);
91
- if ((cp >= 0x1100 && cp <= 0x115f) ||
92
- (cp >= 0x2e80 && cp <= 0xa4cf) ||
93
- (cp >= 0xac00 && cp <= 0xd7a3) ||
94
- (cp >= 0xf900 && cp <= 0xfaff) ||
95
- (cp >= 0xfe30 && cp <= 0xfe4f) ||
96
- (cp >= 0xff00 && cp <= 0xff60) ||
97
- (cp >= 0xffe0 && cp <= 0xffe6) ||
98
- (cp >= 0x1f300 && cp <= 0x1faff)) {
99
- w += 2;
100
- }
101
- else {
102
- w += 1;
103
- }
104
- }
105
- return w;
106
- }