editcodewithai 0.0.1 → 0.1.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.
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.models = void 0;
4
+ exports.models = [
5
+ "anthropic/claude-3.7-sonnet",
6
+ "anthropic/claude-3.7-sonnet:thinking",
7
+ "google/gemini-2.0-flash-001",
8
+ "anthropic/claude-3.5-sonnet",
9
+ "deepseek/deepseek-r1",
10
+ "openai/o3-mini-high",
11
+ "openai/o3-mini",
12
+ "deepseek/deepseek-chat",
13
+ "qwen/qwen-2.5-72b-instruct",
14
+ "qwen/qwq-32b",
15
+ "anthropic/claude-3.5-haiku",
16
+ "qwen/qwen-2.5-7b-instruct",
17
+ "amazon/nova-micro-v1",
18
+ "meta-llama/llama-3.1-8b-instruct",
19
+ "meta-llama/llama-3.2-3b-instruct",
20
+ "meta-llama/llama-3.2-1b-instruct",
21
+ ];
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Runs "index.mjs" for a given challenge directory with Node and returns exit code.
3
+ */
4
+ export declare function runNodeTest(challengeDir: string): number;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runNodeTest = runNodeTest;
4
+ const child_process_1 = require("child_process");
5
+ /**
6
+ * Runs "index.mjs" for a given challenge directory with Node and returns exit code.
7
+ */
8
+ function runNodeTest(challengeDir) {
9
+ const result = runNodeProcess(challengeDir);
10
+ logTestResult(challengeDir, result);
11
+ return result.status ?? 1; // If status is null, consider that a failure
12
+ }
13
+ /**
14
+ * Executes the Node.js process for a test
15
+ */
16
+ function runNodeProcess(challengeDir) {
17
+ const child = (0, child_process_1.spawnSync)("node", ["index.mjs"], {
18
+ cwd: challengeDir,
19
+ encoding: "utf-8",
20
+ });
21
+ return {
22
+ status: child.status,
23
+ stdout: child.stdout,
24
+ stderr: child.stderr
25
+ };
26
+ }
27
+ /**
28
+ * Logs the test result to the console
29
+ */
30
+ function logTestResult(challengeDir, result) {
31
+ console.log(`--- Running test in ${challengeDir} ---`);
32
+ console.log("stdout:\n", result.stdout.trim());
33
+ console.log("stderr:\n", result.stderr.trim());
34
+ console.log("exit code:", result.status, "\n");
35
+ }
@@ -0,0 +1,16 @@
1
+ import { VizFiles } from "@vizhub/viz-types";
2
+ export interface Challenge {
3
+ name: string;
4
+ prompt: string;
5
+ files: VizFiles;
6
+ }
7
+ export interface ChallengeResult {
8
+ challenge: string;
9
+ model: string;
10
+ passFail: "pass" | "fail" | "error";
11
+ }
12
+ export interface TestRunResult {
13
+ status: number | null;
14
+ stdout: string;
15
+ stderr: string;
16
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const commander_1 = require("commander");
41
+ const path = __importStar(require("path"));
42
+ const readline = __importStar(require("readline"));
43
+ const index_1 = require("./index");
44
+ const llm_1 = require("./benchmarks/llm");
45
+ const computeInitialDocument_1 = require("./computeInitialDocument");
46
+ const dotenv_1 = __importDefault(require("dotenv"));
47
+ const fs = __importStar(require("fs/promises"));
48
+ // Load environment variables
49
+ dotenv_1.default.config();
50
+ const program = new commander_1.Command();
51
+ program
52
+ .name("editcode")
53
+ .description("Edit code files using AI")
54
+ .option("-p, --prompt <prompt>", "The instruction for editing the code")
55
+ .option("-d, --dir <directory>", "Directory to process", process.cwd())
56
+ .option("--dry-run", "Show changes without writing them", false)
57
+ .option("--model <model>", "OpenRouter model to use", "anthropic/claude-3.7-sonnet")
58
+ .action(async (options) => {
59
+ try {
60
+ // Get prompt from user if not provided as argument
61
+ if (!options.prompt) {
62
+ const rl = readline.createInterface({
63
+ input: process.stdin,
64
+ output: process.stdout
65
+ });
66
+ options.prompt = await new Promise((resolve) => {
67
+ rl.question("> ", (answer) => {
68
+ rl.close();
69
+ resolve(answer);
70
+ });
71
+ });
72
+ }
73
+ if (!options.prompt.trim()) {
74
+ console.error("Error: Prompt cannot be empty");
75
+ process.exit(1);
76
+ }
77
+ // Debug: Log environment
78
+ console.log('Environment variables:', {
79
+ OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY ? '[PRESENT]' : '[MISSING]',
80
+ NODE_ENV: process.env.NODE_ENV,
81
+ PATH: process.env.PATH?.substring(0, 50) + '...'
82
+ });
83
+ // Validate API key
84
+ const apiKey = process.env.OPENROUTER_API_KEY;
85
+ if (!apiKey) {
86
+ console.error("Error: OPENROUTER_API_KEY environment variable is required");
87
+ process.exit(1);
88
+ }
89
+ // Get absolute path
90
+ const fullPath = path.resolve(options.dir);
91
+ console.log(`Processing directory: ${fullPath}`);
92
+ // Use computeInitialDocument to get files (respects .ignore files)
93
+ const initialDocument = (0, computeInitialDocument_1.computeInitialDocument)({ fullPath });
94
+ // Convert to VizFiles format
95
+ const vizFiles = {};
96
+ Object.entries(initialDocument.files).forEach(([id, file]) => {
97
+ if (file.text !== null) {
98
+ // Skip directories
99
+ vizFiles[id] = {
100
+ name: path.relative(process.cwd(), path.join(fullPath, file.name)),
101
+ text: file.text,
102
+ };
103
+ }
104
+ });
105
+ const fileCount = Object.keys(vizFiles).length;
106
+ if (fileCount === 0) {
107
+ console.error("No files found to process");
108
+ process.exit(1);
109
+ }
110
+ console.log(`Found ${fileCount} files to process`);
111
+ // Create LLM function
112
+ const llmFunction = (0, llm_1.createOpenRouterLlmFunction)(options.model, apiKey);
113
+ // Perform the edit
114
+ console.log("\nRequesting changes from AI...");
115
+ const result = await (0, index_1.performAiEdit)({
116
+ prompt: options.prompt,
117
+ files: vizFiles,
118
+ llmFunction,
119
+ apiKey,
120
+ });
121
+ // Show changes
122
+ console.log("\nProposed changes:");
123
+ for (const [id, file] of Object.entries(result.changedFiles)) {
124
+ const original = vizFiles[id];
125
+ if (!original) {
126
+ console.log(`\nNew file: ${file.name}`);
127
+ console.log(file.text);
128
+ }
129
+ else if (original.text !== file.text) {
130
+ console.log(`\nModified: ${file.name}`);
131
+ console.log(file.text);
132
+ }
133
+ }
134
+ // Write changes if not dry run
135
+ if (!options.dryRun) {
136
+ console.log("\nWriting changes...");
137
+ for (const [_, file] of Object.entries(result.changedFiles)) {
138
+ const fullPath = path.resolve(file.name);
139
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
140
+ await fs.writeFile(fullPath, file.text);
141
+ console.log(`Updated ${fullPath}`);
142
+ }
143
+ }
144
+ // Show metadata
145
+ console.log("\nMetadata:");
146
+ console.log(`Model: ${options.model}`);
147
+ console.log(`Provider: ${result.provider}`);
148
+ console.log(`Cost: ${result.upstreamCostCents / 100} USD`);
149
+ console.log(`Input tokens: ${result.inputTokens}`);
150
+ console.log(`Output tokens: ${result.outputTokens}`);
151
+ }
152
+ catch (error) {
153
+ console.error("Error:", error);
154
+ process.exit(1);
155
+ }
156
+ });
157
+ program.parse();
@@ -0,0 +1,13 @@
1
+ interface InitialDocument {
2
+ files: {
3
+ [key: string]: {
4
+ text: string | null;
5
+ name: string;
6
+ };
7
+ };
8
+ }
9
+ export declare const isDirectory: (path: string) => boolean;
10
+ export declare const computeInitialDocument: ({ fullPath }: {
11
+ fullPath: string;
12
+ }) => InitialDocument;
13
+ export {};
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.computeInitialDocument = exports.isDirectory = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const ignore_1 = __importDefault(require("ignore"));
10
+ // Configuration constants
11
+ const ignoreFilePattern = '**/{.ignore,.gitignore}';
12
+ const baseIgnore = [
13
+ '.git',
14
+ 'node_modules',
15
+ 'dist',
16
+ '*.log',
17
+ '.DS_Store',
18
+ 'coverage',
19
+ '.env',
20
+ '.env.*',
21
+ ];
22
+ const debugIgnore = false;
23
+ const debugDirectories = false;
24
+ const enableDirectories = true;
25
+ /**
26
+ * @param {string} fullPath - absolute path of the workspace root
27
+ * @param {string} currentDirectoryPath - path where the ignore file is found, relative to fullPath
28
+ * @param {string} fileName - name of the ignore file
29
+ * @returns {string[]} parsed lines
30
+ */
31
+ const parseIgnoreFile = (fullPath, currentDirectory, fileName) => {
32
+ const filePath = path_1.default.join(fullPath, currentDirectory, fileName);
33
+ const content = fs_1.default.readFileSync(filePath, 'utf8');
34
+ const globs = content
35
+ .split(/[\n\r]+/)
36
+ .filter(
37
+ // remove blank line and comments
38
+ (line) => line.length > 0 && !line.startsWith('#'))
39
+ .map((line) => {
40
+ const { bang, slash, glob } = line.match(/^(?<bang>!?)(?<slash>\/?)(?<glob>.*)$/).groups;
41
+ const hasSlash = Boolean(slash) || /\/.*\S/.test(glob);
42
+ const relativeGlob = path_1.default.posix.join(currentDirectory.replace(
43
+ // escape characters with special meaning in glob expressions
44
+ /[*?!# \[\]\\]/g, (char) => '\\' + char),
45
+ // a pattern that doesn't include a slash (not counting a trailing one) matches files in any descendant directory of the current one
46
+ hasSlash ? '' : '**', glob);
47
+ // preserve leading `!` and `/` characters
48
+ return bang + slash + relativeGlob;
49
+ });
50
+ if (debugIgnore) {
51
+ console.debug('at', currentDirectory, 'parsing', fileName, 'obtained globs', globs);
52
+ }
53
+ return globs;
54
+ };
55
+ const isDirectory = (path) => path.endsWith('/');
56
+ exports.isDirectory = isDirectory;
57
+ // Lists files from the file system,
58
+ // converts them into the internal data structure.
59
+ const computeInitialDocument = ({ fullPath }) => {
60
+ // Initialize the document using our data structure for representing files.
61
+ const initialDocument = {
62
+ files: {},
63
+ };
64
+ /**
65
+ * Stack for recursively traversing directories.
66
+ * @type {string[]}
67
+ */
68
+ let files = [];
69
+ const ignoreFileMatcher = (0, ignore_1.default)().add(ignoreFilePattern);
70
+ const isIgnoreFile = (fileName) => ignoreFileMatcher.ignores(fileName);
71
+ const unsearchedDirectories = [
72
+ {
73
+ currentDirectory: '.',
74
+ ignore: (0, ignore_1.default)().add(baseIgnore),
75
+ },
76
+ ];
77
+ while (unsearchedDirectories.length !== 0) {
78
+ const { currentDirectory, ignore: parentIgnore } = unsearchedDirectories.pop();
79
+ const currentDirectoryPath = path_1.default.join(fullPath, currentDirectory);
80
+ const dirEntries = fs_1.default
81
+ .readdirSync(currentDirectoryPath, {
82
+ withFileTypes: true,
83
+ })
84
+ .filter((dirent) => enableDirectories ? true : dirent.isFile());
85
+ // find .ignore or .gitignore files in the current directory
86
+ const ignoreFiles = dirEntries
87
+ .filter((dirent) => dirent.isFile() && isIgnoreFile(dirent.name))
88
+ .map((file) => file.name);
89
+ let ignore = parentIgnore;
90
+ if (ignoreFiles.length > 0) {
91
+ const globs = ignoreFiles.flatMap((fileName) => parseIgnoreFile(fullPath, currentDirectory, fileName));
92
+ ignore = (0, ignore_1.default)().add(parentIgnore).add(globs);
93
+ }
94
+ const newFiles = dirEntries
95
+ .filter((dirent) => {
96
+ const relativePath = path_1.default.posix.join(currentDirectory, dirent.name) +
97
+ (dirent.isDirectory() ? '/' : '');
98
+ const keep = !ignore.ignores(relativePath);
99
+ if (debugIgnore && !keep) {
100
+ console.debug('at', currentDirectory, 'ignoring', relativePath);
101
+ }
102
+ return keep;
103
+ })
104
+ // Add a trailing slash for directories
105
+ .map((dirent) => {
106
+ const relativePath = path_1.default.posix.join(currentDirectory, dirent.name);
107
+ if (!dirent.isDirectory()) {
108
+ return relativePath;
109
+ }
110
+ unsearchedDirectories.push({
111
+ currentDirectory: relativePath,
112
+ ignore,
113
+ });
114
+ return relativePath + '/';
115
+ });
116
+ files.push(...newFiles);
117
+ }
118
+ files.forEach((file) => {
119
+ const id = Math.floor(Math.random() * 10000000).toString();
120
+ initialDocument.files[id] = {
121
+ text: (0, exports.isDirectory)(file)
122
+ ? null
123
+ : fs_1.default.readFileSync(path_1.default.join(fullPath, file), 'utf-8'),
124
+ name: file,
125
+ };
126
+ });
127
+ if (debugDirectories) {
128
+ console.log('files:');
129
+ console.log(files);
130
+ console.log('initialDocument:');
131
+ console.log(JSON.stringify(initialDocument, null, 2));
132
+ }
133
+ return initialDocument;
134
+ };
135
+ exports.computeInitialDocument = computeInitialDocument;
@@ -0,0 +1,20 @@
1
+ import { VizFiles, VizFile } from "@vizhub/viz-types";
2
+ /**
3
+ * If the LLM outputs empty text for a file, we interpret this
4
+ * as a request to delete the file.
5
+ */
6
+ export declare function shouldDeleteFile(file?: VizFile): boolean;
7
+ /**
8
+ * Processes files for the prompt by truncating large files
9
+ */
10
+ export declare function prepareFilesForPrompt(files: VizFiles): {
11
+ name: string;
12
+ text: string;
13
+ }[];
14
+ /**
15
+ * Merges original files with changes from the LLM
16
+ */
17
+ export declare function mergeFileChanges(originalFiles: VizFiles, parsedFiles: {
18
+ name: string;
19
+ text: string;
20
+ }[]): VizFiles;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shouldDeleteFile = shouldDeleteFile;
4
+ exports.prepareFilesForPrompt = prepareFilesForPrompt;
5
+ exports.mergeFileChanges = mergeFileChanges;
6
+ const viz_utils_1 = require("@vizhub/viz-utils");
7
+ /**
8
+ * If the LLM outputs empty text for a file, we interpret this
9
+ * as a request to delete the file.
10
+ */
11
+ function shouldDeleteFile(file) {
12
+ if (!file)
13
+ return false;
14
+ return file.text.trim() === "";
15
+ }
16
+ /**
17
+ * Processes files for the prompt by truncating large files
18
+ */
19
+ function prepareFilesForPrompt(files) {
20
+ return Object.values(files).map((file) => ({
21
+ name: file.name,
22
+ // Example: truncate large files, etc.
23
+ text: file.text
24
+ .split("\n")
25
+ .slice(0, file.name.endsWith(".csv") || file.name.endsWith(".json") ? 50 : 500)
26
+ .map((line) => line.slice(0, 200))
27
+ .join("\n"),
28
+ }));
29
+ }
30
+ /**
31
+ * Merges original files with changes from the LLM
32
+ */
33
+ function mergeFileChanges(originalFiles, parsedFiles) {
34
+ // Start with existing files
35
+ let changedFiles = Object.keys(originalFiles).reduce((acc, fileId) => {
36
+ const original = originalFiles[fileId];
37
+ const changedFile = parsedFiles.find((f) => f.name === original.name);
38
+ if (shouldDeleteFile(changedFile)) {
39
+ // Exclude from new set
40
+ return acc;
41
+ }
42
+ // If changedFile is present, use it; otherwise use original
43
+ acc[fileId] = {
44
+ ...original,
45
+ text: changedFile ? changedFile.text : original.text,
46
+ };
47
+ return acc;
48
+ }, {});
49
+ // Handle newly-created files
50
+ parsedFiles.forEach((changedFile) => {
51
+ const existingFile = Object.values(changedFiles).find((file) => file.name === changedFile.name);
52
+ // If no existing file and not empty => it's a new file
53
+ if (!existingFile && !shouldDeleteFile(changedFile)) {
54
+ const newFileId = (0, viz_utils_1.generateVizFileId)();
55
+ changedFiles[newFileId] = {
56
+ name: changedFile.name,
57
+ text: changedFile.text,
58
+ };
59
+ }
60
+ });
61
+ return changedFiles;
62
+ }
package/dist/index.d.ts CHANGED
@@ -1,25 +1,9 @@
1
- import { VizFiles } from "@vizhub/viz-types";
2
- export interface PerformAiEditParams {
3
- prompt: string;
4
- modelName: string;
5
- files: VizFiles;
6
- apiKey: string;
7
- baseURL: string | undefined;
8
- }
9
- export interface PerformAiEditResult {
10
- changedFiles: VizFiles;
11
- openRouterGenerationId: string;
12
- upstreamCostCents: number;
13
- provider: string;
14
- inputTokens: number;
15
- outputTokens: number;
16
- promptTemplateVersion: number;
17
- }
1
+ import { PerformAiEditParams, PerformAiEditResult } from "./types";
18
2
  /**
19
3
  * Core AI logic for:
20
4
  * - Building the prompt (including context)
21
- * - Calling the LLM (ChatOpenAI)
5
+ * - Calling the provided LLM function
22
6
  * - Parsing and merging file changes
23
7
  * - Retrieving cost metadata
24
8
  */
25
- export declare function performAiEdit({ prompt, modelName, files, apiKey, baseURL, }: PerformAiEditParams): Promise<PerformAiEditResult>;
9
+ export declare function performAiEdit({ prompt, files, llmFunction, apiKey, }: PerformAiEditParams): Promise<PerformAiEditResult>;