@hunterzhu/pulse-cli 0.2.1 → 0.4.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.md +1 -1
- package/dist/bin.js +0 -5
- package/dist/commands/interactive.js +11 -5
- package/dist/components/App.js +56 -8
- package/dist/components/HelpView.js +2 -1
- package/dist/components/InputArea.d.ts +3 -1
- package/dist/components/InputArea.js +13 -20
- package/dist/components/MessageList.d.ts +2 -1
- package/dist/components/MessageList.js +2 -2
- package/dist/components/MessageViewport.d.ts +2 -1
- package/dist/components/MessageViewport.js +63 -3
- package/dist/components/StatusHud.d.ts +1 -2
- package/dist/components/StatusHud.js +2 -2
- package/dist/config.d.ts +0 -2
- package/dist/config.js +0 -1
- package/dist/hooks/useConversation.d.ts +3 -1
- package/dist/hooks/useConversation.js +3 -1
- package/dist/hooks/useRun.d.ts +3 -1
- package/dist/hooks/useRun.js +138 -39
- package/dist/hooks/useSlashCommands.d.ts +1 -1
- package/dist/hooks/useSlashCommands.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/utils/clipboard.d.ts +14 -0
- package/dist/utils/clipboard.js +49 -0
- package/dist/utils/inputHistory.d.ts +11 -0
- package/dist/utils/inputHistory.js +24 -0
- package/dist/utils/mouse.d.ts +10 -5
- package/dist/utils/mouse.js +14 -4
- package/dist/utils/selection.d.ts +13 -0
- package/dist/utils/selection.js +107 -0
- package/dist/utils/transcript.js +43 -10
- package/package.json +2 -2
- package/scripts/postinstall.mjs +50 -3
package/dist/utils/transcript.js
CHANGED
|
@@ -6,25 +6,58 @@ import { renderMarkdownToAnsi } from './markdown.js';
|
|
|
6
6
|
export function transcriptLines(messages, width, verbosity = 'normal') {
|
|
7
7
|
const blocks = messages.map((message) => {
|
|
8
8
|
const text = stripTerminalControls(message.text);
|
|
9
|
-
|
|
10
|
-
return chalk.cyan(`你 › ${text}`);
|
|
9
|
+
const contentWidth = Math.max(8, width - 2);
|
|
11
10
|
if (message.role === 'system')
|
|
12
|
-
return chalk.dim(text);
|
|
11
|
+
return { id: message.id, role: message.role, lines: wrapAnsi(chalk.dim(`· ${text}`), Math.max(8, width), { hard: true, trim: true }).split('\n') };
|
|
12
|
+
if (message.role === 'user') {
|
|
13
|
+
const body = wrapAnsi(text, contentWidth, { hard: true, trim: false }).split('\n');
|
|
14
|
+
return { id: message.id, role: message.role, lines: [chalk.blue.bold('❯ 你发来'), ...body.map((line) => ` ${line}`)] };
|
|
15
|
+
}
|
|
13
16
|
const parts = [];
|
|
14
17
|
const tools = message.toolCalls ?? [];
|
|
15
18
|
if (verbosity !== 'quiet' && tools.length) {
|
|
16
19
|
const failed = tools.filter((tool) => tool.status === 'failed' || tool.status === 'cancelled');
|
|
17
20
|
parts.push(chalk.dim(`工具 · ${tools.filter((tool) => tool.status === 'succeeded').length}/${tools.length} 已完成${failed.length ? ` · ${failed.length} 未成功` : ''}`));
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const statusLabel = { running: '正在调用', succeeded: '已完成', failed: '失败', cancelled: '已取消' };
|
|
22
|
+
if (verbosity === 'verbose') {
|
|
23
|
+
for (const tool of tools) {
|
|
24
|
+
const detail = `${tool.name} · ${statusLabel[tool.status]}\n${JSON.stringify(tool.arguments ?? {}, null, 2)}${tool.result === undefined ? '' : `\n${typeof tool.result === 'string' ? tool.result : JSON.stringify(tool.result, null, 2)}`}`;
|
|
25
|
+
parts.push(chalk.dim(stripTerminalControls(detail)));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const groups = new Map();
|
|
30
|
+
for (const tool of tools) {
|
|
31
|
+
const result = tool.result && typeof tool.result === 'object' && !Array.isArray(tool.result) ? tool.result : undefined;
|
|
32
|
+
const error = tool.status === 'failed' || tool.status === 'cancelled'
|
|
33
|
+
? [result?.code, result?.message].filter((value) => typeof value === 'string').at(-1)
|
|
34
|
+
: undefined;
|
|
35
|
+
const key = `${tool.name}\0${tool.status}\0${error ?? ''}`;
|
|
36
|
+
const group = groups.get(key);
|
|
37
|
+
if (group)
|
|
38
|
+
group.count++;
|
|
39
|
+
else
|
|
40
|
+
groups.set(key, { name: tool.name, status: tool.status, count: 1, ...(error ? { error } : {}) });
|
|
41
|
+
}
|
|
42
|
+
for (const group of groups.values()) {
|
|
43
|
+
parts.push(chalk.dim(`${group.name}${group.count > 1 ? ` × ${group.count}` : ''} · ${statusLabel[group.status]}${group.error ? ` · ${group.error}` : ''}`));
|
|
44
|
+
}
|
|
21
45
|
}
|
|
22
46
|
}
|
|
23
47
|
if (text)
|
|
24
|
-
parts.push(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
parts.push(renderMarkdownToAnsi(text));
|
|
49
|
+
const body = wrapAnsi(parts.join('\n'), contentWidth, { hard: true, trim: false }).split('\n');
|
|
50
|
+
const streamLabel = message.streamStatus === 'streaming' ? chalk.yellow(' · 正在生成') : message.streamStatus === 'incomplete' ? chalk.red(' · 未完成') : '';
|
|
51
|
+
return { id: message.id, role: message.role, lines: [chalk.cyan.bold('Pulse') + streamLabel, ...body.map((line) => ` ${line}`)] };
|
|
52
|
+
}).filter((block) => block.lines.length > 0);
|
|
53
|
+
return blocks.flatMap((block, index) => {
|
|
54
|
+
if (index === blocks.length - 1)
|
|
55
|
+
return block.lines;
|
|
56
|
+
const next = blocks[index + 1];
|
|
57
|
+
const isLiveActivity = (item) => item.role === 'system' && item.id.startsWith('run-activity-');
|
|
58
|
+
const separator = isLiveActivity(block) || isLiveActivity(next) ? [] : [''];
|
|
59
|
+
return [...block.lines, ...separator];
|
|
60
|
+
});
|
|
28
61
|
}
|
|
29
62
|
export function scrollAction(input, key) {
|
|
30
63
|
if (key.pageUp || (key.ctrl && input === 'p'))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A local-first AI task assistant for coding, research, and file workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"registry": "https://registry.npmjs.org"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@hunterzhu/pulse-server": "0.
|
|
38
|
+
"@hunterzhu/pulse-server": "0.4.0",
|
|
39
39
|
"chalk": "^5.4.1",
|
|
40
40
|
"highlight.js": "^11.10.0",
|
|
41
41
|
"ink": "^7.1.1",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,11 +1,58 @@
|
|
|
1
1
|
import { ensurePulseUserConfig, defaultPulseConfigPath } from '../dist/config.js'
|
|
2
2
|
|
|
3
|
+
// Install-time welcome screen. Colors stay off for piped or captured install
|
|
4
|
+
// logs and can be forced with FORCE_COLOR for debugging.
|
|
5
|
+
const colorEnabled =
|
|
6
|
+
process.env.NO_COLOR === undefined &&
|
|
7
|
+
process.env.FORCE_COLOR !== '0' &&
|
|
8
|
+
(process.stdout.isTTY === true || process.env.FORCE_COLOR !== undefined)
|
|
9
|
+
|
|
10
|
+
const paint = (code) => (text) => (colorEnabled ? `\u001b[${code}m${text}\u001b[0m` : text)
|
|
11
|
+
const cyan = paint('36')
|
|
12
|
+
const bold = paint('1')
|
|
13
|
+
const dim = paint('2')
|
|
14
|
+
|
|
15
|
+
const banner = [
|
|
16
|
+
'██████╗ ██╗ ██╗██╗ ███████╗███████╗',
|
|
17
|
+
'██╔══██╗██║ ██║██║ ██╔════╝██╔════╝',
|
|
18
|
+
'██████╔╝██║ ██║██║ ███████╗█████╗ ',
|
|
19
|
+
'██╔═══╝ ██║ ██║██║ ╚════██║██╔══╝ ',
|
|
20
|
+
'██║ ╚██████╔╝███████╗███████║███████╗',
|
|
21
|
+
'╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝',
|
|
22
|
+
].map((line) => cyan(line)).join('\n')
|
|
23
|
+
|
|
24
|
+
function printWelcome({ configPath, error } = {}) {
|
|
25
|
+
const lines = [
|
|
26
|
+
'',
|
|
27
|
+
banner,
|
|
28
|
+
'',
|
|
29
|
+
` ${bold('Pulse')} - a neural-signal like AI assistant for coding, research and file tasks.`,
|
|
30
|
+
'',
|
|
31
|
+
` ${dim('Get started:')}`,
|
|
32
|
+
` ${cyan('cd project_dir')}`,
|
|
33
|
+
` ${cyan('pulse')}`,
|
|
34
|
+
'',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
if (configPath) {
|
|
38
|
+
// Print the fully resolved absolute path on its own, uncolored line so it
|
|
39
|
+
// can be copied straight out of the terminal without ANSI escape codes.
|
|
40
|
+
lines.push(` ${dim('Config ready at')}`)
|
|
41
|
+
lines.push(` ${configPath}`)
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(` ${dim('Config not created:')} ${error}`)
|
|
44
|
+
lines.push(` ${dim('Run "pulse setup" later to create it.')}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lines.push('')
|
|
48
|
+
console.log(lines.join('\n'))
|
|
49
|
+
}
|
|
50
|
+
|
|
3
51
|
try {
|
|
4
52
|
const configPath = defaultPulseConfigPath()
|
|
5
53
|
await ensurePulseUserConfig(configPath)
|
|
6
|
-
|
|
54
|
+
printWelcome({ configPath })
|
|
7
55
|
} catch (error) {
|
|
8
56
|
const message = error instanceof Error ? error.message : String(error)
|
|
9
|
-
|
|
10
|
-
console.warn('Run "pulse setup" later to create it.')
|
|
57
|
+
printWelcome({ error: message })
|
|
11
58
|
}
|