@rxpm/forge-cli 0.0.1
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/.dockerignore +6 -0
- package/.prettierrc +9 -0
- package/AGENT_RULES.md +42 -0
- package/Dockerfile +32 -0
- package/README.md +117 -0
- package/REQUIREMENTS.md +233 -0
- package/assets/preview_explain.webp +0 -0
- package/dist/agent/loop.d.ts +6 -0
- package/dist/agent/loop.js +62 -0
- package/dist/agent/loop.js.map +1 -0
- package/dist/cli/commands.d.ts +3 -0
- package/dist/cli/commands.js +40 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/config/env.d.ts +9 -0
- package/dist/config/env.js +16 -0
- package/dist/config/env.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/provider.d.ts +4 -0
- package/dist/llm/provider.js +12 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/tools/code.d.ts +15 -0
- package/dist/tools/code.js +53 -0
- package/dist/tools/code.js.map +1 -0
- package/dist/tools/fs.d.ts +18 -0
- package/dist/tools/fs.js +70 -0
- package/dist/tools/fs.js.map +1 -0
- package/dist/tools/git.d.ts +15 -0
- package/dist/tools/git.js +61 -0
- package/dist/tools/git.js.map +1 -0
- package/dist/tools/index.d.ts +35 -0
- package/dist/tools/index.js +22 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/shell.d.ts +9 -0
- package/dist/tools/shell.js +31 -0
- package/dist/tools/shell.js.map +1 -0
- package/dist/ui/activity.d.ts +49 -0
- package/dist/ui/activity.js +421 -0
- package/dist/ui/activity.js.map +1 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.js +10 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/logger2.d.ts +16 -0
- package/dist/utils/logger2.js +55 -0
- package/dist/utils/logger2.js.map +1 -0
- package/dist/utils/test-tools.d.ts +1 -0
- package/dist/utils/test-tools.js +31 -0
- package/dist/utils/test-tools.js.map +1 -0
- package/package.json +43 -0
- package/src/agent/loop.ts +68 -0
- package/src/cli/commands.ts +44 -0
- package/src/config/env.ts +17 -0
- package/src/index.ts +22 -0
- package/src/llm/provider.ts +13 -0
- package/src/tools/code.ts +53 -0
- package/src/tools/fs.ts +71 -0
- package/src/tools/git.ts +60 -0
- package/src/tools/index.ts +23 -0
- package/src/tools/shell.ts +32 -0
- package/src/ui/activity.ts +504 -0
- package/src/utils/logger.ts +10 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { stepCountIs, ToolLoopAgent } from 'ai';
|
|
2
|
+
import { ollama, defaultModel } from '../llm/provider.js';
|
|
3
|
+
import { tools } from '../tools/index.js';
|
|
4
|
+
import { ActivityDisplay } from '../ui/activity.js';
|
|
5
|
+
|
|
6
|
+
const SYSTEM_PROMPT = `
|
|
7
|
+
You are a senior AI coding agent operating in a real filesystem.
|
|
8
|
+
Your goal is to solve the user's coding task by following a logical process:
|
|
9
|
+
1. Explore the codebase to understand the current state.
|
|
10
|
+
2. Plan your changes carefully.
|
|
11
|
+
3. Execute the changes by writing complete, valid files or code blocks.
|
|
12
|
+
4. Verify your work using the tools provided.
|
|
13
|
+
|
|
14
|
+
## Core Rules:
|
|
15
|
+
1. ALWAYS read a file before modifying it.
|
|
16
|
+
2. NEVER guess file content.
|
|
17
|
+
3. When writing a file, PROVIDE THE FULL CONTENT of the file unless instructed otherwise.
|
|
18
|
+
4. Use small, verifiable steps.
|
|
19
|
+
5. If a command fails, analyze the error and try a different approach.
|
|
20
|
+
6. Be concise and professional. No fluff.
|
|
21
|
+
|
|
22
|
+
## Tools Available:
|
|
23
|
+
- read_file(path): Returns the full content of a file.
|
|
24
|
+
- write_file(path, content): Writes the EXACT content to a file.
|
|
25
|
+
- list_files(path): Lists all files in the given directory.
|
|
26
|
+
- search_code(query): Searches for a string in the codebase.
|
|
27
|
+
- open_file_lines(path, start, end): Reads specific lines (1-indexed).
|
|
28
|
+
- run_command(cmd): Executes a shell command.
|
|
29
|
+
- git_status(), git_diff(), git_commit(message): Git operations.
|
|
30
|
+
|
|
31
|
+
Always verify your changes after writing.
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Runs the AI agent to solve the given task.
|
|
36
|
+
* @param task The task to solve.
|
|
37
|
+
* @param maxIterations The maximum number of iterations to run the agent for.
|
|
38
|
+
*/
|
|
39
|
+
export async function runAgent(task: string, maxIterations = 10) {
|
|
40
|
+
const display = new ActivityDisplay();
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Show branded banner
|
|
44
|
+
ActivityDisplay.printBanner(task);
|
|
45
|
+
|
|
46
|
+
// Start thinking indicator
|
|
47
|
+
display.startThinking();
|
|
48
|
+
|
|
49
|
+
const agent = new ToolLoopAgent({
|
|
50
|
+
model: ollama(defaultModel),
|
|
51
|
+
instructions: SYSTEM_PROMPT,
|
|
52
|
+
tools,
|
|
53
|
+
maxRetries: 2,
|
|
54
|
+
toolChoice: 'required',
|
|
55
|
+
stopWhen: stepCountIs(maxIterations),
|
|
56
|
+
onStepFinish(step) {
|
|
57
|
+
display.onStepFinish(step);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await agent.generate({ prompt: task });
|
|
62
|
+
|
|
63
|
+
display.printSummary('success');
|
|
64
|
+
} catch (error: any) {
|
|
65
|
+
display.printSummary('error', error.message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { runAgent } from '../agent/loop.js';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
|
|
5
|
+
const program = new Command();
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name('forge')
|
|
9
|
+
.description('AI coding agent CLI tool')
|
|
10
|
+
.version('0.0.1')
|
|
11
|
+
.argument('[task]', 'The task to execute (default if no command provided)')
|
|
12
|
+
.option('-i, --iterations <number>', 'Maximum number of iterations', '10')
|
|
13
|
+
.action(async function (task, options) {
|
|
14
|
+
if (task) {
|
|
15
|
+
await runAgent(task, parseInt(options.iterations));
|
|
16
|
+
} else {
|
|
17
|
+
program.help();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
program
|
|
22
|
+
.command('chat')
|
|
23
|
+
.description('Interactive mode (v1: executes a single multi-step task)')
|
|
24
|
+
.action(async function () {
|
|
25
|
+
logger.warn('Interactive chat mode is currently limited to executing single tasks via main "agent <task>" command.');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command('explain')
|
|
30
|
+
.argument('<file>', 'The file to explain')
|
|
31
|
+
.action(async function (file) {
|
|
32
|
+
const task = `Explain the code in the file: ${file}`;
|
|
33
|
+
await runAgent(task);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.command('fix')
|
|
38
|
+
.description('Detect and fix errors in the project')
|
|
39
|
+
.action(async function () {
|
|
40
|
+
const task = `Search for errors in the project, check git status, and try to fix any obvious bugs.`;
|
|
41
|
+
await runAgent(task);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export { program };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
/** Get the API URL for the Ollama server. */
|
|
3
|
+
get ollamaApiUrl(): string {
|
|
4
|
+
const baseUrl = process.env.OLLAMA_API_URL || 'http://localhost:11434';
|
|
5
|
+
return `${baseUrl}/api`;
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
/** Get the API secret for the Ollama server. */
|
|
9
|
+
get ollamaApiSecret(): string {
|
|
10
|
+
return process.env.OLLAMA_API_KEY || '';
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
/** Get the default model to use for the AI agent. */
|
|
14
|
+
get defaultModel(): string {
|
|
15
|
+
return process.env.OLLAMA_DEFAULT_MODEL || 'gpt-oss:120b';
|
|
16
|
+
},
|
|
17
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* File: src/index.ts
|
|
5
|
+
* Author: Rajat Sharma
|
|
6
|
+
* Description: The main entry point for the CLI tool.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { program } from './cli/commands.js';
|
|
10
|
+
import { logger } from './utils/logger.js';
|
|
11
|
+
|
|
12
|
+
/** The main function that parses the command line arguments and executes the corresponding command. */
|
|
13
|
+
async function main() {
|
|
14
|
+
try {
|
|
15
|
+
await program.parseAsync(process.argv);
|
|
16
|
+
} catch (error: any) {
|
|
17
|
+
logger.error(`CLI execution failed: ${error.message}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
main();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createOllama } from 'ollama-ai-provider-v2';
|
|
2
|
+
import env from '../config/env.js';
|
|
3
|
+
|
|
4
|
+
/** The default model to use for the AI agent. */
|
|
5
|
+
export const defaultModel = env.defaultModel;
|
|
6
|
+
|
|
7
|
+
/** The Ollama client for the AI agent. */
|
|
8
|
+
export const ollama = createOllama({
|
|
9
|
+
baseURL: env.ollamaApiUrl + '/api',
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${env.ollamaApiSecret}`,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: code.ts
|
|
3
|
+
* Author: Rajat Sharma
|
|
4
|
+
* Description: Code tools for the AI agent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { tool } from 'ai';
|
|
10
|
+
import { execa } from 'execa';
|
|
11
|
+
|
|
12
|
+
/** Searches for a string in the codebase using grep. */
|
|
13
|
+
export const searchCodeTool = tool({
|
|
14
|
+
title: 'SearchCode',
|
|
15
|
+
description: 'Search for a string in the codebase using grep.',
|
|
16
|
+
inputSchema: z.object({
|
|
17
|
+
query: z.string().describe('The search query string.'),
|
|
18
|
+
}),
|
|
19
|
+
async execute({ query }) {
|
|
20
|
+
try {
|
|
21
|
+
// Use grep to search for the query string.
|
|
22
|
+
// -r: recursive, -n: show line numbers, -l: show only filenames (optional),
|
|
23
|
+
// -i: ignore case (optional), -E: extended regex
|
|
24
|
+
const { stdout } = await execa('grep', ['-rnE', query, '.', '--exclude-dir={node_modules,dist,.git}'], {
|
|
25
|
+
reject: false, // Don't throw if grep finds nothing (it exits with code 1)
|
|
26
|
+
});
|
|
27
|
+
return stdout || 'No matches found.';
|
|
28
|
+
} catch (error: any) {
|
|
29
|
+
return `Error searching code: ${error.message}`;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Opens a file and reads specific line range. */
|
|
35
|
+
export const openFileLinesTool = tool({
|
|
36
|
+
title: 'OpenFileLines',
|
|
37
|
+
description: 'Open a file and read specific line range.',
|
|
38
|
+
inputSchema: z.object({
|
|
39
|
+
path: z.string().describe('The path to the file.'),
|
|
40
|
+
start: z.coerce.number().describe('The start line number (1-based).'),
|
|
41
|
+
end: z.coerce.number().describe('The end line number (inclusive).'),
|
|
42
|
+
}),
|
|
43
|
+
async execute({ path, start, end }) {
|
|
44
|
+
try {
|
|
45
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
46
|
+
const lines = content.split('\n');
|
|
47
|
+
const slicedLines = lines.slice(Math.max(0, start - 1), end);
|
|
48
|
+
return slicedLines.join('\n');
|
|
49
|
+
} catch (error: any) {
|
|
50
|
+
return `Error reading file lines: ${error.message}`;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
package/src/tools/fs.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: fs.ts
|
|
3
|
+
* Author: Rajat Sharma
|
|
4
|
+
* Description: File system tools for the AI agent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { tool } from 'ai';
|
|
10
|
+
import { glob } from 'glob';
|
|
11
|
+
|
|
12
|
+
/** Reads the contents of a file. */
|
|
13
|
+
export const readFileTool = tool({
|
|
14
|
+
title: 'ReadFile',
|
|
15
|
+
description: 'Read the contents of a file. LIMIT file size (max 5000 lines per read).',
|
|
16
|
+
inputSchema: z.object({
|
|
17
|
+
path: z.string().describe('The path to the file to read.'),
|
|
18
|
+
}),
|
|
19
|
+
async execute({ path }) {
|
|
20
|
+
try {
|
|
21
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
22
|
+
const lines = content.split('\n');
|
|
23
|
+
if (lines.length > 5000) {
|
|
24
|
+
return `File too large: ${lines.length} lines. Max 5000 lines.`;
|
|
25
|
+
}
|
|
26
|
+
return content;
|
|
27
|
+
} catch (error: any) {
|
|
28
|
+
return `Error reading file: ${error.message}`;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/** Writes content to a file. */
|
|
34
|
+
export const writeFileTool = tool({
|
|
35
|
+
title: 'WriteFile',
|
|
36
|
+
description: 'Write content to a file. Justify large rewrites.',
|
|
37
|
+
inputSchema: z.object({
|
|
38
|
+
path: z.string().describe('The path to the file to write.'),
|
|
39
|
+
content: z.string().describe('The content to write to the file.'),
|
|
40
|
+
}),
|
|
41
|
+
async execute({ path, content }) {
|
|
42
|
+
try {
|
|
43
|
+
await fs.writeFile(path, content, 'utf-8');
|
|
44
|
+
return `Successfully wrote to ${path}`;
|
|
45
|
+
} catch (error: any) {
|
|
46
|
+
return `Error writing file: ${error.message}`;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** Lists files in a directory recursively. */
|
|
52
|
+
export const listFilesTool = tool({
|
|
53
|
+
title: 'ListFiles',
|
|
54
|
+
description: 'List files in a directory recursively.',
|
|
55
|
+
inputSchema: z.object({
|
|
56
|
+
path: z.string().describe('The directory path to list files from. Defaults to ".".').default('.'),
|
|
57
|
+
}),
|
|
58
|
+
async execute({ path }) {
|
|
59
|
+
try {
|
|
60
|
+
const files = await glob('**/*', {
|
|
61
|
+
cwd: path,
|
|
62
|
+
nodir: true,
|
|
63
|
+
ignore: ['node_modules/**', 'dist/**', '.git/**', '.gitignore'],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return files.join('\n');
|
|
67
|
+
} catch (error: any) {
|
|
68
|
+
return `Error listing files: ${error.message}`;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
package/src/tools/git.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: git.ts
|
|
3
|
+
* Author: Rajat Sharma
|
|
4
|
+
* Description: Git tools for the AI agent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { tool } from 'ai';
|
|
9
|
+
import { execa } from 'execa';
|
|
10
|
+
|
|
11
|
+
/** Checks the git status of the project. */
|
|
12
|
+
export const gitStatusTool = tool({
|
|
13
|
+
title: 'GitStatus',
|
|
14
|
+
description: 'Check the git status of the project.',
|
|
15
|
+
inputSchema: z.object({}),
|
|
16
|
+
async execute() {
|
|
17
|
+
try {
|
|
18
|
+
const { stdout } = await execa('git', ['status']);
|
|
19
|
+
return stdout || 'No output from git status.';
|
|
20
|
+
} catch (error: any) {
|
|
21
|
+
return `Error checking git status: ${error.message}`;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** Shows git diff for staged and unstaged changes. */
|
|
27
|
+
export const gitDiffTool = tool({
|
|
28
|
+
title: 'GitDiff',
|
|
29
|
+
description: 'Show git diff for staged and unstaged changes.',
|
|
30
|
+
inputSchema: z.object({
|
|
31
|
+
staged: z.boolean().optional().describe('Show staged changes only.').default(false),
|
|
32
|
+
}),
|
|
33
|
+
async execute({ staged }) {
|
|
34
|
+
try {
|
|
35
|
+
const args = ['diff'];
|
|
36
|
+
if (staged) args.push('--staged');
|
|
37
|
+
const { stdout } = await execa('git', args);
|
|
38
|
+
return stdout || 'No differences found.';
|
|
39
|
+
} catch (error: any) {
|
|
40
|
+
return `Error showing git diff: ${error.message}`;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/** Commits staged changes to git with a message. */
|
|
46
|
+
export const gitCommitTool = tool({
|
|
47
|
+
title: 'GitCommit',
|
|
48
|
+
description: 'Commit staged changes to git with a message.',
|
|
49
|
+
inputSchema: z.object({
|
|
50
|
+
message: z.string().describe('The commit message.'),
|
|
51
|
+
}),
|
|
52
|
+
async execute({ message }) {
|
|
53
|
+
try {
|
|
54
|
+
const { stdout } = await execa('git', ['commit', '-m', message]);
|
|
55
|
+
return stdout || 'Committed changes.';
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
return `Error committing changes: ${error.message}`;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: index.ts
|
|
3
|
+
* Author: Rajat Sharma
|
|
4
|
+
* Description: Index of all tools for the AI agent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileTool, writeFileTool, listFilesTool } from './fs.js';
|
|
8
|
+
import { searchCodeTool, openFileLinesTool } from './code.js';
|
|
9
|
+
import { runCommandTool } from './shell.js';
|
|
10
|
+
import { gitStatusTool, gitDiffTool, gitCommitTool } from './git.js';
|
|
11
|
+
|
|
12
|
+
// Export all tools
|
|
13
|
+
export const tools = {
|
|
14
|
+
read_file: readFileTool,
|
|
15
|
+
write_file: writeFileTool,
|
|
16
|
+
list_files: listFilesTool,
|
|
17
|
+
search_code: searchCodeTool,
|
|
18
|
+
open_file_lines: openFileLinesTool,
|
|
19
|
+
run_command: runCommandTool,
|
|
20
|
+
git_status: gitStatusTool,
|
|
21
|
+
git_diff: gitDiffTool,
|
|
22
|
+
git_commit: gitCommitTool,
|
|
23
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: shell.ts
|
|
3
|
+
* Author: Rajat Sharma
|
|
4
|
+
* Description: Shell tools for the AI agent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { tool } from 'ai';
|
|
9
|
+
import { execa } from 'execa';
|
|
10
|
+
|
|
11
|
+
/** Runs a shell command in the project root. */
|
|
12
|
+
export const runCommandTool = tool({
|
|
13
|
+
title: 'RunCommand',
|
|
14
|
+
description: 'Run a shell command in the project root. DO NOT run destructive commands without confirmation.',
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
command: z.string().describe('The shell command to execute.'),
|
|
17
|
+
}),
|
|
18
|
+
async execute({ command }) {
|
|
19
|
+
// Simple safety check for common destructive commands
|
|
20
|
+
const destructive = ['rm -rf', 'sudo', 'chmod', 'chown', 'mkfs', 'dd'];
|
|
21
|
+
if (destructive.some((d) => command.includes(d))) {
|
|
22
|
+
return `Error: Command "${command}" is considered potentially destructive and requires manual execution or explicit user confirmation.`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const { stdout, stderr } = await execa(command, { shell: true });
|
|
27
|
+
return stdout || stderr || 'Command executed successfully (no output).';
|
|
28
|
+
} catch (error: any) {
|
|
29
|
+
return `Error executing command: ${error.message}\n${error.stderr || ''}`;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|