@hunterzhu/pulse-cli 0.4.1 → 0.4.3
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 +121 -13
- package/dist/commands/interactive.js +9 -1
- package/dist/commands/resume.js +2 -2
- package/dist/commands/run.js +5 -6
- package/dist/release-highlights.generated.js +32 -0
- package/package.json +2 -2
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 { createMcpCapabilityPack, createPdfCapabilityPack, createSkillCapabilityPack, createSpreadsheetCapabilityPack } from '@hunterzhu/pulse-server';
|
|
7
|
-
import { ensurePulseUserConfig, expandHome, loadPulseConfig } from './config.js';
|
|
7
|
+
import { defaultPulseConfig, 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';
|
|
@@ -60,6 +60,12 @@ Options:
|
|
|
60
60
|
--system-prompt-file <path> load custom system instructions from file
|
|
61
61
|
--mock-response <text> deterministic response for local debugging
|
|
62
62
|
--mock-task-assessment <json> deterministic verifier result for local debugging
|
|
63
|
+
--mock-stage-assessment <json> deterministic stage verifier results for local debugging
|
|
64
|
+
--mock-parallel-plan <json> deterministic task or read-only split plan for local debugging
|
|
65
|
+
--mock-tool-calls <json> deterministic tool calls for local debugging
|
|
66
|
+
--mock-after-tool-response <text> deterministic response after tool execution
|
|
67
|
+
--task-controller force the multi-stage task controller
|
|
68
|
+
--no-task-controller keep a single ReAct loop
|
|
63
69
|
--live doctor: make one real provider request
|
|
64
70
|
--no-color disable terminal styling
|
|
65
71
|
--help, -h show this help
|
|
@@ -123,17 +129,37 @@ export async function hostOptions(parsed) {
|
|
|
123
129
|
if (explicitConfig === undefined && process.env.PULSE_CONFIG === undefined) {
|
|
124
130
|
await ensurePulseUserConfig();
|
|
125
131
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
132
|
+
const loaded = (await loadPulseConfig(requestedCwd ?? process.cwd(), explicitConfig, parsed.options['trust-workspace'] === true)).value;
|
|
133
|
+
// A missing user config still has to answer usage errors and list sessions.
|
|
134
|
+
const config = loaded.activeModel || loaded.models || loaded.providers ? loaded : { ...defaultPulseConfig, ...loaded };
|
|
135
|
+
const mockResponse = option(parsed.options, 'mock-response');
|
|
136
|
+
const mockTaskAssessment = option(parsed.options, 'mock-task-assessment');
|
|
137
|
+
const mockStageAssessment = option(parsed.options, 'mock-stage-assessment');
|
|
138
|
+
const mockToolCallsOption = option(parsed.options, 'mock-tool-calls');
|
|
139
|
+
const mockAfterToolResponse = option(parsed.options, 'mock-after-tool-response');
|
|
140
|
+
const mockParallelPlanOption = option(parsed.options, 'mock-parallel-plan');
|
|
141
|
+
const hasMockDemand = mockResponse !== undefined || mockTaskAssessment !== undefined || mockStageAssessment !== undefined || mockToolCallsOption !== undefined || mockAfterToolResponse !== undefined || mockParallelPlanOption !== undefined;
|
|
142
|
+
const cliModel = option(parsed.options, 'model');
|
|
143
|
+
const requestedModel = cliModel ?? (hasMockDemand ? 'mock' : process.env.PULSE_MODEL ?? config.activeModel);
|
|
144
|
+
const effectiveModels = { ...(config.models ?? {}) };
|
|
145
|
+
const effectiveProviders = { ...(config.providers ?? {}) };
|
|
146
|
+
if (requestedModel === 'mock' || (hasMockDemand && cliModel === undefined)) {
|
|
147
|
+
if (!effectiveModels.mock) {
|
|
148
|
+
effectiveModels.mock = { displayName: 'mock', provider: 'mock', modelCode: 'mock', maxContextTokens: 32_000, maxOutputTokens: 4_096, reasoningEffort: 'medium' };
|
|
149
|
+
}
|
|
150
|
+
if (!effectiveProviders.mock) {
|
|
151
|
+
effectiveProviders.mock = { provider: 'mock', name: 'Mock' };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
128
154
|
const configuredModelEntry = requestedModel === undefined
|
|
129
155
|
? undefined
|
|
130
|
-
: Object.entries(
|
|
156
|
+
: Object.entries(effectiveModels).find(([name, item]) => name === requestedModel || item.displayName === requestedModel);
|
|
131
157
|
if (!configuredModelEntry)
|
|
132
158
|
throw new Error(`UNKNOWN_MODEL_DISPLAY_NAME:${requestedModel ?? '(missing)'}`);
|
|
133
159
|
const [, modelSelection] = configuredModelEntry;
|
|
134
160
|
const activeModelName = modelSelection.displayName;
|
|
135
161
|
const providerName = modelSelection.provider;
|
|
136
|
-
const profile =
|
|
162
|
+
const profile = effectiveProviders[providerName];
|
|
137
163
|
if (!profile)
|
|
138
164
|
throw new Error(`MODEL_PROVIDER_NOT_FOUND:${providerName}`);
|
|
139
165
|
const model = modelSelection.modelCode;
|
|
@@ -169,7 +195,7 @@ export async function hostOptions(parsed) {
|
|
|
169
195
|
...(configuredReasoningEffort === undefined ? {} : { reasoningEffort: configuredReasoningEffort }),
|
|
170
196
|
...(configuredToolChoice === undefined ? {} : { toolChoice: configuredToolChoice }),
|
|
171
197
|
};
|
|
172
|
-
const providerProfiles = Object.fromEntries(Object.entries(
|
|
198
|
+
const providerProfiles = Object.fromEntries(Object.entries(effectiveProviders).flatMap(([name, item]) => {
|
|
173
199
|
if (!item.provider)
|
|
174
200
|
throw new Error(`PROVIDER_PROTOCOL_REQUIRED:${name}`);
|
|
175
201
|
const adapterProvider = item.provider;
|
|
@@ -186,8 +212,8 @@ export async function hostOptions(parsed) {
|
|
|
186
212
|
}]];
|
|
187
213
|
}));
|
|
188
214
|
const modelDisplayNames = new Set();
|
|
189
|
-
const providerModels = Object.fromEntries(Object.entries(
|
|
190
|
-
if (!
|
|
215
|
+
const providerModels = Object.fromEntries(Object.entries(effectiveModels).flatMap(([name, item]) => {
|
|
216
|
+
if (!effectiveProviders[item.provider])
|
|
191
217
|
throw new Error(`MODEL_PROVIDER_NOT_FOUND:${item.provider}`);
|
|
192
218
|
const displayName = item.displayName.trim();
|
|
193
219
|
if (!displayName)
|
|
@@ -213,8 +239,6 @@ export async function hostOptions(parsed) {
|
|
|
213
239
|
}
|
|
214
240
|
const cwd = requestedCwd ?? expandHome(config.cwd);
|
|
215
241
|
const dataDir = expandHome(option(parsed.options, 'data-dir') ?? process.env.PULSE_DATA_DIR ?? config.dataDir);
|
|
216
|
-
const mockResponse = option(parsed.options, 'mock-response');
|
|
217
|
-
const mockTaskAssessment = option(parsed.options, 'mock-task-assessment');
|
|
218
242
|
let mockTaskAssessments;
|
|
219
243
|
if (mockTaskAssessment !== undefined) {
|
|
220
244
|
try {
|
|
@@ -227,6 +251,45 @@ export async function hostOptions(parsed) {
|
|
|
227
251
|
throw new Error('INVALID_MOCK_TASK_ASSESSMENT: expected a non-empty JSON array of assessment objects');
|
|
228
252
|
}
|
|
229
253
|
}
|
|
254
|
+
else if (hasMockDemand) {
|
|
255
|
+
mockTaskAssessments = [{ status: 'accepted', criteria: [{ criterionId: 'criterion-1', status: 'passed', evidenceRefs: ['AUTO'], rationale: 'Accepted by mock verifier.' }] }];
|
|
256
|
+
}
|
|
257
|
+
let mockStageAssessments;
|
|
258
|
+
if (mockStageAssessment !== undefined) {
|
|
259
|
+
try {
|
|
260
|
+
const value = JSON.parse(mockStageAssessment);
|
|
261
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((item) => !item || typeof item !== 'object' || Array.isArray(item)))
|
|
262
|
+
throw new Error('INVALID');
|
|
263
|
+
mockStageAssessments = value;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
throw new Error('INVALID_MOCK_STAGE_ASSESSMENT: expected a non-empty JSON array of stage assessment objects');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
let mockToolCalls;
|
|
270
|
+
if (mockToolCallsOption !== undefined) {
|
|
271
|
+
try {
|
|
272
|
+
const value = JSON.parse(mockToolCallsOption);
|
|
273
|
+
if (!Array.isArray(value) || value.some((item) => !item || typeof item !== 'object' || Array.isArray(item)))
|
|
274
|
+
throw new Error('INVALID');
|
|
275
|
+
mockToolCalls = value;
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
throw new Error('INVALID_MOCK_TOOL_CALLS: expected a JSON array of tool call objects');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
let mockParallelPlan;
|
|
282
|
+
if (mockParallelPlanOption !== undefined) {
|
|
283
|
+
try {
|
|
284
|
+
const value = JSON.parse(mockParallelPlanOption);
|
|
285
|
+
if (!value || typeof value !== 'object')
|
|
286
|
+
throw new Error('INVALID');
|
|
287
|
+
mockParallelPlan = value;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
throw new Error('INVALID_MOCK_PARALLEL_PLAN: expected a JSON object');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
230
293
|
const requestedApprovalMode = option(parsed.options, 'approval-mode') ?? process.env.PULSE_APPROVAL_MODE;
|
|
231
294
|
const approvalMode = requestedApprovalMode === 'read-only' || parsed.options['read-only'] === true
|
|
232
295
|
? 'read-only'
|
|
@@ -291,7 +354,9 @@ export async function hostOptions(parsed) {
|
|
|
291
354
|
}
|
|
292
355
|
}
|
|
293
356
|
}
|
|
357
|
+
const explicitTaskController = parsed.options['task-controller'] === true ? true : parsed.options['no-task-controller'] === true ? false : mockParallelPlanOption !== undefined ? true : undefined;
|
|
294
358
|
return {
|
|
359
|
+
...(explicitTaskController === undefined ? {} : { taskController: explicitTaskController }),
|
|
295
360
|
...(cwd === undefined ? {} : { cwd }),
|
|
296
361
|
...(dataDir === undefined ? {} : { dataDir }),
|
|
297
362
|
...(systemPrompt && systemPrompt.trim().length > 0 ? { systemPrompt: systemPrompt.trim() } : {}),
|
|
@@ -307,6 +372,10 @@ export async function hostOptions(parsed) {
|
|
|
307
372
|
activeModel: activeModelName,
|
|
308
373
|
...(mockResponse === undefined ? {} : { mockResponse }),
|
|
309
374
|
...(mockTaskAssessments === undefined ? {} : { mockTaskAssessments }),
|
|
375
|
+
...(mockStageAssessments === undefined ? {} : { mockStageAssessments }),
|
|
376
|
+
...(mockToolCalls === undefined ? {} : { mockToolCalls }),
|
|
377
|
+
...(mockAfterToolResponse === undefined ? {} : { mockAfterToolResponse }),
|
|
378
|
+
...(mockParallelPlan === undefined ? {} : { mockParallelPlan }),
|
|
310
379
|
...(approvalMode === undefined ? {} : { approvalMode }),
|
|
311
380
|
...(configuredMaxTurns === undefined ? {} : { maxTurns: configuredMaxTurns }),
|
|
312
381
|
...(configuredAutoCompactPercent === undefined ? {} : { autoCompactPercent: Math.min(90, configuredAutoCompactPercent) }),
|
|
@@ -345,21 +414,60 @@ export async function main() {
|
|
|
345
414
|
return runMcpDoctor(options, parsed.positionals.slice(1));
|
|
346
415
|
if (parsed.command === 'template')
|
|
347
416
|
return runTemplateCommand(options, parsed.positionals, option(parsed.options, 'format') ?? 'text', version);
|
|
417
|
+
async function readPipedStdin() {
|
|
418
|
+
if (process.stdin.isTTY)
|
|
419
|
+
return '';
|
|
420
|
+
return new Promise((resolve) => {
|
|
421
|
+
let data = '';
|
|
422
|
+
let settled = false;
|
|
423
|
+
const finish = () => {
|
|
424
|
+
if (settled)
|
|
425
|
+
return;
|
|
426
|
+
settled = true;
|
|
427
|
+
clearTimeout(idle);
|
|
428
|
+
process.stdin.pause();
|
|
429
|
+
resolve(data.trim());
|
|
430
|
+
};
|
|
431
|
+
// An open stdin with no bytes must not block a command that already has a task.
|
|
432
|
+
const idle = setTimeout(finish, 250);
|
|
433
|
+
process.stdin.setEncoding('utf8');
|
|
434
|
+
process.stdin.on('data', (chunk) => {
|
|
435
|
+
data += chunk;
|
|
436
|
+
clearTimeout(idle);
|
|
437
|
+
});
|
|
438
|
+
process.stdin.on('end', finish);
|
|
439
|
+
process.stdin.on('error', finish);
|
|
440
|
+
if (process.stdin.readableEnded)
|
|
441
|
+
finish();
|
|
442
|
+
});
|
|
443
|
+
}
|
|
348
444
|
if (parsed.command === 'run') {
|
|
349
|
-
|
|
445
|
+
let task = parsed.positionals.join(' ').trim();
|
|
446
|
+
if (!task && !process.stdin.isTTY) {
|
|
447
|
+
task = await readPipedStdin();
|
|
448
|
+
}
|
|
449
|
+
else if (task && !process.stdin.isTTY) {
|
|
450
|
+
const piped = await readPipedStdin();
|
|
451
|
+
if (piped)
|
|
452
|
+
task = `${task}\n\n${piped}`;
|
|
453
|
+
}
|
|
350
454
|
if (!task)
|
|
351
455
|
throw new Error('TASK_REQUIRED');
|
|
352
456
|
return runOneShot(options, task, option(parsed.options, 'format') ?? 'text', version);
|
|
353
457
|
}
|
|
354
458
|
if (parsed.command === 'resume') {
|
|
355
459
|
const id = parsed.positionals.shift();
|
|
356
|
-
|
|
460
|
+
let task = parsed.positionals.join(' ').trim();
|
|
461
|
+
if (!task && !process.stdin.isTTY) {
|
|
462
|
+
task = await readPipedStdin();
|
|
463
|
+
}
|
|
357
464
|
if (!id)
|
|
358
465
|
throw new Error('RESUME_REQUIRES_ID');
|
|
359
466
|
return runResume(options, id, task || undefined, option(parsed.options, 'format') ?? 'text', version);
|
|
360
467
|
}
|
|
361
468
|
// 默认启动交互式 Ink 界面
|
|
362
|
-
|
|
469
|
+
const initialTask = parsed.positionals.join(' ').trim() || undefined;
|
|
470
|
+
return runInteractive(options, undefined, initialTask, version, parsed.options.resume === true);
|
|
363
471
|
}
|
|
364
472
|
if (process.argv[1] && isSameModulePath(process.argv[1], fileURLToPath(import.meta.url))) {
|
|
365
473
|
main()
|
|
@@ -3,6 +3,7 @@ import { render } from 'ink';
|
|
|
3
3
|
import { App } from '../components/App.js';
|
|
4
4
|
import { createLocalHost } from '@hunterzhu/pulse-server';
|
|
5
5
|
import { closePendingHosts } from '../hooks/useHost.js';
|
|
6
|
+
import { runOneShot } from './run.js';
|
|
6
7
|
export async function findResumeConversation(options) {
|
|
7
8
|
const host = createLocalHost(options);
|
|
8
9
|
await host.init();
|
|
@@ -14,7 +15,14 @@ export async function findResumeConversation(options) {
|
|
|
14
15
|
await host.close();
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
export async function runInteractive(options, conversationId, initialTask, version = '0.1
|
|
18
|
+
export async function runInteractive(options, conversationId, initialTask, version = '0.4.1', resumeLatest = false) {
|
|
19
|
+
if (!process.stdin.isTTY) {
|
|
20
|
+
if (initialTask) {
|
|
21
|
+
return runOneShot(options, initialTask, 'text', version);
|
|
22
|
+
}
|
|
23
|
+
process.stderr.write('交互模式需要 TTY 终端。非交互执行请使用 "pulse run <task>"。\n');
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
18
26
|
const selectedConversationId = resumeLatest
|
|
19
27
|
? await findResumeConversation(options)
|
|
20
28
|
: conversationId;
|
package/dist/commands/resume.js
CHANGED
|
@@ -20,8 +20,8 @@ function writeEvent(event, format) {
|
|
|
20
20
|
process.stderr.write(`\n[错误] ${String(event.data ?? '')}\n`);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
export async function runResume(options, conversationId, task, format = 'text', version = '0.1
|
|
24
|
-
//
|
|
23
|
+
export async function runResume(options, conversationId, task, format = 'text', version = '0.4.1') {
|
|
24
|
+
// 交互式终端进入 Ink 界面;jsonl 保持非交互输出
|
|
25
25
|
if (process.stdout.isTTY && format !== 'jsonl') {
|
|
26
26
|
return runInteractive(options, conversationId, task, version);
|
|
27
27
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createLocalHost } from '@hunterzhu/pulse-server';
|
|
2
|
-
import { runInteractive } from './interactive.js';
|
|
3
2
|
import { bindRunSignals } from './signals.js';
|
|
4
3
|
function writeEvent(event, format) {
|
|
5
4
|
if (format === 'jsonl') {
|
|
@@ -20,11 +19,7 @@ function writeEvent(event, format) {
|
|
|
20
19
|
process.stderr.write(`\n[错误] ${String(event.data ?? '')}\n`);
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
|
-
export async function runOneShot(options, task, format = 'text', version = '0.1
|
|
24
|
-
// 如果在交互式终端且没有指定 jsonl,启动交互式 Ink 界面直接执行任务
|
|
25
|
-
if (process.stdout.isTTY && format !== 'jsonl') {
|
|
26
|
-
return runInteractive(options, undefined, task, version);
|
|
27
|
-
}
|
|
22
|
+
export async function runOneShot(options, task, format = 'text', version = '0.4.1') {
|
|
28
23
|
const host = createLocalHost(options);
|
|
29
24
|
let activeRun;
|
|
30
25
|
const unbindSignals = bindRunSignals(host, () => activeRun);
|
|
@@ -76,6 +71,10 @@ export async function runOneShot(options, task, format = 'text', version = '0.1.
|
|
|
76
71
|
return 3;
|
|
77
72
|
return outcome.status === 'succeeded' && (!taskOutcome || taskOutcome.status === 'accepted') ? 0 : 1;
|
|
78
73
|
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
process.stderr.write(`\n[错误] ${error instanceof Error ? error.message : String(error)}\n`);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
79
78
|
finally {
|
|
80
79
|
unbindSignals();
|
|
81
80
|
await host.close();
|
|
@@ -1,5 +1,37 @@
|
|
|
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.4.3": [
|
|
4
|
+
{
|
|
5
|
+
"zh": "OpenAI 兼容接口会按配置发送 reasoning effort;o1 与 o3 系列改用 `max_completion_tokens`。某个模型拒绝该参数时,只停用这一模型并重试。",
|
|
6
|
+
"en": "OpenAI-compatible requests send the configured reasoning effort, and o1/o3 models use `max_completion_tokens`. If one model rejects that parameter, only that model is disabled and the request is retried."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"zh": "DeepSeek 协议仍使用 JSON object 和 schema 提示。其他协议即使模型名包含 deepseek,也继续使用 JSON schema。",
|
|
10
|
+
"en": "The DeepSeek protocol still uses JSON object mode with schema guidance. Other protocols keep JSON schema even when the model name contains deepseek."
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"0.4.2": [
|
|
14
|
+
{
|
|
15
|
+
"zh": "阶段验收要求每条引用都有效;涉及文件、命令或测试的阶段必须有成功的工具证据,纯写作阶段仍可凭候选结果通过。",
|
|
16
|
+
"en": "Stage acceptance requires every cited reference to be valid. Stages that touch files, commands, or tests need successful tool evidence, while pure writing stages can still pass on the candidate result."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"zh": "DeepSeek 不再把未命名 JSON 当成文件写入或命令执行;回复后的附加文本保持为正文,损坏的 DSML 参数按截断处理。",
|
|
20
|
+
"en": "DeepSeek recovery no longer treats unnamed JSON as a file write or shell command. Trailing text stays text, and invalid DSML parameters are treated as truncation."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"zh": "暂存写入和提交会拒绝 `.pulse` 路径;列出不存在的目录会返回 ENOENT,已存在的空目录仍返回空列表。",
|
|
24
|
+
"en": "Staged writes and commits reject `.pulse` paths. Listing a missing directory returns ENOENT, while an existing empty directory still returns an empty list."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"zh": "命令拆分能识别引号;通过 `sh -c`、`bash -c` 等启动器执行的命令会被拒绝。",
|
|
28
|
+
"en": "Command splitting respects quotes. Commands launched through shells such as `sh -c` or `bash -c` are rejected."
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"zh": "`pulse resume` 在终端重新进入交互界面;已有任务的非交互运行不再因空闲标准输入一直等待,显式 mock 参数可以覆盖 `PULSE_MODEL`。",
|
|
32
|
+
"en": "`pulse resume` returns to the interactive screen on a terminal. A non-interactive run that already has a task no longer waits on idle stdin, and explicit mock flags can override `PULSE_MODEL`."
|
|
33
|
+
}
|
|
34
|
+
],
|
|
3
35
|
"0.4.1": [
|
|
4
36
|
{
|
|
5
37
|
"zh": "新增按需发现与调用 Skill 的能力,减少无关指令进入任务上下文。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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.
|
|
39
|
+
"@hunterzhu/pulse-server": "0.4.3",
|
|
40
40
|
"chalk": "^5.4.1",
|
|
41
41
|
"highlight.js": "^11.10.0",
|
|
42
42
|
"ink": "^7.1.1",
|