@hunterzhu/pulse-cli 0.1.0 → 0.1.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/dist/bin.js CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { createInterface } from 'node:readline';
3
3
  import { access, chmod, mkdir, writeFile } from 'node:fs/promises';
4
- import { homedir } from 'node:os';
5
- import { join } from 'node:path';
4
+ import { dirname } from 'node:path';
6
5
  import { createLocalHost } from '@hunterzhu/pulse-server';
7
- import { expandHome, loadPulseConfig } from './config.js';
6
+ import { defaultPulseConfig, defaultPulseConfigPath, ensurePulseUserConfig, expandHome, loadPulseConfig } from './config.js';
8
7
  const version = '0.1.0';
9
8
  const help = `Pulse ${version}
10
9
 
@@ -32,7 +31,7 @@ Options:
32
31
  --no-color disable terminal styling
33
32
  --help, -h show this help
34
33
  --version, -v show the version
35
- setup --force write a user config template
34
+ setup --force write a user config template (also created on first run)
36
35
  `;
37
36
  function parse(argv) {
38
37
  const options = {};
@@ -79,8 +78,9 @@ function parse(argv) {
79
78
  return { command, positionals, options };
80
79
  }
81
80
  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 {
81
+ async function hostOptions(parsed) { const requestedCwd = expandHome(option(parsed.options, 'cwd')); const explicitConfig = expandHome(option(parsed.options, 'config')); if (explicitConfig === undefined && process.env.PULSE_CONFIG === undefined)
82
+ await ensurePulseUserConfig(); const config = (await loadPulseConfig(requestedCwd ?? process.cwd(), explicitConfig, 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 ?? defaultPulseConfigPath(); try {
84
84
  await access(path);
85
85
  if (!force)
86
86
  throw new Error(`CONFIG_EXISTS:${path}`);
@@ -88,7 +88,7 @@ async function setupConfig(force, explicitPath) { const path = explicitPath ?? p
88
88
  catch (error) {
89
89
  if (error.code !== 'ENOENT' && error instanceof Error && error.message.startsWith('CONFIG_EXISTS:'))
90
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`); }
91
+ } await mkdir(dirname(path), { recursive: true }); await writeFile(path, `${JSON.stringify(defaultPulseConfig, null, 2)}\n`, { mode: 0o600 }); await chmod(path, 0o600); process.stdout.write(`Wrote ${path}\n`); }
92
92
  function writeEvent(event, format) { if (format === 'jsonl') {
93
93
  process.stdout.write(`${JSON.stringify(event)}\n`);
94
94
  return;
package/dist/config.d.ts CHANGED
@@ -10,6 +10,10 @@ export interface PulseCliConfig {
10
10
  approvalMode?: 'read-only' | 'ask' | 'auto';
11
11
  allowNetwork?: boolean;
12
12
  }
13
+ export declare const defaultPulseConfig: PulseCliConfig;
14
+ export declare function defaultPulseConfigPath(): string;
15
+ /** Create the user config on first run without replacing an existing file. */
16
+ export declare function ensurePulseUserConfig(path?: string): Promise<void>;
13
17
  export declare function expandHome(path: string | undefined): string | undefined;
14
18
  /** Workspace files must not escalate approval, network, or provider credentials. */
15
19
  export declare function sanitizeWorkspaceConfig(value: PulseCliConfig): PulseCliConfig;
package/dist/config.js CHANGED
@@ -1,6 +1,35 @@
1
- import { readFile } from 'node:fs/promises';
1
+ import { access, chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import { homedir } from 'node:os';
3
- import { join } from 'node:path';
3
+ import { dirname, join } from 'node:path';
4
+ export const defaultPulseConfig = {
5
+ provider: { provider: 'mock', model: 'mock', apiKeyEnv: 'OPENAI_API_KEY' },
6
+ approvalMode: 'ask',
7
+ allowNetwork: false,
8
+ };
9
+ export function defaultPulseConfigPath() {
10
+ return join(homedir(), '.pulse', 'config.json');
11
+ }
12
+ /** Create the user config on first run without replacing an existing file. */
13
+ export async function ensurePulseUserConfig(path = defaultPulseConfigPath()) {
14
+ await mkdir(dirname(path), { recursive: true });
15
+ try {
16
+ await access(path);
17
+ return;
18
+ }
19
+ catch (error) {
20
+ if (error.code !== 'ENOENT')
21
+ throw error;
22
+ }
23
+ try {
24
+ await writeFile(path, `${JSON.stringify(defaultPulseConfig, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
25
+ }
26
+ catch (error) {
27
+ // Another process may have initialized the config between access and write.
28
+ if (error.code !== 'EEXIST')
29
+ throw error;
30
+ }
31
+ await chmod(path, 0o600);
32
+ }
4
33
  export function expandHome(path) {
5
34
  if (path === undefined)
6
35
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunterzhu/pulse-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zhuhengtan/Pulse"
@@ -19,6 +19,6 @@
19
19
  "build": "tsc -p tsconfig.json"
20
20
  },
21
21
  "dependencies": {
22
- "@hunterzhu/pulse-server": "0.1.0"
22
+ "@hunterzhu/pulse-server": "0.1.1"
23
23
  }
24
24
  }