New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

pathchie-cli

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pathchie-cli - npm Package Compare versions

Comparing version
0.1.4
to
0.2.0
+303
-15
index.js

@@ -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": [