@covibes/zeroshot 5.2.0 → 5.3.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +178 -186
  2. package/README.md +199 -248
  3. package/cli/commands/providers.js +150 -0
  4. package/cli/index.js +214 -58
  5. package/cli/lib/first-run.js +40 -3
  6. package/cluster-templates/base-templates/debug-workflow.json +24 -78
  7. package/cluster-templates/base-templates/full-workflow.json +44 -145
  8. package/cluster-templates/base-templates/single-worker.json +23 -15
  9. package/cluster-templates/base-templates/worker-validator.json +47 -34
  10. package/cluster-templates/conductor-bootstrap.json +7 -5
  11. package/lib/docker-config.js +6 -1
  12. package/lib/provider-detection.js +59 -0
  13. package/lib/provider-names.js +56 -0
  14. package/lib/settings.js +191 -6
  15. package/lib/stream-json-parser.js +4 -238
  16. package/package.json +21 -5
  17. package/scripts/validate-templates.js +100 -0
  18. package/src/agent/agent-config.js +37 -13
  19. package/src/agent/agent-context-builder.js +64 -2
  20. package/src/agent/agent-hook-executor.js +82 -9
  21. package/src/agent/agent-lifecycle.js +53 -14
  22. package/src/agent/agent-task-executor.js +196 -194
  23. package/src/agent/output-extraction.js +200 -0
  24. package/src/agent/output-reformatter.js +175 -0
  25. package/src/agent/schema-utils.js +111 -0
  26. package/src/agent-wrapper.js +102 -30
  27. package/src/agents/git-pusher-agent.json +2 -2
  28. package/src/claude-task-runner.js +80 -30
  29. package/src/config-router.js +13 -13
  30. package/src/config-validator.js +231 -10
  31. package/src/github.js +36 -0
  32. package/src/isolation-manager.js +243 -154
  33. package/src/ledger.js +28 -6
  34. package/src/orchestrator.js +391 -96
  35. package/src/preflight.js +85 -82
  36. package/src/providers/anthropic/cli-builder.js +45 -0
  37. package/src/providers/anthropic/index.js +134 -0
  38. package/src/providers/anthropic/models.js +23 -0
  39. package/src/providers/anthropic/output-parser.js +159 -0
  40. package/src/providers/base-provider.js +181 -0
  41. package/src/providers/capabilities.js +51 -0
  42. package/src/providers/google/cli-builder.js +55 -0
  43. package/src/providers/google/index.js +116 -0
  44. package/src/providers/google/models.js +24 -0
  45. package/src/providers/google/output-parser.js +92 -0
  46. package/src/providers/index.js +75 -0
  47. package/src/providers/openai/cli-builder.js +122 -0
  48. package/src/providers/openai/index.js +135 -0
  49. package/src/providers/openai/models.js +21 -0
  50. package/src/providers/openai/output-parser.js +129 -0
  51. package/src/sub-cluster-wrapper.js +18 -3
  52. package/src/task-runner.js +8 -6
  53. package/src/tui/layout.js +20 -3
  54. package/task-lib/attachable-watcher.js +80 -78
  55. package/task-lib/claude-recovery.js +119 -0
  56. package/task-lib/commands/list.js +1 -1
  57. package/task-lib/commands/resume.js +3 -2
  58. package/task-lib/commands/run.js +12 -3
  59. package/task-lib/runner.js +59 -38
  60. package/task-lib/scheduler.js +2 -2
  61. package/task-lib/store.js +43 -30
  62. package/task-lib/watcher.js +81 -62
package/src/preflight.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Preflight Validation - Check all dependencies before starting
3
3
  *
4
4
  * Validates:
5
- * - Claude CLI installed and authenticated
5
+ * - Selected provider CLI installed
6
6
  * - gh CLI installed and authenticated (if using issue numbers)
7
7
  * - Docker available (if using --docker)
8
8
  *
@@ -42,13 +42,15 @@ function formatError(title, detail, recovery) {
42
42
  }
43
43
 
44
44
  /**
45
- * Check if a command exists
45
+ * Check if a command exists (cross-platform)
46
46
  * @param {string} cmd - Command to check
47
47
  * @returns {boolean}
48
48
  */
49
49
  function commandExists(cmd) {
50
50
  try {
51
- execSync(`which ${cmd}`, { encoding: 'utf8', stdio: 'pipe' });
51
+ // Windows uses 'where', Unix uses 'which'
52
+ const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
53
+ execSync(checkCmd, { encoding: 'utf8', stdio: 'pipe' });
52
54
  return true;
53
55
  } catch {
54
56
  return false;
@@ -280,57 +282,45 @@ function checkDocker() {
280
282
  * @param {boolean} options.requireGit - Whether git repo is required (true if using --worktree)
281
283
  * @param {boolean} options.quiet - Suppress success messages
282
284
  * @param {string} options.claudeCommand - Custom Claude command (from settings)
285
+ * @param {string} options.provider - Provider override
283
286
  * @returns {ValidationResult}
284
287
  */
285
288
  function runPreflight(options = {}) {
286
289
  const errors = [];
287
290
  const warnings = [];
288
291
 
289
- // Get configured Claude command (supports custom commands like 'ccr code')
290
- const { getClaudeCommand } = require('../lib/settings.js');
291
- const { command, args } = getClaudeCommand();
292
- const claudeCommand = options.claudeCommand || [command, ...args].join(' ');
292
+ const { loadSettings, getClaudeCommand } = require('../lib/settings.js');
293
+ const { normalizeProviderName } = require('../lib/provider-names');
294
+ const settings = loadSettings();
295
+ const providerName = normalizeProviderName(
296
+ options.provider || settings.defaultProvider || 'claude'
297
+ );
293
298
 
294
- // 1. Check Claude CLI installation
295
- const claude = getClaudeVersion(claudeCommand);
296
- if (!claude.installed) {
297
- errors.push(
298
- formatError(
299
- 'Claude command not available',
300
- claude.error,
301
- claudeCommand === 'claude'
302
- ? [
303
- 'Install Claude CLI: npm install -g @anthropic-ai/claude-code',
304
- 'Or: brew install claude (macOS)',
305
- 'Then run: claude --version',
306
- ]
307
- : [
308
- `Command '${claudeCommand}' not found`,
309
- 'Check settings: zeroshot settings',
310
- 'Update claudeCommand: zeroshot settings set claudeCommand "your-command"',
311
- 'Or install the missing command',
312
- ]
313
- )
314
- );
315
- } else {
316
- // 2. Check Claude CLI authentication
317
- const auth = checkClaudeAuth();
318
- if (!auth.authenticated) {
299
+ if (providerName === 'claude') {
300
+ const { command, args } = getClaudeCommand();
301
+ const claudeCommand = options.claudeCommand || [command, ...args].join(' ');
302
+
303
+ const claude = getClaudeVersion(claudeCommand);
304
+ if (!claude.installed) {
319
305
  errors.push(
320
306
  formatError(
321
- 'Claude CLI not authenticated',
322
- auth.error,
323
- [
324
- 'Run: claude login',
325
- 'Follow the browser prompts to authenticate',
326
- `Config directory: ${auth.configDir}`,
327
- ]
307
+ 'Claude command not available',
308
+ claude.error,
309
+ claudeCommand === 'claude'
310
+ ? [
311
+ 'Install Claude CLI: npm install -g @anthropic-ai/claude-code',
312
+ 'Or: brew install claude (macOS)',
313
+ 'Then run: claude --version',
314
+ ]
315
+ : [
316
+ `Command '${claudeCommand}' not found`,
317
+ 'Check settings: zeroshot settings',
318
+ 'Update claudeCommand: zeroshot settings set claudeCommand "your-command"',
319
+ 'Or install the missing command',
320
+ ]
328
321
  )
329
322
  );
330
- }
331
-
332
- // Check version (warn if old)
333
- if (claude.version) {
323
+ } else if (claude.version) {
334
324
  const [major, minor] = claude.version.split('.').map(Number);
335
325
  if (major < 1 || (major === 1 && minor < 0)) {
336
326
  warnings.push(
@@ -338,21 +328,45 @@ function runPreflight(options = {}) {
338
328
  );
339
329
  }
340
330
  }
341
- }
342
331
 
343
- // 3. Check if running as root (blocks --dangerously-skip-permissions)
344
- if (process.getuid && process.getuid() === 0) {
332
+ // Claude CLI refuses --dangerously-skip-permissions when running as root.
333
+ if (process.getuid && process.getuid() === 0) {
334
+ errors.push(
335
+ formatError(
336
+ 'Running as root',
337
+ 'Claude CLI refuses --dangerously-skip-permissions flag when running as root (UID 0)',
338
+ [
339
+ 'Run as non-root user in Docker: docker run --user 1000:1000 ...',
340
+ 'Or create non-root user: adduser testuser && su - testuser',
341
+ 'Or use existing node user: docker run --user node ...',
342
+ 'Security: Claude CLI blocks this flag as root to prevent privilege escalation',
343
+ ]
344
+ )
345
+ );
346
+ }
347
+ } else if (providerName === 'codex') {
348
+ if (!commandExists('codex')) {
349
+ errors.push(
350
+ formatError('Codex CLI not available', 'Command "codex" not installed', [
351
+ 'Install Codex CLI: npm install -g @openai/codex',
352
+ 'Then run: codex --version',
353
+ ])
354
+ );
355
+ }
356
+ } else if (providerName === 'gemini') {
357
+ if (!commandExists('gemini')) {
358
+ errors.push(
359
+ formatError('Gemini CLI not available', 'Command "gemini" not installed', [
360
+ 'Install Gemini CLI: npm install -g @google/gemini-cli',
361
+ 'Then run: gemini --version',
362
+ ])
363
+ );
364
+ }
365
+ } else {
345
366
  errors.push(
346
- formatError(
347
- 'Running as root',
348
- 'Claude CLI refuses --dangerously-skip-permissions flag when running as root (UID 0)',
349
- [
350
- 'Run as non-root user in Docker: docker run --user 1000:1000 ...',
351
- 'Or create non-root user: adduser testuser && su - testuser',
352
- 'Or use existing node user: docker run --user node ...',
353
- 'Security: Claude CLI blocks this flag as root to prevent privilege escalation',
354
- ]
355
- )
367
+ formatError('Unknown provider', `Provider "${providerName}" is not supported`, [
368
+ 'Use claude, codex, or gemini',
369
+ ])
356
370
  );
357
371
  }
358
372
 
@@ -361,26 +375,18 @@ function runPreflight(options = {}) {
361
375
  const gh = checkGhAuth();
362
376
  if (!gh.installed) {
363
377
  errors.push(
364
- formatError(
365
- 'GitHub CLI (gh) not installed',
366
- 'Required for fetching issues by number',
367
- [
368
- 'Install: brew install gh (macOS) or apt install gh (Linux)',
369
- 'Or download from: https://cli.github.com/',
370
- ]
371
- )
378
+ formatError('GitHub CLI (gh) not installed', 'Required for fetching issues by number', [
379
+ 'Install: brew install gh (macOS) or apt install gh (Linux)',
380
+ 'Or download from: https://cli.github.com/',
381
+ ])
372
382
  );
373
383
  } else if (!gh.authenticated) {
374
384
  errors.push(
375
- formatError(
376
- 'GitHub CLI (gh) not authenticated',
377
- gh.error,
378
- [
379
- 'Run: gh auth login',
380
- 'Select GitHub.com, HTTPS, and authenticate via browser',
381
- 'Then verify: gh auth status',
382
- ]
383
- )
385
+ formatError('GitHub CLI (gh) not authenticated', gh.error, [
386
+ 'Run: gh auth login',
387
+ 'Select GitHub.com, HTTPS, and authenticate via browser',
388
+ 'Then verify: gh auth status',
389
+ ])
384
390
  );
385
391
  }
386
392
  }
@@ -415,15 +421,11 @@ function runPreflight(options = {}) {
415
421
  }
416
422
  if (!isGitRepo) {
417
423
  errors.push(
418
- formatError(
419
- 'Not in a git repository',
420
- 'Worktree isolation requires a git repository',
421
- [
422
- 'Run from within a git repository',
423
- 'Or use --docker instead of --worktree for non-git directories',
424
- 'Initialize a repo with: git init',
425
- ]
426
- )
424
+ formatError('Not in a git repository', 'Worktree isolation requires a git repository', [
425
+ 'Run from within a git repository',
426
+ 'Or use --docker instead of --worktree for non-git directories',
427
+ 'Initialize a repo with: git init',
428
+ ])
427
429
  );
428
430
  }
429
431
  }
@@ -442,6 +444,7 @@ function runPreflight(options = {}) {
442
444
  * @param {boolean} options.requireDocker - Whether Docker is required
443
445
  * @param {boolean} options.requireGit - Whether git repo is required
444
446
  * @param {boolean} options.quiet - Suppress success messages
447
+ * @param {string} options.provider - Provider override
445
448
  */
446
449
  function requirePreflight(options = {}) {
447
450
  const result = runPreflight(options);
@@ -0,0 +1,45 @@
1
+ function buildCommand(context, options = {}, commandConfig = {}) {
2
+ const { modelSpec, outputFormat, jsonSchema, autoApprove, cliFeatures = {} } = options;
3
+
4
+ const command = commandConfig.command || 'claude';
5
+ const extraArgs = commandConfig.args || [];
6
+ const args = [...extraArgs, '--print', '--input-format', 'text'];
7
+
8
+ if (outputFormat && cliFeatures.supportsOutputFormat !== false) {
9
+ args.push('--output-format', outputFormat);
10
+ }
11
+
12
+ if (outputFormat === 'stream-json') {
13
+ if (cliFeatures.supportsVerbose !== false) {
14
+ args.push('--verbose');
15
+ }
16
+ if (cliFeatures.supportsIncludePartials !== false) {
17
+ args.push('--include-partial-messages');
18
+ }
19
+ }
20
+
21
+ if (jsonSchema && outputFormat === 'json' && cliFeatures.supportsJsonSchema !== false) {
22
+ const schemaString = typeof jsonSchema === 'string' ? jsonSchema : JSON.stringify(jsonSchema);
23
+ args.push('--json-schema', schemaString);
24
+ }
25
+
26
+ if (modelSpec?.model && cliFeatures.supportsModel !== false) {
27
+ args.push('--model', modelSpec.model);
28
+ }
29
+
30
+ if (autoApprove && cliFeatures.supportsAutoApprove !== false) {
31
+ args.push('--dangerously-skip-permissions');
32
+ }
33
+
34
+ args.push(context);
35
+
36
+ return {
37
+ binary: command,
38
+ args,
39
+ env: {},
40
+ };
41
+ }
42
+
43
+ module.exports = {
44
+ buildCommand,
45
+ };
@@ -0,0 +1,134 @@
1
+ const BaseProvider = require('../base-provider');
2
+ const { getClaudeCommand } = require('../../../lib/settings');
3
+ const { commandExists, getCommandPath, getHelpOutput } = require('../../../lib/provider-detection');
4
+ const { buildCommand } = require('./cli-builder');
5
+ const { parseEvent } = require('./output-parser');
6
+ const {
7
+ MODEL_CATALOG,
8
+ LEVEL_MAPPING,
9
+ DEFAULT_LEVEL,
10
+ DEFAULT_MAX_LEVEL,
11
+ DEFAULT_MIN_LEVEL,
12
+ } = require('./models');
13
+
14
+ const warned = new Set();
15
+
16
+ class AnthropicProvider extends BaseProvider {
17
+ constructor() {
18
+ super({ name: 'claude', displayName: 'Claude', cliCommand: 'claude' });
19
+ this._cliFeatures = null;
20
+ }
21
+
22
+ // SDK not implemented - uses CLI only
23
+ // See BaseProvider for SDK extension point documentation
24
+
25
+ isAvailable() {
26
+ const { command } = getClaudeCommand();
27
+ return commandExists(command);
28
+ }
29
+
30
+ getCliPath() {
31
+ const { command } = getClaudeCommand();
32
+ return getCommandPath(command) || command;
33
+ }
34
+
35
+ getInstallInstructions() {
36
+ return ['npm install -g @anthropic-ai/claude-code', 'Or (macOS): brew install claude'].join(
37
+ '\n'
38
+ );
39
+ }
40
+
41
+ getAuthInstructions() {
42
+ return 'claude login';
43
+ }
44
+
45
+ getCliFeatures() {
46
+ if (this._cliFeatures) return this._cliFeatures;
47
+
48
+ const { command, args } = getClaudeCommand();
49
+ const help = getHelpOutput(command, args);
50
+ const unknown = !help;
51
+
52
+ const features = {
53
+ supportsOutputFormat: unknown ? true : /--output-format/.test(help),
54
+ supportsStreamJson: unknown ? true : /stream-json/.test(help),
55
+ supportsJsonSchema: unknown ? true : /--json-schema/.test(help),
56
+ supportsAutoApprove: unknown ? true : /--dangerously-skip-permissions/.test(help),
57
+ supportsIncludePartials: unknown ? true : /--include-partial-messages/.test(help),
58
+ supportsVerbose: unknown ? true : /--verbose/.test(help),
59
+ supportsModel: unknown ? true : /--model/.test(help),
60
+ unknown,
61
+ };
62
+
63
+ this._cliFeatures = features;
64
+ return features;
65
+ }
66
+
67
+ getCredentialPaths() {
68
+ return ['~/.claude'];
69
+ }
70
+
71
+ buildCommand(context, options) {
72
+ const { command, args } = getClaudeCommand();
73
+ const cliFeatures = options.cliFeatures || {};
74
+
75
+ if (options.jsonSchema && options.outputFormat !== 'json' && !options.strictSchema) {
76
+ this._warnOnce(
77
+ 'claude-jsonschema-stream',
78
+ 'jsonSchema requested with stream output; schema enforcement will be post-validated.'
79
+ );
80
+ }
81
+
82
+ if (
83
+ options.jsonSchema &&
84
+ options.outputFormat === 'json' &&
85
+ cliFeatures.supportsJsonSchema === false
86
+ ) {
87
+ this._warnOnce(
88
+ 'claude-jsonschema-flag',
89
+ 'Claude CLI does not support --json-schema; skipping schema flag.'
90
+ );
91
+ }
92
+
93
+ if (options.autoApprove && cliFeatures.supportsAutoApprove === false) {
94
+ this._warnOnce(
95
+ 'claude-auto-approve',
96
+ 'Claude CLI does not support --dangerously-skip-permissions; continuing without auto-approve.'
97
+ );
98
+ }
99
+
100
+ return buildCommand(context, { ...options, cliFeatures }, { command, args });
101
+ }
102
+
103
+ parseEvent(line) {
104
+ return parseEvent(line);
105
+ }
106
+
107
+ getModelCatalog() {
108
+ return MODEL_CATALOG;
109
+ }
110
+
111
+ getLevelMapping() {
112
+ return LEVEL_MAPPING;
113
+ }
114
+
115
+ getDefaultLevel() {
116
+ return DEFAULT_LEVEL;
117
+ }
118
+
119
+ getDefaultMaxLevel() {
120
+ return DEFAULT_MAX_LEVEL;
121
+ }
122
+
123
+ getDefaultMinLevel() {
124
+ return DEFAULT_MIN_LEVEL;
125
+ }
126
+
127
+ _warnOnce(key, message) {
128
+ if (warned.has(key)) return;
129
+ warned.add(key);
130
+ console.warn(`⚠️ ${message}`);
131
+ }
132
+ }
133
+
134
+ module.exports = AnthropicProvider;
@@ -0,0 +1,23 @@
1
+ const MODEL_CATALOG = {
2
+ haiku: { rank: 1 },
3
+ sonnet: { rank: 2 },
4
+ opus: { rank: 3 },
5
+ };
6
+
7
+ const LEVEL_MAPPING = {
8
+ level1: { rank: 1, model: 'haiku' },
9
+ level2: { rank: 2, model: 'sonnet' },
10
+ level3: { rank: 3, model: 'opus' },
11
+ };
12
+
13
+ const DEFAULT_LEVEL = 'level2';
14
+ const DEFAULT_MAX_LEVEL = 'level3';
15
+ const DEFAULT_MIN_LEVEL = 'level1';
16
+
17
+ module.exports = {
18
+ MODEL_CATALOG,
19
+ LEVEL_MAPPING,
20
+ DEFAULT_LEVEL,
21
+ DEFAULT_MAX_LEVEL,
22
+ DEFAULT_MIN_LEVEL,
23
+ };
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Stream JSON Parser for Claude Code output
3
+ */
4
+
5
+ /**
6
+ * Parse a single JSON line and extract displayable content
7
+ * @param {string} line
8
+ * @returns {Object|null}
9
+ */
10
+ function parseEvent(line) {
11
+ const trimmed = line.trim();
12
+ if (!trimmed || !trimmed.startsWith('{') || !trimmed.endsWith('}')) {
13
+ return null;
14
+ }
15
+
16
+ let event;
17
+ try {
18
+ event = JSON.parse(trimmed);
19
+ } catch {
20
+ return null;
21
+ }
22
+
23
+ if (event.type === 'stream_event' && event.event) {
24
+ return parseStreamEvent(event.event);
25
+ }
26
+
27
+ if (event.type === 'assistant' && event.message?.content) {
28
+ return parseAssistantMessage(event.message);
29
+ }
30
+
31
+ if (event.type === 'user' && event.message?.content) {
32
+ return parseUserMessage(event.message);
33
+ }
34
+
35
+ if (event.type === 'result') {
36
+ const usage = event.usage || {};
37
+ return {
38
+ type: 'result',
39
+ success: event.subtype === 'success',
40
+ result: event.result,
41
+ error: event.is_error ? event.result : null,
42
+ cost: event.total_cost_usd,
43
+ duration: event.duration_ms,
44
+ inputTokens: usage.input_tokens || 0,
45
+ outputTokens: usage.output_tokens || 0,
46
+ cacheReadInputTokens: usage.cache_read_input_tokens || 0,
47
+ cacheCreationInputTokens: usage.cache_creation_input_tokens || 0,
48
+ modelUsage: event.modelUsage || null,
49
+ };
50
+ }
51
+
52
+ if (event.type === 'system') {
53
+ return null;
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ function parseStreamEvent(inner) {
60
+ if (inner.type === 'content_block_delta' && inner.delta) {
61
+ const delta = inner.delta;
62
+
63
+ if (delta.type === 'text_delta' && delta.text) {
64
+ return {
65
+ type: 'text',
66
+ text: delta.text,
67
+ };
68
+ }
69
+
70
+ if (delta.type === 'thinking_delta' && delta.thinking) {
71
+ return {
72
+ type: 'thinking',
73
+ text: delta.thinking,
74
+ };
75
+ }
76
+ }
77
+
78
+ return null;
79
+ }
80
+
81
+ function parseAssistantMessage(message) {
82
+ const results = [];
83
+
84
+ for (const block of message.content) {
85
+ if (block.type === 'tool_use') {
86
+ results.push({
87
+ type: 'tool_call',
88
+ toolName: block.name,
89
+ toolId: block.id,
90
+ input: block.input,
91
+ });
92
+ }
93
+
94
+ if (block.type === 'thinking' && block.thinking) {
95
+ results.push({
96
+ type: 'thinking',
97
+ text: block.thinking,
98
+ });
99
+ }
100
+ }
101
+
102
+ if (results.length === 1) {
103
+ return results[0];
104
+ }
105
+
106
+ if (results.length > 1) {
107
+ return results;
108
+ }
109
+
110
+ return null;
111
+ }
112
+
113
+ function parseUserMessage(message) {
114
+ const results = [];
115
+
116
+ for (const block of message.content) {
117
+ if (block.type === 'tool_result') {
118
+ results.push({
119
+ type: 'tool_result',
120
+ toolId: block.tool_use_id,
121
+ content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content),
122
+ isError: block.is_error || false,
123
+ });
124
+ }
125
+ }
126
+
127
+ if (results.length === 1) {
128
+ return results[0];
129
+ }
130
+
131
+ if (results.length > 1) {
132
+ return results;
133
+ }
134
+
135
+ return null;
136
+ }
137
+
138
+ function parseChunk(chunk) {
139
+ const events = [];
140
+ const lines = chunk.split('\n');
141
+
142
+ for (const line of lines) {
143
+ const event = parseEvent(line);
144
+ if (event) {
145
+ if (Array.isArray(event)) {
146
+ events.push(...event);
147
+ } else {
148
+ events.push(event);
149
+ }
150
+ }
151
+ }
152
+
153
+ return events;
154
+ }
155
+
156
+ module.exports = {
157
+ parseEvent,
158
+ parseChunk,
159
+ };