pathchie-cli
Advanced tools
+303
-15
@@ -13,2 +13,3 @@ #!/usr/bin/env node | ||
| import { createRequire } from "module"; | ||
| import { exec } from "child_process"; | ||
@@ -81,24 +82,266 @@ const require = createRequire(import.meta.url); | ||
| async function chatLoop() { | ||
| async function chatLoop(agentMode = 'prompt', workingDirectory) { | ||
| const apiKey = process.env.GEMINI_API_KEY; | ||
| if (!apiKey) { | ||
| console.log(chalk.red("API key not set.")); | ||
| return; | ||
| } | ||
| const systemPrompt = `You are a coding agent running in the Pathchie-CLI, a terminal-based coding assistant. Pathchie CLI is an open source project led by BRcloud. You are expected to be precise, safe, and helpful. | ||
| Your capabilities: | ||
| - Receive user prompts and other context provided by the harness, such as files in the workspace. | ||
| - Communicate with the user by streaming thinking & responses, and by making & updating plans. | ||
| - Emit function calls to run terminal commands and apply patches. Depending on how this specific run is configured, you can request that these function calls be escalated to the user for approval before running. | ||
| Important: only BRcloud is running the CLI at version 0.2.0, and the LLM in use is Pathchie 3.8. | ||
| Within this context, pathchie refers to the open-source agentic coding interface (not the old pathchie language model built by BRcloud). | ||
| If you decide to make changes to the workspace or run commands, respond with a JSON object inside a fenced code block labeled as json, for example: | ||
| \`\`\`json | ||
| { | ||
| "actions": [ | ||
| {"type": "write_file", "path": "src/newfile.js", "content": "console.log(\\"hello\\");"}, | ||
| {"type": "run_command", "command": "node src/newfile.js"}, | ||
| {"type": "analyze_file", "path": "src/buggy.js"} | ||
| ] | ||
| } | ||
| \`\`\` | ||
| Only include such a JSON block when you intend the CLI to perform file or shell actions. Always also include a brief human-readable explanation outside the JSON block. | ||
| # How you work | ||
| ## Personality | ||
| Your default personality and tone is concise, direct, and friendly. You communicate efficiently, always keeping the user clearly informed about ongoing actions without unnecessary detail. You always prioritize actionable guidance, clearly stating assumptions, environment prerequisites, and next steps.`; | ||
| Your capabilities: | ||
| - Receive user prompts and other context provided by the harness, such as files in the workspace. | ||
| - Communicate with the user by streaming thinking & responses, and by making & updating plans. | ||
| - Emit function calls to run terminal commands and apply patches. Depending on how this specific run is configured, you can request that these function calls be escalated to the user for approval before running. | ||
| console.log(chalk.cyan("🧠 You can now chat with Pathchie. Type 'exit' or 'quit' to end.\n")); | ||
| Within this context, pathchie refers to the open-source agentic coding interface (not the old pathchie language model built by BRcloud). | ||
| # How you work | ||
| ## Personality | ||
| Your default personality and tone is concise, direct, and friendly. You communicate efficiently, always keeping the user clearly informed about ongoing actions without unnecessary detail. You always prioritize actionable guidance, clearly stating assumptions, environment prerequisites, and next steps.`; | ||
| console.log(chalk.cyan("🧠 You can now chat with Pathchie. Type 'exit' or 'quit' to end. Use commands starting with '/' to manage files or run code locally (e.g. /file create test.txt). To let the LLM request file or command actions, include a JSON actions block as described in the system instructions.\n")); | ||
| // Helper: confirms and writes file | ||
| async function writeFileInteractive(targetPath, initialContent = "", mode = "write") { | ||
| const fullPath = path.resolve(workingDirectory, targetPath); | ||
| const result = await inquirer.prompt([ | ||
| { | ||
| type: "editor", | ||
| name: "content", | ||
| message: `${mode === "append" ? "Append" : mode === "write" ? "Write" : "Create"} content for ${fullPath} (editor):`, | ||
| default: initialContent | ||
| } | ||
| ]); | ||
| const content = result.content ?? ""; | ||
| if (mode === "append") { | ||
| fs.appendFileSync(fullPath, content, "utf8"); | ||
| } else { | ||
| // write (overwrite) or create | ||
| fs.mkdirSync(path.dirname(fullPath), { recursive: true }); | ||
| fs.writeFileSync(fullPath, content, "utf8"); | ||
| } | ||
| console.log(chalk.green(`✅ ${mode} successful: ${fullPath}`)); | ||
| } | ||
| async function readFileInteractive(targetPath) { | ||
| const fullPath = path.resolve(workingDirectory, targetPath); | ||
| if (!fs.existsSync(fullPath)) { | ||
| console.log(chalk.red(`File not found: ${fullPath}`)); | ||
| return; | ||
| } | ||
| const content = fs.readFileSync(fullPath, "utf8"); | ||
| console.log(chalk.gray(`--- Start of ${fullPath} ---`)); | ||
| console.log(content); | ||
| console.log(chalk.gray(`--- End of ${fullPath} ---`)); | ||
| } | ||
| // Helper: extracts JSON code block labeled json | ||
| function extractJSONFromText(text) { | ||
| if (!text) return null; | ||
| const fencedRegex = /```json\s*([\s\S]*?)```/i; | ||
| const m = text.match(fencedRegex); | ||
| let jsonText = null; | ||
| if (m) { | ||
| jsonText = m[1]; | ||
| } else { | ||
| // fallback: try to find first { ... } object | ||
| const braceIndex = text.indexOf('{'); | ||
| if (braceIndex !== -1) { | ||
| const candidate = text.slice(braceIndex); | ||
| // crude attempt to find matching closing brace | ||
| try { | ||
| // try parse progressively until success | ||
| for (let i = candidate.length; i > 0; i--) { | ||
| const sub = candidate.slice(0, i); | ||
| try { | ||
| const parsed = JSON.parse(sub); | ||
| return parsed; | ||
| } catch (e) { | ||
| // continue | ||
| } | ||
| } | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
| if (!jsonText) return null; | ||
| try { | ||
| return JSON.parse(jsonText); | ||
| } catch (e) { | ||
| return null; | ||
| } | ||
| } | ||
| async function applyActions(actions) { | ||
| if (!Array.isArray(actions)) return; | ||
| for (const act of actions) { | ||
| const type = act.type; | ||
| if (type === 'write_file' || type === 'create_file') { | ||
| const target = path.resolve(workingDirectory, act.path || act.file || ''); | ||
| const content = act.content || ''; | ||
| if (!target) { | ||
| console.log(chalk.yellow('Skipping write_file: no path provided')); | ||
| continue; | ||
| } | ||
| const proceed = agentMode === 'auto' ? true : await inquirer.prompt([{ type: 'confirm', name: 'ok', message: `Write file ${target}?`, default: false }]).then(r => r.ok); | ||
| if (!proceed) { console.log(chalk.yellow('Skipped.')); continue; } | ||
| fs.mkdirSync(path.dirname(target), { recursive: true }); | ||
| fs.writeFileSync(target, content, 'utf8'); | ||
| console.log(chalk.green(`✅ Wrote file: ${target}`)); | ||
| } else if (type === 'append_file') { | ||
| const target = path.resolve(workingDirectory, act.path || act.file || ''); | ||
| const content = act.content || ''; | ||
| if (!target) { | ||
| console.log(chalk.yellow('Skipping append_file: no path provided')); | ||
| continue; | ||
| } | ||
| const proceed = agentMode === 'auto' ? true : await inquirer.prompt([{ type: 'confirm', name: 'ok', message: `Append to file ${target}?`, default: false }]).then(r => r.ok); | ||
| if (!proceed) { console.log(chalk.yellow('Skipped.')); continue; } | ||
| fs.mkdirSync(path.dirname(target), { recursive: true }); | ||
| fs.appendFileSync(target, content, 'utf8'); | ||
| console.log(chalk.green(`✅ Appended to file: ${target}`)); | ||
| } else if (type === 'read_file') { | ||
| const target = path.resolve(workingDirectory, act.path || act.file || ''); | ||
| if (!fs.existsSync(target)) { console.log(chalk.red(`File not found: ${target}`)); continue; } | ||
| const content = fs.readFileSync(target, 'utf8'); | ||
| console.log(chalk.gray(`--- Start of ${target} ---`)); | ||
| console.log(content); | ||
| console.log(chalk.gray(`--- End of ${target} ---`)); | ||
| } else if (type === 'run_command') { | ||
| const command = act.command || act.cmd || ''; | ||
| if (!command) { console.log(chalk.yellow('Skipping run_command: no command provided')); continue; } | ||
| const proceed = agentMode === 'auto' ? true : await inquirer.prompt([{ type: 'confirm', name: 'ok', message: `Run command: ${command}?`, default: false }]).then(r => r.ok); | ||
| if (!proceed) { console.log(chalk.yellow('Skipped.')); continue; } | ||
| console.log(chalk.gray(`Running: ${command}`)); | ||
| const { error, stdout, stderr } = await runShellCommand(command); | ||
| if (stdout) console.log(chalk.green(stdout)); | ||
| if (stderr) console.log(chalk.red(stderr)); | ||
| if (error) console.log(chalk.red(`Command exited with error: ${error.message}`)); | ||
| } else if (type === 'analyze_file') { | ||
| const target = path.resolve(workingDirectory, act.path || act.file || ''); | ||
| if (!target) { | ||
| console.log(chalk.yellow('Skipping analyze_file: no path provided')); | ||
| continue; | ||
| } | ||
| await analyzeFile(target); | ||
| } else { | ||
| console.log(chalk.yellow(`Unknown action type: ${type}`)); | ||
| } | ||
| } | ||
| } | ||
| async function analyzeFile(filePath) { | ||
| const fullPath = path.resolve(workingDirectory, filePath); | ||
| if (!fs.existsSync(fullPath)) { | ||
| console.log(chalk.red(`File not found for analysis: ${fullPath}`)); | ||
| return; | ||
| } | ||
| console.log(chalk.cyan(`Analyzing ${fullPath} for bugs...`)); | ||
| const fileContent = fs.readFileSync(fullPath, 'utf8'); | ||
| const analysisPrompt = `Please analyze the following code from the file "${path.basename(fullPath)}" for bugs, potential issues, or areas of improvement. Provide a summary of your findings.\n\n\`\`\`\n${fileContent}\n\`\`\``; | ||
| try { | ||
| const res = await fetch( | ||
| `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`, | ||
| { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| contents: [ | ||
| { role: "user", parts: [{ text: systemPrompt }] }, | ||
| { role: "user", parts: [{ text: analysisPrompt }] } | ||
| ] | ||
| }) | ||
| } | ||
| ); | ||
| const data = await res.json(); | ||
| const output = data?.candidates?.[0]?.content?.parts?.[0]?.text; | ||
| console.log(chalk.blue("Pathchie Analysis:") + " " + (output || "(no response)")); | ||
| } catch (err) { | ||
| console.error(chalk.red("Analysis request failed:"), err); | ||
| } | ||
| } | ||
| // Helper: run shell command | ||
| function runShellCommand(command) { | ||
| return new Promise((resolve) => { | ||
| exec(command, { cwd: workingDirectory, windowsHide: true }, (error, stdout, stderr) => { | ||
| resolve({ error, stdout, stderr }); | ||
| }); | ||
| }); | ||
| } | ||
| // Helper: handle local /file and /run commands | ||
| async function handleAgentCommand(text) { | ||
| const parts = text.trim().split(" "); | ||
| const cmd = parts[0].toLowerCase(); | ||
| if (cmd === "/file") { | ||
| const sub = parts[1]; | ||
| const target = parts.slice(2).join(" "); | ||
| if (!sub || !target) { | ||
| console.log(chalk.yellow("Usage: /file <create|write|append|read> <relative-path>")); | ||
| return; | ||
| } | ||
| if (sub === "create") { | ||
| if (fs.existsSync(path.resolve(workingDirectory, target))) { | ||
| const { overwrite } = await inquirer.prompt([{ type: "confirm", name: "overwrite", message: "File exists. Overwrite?", default: false }]); | ||
| if (!overwrite) { | ||
| console.log(chalk.yellow("Aborted.")); | ||
| return; | ||
| } | ||
| } | ||
| await writeFileInteractive(target, "", "create"); | ||
| } else if (sub === "write") { | ||
| await writeFileInteractive(target, "", "write"); | ||
| } else if (sub === "append") { | ||
| await writeFileInteractive(target, "", "append"); | ||
| } else if (sub === "read") { | ||
| await readFileInteractive(target); | ||
| } else { | ||
| console.log(chalk.yellow("Unknown /file subcommand. Use create|write|append|read.")); | ||
| } | ||
| } else if (cmd === "/run") { | ||
| const commandToRun = parts.slice(1).join(" "); | ||
| if (!commandToRun) { | ||
| console.log(chalk.yellow("Usage: /run <shell-command>")); | ||
| return; | ||
| } | ||
| const { confirmRun } = await inquirer.prompt([{ type: "confirm", name: "confirmRun", message: `Run command: ${commandToRun}?`, default: false }]); | ||
| if (!confirmRun) { | ||
| console.log(chalk.yellow("Aborted.")); | ||
| return; | ||
| } | ||
| console.log(chalk.gray(`Running: ${commandToRun}`)); | ||
| const { error, stdout, stderr } = await runShellCommand(commandToRun); | ||
| if (stdout) console.log(chalk.green(stdout)); | ||
| if (stderr) console.log(chalk.red(stderr)); | ||
| if (error) console.log(chalk.red(`Command exited with error: ${error.message}`)); | ||
| } else if (cmd === "/analyze") { | ||
| const target = parts.slice(1).join(" "); | ||
| if (!target) { | ||
| console.log(chalk.yellow("Usage: /analyze <relative-path>")); | ||
| return; | ||
| } | ||
| await analyzeFile(target); | ||
| } else { | ||
| console.log(chalk.yellow("Unknown command. Commands start with '/'. Example: /file create notes.txt")); | ||
| } | ||
| } | ||
| while (true) { | ||
@@ -110,2 +353,14 @@ const { prompt } = await inquirer.prompt([{ type: "input", name: "prompt", message: chalk.green("You:") }]); | ||
| try { | ||
| if (text.startsWith("/")) { | ||
| // Local agent command (file ops, run) | ||
| await handleAgentCommand(text); | ||
| continue; | ||
| } | ||
| if (!apiKey) { | ||
| console.log(chalk.red("No GEMINI_API_KEY set. Only local commands (starting with '/') are available, or enable agent mode with an API key.")); | ||
| continue; | ||
| } | ||
| // If we have an API key, forward to the LLM | ||
| const res = await fetch( | ||
@@ -127,2 +382,12 @@ `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`, | ||
| console.log(chalk.blue("Pathchie:") + " " + (output || "(no response)")); | ||
| // Try to extract JSON actions and apply them according to agentMode | ||
| const parsed = extractJSONFromText(output); | ||
| if (parsed && parsed.actions) { | ||
| if (agentMode === 'manual') { | ||
| console.log(chalk.yellow('LLM requested actions but agent mode is MANUAL. Use /file or /run commands to apply changes.')); | ||
| } else { | ||
| await applyActions(parsed.actions); | ||
| } | ||
| } | ||
| } catch (err) { | ||
@@ -138,3 +403,26 @@ console.error(chalk.red("Request failed:"), err); | ||
| await authenticate(); | ||
| await chatLoop(); | ||
| const { workDir } = await inquirer.prompt([{ | ||
| type: 'input', | ||
| name: 'workDir', | ||
| message: 'Enter the working directory for this session:', | ||
| default: path.join(process.cwd(), 'pathchie-workspace') | ||
| }]); | ||
| const workingDirectory = path.resolve(workDir); | ||
| if (!fs.existsSync(workingDirectory)) { | ||
| try { | ||
| fs.mkdirSync(workingDirectory, { recursive: true }); | ||
| console.log(chalk.green(`✅ Created working directory: ${workingDirectory}\n`)); | ||
| } catch (err) { | ||
| console.error(chalk.red(`Could not create working directory: ${err.message}`)); | ||
| process.exit(1); | ||
| } | ||
| } else { | ||
| console.log(chalk.cyan(`Working directory set to: ${workingDirectory}\n`)); | ||
| } | ||
| // Ask which agent mode the user prefers | ||
| const { agentMode } = await inquirer.prompt([{ type: 'list', name: 'agentMode', message: 'Agent action mode:', choices: [ { name: 'Manual (no automatic actions, use /file and /run)', value: 'manual' }, { name: 'Prompt (ask before applying each action)', value: 'prompt' }, { name: 'Auto (apply actions without prompting)', value: 'auto' } ], default: 'prompt' }]); | ||
| await chatLoop(agentMode, workingDirectory); | ||
| await showTips(); | ||
@@ -141,0 +429,0 @@ console.log("https://github.com/Filox77250/pathchie-cli"); |
+3
-2
| { | ||
| "name": "pathchie-cli", | ||
| "version": "0.1.4", | ||
| "version": "0.2.0", | ||
| "description": "Interactive CLI for Pathchie IA", | ||
@@ -14,3 +14,4 @@ "bin": { | ||
| "scripts": { | ||
| "start": "node index.js" | ||
| "start": "node index.js", | ||
| "dev": "node index.js" | ||
| }, | ||
@@ -17,0 +18,0 @@ "keywords": [ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
18452
209.65%391
223.14%8
14.29%6
100%