@hunterzhu/pulse-cli 0.1.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.d.ts +2 -0
- package/dist/bin.js +263 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.js +59 -0
- package/package.json +24 -0
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { access, chmod, mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { createLocalHost } from '@hunterzhu/pulse-server';
|
|
7
|
+
import { expandHome, loadPulseConfig } from './config.js';
|
|
8
|
+
const version = '0.1.0';
|
|
9
|
+
const help = `Pulse ${version}
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
pulse [options] start an interactive conversation
|
|
13
|
+
pulse run <task> [options] run one task
|
|
14
|
+
pulse sessions [options] list saved conversations
|
|
15
|
+
pulse resume <conversation-id> [task] continue or recover a conversation
|
|
16
|
+
pulse doctor [options] check local configuration
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--cwd <path> workspace directory
|
|
20
|
+
--data-dir <path> Pulse data directory
|
|
21
|
+
--config <path> user configuration file (default home/.pulse/config.json)
|
|
22
|
+
--provider <name> mock, openai-compatible, or anthropic
|
|
23
|
+
--model <name> provider model name
|
|
24
|
+
--base-url <url> provider endpoint
|
|
25
|
+
--format <text|jsonl> output format
|
|
26
|
+
--read-only disable write and shell tools
|
|
27
|
+
--auto-approve allow local writes and shell execution
|
|
28
|
+
--allow-network enable public web search and fetch tools
|
|
29
|
+
--trust-workspace treat workspace .pulse/config.json as user-trusted
|
|
30
|
+
--mock-response <text> deterministic response for local debugging
|
|
31
|
+
--live doctor: make one real provider request
|
|
32
|
+
--no-color disable terminal styling
|
|
33
|
+
--help, -h show this help
|
|
34
|
+
--version, -v show the version
|
|
35
|
+
setup --force write a user config template
|
|
36
|
+
`;
|
|
37
|
+
function parse(argv) {
|
|
38
|
+
const options = {};
|
|
39
|
+
const positionals = [];
|
|
40
|
+
let command = '';
|
|
41
|
+
const forwarded = argv[0] === '--' ? argv.slice(1) : argv;
|
|
42
|
+
for (let index = 0; index < forwarded.length; index++) {
|
|
43
|
+
const arg = forwarded[index];
|
|
44
|
+
if (!arg)
|
|
45
|
+
continue;
|
|
46
|
+
if (arg === '--') {
|
|
47
|
+
positionals.push(...forwarded.slice(index + 1));
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (arg.startsWith('--')) {
|
|
51
|
+
const parts = arg.slice(2).split('=', 2);
|
|
52
|
+
const key = parts[0] ?? '';
|
|
53
|
+
const inline = parts[1];
|
|
54
|
+
if (!key)
|
|
55
|
+
continue;
|
|
56
|
+
if (inline !== undefined) {
|
|
57
|
+
options[key] = inline;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const next = forwarded[index + 1];
|
|
61
|
+
if (next && !next.startsWith('-')) {
|
|
62
|
+
options[key] = next;
|
|
63
|
+
index++;
|
|
64
|
+
}
|
|
65
|
+
else
|
|
66
|
+
options[key] = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (arg.startsWith('-') && arg.length === 2) {
|
|
70
|
+
const key = arg === '-h' ? 'help' : arg === '-v' ? 'version' : arg.slice(1);
|
|
71
|
+
options[key] = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!command && ['run', 'sessions', 'resume', 'doctor', 'setup'].includes(arg))
|
|
75
|
+
command = arg;
|
|
76
|
+
else
|
|
77
|
+
positionals.push(arg);
|
|
78
|
+
}
|
|
79
|
+
return { command, positionals, options };
|
|
80
|
+
}
|
|
81
|
+
function option(options, key) { const value = options[key]; return typeof value === 'string' ? value : undefined; }
|
|
82
|
+
async function hostOptions(parsed) { const requestedCwd = expandHome(option(parsed.options, 'cwd')); const config = (await loadPulseConfig(requestedCwd ?? process.cwd(), expandHome(option(parsed.options, 'config')), parsed.options['trust-workspace'] === true)).value; const providerName = option(parsed.options, 'provider') ?? process.env.PULSE_PROVIDER ?? config.provider?.provider; const model = option(parsed.options, 'model') ?? process.env.PULSE_MODEL ?? config.provider?.model; const baseURL = option(parsed.options, 'base-url') ?? process.env.PULSE_BASE_URL ?? config.provider?.baseURL; const apiKeyEnv = config.provider?.apiKeyEnv ?? (providerName === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'); const apiKey = process.env[apiKeyEnv]; const provider = providerName ? { provider: providerName, ...(model === undefined ? {} : { defaultModel: model }), ...(baseURL === undefined ? {} : { baseURL }), ...(apiKey === undefined ? {} : { apiKey }) } : undefined; const cwd = requestedCwd ?? expandHome(config.cwd); const dataDir = expandHome(option(parsed.options, 'data-dir') ?? process.env.PULSE_DATA_DIR ?? config.dataDir); const mockResponse = option(parsed.options, 'mock-response'); const approvalMode = parsed.options['read-only'] === true ? 'read-only' : parsed.options['auto-approve'] === true || process.env.PULSE_AUTO_APPROVE === '1' ? 'auto' : config.approvalMode; const allowNetwork = parsed.options['allow-network'] === true || process.env.PULSE_ALLOW_NETWORK === '1' ? true : config.allowNetwork; return { ...(cwd === undefined ? {} : { cwd }), ...(dataDir === undefined ? {} : { dataDir }), ...(provider === undefined ? {} : { provider }), ...(mockResponse === undefined ? {} : { mockResponse }), ...(approvalMode === undefined ? {} : { approvalMode }), ...(allowNetwork === undefined ? {} : { allowNetwork }) }; }
|
|
83
|
+
async function setupConfig(force, explicitPath) { const path = explicitPath ?? process.env.PULSE_CONFIG ?? join(homedir(), '.pulse', 'config.json'); try {
|
|
84
|
+
await access(path);
|
|
85
|
+
if (!force)
|
|
86
|
+
throw new Error(`CONFIG_EXISTS:${path}`);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error.code !== 'ENOENT' && error instanceof Error && error.message.startsWith('CONFIG_EXISTS:'))
|
|
90
|
+
throw error;
|
|
91
|
+
} await mkdir(join(path, '..'), { recursive: true }); await writeFile(path, `${JSON.stringify({ provider: { provider: 'mock', model: 'mock', apiKeyEnv: 'OPENAI_API_KEY' }, approvalMode: 'ask', allowNetwork: false }, null, 2)}\n`, { mode: 0o600 }); await chmod(path, 0o600); process.stdout.write(`Wrote ${path}\n`); }
|
|
92
|
+
function writeEvent(event, format) { if (format === 'jsonl') {
|
|
93
|
+
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
94
|
+
return;
|
|
95
|
+
} if (event.type === 'text')
|
|
96
|
+
process.stdout.write(String(event.data ?? ''));
|
|
97
|
+
else if (event.type === 'waiting')
|
|
98
|
+
process.stdout.write(`\n[需要输入] ${JSON.stringify(event.data ?? '')}\n`);
|
|
99
|
+
else if (event.type === 'error')
|
|
100
|
+
process.stderr.write(`\n[错误] ${String(event.data ?? '')}\n`); }
|
|
101
|
+
async function consume(run, format, approvalInput) { let streamedText = false; let ownedApprovalInput = false; let input = approvalInput; try {
|
|
102
|
+
for await (const event of run.events) {
|
|
103
|
+
if (event.type === 'text')
|
|
104
|
+
streamedText = true;
|
|
105
|
+
writeEvent(event, format);
|
|
106
|
+
if (event.type === 'waiting') {
|
|
107
|
+
const payload = event.data && typeof event.data === 'object' && !Array.isArray(event.data) ? event.data : {};
|
|
108
|
+
const effectId = typeof payload.effectId === 'string' ? payload.effectId : undefined;
|
|
109
|
+
const request = payload.input && typeof payload.input === 'object' && !Array.isArray(payload.input) ? payload.input : {};
|
|
110
|
+
if (!effectId) {
|
|
111
|
+
await run.cancel('INVALID_APPROVAL_REQUEST');
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!process.stdin.isTTY) {
|
|
115
|
+
await run.cancel('INTERACTION_REQUIRED');
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!input) {
|
|
119
|
+
input = createInterface({ input: process.stdin, output: process.stderr, terminal: true });
|
|
120
|
+
ownedApprovalInput = true;
|
|
121
|
+
}
|
|
122
|
+
const prompt = typeof request.prompt === 'string' ? request.prompt : 'Approve this operation?';
|
|
123
|
+
const answer = await new Promise((resolve) => input.question(`${prompt}\nApprove? [y/N] `, resolve));
|
|
124
|
+
const approved = ['y', 'yes', '是', '确认'].includes(answer.trim().toLocaleLowerCase());
|
|
125
|
+
await run.reply(effectId, { approved, ...(approved ? {} : { reason: answer.trim() || 'User denied the operation.' }) });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const outcome = await run.outcome();
|
|
129
|
+
if (format === 'jsonl') {
|
|
130
|
+
process.stdout.write(`${JSON.stringify({ schemaVersion: 1, type: 'result', runId: run.id, status: outcome.status, text: outcome.text ?? null })}\n`);
|
|
131
|
+
}
|
|
132
|
+
else if (!streamedText && outcome.text)
|
|
133
|
+
process.stdout.write(`${outcome.text}\n`);
|
|
134
|
+
else
|
|
135
|
+
process.stdout.write(`\n[${outcome.status}]\n`);
|
|
136
|
+
return outcome;
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
if (ownedApprovalInput)
|
|
140
|
+
input?.close();
|
|
141
|
+
} }
|
|
142
|
+
async function consumeManaged(run, format, approvalInput, setActive) { setActive(run); try {
|
|
143
|
+
return await consume(run, format, approvalInput);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
setActive(undefined);
|
|
147
|
+
} }
|
|
148
|
+
async function interactive(host, conversationId, setActive) {
|
|
149
|
+
const conversation = conversationId ? await host.getConversation(conversationId) : await host.createConversation();
|
|
150
|
+
process.stderr.write(`Pulse · ${conversation.summary.cwd}\nType /help for commands.\n`);
|
|
151
|
+
const input = createInterface({ input: process.stdin, output: process.stderr, terminal: process.stdin.isTTY });
|
|
152
|
+
input.setPrompt('› ');
|
|
153
|
+
const prompt = () => { if (input.terminal)
|
|
154
|
+
input.prompt(); };
|
|
155
|
+
prompt();
|
|
156
|
+
for await (const line of input) {
|
|
157
|
+
const text = line.trim();
|
|
158
|
+
if (!text) {
|
|
159
|
+
prompt();
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (text === '/exit' || text === '/quit')
|
|
163
|
+
break;
|
|
164
|
+
if (text === '/help') {
|
|
165
|
+
process.stderr.write('Commands: /help /status /tools /artifacts /exit\n');
|
|
166
|
+
prompt();
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (text === '/status') {
|
|
170
|
+
process.stderr.write(`${JSON.stringify(conversation.summary)}\n`);
|
|
171
|
+
prompt();
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (text === '/tools') {
|
|
175
|
+
process.stderr.write(`${(await host.doctor()).tools.join(', ')}\n`);
|
|
176
|
+
prompt();
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (text === '/artifacts') {
|
|
180
|
+
process.stderr.write(`${JSON.stringify(await host.listArtifacts(conversation.id), null, 2)}\n`);
|
|
181
|
+
prompt();
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (text === '/new') {
|
|
185
|
+
process.stderr.write('Start another `npx @hunterzhu/pulse-cli` process for a new conversation.\n');
|
|
186
|
+
prompt();
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const run = await host.sendMessage(conversation.id, { text });
|
|
191
|
+
await consumeManaged(run, 'text', input, setActive);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
process.stderr.write(`[错误] ${error instanceof Error ? error.message : String(error)}\n`);
|
|
195
|
+
}
|
|
196
|
+
prompt();
|
|
197
|
+
}
|
|
198
|
+
input.close();
|
|
199
|
+
}
|
|
200
|
+
async function main() {
|
|
201
|
+
const parsed = parse(process.argv.slice(2));
|
|
202
|
+
if (parsed.options.help || parsed.options.h) {
|
|
203
|
+
process.stdout.write(help);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
if (parsed.options.version || parsed.options.v) {
|
|
207
|
+
process.stdout.write(`${version}\n`);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
if (parsed.command === 'setup') {
|
|
211
|
+
await setupConfig(parsed.options.force === true, expandHome(option(parsed.options, 'config')));
|
|
212
|
+
process.stdout.write('Configure the provider in that file, then run `pulse doctor`.\n');
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
const host = createLocalHost(await hostOptions(parsed));
|
|
216
|
+
await host.init();
|
|
217
|
+
let activeRun;
|
|
218
|
+
const onInterrupt = () => { if (activeRun) {
|
|
219
|
+
void activeRun.cancel('USER_INTERRUPT');
|
|
220
|
+
}
|
|
221
|
+
else
|
|
222
|
+
process.exitCode = 130; };
|
|
223
|
+
process.once('SIGINT', onInterrupt);
|
|
224
|
+
process.once('SIGTERM', onInterrupt);
|
|
225
|
+
try {
|
|
226
|
+
if (parsed.command === 'doctor') {
|
|
227
|
+
const result = await host.doctor({ live: parsed.options.live === true });
|
|
228
|
+
process.stdout.write(parsed.options.format === 'jsonl' ? `${JSON.stringify(result)}\n` : `${result.ok ? 'ok' : 'error'}\nworkspace: ${result.cwd}\ndata: ${result.dataDir}\nnode: ${result.node}\nprovider: ${result.provider}\ntools: ${result.tools.join(', ')}\n${result.live ? `live: ${result.live.ok ? 'ok' : 'error'} (${result.live.message})\n` : ''}${result.errors.map((item) => `error: ${item}`).join('\n')}`.trim() + '\n');
|
|
229
|
+
return result.ok ? 0 : 1;
|
|
230
|
+
}
|
|
231
|
+
if (parsed.command === 'sessions') {
|
|
232
|
+
const sessions = await host.listConversations();
|
|
233
|
+
process.stdout.write(parsed.options.format === 'jsonl' ? sessions.map((item) => `${JSON.stringify(item)}\n`).join('') : (sessions.length ? sessions.map((item) => `${item.id}\t${item.updatedAt}\t${item.title}\t${item.cwd}`).join('\n') + '\n' : 'No conversations.\n'));
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
if (parsed.command === 'run') {
|
|
237
|
+
const task = parsed.positionals.join(' ').trim();
|
|
238
|
+
if (!task)
|
|
239
|
+
throw new Error('TASK_REQUIRED');
|
|
240
|
+
const cwd = option(parsed.options, 'cwd');
|
|
241
|
+
const conversation = await host.createConversation(cwd === undefined ? {} : { cwd });
|
|
242
|
+
const outcome = await consumeManaged(await host.sendMessage(conversation.id, { text: task, format: option(parsed.options, 'format') === 'jsonl' ? 'jsonl' : 'text' }), option(parsed.options, 'format') ?? 'text', undefined, (run) => { activeRun = run; });
|
|
243
|
+
return outcome.status === 'succeeded' ? 0 : outcome.status === 'cancelled' ? 3 : 1;
|
|
244
|
+
}
|
|
245
|
+
if (parsed.command === 'resume') {
|
|
246
|
+
const id = parsed.positionals.shift();
|
|
247
|
+
const task = parsed.positionals.join(' ').trim();
|
|
248
|
+
if (!id)
|
|
249
|
+
throw new Error('RESUME_REQUIRES_ID');
|
|
250
|
+
const run = task ? await host.sendMessage(id, { text: task }) : await host.resumeRun(id);
|
|
251
|
+
const outcome = await consumeManaged(run, option(parsed.options, 'format') ?? 'text', undefined, (current) => { activeRun = current; });
|
|
252
|
+
return outcome.status === 'succeeded' ? 0 : outcome.status === 'cancelled' ? 3 : 1;
|
|
253
|
+
}
|
|
254
|
+
await interactive(host, undefined, (run) => { activeRun = run; });
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
process.removeListener('SIGINT', onInterrupt);
|
|
259
|
+
process.removeListener('SIGTERM', onInterrupt);
|
|
260
|
+
await host.close();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
main().then((code) => { process.exitCode = code; }).catch((error) => { process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; });
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface PulseCliConfig {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
dataDir?: string;
|
|
4
|
+
provider?: {
|
|
5
|
+
provider?: string;
|
|
6
|
+
model?: string;
|
|
7
|
+
baseURL?: string;
|
|
8
|
+
apiKeyEnv?: string;
|
|
9
|
+
};
|
|
10
|
+
approvalMode?: 'read-only' | 'ask' | 'auto';
|
|
11
|
+
allowNetwork?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function expandHome(path: string | undefined): string | undefined;
|
|
14
|
+
/** Workspace files must not escalate approval, network, or provider credentials. */
|
|
15
|
+
export declare function sanitizeWorkspaceConfig(value: PulseCliConfig): PulseCliConfig;
|
|
16
|
+
export declare function mergePulseConfigs(layers: Array<{
|
|
17
|
+
value: PulseCliConfig;
|
|
18
|
+
trust: 'workspace' | 'user';
|
|
19
|
+
}>): PulseCliConfig;
|
|
20
|
+
export declare function loadPulseConfig(cwd: string, explicitPath?: string, trustWorkspace?: boolean): Promise<{
|
|
21
|
+
value: PulseCliConfig;
|
|
22
|
+
source?: string;
|
|
23
|
+
}>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
export function expandHome(path) {
|
|
5
|
+
if (path === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
return path === '~' ? homedir() : path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
|
|
8
|
+
}
|
|
9
|
+
function asConfig(value) {
|
|
10
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
11
|
+
return undefined;
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
/** Workspace files must not escalate approval, network, or provider credentials. */
|
|
15
|
+
export function sanitizeWorkspaceConfig(value) {
|
|
16
|
+
const provider = value.provider === undefined ? undefined : {
|
|
17
|
+
...(value.provider.provider === undefined ? {} : { provider: value.provider.provider }),
|
|
18
|
+
...(value.provider.model === undefined ? {} : { model: value.provider.model }),
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
...(value.cwd === undefined ? {} : { cwd: value.cwd }),
|
|
22
|
+
...(value.dataDir === undefined ? {} : { dataDir: value.dataDir }),
|
|
23
|
+
...(provider === undefined || Object.keys(provider).length === 0 ? {} : { provider }),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function mergePulseConfigs(layers) {
|
|
27
|
+
let merged = {};
|
|
28
|
+
for (const layer of layers) {
|
|
29
|
+
const value = layer.trust === 'workspace' ? sanitizeWorkspaceConfig(layer.value) : layer.value;
|
|
30
|
+
merged = {
|
|
31
|
+
...merged,
|
|
32
|
+
...value,
|
|
33
|
+
...(merged.provider === undefined && value.provider === undefined ? {} : { provider: { ...merged.provider, ...value.provider } }),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return merged;
|
|
37
|
+
}
|
|
38
|
+
export async function loadPulseConfig(cwd, explicitPath, trustWorkspace = false) {
|
|
39
|
+
const workspacePath = join(cwd, '.pulse', 'config.json');
|
|
40
|
+
const homePath = join(homedir(), '.pulse', 'config.json');
|
|
41
|
+
const envPath = expandHome(process.env.PULSE_CONFIG);
|
|
42
|
+
const layers = [];
|
|
43
|
+
for (const [path, trust] of [
|
|
44
|
+
[workspacePath, trustWorkspace ? 'user' : 'workspace'],
|
|
45
|
+
[homePath, 'user'],
|
|
46
|
+
[envPath, 'user'],
|
|
47
|
+
[explicitPath, 'user'],
|
|
48
|
+
]) {
|
|
49
|
+
if (!path)
|
|
50
|
+
continue;
|
|
51
|
+
try {
|
|
52
|
+
const parsed = asConfig(JSON.parse(await readFile(path, 'utf8')));
|
|
53
|
+
if (parsed)
|
|
54
|
+
layers.push({ value: parsed, trust, source: path });
|
|
55
|
+
}
|
|
56
|
+
catch { /* absent or invalid config is reported by doctor */ }
|
|
57
|
+
}
|
|
58
|
+
return { value: mergePulseConfigs(layers), ...(layers.at(-1) === undefined ? {} : { source: layers.at(-1).source }) };
|
|
59
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hunterzhu/pulse-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/zhuhengtan/Pulse"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"pulse": "dist/bin.js"
|
|
11
|
+
},
|
|
12
|
+
"main": "dist/bin.js",
|
|
13
|
+
"types": "dist/bin.d.ts",
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public",
|
|
16
|
+
"registry": "https://registry.npmjs.org"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@hunterzhu/pulse-server": "0.1.0"
|
|
23
|
+
}
|
|
24
|
+
}
|