@hunterzhu/pulse-cli 0.4.6 → 0.5.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/dist/bin.js CHANGED
@@ -4,7 +4,7 @@ import { createRequire } from 'node:module';
4
4
  import { isAbsolute, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { assertJarvisApiUrl, createMcpCapabilityPack, createPdfCapabilityPack, createSkillCapabilityPack, createSpreadsheetCapabilityPack } from '@hunterzhu/pulse-server';
7
- import { defaultPulseConfig, ensurePulseUserConfig, expandHome, loadPulseConfig } from './config.js';
7
+ import { defaultPulseConfig, defaultPulseConfigPath, ensurePulseUserConfig, expandHome, loadPulseConfig } from './config.js';
8
8
  import { runInteractive } from './commands/interactive.js';
9
9
  import { runOneShot } from './commands/run.js';
10
10
  import { runDoctor } from './commands/doctor.js';
@@ -15,6 +15,7 @@ import { runScheduledCommand } from './commands/scheduled.js';
15
15
  import { runServiceCommand } from './commands/service.js';
16
16
  import { runTemplateCommand } from './commands/template.js';
17
17
  import { runMcpDoctor } from './commands/mcp.js';
18
+ import { runH5Command } from './commands/h5.js';
18
19
  import { isSameModulePath } from './utils/is-main-module.js';
19
20
  import { formatVersionOutput, shouldUseColor } from './presentation.js';
20
21
  const packageManifest = createRequire(import.meta.url)('../package.json');
@@ -32,6 +33,8 @@ Usage:
32
33
  pulse sessions [options] list saved conversations
33
34
  pulse resume <conversation-id> [task] continue or recover a conversation
34
35
  pulse doctor [options] check local configuration
36
+ pulse h5 [start|qr|devices|revoke|install|status|stop|uninstall]
37
+ mobile web assistant and launchd daemon
35
38
  pulse schedule <add|list|pause|resume|remove|daemon> [options]
36
39
  manage recurring tasks (daemon requires read-only or auto approval)
37
40
  pulse service <install|status|start|stop|uninstall>
@@ -138,7 +141,7 @@ export function parse(argv) {
138
141
  options[key] = true;
139
142
  continue;
140
143
  }
141
- if (!command && ['run', 'sessions', 'resume', 'doctor', 'setup', 'schedule', 'service', 'template', 'mcp'].includes(arg)) {
144
+ if (!command && ['run', 'sessions', 'resume', 'doctor', 'setup', 'schedule', 'service', 'template', 'mcp', 'h5'].includes(arg)) {
142
145
  command = arg;
143
146
  }
144
147
  else {
@@ -259,6 +262,9 @@ export async function hostOptions(parsed) {
259
262
  ...(item.reasoningEffort === undefined ? {} : { reasoningEffort: item.reasoningEffort }),
260
263
  }]];
261
264
  }));
265
+ const assistantModelName = config.assistantModel ?? activeModelName;
266
+ if (!providerModels[assistantModelName])
267
+ throw new Error(`UNKNOWN_ASSISTANT_MODEL:${assistantModelName}`);
262
268
  const taskRouting = config.taskRouting;
263
269
  for (const [task, models] of Object.entries(taskRouting ?? {})) {
264
270
  if (!Array.isArray(models) || models.length === 0 || models.some((model) => typeof model !== 'string' || !providerModels[model])) {
@@ -426,6 +432,7 @@ export async function hostOptions(parsed) {
426
432
  capabilityConfig: capabilitySettings.skills === undefined ? {} : { skills: skillNames },
427
433
  activeProviderCode: providerName,
428
434
  activeModel: activeModelName,
435
+ assistantModel: assistantModelName,
429
436
  ...(mockResponse === undefined ? {} : { mockResponse }),
430
437
  ...(mockTaskAssessments === undefined ? {} : { mockTaskAssessments }),
431
438
  ...(mockStageAssessments === undefined ? {} : { mockStageAssessments }),
@@ -439,6 +446,8 @@ export async function hostOptions(parsed) {
439
446
  ...(allowNetwork === undefined ? {} : { allowNetwork }),
440
447
  ...(config.networkHosts === undefined ? {} : { networkHosts: config.networkHosts }),
441
448
  jarvis,
449
+ ...(config.frontModel ? { frontModel: config.frontModel } : {}),
450
+ ...(config.approvalReview?.model ? { approvalReview: { provider: config.approvalReview.provider ?? 'ollama', model: config.approvalReview.model, baseURL: config.approvalReview.baseURL ?? 'http://127.0.0.1:11434', timeoutMs: Math.max(1_000, Math.min(60_000, config.approvalReview.timeoutMs ?? 15_000)) } } : {}),
442
451
  };
443
452
  }
444
453
  export async function main() {
@@ -468,6 +477,11 @@ export async function main() {
468
477
  }
469
478
  if (parsed.command === 'service')
470
479
  return runServiceCommand(options, parsed.positionals);
480
+ if (parsed.command === 'h5') {
481
+ const configPath = expandHome(option(parsed.options, 'config') ?? process.env.PULSE_CONFIG) ?? defaultPulseConfigPath();
482
+ const prepare = (candidatePath) => hostOptions({ ...parsed, options: { ...parsed.options, config: candidatePath } });
483
+ return runH5Command(options, parsed.positionals, parsed.options, { path: configPath, prepare });
484
+ }
471
485
  if (parsed.command === 'mcp' && parsed.positionals[0] === 'doctor')
472
486
  return runMcpDoctor(options, parsed.positionals.slice(1));
473
487
  if (parsed.command === 'template')
@@ -0,0 +1,8 @@
1
+ import { type LocalHostOptions } from '@hunterzhu/pulse-server';
2
+ /** Network H5 keeps ask/read-only unless this command explicitly requests auto. */
3
+ export declare function h5RuntimeOptions(options: LocalHostOptions, cliOptions?: Record<string, string | boolean>): LocalHostOptions;
4
+ export declare function h5DaemonArguments(executable: string, entry: string, port: number): string[];
5
+ export declare function runH5Command(options: LocalHostOptions, args: string[], cliOptions?: Record<string, string | boolean>, configuration?: {
6
+ path: string;
7
+ prepare: (candidatePath: string) => Promise<LocalHostOptions>;
8
+ }): Promise<number>;
@@ -0,0 +1,308 @@
1
+ import { access, mkdir, rm, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { spawnSync } from 'node:child_process';
6
+ import QRCode from 'qrcode';
7
+ import { createLocalHost, H5Server, H5AuthStore, DEFAULT_H5_PORT, runH5WorkWorker } from '@hunterzhu/pulse-server';
8
+ const serviceId = 'com.hunterzhu.pulse.h5';
9
+ function checked(file, args) {
10
+ const result = spawnSync(file, args, { encoding: 'utf8' });
11
+ if (result.status !== 0) {
12
+ throw new Error(`${file} ${args[0]} failed: ${(result.stderr || result.stdout || '').trim()}`);
13
+ }
14
+ }
15
+ function xml(value) {
16
+ return value
17
+ .replaceAll('&', '&amp;')
18
+ .replaceAll('<', '&lt;')
19
+ .replaceAll('>', '&gt;')
20
+ .replaceAll('"', '&quot;')
21
+ .replaceAll("'", '&apos;');
22
+ }
23
+ function quoteSystemd(value) {
24
+ return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('%', '%%')}"`;
25
+ }
26
+ const h5Actions = new Set(['start', 'daemon', 'qr', 'devices', 'revoke', 'install', 'status', 'stop', 'uninstall', 'work-worker']);
27
+ /** Network H5 keeps ask/read-only unless this command explicitly requests auto. */
28
+ export function h5RuntimeOptions(options, cliOptions = {}) {
29
+ const requested = cliOptions['approval-mode'];
30
+ if (requested === 'auto' || cliOptions['auto-approve'] === true)
31
+ return { ...options, approvalMode: 'auto' };
32
+ if (requested === 'read-only' || cliOptions['read-only'] === true)
33
+ return { ...options, approvalMode: 'read-only' };
34
+ if (requested === 'ask')
35
+ return { ...options, approvalMode: 'ask' };
36
+ if (options.approvalMode === 'auto')
37
+ return { ...options, approvalMode: 'ask' };
38
+ return options;
39
+ }
40
+ function h5Port(cliOptions) {
41
+ if (typeof cliOptions.port !== 'string')
42
+ return DEFAULT_H5_PORT;
43
+ const port = Number(cliOptions.port);
44
+ return Number.isInteger(port) && port >= 0 && port <= 65535 ? port : undefined;
45
+ }
46
+ export function h5DaemonArguments(executable, entry, port) {
47
+ return [executable, entry, 'h5', 'daemon', '--port', String(port)];
48
+ }
49
+ export async function runH5Command(options, args, cliOptions = {}, configuration) {
50
+ const [action = 'start', targetId] = args;
51
+ if (!h5Actions.has(action)) {
52
+ process.stderr.write(`未知命令: pulse h5 ${action}\n可用子命令: start, qr, devices, revoke, install, status, stop, uninstall\n`);
53
+ return 1;
54
+ }
55
+ const port = h5Port(cliOptions);
56
+ if (port === undefined && ['start', 'daemon', 'qr', 'install'].includes(action)) {
57
+ process.stderr.write('错误: 端口必须是 0 到 65535 的整数。\n');
58
+ return 1;
59
+ }
60
+ if (action === 'install' && port === 0) {
61
+ process.stderr.write('错误: 后台服务需要固定端口,不能使用端口 0。\n');
62
+ return 1;
63
+ }
64
+ const home = process.env.PULSE_HOME ?? homedir();
65
+ const data = options.dataDir ?? process.env.PULSE_DATA_DIR ?? join(home, '.pulse', 'data');
66
+ const cwd = options.cwd ?? process.cwd();
67
+ const platform = process.platform;
68
+ // Internal child process. The parent sends the task over versioned IPC so
69
+ // prompts and credentials never appear in the process argument list.
70
+ if (action === 'work-worker') {
71
+ if (!targetId || !/^conv-[0-9a-f-]{36}$/i.test(targetId)) {
72
+ process.stderr.write('工作进程会话标识无效。\n');
73
+ return 1;
74
+ }
75
+ const host = createLocalHost(h5RuntimeOptions(options, cliOptions));
76
+ return runH5WorkWorker(host, targetId);
77
+ }
78
+ // 1. Devices list
79
+ if (action === 'devices') {
80
+ const auth = new H5AuthStore(data);
81
+ await auth.init();
82
+ const devices = await auth.listDevices();
83
+ if (devices.length === 0) {
84
+ process.stdout.write('暂无已配对的设备。\n');
85
+ return 0;
86
+ }
87
+ process.stdout.write('已配对设备列表:\n');
88
+ process.stdout.write('ID\t\t\t\t\t设备名称\t\t配对时间\t\t最近活动\n');
89
+ process.stdout.write('------------------------------------------------------------------------------------\n');
90
+ for (const d of devices) {
91
+ process.stdout.write(`${d.id}\t${d.name}\t${new Date(d.createdAt).toLocaleString()}\t${new Date(d.lastUsedAt).toLocaleString()}\n`);
92
+ }
93
+ return 0;
94
+ }
95
+ // 2. Revoke device
96
+ if (action === 'revoke') {
97
+ if (!targetId?.trim()) {
98
+ process.stderr.write('错误: 请提供要吊销的设备 ID,例如: pulse h5 revoke dev-xxx\n');
99
+ return 1;
100
+ }
101
+ const auth = new H5AuthStore(data);
102
+ await auth.init();
103
+ const ok = await auth.revokeDevice(targetId.trim());
104
+ if (ok) {
105
+ process.stdout.write(`已成功吊销设备 ${targetId}。\n`);
106
+ return 0;
107
+ }
108
+ else {
109
+ process.stderr.write(`未找到设备 ${targetId}。\n`);
110
+ return 1;
111
+ }
112
+ }
113
+ // 3. QR code retrieval (queries running server or generates one-off)
114
+ if (action === 'qr') {
115
+ try {
116
+ const res = await fetch(`http://127.0.0.1:${port}/api/pair/qr`, { signal: AbortSignal.timeout(1500) });
117
+ if (res.ok) {
118
+ const info = (await res.json());
119
+ process.stdout.write(`\n📱 手机扫码连接 Pulse 个人助理:\n`);
120
+ process.stdout.write(`局域网地址: ${info.pairUrl}\n`);
121
+ process.stdout.write(`配对码: ${info.code} (5 分钟内有效)\n\n`);
122
+ const terminalQr = await QRCode.toString(info.pairUrl, { type: 'terminal', small: true });
123
+ process.stdout.write(terminalQr + '\n');
124
+ return 0;
125
+ }
126
+ }
127
+ catch {
128
+ // Server not running
129
+ }
130
+ process.stdout.write(`H5 服务当前未在端口 ${port} 运行。\n可运行 'pulse h5' 在前台启动,或运行 'pulse h5 install' 配置开机后台运行。\n`);
131
+ return 1;
132
+ }
133
+ // 4. Service install (launchd on macOS / systemd on Linux)
134
+ if (action === 'install') {
135
+ if (!['darwin', 'linux'].includes(platform)) {
136
+ throw new Error(`SERVICE_PLATFORM_UNSUPPORTED:${platform}`);
137
+ }
138
+ const executable = process.execPath;
139
+ const entry = fileURLToPath(new URL('../bin.js', import.meta.url));
140
+ const config = process.env.PULSE_CONFIG ?? join(home, '.pulse', 'config.json');
141
+ const daemonArguments = h5DaemonArguments(executable, entry, port ?? DEFAULT_H5_PORT);
142
+ if (platform === 'darwin') {
143
+ const directory = join(home, 'Library', 'LaunchAgents');
144
+ const file = join(directory, `${serviceId}.plist`);
145
+ await mkdir(directory, { recursive: true });
146
+ await writeFile(file, `<?xml version="1.0" encoding="UTF-8"?>
147
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
148
+ <plist version="1.0">
149
+ <dict>
150
+ <key>Label</key>
151
+ <string>${serviceId}</string>
152
+ <key>ProgramArguments</key>
153
+ <array>
154
+ ${daemonArguments.map((argument) => ` <string>${xml(argument)}</string>`).join('\n')}
155
+ </array>
156
+ <key>WorkingDirectory</key>
157
+ <string>${xml(cwd)}</string>
158
+ <key>EnvironmentVariables</key>
159
+ <dict>
160
+ <key>PULSE_HOME</key>
161
+ <string>${xml(home)}</string>
162
+ <key>PULSE_CONFIG</key>
163
+ <string>${xml(config)}</string>
164
+ <key>PULSE_DATA_DIR</key>
165
+ <string>${xml(data)}</string>
166
+ </dict>
167
+ <key>RunAtLoad</key>
168
+ <true/>
169
+ <key>KeepAlive</key>
170
+ <dict>
171
+ <key>SuccessfulExit</key>
172
+ <false/>
173
+ </dict>
174
+ <key>ThrottleInterval</key>
175
+ <integer>10</integer>
176
+ <key>StandardOutPath</key>
177
+ <string>${xml(join(data, 'h5-service.log'))}</string>
178
+ <key>StandardErrorPath</key>
179
+ <string>${xml(join(data, 'h5-service.error.log'))}</string>
180
+ </dict>
181
+ </plist>
182
+ `, { mode: 0o600 });
183
+ await mkdir(data, { recursive: true });
184
+ checked('launchctl', ['bootstrap', `gui/${process.getuid?.() ?? 0}`, file]);
185
+ process.stdout.write(`已安装 Pulse H5 launchd 守护服务;状态查看: pulse h5 status\n`);
186
+ return 0;
187
+ }
188
+ else if (platform === 'linux') {
189
+ const directory = join(home, '.config', 'systemd', 'user');
190
+ const file = join(directory, 'pulse-h5.service');
191
+ await mkdir(directory, { recursive: true });
192
+ await writeFile(file, `[Unit]
193
+ Description=Pulse H5 mobile web assistant service
194
+
195
+ [Service]
196
+ Type=simple
197
+ WorkingDirectory=${quoteSystemd(cwd)}
198
+ Environment=${quoteSystemd(`PULSE_HOME=${home}`)}
199
+ Environment=${quoteSystemd(`PULSE_CONFIG=${config}`)}
200
+ Environment=${quoteSystemd(`PULSE_DATA_DIR=${data}`)}
201
+ ExecStart=${daemonArguments.map(quoteSystemd).join(' ')}
202
+ Restart=on-failure
203
+ RestartSec=10s
204
+
205
+ [Install]
206
+ WantedBy=default.target
207
+ `, { mode: 0o600 });
208
+ checked('systemctl', ['--user', 'daemon-reload']);
209
+ checked('systemctl', ['--user', 'enable', '--now', 'pulse-h5.service']);
210
+ process.stdout.write(`已安装 Pulse H5 systemd 用户服务;状态查看: pulse h5 status\n`);
211
+ return 0;
212
+ }
213
+ }
214
+ // 5. Service status / stop / uninstall
215
+ if (['status', 'stop', 'uninstall'].includes(action)) {
216
+ if (platform === 'darwin') {
217
+ const file = join(home, 'Library', 'LaunchAgents', `${serviceId}.plist`);
218
+ if (action === 'status' && (await access(file).then(() => false, () => true))) {
219
+ process.stdout.write('Pulse H5 守护服务未安装。\n');
220
+ return 0;
221
+ }
222
+ if (action === 'uninstall') {
223
+ spawnSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 0}`, file], { stdio: 'ignore' });
224
+ await rm(file, { force: true });
225
+ process.stdout.write('已卸载 Pulse H5 守护服务。\n');
226
+ return 0;
227
+ }
228
+ const verb = action === 'status' ? 'print' : 'kill';
229
+ const extraArgs = action === 'stop' ? ['SIGTERM'] : [];
230
+ const result = spawnSync('launchctl', [verb, ...extraArgs, `gui/${process.getuid?.() ?? 0}/${serviceId}`], { encoding: 'utf8' });
231
+ process.stdout.write(result.stdout || result.stderr || `${action}: ${result.status === 0 ? 'ok' : 'not running'}\n`);
232
+ return result.status === 0 ? 0 : 1;
233
+ }
234
+ else if (platform === 'linux') {
235
+ const unit = 'pulse-h5.service';
236
+ if (action === 'status' && (await access(join(home, '.config', 'systemd', 'user', unit)).then(() => false, () => true))) {
237
+ process.stdout.write('Pulse H5 守护服务未安装。\n');
238
+ return 0;
239
+ }
240
+ const verb = action === 'status' ? 'status' : action === 'stop' ? 'stop' : 'disable';
241
+ const result = spawnSync('systemctl', ['--user', verb, ...(action === 'uninstall' ? ['--now'] : []), unit], { encoding: 'utf8' });
242
+ if (action === 'uninstall') {
243
+ await rm(join(home, '.config', 'systemd', 'user', unit), { force: true });
244
+ process.stdout.write('已卸载 Pulse H5 systemd 服务。\n');
245
+ }
246
+ else {
247
+ process.stdout.write(result.stdout || result.stderr || '');
248
+ }
249
+ return result.status === 0 ? 0 : 1;
250
+ }
251
+ process.stderr.write(`当前平台不支持 H5 守护进程管理: ${platform}\n`);
252
+ return 1;
253
+ }
254
+ if (action !== 'start' && action !== 'daemon') {
255
+ process.stderr.write(`未知命令: pulse h5 ${action}\n`);
256
+ return 1;
257
+ }
258
+ // 6. Foreground server / daemon worker
259
+ const isDaemon = action === 'daemon';
260
+ const runtimeOptions = h5RuntimeOptions(options, cliOptions);
261
+ if (options.approvalMode === 'auto' && runtimeOptions.approvalMode === 'ask') {
262
+ process.stderr.write('H5 网络服务已将审批模式从 auto 降为 ask。若确需自动批准,请显式传入 --approval-mode auto。\n');
263
+ }
264
+ const host = createLocalHost(runtimeOptions);
265
+ const server = new H5Server(host, {
266
+ dataDir: data,
267
+ port: port ?? DEFAULT_H5_PORT,
268
+ ...(process.env.PULSE_H5_HOST ? { host: process.env.PULSE_H5_HOST } : {}),
269
+ ...(configuration ? { configuration } : {}),
270
+ workWorker: {
271
+ entry: fileURLToPath(new URL('../bin.js', import.meta.url)),
272
+ dataDir: data,
273
+ ...(configuration ? { configPath: configuration.path } : {}),
274
+ approvalMode: runtimeOptions.approvalMode,
275
+ trustWorkspace: cliOptions['trust-workspace'] === true,
276
+ cwd,
277
+ },
278
+ });
279
+ const started = await server.start();
280
+ if (!isDaemon) {
281
+ process.stdout.write(`\n🚀 Pulse H5 个人助理服务已启动!\n`);
282
+ process.stdout.write(`电脑本机管理页: http://localhost:${started.port}/pair\n`);
283
+ process.stdout.write(`手机扫码连接页: ${started.pairUrl}\n`);
284
+ process.stdout.write(`当前局域网 IP: ${started.lanIp}\n\n`);
285
+ try {
286
+ const qr = await QRCode.toString(started.pairUrl, { type: 'terminal', small: true });
287
+ process.stdout.write(qr + '\n');
288
+ }
289
+ catch {
290
+ // ignore
291
+ }
292
+ process.stdout.write(`提示: 手机连上同一 Wi-Fi 后扫码即可使用。按 Ctrl+C 可停止服务。\n\n`);
293
+ }
294
+ await new Promise((resolve) => {
295
+ let shuttingDown = false;
296
+ const shutdown = () => {
297
+ if (shuttingDown) {
298
+ process.exit(130);
299
+ return;
300
+ }
301
+ shuttingDown = true;
302
+ void server.stop().then(() => host.close()).then(() => resolve(), () => resolve());
303
+ };
304
+ process.once('SIGINT', shutdown);
305
+ process.once('SIGTERM', shutdown);
306
+ });
307
+ return 0;
308
+ }
package/dist/config.d.ts CHANGED
@@ -65,6 +65,8 @@ export interface PulseCliConfig {
65
65
  models?: Record<string, PulseCliModel>;
66
66
  /** Active Pulse model display name. */
67
67
  activeModel?: string;
68
+ /** Dedicated model for the persistent Pulse assistant chat; defaults to activeModel. */
69
+ assistantModel?: string;
68
70
  /** Ordered model display names per task; later entries act as fallbacks. */
69
71
  taskRouting?: Partial<Record<PulseModelTask, string[]>>;
70
72
  capabilities?: PulseCliCapabilities;
@@ -78,6 +80,19 @@ export interface PulseCliConfig {
78
80
  networkHosts?: string[];
79
81
  allowNetwork?: boolean;
80
82
  jarvis?: PulseCliJarvisConfig;
83
+ frontModel?: {
84
+ enabled?: boolean;
85
+ provider?: string;
86
+ model?: string;
87
+ baseURL?: string;
88
+ timeoutMs?: number;
89
+ };
90
+ approvalReview?: {
91
+ provider?: 'ollama';
92
+ model?: string;
93
+ baseURL?: string;
94
+ timeoutMs?: number;
95
+ };
81
96
  }
82
97
  export interface PulseCliJarvisConfig {
83
98
  /** Master switch to enable or disable Jarvis cognitive memory architecture. */
@@ -1,5 +1,65 @@
1
1
  // Generated from CHANGELOG.md and CHANGELOG.en.md by scripts/release/sync-cli-highlights.mjs. Do not edit manually.
2
2
  export const releaseHighlights = {
3
+ "0.5.0": [
4
+ {
5
+ "zh": "新增独立 Web 工作台,统一助理聊天、项目工作会话、历史记录、设置与本地终端。",
6
+ "en": "Added a standalone Web workbench for assistant chats, project work sessions, history, settings, and local terminals."
7
+ },
8
+ {
9
+ "zh": "项目任务使用独立工作进程和 Git 工作区,继承当前项目改动,验收通过后合入结果;非 Git 项目使用互斥锁串行执行。",
10
+ "en": "Project tasks run in separate worker processes and Git worktrees that inherit current project edits and merge accepted results; non-Git projects use exclusive locks for serial execution."
11
+ },
12
+ {
13
+ "zh": "支持助理调用项目、工作会话及 Jarvis 工具,跟踪任务状态,并通过持久化消息队列恢复中断交互。",
14
+ "en": "The assistant can invoke project, work-session, and Jarvis tools, track task status, and recover interrupted interactions through a persistent message inbox."
15
+ },
16
+ {
17
+ "zh": "多个澄清问题可分组一次回答;Jarvis 检索结果按当前请求筛选后进入执行上下文。",
18
+ "en": "Related clarification questions can be answered as one group; retrieved Jarvis context is selected for the current request before execution."
19
+ },
20
+ {
21
+ "zh": "工作区交付比较任务起始快照,正确合入已提交成果,并支持解决冲突后重试;非 Git 失败重试保留共享目录锁语义。",
22
+ "en": "Workspace delivery compares against the task's starting snapshot, merges already committed results, and supports retries after conflict resolution; non-Git retries retain shared-directory locking."
23
+ },
24
+ {
25
+ "zh": "Jarvis 同步保留观察与结果事件类型,兼容旧队列;队列满时保留未送达和待审核记录,明确拒绝无法接纳的新请求。",
26
+ "en": "Jarvis synchronization preserves observation and result event types and supports older queues; full queues retain undelivered writes and candidates under review, explicitly rejecting new requests that cannot be accepted."
27
+ },
28
+ {
29
+ "zh": "改善 H5 实时消息、工具审批、设备认证和工作进程配置传递,打包时包含完整 Web 资源。",
30
+ "en": "Improved H5 live messages, tool approvals, device authentication, and worker configuration transfer, and included the complete Web assets in packages."
31
+ },
32
+ {
33
+ "zh": "修复任务接收确认和服务状态提示,兼容已有运行记录及工具定义。",
34
+ "en": "Fixed task acknowledgement and service status messages while retaining compatibility with existing run records and tool definitions."
35
+ }
36
+ ],
37
+ "0.4.7": [
38
+ {
39
+ "zh": "新增 H5 手机助理:支持扫码配对、设备管理、实时会话与工具审批,以及 macOS 和 Linux 后台服务。",
40
+ "en": "Added an H5 mobile assistant with QR pairing, device management, live conversations, tool approvals, and background services on macOS and Linux."
41
+ },
42
+ {
43
+ "zh": "支持本地 Ollama 即时回复与任务意图整理,并结合最近对话处理澄清回答。",
44
+ "en": "Added local Ollama instant replies and structured task briefs that use recent dialogue to interpret clarification answers."
45
+ },
46
+ {
47
+ "zh": "Jarvis 事件、候选记忆和会话关闭增加持久化待发送队列及幂等请求标识,失败后可恢复重试,收到确认后才推进状态。",
48
+ "en": "Jarvis events, candidate memories, and session completion use a durable outbox and idempotency keys; failed writes can be retried after restoration, and state advances only after acknowledgement."
49
+ },
50
+ {
51
+ "zh": "执行中的用户补充输入可记录为 Jarvis 观察事件;候选记忆使用 Jarvis 可解析的会话来源。",
52
+ "en": "Mid-run user input is recorded as Jarvis observation events; candidate memories reference sessions that Jarvis can resolve as evidence."
53
+ },
54
+ {
55
+ "zh": "H5 默认端口改为 7332,避免与 Jarvis MCP 冲突;后台服务保留自定义端口并拒绝随机端口。",
56
+ "en": "Changed the default H5 port to 7332 to avoid conflicts with Jarvis MCP; background services preserve custom ports and reject random ports."
57
+ },
58
+ {
59
+ "zh": "修复手机页事件与审批流程、会话工作目录限制、Host 校验和标题转义;网络服务默认保持人工审批。",
60
+ "en": "Fixed mobile event and approval flows, session workspace restrictions, Host validation, and title escaping; network services keep manual approval by default."
61
+ }
62
+ ],
3
63
  "0.4.6": [
4
64
  {
5
65
  "zh": "支持通过 CLI、环境变量或用户配置启用 Jarvis 会话、项目上下文、执行事件和候选记忆;默认关闭,服务不可用时继续独立运行。",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunterzhu/pulse-cli",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "description": "A local-first AI task assistant for coding, research, and file workflows.",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "keywords": [
@@ -36,7 +36,7 @@
36
36
  "registry": "https://registry.npmjs.org"
37
37
  },
38
38
  "dependencies": {
39
- "@hunterzhu/pulse-server": "0.4.6",
39
+ "@hunterzhu/pulse-server": "0.5.0",
40
40
  "chalk": "^5.4.1",
41
41
  "highlight.js": "^11.10.0",
42
42
  "ink": "^7.1.1",
@@ -44,10 +44,12 @@
44
44
  "ink-spinner": "^5.0.0",
45
45
  "ink-text-input": "^6.0.0",
46
46
  "marked": "^14.0.0",
47
+ "qrcode": "^1.5.4",
47
48
  "react": "^19.0.0",
48
49
  "wrap-ansi": "10.0.2"
49
50
  },
50
51
  "devDependencies": {
52
+ "@types/qrcode": "^1.5.6",
51
53
  "@types/react": "^19.0.0"
52
54
  }
53
55
  }