@codeany/open-agent-sdk 0.2.1 → 0.2.3

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 (57) hide show
  1. package/dist/engine.d.ts.map +1 -1
  2. package/dist/engine.js +2 -0
  3. package/dist/engine.js.map +1 -1
  4. package/package.json +5 -6
  5. package/src/agent.ts +0 -516
  6. package/src/engine.ts +0 -630
  7. package/src/hooks.ts +0 -261
  8. package/src/index.ts +0 -426
  9. package/src/mcp/client.ts +0 -150
  10. package/src/providers/anthropic.ts +0 -60
  11. package/src/providers/index.ts +0 -34
  12. package/src/providers/openai.ts +0 -315
  13. package/src/providers/types.ts +0 -85
  14. package/src/sdk-mcp-server.ts +0 -78
  15. package/src/session.ts +0 -227
  16. package/src/skills/bundled/commit.ts +0 -38
  17. package/src/skills/bundled/debug.ts +0 -48
  18. package/src/skills/bundled/index.ts +0 -28
  19. package/src/skills/bundled/review.ts +0 -41
  20. package/src/skills/bundled/simplify.ts +0 -51
  21. package/src/skills/bundled/test.ts +0 -43
  22. package/src/skills/index.ts +0 -25
  23. package/src/skills/registry.ts +0 -133
  24. package/src/skills/types.ts +0 -99
  25. package/src/tool-helper.ts +0 -127
  26. package/src/tools/agent-tool.ts +0 -164
  27. package/src/tools/ask-user.ts +0 -79
  28. package/src/tools/bash.ts +0 -75
  29. package/src/tools/config-tool.ts +0 -89
  30. package/src/tools/cron-tools.ts +0 -153
  31. package/src/tools/edit.ts +0 -74
  32. package/src/tools/glob.ts +0 -77
  33. package/src/tools/grep.ts +0 -168
  34. package/src/tools/index.ts +0 -240
  35. package/src/tools/lsp-tool.ts +0 -163
  36. package/src/tools/mcp-resource-tools.ts +0 -125
  37. package/src/tools/notebook-edit.ts +0 -93
  38. package/src/tools/plan-tools.ts +0 -88
  39. package/src/tools/read.ts +0 -73
  40. package/src/tools/send-message.ts +0 -96
  41. package/src/tools/skill-tool.ts +0 -133
  42. package/src/tools/task-tools.ts +0 -290
  43. package/src/tools/team-tools.ts +0 -128
  44. package/src/tools/todo-tool.ts +0 -112
  45. package/src/tools/tool-search.ts +0 -87
  46. package/src/tools/types.ts +0 -66
  47. package/src/tools/web-fetch.ts +0 -66
  48. package/src/tools/web-search.ts +0 -86
  49. package/src/tools/worktree-tools.ts +0 -140
  50. package/src/tools/write.ts +0 -42
  51. package/src/types.ts +0 -486
  52. package/src/utils/compact.ts +0 -207
  53. package/src/utils/context.ts +0 -191
  54. package/src/utils/fileCache.ts +0 -148
  55. package/src/utils/messages.ts +0 -195
  56. package/src/utils/retry.ts +0 -140
  57. package/src/utils/tokens.ts +0 -145
@@ -1,133 +0,0 @@
1
- /**
2
- * Skill Registry
3
- *
4
- * Central registry for managing skill definitions.
5
- * Skills can be registered programmatically or loaded from bundled definitions.
6
- */
7
-
8
- import type { SkillDefinition } from './types.js'
9
-
10
- /** Internal skill store */
11
- const skills: Map<string, SkillDefinition> = new Map()
12
-
13
- /** Alias -> skill name mapping */
14
- const aliases: Map<string, string> = new Map()
15
-
16
- /**
17
- * Register a skill definition.
18
- */
19
- export function registerSkill(definition: SkillDefinition): void {
20
- skills.set(definition.name, definition)
21
-
22
- // Register aliases
23
- if (definition.aliases) {
24
- for (const alias of definition.aliases) {
25
- aliases.set(alias, definition.name)
26
- }
27
- }
28
- }
29
-
30
- /**
31
- * Get a skill by name or alias.
32
- */
33
- export function getSkill(name: string): SkillDefinition | undefined {
34
- // Direct lookup
35
- const direct = skills.get(name)
36
- if (direct) return direct
37
-
38
- // Alias lookup
39
- const resolved = aliases.get(name)
40
- if (resolved) return skills.get(resolved)
41
-
42
- return undefined
43
- }
44
-
45
- /**
46
- * Get all registered skills.
47
- */
48
- export function getAllSkills(): SkillDefinition[] {
49
- return Array.from(skills.values())
50
- }
51
-
52
- /**
53
- * Get all user-invocable skills (for /command listing).
54
- */
55
- export function getUserInvocableSkills(): SkillDefinition[] {
56
- return getAllSkills().filter(
57
- (s) => s.userInvocable !== false && (!s.isEnabled || s.isEnabled()),
58
- )
59
- }
60
-
61
- /**
62
- * Check if a skill exists.
63
- */
64
- export function hasSkill(name: string): boolean {
65
- return skills.has(name) || aliases.has(name)
66
- }
67
-
68
- /**
69
- * Remove a skill.
70
- */
71
- export function unregisterSkill(name: string): boolean {
72
- const skill = skills.get(name)
73
- if (!skill) return false
74
-
75
- // Remove aliases
76
- if (skill.aliases) {
77
- for (const alias of skill.aliases) {
78
- aliases.delete(alias)
79
- }
80
- }
81
-
82
- return skills.delete(name)
83
- }
84
-
85
- /**
86
- * Clear all skills (for testing).
87
- */
88
- export function clearSkills(): void {
89
- skills.clear()
90
- aliases.clear()
91
- }
92
-
93
- /**
94
- * Format skills listing for system prompt injection.
95
- *
96
- * Uses a budget system: skills listing gets a limited character budget
97
- * to avoid bloating the context window.
98
- */
99
- export function formatSkillsForPrompt(
100
- contextWindowTokens?: number,
101
- ): string {
102
- const invocable = getUserInvocableSkills()
103
- if (invocable.length === 0) return ''
104
-
105
- // Budget: 1% of context window in characters (4 chars per token)
106
- const CHARS_PER_TOKEN = 4
107
- const DEFAULT_BUDGET = 8000
108
- const MAX_DESC_CHARS = 250
109
- const budget = contextWindowTokens
110
- ? Math.floor(contextWindowTokens * 0.01 * CHARS_PER_TOKEN)
111
- : DEFAULT_BUDGET
112
-
113
- const lines: string[] = []
114
- let used = 0
115
-
116
- for (const skill of invocable) {
117
- const desc = skill.description.length > MAX_DESC_CHARS
118
- ? skill.description.slice(0, MAX_DESC_CHARS) + '...'
119
- : skill.description
120
-
121
- const trigger = skill.whenToUse
122
- ? ` TRIGGER when: ${skill.whenToUse}`
123
- : ''
124
-
125
- const line = `- ${skill.name}: ${desc}${trigger}`
126
-
127
- if (used + line.length > budget) break
128
- lines.push(line)
129
- used += line.length
130
- }
131
-
132
- return lines.join('\n')
133
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * Skill System Types
3
- *
4
- * Skills are reusable prompt templates that extend agent capabilities.
5
- * They can be invoked by the model via the Skill tool or by users via /skillname.
6
- */
7
-
8
- import type { ToolContext } from '../types.js'
9
- import type { HookConfig } from '../hooks.js'
10
-
11
- /**
12
- * Content block for skill prompts (compatible with Anthropic API).
13
- */
14
- export type SkillContentBlock =
15
- | { type: 'text'; text: string }
16
- | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }
17
-
18
- /**
19
- * Bundled skill definition.
20
- *
21
- * Inspired by Claude Code's skill system. Skills provide specialized
22
- * capabilities by injecting context-specific prompts with optional
23
- * tool restrictions and model overrides.
24
- */
25
- export interface SkillDefinition {
26
- /** Unique skill name (e.g., 'simplify', 'commit') */
27
- name: string
28
-
29
- /** Human-readable description */
30
- description: string
31
-
32
- /** Alternative names for the skill */
33
- aliases?: string[]
34
-
35
- /** When the model should invoke this skill (used in system prompt) */
36
- whenToUse?: string
37
-
38
- /** Hint for expected arguments */
39
- argumentHint?: string
40
-
41
- /** Tools the skill is allowed to use (empty = all tools) */
42
- allowedTools?: string[]
43
-
44
- /** Model override for this skill */
45
- model?: string
46
-
47
- /** Whether the skill can be invoked by users via /command */
48
- userInvocable?: boolean
49
-
50
- /** Runtime check for availability */
51
- isEnabled?: () => boolean
52
-
53
- /** Hook overrides while skill is active */
54
- hooks?: HookConfig
55
-
56
- /** Execution context: 'inline' runs in current context, 'fork' spawns a subagent */
57
- context?: 'inline' | 'fork'
58
-
59
- /** Subagent type for forked execution */
60
- agent?: string
61
-
62
- /**
63
- * Generate the prompt content blocks for this skill.
64
- *
65
- * @param args - User-provided arguments (e.g., from "/simplify focus on error handling")
66
- * @param context - Tool execution context (cwd, etc.)
67
- * @returns Content blocks to inject into the conversation
68
- */
69
- getPrompt: (
70
- args: string,
71
- context: ToolContext,
72
- ) => Promise<SkillContentBlock[]>
73
- }
74
-
75
- /**
76
- * Result of executing a skill.
77
- */
78
- export interface SkillResult {
79
- /** Whether execution succeeded */
80
- success: boolean
81
-
82
- /** Skill name that was executed */
83
- skillName: string
84
-
85
- /** Execution status */
86
- status: 'inline' | 'forked'
87
-
88
- /** Allowed tools override (for inline execution) */
89
- allowedTools?: string[]
90
-
91
- /** Model override */
92
- model?: string
93
-
94
- /** Result text (for forked execution) */
95
- result?: string
96
-
97
- /** Error message */
98
- error?: string
99
- }
@@ -1,127 +0,0 @@
1
- /**
2
- * tool() helper - Create tools using Zod schemas
3
- *
4
- * Compatible with open-agent-sdk's tool() function.
5
- *
6
- * Usage:
7
- * import { tool } from 'open-agent-sdk'
8
- * import { z } from 'zod'
9
- *
10
- * const weatherTool = tool(
11
- * 'get_weather',
12
- * 'Get weather for a city',
13
- * { city: z.string().describe('City name') },
14
- * async ({ city }) => {
15
- * return { content: [{ type: 'text', text: `Weather in ${city}: 22°C` }] }
16
- * }
17
- * )
18
- */
19
-
20
- import { z, type ZodRawShape, type ZodObject } from 'zod'
21
- import { zodToJsonSchema } from 'zod-to-json-schema'
22
- import type { ToolDefinition, ToolResult, ToolContext } from './types.js'
23
-
24
- /**
25
- * Tool annotations (MCP standard).
26
- */
27
- export interface ToolAnnotations {
28
- readOnlyHint?: boolean
29
- destructiveHint?: boolean
30
- idempotentHint?: boolean
31
- openWorldHint?: boolean
32
- }
33
-
34
- /**
35
- * Tool call result (MCP-compatible).
36
- */
37
- export interface CallToolResult {
38
- content: Array<
39
- | { type: 'text'; text: string }
40
- | { type: 'image'; data: string; mimeType: string }
41
- | { type: 'resource'; resource: { uri: string; text?: string; blob?: string } }
42
- >
43
- isError?: boolean
44
- }
45
-
46
- /**
47
- * SDK MCP tool definition.
48
- */
49
- export interface SdkMcpToolDefinition<T extends ZodRawShape = ZodRawShape> {
50
- name: string
51
- description: string
52
- inputSchema: ZodObject<T>
53
- handler: (args: z.infer<ZodObject<T>>, extra: unknown) => Promise<CallToolResult>
54
- annotations?: ToolAnnotations
55
- }
56
-
57
- /**
58
- * Create a tool using Zod schema.
59
- *
60
- * Compatible with open-agent-sdk's tool() function.
61
- */
62
- export function tool<T extends ZodRawShape>(
63
- name: string,
64
- description: string,
65
- inputSchema: T,
66
- handler: (args: z.infer<ZodObject<T>>, extra: unknown) => Promise<CallToolResult>,
67
- extras?: { annotations?: ToolAnnotations },
68
- ): SdkMcpToolDefinition<T> {
69
- return {
70
- name,
71
- description,
72
- inputSchema: z.object(inputSchema),
73
- handler,
74
- annotations: extras?.annotations,
75
- }
76
- }
77
-
78
- /**
79
- * Convert an SdkMcpToolDefinition to a ToolDefinition for the engine.
80
- */
81
- export function sdkToolToToolDefinition(sdkTool: SdkMcpToolDefinition<any>): ToolDefinition {
82
- const jsonSchema = zodToJsonSchema(sdkTool.inputSchema, { target: 'openApi3' }) as any
83
-
84
- return {
85
- name: sdkTool.name,
86
- description: sdkTool.description,
87
- inputSchema: {
88
- type: 'object',
89
- properties: jsonSchema.properties || {},
90
- required: jsonSchema.required || [],
91
- },
92
- isReadOnly: () => sdkTool.annotations?.readOnlyHint ?? false,
93
- isConcurrencySafe: () => sdkTool.annotations?.readOnlyHint ?? false,
94
- isEnabled: () => true,
95
- async prompt() { return sdkTool.description },
96
- async call(input: any, _context: ToolContext): Promise<ToolResult> {
97
- try {
98
- const parsed = sdkTool.inputSchema.parse(input)
99
- const result = await sdkTool.handler(parsed, {})
100
-
101
- // Convert MCP content blocks to string
102
- const text = result.content
103
- .map((block) => {
104
- if (block.type === 'text') return block.text
105
- if (block.type === 'image') return `[Image: ${block.mimeType}]`
106
- if (block.type === 'resource') return block.resource.text || `[Resource: ${block.resource.uri}]`
107
- return JSON.stringify(block)
108
- })
109
- .join('\n')
110
-
111
- return {
112
- type: 'tool_result',
113
- tool_use_id: '',
114
- content: text,
115
- is_error: result.isError || false,
116
- }
117
- } catch (err: any) {
118
- return {
119
- type: 'tool_result',
120
- tool_use_id: '',
121
- content: `Error: ${err.message}`,
122
- is_error: true,
123
- }
124
- }
125
- },
126
- }
127
- }
@@ -1,164 +0,0 @@
1
- /**
2
- * AgentTool - Spawn subagents for parallel/delegated work
3
- *
4
- * Supports built-in agents (Explore, Plan) and custom agent definitions.
5
- * Agents run as nested query loops with their own context and tool sets.
6
- */
7
-
8
- import type { ToolDefinition, ToolContext, ToolResult, AgentDefinition } from '../types.js'
9
- import { QueryEngine } from '../engine.js'
10
- import { getAllBaseTools, filterTools } from './index.js'
11
- import { createProvider, type ApiType } from '../providers/index.js'
12
-
13
- // Store for registered agent definitions
14
- let registeredAgents: Record<string, AgentDefinition> = {}
15
-
16
- /**
17
- * Register agent definitions for the AgentTool to use.
18
- */
19
- export function registerAgents(agents: Record<string, AgentDefinition>): void {
20
- registeredAgents = { ...registeredAgents, ...agents }
21
- }
22
-
23
- /**
24
- * Clear registered agents.
25
- */
26
- export function clearAgents(): void {
27
- registeredAgents = {}
28
- }
29
-
30
- /**
31
- * Built-in agent definitions.
32
- */
33
- const BUILTIN_AGENTS: Record<string, AgentDefinition> = {
34
- Explore: {
35
- description: 'Fast agent for exploring codebases. Use for finding files, searching code, and answering questions about the codebase.',
36
- prompt: 'You are a codebase exploration agent. Search through files and code to answer questions. Be thorough but efficient. Use Glob to find files, Grep to search content, and Read to examine files.',
37
- tools: ['Read', 'Glob', 'Grep', 'Bash'],
38
- },
39
- Plan: {
40
- description: 'Software architect agent for designing implementation plans. Returns step-by-step plans and identifies critical files.',
41
- prompt: 'You are a software architect. Design implementation plans for the given task. Identify critical files, consider trade-offs, and provide step-by-step plans. Use search tools to understand the codebase before planning.',
42
- tools: ['Read', 'Glob', 'Grep', 'Bash'],
43
- },
44
- }
45
-
46
- export const AgentTool: ToolDefinition = {
47
- name: 'Agent',
48
- description: 'Launch a subagent to handle complex, multi-step tasks autonomously. Subagents have their own context and can run specialized tool sets.',
49
- inputSchema: {
50
- type: 'object',
51
- properties: {
52
- prompt: {
53
- type: 'string',
54
- description: 'The task for the agent to perform',
55
- },
56
- description: {
57
- type: 'string',
58
- description: 'A short (3-5 word) description of the task',
59
- },
60
- subagent_type: {
61
- type: 'string',
62
- description: 'The type of agent to use (e.g., "Explore", "Plan", or a custom agent name)',
63
- },
64
- model: {
65
- type: 'string',
66
- description: 'Optional model override for this agent',
67
- },
68
- name: {
69
- type: 'string',
70
- description: 'Name for the spawned agent',
71
- },
72
- run_in_background: {
73
- type: 'boolean',
74
- description: 'Whether to run in background',
75
- },
76
- },
77
- required: ['prompt', 'description'],
78
- },
79
- isReadOnly: () => false,
80
- isConcurrencySafe: () => false,
81
- isEnabled: () => true,
82
- async prompt() {
83
- return 'Launch a subagent to handle complex tasks autonomously.'
84
- },
85
- async call(input: any, context: ToolContext): Promise<ToolResult> {
86
- const agentType = input.subagent_type || 'general-purpose'
87
-
88
- // Find agent definition
89
- const agentDef = registeredAgents[agentType] || BUILTIN_AGENTS[agentType]
90
-
91
- // Determine tools for subagent
92
- let tools = getAllBaseTools()
93
- if (agentDef?.tools) {
94
- tools = filterTools(tools, agentDef.tools)
95
- }
96
-
97
- // Remove AgentTool from subagent to prevent infinite recursion
98
- tools = tools.filter(t => t.name !== 'Agent')
99
-
100
- // Build system prompt
101
- const systemPrompt = agentDef?.prompt ||
102
- 'You are a helpful assistant. Complete the given task using the available tools.'
103
-
104
- // Inherit provider and model from parent agent context, fall back to env vars
105
- const subModel = input.model || context.model || process.env.CODEANY_MODEL || 'claude-sonnet-4-6'
106
- const provider = context.provider ?? createProvider(
107
- (context.apiType || process.env.CODEANY_API_TYPE as ApiType) || 'anthropic-messages',
108
- {
109
- apiKey: process.env.CODEANY_API_KEY,
110
- baseURL: process.env.CODEANY_BASE_URL,
111
- },
112
- )
113
-
114
- // Create subagent engine
115
- const engine = new QueryEngine({
116
- cwd: context.cwd,
117
- model: subModel,
118
- provider,
119
- tools,
120
- systemPrompt,
121
- maxTurns: agentDef?.maxTurns || 10,
122
- maxTokens: 16384,
123
- canUseTool: async () => ({ behavior: 'allow' }),
124
- includePartialMessages: false,
125
- })
126
-
127
- // Run the subagent
128
- let resultText = ''
129
- let toolCalls: string[] = []
130
-
131
- try {
132
- for await (const event of engine.submitMessage(input.prompt)) {
133
- if (event.type === 'assistant') {
134
- for (const block of event.message.content) {
135
- if ('text' in block && block.text) {
136
- resultText = block.text
137
- }
138
- if ('name' in block) {
139
- toolCalls.push(block.name as string)
140
- }
141
- }
142
- }
143
- }
144
- } catch (err: any) {
145
- return {
146
- type: 'tool_result',
147
- tool_use_id: '',
148
- content: `Subagent error: ${err.message}`,
149
- is_error: true,
150
- }
151
- }
152
-
153
- const output = resultText || '(Subagent completed with no text output)'
154
- const toolSummary = toolCalls.length > 0
155
- ? `\n[Tools used: ${toolCalls.join(', ')}]`
156
- : ''
157
-
158
- return {
159
- type: 'tool_result',
160
- tool_use_id: '',
161
- content: output + toolSummary,
162
- }
163
- },
164
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * AskUserQuestionTool - Interactive user questions
3
- *
4
- * In SDK mode, returns a permission_request event and waits
5
- * for the consumer to provide an answer.
6
- * In non-interactive mode, returns a default or denies.
7
- */
8
-
9
- import type { ToolDefinition, ToolResult } from '../types.js'
10
-
11
- // Callback for handling user questions (set by the agent)
12
- let questionHandler: ((question: string, options?: string[]) => Promise<string>) | null = null
13
-
14
- /**
15
- * Set the question handler for AskUserQuestion.
16
- */
17
- export function setQuestionHandler(
18
- handler: (question: string, options?: string[]) => Promise<string>,
19
- ): void {
20
- questionHandler = handler
21
- }
22
-
23
- /**
24
- * Clear the question handler.
25
- */
26
- export function clearQuestionHandler(): void {
27
- questionHandler = null
28
- }
29
-
30
- export const AskUserQuestionTool: ToolDefinition = {
31
- name: 'AskUserQuestion',
32
- description: 'Ask the user a question and wait for their response. Use when you need clarification or input from the user.',
33
- inputSchema: {
34
- type: 'object',
35
- properties: {
36
- question: { type: 'string', description: 'The question to ask the user' },
37
- options: {
38
- type: 'array',
39
- items: { type: 'string' },
40
- description: 'Optional list of choices for the user',
41
- },
42
- allow_multiselect: {
43
- type: 'boolean',
44
- description: 'Whether to allow multiple selections (for options)',
45
- },
46
- },
47
- required: ['question'],
48
- },
49
- isReadOnly: () => true,
50
- isConcurrencySafe: () => false,
51
- isEnabled: () => true,
52
- async prompt() { return 'Ask the user a question.' },
53
- async call(input: any): Promise<ToolResult> {
54
- if (questionHandler) {
55
- try {
56
- const answer = await questionHandler(input.question, input.options)
57
- return {
58
- type: 'tool_result',
59
- tool_use_id: '',
60
- content: answer,
61
- }
62
- } catch (err: any) {
63
- return {
64
- type: 'tool_result',
65
- tool_use_id: '',
66
- content: `User declined to answer: ${err.message}`,
67
- is_error: true,
68
- }
69
- }
70
- }
71
-
72
- // Non-interactive: return informative message
73
- return {
74
- type: 'tool_result',
75
- tool_use_id: '',
76
- content: `[Non-interactive mode] Question: ${input.question}${input.options ? `\nOptions: ${input.options.join(', ')}` : ''}\n\nNo user available to answer. Proceeding with best judgment.`,
77
- }
78
- },
79
- }
package/src/tools/bash.ts DELETED
@@ -1,75 +0,0 @@
1
- /**
2
- * BashTool - Execute shell commands
3
- */
4
-
5
- import { spawn } from 'child_process'
6
- import { defineTool } from './types.js'
7
-
8
- export const BashTool = defineTool({
9
- name: 'Bash',
10
- description: 'Execute a bash command and return its output. Use for running shell commands, scripts, and system operations.',
11
- inputSchema: {
12
- type: 'object',
13
- properties: {
14
- command: {
15
- type: 'string',
16
- description: 'The bash command to execute',
17
- },
18
- timeout: {
19
- type: 'number',
20
- description: 'Optional timeout in milliseconds (max 600000, default 120000)',
21
- },
22
- },
23
- required: ['command'],
24
- },
25
- isReadOnly: false,
26
- isConcurrencySafe: false,
27
- async call(input, context) {
28
- const { command, timeout: userTimeout } = input
29
- const timeoutMs = Math.min(userTimeout || 120000, 600000)
30
-
31
- return new Promise<string>((resolve) => {
32
- const chunks: Buffer[] = []
33
- const errChunks: Buffer[] = []
34
-
35
- const proc = spawn('bash', ['-c', command], {
36
- cwd: context.cwd,
37
- env: { ...process.env },
38
- timeout: timeoutMs,
39
- stdio: ['pipe', 'pipe', 'pipe'],
40
- })
41
-
42
- proc.stdout?.on('data', (data: Buffer) => chunks.push(data))
43
- proc.stderr?.on('data', (data: Buffer) => errChunks.push(data))
44
-
45
- if (context.abortSignal) {
46
- context.abortSignal.addEventListener('abort', () => {
47
- proc.kill('SIGTERM')
48
- }, { once: true })
49
- }
50
-
51
- proc.on('close', (code) => {
52
- const stdout = Buffer.concat(chunks).toString('utf-8')
53
- const stderr = Buffer.concat(errChunks).toString('utf-8')
54
-
55
- let output = ''
56
- if (stdout) output += stdout
57
- if (stderr) output += (output ? '\n' : '') + stderr
58
- if (code !== 0 && code !== null) {
59
- output += `\nExit code: ${code}`
60
- }
61
-
62
- // Truncate very large outputs
63
- if (output.length > 100000) {
64
- output = output.slice(0, 50000) + '\n...(truncated)...\n' + output.slice(-50000)
65
- }
66
-
67
- resolve(output || '(no output)')
68
- })
69
-
70
- proc.on('error', (err) => {
71
- resolve(`Error executing command: ${err.message}`)
72
- })
73
- })
74
- },
75
- })