@hunterzhu/pulse-cli 0.4.0 → 0.4.1
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 +9 -1
- package/dist/bin.js +5 -2
- package/dist/components/App.js +36 -3
- package/dist/components/HelpView.js +1 -0
- package/dist/components/InputArea.d.ts +4 -1
- package/dist/components/InputArea.js +32 -3
- package/dist/config.d.ts +1 -1
- package/dist/presentation.d.ts +11 -0
- package/dist/presentation.js +64 -0
- package/dist/release-highlights.generated.d.ts +4 -0
- package/dist/release-highlights.generated.js +117 -0
- package/dist/utils/slashCompletion.d.ts +6 -0
- package/dist/utils/slashCompletion.js +11 -0
- package/package.json +4 -3
- package/scripts/postinstall.mjs +6 -49
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @hunterzhu/pulse-cli
|
|
2
2
|
|
|
3
|
+
[简体中文](https://github.com/zhuhengtan/Pulse/blob/main/packages/cli/README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
[Changelog](https://github.com/zhuhengtan/Pulse/blob/main/CHANGELOG.en.md)
|
|
6
|
+
|
|
3
7
|
**A neural-signal like AI assistant for coding, research and file tasks.**
|
|
4
8
|
|
|
5
9
|
Pulse runs in your project folder, keeps conversations on your machine, and can resume unfinished work. Requires Node.js 22+.
|
|
@@ -9,7 +13,7 @@ Pulse runs in your project folder, keeps conversations on your machine, and can
|
|
|
9
13
|
Install the CLI globally so the `pulse` command is on your `PATH`:
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
|
-
npm install -g @hunterzhu/pulse-cli
|
|
16
|
+
npm install -g @hunterzhu/pulse-cli --foreground-scripts
|
|
13
17
|
pulse --version
|
|
14
18
|
```
|
|
15
19
|
|
|
@@ -49,6 +53,10 @@ pulse --read-only
|
|
|
49
53
|
|
|
50
54
|
See `pulse --help` for all options.
|
|
51
55
|
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
This package is licensed under [PolyForm Noncommercial 1.0.0](./LICENSE). Commercial use requires separate permission. This is a source-available noncommercial license, not an OSI-approved open-source license.
|
|
59
|
+
|
|
52
60
|
## Links
|
|
53
61
|
|
|
54
62
|
- [Full CLI configuration guide](https://github.com/zhuhengtan/Pulse/blob/main/docs/cli-config.md)
|
package/dist/bin.js
CHANGED
|
@@ -16,6 +16,7 @@ import { runServiceCommand } from './commands/service.js';
|
|
|
16
16
|
import { runTemplateCommand } from './commands/template.js';
|
|
17
17
|
import { runMcpDoctor } from './commands/mcp.js';
|
|
18
18
|
import { isSameModulePath } from './utils/is-main-module.js';
|
|
19
|
+
import { formatVersionOutput, shouldUseColor } from './presentation.js';
|
|
19
20
|
const packageManifest = createRequire(import.meta.url)('../package.json');
|
|
20
21
|
export const version = packageManifest.version;
|
|
21
22
|
process.stdout.on('error', (error) => {
|
|
@@ -301,7 +302,7 @@ export async function hostOptions(parsed) {
|
|
|
301
302
|
...(taskRouting === undefined ? {} : { taskRouting }),
|
|
302
303
|
capabilityPacks,
|
|
303
304
|
enabledCapabilityPacks,
|
|
304
|
-
capabilityConfig: { skills: skillNames },
|
|
305
|
+
capabilityConfig: capabilitySettings.skills === undefined ? {} : { skills: skillNames },
|
|
305
306
|
activeProviderCode: providerName,
|
|
306
307
|
activeModel: activeModelName,
|
|
307
308
|
...(mockResponse === undefined ? {} : { mockResponse }),
|
|
@@ -320,7 +321,9 @@ export async function main() {
|
|
|
320
321
|
return 0;
|
|
321
322
|
}
|
|
322
323
|
if (parsed.options.version || parsed.options.v) {
|
|
323
|
-
process.stdout.write(
|
|
324
|
+
process.stdout.write(formatVersionOutput(version, {
|
|
325
|
+
color: parsed.options['no-color'] !== true && shouldUseColor(process.env, process.stdout.isTTY === true),
|
|
326
|
+
}));
|
|
324
327
|
return 0;
|
|
325
328
|
}
|
|
326
329
|
if (parsed.command === 'setup') {
|
package/dist/components/App.js
CHANGED
|
@@ -18,11 +18,14 @@ import { useTokenStats } from '../hooks/useTokenStats.js';
|
|
|
18
18
|
import { useSlashCommands } from '../hooks/useSlashCommands.js';
|
|
19
19
|
import { copyToClipboard, isCopyShortcut } from '../utils/clipboard.js';
|
|
20
20
|
import { emptyInputHistory, navigateInputHistory, rememberInput } from '../utils/inputHistory.js';
|
|
21
|
+
import { skillSuggestions } from '../utils/slashCompletion.js';
|
|
21
22
|
export function App({ hostOptions, conversationId: initialConversationId, initialTask, resumeOnStart = false, version, }) {
|
|
22
23
|
const { exit } = useApp();
|
|
23
24
|
const { stdout } = useStdout();
|
|
24
25
|
const [terminalRows, setTerminalRows] = useState(stdout.rows || 24);
|
|
25
26
|
useEffect(() => { const resize = () => setTerminalRows(stdout.rows || 24); stdout.on('resize', resize); return () => { stdout.off('resize', resize); }; }, [stdout]);
|
|
27
|
+
const [skillNames, setSkillNames] = useState([]);
|
|
28
|
+
const [skillError, setSkillError] = useState(null);
|
|
26
29
|
const [mode, setMode] = useState('chat');
|
|
27
30
|
const [mouseEnabled, setMouseEnabled] = useState(true);
|
|
28
31
|
const [selectedText, setSelectedText] = useState('');
|
|
@@ -38,6 +41,11 @@ export function App({ hostOptions, conversationId: initialConversationId, initia
|
|
|
38
41
|
const startedTask = useRef(false);
|
|
39
42
|
const startedResume = useRef(false);
|
|
40
43
|
const { host, error: hostError, ready: hostReady } = useHost(hostOptions);
|
|
44
|
+
const refreshSkills = useCallback(() => {
|
|
45
|
+
if (host)
|
|
46
|
+
void host.listSkills().then(names => { setSkillNames(names); setSkillError(null); }, error => setSkillError(String(error)));
|
|
47
|
+
}, [host]);
|
|
48
|
+
useEffect(refreshSkills, [refreshSkills]);
|
|
41
49
|
const { conversation, messages, addUserMessage, addAssistantMessage, clearMessages, switchConversation, newConversation, createConversation, error: conversationError, } = useConversation({ host, conversationId: initialConversationId });
|
|
42
50
|
const { isRunning, error: runError, approvalRequest, askRequest, lanes, approvalSubmitting, sendMessage, resumeActive, approveAction, replyAsk, cancelRun, } = useRun({
|
|
43
51
|
host,
|
|
@@ -127,7 +135,7 @@ export function App({ hostOptions, conversationId: initialConversationId, initia
|
|
|
127
135
|
await sendMessage(initialTask, target.id);
|
|
128
136
|
})();
|
|
129
137
|
}, [hostReady, conversation, initialTask, isRunning, addUserMessage, addAssistantMessage, createConversation, sendMessage]);
|
|
130
|
-
const { executeCommand, isSlashCommand } = useSlashCommands({
|
|
138
|
+
const { commands, executeCommand, isSlashCommand } = useSlashCommands({
|
|
131
139
|
onMouse: (arg) => setMouseEnabled((current) => arg === 'off' ? false : arg === 'on' ? true : !current),
|
|
132
140
|
onCopy: async () => {
|
|
133
141
|
if (selectedText) {
|
|
@@ -455,6 +463,31 @@ export function App({ hostOptions, conversationId: initialConversationId, initia
|
|
|
455
463
|
if (isSlashCommand(text)) {
|
|
456
464
|
const handled = await executeCommand(text);
|
|
457
465
|
if (!handled) {
|
|
466
|
+
const token = text.split(/\s+/)[0].slice(1).replace(/^skill:/, '');
|
|
467
|
+
let currentSkills = skillNames;
|
|
468
|
+
try {
|
|
469
|
+
if (host) {
|
|
470
|
+
currentSkills = await host.listSkills();
|
|
471
|
+
setSkillNames(currentSkills);
|
|
472
|
+
setSkillError(null);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
setSkillError(String(error));
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (currentSkills.includes(token)) {
|
|
480
|
+
if (isRunning) {
|
|
481
|
+
addAssistantMessage({ id: `skill-busy-${Date.now()}`, role: 'system', text: '请等当前任务完成,或使用 /cancel 后再调用技能。', createdAt: new Date().toISOString() });
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const target = conversation ?? await createConversation();
|
|
485
|
+
if (!target)
|
|
486
|
+
return;
|
|
487
|
+
addUserMessage(text);
|
|
488
|
+
await sendMessage(text, target.id);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
458
491
|
addAssistantMessage({
|
|
459
492
|
id: `command-${Date.now()}`,
|
|
460
493
|
role: 'system',
|
|
@@ -470,7 +503,7 @@ export function App({ hostOptions, conversationId: initialConversationId, initia
|
|
|
470
503
|
addUserMessage(text);
|
|
471
504
|
await sendMessage(text, target.id);
|
|
472
505
|
}
|
|
473
|
-
}, [isSlashCommand, executeCommand, conversation, createConversation, addUserMessage, sendMessage]);
|
|
506
|
+
}, [isSlashCommand, executeCommand, conversation, createConversation, addUserMessage, sendMessage, skillNames, isRunning, addAssistantMessage, host]);
|
|
474
507
|
useInput((input, key) => {
|
|
475
508
|
if (isCopyShortcut(input, key)) {
|
|
476
509
|
if (selectedText)
|
|
@@ -541,6 +574,6 @@ export function App({ hostOptions, conversationId: initialConversationId, initia
|
|
|
541
574
|
}, onInput: () => {
|
|
542
575
|
setApprovalChoiceFocused(false);
|
|
543
576
|
setApprovalInputMode(true);
|
|
544
|
-
} })) : askRequest ? (_jsx(AskPrompt, { request: askRequest, disabled: approvalSubmitting, onReply: (value) => void replyAsk(askRequest.effectId, value) }, askRequest.effectId)) : (
|
|
577
|
+
} })) : askRequest ? (_jsx(AskPrompt, { request: askRequest, disabled: approvalSubmitting, onReply: (value) => void replyAsk(askRequest.effectId, value) }, askRequest.effectId)) : (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [skillError && _jsxs(Text, { color: "red", children: ["\u6280\u80FD\u7D22\u5F15\u5931\u8D25\uFF1A", skillError] }), _jsx(InputArea, { suggestions: [...commands, ...skillSuggestions(skillNames, commands.flatMap(command => [command, ...(command.aliases ?? []).map(name => ({ name, description: command.description }))]))], onSearchSkills: refreshSkills, onSubmit: (txt) => void handleSubmit(txt), onRememberInput: rememberUserInput, onNavigateHistory: navigateUserInputHistory, disabled: approvalSubmitting, focus: !approvalSubmitting, placeholder: isRunning ? '运行中也可以输入;/cancel 可取消当前运行...' : '输入消息或 /help...' })] }))] }));
|
|
545
578
|
}
|
|
546
579
|
export default App;
|
|
@@ -38,6 +38,7 @@ const COMMANDS = [
|
|
|
38
38
|
{ name: '/model [name]', desc: '切换模型' },
|
|
39
39
|
{ name: '/thinking [level]', desc: '设置模型思考深度 (low/medium/high/off)' },
|
|
40
40
|
{ name: '/compact', desc: '手动压缩历史;默认在上下文 90% 时自动压缩' },
|
|
41
|
+
{ name: '/skill:名称 任务', desc: '按需加载技能;输入 / 搜索,↑↓ 选择,Tab 补全' },
|
|
41
42
|
{ name: '/config', desc: '查看当前运行时配置' },
|
|
42
43
|
],
|
|
43
44
|
},
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { type SlashSuggestion } from '../utils/slashCompletion.js';
|
|
1
2
|
interface Props {
|
|
3
|
+
suggestions?: SlashSuggestion[];
|
|
4
|
+
onSearchSkills?: () => void;
|
|
2
5
|
onSubmit: (text: string) => void;
|
|
3
6
|
onRememberInput: (text: string) => void;
|
|
4
7
|
onNavigateHistory: (direction: 'up' | 'down', currentValue: string) => string | undefined;
|
|
@@ -6,5 +9,5 @@ interface Props {
|
|
|
6
9
|
focus?: boolean;
|
|
7
10
|
placeholder?: string;
|
|
8
11
|
}
|
|
9
|
-
export declare function InputArea({ onSubmit, onRememberInput, onNavigateHistory, disabled, focus, placeholder }: Props): import("react").JSX.Element;
|
|
12
|
+
export declare function InputArea({ onSubmit, onRememberInput, onNavigateHistory, disabled, focus, placeholder, suggestions, onSearchSkills }: Props): import("react").JSX.Element;
|
|
10
13
|
export {};
|
|
@@ -2,15 +2,44 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
3
|
import TextInput from './TextInput.js';
|
|
4
4
|
import { useState } from 'react';
|
|
5
|
+
import { searchSlashSuggestions } from '../utils/slashCompletion.js';
|
|
5
6
|
import { theme } from '../theme.js';
|
|
6
|
-
export function InputArea({ onSubmit, onRememberInput, onNavigateHistory, disabled, focus = true, placeholder }) {
|
|
7
|
+
export function InputArea({ onSubmit, onRememberInput, onNavigateHistory, disabled, focus = true, placeholder, suggestions = [], onSearchSkills }) {
|
|
7
8
|
const [value, setValue] = useState('');
|
|
9
|
+
const [completionRevision, setCompletionRevision] = useState(0);
|
|
10
|
+
const [selected, setSelected] = useState(0);
|
|
11
|
+
const [dismissed, setDismissed] = useState(false);
|
|
12
|
+
const matches = dismissed ? [] : searchSlashSuggestions(value, suggestions);
|
|
13
|
+
const chosen = Math.min(selected, Math.max(0, matches.length - 1));
|
|
14
|
+
const changeValue = (next) => {
|
|
15
|
+
if (next.startsWith('/') && !value.startsWith('/'))
|
|
16
|
+
onSearchSkills?.();
|
|
17
|
+
setSelected(0);
|
|
18
|
+
setDismissed(false);
|
|
19
|
+
setValue(next);
|
|
20
|
+
};
|
|
8
21
|
const [accumulatedLines, setAccumulatedLines] = useState([]);
|
|
9
22
|
useInput((input, key) => {
|
|
10
23
|
if (disabled || !focus)
|
|
11
24
|
return;
|
|
12
25
|
if (key.ctrl || key.meta || key.pageUp || key.pageDown)
|
|
13
26
|
return;
|
|
27
|
+
if (matches.length && !accumulatedLines.length) {
|
|
28
|
+
if (key.escape) {
|
|
29
|
+
setDismissed(true);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (key.upArrow || key.downArrow) {
|
|
33
|
+
setSelected(Math.max(0, Math.min(matches.length - 1, chosen + (key.upArrow ? -1 : 1))));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (key.tab) {
|
|
37
|
+
setValue(`${matches[chosen].name} `);
|
|
38
|
+
setCompletionRevision(revision => revision + 1);
|
|
39
|
+
setSelected(0);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
14
43
|
if (key.return) {
|
|
15
44
|
if (key.shift) {
|
|
16
45
|
setAccumulatedLines((current) => [...current, value]);
|
|
@@ -35,7 +64,7 @@ export function InputArea({ onSubmit, onRememberInput, onNavigateHistory, disabl
|
|
|
35
64
|
const restoreInput = (text) => {
|
|
36
65
|
const lines = text.split('\n');
|
|
37
66
|
setAccumulatedLines(lines.slice(0, -1));
|
|
38
|
-
|
|
67
|
+
changeValue(lines.at(-1) ?? '');
|
|
39
68
|
};
|
|
40
69
|
const handleSubmit = (text) => {
|
|
41
70
|
if (text.endsWith('\\')) {
|
|
@@ -56,5 +85,5 @@ export function InputArea({ onSubmit, onRememberInput, onNavigateHistory, disabl
|
|
|
56
85
|
};
|
|
57
86
|
const promptChar = accumulatedLines.length > 0 ? '…' : '›';
|
|
58
87
|
const promptColor = disabled ? theme.dim : theme.primary;
|
|
59
|
-
return (_jsxs(Box, { flexDirection: "column", children: [accumulatedLines.map((line, idx) => (_jsxs(Box, { children: [_jsxs(Text, { color: promptColor, children: [promptChar, " "] }), _jsx(Text, { children: line })] }, idx))), _jsxs(Box, { children: [_jsxs(Text, { color: promptColor, children: [promptChar, " "] }), disabled ? (_jsx(Text, { color: theme.dim, children: placeholder || '处理中...' })) : (_jsx(TextInput, { focus: focus, value: value, onChange:
|
|
88
|
+
return (_jsxs(Box, { flexDirection: "column", children: [matches.length > 0 && accumulatedLines.length === 0 && (_jsxs(Box, { flexDirection: "column", children: [matches.slice(Math.max(0, chosen - 5), Math.max(0, chosen - 5) + 6).map(item => (_jsxs(Text, { color: item === matches[chosen] ? theme.primary : theme.dim, children: [item === matches[chosen] ? '› ' : ' ', item.name, " ", item.description] }, item.name))), _jsxs(Text, { dimColor: true, children: ["\u2191\u2193 \u9009\u62E9 \u00B7 Tab \u8865\u5168 \u00B7 \u8F93\u5165\u4EFB\u52A1\u540E Enter \u53D1\u9001 \u00B7 Esc \u6536\u8D77\uFF08", matches.length, " \u9879\uFF09"] })] })), accumulatedLines.map((line, idx) => (_jsxs(Box, { children: [_jsxs(Text, { color: promptColor, children: [promptChar, " "] }), _jsx(Text, { children: line })] }, idx))), _jsxs(Box, { children: [_jsxs(Text, { color: promptColor, children: [promptChar, " "] }), disabled ? (_jsx(Text, { color: theme.dim, children: placeholder || '处理中...' })) : (_jsx(TextInput, { focus: focus, value: value, onChange: changeValue, placeholder: placeholder || '' }, completionRevision))] })] }));
|
|
60
89
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface PulseCliMcpServer {
|
|
|
47
47
|
export interface PulseCliCapabilities {
|
|
48
48
|
/** IDs must be explicitly enabled; workspace config cannot enable host extensions. */
|
|
49
49
|
enabled?: string[];
|
|
50
|
-
/**
|
|
50
|
+
/** Optional discovery allow-list of skill directory names; omitted discovers all. Bodies load only on invocation. */
|
|
51
51
|
skills?: string[];
|
|
52
52
|
/** Additional absolute roots explicitly trusted by the user for installed skills. */
|
|
53
53
|
trustedSkillRoots?: string[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PresentationOptions {
|
|
2
|
+
color?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export declare function shouldUseColor(env: NodeJS.ProcessEnv, isTTY: boolean): boolean;
|
|
5
|
+
export declare function formatVersionOutput(version: string, options?: PresentationOptions): string;
|
|
6
|
+
export interface InstallWelcomeOptions extends PresentationOptions {
|
|
7
|
+
version: string;
|
|
8
|
+
configPath?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function formatInstallWelcome(options: InstallWelcomeOptions): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { releaseHighlights } from './release-highlights.generated.js';
|
|
2
|
+
const bannerLines = [
|
|
3
|
+
'██████╗ ██╗ ██╗██╗ ███████╗███████╗',
|
|
4
|
+
'██╔══██╗██║ ██║██║ ██╔════╝██╔════╝',
|
|
5
|
+
'██████╔╝██║ ██║██║ ███████╗█████╗ ',
|
|
6
|
+
'██╔═══╝ ██║ ██║██║ ╚════██║██╔══╝ ',
|
|
7
|
+
'██║ ╚██████╔╝███████╗███████║███████╗',
|
|
8
|
+
'╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝',
|
|
9
|
+
];
|
|
10
|
+
function paint(code, enabled, value) {
|
|
11
|
+
return enabled ? `\u001b[${code}m${value}\u001b[0m` : value;
|
|
12
|
+
}
|
|
13
|
+
export function shouldUseColor(env, isTTY) {
|
|
14
|
+
return env.NO_COLOR === undefined && env.FORCE_COLOR !== '0' && (isTTY || env.FORCE_COLOR !== undefined);
|
|
15
|
+
}
|
|
16
|
+
function banner(color) {
|
|
17
|
+
return bannerLines.map((line) => paint('36', color, line)).join('\n');
|
|
18
|
+
}
|
|
19
|
+
export function formatVersionOutput(version, options = {}) {
|
|
20
|
+
const color = options.color === true;
|
|
21
|
+
const highlights = releaseHighlights[version];
|
|
22
|
+
const lines = [
|
|
23
|
+
banner(color),
|
|
24
|
+
'',
|
|
25
|
+
` ${paint('1', color, 'Pulse')} ${paint('2', color, `v${version}`)}`,
|
|
26
|
+
'',
|
|
27
|
+
` ${paint('1', color, '本版本亮点 / What’s new in this version')}`,
|
|
28
|
+
];
|
|
29
|
+
if (highlights?.length) {
|
|
30
|
+
for (const item of highlights)
|
|
31
|
+
lines.push(` • ${item.zh}`, ` ${item.en}`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
lines.push(' 暂无此版本的更新亮点记录。', ' No release highlights are available for this version yet.');
|
|
35
|
+
}
|
|
36
|
+
lines.push('');
|
|
37
|
+
return `${lines.join('\n')}\n`;
|
|
38
|
+
}
|
|
39
|
+
export function formatInstallWelcome(options) {
|
|
40
|
+
const { color = false, version, configPath, error } = options;
|
|
41
|
+
const cyan = (value) => paint('36', color, value);
|
|
42
|
+
const dim = (value) => paint('2', color, value);
|
|
43
|
+
const lines = [
|
|
44
|
+
'',
|
|
45
|
+
banner(color),
|
|
46
|
+
'',
|
|
47
|
+
` ${paint('1', color, 'Pulse')} v${version} — 面向编程、研究与文件工作的 AI 助手`,
|
|
48
|
+
` ${paint('1', color, 'Pulse')} — an AI assistant for coding, research, and file tasks`,
|
|
49
|
+
'',
|
|
50
|
+
` ${paint('1', color, '开始使用 / Get started:')}`,
|
|
51
|
+
` ${cyan('cd project_dir')}`,
|
|
52
|
+
` ${cyan('pulse')}`,
|
|
53
|
+
'',
|
|
54
|
+
];
|
|
55
|
+
if (configPath) {
|
|
56
|
+
lines.push(` ${dim('配置已就绪 / Configuration ready at:')}`, ` ${configPath}`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
lines.push(` ${dim('配置创建失败 / Configuration could not be created:')} ${error ?? 'Unknown error'}`);
|
|
60
|
+
lines.push(` ${dim('稍后运行 "pulse setup" 创建配置。 / Run "pulse setup" later to create it.')}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
return `${lines.join('\n')}\n`;
|
|
64
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Generated from CHANGELOG.md and CHANGELOG.en.md by scripts/release/sync-cli-highlights.mjs. Do not edit manually.
|
|
2
|
+
export const releaseHighlights = {
|
|
3
|
+
"0.4.1": [
|
|
4
|
+
{
|
|
5
|
+
"zh": "新增按需发现与调用 Skill 的能力,减少无关指令进入任务上下文。",
|
|
6
|
+
"en": "Added on-demand Skill discovery and invocation to keep unrelated instructions out of task context."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"zh": "美化安装欢迎界面和版本页,提供中英双语介绍及版本亮点。",
|
|
10
|
+
"en": "Polished the installation welcome screen and version page with bilingual product information and release highlights."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"zh": "提供中英文分离的更新日志和双语用户文档,并校验版本亮点来源。",
|
|
14
|
+
"en": "Added separate Chinese and English changelogs and bilingual user documentation, with validation for version highlights."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"zh": "为全部五个 npm 包采用 PolyForm Noncommercial 1.0.0,并随包提供许可证说明。",
|
|
18
|
+
"en": "Licensed all five npm packages under PolyForm Noncommercial 1.0.0 and included package-level license notices."
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"0.4.0": [
|
|
22
|
+
{
|
|
23
|
+
"zh": "默认使用异步并行 event loop,让模型请求、工具调用等副作用并发推进。",
|
|
24
|
+
"en": "Uses an asynchronous, parallel event loop by default so model requests, tool calls, and other effects can progress concurrently."
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"0.3.0": [
|
|
28
|
+
{
|
|
29
|
+
"zh": "增强 CLI 交互体验,支持内容选择与复制、推理用量归因、任务进度流式展示和可恢复会话。",
|
|
30
|
+
"en": "Improved CLI interaction with text selection and copying, reasoning-usage attribution, streamed task progress, and resumable sessions."
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"0.2.1": [
|
|
34
|
+
{
|
|
35
|
+
"zh": "增加安装后的欢迎界面,并完善跨平台本地与云端 CI 验证入口。",
|
|
36
|
+
"en": "Added the post-install welcome screen and improved the shared cross-platform local and cloud CI entry point."
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"0.2.0": [
|
|
40
|
+
{
|
|
41
|
+
"zh": "扩展 CLI 与 Host 能力,完善会话恢复、人机协作、工具调用和 Windows 沙箱及发布验证。",
|
|
42
|
+
"en": "Expanded CLI and Host capabilities with resumable sessions, human interaction, tool calls, and improved Windows sandbox and release validation."
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"0.1.11": [
|
|
46
|
+
{
|
|
47
|
+
"zh": "修复 Windows 下打包 CLI 入口加载问题,并改进跨平台安装与发布验证。",
|
|
48
|
+
"en": "Fixed loading the packaged CLI entry point on Windows and improved cross-platform installation and release validation."
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"0.1.10": [
|
|
52
|
+
{
|
|
53
|
+
"zh": "维护性版本发布;未单独记录用户可见更新。",
|
|
54
|
+
"en": "Maintenance release; no separate user-facing changes were recorded."
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"0.1.9": [
|
|
58
|
+
{
|
|
59
|
+
"zh": "维护性版本发布;未单独记录用户可见更新。",
|
|
60
|
+
"en": "Maintenance release; no separate user-facing changes were recorded."
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"0.1.8": [
|
|
64
|
+
{
|
|
65
|
+
"zh": "增加 npm 安装后的首次配置初始化流程。",
|
|
66
|
+
"en": "Added first-run configuration initialization after npm installation."
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"0.1.7": [
|
|
70
|
+
{
|
|
71
|
+
"zh": "增加完整屏幕 CLI 工作区、模型配置与会话交互能力。",
|
|
72
|
+
"en": "Added a full-screen CLI workspace, model configuration, and interactive session capabilities."
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"0.1.6": [
|
|
76
|
+
{
|
|
77
|
+
"zh": "增加人类输入仲裁和主动 CLI 交互,并强化上下文、持久化与流式工具调用。",
|
|
78
|
+
"en": "Added human-input arbitration and active CLI interaction, while strengthening context, persistence, and streamed tool calls."
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"0.1.5": [
|
|
82
|
+
{
|
|
83
|
+
"zh": "改进 Provider 工具名称兼容性。",
|
|
84
|
+
"en": "Improved compatibility of tool names across providers."
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"0.1.4": [
|
|
88
|
+
{
|
|
89
|
+
"zh": "修复 Provider 安全工具名称的适配问题。",
|
|
90
|
+
"en": "Fixed adapter handling for provider-safe tool names."
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"0.1.3": [
|
|
94
|
+
{
|
|
95
|
+
"zh": "完善 CLI 工具暴露、用户文件目录和 Provider 错误反馈。",
|
|
96
|
+
"en": "Improved CLI tool exposure, user-file locations, and provider error reporting."
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"0.1.2": [
|
|
100
|
+
{
|
|
101
|
+
"zh": "修复 CLI 显示已安装包版本的问题。",
|
|
102
|
+
"en": "Fixed CLI reporting of the installed package version."
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"0.1.1": [
|
|
106
|
+
{
|
|
107
|
+
"zh": "完善首次运行配置创建及基础 npm 工作区使用体验。",
|
|
108
|
+
"en": "Improved first-run configuration setup and basic npm workspace usage."
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"0.1.0": [
|
|
112
|
+
{
|
|
113
|
+
"zh": "建立 Pulse CLI 初始 npm 发布包和仓库发布元数据。",
|
|
114
|
+
"en": "Established the initial Pulse CLI npm package and repository release metadata."
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface SlashSuggestion {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function skillSuggestions(names: readonly string[], commands: readonly SlashSuggestion[]): SlashSuggestion[];
|
|
6
|
+
export declare function searchSlashSuggestions(value: string, items: readonly SlashSuggestion[]): SlashSuggestion[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function skillSuggestions(names, commands) {
|
|
2
|
+
const reserved = new Set(commands.map(item => item.name.toLowerCase()));
|
|
3
|
+
return names.map(name => ({ name: reserved.has(`/${name}`.toLowerCase()) ? `/skill:${name}` : `/${name}`, description: '技能 · 提交时加载' }));
|
|
4
|
+
}
|
|
5
|
+
export function searchSlashSuggestions(value, items) {
|
|
6
|
+
if (!/^\/\S*$/.test(value))
|
|
7
|
+
return [];
|
|
8
|
+
const query = value.slice(1).toLowerCase();
|
|
9
|
+
return items.filter(item => item.name.slice(1).toLowerCase().includes(query))
|
|
10
|
+
.sort((a, b) => Number(b.name.slice(1).toLowerCase().startsWith(query)) - Number(a.name.slice(1).toLowerCase().startsWith(query)) || a.name.localeCompare(b.name));
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "A local-first AI task assistant for coding, research, and file workflows.",
|
|
5
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"ai",
|
|
7
8
|
"agent",
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"scripts"
|
|
28
29
|
],
|
|
29
30
|
"scripts": {
|
|
30
|
-
"build": "tsc -p tsconfig.json",
|
|
31
|
+
"build": "pnpm --dir ../.. changelog:sync && tsc -p tsconfig.json",
|
|
31
32
|
"postinstall": "node ./scripts/postinstall.mjs"
|
|
32
33
|
},
|
|
33
34
|
"publishConfig": {
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"registry": "https://registry.npmjs.org"
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@hunterzhu/pulse-server": "0.4.
|
|
39
|
+
"@hunterzhu/pulse-server": "0.4.1",
|
|
39
40
|
"chalk": "^5.4.1",
|
|
40
41
|
"highlight.js": "^11.10.0",
|
|
41
42
|
"ink": "^7.1.1",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,58 +1,15 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
1
2
|
import { ensurePulseUserConfig, defaultPulseConfigPath } from '../dist/config.js'
|
|
3
|
+
import { formatInstallWelcome, shouldUseColor } from '../dist/presentation.js'
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
}
|
|
5
|
+
const { version } = createRequire(import.meta.url)('../package.json')
|
|
6
|
+
const color = shouldUseColor(process.env, process.stdout.isTTY === true)
|
|
50
7
|
|
|
51
8
|
try {
|
|
52
9
|
const configPath = defaultPulseConfigPath()
|
|
53
10
|
await ensurePulseUserConfig(configPath)
|
|
54
|
-
|
|
11
|
+
process.stdout.write(formatInstallWelcome({ version, configPath, color }))
|
|
55
12
|
} catch (error) {
|
|
56
13
|
const message = error instanceof Error ? error.message : String(error)
|
|
57
|
-
|
|
14
|
+
process.stdout.write(formatInstallWelcome({ version, error: message, color }))
|
|
58
15
|
}
|