@kb-labs/workflow-builtins 1.6.0 → 2.6.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/index.js CHANGED
@@ -89,17 +89,25 @@ async function shellHandler(ctx, input) {
89
89
  stdoutBuf += chunk.toString();
90
90
  const lines = stdoutBuf.split("\n");
91
91
  stdoutBuf = lines.pop() ?? "";
92
- for (const line of lines) emitLine("stdout", line);
92
+ for (const line of lines) {
93
+ emitLine("stdout", line);
94
+ }
93
95
  });
94
96
  proc.stderr?.on("data", (chunk) => {
95
97
  stderrBuf += chunk.toString();
96
98
  const lines = stderrBuf.split("\n");
97
99
  stderrBuf = lines.pop() ?? "";
98
- for (const line of lines) emitLine("stderr", line);
100
+ for (const line of lines) {
101
+ emitLine("stderr", line);
102
+ }
99
103
  });
100
104
  const result = await proc;
101
- if (stdoutBuf) emitLine("stdout", stdoutBuf);
102
- if (stderrBuf) emitLine("stderr", stderrBuf);
105
+ if (stdoutBuf) {
106
+ emitLine("stdout", stdoutBuf);
107
+ }
108
+ if (stderrBuf) {
109
+ emitLine("stderr", stderrBuf);
110
+ }
103
111
  const output = {
104
112
  stdout: result.stdout,
105
113
  stderr: result.stderr,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shell.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/workflow-runtime/builtin-handlers/shell\n * Built-in shell execution handler for workflows\n *\n * Security features:\n * - Blocks dangerous commands (rm -rf /, fork bombs, etc.)\n * - Timeout enforcement (default 5 minutes)\n * - Environment variable isolation\n * - Working directory restrictions\n */\n\nimport { execaCommand } from 'execa';\nimport type { PluginContextV3 } from '@kb-labs/plugin-contracts';\n\n/**\n * Commands that are always blocked (dangerous)\n */\nconst BLOCKED_COMMANDS = [\n 'rm -rf /',\n 'rm -rf /*',\n 'mkfs',\n 'dd if=',\n ':(){:|:&};:', // Fork bomb\n 'chmod -R 777 /',\n 'chown -R',\n '> /dev/sda',\n 'mv /* ',\n 'fdisk',\n];\n\n/**\n * Split string into chunks of specified size\n */\nfunction chunkString(str: string, chunkSize: number): string[] {\n const chunks: string[] = [];\n for (let i = 0; i < str.length; i += chunkSize) {\n chunks.push(str.slice(i, i + chunkSize));\n }\n return chunks;\n}\n\n/**\n * Shell handler input\n */\nexport interface ShellInput {\n /** Command to execute */\n command: string;\n\n /** Additional environment variables */\n env?: Record<string, string>;\n\n /** Timeout in milliseconds (default: 300000 = 5 min) */\n timeout?: number;\n\n /** Throw on non-zero exit code (default: false) */\n throwOnError?: boolean;\n}\n\n/**\n * Shell handler output\n */\nexport interface ShellOutput {\n /** Standard output */\n stdout: string;\n\n /** Standard error */\n stderr: string;\n\n /** Exit code */\n exitCode: number;\n\n /** Whether command succeeded (exitCode === 0) */\n ok: boolean;\n}\n\n/**\n * Output marker prefix. Shell commands emit structured outputs via:\n * echo '::kb-output::{\"passed\":true}'\n *\n * This separates logs (plain stdout) from structured data (outputs).\n * Similar to GitHub Actions ::set-output:: pattern.\n */\nconst OUTPUT_MARKER = '::kb-output::';\n\n/**\n * Extract structured outputs from shell stdout.\n *\n * Priority:\n * 1. ::kb-output::{...} marker lines — explicit, recommended\n * 2. Entire stdout as JSON — fallback for backward compat (simple commands)\n *\n * Logs and other stdout content are ignored for output purposes.\n */\nfunction mergeJsonOutputs(output: ShellOutput): Record<string, unknown> {\n const base: Record<string, unknown> = { ...output };\n const trimmed = output.stdout.trim();\n if (!trimmed) {return base;}\n\n // Priority 1: Look for ::kb-output:: marker lines\n const lines = output.stdout.split('\\n');\n let foundMarker = false;\n for (const line of lines) {\n const idx = line.indexOf(OUTPUT_MARKER);\n if (idx !== -1) {\n foundMarker = true;\n try {\n const parsed = JSON.parse(line.slice(idx + OUTPUT_MARKER.length));\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n Object.assign(base, parsed);\n }\n } catch {\n // Malformed marker — skip\n }\n }\n }\n\n if (foundMarker) {return base;}\n\n // Priority 2: Fallback — entire stdout as JSON (backward compat)\n try {\n const parsed = JSON.parse(trimmed);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n Object.assign(base, parsed);\n }\n } catch {\n // Not JSON — return as-is\n }\n\n return base;\n}\n\n/**\n * Built-in shell execution handler.\n *\n * Executes shell commands with safety checks and timeout enforcement.\n *\n * @param ctx - Handler execution context\n * @param input - Shell command input\n * @returns Shell execution result\n * @throws Error if dangerous command detected or timeout exceeded\n */\nasync function shellHandler(\n ctx: PluginContextV3,\n input: ShellInput,\n): Promise<Record<string, unknown>> {\n const { command, env = {}, timeout = 300000, throwOnError = false } = input;\n\n // Security: Check for dangerous commands\n const normalizedCommand = command.toLowerCase().trim();\n for (const blocked of BLOCKED_COMMANDS) {\n if (normalizedCommand.includes(blocked.toLowerCase())) {\n throw new Error(\n `Dangerous command blocked: \"${blocked}\". Command attempted: ${command.slice(0, 100)}`,\n );\n }\n }\n\n // Get working directory from context (workflow workspace)\n const cwd = ctx.cwd;\n\n // Merge environment variables\n const mergedEnv = {\n ...process.env,\n ...env,\n };\n\n ctx.platform.logger.info('Executing shell command', {\n command: command.slice(0, 200),\n cwd,\n timeout,\n });\n\n try {\n const proc = execaCommand(command, {\n cwd,\n env: mergedEnv,\n shell: true,\n stdio: 'pipe',\n timeout,\n reject: false, // We handle exit codes ourselves\n });\n\n // Stream stdout/stderr line-by-line in real-time\n let lineNo = 0;\n let stdoutBuf = '';\n let stderrBuf = '';\n\n const emitLine = (stream: 'stdout' | 'stderr', line: string) => {\n lineNo++;\n void ctx.api.events.emit('log.line', { stream, line, lineNo, level: stream === 'stderr' ? 'error' : 'info' });\n };\n\n proc.stdout?.on('data', (chunk: Buffer) => {\n stdoutBuf += chunk.toString();\n const lines = stdoutBuf.split('\\n');\n stdoutBuf = lines.pop() ?? '';\n for (const line of lines) emitLine('stdout', line);\n });\n\n proc.stderr?.on('data', (chunk: Buffer) => {\n stderrBuf += chunk.toString();\n const lines = stderrBuf.split('\\n');\n stderrBuf = lines.pop() ?? '';\n for (const line of lines) emitLine('stderr', line);\n });\n\n const result = await proc;\n\n // Flush remaining buffered content\n if (stdoutBuf) emitLine('stdout', stdoutBuf);\n if (stderrBuf) emitLine('stderr', stderrBuf);\n\n const output: ShellOutput = {\n stdout: result.stdout,\n stderr: result.stderr,\n exitCode: result.exitCode ?? 0,\n ok: (result.exitCode ?? 0) === 0,\n };\n\n if (output.ok) {\n ctx.platform.logger.info('Shell command completed successfully', {\n exitCode: output.exitCode,\n stdoutLines: output.stdout.split('\\n').length,\n });\n } else {\n ctx.platform.logger.warn('Shell command failed', {\n exitCode: output.exitCode,\n stderrLines: output.stderr.split('\\n').length,\n });\n\n if (throwOnError) {\n throw new Error(`Shell command failed with exit code ${output.exitCode}: ${output.stderr.slice(0, 500)}`);\n }\n }\n\n return mergeJsonOutputs(output);\n } catch (error) {\n // Handle timeout\n if (error && typeof error === 'object' && 'timedOut' in error && error.timedOut) {\n throw new Error(`Shell command timed out after ${timeout}ms`);\n }\n\n // Handle execution error\n if (error && typeof error === 'object' && 'exitCode' in error) {\n const execError = error as { exitCode?: number; stdout?: string; stderr?: string };\n const output: ShellOutput = {\n stdout: execError.stdout ?? '',\n stderr: execError.stderr ?? '',\n exitCode: execError.exitCode ?? 1,\n ok: false,\n };\n\n ctx.platform.logger.error('Shell command execution failed', undefined, {\n exitCode: output.exitCode,\n stderr: output.stderr.slice(0, 500),\n });\n\n if (!throwOnError) {\n return { ...output };\n }\n }\n\n throw error;\n }\n}\n\n// Export handler in format expected by ExecutionBackend\nexport default {\n execute: shellHandler,\n};\n"]}
1
+ {"version":3,"sources":["../src/shell.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/workflow-runtime/builtin-handlers/shell\n * Built-in shell execution handler for workflows\n *\n * Security features:\n * - Blocks dangerous commands (rm -rf /, fork bombs, etc.)\n * - Timeout enforcement (default 5 minutes)\n * - Environment variable isolation\n * - Working directory restrictions\n */\n\nimport { execaCommand } from 'execa';\nimport type { PluginContextV3 } from '@kb-labs/plugin-contracts';\n\n/**\n * Commands that are always blocked (dangerous)\n */\nconst BLOCKED_COMMANDS = [\n 'rm -rf /',\n 'rm -rf /*',\n 'mkfs',\n 'dd if=',\n ':(){:|:&};:', // Fork bomb\n 'chmod -R 777 /',\n 'chown -R',\n '> /dev/sda',\n 'mv /* ',\n 'fdisk',\n];\n\n/**\n * Split string into chunks of specified size\n */\nfunction chunkString(str: string, chunkSize: number): string[] {\n const chunks: string[] = [];\n for (let i = 0; i < str.length; i += chunkSize) {\n chunks.push(str.slice(i, i + chunkSize));\n }\n return chunks;\n}\n\n/**\n * Shell handler input\n */\nexport interface ShellInput {\n /** Command to execute */\n command: string;\n\n /** Additional environment variables */\n env?: Record<string, string>;\n\n /** Timeout in milliseconds (default: 300000 = 5 min) */\n timeout?: number;\n\n /** Throw on non-zero exit code (default: false) */\n throwOnError?: boolean;\n}\n\n/**\n * Shell handler output\n */\nexport interface ShellOutput {\n /** Standard output */\n stdout: string;\n\n /** Standard error */\n stderr: string;\n\n /** Exit code */\n exitCode: number;\n\n /** Whether command succeeded (exitCode === 0) */\n ok: boolean;\n}\n\n/**\n * Output marker prefix. Shell commands emit structured outputs via:\n * echo '::kb-output::{\"passed\":true}'\n *\n * This separates logs (plain stdout) from structured data (outputs).\n * Similar to GitHub Actions ::set-output:: pattern.\n */\nconst OUTPUT_MARKER = '::kb-output::';\n\n/**\n * Extract structured outputs from shell stdout.\n *\n * Priority:\n * 1. ::kb-output::{...} marker lines — explicit, recommended\n * 2. Entire stdout as JSON — fallback for backward compat (simple commands)\n *\n * Logs and other stdout content are ignored for output purposes.\n */\nfunction mergeJsonOutputs(output: ShellOutput): Record<string, unknown> {\n const base: Record<string, unknown> = { ...output };\n const trimmed = output.stdout.trim();\n if (!trimmed) {return base;}\n\n // Priority 1: Look for ::kb-output:: marker lines\n const lines = output.stdout.split('\\n');\n let foundMarker = false;\n for (const line of lines) {\n const idx = line.indexOf(OUTPUT_MARKER);\n if (idx !== -1) {\n foundMarker = true;\n try {\n const parsed = JSON.parse(line.slice(idx + OUTPUT_MARKER.length));\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n Object.assign(base, parsed);\n }\n } catch {\n // Malformed marker — skip\n }\n }\n }\n\n if (foundMarker) {return base;}\n\n // Priority 2: Fallback — entire stdout as JSON (backward compat)\n try {\n const parsed = JSON.parse(trimmed);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n Object.assign(base, parsed);\n }\n } catch {\n // Not JSON — return as-is\n }\n\n return base;\n}\n\n/**\n * Built-in shell execution handler.\n *\n * Executes shell commands with safety checks and timeout enforcement.\n *\n * @param ctx - Handler execution context\n * @param input - Shell command input\n * @returns Shell execution result\n * @throws Error if dangerous command detected or timeout exceeded\n */\nasync function shellHandler(\n ctx: PluginContextV3,\n input: ShellInput,\n): Promise<Record<string, unknown>> {\n const { command, env = {}, timeout = 300000, throwOnError = false } = input;\n\n // Security: Check for dangerous commands\n const normalizedCommand = command.toLowerCase().trim();\n for (const blocked of BLOCKED_COMMANDS) {\n if (normalizedCommand.includes(blocked.toLowerCase())) {\n throw new Error(\n `Dangerous command blocked: \"${blocked}\". Command attempted: ${command.slice(0, 100)}`,\n );\n }\n }\n\n // Get working directory from context (workflow workspace)\n const cwd = ctx.cwd;\n\n // Merge environment variables\n const mergedEnv = {\n ...process.env,\n ...env,\n };\n\n ctx.platform.logger.info('Executing shell command', {\n command: command.slice(0, 200),\n cwd,\n timeout,\n });\n\n try {\n const proc = execaCommand(command, {\n cwd,\n env: mergedEnv,\n shell: true,\n stdio: 'pipe',\n timeout,\n reject: false, // We handle exit codes ourselves\n });\n\n // Stream stdout/stderr line-by-line in real-time\n let lineNo = 0;\n let stdoutBuf = '';\n let stderrBuf = '';\n\n const emitLine = (stream: 'stdout' | 'stderr', line: string) => {\n lineNo++;\n void ctx.api.events.emit('log.line', { stream, line, lineNo, level: stream === 'stderr' ? 'error' : 'info' });\n };\n\n proc.stdout?.on('data', (chunk: Buffer) => {\n stdoutBuf += chunk.toString();\n const lines = stdoutBuf.split('\\n');\n stdoutBuf = lines.pop() ?? '';\n for (const line of lines) {emitLine('stdout', line);}\n });\n\n proc.stderr?.on('data', (chunk: Buffer) => {\n stderrBuf += chunk.toString();\n const lines = stderrBuf.split('\\n');\n stderrBuf = lines.pop() ?? '';\n for (const line of lines) {emitLine('stderr', line);}\n });\n\n const result = await proc;\n\n // Flush remaining buffered content\n if (stdoutBuf) {emitLine('stdout', stdoutBuf);}\n if (stderrBuf) {emitLine('stderr', stderrBuf);}\n\n const output: ShellOutput = {\n stdout: result.stdout,\n stderr: result.stderr,\n exitCode: result.exitCode ?? 0,\n ok: (result.exitCode ?? 0) === 0,\n };\n\n if (output.ok) {\n ctx.platform.logger.info('Shell command completed successfully', {\n exitCode: output.exitCode,\n stdoutLines: output.stdout.split('\\n').length,\n });\n } else {\n ctx.platform.logger.warn('Shell command failed', {\n exitCode: output.exitCode,\n stderrLines: output.stderr.split('\\n').length,\n });\n\n if (throwOnError) {\n throw new Error(`Shell command failed with exit code ${output.exitCode}: ${output.stderr.slice(0, 500)}`);\n }\n }\n\n return mergeJsonOutputs(output);\n } catch (error) {\n // Handle timeout\n if (error && typeof error === 'object' && 'timedOut' in error && error.timedOut) {\n throw new Error(`Shell command timed out after ${timeout}ms`);\n }\n\n // Handle execution error\n if (error && typeof error === 'object' && 'exitCode' in error) {\n const execError = error as { exitCode?: number; stdout?: string; stderr?: string };\n const output: ShellOutput = {\n stdout: execError.stdout ?? '',\n stderr: execError.stderr ?? '',\n exitCode: execError.exitCode ?? 1,\n ok: false,\n };\n\n ctx.platform.logger.error('Shell command execution failed', undefined, {\n exitCode: output.exitCode,\n stderr: output.stderr.slice(0, 500),\n });\n\n if (!throwOnError) {\n return { ...output };\n }\n }\n\n throw error;\n }\n}\n\n// Export handler in format expected by ExecutionBackend\nexport default {\n execute: shellHandler,\n};\n"]}
package/dist/shell.js CHANGED
@@ -89,17 +89,25 @@ async function shellHandler(ctx, input) {
89
89
  stdoutBuf += chunk.toString();
90
90
  const lines = stdoutBuf.split("\n");
91
91
  stdoutBuf = lines.pop() ?? "";
92
- for (const line of lines) emitLine("stdout", line);
92
+ for (const line of lines) {
93
+ emitLine("stdout", line);
94
+ }
93
95
  });
94
96
  proc.stderr?.on("data", (chunk) => {
95
97
  stderrBuf += chunk.toString();
96
98
  const lines = stderrBuf.split("\n");
97
99
  stderrBuf = lines.pop() ?? "";
98
- for (const line of lines) emitLine("stderr", line);
100
+ for (const line of lines) {
101
+ emitLine("stderr", line);
102
+ }
99
103
  });
100
104
  const result = await proc;
101
- if (stdoutBuf) emitLine("stdout", stdoutBuf);
102
- if (stderrBuf) emitLine("stderr", stderrBuf);
105
+ if (stdoutBuf) {
106
+ emitLine("stdout", stdoutBuf);
107
+ }
108
+ if (stderrBuf) {
109
+ emitLine("stderr", stderrBuf);
110
+ }
103
111
  const output = {
104
112
  stdout: result.stdout,
105
113
  stderr: result.stderr,
package/dist/shell.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shell.ts"],"names":[],"mappings":"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","file":"shell.js","sourcesContent":["/**\n * @module @kb-labs/workflow-runtime/builtin-handlers/shell\n * Built-in shell execution handler for workflows\n *\n * Security features:\n * - Blocks dangerous commands (rm -rf /, fork bombs, etc.)\n * - Timeout enforcement (default 5 minutes)\n * - Environment variable isolation\n * - Working directory restrictions\n */\n\nimport { execaCommand } from 'execa';\nimport type { PluginContextV3 } from '@kb-labs/plugin-contracts';\n\n/**\n * Commands that are always blocked (dangerous)\n */\nconst BLOCKED_COMMANDS = [\n 'rm -rf /',\n 'rm -rf /*',\n 'mkfs',\n 'dd if=',\n ':(){:|:&};:', // Fork bomb\n 'chmod -R 777 /',\n 'chown -R',\n '> /dev/sda',\n 'mv /* ',\n 'fdisk',\n];\n\n/**\n * Split string into chunks of specified size\n */\nfunction chunkString(str: string, chunkSize: number): string[] {\n const chunks: string[] = [];\n for (let i = 0; i < str.length; i += chunkSize) {\n chunks.push(str.slice(i, i + chunkSize));\n }\n return chunks;\n}\n\n/**\n * Shell handler input\n */\nexport interface ShellInput {\n /** Command to execute */\n command: string;\n\n /** Additional environment variables */\n env?: Record<string, string>;\n\n /** Timeout in milliseconds (default: 300000 = 5 min) */\n timeout?: number;\n\n /** Throw on non-zero exit code (default: false) */\n throwOnError?: boolean;\n}\n\n/**\n * Shell handler output\n */\nexport interface ShellOutput {\n /** Standard output */\n stdout: string;\n\n /** Standard error */\n stderr: string;\n\n /** Exit code */\n exitCode: number;\n\n /** Whether command succeeded (exitCode === 0) */\n ok: boolean;\n}\n\n/**\n * Output marker prefix. Shell commands emit structured outputs via:\n * echo '::kb-output::{\"passed\":true}'\n *\n * This separates logs (plain stdout) from structured data (outputs).\n * Similar to GitHub Actions ::set-output:: pattern.\n */\nconst OUTPUT_MARKER = '::kb-output::';\n\n/**\n * Extract structured outputs from shell stdout.\n *\n * Priority:\n * 1. ::kb-output::{...} marker lines — explicit, recommended\n * 2. Entire stdout as JSON — fallback for backward compat (simple commands)\n *\n * Logs and other stdout content are ignored for output purposes.\n */\nfunction mergeJsonOutputs(output: ShellOutput): Record<string, unknown> {\n const base: Record<string, unknown> = { ...output };\n const trimmed = output.stdout.trim();\n if (!trimmed) {return base;}\n\n // Priority 1: Look for ::kb-output:: marker lines\n const lines = output.stdout.split('\\n');\n let foundMarker = false;\n for (const line of lines) {\n const idx = line.indexOf(OUTPUT_MARKER);\n if (idx !== -1) {\n foundMarker = true;\n try {\n const parsed = JSON.parse(line.slice(idx + OUTPUT_MARKER.length));\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n Object.assign(base, parsed);\n }\n } catch {\n // Malformed marker — skip\n }\n }\n }\n\n if (foundMarker) {return base;}\n\n // Priority 2: Fallback — entire stdout as JSON (backward compat)\n try {\n const parsed = JSON.parse(trimmed);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n Object.assign(base, parsed);\n }\n } catch {\n // Not JSON — return as-is\n }\n\n return base;\n}\n\n/**\n * Built-in shell execution handler.\n *\n * Executes shell commands with safety checks and timeout enforcement.\n *\n * @param ctx - Handler execution context\n * @param input - Shell command input\n * @returns Shell execution result\n * @throws Error if dangerous command detected or timeout exceeded\n */\nasync function shellHandler(\n ctx: PluginContextV3,\n input: ShellInput,\n): Promise<Record<string, unknown>> {\n const { command, env = {}, timeout = 300000, throwOnError = false } = input;\n\n // Security: Check for dangerous commands\n const normalizedCommand = command.toLowerCase().trim();\n for (const blocked of BLOCKED_COMMANDS) {\n if (normalizedCommand.includes(blocked.toLowerCase())) {\n throw new Error(\n `Dangerous command blocked: \"${blocked}\". Command attempted: ${command.slice(0, 100)}`,\n );\n }\n }\n\n // Get working directory from context (workflow workspace)\n const cwd = ctx.cwd;\n\n // Merge environment variables\n const mergedEnv = {\n ...process.env,\n ...env,\n };\n\n ctx.platform.logger.info('Executing shell command', {\n command: command.slice(0, 200),\n cwd,\n timeout,\n });\n\n try {\n const proc = execaCommand(command, {\n cwd,\n env: mergedEnv,\n shell: true,\n stdio: 'pipe',\n timeout,\n reject: false, // We handle exit codes ourselves\n });\n\n // Stream stdout/stderr line-by-line in real-time\n let lineNo = 0;\n let stdoutBuf = '';\n let stderrBuf = '';\n\n const emitLine = (stream: 'stdout' | 'stderr', line: string) => {\n lineNo++;\n void ctx.api.events.emit('log.line', { stream, line, lineNo, level: stream === 'stderr' ? 'error' : 'info' });\n };\n\n proc.stdout?.on('data', (chunk: Buffer) => {\n stdoutBuf += chunk.toString();\n const lines = stdoutBuf.split('\\n');\n stdoutBuf = lines.pop() ?? '';\n for (const line of lines) emitLine('stdout', line);\n });\n\n proc.stderr?.on('data', (chunk: Buffer) => {\n stderrBuf += chunk.toString();\n const lines = stderrBuf.split('\\n');\n stderrBuf = lines.pop() ?? '';\n for (const line of lines) emitLine('stderr', line);\n });\n\n const result = await proc;\n\n // Flush remaining buffered content\n if (stdoutBuf) emitLine('stdout', stdoutBuf);\n if (stderrBuf) emitLine('stderr', stderrBuf);\n\n const output: ShellOutput = {\n stdout: result.stdout,\n stderr: result.stderr,\n exitCode: result.exitCode ?? 0,\n ok: (result.exitCode ?? 0) === 0,\n };\n\n if (output.ok) {\n ctx.platform.logger.info('Shell command completed successfully', {\n exitCode: output.exitCode,\n stdoutLines: output.stdout.split('\\n').length,\n });\n } else {\n ctx.platform.logger.warn('Shell command failed', {\n exitCode: output.exitCode,\n stderrLines: output.stderr.split('\\n').length,\n });\n\n if (throwOnError) {\n throw new Error(`Shell command failed with exit code ${output.exitCode}: ${output.stderr.slice(0, 500)}`);\n }\n }\n\n return mergeJsonOutputs(output);\n } catch (error) {\n // Handle timeout\n if (error && typeof error === 'object' && 'timedOut' in error && error.timedOut) {\n throw new Error(`Shell command timed out after ${timeout}ms`);\n }\n\n // Handle execution error\n if (error && typeof error === 'object' && 'exitCode' in error) {\n const execError = error as { exitCode?: number; stdout?: string; stderr?: string };\n const output: ShellOutput = {\n stdout: execError.stdout ?? '',\n stderr: execError.stderr ?? '',\n exitCode: execError.exitCode ?? 1,\n ok: false,\n };\n\n ctx.platform.logger.error('Shell command execution failed', undefined, {\n exitCode: output.exitCode,\n stderr: output.stderr.slice(0, 500),\n });\n\n if (!throwOnError) {\n return { ...output };\n }\n }\n\n throw error;\n }\n}\n\n// Export handler in format expected by ExecutionBackend\nexport default {\n execute: shellHandler,\n};\n"]}
1
+ {"version":3,"sources":["../src/shell.ts"],"names":[],"mappings":"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","file":"shell.js","sourcesContent":["/**\n * @module @kb-labs/workflow-runtime/builtin-handlers/shell\n * Built-in shell execution handler for workflows\n *\n * Security features:\n * - Blocks dangerous commands (rm -rf /, fork bombs, etc.)\n * - Timeout enforcement (default 5 minutes)\n * - Environment variable isolation\n * - Working directory restrictions\n */\n\nimport { execaCommand } from 'execa';\nimport type { PluginContextV3 } from '@kb-labs/plugin-contracts';\n\n/**\n * Commands that are always blocked (dangerous)\n */\nconst BLOCKED_COMMANDS = [\n 'rm -rf /',\n 'rm -rf /*',\n 'mkfs',\n 'dd if=',\n ':(){:|:&};:', // Fork bomb\n 'chmod -R 777 /',\n 'chown -R',\n '> /dev/sda',\n 'mv /* ',\n 'fdisk',\n];\n\n/**\n * Split string into chunks of specified size\n */\nfunction chunkString(str: string, chunkSize: number): string[] {\n const chunks: string[] = [];\n for (let i = 0; i < str.length; i += chunkSize) {\n chunks.push(str.slice(i, i + chunkSize));\n }\n return chunks;\n}\n\n/**\n * Shell handler input\n */\nexport interface ShellInput {\n /** Command to execute */\n command: string;\n\n /** Additional environment variables */\n env?: Record<string, string>;\n\n /** Timeout in milliseconds (default: 300000 = 5 min) */\n timeout?: number;\n\n /** Throw on non-zero exit code (default: false) */\n throwOnError?: boolean;\n}\n\n/**\n * Shell handler output\n */\nexport interface ShellOutput {\n /** Standard output */\n stdout: string;\n\n /** Standard error */\n stderr: string;\n\n /** Exit code */\n exitCode: number;\n\n /** Whether command succeeded (exitCode === 0) */\n ok: boolean;\n}\n\n/**\n * Output marker prefix. Shell commands emit structured outputs via:\n * echo '::kb-output::{\"passed\":true}'\n *\n * This separates logs (plain stdout) from structured data (outputs).\n * Similar to GitHub Actions ::set-output:: pattern.\n */\nconst OUTPUT_MARKER = '::kb-output::';\n\n/**\n * Extract structured outputs from shell stdout.\n *\n * Priority:\n * 1. ::kb-output::{...} marker lines — explicit, recommended\n * 2. Entire stdout as JSON — fallback for backward compat (simple commands)\n *\n * Logs and other stdout content are ignored for output purposes.\n */\nfunction mergeJsonOutputs(output: ShellOutput): Record<string, unknown> {\n const base: Record<string, unknown> = { ...output };\n const trimmed = output.stdout.trim();\n if (!trimmed) {return base;}\n\n // Priority 1: Look for ::kb-output:: marker lines\n const lines = output.stdout.split('\\n');\n let foundMarker = false;\n for (const line of lines) {\n const idx = line.indexOf(OUTPUT_MARKER);\n if (idx !== -1) {\n foundMarker = true;\n try {\n const parsed = JSON.parse(line.slice(idx + OUTPUT_MARKER.length));\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n Object.assign(base, parsed);\n }\n } catch {\n // Malformed marker — skip\n }\n }\n }\n\n if (foundMarker) {return base;}\n\n // Priority 2: Fallback — entire stdout as JSON (backward compat)\n try {\n const parsed = JSON.parse(trimmed);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n Object.assign(base, parsed);\n }\n } catch {\n // Not JSON — return as-is\n }\n\n return base;\n}\n\n/**\n * Built-in shell execution handler.\n *\n * Executes shell commands with safety checks and timeout enforcement.\n *\n * @param ctx - Handler execution context\n * @param input - Shell command input\n * @returns Shell execution result\n * @throws Error if dangerous command detected or timeout exceeded\n */\nasync function shellHandler(\n ctx: PluginContextV3,\n input: ShellInput,\n): Promise<Record<string, unknown>> {\n const { command, env = {}, timeout = 300000, throwOnError = false } = input;\n\n // Security: Check for dangerous commands\n const normalizedCommand = command.toLowerCase().trim();\n for (const blocked of BLOCKED_COMMANDS) {\n if (normalizedCommand.includes(blocked.toLowerCase())) {\n throw new Error(\n `Dangerous command blocked: \"${blocked}\". Command attempted: ${command.slice(0, 100)}`,\n );\n }\n }\n\n // Get working directory from context (workflow workspace)\n const cwd = ctx.cwd;\n\n // Merge environment variables\n const mergedEnv = {\n ...process.env,\n ...env,\n };\n\n ctx.platform.logger.info('Executing shell command', {\n command: command.slice(0, 200),\n cwd,\n timeout,\n });\n\n try {\n const proc = execaCommand(command, {\n cwd,\n env: mergedEnv,\n shell: true,\n stdio: 'pipe',\n timeout,\n reject: false, // We handle exit codes ourselves\n });\n\n // Stream stdout/stderr line-by-line in real-time\n let lineNo = 0;\n let stdoutBuf = '';\n let stderrBuf = '';\n\n const emitLine = (stream: 'stdout' | 'stderr', line: string) => {\n lineNo++;\n void ctx.api.events.emit('log.line', { stream, line, lineNo, level: stream === 'stderr' ? 'error' : 'info' });\n };\n\n proc.stdout?.on('data', (chunk: Buffer) => {\n stdoutBuf += chunk.toString();\n const lines = stdoutBuf.split('\\n');\n stdoutBuf = lines.pop() ?? '';\n for (const line of lines) {emitLine('stdout', line);}\n });\n\n proc.stderr?.on('data', (chunk: Buffer) => {\n stderrBuf += chunk.toString();\n const lines = stderrBuf.split('\\n');\n stderrBuf = lines.pop() ?? '';\n for (const line of lines) {emitLine('stderr', line);}\n });\n\n const result = await proc;\n\n // Flush remaining buffered content\n if (stdoutBuf) {emitLine('stdout', stdoutBuf);}\n if (stderrBuf) {emitLine('stderr', stderrBuf);}\n\n const output: ShellOutput = {\n stdout: result.stdout,\n stderr: result.stderr,\n exitCode: result.exitCode ?? 0,\n ok: (result.exitCode ?? 0) === 0,\n };\n\n if (output.ok) {\n ctx.platform.logger.info('Shell command completed successfully', {\n exitCode: output.exitCode,\n stdoutLines: output.stdout.split('\\n').length,\n });\n } else {\n ctx.platform.logger.warn('Shell command failed', {\n exitCode: output.exitCode,\n stderrLines: output.stderr.split('\\n').length,\n });\n\n if (throwOnError) {\n throw new Error(`Shell command failed with exit code ${output.exitCode}: ${output.stderr.slice(0, 500)}`);\n }\n }\n\n return mergeJsonOutputs(output);\n } catch (error) {\n // Handle timeout\n if (error && typeof error === 'object' && 'timedOut' in error && error.timedOut) {\n throw new Error(`Shell command timed out after ${timeout}ms`);\n }\n\n // Handle execution error\n if (error && typeof error === 'object' && 'exitCode' in error) {\n const execError = error as { exitCode?: number; stdout?: string; stderr?: string };\n const output: ShellOutput = {\n stdout: execError.stdout ?? '',\n stderr: execError.stderr ?? '',\n exitCode: execError.exitCode ?? 1,\n ok: false,\n };\n\n ctx.platform.logger.error('Shell command execution failed', undefined, {\n exitCode: output.exitCode,\n stderr: output.stderr.slice(0, 500),\n });\n\n if (!throwOnError) {\n return { ...output };\n }\n }\n\n throw error;\n }\n}\n\n// Export handler in format expected by ExecutionBackend\nexport default {\n execute: shellHandler,\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kb-labs/workflow-builtins",
3
- "version": "1.6.0",
3
+ "version": "2.6.0",
4
4
  "description": "Built-in workflow handlers (shell, etc.)",
5
5
  "files": [
6
6
  "dist"
@@ -30,7 +30,7 @@
30
30
  "test:watch": "vitest"
31
31
  },
32
32
  "dependencies": {
33
- "@kb-labs/plugin-contracts": "^1.4.0",
33
+ "@kb-labs/plugin-contracts": "^2.6.0",
34
34
  "execa": "^8.0.0"
35
35
  },
36
36
  "devDependencies": {
@@ -38,7 +38,7 @@
38
38
  "rimraf": "^6.0.1",
39
39
  "tsup": "^8.5.0",
40
40
  "typescript": "^5.6.3",
41
- "@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
41
+ "@kb-labs/devkit": "workspace:*",
42
42
  "vitest": "^3.2.4"
43
43
  },
44
44
  "engines": {