prjct-cli 0.5.1 → 0.7.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/CHANGELOG.md +220 -7
- package/CLAUDE.md +476 -55
- package/README.md +48 -55
- package/bin/prjct +170 -225
- package/core/agentic/command-executor.js +113 -0
- package/core/agentic/context-builder.js +85 -0
- package/core/agentic/prompt-builder.js +86 -0
- package/core/agentic/template-loader.js +104 -0
- package/core/agentic/tool-registry.js +117 -0
- package/core/command-registry.js +597 -0
- package/core/commands.js +2046 -2028
- package/core/domain/agent-generator.js +118 -0
- package/core/domain/analyzer.js +211 -0
- package/core/domain/architect-session.js +300 -0
- package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
- package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
- package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
- package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
- package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
- package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
- package/core/{migrator.js → infrastructure/migrator.js} +34 -19
- package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
- package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
- package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
- package/core/{animations-simple.js → utils/animations.js} +3 -23
- package/core/utils/date-helper.js +238 -0
- package/core/utils/file-helper.js +327 -0
- package/core/utils/jsonl-helper.js +206 -0
- package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
- package/core/utils/session-helper.js +277 -0
- package/core/{version.js → utils/version.js} +1 -1
- package/package.json +5 -12
- package/templates/agents/AGENTS.md +151 -99
- package/templates/analysis/analyze.md +84 -0
- package/templates/commands/analyze.md +37 -233
- package/templates/commands/bug.md +79 -0
- package/templates/commands/build.md +44 -0
- package/templates/commands/cleanup.md +24 -84
- package/templates/commands/design.md +20 -95
- package/templates/commands/done.md +17 -180
- package/templates/commands/feature.md +113 -0
- package/templates/commands/fix.md +58 -66
- package/templates/commands/git.md +35 -57
- package/templates/commands/help.md +18 -52
- package/templates/commands/idea.md +18 -34
- package/templates/commands/init.md +65 -257
- package/templates/commands/next.md +20 -60
- package/templates/commands/now.md +21 -23
- package/templates/commands/progress.md +40 -73
- package/templates/commands/recap.md +52 -75
- package/templates/commands/roadmap.md +30 -85
- package/templates/commands/ship.md +93 -126
- package/templates/commands/status.md +42 -0
- package/templates/commands/sync.md +19 -205
- package/templates/commands/task.md +19 -79
- package/templates/commands/test.md +25 -71
- package/templates/commands/workflow.md +20 -210
- package/core/agent-generator.js +0 -516
- package/core/analyzer.js +0 -600
- package/core/animations.js +0 -277
- package/core/git-integration.js +0 -401
- package/core/workflow-engine.js +0 -213
- package/core/workflow-prompts.js +0 -192
- package/core/workflow-rules.js +0 -147
- package/scripts/post-install.js +0 -121
- package/scripts/preuninstall.js +0 -94
- package/scripts/verify-installation.sh +0 -158
- package/templates/agents/be.template.md +0 -42
- package/templates/agents/data.template.md +0 -41
- package/templates/agents/devops.template.md +0 -41
- package/templates/agents/fe.template.md +0 -42
- package/templates/agents/mobile.template.md +0 -41
- package/templates/agents/pm.template.md +0 -84
- package/templates/agents/qa.template.md +0 -54
- package/templates/agents/scribe.template.md +0 -95
- package/templates/agents/security.template.md +0 -41
- package/templates/agents/ux.template.md +0 -49
- package/templates/commands/context.md +0 -105
- package/templates/commands/stuck.md +0 -48
- package/templates/examples/natural-language-examples.md +0 -532
- /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Executor
|
|
3
|
+
* Executes commands using templates - Claude decides everything
|
|
4
|
+
* ZERO if/else business logic
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const templateLoader = require('./template-loader')
|
|
8
|
+
const contextBuilder = require('./context-builder')
|
|
9
|
+
const promptBuilder = require('./prompt-builder')
|
|
10
|
+
const toolRegistry = require('./tool-registry')
|
|
11
|
+
|
|
12
|
+
class CommandExecutor {
|
|
13
|
+
/**
|
|
14
|
+
* Execute a command agentically
|
|
15
|
+
* @param {string} commandName - Command name (e.g., 'now', 'done')
|
|
16
|
+
* @param {Object} params - Command parameters
|
|
17
|
+
* @param {string} projectPath - Project path
|
|
18
|
+
* @returns {Promise<Object>} Execution result
|
|
19
|
+
*/
|
|
20
|
+
async execute(commandName, params, projectPath) {
|
|
21
|
+
try {
|
|
22
|
+
// 1. Load template
|
|
23
|
+
const template = await templateLoader.load(commandName)
|
|
24
|
+
|
|
25
|
+
// 2. Build context
|
|
26
|
+
const context = await contextBuilder.build(projectPath, params)
|
|
27
|
+
|
|
28
|
+
// 3. Load current state
|
|
29
|
+
const state = await contextBuilder.loadState(context)
|
|
30
|
+
|
|
31
|
+
// 4. Build prompt for Claude
|
|
32
|
+
const prompt = promptBuilder.build(template, context, state)
|
|
33
|
+
|
|
34
|
+
// 5. Execute (in real implementation, this would call Claude)
|
|
35
|
+
// For now, we return structured data that Claude can work with
|
|
36
|
+
return {
|
|
37
|
+
success: true,
|
|
38
|
+
template,
|
|
39
|
+
context,
|
|
40
|
+
state,
|
|
41
|
+
prompt,
|
|
42
|
+
// In production: result from Claude's execution
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: error.message,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Execute tool with permission check
|
|
54
|
+
* @param {string} toolName - Tool name
|
|
55
|
+
* @param {Array} args - Tool arguments
|
|
56
|
+
* @param {string[]} allowedTools - Allowed tools for this command
|
|
57
|
+
* @returns {Promise<any>}
|
|
58
|
+
*/
|
|
59
|
+
async executeTool(toolName, args, allowedTools) {
|
|
60
|
+
// Check if tool is allowed
|
|
61
|
+
if (!toolRegistry.isAllowed(toolName, allowedTools)) {
|
|
62
|
+
throw new Error(`Tool ${toolName} not allowed for this command`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get tool function
|
|
66
|
+
const tool = toolRegistry.get(toolName)
|
|
67
|
+
|
|
68
|
+
// Execute tool
|
|
69
|
+
return await tool(...args)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Simple execution for direct tool access
|
|
74
|
+
* Used by legacy commands during migration
|
|
75
|
+
* @param {string} commandName - Command name
|
|
76
|
+
* @param {Function} executionFn - Function that uses tools
|
|
77
|
+
* @param {string} projectPath - Project path
|
|
78
|
+
* @returns {Promise<Object>}
|
|
79
|
+
*/
|
|
80
|
+
async executeSimple(commandName, executionFn, projectPath) {
|
|
81
|
+
try {
|
|
82
|
+
// Load template to get allowed tools
|
|
83
|
+
const template = await templateLoader.load(commandName)
|
|
84
|
+
const allowedTools = template.frontmatter['allowed-tools'] || []
|
|
85
|
+
|
|
86
|
+
// Build context
|
|
87
|
+
const context = await contextBuilder.build(projectPath)
|
|
88
|
+
|
|
89
|
+
// Create tools proxy that checks permissions
|
|
90
|
+
const tools = {
|
|
91
|
+
read: async (filePath) => this.executeTool('Read', [filePath], allowedTools),
|
|
92
|
+
write: async (filePath, content) =>
|
|
93
|
+
this.executeTool('Write', [filePath, content], allowedTools),
|
|
94
|
+
bash: async (command) => this.executeTool('Bash', [command], allowedTools),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Execute user function with tools
|
|
98
|
+
const result = await executionFn(tools, context)
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
result,
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: error.message,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = new CommandExecutor()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Builder
|
|
3
|
+
* Builds project context for Claude to make decisions
|
|
4
|
+
* NO if/else logic - just data collection
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs').promises
|
|
8
|
+
const pathManager = require('../infrastructure/path-manager')
|
|
9
|
+
const configManager = require('../infrastructure/config-manager')
|
|
10
|
+
|
|
11
|
+
class ContextBuilder {
|
|
12
|
+
/**
|
|
13
|
+
* Build full project context for Claude
|
|
14
|
+
* @param {string} projectPath - Local project path
|
|
15
|
+
* @param {Object} commandParams - Command-specific parameters
|
|
16
|
+
* @returns {Promise<Object>} Context object
|
|
17
|
+
*/
|
|
18
|
+
async build(projectPath, commandParams = {}) {
|
|
19
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
20
|
+
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
// Project identification
|
|
24
|
+
projectId,
|
|
25
|
+
projectPath,
|
|
26
|
+
globalPath,
|
|
27
|
+
|
|
28
|
+
// File paths
|
|
29
|
+
paths: {
|
|
30
|
+
now: pathManager.getFilePath(projectId, 'core', 'now.md'),
|
|
31
|
+
next: pathManager.getFilePath(projectId, 'core', 'next.md'),
|
|
32
|
+
context: pathManager.getFilePath(projectId, 'core', 'context.md'),
|
|
33
|
+
shipped: pathManager.getFilePath(projectId, 'progress', 'shipped.md'),
|
|
34
|
+
metrics: pathManager.getFilePath(projectId, 'progress', 'metrics.md'),
|
|
35
|
+
ideas: pathManager.getFilePath(projectId, 'planning', 'ideas.md'),
|
|
36
|
+
roadmap: pathManager.getFilePath(projectId, 'planning', 'roadmap.md'),
|
|
37
|
+
memory: pathManager.getFilePath(projectId, 'memory', 'context.jsonl'),
|
|
38
|
+
analysis: pathManager.getFilePath(projectId, 'analysis', 'repo-summary.md'),
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Command parameters
|
|
42
|
+
params: commandParams,
|
|
43
|
+
|
|
44
|
+
// Timestamps
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
date: new Date().toLocaleString(),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load current project state
|
|
52
|
+
* @param {Object} context - Context from build()
|
|
53
|
+
* @returns {Promise<Object>} Current state
|
|
54
|
+
*/
|
|
55
|
+
async loadState(context) {
|
|
56
|
+
const state = {}
|
|
57
|
+
|
|
58
|
+
// Read all core files
|
|
59
|
+
for (const [key, filePath] of Object.entries(context.paths)) {
|
|
60
|
+
try {
|
|
61
|
+
state[key] = await fs.readFile(filePath, 'utf-8')
|
|
62
|
+
} catch {
|
|
63
|
+
state[key] = null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return state
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check file existence
|
|
72
|
+
* @param {string} filePath - File path
|
|
73
|
+
* @returns {Promise<boolean>}
|
|
74
|
+
*/
|
|
75
|
+
async fileExists(filePath) {
|
|
76
|
+
try {
|
|
77
|
+
await fs.access(filePath)
|
|
78
|
+
return true
|
|
79
|
+
} catch {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = new ContextBuilder()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Builder
|
|
3
|
+
* Builds prompts for Claude based on templates and context
|
|
4
|
+
* Claude decides what to do - NO if/else logic here
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class PromptBuilder {
|
|
8
|
+
/**
|
|
9
|
+
* Build execution prompt for Claude
|
|
10
|
+
* @param {Object} template - Template from template-loader
|
|
11
|
+
* @param {Object} context - Context from context-builder
|
|
12
|
+
* @param {Object} state - Current state from context-builder
|
|
13
|
+
* @returns {string} Prompt for Claude
|
|
14
|
+
*/
|
|
15
|
+
build(template, context, state) {
|
|
16
|
+
const parts = []
|
|
17
|
+
|
|
18
|
+
// 1. Command instructions from template
|
|
19
|
+
parts.push('# Command Instructions\n')
|
|
20
|
+
parts.push(template.content)
|
|
21
|
+
parts.push('\n')
|
|
22
|
+
|
|
23
|
+
// 2. Allowed tools
|
|
24
|
+
if (template.frontmatter['allowed-tools']) {
|
|
25
|
+
parts.push('## Allowed Tools\n')
|
|
26
|
+
parts.push(`You can use: ${template.frontmatter['allowed-tools'].join(', ')}\n\n`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 3. Project context
|
|
30
|
+
parts.push('## Project Context\n')
|
|
31
|
+
parts.push(`- Project ID: ${context.projectId}\n`)
|
|
32
|
+
parts.push(`- Timestamp: ${context.timestamp}\n`)
|
|
33
|
+
parts.push('\n')
|
|
34
|
+
|
|
35
|
+
// 4. Current state (only non-null files)
|
|
36
|
+
parts.push('## Current State\n')
|
|
37
|
+
for (const [key, content] of Object.entries(state)) {
|
|
38
|
+
if (content && content.trim()) {
|
|
39
|
+
parts.push(`### ${key}\n`)
|
|
40
|
+
parts.push('```\n')
|
|
41
|
+
parts.push(content)
|
|
42
|
+
parts.push('\n```\n\n')
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 5. Command parameters
|
|
47
|
+
if (Object.keys(context.params).length > 0) {
|
|
48
|
+
parts.push('## Parameters\n')
|
|
49
|
+
for (const [key, value] of Object.entries(context.params)) {
|
|
50
|
+
parts.push(`- ${key}: ${value}\n`)
|
|
51
|
+
}
|
|
52
|
+
parts.push('\n')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 6. Final instruction
|
|
56
|
+
parts.push('## Execute\n')
|
|
57
|
+
parts.push('Based on the instructions above, execute the command.\n')
|
|
58
|
+
parts.push('Use ONLY the allowed tools.\n')
|
|
59
|
+
parts.push('Make decisions based on context - do not follow rigid if/else rules.\n')
|
|
60
|
+
|
|
61
|
+
return parts.join('')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build analysis prompt
|
|
66
|
+
* Used for tasks that need Claude to analyze before acting
|
|
67
|
+
* @param {string} analysisType - Type of analysis
|
|
68
|
+
* @param {Object} context - Context
|
|
69
|
+
* @returns {string} Analysis prompt
|
|
70
|
+
*/
|
|
71
|
+
buildAnalysis(analysisType, context) {
|
|
72
|
+
const parts = []
|
|
73
|
+
|
|
74
|
+
parts.push(`# Analyze: ${analysisType}\n\n`)
|
|
75
|
+
parts.push('Read the project context and provide your analysis.\n')
|
|
76
|
+
parts.push('No predetermined patterns - decide based on what you find.\n\n')
|
|
77
|
+
|
|
78
|
+
parts.push('## Project Context\n')
|
|
79
|
+
parts.push(`- Path: ${context.projectPath}\n`)
|
|
80
|
+
parts.push(`- ID: ${context.projectId}\n\n`)
|
|
81
|
+
|
|
82
|
+
return parts.join('')
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = new PromptBuilder()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Loader
|
|
3
|
+
* Loads command templates with frontmatter parsing
|
|
4
|
+
* Templates define what Claude should do - NO if/else logic
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs').promises
|
|
8
|
+
const path = require('path')
|
|
9
|
+
|
|
10
|
+
class TemplateLoader {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.templatesDir = path.join(__dirname, '..', '..', 'templates', 'commands')
|
|
13
|
+
this.cache = new Map()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load template with frontmatter
|
|
18
|
+
* @param {string} commandName - Command name (e.g., 'now', 'done', 'ship')
|
|
19
|
+
* @returns {Promise<{frontmatter: Object, content: string}>}
|
|
20
|
+
*/
|
|
21
|
+
async load(commandName) {
|
|
22
|
+
// Check cache first
|
|
23
|
+
if (this.cache.has(commandName)) {
|
|
24
|
+
return this.cache.get(commandName)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const templatePath = path.join(this.templatesDir, `${commandName}.md`)
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const rawContent = await fs.readFile(templatePath, 'utf-8')
|
|
31
|
+
const parsed = this.parseFrontmatter(rawContent)
|
|
32
|
+
|
|
33
|
+
// Cache result
|
|
34
|
+
this.cache.set(commandName, parsed)
|
|
35
|
+
|
|
36
|
+
return parsed
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`Template not found: ${commandName}.md`)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse frontmatter from markdown
|
|
44
|
+
* @param {string} content - Raw markdown content
|
|
45
|
+
* @returns {Object} Parsed template with frontmatter and content
|
|
46
|
+
*/
|
|
47
|
+
parseFrontmatter(content) {
|
|
48
|
+
const frontmatterRegex = /^---\n([\s\S]+?)\n---\n([\s\S]*)$/
|
|
49
|
+
const match = content.match(frontmatterRegex)
|
|
50
|
+
|
|
51
|
+
if (!match) {
|
|
52
|
+
return {
|
|
53
|
+
frontmatter: {},
|
|
54
|
+
content: content.trim(),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const [, frontmatterText, mainContent] = match
|
|
59
|
+
const frontmatter = {}
|
|
60
|
+
|
|
61
|
+
// Parse frontmatter lines
|
|
62
|
+
frontmatterText.split('\n').forEach((line) => {
|
|
63
|
+
const [key, ...valueParts] = line.split(':')
|
|
64
|
+
if (key && valueParts.length > 0) {
|
|
65
|
+
const value = valueParts.join(':').trim()
|
|
66
|
+
|
|
67
|
+
// Parse arrays
|
|
68
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
69
|
+
frontmatter[key.trim()] = value
|
|
70
|
+
.slice(1, -1)
|
|
71
|
+
.split(',')
|
|
72
|
+
.map((v) => v.trim())
|
|
73
|
+
} else {
|
|
74
|
+
// Remove quotes if present
|
|
75
|
+
frontmatter[key.trim()] = value.replace(/^["']|["']$/g, '')
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
frontmatter,
|
|
82
|
+
content: mainContent.trim(),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get allowed tools for a command
|
|
88
|
+
* @param {string} commandName - Command name
|
|
89
|
+
* @returns {Promise<string[]>}
|
|
90
|
+
*/
|
|
91
|
+
async getAllowedTools(commandName) {
|
|
92
|
+
const template = await this.load(commandName)
|
|
93
|
+
return template.frontmatter['allowed-tools'] || []
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Clear cache (useful for testing)
|
|
98
|
+
*/
|
|
99
|
+
clearCache() {
|
|
100
|
+
this.cache.clear()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = new TemplateLoader()
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Registry
|
|
3
|
+
* Maps allowed-tools from templates to actual functions
|
|
4
|
+
* Simple I/O operations - NO business logic, NO if/else
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs').promises
|
|
8
|
+
const path = require('path')
|
|
9
|
+
const { promisify } = require('util')
|
|
10
|
+
const { exec: execCallback } = require('child_process')
|
|
11
|
+
const exec = promisify(execCallback)
|
|
12
|
+
|
|
13
|
+
class ToolRegistry {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.tools = {
|
|
16
|
+
Read: this.read.bind(this),
|
|
17
|
+
Write: this.write.bind(this),
|
|
18
|
+
Bash: this.bash.bind(this),
|
|
19
|
+
Exec: this.bash.bind(this), // Alias
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get tool function by name
|
|
25
|
+
* @param {string} toolName - Tool name (e.g., 'Read', 'Write')
|
|
26
|
+
* @returns {Function}
|
|
27
|
+
*/
|
|
28
|
+
get(toolName) {
|
|
29
|
+
const tool = this.tools[toolName]
|
|
30
|
+
if (!tool) {
|
|
31
|
+
throw new Error(`Unknown tool: ${toolName}`)
|
|
32
|
+
}
|
|
33
|
+
return tool
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if tool is allowed
|
|
38
|
+
* @param {string} toolName - Tool name
|
|
39
|
+
* @param {string[]} allowedTools - List of allowed tools
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
isAllowed(toolName, allowedTools) {
|
|
43
|
+
return allowedTools.includes(toolName)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Read file
|
|
48
|
+
* @param {string} filePath - File path
|
|
49
|
+
* @returns {Promise<string>}
|
|
50
|
+
*/
|
|
51
|
+
async read(filePath) {
|
|
52
|
+
try {
|
|
53
|
+
return await fs.readFile(filePath, 'utf-8')
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Write file
|
|
61
|
+
* @param {string} filePath - File path
|
|
62
|
+
* @param {string} content - Content to write
|
|
63
|
+
* @returns {Promise<void>}
|
|
64
|
+
*/
|
|
65
|
+
async write(filePath, content) {
|
|
66
|
+
// Ensure directory exists
|
|
67
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
68
|
+
await fs.writeFile(filePath, content, 'utf-8')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Execute bash command
|
|
73
|
+
* @param {string} command - Command to execute
|
|
74
|
+
* @returns {Promise<{stdout: string, stderr: string}>}
|
|
75
|
+
*/
|
|
76
|
+
async bash(command) {
|
|
77
|
+
try {
|
|
78
|
+
const { stdout, stderr } = await exec(command)
|
|
79
|
+
return { stdout, stderr }
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return {
|
|
82
|
+
stdout: '',
|
|
83
|
+
stderr: error.message,
|
|
84
|
+
error: true,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if file exists
|
|
91
|
+
* @param {string} filePath - File path
|
|
92
|
+
* @returns {Promise<boolean>}
|
|
93
|
+
*/
|
|
94
|
+
async exists(filePath) {
|
|
95
|
+
try {
|
|
96
|
+
await fs.access(filePath)
|
|
97
|
+
return true
|
|
98
|
+
} catch {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* List directory
|
|
105
|
+
* @param {string} dirPath - Directory path
|
|
106
|
+
* @returns {Promise<string[]>}
|
|
107
|
+
*/
|
|
108
|
+
async list(dirPath) {
|
|
109
|
+
try {
|
|
110
|
+
return await fs.readdir(dirPath)
|
|
111
|
+
} catch {
|
|
112
|
+
return []
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = new ToolRegistry()
|