edsger 0.33.4 → 0.33.6

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.
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Env Store - Persistent environment variable storage for the Edsger CLI
3
+ *
4
+ * Stores user-configured environment variables in ~/.edsger/env.json
5
+ * These are loaded into process.env at CLI startup, with lower priority
6
+ * than real environment variables.
7
+ */
8
+ /**
9
+ * Load all stored env vars from ~/.edsger/env.json
10
+ */
11
+ export declare function loadEnvStore(): Record<string, string>;
12
+ /**
13
+ * Set a single environment variable
14
+ */
15
+ export declare function setEnvVar(key: string, value: string): void;
16
+ /**
17
+ * Get a single environment variable from the store
18
+ */
19
+ export declare function getEnvVar(key: string): string | undefined;
20
+ /**
21
+ * Remove a single environment variable from the store
22
+ */
23
+ export declare function unsetEnvVar(key: string): boolean;
24
+ /**
25
+ * List all stored environment variables.
26
+ * Secret values are masked unless showSecrets is true.
27
+ */
28
+ export declare function listEnvVars(showSecrets?: boolean): Array<{
29
+ key: string;
30
+ value: string;
31
+ }>;
32
+ /**
33
+ * Apply stored env vars to process.env.
34
+ * Only sets vars that are NOT already defined in process.env
35
+ * (real env vars take precedence).
36
+ */
37
+ export declare function applyEnvStore(): void;
38
+ /**
39
+ * Get the env file path (for display purposes)
40
+ */
41
+ export declare function getEnvFilePath(): string;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Env Store - Persistent environment variable storage for the Edsger CLI
3
+ *
4
+ * Stores user-configured environment variables in ~/.edsger/env.json
5
+ * These are loaded into process.env at CLI startup, with lower priority
6
+ * than real environment variables.
7
+ */
8
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, } from 'fs';
9
+ import { join } from 'path';
10
+ import { homedir } from 'os';
11
+ const EDSGER_DIR = join(homedir(), '.edsger');
12
+ const ENV_FILE = join(EDSGER_DIR, 'env.json');
13
+ /** Known keys that contain secrets and should be masked in display */
14
+ const SECRET_KEYS = new Set([
15
+ 'ELEVENLABS_API_KEY',
16
+ 'DEEPGRAM_API_KEY',
17
+ 'OPENAI_API_KEY',
18
+ 'CLAUDE_API_KEY',
19
+ 'GEMINI_API_KEY',
20
+ 'EDSGER_MCP_TOKEN',
21
+ 'GITHUB_TOKEN',
22
+ ]);
23
+ /**
24
+ * Ensure the ~/.edsger directory exists
25
+ */
26
+ function ensureDir() {
27
+ if (!existsSync(EDSGER_DIR)) {
28
+ mkdirSync(EDSGER_DIR, { recursive: true, mode: 0o700 });
29
+ }
30
+ }
31
+ /**
32
+ * Load all stored env vars from ~/.edsger/env.json
33
+ */
34
+ export function loadEnvStore() {
35
+ try {
36
+ if (!existsSync(ENV_FILE)) {
37
+ return {};
38
+ }
39
+ const content = readFileSync(ENV_FILE, 'utf-8');
40
+ return JSON.parse(content);
41
+ }
42
+ catch {
43
+ return {};
44
+ }
45
+ }
46
+ /**
47
+ * Save env vars to ~/.edsger/env.json
48
+ */
49
+ function saveEnvStore(env) {
50
+ ensureDir();
51
+ writeFileSync(ENV_FILE, JSON.stringify(env, null, 2), 'utf-8');
52
+ try {
53
+ chmodSync(ENV_FILE, 0o600);
54
+ }
55
+ catch {
56
+ // chmod may fail on Windows
57
+ }
58
+ }
59
+ /**
60
+ * Set a single environment variable
61
+ */
62
+ export function setEnvVar(key, value) {
63
+ const env = loadEnvStore();
64
+ env[key] = value;
65
+ saveEnvStore(env);
66
+ }
67
+ /**
68
+ * Get a single environment variable from the store
69
+ */
70
+ export function getEnvVar(key) {
71
+ const env = loadEnvStore();
72
+ return env[key];
73
+ }
74
+ /**
75
+ * Remove a single environment variable from the store
76
+ */
77
+ export function unsetEnvVar(key) {
78
+ const env = loadEnvStore();
79
+ if (!(key in env)) {
80
+ return false;
81
+ }
82
+ delete env[key];
83
+ saveEnvStore(env);
84
+ return true;
85
+ }
86
+ /**
87
+ * List all stored environment variables.
88
+ * Secret values are masked unless showSecrets is true.
89
+ */
90
+ export function listEnvVars(showSecrets = false) {
91
+ const env = loadEnvStore();
92
+ return Object.entries(env).map(([key, value]) => ({
93
+ key,
94
+ value: !showSecrets && SECRET_KEYS.has(key) ? maskValue(value) : value,
95
+ }));
96
+ }
97
+ /**
98
+ * Apply stored env vars to process.env.
99
+ * Only sets vars that are NOT already defined in process.env
100
+ * (real env vars take precedence).
101
+ */
102
+ export function applyEnvStore() {
103
+ const env = loadEnvStore();
104
+ for (const [key, value] of Object.entries(env)) {
105
+ if (process.env[key] === undefined) {
106
+ process.env[key] = value;
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * Get the env file path (for display purposes)
112
+ */
113
+ export function getEnvFilePath() {
114
+ return ENV_FILE;
115
+ }
116
+ /**
117
+ * Mask a secret value for display, showing first 4 and last 4 chars
118
+ */
119
+ function maskValue(value) {
120
+ if (value.length <= 12) {
121
+ return '****';
122
+ }
123
+ return `${value.substring(0, 4)}...${value.substring(value.length - 4)}`;
124
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * edsger config set KEY=VALUE
3
+ */
4
+ export declare function runConfigSet(pair: string): void;
5
+ /**
6
+ * edsger config get KEY
7
+ */
8
+ export declare function runConfigGet(key: string): void;
9
+ /**
10
+ * edsger config unset KEY
11
+ */
12
+ export declare function runConfigUnset(key: string): void;
13
+ /**
14
+ * edsger config list
15
+ */
16
+ export declare function runConfigList(): void;
@@ -0,0 +1,60 @@
1
+ import { logInfo, logSuccess, logError, logWarning, logRaw } from '../../utils/logger.js';
2
+ import { setEnvVar, getEnvVar, unsetEnvVar, listEnvVars, getEnvFilePath, } from '../../auth/env-store.js';
3
+ /**
4
+ * edsger config set KEY=VALUE
5
+ */
6
+ export function runConfigSet(pair) {
7
+ const eqIndex = pair.indexOf('=');
8
+ if (eqIndex === -1) {
9
+ logError('Invalid format. Use: edsger config set KEY=VALUE');
10
+ process.exit(1);
11
+ }
12
+ const key = pair.substring(0, eqIndex).trim();
13
+ const value = pair.substring(eqIndex + 1).trim();
14
+ if (!key) {
15
+ logError('Key cannot be empty.');
16
+ process.exit(1);
17
+ }
18
+ setEnvVar(key, value);
19
+ logSuccess(`${key} saved to ${getEnvFilePath()}`);
20
+ }
21
+ /**
22
+ * edsger config get KEY
23
+ */
24
+ export function runConfigGet(key) {
25
+ const value = getEnvVar(key);
26
+ if (value === undefined) {
27
+ logWarning(`${key} is not set.`);
28
+ }
29
+ else {
30
+ logRaw(value);
31
+ }
32
+ }
33
+ /**
34
+ * edsger config unset KEY
35
+ */
36
+ export function runConfigUnset(key) {
37
+ const removed = unsetEnvVar(key);
38
+ if (removed) {
39
+ logSuccess(`${key} removed.`);
40
+ }
41
+ else {
42
+ logWarning(`${key} was not set.`);
43
+ }
44
+ }
45
+ /**
46
+ * edsger config list
47
+ */
48
+ export function runConfigList() {
49
+ const vars = listEnvVars();
50
+ if (vars.length === 0) {
51
+ logInfo('No environment variables configured.');
52
+ logInfo(`Run \`edsger config set KEY=VALUE\` to add one.`);
53
+ return;
54
+ }
55
+ logInfo(`Stored environment variables (${getEnvFilePath()}):`);
56
+ logRaw('');
57
+ for (const { key, value } of vars) {
58
+ logRaw(` ${key}=${value}`);
59
+ }
60
+ }
@@ -1,6 +1,7 @@
1
1
  import { logInfo, logError, logSuccess } from '../../utils/logger.js';
2
2
  import { validateConfiguration } from '../../utils/validation.js';
3
3
  import { analyseGrowth, } from '../../phases/growth-analysis/index.js';
4
+ import { checkVideoDependencies } from '../../services/video/index.js';
4
5
  /**
5
6
  * Run AI-powered growth analysis for a product.
6
7
  * Analyzes the product, reviews previous campaigns, and generates
@@ -11,6 +12,19 @@ export const runGrowthAnalysis = async (options) => {
11
12
  if (!productId) {
12
13
  throw new Error('Product ID is required for growth analysis');
13
14
  }
15
+ // Pre-flight checks: ensure TTS key and video dependencies are available
16
+ if (!process.env.ELEVENLABS_API_KEY && !process.env.DEEPGRAM_API_KEY) {
17
+ throw new Error('TTS API key required for growth analysis.\n' +
18
+ 'Set one of the following environment variables:\n' +
19
+ ' ELEVENLABS_API_KEY (primary)\n' +
20
+ ' DEEPGRAM_API_KEY (alternative)\n\n' +
21
+ 'Get a key at https://elevenlabs.io or https://deepgram.com');
22
+ }
23
+ const missingDeps = await checkVideoDependencies();
24
+ if (missingDeps.length > 0) {
25
+ throw new Error('Missing video dependencies:\n' +
26
+ missingDeps.map((d) => ` - ${d}`).join('\n'));
27
+ }
14
28
  const config = validateConfiguration(options);
15
29
  logInfo(`Starting growth analysis for product: ${productId}`);
16
30
  try {
package/dist/index.js CHANGED
@@ -15,14 +15,19 @@ import { runAnalyzeLogs } from './commands/analyze-logs/index.js';
15
15
  import { runAgentWorkflow } from './commands/agent-workflow/index.js';
16
16
  import { runTaskWorker } from './commands/task-worker/index.js';
17
17
  import { runLogin, runLogout, runStatus } from './auth/login.js';
18
+ import { applyEnvStore } from './auth/env-store.js';
19
+ import { runConfigSet, runConfigGet, runConfigUnset, runConfigList, } from './commands/config/index.js';
18
20
  // Get package.json version dynamically
19
21
  const __filename = fileURLToPath(import.meta.url);
20
22
  const __dirname = dirname(__filename);
21
23
  const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
22
24
  const version = packageJson.version;
23
- // Load environment variables from .env file
24
- // Load from current working directory (where the command is run)
25
+ // Load environment variables (each only sets vars not already defined):
26
+ // 1. Real environment variables (highest priority - already in process.env)
27
+ // 2. .env file in cwd (medium priority - loaded first)
28
+ // 3. ~/.edsger/env.json (lowest priority - loaded last)
25
29
  dotenvConfig({ path: join(process.cwd(), '.env') });
30
+ applyEnvStore();
26
31
  const program = new Command();
27
32
  program
28
33
  .name('edsger')
@@ -74,6 +79,36 @@ program
74
79
  }
75
80
  });
76
81
  // ============================================================
82
+ // Subcommand: edsger config set/get/list/unset
83
+ // ============================================================
84
+ const configCmd = program
85
+ .command('config')
86
+ .description('Manage CLI environment variables (stored in ~/.edsger/env.json)');
87
+ configCmd
88
+ .command('set <key=value>')
89
+ .description('Set an environment variable (e.g., edsger config set ELEVENLABS_API_KEY=sk-xxx)')
90
+ .action((pair) => {
91
+ runConfigSet(pair);
92
+ });
93
+ configCmd
94
+ .command('get <key>')
95
+ .description('Get the value of a stored environment variable')
96
+ .action((key) => {
97
+ runConfigGet(key);
98
+ });
99
+ configCmd
100
+ .command('unset <key>')
101
+ .description('Remove a stored environment variable')
102
+ .action((key) => {
103
+ runConfigUnset(key);
104
+ });
105
+ configCmd
106
+ .command('list')
107
+ .description('List all stored environment variables')
108
+ .action(() => {
109
+ runConfigList();
110
+ });
111
+ // ============================================================
77
112
  // Subcommand: edsger growth <productId>
78
113
  // ============================================================
79
114
  program
@@ -41,6 +41,8 @@ async function* prompt(analysisPrompt) {
41
41
  export async function executeAppStoreQuery(currentPrompt, systemPrompt, config, verbose, cwd) {
42
42
  let lastAssistantResponse = '';
43
43
  let structuredResult = null;
44
+ let turnCount = 0;
45
+ logInfo('Connecting to AI agent...');
44
46
  for await (const message of query({
45
47
  prompt: prompt(currentPrompt),
46
48
  options: {
@@ -59,19 +61,21 @@ export async function executeAppStoreQuery(currentPrompt, systemPrompt, config,
59
61
  logInfo(`Received message type: ${message.type}`);
60
62
  }
61
63
  if (message.type === 'assistant' && message.message?.content) {
64
+ turnCount++;
62
65
  for (const content of message.message.content) {
63
66
  if (content.type === 'text') {
64
67
  lastAssistantResponse += content.text + '\n';
65
68
  logDebug(`${content.text}`, verbose);
66
69
  }
67
70
  else if (content.type === 'tool_use') {
68
- logDebug(`${content.name}: ${content.input.description || 'Running...'}`, verbose);
71
+ const desc = content.input?.description || content.input?.command || 'Running...';
72
+ logInfo(`[Turn ${turnCount}] ${content.name}: ${typeof desc === 'string' ? desc.slice(0, 120) : 'Running...'}`);
69
73
  }
70
74
  }
71
75
  }
72
76
  if (message.type === 'result') {
73
77
  if (message.subtype === 'success') {
74
- logInfo('\nApp store generation completed, parsing results...');
78
+ logInfo(`\nAI generation completed after ${turnCount} turns, parsing results...`);
75
79
  const responseText = message.result || lastAssistantResponse;
76
80
  const parsed = parseAppStoreResult(responseText);
77
81
  if (parsed.error) {
@@ -41,6 +41,8 @@ async function* prompt(analysisPrompt) {
41
41
  export async function executeGrowthAnalysisQuery(currentPrompt, systemPrompt, config, verbose, cwd) {
42
42
  let lastAssistantResponse = '';
43
43
  let structuredResult = null;
44
+ let turnCount = 0;
45
+ logInfo('Connecting to AI agent...');
44
46
  for await (const message of query({
45
47
  prompt: prompt(currentPrompt),
46
48
  options: {
@@ -59,19 +61,21 @@ export async function executeGrowthAnalysisQuery(currentPrompt, systemPrompt, co
59
61
  logInfo(`Received message type: ${message.type}`);
60
62
  }
61
63
  if (message.type === 'assistant' && message.message?.content) {
64
+ turnCount++;
62
65
  for (const content of message.message.content) {
63
66
  if (content.type === 'text') {
64
67
  lastAssistantResponse += content.text + '\n';
65
68
  logDebug(`${content.text}`, verbose);
66
69
  }
67
70
  else if (content.type === 'tool_use') {
68
- logDebug(`${content.name}: ${content.input.description || 'Running...'}`, verbose);
71
+ const desc = content.input?.description || content.input?.command || 'Running...';
72
+ logInfo(`[Turn ${turnCount}] ${content.name}: ${typeof desc === 'string' ? desc.slice(0, 120) : 'Running...'}`);
69
73
  }
70
74
  }
71
75
  }
72
76
  if (message.type === 'result') {
73
77
  if (message.subtype === 'success') {
74
- logInfo('\nGrowth analysis completed, parsing results...');
78
+ logInfo(`\nGrowth analysis completed after ${turnCount} turns, parsing results...`);
75
79
  const responseText = message.result || lastAssistantResponse;
76
80
  const parsed = parseGrowthResult(responseText);
77
81
  if (parsed.error) {
@@ -5,7 +5,7 @@ import { executeGrowthAnalysisQuery } from './agent.js';
5
5
  import { saveGrowthAnalysis } from '../../api/growth.js';
6
6
  import { getGitHubConfigByProduct } from '../../api/github.js';
7
7
  import { ensureWorkspaceDir, cloneFeatureRepo, } from '../../workspace/workspace-manager.js';
8
- import { generateGrowthVideo, checkVideoDependencies, } from '../../services/video/index.js';
8
+ import { generateGrowthVideo, } from '../../services/video/index.js';
9
9
  /**
10
10
  * Extract video plans from AI-generated content suggestions
11
11
  */
@@ -98,56 +98,44 @@ export const analyseGrowth = async (options, config) => {
98
98
  const videoResults = new Map();
99
99
  if (videoPlans.length > 0) {
100
100
  logInfo(`\nFound ${videoPlans.length} content suggestion(s) with video plans`);
101
- // Check video dependencies once
102
- const missingDeps = await checkVideoDependencies();
103
- const hasTtsKey = !!(process.env.ELEVENLABS_API_KEY || process.env.DEEPGRAM_API_KEY);
104
- if (missingDeps.length > 0) {
105
- logWarning(`Skipping video generation - missing dependencies:\n${missingDeps.map((d) => ` - ${d}`).join('\n')}`);
101
+ const analysisId = savedAnalysis?.id || productId;
102
+ // Log all planned videos
103
+ for (const { index, plan } of videoPlans) {
104
+ const suggestion = contentSuggestions[index];
105
+ logInfo(`\nVideo planned for: "${suggestion.title}" [${suggestion.channel}]`);
106
+ logInfo(` Reason: ${plan.videoReason}`);
107
+ logInfo(` Scenes: ${plan.scenes.length}`);
106
108
  }
107
- else if (!hasTtsKey) {
108
- logWarning('Skipping video generation - no TTS API key configured.\n' +
109
- ' Set ELEVENLABS_API_KEY or DEEPGRAM_API_KEY environment variable.');
110
- }
111
- else {
112
- const analysisId = savedAnalysis?.id || productId;
113
- // Log all planned videos
114
- for (const { index, plan } of videoPlans) {
109
+ // Generate videos with concurrency limit (max 2 simultaneous)
110
+ const MAX_CONCURRENT_VIDEOS = 2;
111
+ logInfo(`\nGenerating ${videoPlans.length} video(s) (max ${MAX_CONCURRENT_VIDEOS} concurrent)...`);
112
+ const settled = await runWithConcurrencyLimit(videoPlans.map(({ index, plan }) => () => generateGrowthVideo(plan, {
113
+ productId,
114
+ analysisId,
115
+ verbose,
116
+ }).then((result) => ({ index, result }))), MAX_CONCURRENT_VIDEOS);
117
+ for (const outcome of settled) {
118
+ if (outcome.status === 'fulfilled') {
119
+ const { index, result } = outcome.value;
115
120
  const suggestion = contentSuggestions[index];
116
- logInfo(`\nVideo planned for: "${suggestion.title}" [${suggestion.channel}]`);
117
- logInfo(` Reason: ${plan.videoReason}`);
118
- logInfo(` Scenes: ${plan.scenes.length}`);
119
- }
120
- // Generate videos with concurrency limit (max 2 simultaneous)
121
- const MAX_CONCURRENT_VIDEOS = 2;
122
- logInfo(`\nGenerating ${videoPlans.length} video(s) (max ${MAX_CONCURRENT_VIDEOS} concurrent)...`);
123
- const settled = await runWithConcurrencyLimit(videoPlans.map(({ index, plan }) => () => generateGrowthVideo(plan, {
124
- productId,
125
- analysisId,
126
- verbose,
127
- }).then((result) => ({ index, result }))), MAX_CONCURRENT_VIDEOS);
128
- for (const outcome of settled) {
129
- if (outcome.status === 'fulfilled') {
130
- const { index, result } = outcome.value;
131
- const suggestion = contentSuggestions[index];
132
- videoResults.set(index, result);
133
- if (result.success) {
134
- logSuccess(`Video generated for "${suggestion.title}": ${result.duration?.toFixed(1)}s, ${result.sceneCount} scenes`);
135
- if (result.localPath) {
136
- logInfo(` Local: ${result.localPath}`);
137
- }
138
- if (result.videoUrl) {
139
- logInfo(` URL: ${result.videoUrl}`);
140
- }
121
+ videoResults.set(index, result);
122
+ if (result.success) {
123
+ logSuccess(`Video generated for "${suggestion.title}": ${result.duration?.toFixed(1)}s, ${result.sceneCount} scenes`);
124
+ if (result.localPath) {
125
+ logInfo(` Local: ${result.localPath}`);
141
126
  }
142
- else {
143
- logWarning(`Video generation failed for "${suggestion.title}": ${result.error}`);
127
+ if (result.videoUrl) {
128
+ logInfo(` URL: ${result.videoUrl}`);
144
129
  }
145
130
  }
146
131
  else {
147
- // Find the index from the error — log it generically
148
- logError(`Video generation error: ${outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason)}`);
132
+ logWarning(`Video generation failed for "${suggestion.title}": ${result.error}`);
149
133
  }
150
134
  }
135
+ else {
136
+ // Find the index from the error — log it generically
137
+ logError(`Video generation error: ${outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason)}`);
138
+ }
151
139
  }
152
140
  }
153
141
  // Build final content suggestions with video results
@@ -128,15 +128,17 @@ export async function generateGrowthVideo(plan, options) {
128
128
  // Step 4: Upload and save
129
129
  logInfo('Step 4/4: Uploading video...');
130
130
  const uploadResult = await uploadGrowthVideo(assembledVideo.videoPath, productId, analysisId, verbose);
131
- // Save metadata
132
- await saveVideoMetadata(analysisId, {
133
- video_url: uploadResult.publicUrl,
134
- video_storage_path: uploadResult.storagePath,
135
- video_local_path: uploadResult.localPath,
136
- video_duration: assembledVideo.duration,
137
- video_file_size: assembledVideo.fileSize,
138
- video_scene_count: assembledVideo.sceneCount,
139
- }, verbose);
131
+ // Save metadata only if upload succeeded (has a public URL)
132
+ if (uploadResult.publicUrl) {
133
+ await saveVideoMetadata(analysisId, {
134
+ video_url: uploadResult.publicUrl,
135
+ video_storage_path: uploadResult.storagePath,
136
+ video_local_path: uploadResult.localPath,
137
+ video_duration: assembledVideo.duration,
138
+ video_file_size: assembledVideo.fileSize,
139
+ video_scene_count: assembledVideo.sceneCount,
140
+ }, verbose);
141
+ }
140
142
  logSuccess('Video generation completed successfully!');
141
143
  return {
142
144
  success: true,
@@ -52,7 +52,9 @@ export interface ScreenshotOptions {
52
52
  */
53
53
  export declare function isPlaywrightAvailable(): Promise<boolean>;
54
54
  /**
55
- * Get or create the temporary directory for growth video screenshots
55
+ * Get a unique temporary directory for a single video generation run.
56
+ * Each call returns a distinct path to avoid race conditions when
57
+ * multiple videos are generated concurrently for the same product.
56
58
  */
57
59
  export declare function getVideoTempDir(productId: string): string;
58
60
  /**
@@ -29,10 +29,13 @@ export async function isPlaywrightAvailable() {
29
29
  return chromium !== null;
30
30
  }
31
31
  /**
32
- * Get or create the temporary directory for growth video screenshots
32
+ * Get a unique temporary directory for a single video generation run.
33
+ * Each call returns a distinct path to avoid race conditions when
34
+ * multiple videos are generated concurrently for the same product.
33
35
  */
34
36
  export function getVideoTempDir(productId) {
35
- return join(tmpdir(), 'edsger-growth-videos', productId);
37
+ const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
38
+ return join(tmpdir(), 'edsger-growth-videos', productId, uniqueId);
36
39
  }
37
40
  /**
38
41
  * Auto-detect device frame type based on viewport dimensions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.33.4",
3
+ "version": "0.33.6",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"